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 | |
57 | namespace WebCore { |
58 | |
59 | static 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 | |
68 | std::unique_ptr<DatabaseTracker> DatabaseTracker::trackerWithDatabasePath(const String& databasePath) |
69 | { |
70 | return std::unique_ptr<DatabaseTracker>(new DatabaseTracker(databasePath)); |
71 | } |
72 | |
73 | static DatabaseTracker* staticTracker = nullptr; |
74 | |
75 | void DatabaseTracker::initializeTracker(const String& databasePath) |
76 | { |
77 | ASSERT(!staticTracker); |
78 | if (staticTracker) |
79 | return; |
80 | staticTracker = new DatabaseTracker(databasePath); |
81 | } |
82 | |
83 | bool DatabaseTracker::isInitialized() |
84 | { |
85 | return !!staticTracker; |
86 | } |
87 | |
88 | DatabaseTracker& DatabaseTracker::singleton() |
89 | { |
90 | if (!staticTracker) |
91 | staticTracker = new DatabaseTracker(emptyString()); |
92 | return *staticTracker; |
93 | } |
94 | |
95 | DatabaseTracker::DatabaseTracker(const String& databasePath) |
96 | : m_databaseDirectoryPath(databasePath.isolatedCopy()) |
97 | { |
98 | } |
99 | |
100 | String DatabaseTracker::trackerDatabasePath() const |
101 | { |
102 | return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.isolatedCopy(), "Databases.db" ); |
103 | } |
104 | |
105 | void 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 | |
144 | ExceptionOr<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 | |
160 | ExceptionOr<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. |
208 | ExceptionOr<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 | |
231 | bool 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 | |
249 | bool 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 | |
270 | unsigned 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 | |
295 | void DatabaseTracker::closeAllDatabases(CurrentQueryBehavior currentQueryBehavior) |
296 | { |
297 | for (auto& database : openDatabases()) { |
298 | if (currentQueryBehavior == CurrentQueryBehavior::Interrupt) |
299 | database->interrupt(); |
300 | database->close(); |
301 | } |
302 | } |
303 | |
304 | String DatabaseTracker::originPath(const SecurityOriginData& origin) const |
305 | { |
306 | return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.isolatedCopy(), origin.databaseIdentifier()); |
307 | } |
308 | |
309 | static String generateDatabaseFileName() |
310 | { |
311 | StringBuilder stringBuilder; |
312 | |
313 | stringBuilder.append(createCanonicalUUIDString()); |
314 | stringBuilder.appendLiteral(".db" ); |
315 | |
316 | return stringBuilder.toString(); |
317 | } |
318 | |
319 | String 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 | |
366 | String 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 | |
372 | Vector<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 | |
398 | Vector<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 | |
425 | Vector<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 | |
435 | DatabaseDetails 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 | |
472 | void 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 | |
523 | void DatabaseTracker::doneCreatingDatabase(Database& database) |
524 | { |
525 | LockHolder lockDatabase(m_databaseGuard); |
526 | doneCreatingDatabase(database.securityOrigin(), database.stringIdentifier()); |
527 | } |
528 | |
529 | Vector<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 | |
547 | void 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 | |
574 | void 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 | |
613 | RefPtr<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 | |
638 | void 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 | |
659 | unsigned 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 | |
668 | unsigned 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 | |
690 | unsigned long long DatabaseTracker::quota(const SecurityOriginData& origin) |
691 | { |
692 | LockHolder lockDatabase(m_databaseGuard); |
693 | return quotaNoLock(origin); |
694 | } |
695 | |
696 | void 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 | |
744 | bool 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 | |
774 | void 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 | |
784 | void 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. |
819 | bool DatabaseTracker::deleteOrigin(const SecurityOriginData& origin) |
820 | { |
821 | return deleteOrigin(origin, DeletionMode::Default); |
822 | } |
823 | |
824 | bool 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 | |
945 | bool 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 | |
952 | void 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 | |
966 | void 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 | |
983 | bool 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 | |
991 | bool DatabaseTracker::canDeleteDatabase(const SecurityOriginData& origin, const String& name) |
992 | { |
993 | ASSERT(!m_databaseGuard.tryLock()); |
994 | return !creatingDatabase(origin, name) && !isDeletingDatabase(origin, name); |
995 | } |
996 | |
997 | void 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 | |
1013 | void 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 | |
1028 | bool 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 | |
1035 | bool DatabaseTracker::canDeleteOrigin(const SecurityOriginData& origin) |
1036 | { |
1037 | ASSERT(!m_databaseGuard.tryLock()); |
1038 | return !(isDeletingOrigin(origin) || m_beingCreated.get(origin)); |
1039 | } |
1040 | |
1041 | bool DatabaseTracker::isDeletingOrigin(const SecurityOriginData& origin) |
1042 | { |
1043 | ASSERT(!m_databaseGuard.tryLock()); |
1044 | return m_originsBeingDeleted.contains(origin); |
1045 | } |
1046 | |
1047 | void DatabaseTracker::recordDeletingOrigin(const SecurityOriginData& origin) |
1048 | { |
1049 | ASSERT(!m_databaseGuard.tryLock()); |
1050 | ASSERT(!isDeletingOrigin(origin)); |
1051 | m_originsBeingDeleted.add(origin.isolatedCopy()); |
1052 | } |
1053 | |
1054 | void DatabaseTracker::doneDeletingOrigin(const SecurityOriginData& origin) |
1055 | { |
1056 | ASSERT(!m_databaseGuard.tryLock()); |
1057 | ASSERT(isDeletingOrigin(origin)); |
1058 | m_originsBeingDeleted.remove(origin); |
1059 | } |
1060 | |
1061 | bool 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. |
1114 | bool 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 | |
1168 | void 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 | |
1246 | static bool isZeroByteFile(const String& path) |
1247 | { |
1248 | long long size = 0; |
1249 | return FileSystem::getFileSize(path, size) && !size; |
1250 | } |
1251 | |
1252 | bool 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 | |
1286 | static Lock openDatabaseLock; |
1287 | Lock& DatabaseTracker::openDatabaseMutex() |
1288 | { |
1289 | return openDatabaseLock; |
1290 | } |
1291 | |
1292 | void 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 | |
1299 | void DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish() |
1300 | { |
1301 | openDatabaseLock.unlock(); |
1302 | } |
1303 | |
1304 | #endif |
1305 | |
1306 | void DatabaseTracker::setClient(DatabaseManagerClient* client) |
1307 | { |
1308 | m_client = client; |
1309 | } |
1310 | |
1311 | static Lock notificationLock; |
1312 | |
1313 | using NotificationQueue = Vector<std::pair<SecurityOriginData, String>>; |
1314 | |
1315 | static NotificationQueue& notificationQueue() |
1316 | { |
1317 | static NeverDestroyed<NotificationQueue> queue; |
1318 | return queue; |
1319 | } |
1320 | |
1321 | void 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 | |
1328 | static bool notificationScheduled = false; |
1329 | |
1330 | void DatabaseTracker::scheduleForNotification() |
1331 | { |
1332 | ASSERT(!notificationLock.tryLock()); |
1333 | |
1334 | if (!notificationScheduled) { |
1335 | callOnMainThread([] { |
1336 | notifyDatabasesChanged(); |
1337 | }); |
1338 | notificationScheduled = true; |
1339 | } |
1340 | } |
1341 | |
1342 | void 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 | |