Clean up Storage code
[WebKit-https.git] / Source / WebKit / 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 "StorageTracker.h"
27
28 #include "StorageThread.h"
29 #include "StorageTrackerClient.h"
30 #include "WebStorageNamespaceProvider.h"
31 #include <WebCore/FileSystem.h>
32 #include <WebCore/PageGroup.h>
33 #include <WebCore/SQLiteDatabaseTracker.h>
34 #include <WebCore/SQLiteStatement.h>
35 #include <WebCore/SecurityOrigin.h>
36 #include <WebCore/TextEncoding.h>
37 #include <wtf/MainThread.h>
38 #include <wtf/StdLibExtras.h>
39 #include <wtf/Vector.h>
40 #include <wtf/text/CString.h>
41
42 #if PLATFORM(IOS)
43 #include <sqlite3_private.h>
44 #endif
45
46 using namespace WebCore;
47
48 namespace WebKit {
49
50 static StorageTracker* storageTracker = nullptr;
51
52 // If there is no document referencing a storage database, close the underlying database
53 // after it has been idle for m_StorageDatabaseIdleInterval seconds.
54 static const double DefaultStorageDatabaseIdleInterval = 300;
55     
56 void StorageTracker::initializeTracker(const String& storagePath, StorageTrackerClient* client)
57 {
58     ASSERT(isMainThread());
59     ASSERT(!storageTracker || !storageTracker->m_client);
60     
61     if (!storageTracker)
62         storageTracker = new StorageTracker(storagePath);
63     
64     storageTracker->m_client = client;
65     storageTracker->m_needsInitialization = true;
66 }
67
68 void StorageTracker::internalInitialize()
69 {
70     m_needsInitialization = false;
71
72     ASSERT(isMainThread());
73
74     // Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead.
75     // FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding?
76     UTF8Encoding();
77     
78     storageTracker->setIsActive(true);
79     storageTracker->m_thread->start();  
80     storageTracker->importOriginIdentifiers();
81 }
82
83 StorageTracker& StorageTracker::tracker()
84 {
85     if (!storageTracker)
86         storageTracker = new StorageTracker("");
87     if (storageTracker->m_needsInitialization)
88         storageTracker->internalInitialize();
89     
90     return *storageTracker;
91 }
92
93 StorageTracker::StorageTracker(const String& storagePath)
94     : m_storageDirectoryPath(storagePath.isolatedCopy())
95     , m_client(0)
96     , m_thread(std::make_unique<StorageThread>())
97     , m_isActive(false)
98     , m_needsInitialization(false)
99     , m_StorageDatabaseIdleInterval(DefaultStorageDatabaseIdleInterval)
100 {
101 }
102
103 void StorageTracker::setDatabaseDirectoryPath(const String& path)
104 {
105     LockHolder locker(m_databaseMutex);
106
107     if (m_database.isOpen())
108         m_database.close();
109
110     m_storageDirectoryPath = path.isolatedCopy();
111
112     {
113         LockHolder locker(m_originSetMutex);
114         m_originSet.clear();
115     }
116
117     if (!m_isActive)
118         return;
119
120     importOriginIdentifiers();
121 }
122
123 String StorageTracker::databaseDirectoryPath() const
124 {
125     return m_storageDirectoryPath.isolatedCopy();
126 }
127
128 String StorageTracker::trackerDatabasePath()
129 {
130     ASSERT(!m_databaseMutex.tryLock());
131     return pathByAppendingComponent(m_storageDirectoryPath, "StorageTracker.db");
132 }
133
134 static bool ensureDatabaseFileExists(const String& fileName, bool createIfDoesNotExist)
135 {
136     if (createIfDoesNotExist)
137         return makeAllDirectories(directoryName(fileName));
138
139     return fileExists(fileName);
140 }
141
142 void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
143 {
144     ASSERT(m_isActive);
145     ASSERT(!isMainThread());
146
147     SQLiteTransactionInProgressAutoCounter transactionCounter;
148
149     ASSERT(!m_databaseMutex.tryLock());
150
151     if (m_database.isOpen())
152         return;
153     
154     String databasePath = trackerDatabasePath();
155     
156     if (!ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
157         if (createIfDoesNotExist)
158             LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
159         return;
160     }
161     
162     if (!m_database.open(databasePath)) {
163         LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
164         return;
165     }
166     
167     m_database.disableThreadingChecks();
168     
169     if (!m_database.tableExists("Origins")) {
170         if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"))
171             LOG_ERROR("Failed to create Origins table.");
172     }
173 }
174
175 void StorageTracker::importOriginIdentifiers()
176 {   
177     if (!m_isActive)
178         return;
179     
180     ASSERT(isMainThread());
181     ASSERT(m_thread);
182
183     m_thread->dispatch([this] {
184         syncImportOriginIdentifiers();
185     });
186 }
187
188 void StorageTracker::finishedImportingOriginIdentifiers()
189 {
190     LockHolder locker(m_databaseMutex);
191     if (m_client)
192         m_client->didFinishLoadingOrigins();
193 }
194
195 void StorageTracker::syncImportOriginIdentifiers()
196 {
197     ASSERT(m_isActive);
198     
199     ASSERT(!isMainThread());
200
201     {
202         LockHolder locker(m_databaseMutex);
203
204         // Don't force creation of StorageTracker's db just because a tracker
205         // was initialized. It will be created if local storage dbs are found
206         // by syncFileSystemAndTrackerDatabse() or the next time a local storage
207         // db is created by StorageAreaSync.
208         openTrackerDatabase(false);
209
210         if (m_database.isOpen()) {
211             SQLiteTransactionInProgressAutoCounter transactionCounter;
212
213             SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
214             if (statement.prepare() != SQLITE_OK) {
215                 LOG_ERROR("Failed to prepare statement.");
216                 return;
217             }
218             
219             int result;
220             
221             {
222                 LockHolder lockOrigins(m_originSetMutex);
223                 while ((result = statement.step()) == SQLITE_ROW)
224                     m_originSet.add(statement.getColumnText(0).isolatedCopy());
225             }
226             
227             if (result != SQLITE_DONE) {
228                 LOG_ERROR("Failed to read in all origins from the database.");
229                 return;
230             }
231         }
232     }
233     
234     syncFileSystemAndTrackerDatabase();
235     
236     {
237         LockHolder locker(m_clientMutex);
238
239         if (m_client) {
240             LockHolder locker(m_originSetMutex);
241             OriginSet::const_iterator end = m_originSet.end();
242             for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
243                 m_client->dispatchDidModifyOrigin(*it);
244         }
245     }
246
247     callOnMainThread([this] {
248         finishedImportingOriginIdentifiers();
249     });
250 }
251     
252 void StorageTracker::syncFileSystemAndTrackerDatabase()
253 {
254     ASSERT(!isMainThread());
255
256     SQLiteTransactionInProgressAutoCounter transactionCounter;
257
258     ASSERT(m_isActive);
259
260     Vector<String> paths;
261     {
262         LockHolder locker(m_databaseMutex);
263         paths = listDirectory(m_storageDirectoryPath, "*.localstorage");
264     }
265
266     // Use a copy of m_originSet to find expired entries and to schedule their
267     // deletions from disk and from m_originSet.
268     OriginSet originSetCopy;
269     {
270         LockHolder locker(m_originSetMutex);
271         for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
272             originSetCopy.add((*it).isolatedCopy());
273     }
274     
275     // Add missing StorageTracker records.
276     OriginSet foundOrigins;
277     String fileExtension = ASCIILiteral(".localstorage");
278
279     for (Vector<String>::const_iterator it = paths.begin(), end = paths.end(); it != end; ++it) {
280         const String& path = *it;
281
282         if (path.length() > fileExtension.length() && path.endsWith(fileExtension)) {
283             String file = pathGetFileName(path);
284             String originIdentifier = file.substring(0, file.length() - fileExtension.length());
285             if (!originSetCopy.contains(originIdentifier))
286                 syncSetOriginDetails(originIdentifier, path);
287
288             foundOrigins.add(originIdentifier);
289         }
290     }
291
292     // Delete stale StorageTracker records.
293     for (OriginSet::const_iterator it = originSetCopy.begin(), end = originSetCopy.end(); it != end; ++it) {
294         const String& originIdentifier = *it;
295         if (foundOrigins.contains(originIdentifier))
296             continue;
297
298         callOnMainThread([this, originIdentifier = originIdentifier.isolatedCopy()] {
299             deleteOriginWithIdentifier(originIdentifier);
300         });
301     }
302 }
303
304 void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
305 {
306     if (!m_isActive)
307         return;
308
309     {
310         LockHolder locker(m_originSetMutex);
311
312         if (m_originSet.contains(originIdentifier))
313             return;
314
315         m_originSet.add(originIdentifier);
316     }
317
318     auto function = [this, originIdentifier = originIdentifier.isolatedCopy(), databaseFile = databaseFile.isolatedCopy()] {
319         syncSetOriginDetails(originIdentifier, databaseFile);
320     };
321
322     if (isMainThread()) {
323         ASSERT(m_thread);
324         m_thread->dispatch(WTFMove(function));
325     } else {
326         // FIXME: This weird ping-ponging was done to fix a deadlock. We should figure out a cleaner way to avoid it instead.
327         callOnMainThread([this, function = WTFMove(function)]() mutable {
328             m_thread->dispatch(WTFMove(function));
329         });
330     }
331 }
332
333 void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
334 {
335     ASSERT(!isMainThread());
336
337     SQLiteTransactionInProgressAutoCounter transactionCounter;
338
339     LockHolder locker(m_databaseMutex);
340
341     openTrackerDatabase(true);
342     
343     if (!m_database.isOpen())
344         return;
345
346     SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
347     if (statement.prepare() != SQLITE_OK) {
348         LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
349         return;
350     } 
351     
352     statement.bindText(1, originIdentifier);
353     statement.bindText(2, databaseFile);
354     
355     if (statement.step() != SQLITE_DONE)
356         LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
357
358     {
359         LockHolder locker(m_originSetMutex);
360         if (!m_originSet.contains(originIdentifier))
361             m_originSet.add(originIdentifier);
362     }
363
364     {
365         LockHolder locker(m_clientMutex);
366         if (m_client)
367             m_client->dispatchDidModifyOrigin(originIdentifier);
368     }
369 }
370
371 void StorageTracker::origins(Vector<RefPtr<SecurityOrigin>>& result)
372 {
373     ASSERT(m_isActive);
374     
375     if (!m_isActive)
376         return;
377
378     LockHolder locker(m_originSetMutex);
379
380     for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
381         result.append(SecurityOrigin::createFromDatabaseIdentifier(*it));
382 }
383
384 void StorageTracker::deleteAllOrigins()
385 {
386     ASSERT(m_isActive);
387     ASSERT(isMainThread());
388     ASSERT(m_thread);
389     
390     if (!m_isActive)
391         return;
392
393     {
394         LockHolder locker(m_originSetMutex);
395         willDeleteAllOrigins();
396         m_originSet.clear();
397     }
398
399     WebStorageNamespaceProvider::clearLocalStorageForAllOrigins();
400
401     m_thread->dispatch([this] {
402         syncDeleteAllOrigins();
403     });
404 }
405
406 #if PLATFORM(IOS)
407 static void truncateDatabaseFile(SQLiteDatabase& database)
408 {
409     sqlite3_file_control(database.sqlite3Handle(), 0, SQLITE_TRUNCATE_DATABASE, 0);
410 }
411 #endif
412
413 void StorageTracker::syncDeleteAllOrigins()
414 {
415     ASSERT(!isMainThread());
416
417     SQLiteTransactionInProgressAutoCounter transactionCounter;
418     
419     LockHolder locker(m_databaseMutex);
420     
421     openTrackerDatabase(false);
422     if (!m_database.isOpen())
423         return;
424     
425     SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
426     if (statement.prepare() != SQLITE_OK) {
427         LOG_ERROR("Failed to prepare statement.");
428         return;
429     }
430     
431     int result;
432     while ((result = statement.step()) == SQLITE_ROW) {
433         if (!canDeleteOrigin(statement.getColumnText(0)))
434             continue;
435
436         deleteFile(statement.getColumnText(1));
437
438         {
439             LockHolder locker(m_clientMutex);
440             if (m_client)
441                 m_client->dispatchDidModifyOrigin(statement.getColumnText(0));
442         }
443     }
444     
445     if (result != SQLITE_DONE)
446         LOG_ERROR("Failed to read in all origins from the database.");
447
448     if (m_database.isOpen()) {
449 #if PLATFORM(IOS)
450         truncateDatabaseFile(m_database);
451 #endif
452         m_database.close();
453     }
454
455 #if !PLATFORM(IOS)
456     if (!deleteFile(trackerDatabasePath())) {
457         // In the case where it is not possible to delete the database file (e.g some other program
458         // like a virus scanner is accessing it), make sure to remove all entries.
459         openTrackerDatabase(false);
460         if (!m_database.isOpen())
461             return;
462         SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
463         if (deleteStatement.prepare() != SQLITE_OK) {
464             LOG_ERROR("Unable to prepare deletion of all origins");
465             return;
466         }
467         if (!deleteStatement.executeCommand()) {
468             LOG_ERROR("Unable to execute deletion of all origins");
469             return;
470         }
471     }
472     deleteEmptyDirectory(m_storageDirectoryPath);
473 #endif
474 }
475
476 void StorageTracker::deleteOriginWithIdentifier(const String& originIdentifier)
477 {
478     deleteOrigin(&SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get());
479 }
480
481 void StorageTracker::deleteOrigin(SecurityOrigin* origin)
482 {    
483     ASSERT(m_isActive);
484     ASSERT(isMainThread());
485     ASSERT(m_thread);
486     
487     if (!m_isActive)
488         return;
489
490     // Before deleting database, we need to clear in-memory local storage data
491     // in StorageArea, and to close the StorageArea db. It's possible for an
492     // item to be added immediately after closing the db and cause StorageAreaSync
493     // to reopen the db before the db is deleted by a StorageTracker thread.
494     // In this case, reopening the db in StorageAreaSync will cancel a pending
495     // StorageTracker db deletion.
496     WebStorageNamespaceProvider::clearLocalStorageForOrigin(origin);
497
498     String originId = origin->databaseIdentifier();
499     
500     {
501         LockHolder locker(m_originSetMutex);
502         willDeleteOrigin(originId);
503         m_originSet.remove(originId);
504     }
505
506     m_thread->dispatch([this, originId = originId.isolatedCopy()] {
507         syncDeleteOrigin(originId);
508     });
509 }
510
511 void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
512 {
513     ASSERT(!isMainThread());
514
515     SQLiteTransactionInProgressAutoCounter transactionCounter;
516
517     LockHolder locker(m_databaseMutex);
518     
519     if (!canDeleteOrigin(originIdentifier)) {
520         LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
521         return;
522     }
523     
524     openTrackerDatabase(false);
525     if (!m_database.isOpen())
526         return;
527
528     String path = databasePathForOrigin(originIdentifier);
529     if (path.isEmpty()) {
530         // It is possible to get a request from the API to delete the storage for an origin that
531         // has no such storage.
532         return;
533     }
534     
535     SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?");
536     if (deleteStatement.prepare() != SQLITE_OK) {
537         LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
538         return;
539     }
540     deleteStatement.bindText(1, originIdentifier);
541     if (!deleteStatement.executeCommand()) {
542         LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
543         return;
544     }
545
546     deleteFile(path);
547     
548     bool shouldDeleteTrackerFiles = false;
549     {
550         LockHolder locker(m_originSetMutex);
551         m_originSet.remove(originIdentifier);
552         shouldDeleteTrackerFiles = m_originSet.isEmpty();
553     }
554
555     if (shouldDeleteTrackerFiles) {
556 #if PLATFORM(IOS)
557         truncateDatabaseFile(m_database);
558 #endif
559         m_database.close();
560 #if !PLATFORM(IOS)
561         deleteFile(trackerDatabasePath());
562         deleteEmptyDirectory(m_storageDirectoryPath);
563 #endif
564     }
565
566     {
567         LockHolder locker(m_clientMutex);
568         if (m_client)
569             m_client->dispatchDidModifyOrigin(originIdentifier);
570     }
571 }
572     
573 void StorageTracker::willDeleteAllOrigins()
574 {
575     ASSERT(!m_originSetMutex.tryLock());
576
577     OriginSet::const_iterator end = m_originSet.end();
578     for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
579         m_originsBeingDeleted.add((*it).isolatedCopy());
580 }
581
582 void StorageTracker::willDeleteOrigin(const String& originIdentifier)
583 {
584     ASSERT(isMainThread());
585     ASSERT(!m_originSetMutex.tryLock());
586
587     m_originsBeingDeleted.add(originIdentifier);
588 }
589     
590 bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
591 {
592     ASSERT(!m_databaseMutex.tryLock());
593     LockHolder locker(m_originSetMutex);
594     return m_originsBeingDeleted.contains(originIdentifier);
595 }
596
597 void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
598 {
599     if (!m_isActive)
600         return;
601
602     LockHolder locker(m_databaseMutex);
603     {
604         LockHolder locker(m_originSetMutex);
605         if (!m_originsBeingDeleted.isEmpty())
606             m_originsBeingDeleted.remove(originIdentifier);
607     }
608 }
609
610 bool StorageTracker::isActive()
611 {
612     return m_isActive;
613 }
614
615 void StorageTracker::setIsActive(bool flag)
616 {
617     m_isActive = flag;
618 }
619     
620 String StorageTracker::databasePathForOrigin(const String& originIdentifier)
621 {
622     ASSERT(!m_databaseMutex.tryLock());
623     ASSERT(m_isActive);
624     
625     if (!m_database.isOpen())
626         return String();
627
628     SQLiteTransactionInProgressAutoCounter transactionCounter;
629
630     SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?");
631     if (pathStatement.prepare() != SQLITE_OK) {
632         LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data());
633         return String();
634     }
635     pathStatement.bindText(1, originIdentifier);
636     int result = pathStatement.step();
637     if (result != SQLITE_ROW)
638         return String();
639
640     return pathStatement.getColumnText(0);
641 }
642     
643 long long StorageTracker::diskUsageForOrigin(SecurityOrigin* origin)
644 {
645     if (!m_isActive)
646         return 0;
647
648     LockHolder locker(m_databaseMutex);
649
650     String path = databasePathForOrigin(origin->databaseIdentifier());
651     if (path.isEmpty())
652         return 0;
653
654     long long size;
655     return getFileSize(path, size) ? size : 0;
656 }
657
658 } // namespace WebCore