1/*
2 * Copyright (C) 2007, 2008, 2012, 2013 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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "DatabaseTracker.h"
31
32#include "Database.h"
33#include "DatabaseContext.h"
34#include "DatabaseManager.h"
35#include "DatabaseManagerClient.h"
36#include "DatabaseThread.h"
37#include "Logging.h"
38#include "OriginLock.h"
39#include "SecurityOrigin.h"
40#include "SecurityOriginData.h"
41#include "SecurityOriginHash.h"
42#include "SQLiteFileSystem.h"
43#include "SQLiteStatement.h"
44#include "SQLiteTransaction.h"
45#include <wtf/FileSystem.h>
46#include <wtf/MainThread.h>
47#include <wtf/NeverDestroyed.h>
48#include <wtf/StdLibExtras.h>
49#include <wtf/UUID.h>
50#include <wtf/text/CString.h>
51#include <wtf/text/StringBuilder.h>
52
53#if PLATFORM(IOS_FAMILY)
54#include "WebCoreThread.h"
55#endif
56
57namespace WebCore {
58
59static Vector<String> isolatedCopy(const Vector<String>& original)
60{
61 Vector<String> copy;
62 copy.reserveInitialCapacity(original.size());
63 for (auto& string : original)
64 copy.uncheckedAppend(string.isolatedCopy());
65 return copy;
66}
67
68std::unique_ptr<DatabaseTracker> DatabaseTracker::trackerWithDatabasePath(const String& databasePath)
69{
70 return std::unique_ptr<DatabaseTracker>(new DatabaseTracker(databasePath));
71}
72
73static DatabaseTracker* staticTracker = nullptr;
74
75void DatabaseTracker::initializeTracker(const String& databasePath)
76{
77 ASSERT(!staticTracker);
78 if (staticTracker)
79 return;
80 staticTracker = new DatabaseTracker(databasePath);
81}
82
83bool DatabaseTracker::isInitialized()
84{
85 return !!staticTracker;
86}
87
88DatabaseTracker& DatabaseTracker::singleton()
89{
90 if (!staticTracker)
91 staticTracker = new DatabaseTracker(emptyString());
92 return *staticTracker;
93}
94
95DatabaseTracker::DatabaseTracker(const String& databasePath)
96 : m_databaseDirectoryPath(databasePath.isolatedCopy())
97{
98}
99
100String DatabaseTracker::trackerDatabasePath() const
101{
102 return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.isolatedCopy(), "Databases.db");
103}
104
105void DatabaseTracker::openTrackerDatabase(TrackerCreationAction createAction)
106{
107 ASSERT(!m_databaseGuard.tryLock());
108
109 if (m_database.isOpen())
110 return;
111
112 // If createIfDoesNotExist is false, SQLiteFileSystem::ensureDatabaseFileExists()
113 // will return false if the database file does not exist.
114 // If createIfDoesNotExist is true, SQLiteFileSystem::ensureDatabaseFileExists()
115 // will attempt to create the path to the database file if it does not
116 // exists yet. It'll return true if the path already exists, or if it
117 // successfully creates the path. Else, it will return false.
118 String databasePath = trackerDatabasePath();
119 if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createAction == CreateIfDoesNotExist))
120 return;
121
122 if (!m_database.open(databasePath)) {
123 // FIXME: What do do here?
124 LOG_ERROR("Failed to open databasePath %s.", databasePath.utf8().data());
125 return;
126 }
127 m_database.disableThreadingChecks();
128
129 if (!m_database.tableExists("Origins")) {
130 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
131 // FIXME: and here
132 LOG_ERROR("Failed to create Origins table");
133 }
134 }
135
136 if (!m_database.tableExists("Databases")) {
137 if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
138 // FIXME: and here
139 LOG_ERROR("Failed to create Databases table");
140 }
141 }
142}
143
144ExceptionOr<void> DatabaseTracker::hasAdequateQuotaForOrigin(const SecurityOriginData& origin, unsigned long long estimatedSize)
145{
146 ASSERT(!m_databaseGuard.tryLock());
147 auto usage = this->usage(origin);
148
149 // If the database will fit, allow its creation.
150 auto requirement = usage + std::max<unsigned long long>(1, estimatedSize);
151 if (requirement < usage) {
152 // The estimated size is so big it causes an overflow; don't allow creation.
153 return Exception { SecurityError };
154 }
155 if (requirement > quotaNoLock(origin))
156 return Exception { QuotaExceededError };
157 return { };
158}
159
160ExceptionOr<void> DatabaseTracker::canEstablishDatabase(DatabaseContext& context, const String& name, unsigned long long estimatedSize)
161{
162 LockHolder lockDatabase(m_databaseGuard);
163
164 // FIXME: What guarantees this context.securityOrigin() is non-null?
165 auto origin = context.securityOrigin();
166
167 if (isDeletingDatabaseOrOriginFor(origin, name))
168 return Exception { SecurityError };
169
170 recordCreatingDatabase(origin, name);
171
172 // If a database already exists, ignore the passed-in estimated size and say it's OK.
173 if (hasEntryForDatabase(origin, name))
174 return { };
175
176 auto result = hasAdequateQuotaForOrigin(origin, estimatedSize);
177 if (!result.hasException())
178 return { };
179
180 // If we get here, then we do not have enough quota for one of the
181 // following reasons as indicated by the set error:
182 //
183 // If the error is DatabaseSizeOverflowed, then this means the requested
184 // estimatedSize if so unreasonably large that it can cause an overflow in
185 // the usage budget computation. In that case, there's nothing more we can
186 // do, and there's no need for a retry. Hence, we should indicate that
187 // we're done with our attempt to create the database.
188 //
189 // If the error is DatabaseSizeExceededQuota, then we'll give the client
190 // a chance to update the quota and call retryCanEstablishDatabase() to try
191 // again. Hence, we don't call doneCreatingDatabase() yet in that case.
192
193 auto exception = result.releaseException();
194 if (exception.code() != QuotaExceededError)
195 doneCreatingDatabase(origin, name);
196
197 return exception;
198}
199
200// Note: a thought about performance: hasAdequateQuotaForOrigin() was also
201// called in canEstablishDatabase(), and hence, we're repeating some work within
202// hasAdequateQuotaForOrigin(). However, retryCanEstablishDatabase() should only
203// be called in the rare even if canEstablishDatabase() fails. Since it is rare,
204// we should not bother optimizing it. It is more beneficial to keep
205// hasAdequateQuotaForOrigin() simple and correct (i.e. bug free), and just
206// re-use it. Also note that the path for opening a database involves IO, and
207// hence should not be a performance critical path anyway.
208ExceptionOr<void> DatabaseTracker::retryCanEstablishDatabase(DatabaseContext& context, const String& name, unsigned long long estimatedSize)
209{
210 LockHolder lockDatabase(m_databaseGuard);
211
212 // FIXME: What guarantees context.securityOrigin() is non-null?
213 auto origin = context.securityOrigin();
214
215 // We have already eliminated other types of errors in canEstablishDatabase().
216 // The only reason we're in retryCanEstablishDatabase() is because we gave
217 // the client a chance to update the quota and are rechecking it here.
218 // If we fail this check, the only possible reason this time should be due
219 // to inadequate quota.
220 auto result = hasAdequateQuotaForOrigin(origin, estimatedSize);
221 if (!result.hasException())
222 return { };
223
224 auto exception = result.releaseException();
225 ASSERT(exception.code() == QuotaExceededError);
226 doneCreatingDatabase(origin, name);
227
228 return exception;
229}
230
231bool DatabaseTracker::hasEntryForOriginNoLock(const SecurityOriginData& origin)
232{
233 ASSERT(!m_databaseGuard.tryLock());
234 openTrackerDatabase(DontCreateIfDoesNotExist);
235 if (!m_database.isOpen())
236 return false;
237
238 SQLiteStatement statement(m_database, "SELECT origin FROM Origins where origin=?;");
239 if (statement.prepare() != SQLITE_OK) {
240 LOG_ERROR("Failed to prepare statement.");
241 return false;
242 }
243
244 statement.bindText(1, origin.databaseIdentifier());
245
246 return statement.step() == SQLITE_ROW;
247}
248
249bool DatabaseTracker::hasEntryForDatabase(const SecurityOriginData& origin, const String& databaseIdentifier)
250{
251 ASSERT(!m_databaseGuard.tryLock());
252 openTrackerDatabase(DontCreateIfDoesNotExist);
253 if (!m_database.isOpen()) {
254 // No "tracker database". Hence, no entry for the database of interest.
255 return false;
256 }
257
258 // We've got a tracker database. Set up a query to ask for the db of interest:
259 SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
260
261 if (statement.prepare() != SQLITE_OK)
262 return false;
263
264 statement.bindText(1, origin.databaseIdentifier());
265 statement.bindText(2, databaseIdentifier);
266
267 return statement.step() == SQLITE_ROW;
268}
269
270unsigned long long DatabaseTracker::maximumSize(Database& database)
271{
272 // The maximum size for a database is the full quota for its origin, minus the current usage within the origin,
273 // plus the current usage of the given database
274 LockHolder lockDatabase(m_databaseGuard);
275 auto origin = database.securityOrigin();
276
277 unsigned long long quota = quotaNoLock(origin);
278 unsigned long long diskUsage = usage(origin);
279 unsigned long long databaseFileSize = SQLiteFileSystem::getDatabaseFileSize(database.fileName());
280 ASSERT(databaseFileSize <= diskUsage);
281
282 if (diskUsage > quota)
283 return databaseFileSize;
284
285 // A previous error may have allowed the origin to exceed its quota, or may
286 // have allowed this database to exceed our cached estimate of the origin
287 // disk usage. Don't multiply that error through integer underflow, or the
288 // effective quota will permanently become 2^64.
289 unsigned long long maxSize = quota - diskUsage + databaseFileSize;
290 if (maxSize > quota)
291 maxSize = databaseFileSize;
292 return maxSize;
293}
294
295void DatabaseTracker::closeAllDatabases(CurrentQueryBehavior currentQueryBehavior)
296{
297 for (auto& database : openDatabases()) {
298 if (currentQueryBehavior == CurrentQueryBehavior::Interrupt)
299 database->interrupt();
300 database->close();
301 }
302}
303
304String DatabaseTracker::originPath(const SecurityOriginData& origin) const
305{
306 return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.isolatedCopy(), origin.databaseIdentifier());
307}
308
309static String generateDatabaseFileName()
310{
311 StringBuilder stringBuilder;
312
313 stringBuilder.append(createCanonicalUUIDString());
314 stringBuilder.appendLiteral(".db");
315
316 return stringBuilder.toString();
317}
318
319String DatabaseTracker::fullPathForDatabaseNoLock(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
320{
321 ASSERT(!m_databaseGuard.tryLock());
322
323 String originIdentifier = origin.databaseIdentifier();
324 String originPath = this->originPath(origin);
325
326 // Make sure the path for this SecurityOrigin exists
327 if (createIfNotExists && !SQLiteFileSystem::ensureDatabaseDirectoryExists(originPath))
328 return String();
329
330 // See if we have a path for this database yet
331 if (!m_database.isOpen())
332 return String();
333 SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
334
335 if (statement.prepare() != SQLITE_OK)
336 return String();
337
338 statement.bindText(1, originIdentifier);
339 statement.bindText(2, name);
340
341 int result = statement.step();
342
343 if (result == SQLITE_ROW)
344 return SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, statement.getColumnText(0));
345 if (!createIfNotExists)
346 return String();
347
348 if (result != SQLITE_DONE) {
349 LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", originIdentifier.utf8().data(), name.utf8().data());
350 return String();
351 }
352 statement.finalize();
353
354 String fileName = generateDatabaseFileName();
355
356 if (!addDatabase(origin, name, fileName))
357 return String();
358
359 // If this origin's quota is being tracked (open handle to a database in this origin), add this new database
360 // to the quota manager now
361 String fullFilePath = SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, fileName);
362
363 return fullFilePath;
364}
365
366String DatabaseTracker::fullPathForDatabase(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
367{
368 LockHolder lockDatabase(m_databaseGuard);
369 return fullPathForDatabaseNoLock(origin, name, createIfNotExists).isolatedCopy();
370}
371
372Vector<SecurityOriginData> DatabaseTracker::origins()
373{
374 LockHolder lockDatabase(m_databaseGuard);
375
376 openTrackerDatabase(DontCreateIfDoesNotExist);
377 if (!m_database.isOpen())
378 return { };
379
380 SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
381 if (statement.prepare() != SQLITE_OK) {
382 LOG_ERROR("Failed to prepare statement.");
383 return { };
384 }
385
386 Vector<SecurityOriginData> origins;
387 int stepResult;
388 while ((stepResult = statement.step()) == SQLITE_ROW)
389 origins.append(SecurityOriginData::fromDatabaseIdentifier(statement.getColumnText(0))->isolatedCopy());
390 origins.shrinkToFit();
391
392 if (stepResult != SQLITE_DONE)
393 LOG_ERROR("Failed to read in all origins from the database.");
394
395 return origins;
396}
397
398Vector<String> DatabaseTracker::databaseNamesNoLock(const SecurityOriginData& origin)
399{
400 ASSERT(!m_databaseGuard.tryLock());
401 openTrackerDatabase(DontCreateIfDoesNotExist);
402 if (!m_database.isOpen())
403 return { };
404
405 SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
406 if (statement.prepare() != SQLITE_OK)
407 return { };
408
409 statement.bindText(1, origin.databaseIdentifier());
410
411 Vector<String> names;
412 int result;
413 while ((result = statement.step()) == SQLITE_ROW)
414 names.append(statement.getColumnText(0));
415 names.shrinkToFit();
416
417 if (result != SQLITE_DONE) {
418 LOG_ERROR("Failed to retrieve all database names for origin %s", origin.databaseIdentifier().utf8().data());
419 return { };
420 }
421
422 return names;
423}
424
425Vector<String> DatabaseTracker::databaseNames(const SecurityOriginData& origin)
426{
427 Vector<String> names;
428 {
429 LockHolder lockDatabase(m_databaseGuard);
430 names = databaseNamesNoLock(origin);
431 }
432 return isolatedCopy(names);
433}
434
435DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, const SecurityOriginData& origin)
436{
437 String originIdentifier = origin.databaseIdentifier();
438 String displayName;
439 int64_t expectedUsage;
440
441 {
442 LockHolder lockDatabase(m_databaseGuard);
443
444 openTrackerDatabase(DontCreateIfDoesNotExist);
445 if (!m_database.isOpen())
446 return DatabaseDetails();
447 SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
448 if (statement.prepare() != SQLITE_OK)
449 return DatabaseDetails();
450
451 statement.bindText(1, originIdentifier);
452 statement.bindText(2, name);
453
454 int result = statement.step();
455 if (result == SQLITE_DONE)
456 return DatabaseDetails();
457
458 if (result != SQLITE_ROW) {
459 LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.utf8().data(), originIdentifier.utf8().data());
460 return DatabaseDetails();
461 }
462 displayName = statement.getColumnText(0);
463 expectedUsage = statement.getColumnInt64(1);
464 }
465
466 String path = fullPathForDatabase(origin, name, false);
467 if (path.isEmpty())
468 return DatabaseDetails(name, displayName, expectedUsage, 0, WTF::nullopt, WTF::nullopt);
469 return DatabaseDetails(name, displayName, expectedUsage, SQLiteFileSystem::getDatabaseFileSize(path), SQLiteFileSystem::databaseCreationTime(path), SQLiteFileSystem::databaseModificationTime(path));
470}
471
472void DatabaseTracker::setDatabaseDetails(const SecurityOriginData& origin, const String& name, const String& displayName, unsigned long long estimatedSize)
473{
474 String originIdentifier = origin.databaseIdentifier();
475 int64_t guid = 0;
476
477 LockHolder lockDatabase(m_databaseGuard);
478
479 openTrackerDatabase(CreateIfDoesNotExist);
480 if (!m_database.isOpen())
481 return;
482 SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
483 if (statement.prepare() != SQLITE_OK)
484 return;
485
486 statement.bindText(1, originIdentifier);
487 statement.bindText(2, name);
488
489 int result = statement.step();
490 if (result == SQLITE_ROW)
491 guid = statement.getColumnInt64(0);
492 statement.finalize();
493
494 if (guid == 0) {
495 if (result != SQLITE_DONE)
496 LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.utf8().data(), originIdentifier.utf8().data());
497 else {
498 // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
499 // But since the tracker file is an external resource not under complete control of our code, it's somewhat invalid to make this an ASSERT case
500 // So we'll print an error instead
501 LOG_ERROR("Could not retrieve guid for database %s in origin %s from the tracker database - it is invalid to set database details on a database that doesn't already exist in the tracker", name.utf8().data(), originIdentifier.utf8().data());
502 }
503 return;
504 }
505
506 SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
507 if (updateStatement.prepare() != SQLITE_OK)
508 return;
509
510 updateStatement.bindText(1, displayName);
511 updateStatement.bindInt64(2, estimatedSize);
512 updateStatement.bindInt64(3, guid);
513
514 if (updateStatement.step() != SQLITE_DONE) {
515 LOG_ERROR("Failed to update details for database %s in origin %s", name.utf8().data(), originIdentifier.utf8().data());
516 return;
517 }
518
519 if (m_client)
520 m_client->dispatchDidModifyDatabase(origin, name);
521}
522
523void DatabaseTracker::doneCreatingDatabase(Database& database)
524{
525 LockHolder lockDatabase(m_databaseGuard);
526 doneCreatingDatabase(database.securityOrigin(), database.stringIdentifier());
527}
528
529Vector<Ref<Database>> DatabaseTracker::openDatabases()
530{
531 Vector<Ref<Database>> openDatabases;
532 {
533 LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
534
535 if (m_openDatabaseMap) {
536 for (auto& nameMap : m_openDatabaseMap->values()) {
537 for (auto& set : nameMap->values()) {
538 for (auto& database : *set)
539 openDatabases.append(*database);
540 }
541 }
542 }
543 }
544 return openDatabases;
545}
546
547void DatabaseTracker::addOpenDatabase(Database& database)
548{
549 LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
550
551 if (!m_openDatabaseMap)
552 m_openDatabaseMap = std::make_unique<DatabaseOriginMap>();
553
554 auto origin = database.securityOrigin();
555
556 auto* nameMap = m_openDatabaseMap->get(origin);
557 if (!nameMap) {
558 nameMap = new DatabaseNameMap;
559 m_openDatabaseMap->add(origin.isolatedCopy(), nameMap);
560 }
561
562 String name = database.stringIdentifier();
563 auto* databaseSet = nameMap->get(name);
564 if (!databaseSet) {
565 databaseSet = new DatabaseSet;
566 nameMap->set(name.isolatedCopy(), databaseSet);
567 }
568
569 databaseSet->add(&database);
570
571 LOG(StorageAPI, "Added open Database %s (%p)\n", database.stringIdentifier().utf8().data(), &database);
572}
573
574void DatabaseTracker::removeOpenDatabase(Database& database)
575{
576 LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
577
578 if (!m_openDatabaseMap) {
579 ASSERT_NOT_REACHED();
580 return;
581 }
582
583 DatabaseNameMap* nameMap = m_openDatabaseMap->get(database.securityOrigin());
584 if (!nameMap) {
585 ASSERT_NOT_REACHED();
586 return;
587 }
588
589 String name = database.stringIdentifier();
590 auto* databaseSet = nameMap->get(name);
591 if (!databaseSet) {
592 ASSERT_NOT_REACHED();
593 return;
594 }
595
596 databaseSet->remove(&database);
597
598 LOG(StorageAPI, "Removed open Database %s (%p)\n", database.stringIdentifier().utf8().data(), &database);
599
600 if (!databaseSet->isEmpty())
601 return;
602
603 nameMap->remove(name);
604 delete databaseSet;
605
606 if (!nameMap->isEmpty())
607 return;
608
609 m_openDatabaseMap->remove(database.securityOrigin());
610 delete nameMap;
611}
612
613RefPtr<OriginLock> DatabaseTracker::originLockFor(const SecurityOriginData& origin)
614{
615 LockHolder lockDatabase(m_databaseGuard);
616 String databaseIdentifier = origin.databaseIdentifier();
617
618 // The originLockMap is accessed from multiple DatabaseThreads since
619 // different script contexts can be writing to different databases from
620 // the same origin. Hence, the databaseIdentifier key needs to be an
621 // isolated copy. An isolated copy gives us a value whose refCounting is
622 // thread-safe, since our copy is guarded by the m_databaseGuard mutex.
623 databaseIdentifier = databaseIdentifier.isolatedCopy();
624
625 OriginLockMap::AddResult addResult =
626 m_originLockMap.add(databaseIdentifier, RefPtr<OriginLock>());
627 if (!addResult.isNewEntry)
628 return addResult.iterator->value;
629
630 String path = originPath(origin);
631 RefPtr<OriginLock> lock = adoptRef(*new OriginLock(path));
632 ASSERT(lock);
633 addResult.iterator->value = lock;
634
635 return lock;
636}
637
638void DatabaseTracker::deleteOriginLockFor(const SecurityOriginData& origin)
639{
640 ASSERT(!m_databaseGuard.tryLock());
641
642 // There is not always an instance of an OriginLock associated with an origin.
643 // For example, if the OriginLock lock file was created by a previous run of
644 // the browser which has now terminated, and the current browser process
645 // has not executed any database transactions from this origin that would
646 // have created the OriginLock instance in memory. In this case, we will have
647 // a lock file but not an OriginLock instance in memory.
648
649 // This function is only called if we are already deleting all the database
650 // files in this origin. We'll give the OriginLock one chance to do an
651 // orderly clean up first when we remove its ref from the m_originLockMap.
652 // This may or may not be possible depending on whether other threads are
653 // also using the OriginLock at the same time. After that, we will delete the lock file.
654
655 m_originLockMap.remove(origin.databaseIdentifier());
656 OriginLock::deleteLockFile(originPath(origin));
657}
658
659unsigned long long DatabaseTracker::usage(const SecurityOriginData& origin)
660{
661 String originPath = this->originPath(origin);
662 unsigned long long diskUsage = 0;
663 for (auto& fileName : FileSystem::listDirectory(originPath, "*.db"_s))
664 diskUsage += SQLiteFileSystem::getDatabaseFileSize(fileName);
665 return diskUsage;
666}
667
668unsigned long long DatabaseTracker::quotaNoLock(const SecurityOriginData& origin)
669{
670 ASSERT(!m_databaseGuard.tryLock());
671 unsigned long long quota = 0;
672
673 openTrackerDatabase(DontCreateIfDoesNotExist);
674 if (!m_database.isOpen())
675 return quota;
676
677 SQLiteStatement statement(m_database, "SELECT quota FROM Origins where origin=?;");
678 if (statement.prepare() != SQLITE_OK) {
679 LOG_ERROR("Failed to prepare statement.");
680 return quota;
681 }
682 statement.bindText(1, origin.databaseIdentifier());
683
684 if (statement.step() == SQLITE_ROW)
685 quota = statement.getColumnInt64(0);
686
687 return quota;
688}
689
690unsigned long long DatabaseTracker::quota(const SecurityOriginData& origin)
691{
692 LockHolder lockDatabase(m_databaseGuard);
693 return quotaNoLock(origin);
694}
695
696void DatabaseTracker::setQuota(const SecurityOriginData& origin, unsigned long long quota)
697{
698 LockHolder lockDatabase(m_databaseGuard);
699
700 if (quotaNoLock(origin) == quota)
701 return;
702
703 openTrackerDatabase(CreateIfDoesNotExist);
704 if (!m_database.isOpen())
705 return;
706
707 bool insertedNewOrigin = false;
708
709 bool originEntryExists = hasEntryForOriginNoLock(origin);
710 if (!originEntryExists) {
711 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
712 if (statement.prepare() != SQLITE_OK) {
713 LOG_ERROR("Unable to establish origin %s in the tracker", origin.databaseIdentifier().utf8().data());
714 } else {
715 statement.bindText(1, origin.databaseIdentifier());
716 statement.bindInt64(2, quota);
717
718 if (statement.step() != SQLITE_DONE)
719 LOG_ERROR("Unable to establish origin %s in the tracker", origin.databaseIdentifier().utf8().data());
720 else
721 insertedNewOrigin = true;
722 }
723 } else {
724 SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
725 bool error = statement.prepare() != SQLITE_OK;
726 if (!error) {
727 statement.bindInt64(1, quota);
728 statement.bindText(2, origin.databaseIdentifier());
729
730 error = !statement.executeCommand();
731 }
732
733 if (error)
734 LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin.databaseIdentifier().utf8().data());
735 }
736
737 if (m_client) {
738 if (insertedNewOrigin)
739 m_client->dispatchDidAddNewOrigin();
740 m_client->dispatchDidModifyOrigin(origin);
741 }
742}
743
744bool DatabaseTracker::addDatabase(const SecurityOriginData& origin, const String& name, const String& path)
745{
746 ASSERT(!m_databaseGuard.tryLock());
747 openTrackerDatabase(CreateIfDoesNotExist);
748 if (!m_database.isOpen())
749 return false;
750
751 // New database should never be added until the origin has been established
752 ASSERT(hasEntryForOriginNoLock(origin));
753
754 SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
755
756 if (statement.prepare() != SQLITE_OK)
757 return false;
758
759 statement.bindText(1, origin.databaseIdentifier());
760 statement.bindText(2, name);
761 statement.bindText(3, path);
762
763 if (!statement.executeCommand()) {
764 LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.utf8().data(), origin.databaseIdentifier().utf8().data(), m_database.lastErrorMsg());
765 return false;
766 }
767
768 if (m_client)
769 m_client->dispatchDidModifyOrigin(origin);
770
771 return true;
772}
773
774void DatabaseTracker::deleteAllDatabasesImmediately()
775{
776 // This method is only intended for use by DumpRenderTree / WebKitTestRunner.
777 // Actually deleting the databases is necessary to reset to a known state before running
778 // each test case, but may be unsafe in deployment use cases (where multiple applications
779 // may be accessing the same databases concurrently).
780 for (auto& origin : origins())
781 deleteOrigin(origin, DeletionMode::Immediate);
782}
783
784void DatabaseTracker::deleteDatabasesModifiedSince(WallTime time)
785{
786 for (auto& origin : origins()) {
787 Vector<String> databaseNames = this->databaseNames(origin);
788 Vector<String> databaseNamesToDelete;
789 databaseNamesToDelete.reserveInitialCapacity(databaseNames.size());
790 for (const auto& databaseName : databaseNames) {
791 auto fullPath = fullPathForDatabase(origin, databaseName, false);
792
793 // If the file doesn't exist, we previously deleted it but failed to remove the information
794 // from the tracker database. We want to delete all of the information associated with this
795 // database from the tracker database, so still add its name to databaseNamesToDelete.
796 if (FileSystem::fileExists(fullPath)) {
797 auto modificationTime = FileSystem::getFileModificationTime(fullPath);
798 if (!modificationTime)
799 continue;
800
801 if (modificationTime.value() < time)
802 continue;
803 }
804
805 databaseNamesToDelete.uncheckedAppend(databaseName);
806 }
807
808 if (databaseNames.size() == databaseNamesToDelete.size())
809 deleteOrigin(origin);
810 else {
811 for (const auto& databaseName : databaseNamesToDelete)
812 deleteDatabase(origin, databaseName);
813 }
814 }
815}
816
817// It is the caller's responsibility to make sure that nobody is trying to create, delete, open, or close databases in this origin while the deletion is
818// taking place.
819bool DatabaseTracker::deleteOrigin(const SecurityOriginData& origin)
820{
821 return deleteOrigin(origin, DeletionMode::Default);
822}
823
824bool DatabaseTracker::deleteOrigin(const SecurityOriginData& origin, DeletionMode deletionMode)
825{
826 Vector<String> databaseNames;
827 {
828 LockHolder lockDatabase(m_databaseGuard);
829 openTrackerDatabase(DontCreateIfDoesNotExist);
830 if (!m_database.isOpen())
831 return false;
832
833 databaseNames = databaseNamesNoLock(origin);
834 if (databaseNames.isEmpty())
835 LOG_ERROR("Unable to retrieve list of database names for origin %s", origin.databaseIdentifier().utf8().data());
836
837 if (!canDeleteOrigin(origin)) {
838 LOG_ERROR("Tried to delete an origin (%s) while either creating database in it or already deleting it", origin.databaseIdentifier().utf8().data());
839 ASSERT_NOT_REACHED();
840 return false;
841 }
842 recordDeletingOrigin(origin);
843 }
844
845 // We drop the lock here because holding locks during a call to deleteDatabaseFile will deadlock.
846 bool failedToDeleteAnyDatabaseFile = false;
847 for (auto& name : databaseNames) {
848 if (FileSystem::fileExists(fullPathForDatabase(origin, name, false)) && !deleteDatabaseFile(origin, name, deletionMode)) {
849 // Even if the file can't be deleted, we want to try and delete the rest, don't return early here.
850 LOG_ERROR("Unable to delete file for database %s in origin %s", name.utf8().data(), origin.databaseIdentifier().utf8().data());
851 failedToDeleteAnyDatabaseFile = true;
852 }
853 }
854
855 // If databaseNames is empty, delete everything in the directory containing the databases for this origin.
856 // This condition indicates that we previously tried to remove the origin but didn't get all of the way
857 // through the deletion process. Because we have lost track of the databases for this origin,
858 // we can assume that no other process is accessing them. This means it should be safe to delete them outright.
859 if (databaseNames.isEmpty()) {
860#if PLATFORM(COCOA)
861 RELEASE_LOG_ERROR(DatabaseTracker, "Unable to retrieve list of database names for origin");
862#endif
863 for (const auto& file : FileSystem::listDirectory(originPath(origin), "*")) {
864 if (!FileSystem::deleteFile(file))
865 failedToDeleteAnyDatabaseFile = true;
866 }
867 }
868
869 // If we failed to delete any database file, don't remove the origin from the tracker
870 // database because we didn't successfully remove all of its data.
871 if (failedToDeleteAnyDatabaseFile) {
872#if PLATFORM(COCOA)
873 RELEASE_LOG_ERROR(DatabaseTracker, "Failed to delete database for origin");
874#endif
875 return false;
876 }
877
878 {
879 LockHolder lockDatabase(m_databaseGuard);
880 deleteOriginLockFor(origin);
881 doneDeletingOrigin(origin);
882
883 SQLiteTransaction transaction(m_database);
884 transaction.begin();
885
886 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
887 if (statement.prepare() != SQLITE_OK) {
888 LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin.databaseIdentifier().utf8().data());
889 return false;
890 }
891
892 statement.bindText(1, origin.databaseIdentifier());
893
894 if (!statement.executeCommand()) {
895 LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.databaseIdentifier().utf8().data());
896 return false;
897 }
898
899 SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?");
900 if (originStatement.prepare() != SQLITE_OK) {
901 LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin.databaseIdentifier().utf8().data());
902 return false;
903 }
904
905 originStatement.bindText(1, origin.databaseIdentifier());
906
907 if (!originStatement.executeCommand()) {
908 LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.databaseIdentifier().utf8().data());
909 return false;
910 }
911
912 transaction.commit();
913
914 SQLiteFileSystem::deleteEmptyDatabaseDirectory(originPath(origin));
915
916 bool isEmpty = true;
917
918 openTrackerDatabase(DontCreateIfDoesNotExist);
919 if (m_database.isOpen()) {
920 SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
921 if (statement.prepare() != SQLITE_OK)
922 LOG_ERROR("Failed to prepare statement.");
923 else if (statement.step() == SQLITE_ROW)
924 isEmpty = false;
925 }
926
927 // If we removed the last origin, do some additional deletion.
928 if (isEmpty) {
929 if (m_database.isOpen())
930 m_database.close();
931 SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
932 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_databaseDirectoryPath);
933 }
934
935 if (m_client) {
936 m_client->dispatchDidModifyOrigin(origin);
937 m_client->dispatchDidDeleteDatabaseOrigin();
938 for (auto& name : databaseNames)
939 m_client->dispatchDidModifyDatabase(origin, name);
940 }
941 }
942 return true;
943}
944
945bool DatabaseTracker::isDeletingDatabaseOrOriginFor(const SecurityOriginData& origin, const String& name)
946{
947 ASSERT(!m_databaseGuard.tryLock());
948 // Can't create a database while someone else is deleting it; there's a risk of leaving untracked database debris on the disk.
949 return isDeletingDatabase(origin, name) || isDeletingOrigin(origin);
950}
951
952void DatabaseTracker::recordCreatingDatabase(const SecurityOriginData& origin, const String& name)
953{
954 ASSERT(!m_databaseGuard.tryLock());
955
956 // We don't use HashMap::ensure here to avoid making an isolated copy of the origin every time.
957 auto* nameSet = m_beingCreated.get(origin);
958 if (!nameSet) {
959 auto ownedSet = std::make_unique<HashCountedSet<String>>();
960 nameSet = ownedSet.get();
961 m_beingCreated.add(origin.isolatedCopy(), WTFMove(ownedSet));
962 }
963 nameSet->add(name.isolatedCopy());
964}
965
966void DatabaseTracker::doneCreatingDatabase(const SecurityOriginData& origin, const String& name)
967{
968 ASSERT(!m_databaseGuard.tryLock());
969
970 ASSERT(m_beingCreated.contains(origin));
971
972 auto iterator = m_beingCreated.find(origin);
973 if (iterator == m_beingCreated.end())
974 return;
975
976 auto& countedSet = *iterator->value;
977 ASSERT(countedSet.contains(name));
978
979 if (countedSet.remove(name) && countedSet.isEmpty())
980 m_beingCreated.remove(iterator);
981}
982
983bool DatabaseTracker::creatingDatabase(const SecurityOriginData& origin, const String& name)
984{
985 ASSERT(!m_databaseGuard.tryLock());
986
987 auto iterator = m_beingCreated.find(origin);
988 return iterator != m_beingCreated.end() && iterator->value->contains(name);
989}
990
991bool DatabaseTracker::canDeleteDatabase(const SecurityOriginData& origin, const String& name)
992{
993 ASSERT(!m_databaseGuard.tryLock());
994 return !creatingDatabase(origin, name) && !isDeletingDatabase(origin, name);
995}
996
997void DatabaseTracker::recordDeletingDatabase(const SecurityOriginData& origin, const String& name)
998{
999 ASSERT(!m_databaseGuard.tryLock());
1000 ASSERT(canDeleteDatabase(origin, name));
1001
1002 // We don't use HashMap::ensure here to avoid making an isolated copy of the origin every time.
1003 auto* nameSet = m_beingDeleted.get(origin);
1004 if (!nameSet) {
1005 auto ownedSet = std::make_unique<HashSet<String>>();
1006 nameSet = ownedSet.get();
1007 m_beingDeleted.add(origin.isolatedCopy(), WTFMove(ownedSet));
1008 }
1009 ASSERT(!nameSet->contains(name));
1010 nameSet->add(name.isolatedCopy());
1011}
1012
1013void DatabaseTracker::doneDeletingDatabase(const SecurityOriginData& origin, const String& name)
1014{
1015 ASSERT(!m_databaseGuard.tryLock());
1016 ASSERT(m_beingDeleted.contains(origin));
1017
1018 auto iterator = m_beingDeleted.find(origin);
1019 if (iterator == m_beingDeleted.end())
1020 return;
1021
1022 ASSERT(iterator->value->contains(name));
1023 iterator->value->remove(name);
1024 if (iterator->value->isEmpty())
1025 m_beingDeleted.remove(iterator);
1026}
1027
1028bool DatabaseTracker::isDeletingDatabase(const SecurityOriginData& origin, const String& name)
1029{
1030 ASSERT(!m_databaseGuard.tryLock());
1031 auto* nameSet = m_beingDeleted.get(origin);
1032 return nameSet && nameSet->contains(name);
1033}
1034
1035bool DatabaseTracker::canDeleteOrigin(const SecurityOriginData& origin)
1036{
1037 ASSERT(!m_databaseGuard.tryLock());
1038 return !(isDeletingOrigin(origin) || m_beingCreated.get(origin));
1039}
1040
1041bool DatabaseTracker::isDeletingOrigin(const SecurityOriginData& origin)
1042{
1043 ASSERT(!m_databaseGuard.tryLock());
1044 return m_originsBeingDeleted.contains(origin);
1045}
1046
1047void DatabaseTracker::recordDeletingOrigin(const SecurityOriginData& origin)
1048{
1049 ASSERT(!m_databaseGuard.tryLock());
1050 ASSERT(!isDeletingOrigin(origin));
1051 m_originsBeingDeleted.add(origin.isolatedCopy());
1052}
1053
1054void DatabaseTracker::doneDeletingOrigin(const SecurityOriginData& origin)
1055{
1056 ASSERT(!m_databaseGuard.tryLock());
1057 ASSERT(isDeletingOrigin(origin));
1058 m_originsBeingDeleted.remove(origin);
1059}
1060
1061bool DatabaseTracker::deleteDatabase(const SecurityOriginData& origin, const String& name)
1062{
1063 {
1064 LockHolder lockDatabase(m_databaseGuard);
1065 openTrackerDatabase(DontCreateIfDoesNotExist);
1066 if (!m_database.isOpen())
1067 return false;
1068
1069 if (!canDeleteDatabase(origin, name)) {
1070 ASSERT_NOT_REACHED();
1071 return false;
1072 }
1073 recordDeletingDatabase(origin, name);
1074 }
1075
1076 // We drop the lock here because holding locks during a call to deleteDatabaseFile will deadlock.
1077 if (FileSystem::fileExists(fullPathForDatabase(origin, name, false)) && !deleteDatabaseFile(origin, name, DeletionMode::Default)) {
1078 LOG_ERROR("Unable to delete file for database %s in origin %s", name.utf8().data(), origin.databaseIdentifier().utf8().data());
1079 LockHolder lockDatabase(m_databaseGuard);
1080 doneDeletingDatabase(origin, name);
1081 return false;
1082 }
1083
1084 LockHolder lockDatabase(m_databaseGuard);
1085
1086 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
1087 if (statement.prepare() != SQLITE_OK) {
1088 LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.utf8().data(), origin.databaseIdentifier().utf8().data());
1089 doneDeletingDatabase(origin, name);
1090 return false;
1091 }
1092
1093 statement.bindText(1, origin.databaseIdentifier());
1094 statement.bindText(2, name);
1095
1096 if (!statement.executeCommand()) {
1097 LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.utf8().data(), origin.databaseIdentifier().utf8().data());
1098 doneDeletingDatabase(origin, name);
1099 return false;
1100 }
1101
1102 if (m_client) {
1103 m_client->dispatchDidModifyOrigin(origin);
1104 m_client->dispatchDidModifyDatabase(origin, name);
1105 m_client->dispatchDidDeleteDatabase();
1106 }
1107 doneDeletingDatabase(origin, name);
1108
1109 return true;
1110}
1111
1112// deleteDatabaseFile has to release locks between looking up the list of databases to close and closing them. While this is in progress, the caller
1113// is responsible for making sure no new databases are opened in the file to be deleted.
1114bool DatabaseTracker::deleteDatabaseFile(const SecurityOriginData& origin, const String& name, DeletionMode deletionMode)
1115{
1116 String fullPath = fullPathForDatabase(origin, name, false);
1117 if (fullPath.isEmpty())
1118 return true;
1119
1120#ifndef NDEBUG
1121 {
1122 LockHolder lockDatabase(m_databaseGuard);
1123 ASSERT(isDeletingDatabaseOrOriginFor(origin, name));
1124 }
1125#endif
1126
1127 Vector<Ref<Database>> deletedDatabases;
1128
1129 // Make sure not to hold the any locks when calling
1130 // Database::markAsDeletedAndClose(), since that can cause a deadlock
1131 // during the synchronous DatabaseThread call it triggers.
1132 {
1133 LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
1134 if (m_openDatabaseMap) {
1135 if (auto* nameMap = m_openDatabaseMap->get(origin)) {
1136 if (auto* databaseSet = nameMap->get(name)) {
1137 for (auto& database : *databaseSet)
1138 deletedDatabases.append(*database);
1139 }
1140 }
1141 }
1142 }
1143
1144 for (auto& database : deletedDatabases)
1145 database->markAsDeletedAndClose();
1146
1147#if PLATFORM(IOS_FAMILY)
1148 if (deletionMode == DeletionMode::Deferred) {
1149 // Other background processes may still be accessing this database. Deleting the database directly
1150 // would nuke the POSIX file locks, potentially causing Safari/WebApp to corrupt the new db if it's running in the background.
1151 // We'll instead truncate the database file to 0 bytes. If another process is operating on this same database file after
1152 // the truncation, it should get an error since the database file is no longer valid. When Safari is launched
1153 // next time, it'll go through the database files and clean up any zero-bytes ones.
1154 SQLiteDatabase database;
1155 if (!database.open(fullPath))
1156 return false;
1157 return SQLiteFileSystem::truncateDatabaseFile(database.sqlite3Handle());
1158 }
1159#else
1160 UNUSED_PARAM(deletionMode);
1161#endif
1162
1163 return SQLiteFileSystem::deleteDatabaseFile(fullPath);
1164}
1165
1166#if PLATFORM(IOS_FAMILY)
1167
1168void DatabaseTracker::removeDeletedOpenedDatabases()
1169{
1170 // This is called when another app has deleted a database. Go through all opened databases in this
1171 // tracker and close any that's no longer being tracked in the database.
1172
1173 {
1174 // Acquire the lock before calling openTrackerDatabase.
1175 LockHolder lockDatabase(m_databaseGuard);
1176 openTrackerDatabase(DontCreateIfDoesNotExist);
1177 }
1178
1179 if (!m_database.isOpen())
1180 return;
1181
1182 // Keep track of which opened databases have been deleted.
1183 Vector<RefPtr<Database>> deletedDatabases;
1184 Vector<std::pair<SecurityOriginData, Vector<String>>> deletedDatabaseNames;
1185
1186 // Make sure not to hold the m_openDatabaseMapGuard mutex when calling
1187 // Database::markAsDeletedAndClose(), since that can cause a deadlock
1188 // during the synchronous DatabaseThread call it triggers.
1189 {
1190 LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
1191 if (m_openDatabaseMap) {
1192 for (auto& openDatabase : *m_openDatabaseMap) {
1193 auto& origin = openDatabase.key;
1194 DatabaseNameMap* databaseNameMap = openDatabase.value;
1195 Vector<String> deletedDatabaseNamesForThisOrigin;
1196
1197 // Loop through all opened databases in this origin. Get the current database file path of each database and see if
1198 // it still matches the path stored in the opened database object.
1199 for (auto& databases : *databaseNameMap) {
1200 String databaseName = databases.key;
1201 String databaseFileName;
1202 SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
1203 if (statement.prepare() == SQLITE_OK) {
1204 statement.bindText(1, origin.databaseIdentifier());
1205 statement.bindText(2, databaseName);
1206 if (statement.step() == SQLITE_ROW)
1207 databaseFileName = statement.getColumnText(0);
1208 statement.finalize();
1209 }
1210
1211 bool foundDeletedDatabase = false;
1212 for (auto& db : *databases.value) {
1213 // We are done if this database has already been marked as deleted.
1214 if (db->deleted())
1215 continue;
1216
1217 // If this database has been deleted or if its database file no longer matches the current version, this database is no longer valid and it should be marked as deleted.
1218 if (databaseFileName.isNull() || databaseFileName != FileSystem::pathGetFileName(db->fileName())) {
1219 deletedDatabases.append(db);
1220 foundDeletedDatabase = true;
1221 }
1222 }
1223
1224 // If the database no longer exists, we should remember to send that information to the client later.
1225 if (m_client && foundDeletedDatabase && databaseFileName.isNull())
1226 deletedDatabaseNamesForThisOrigin.append(databaseName);
1227 }
1228
1229 if (!deletedDatabaseNamesForThisOrigin.isEmpty())
1230 deletedDatabaseNames.append({ origin, WTFMove(deletedDatabaseNamesForThisOrigin) });
1231 }
1232 }
1233 }
1234
1235 for (auto& deletedDatabase : deletedDatabases)
1236 deletedDatabase->markAsDeletedAndClose();
1237
1238 for (auto& deletedDatabase : deletedDatabaseNames) {
1239 auto& origin = deletedDatabase.first;
1240 m_client->dispatchDidModifyOrigin(origin);
1241 for (auto& databaseName : deletedDatabase.second)
1242 m_client->dispatchDidModifyDatabase(origin, databaseName);
1243 }
1244}
1245
1246static bool isZeroByteFile(const String& path)
1247{
1248 long long size = 0;
1249 return FileSystem::getFileSize(path, size) && !size;
1250}
1251
1252bool DatabaseTracker::deleteDatabaseFileIfEmpty(const String& path)
1253{
1254 if (!isZeroByteFile(path))
1255 return false;
1256
1257 SQLiteDatabase database;
1258 if (!database.open(path))
1259 return false;
1260
1261 // Specify that we want the exclusive locking mode, so after the next write,
1262 // we'll be holding the lock to this database file.
1263 SQLiteStatement lockStatement(database, "PRAGMA locking_mode=EXCLUSIVE;");
1264 if (lockStatement.prepare() != SQLITE_OK)
1265 return false;
1266 int result = lockStatement.step();
1267 if (result != SQLITE_ROW && result != SQLITE_DONE)
1268 return false;
1269 lockStatement.finalize();
1270
1271 if (!database.executeCommand("BEGIN EXCLUSIVE TRANSACTION;"))
1272 return false;
1273
1274 // At this point, we hold the exclusive lock to this file.
1275 // Check that the database doesn't contain any tables.
1276 if (!database.executeCommand("SELECT name FROM sqlite_master WHERE type='table';"))
1277 return false;
1278
1279 database.executeCommand("COMMIT TRANSACTION;");
1280
1281 database.close();
1282
1283 return SQLiteFileSystem::deleteDatabaseFile(path);
1284}
1285
1286static Lock openDatabaseLock;
1287Lock& DatabaseTracker::openDatabaseMutex()
1288{
1289 return openDatabaseLock;
1290}
1291
1292void DatabaseTracker::emptyDatabaseFilesRemovalTaskWillBeScheduled()
1293{
1294 // Lock the database from opening any database until we are done with scanning the file system for
1295 // zero byte database files to remove.
1296 openDatabaseLock.lock();
1297}
1298
1299void DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish()
1300{
1301 openDatabaseLock.unlock();
1302}
1303
1304#endif
1305
1306void DatabaseTracker::setClient(DatabaseManagerClient* client)
1307{
1308 m_client = client;
1309}
1310
1311static Lock notificationLock;
1312
1313using NotificationQueue = Vector<std::pair<SecurityOriginData, String>>;
1314
1315static NotificationQueue& notificationQueue()
1316{
1317 static NeverDestroyed<NotificationQueue> queue;
1318 return queue;
1319}
1320
1321void DatabaseTracker::scheduleNotifyDatabaseChanged(const SecurityOriginData& origin, const String& name)
1322{
1323 auto locker = holdLock(notificationLock);
1324 notificationQueue().append(std::make_pair(origin.isolatedCopy(), name.isolatedCopy()));
1325 scheduleForNotification();
1326}
1327
1328static bool notificationScheduled = false;
1329
1330void DatabaseTracker::scheduleForNotification()
1331{
1332 ASSERT(!notificationLock.tryLock());
1333
1334 if (!notificationScheduled) {
1335 callOnMainThread([] {
1336 notifyDatabasesChanged();
1337 });
1338 notificationScheduled = true;
1339 }
1340}
1341
1342void DatabaseTracker::notifyDatabasesChanged()
1343{
1344 // Note that if DatabaseTracker ever becomes non-singleton, we'll have to amend this notification
1345 // mechanism to include which tracker the notification goes out on as well.
1346 auto& tracker = DatabaseTracker::singleton();
1347
1348 NotificationQueue notifications;
1349 {
1350 auto locker = holdLock(notificationLock);
1351 notifications.swap(notificationQueue());
1352 notificationScheduled = false;
1353 }
1354
1355 if (!tracker.m_client)
1356 return;
1357
1358 for (auto& notification : notifications)
1359 tracker.m_client->dispatchDidModifyDatabase(notification.first, notification.second);
1360}
1361
1362
1363} // namespace WebCore
1364