1 /*
2 * Copyright (C) 2007, 2008 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 Computer, 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 #if ENABLE(DATABASE)
33
34 #include "AbstractDatabase.h"
35 #include "Chrome.h"
36 #include "ChromeClient.h"
37 #include "DatabaseThread.h"
38 #include "DatabaseTrackerClient.h"
39 #include "Logging.h"
40 #include "OriginQuotaManager.h"
41 #include "Page.h"
42 #include "ScriptExecutionContext.h"
43 #include "SecurityOrigin.h"
44 #include "SecurityOriginHash.h"
45 #include "SQLiteFileSystem.h"
46 #include "SQLiteStatement.h"
47 #include <wtf/MainThread.h>
48 #include <wtf/StdLibExtras.h>
49 #include <wtf/text/CString.h>
50
51 using namespace std;
52
originQuotaManager()53 static WebCore::OriginQuotaManager& originQuotaManager()
54 {
55 DEFINE_STATIC_LOCAL(WebCore::OriginQuotaManager, quotaManager, ());
56 return quotaManager;
57 }
58
59 namespace WebCore {
60
61 static DatabaseTracker* staticTracker = 0;
62
initializeTracker(const String & databasePath)63 void DatabaseTracker::initializeTracker(const String& databasePath)
64 {
65 ASSERT(!staticTracker);
66 if (staticTracker)
67 return;
68
69 staticTracker = new DatabaseTracker(databasePath);
70 }
71
tracker()72 DatabaseTracker& DatabaseTracker::tracker()
73 {
74 if (!staticTracker)
75 staticTracker = new DatabaseTracker("");
76
77 return *staticTracker;
78 }
79
DatabaseTracker(const String & databasePath)80 DatabaseTracker::DatabaseTracker(const String& databasePath)
81 : m_client(0)
82 {
83 setDatabaseDirectoryPath(databasePath);
84
85 SQLiteFileSystem::registerSQLiteVFS();
86
87 MutexLocker lockDatabase(m_databaseGuard);
88 populateOrigins();
89 }
90
setDatabaseDirectoryPath(const String & path)91 void DatabaseTracker::setDatabaseDirectoryPath(const String& path)
92 {
93 MutexLocker lockDatabase(m_databaseGuard);
94 ASSERT(!m_database.isOpen());
95 m_databaseDirectoryPath = path.threadsafeCopy();
96 }
97
databaseDirectoryPath() const98 String DatabaseTracker::databaseDirectoryPath() const
99 {
100 return m_databaseDirectoryPath.threadsafeCopy();
101 }
102
trackerDatabasePath() const103 String DatabaseTracker::trackerDatabasePath() const
104 {
105 return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath, "Databases.db");
106 }
107
openTrackerDatabase(bool createIfDoesNotExist)108 void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist)
109 {
110 ASSERT(!m_databaseGuard.tryLock());
111
112 if (m_database.isOpen())
113 return;
114
115 String databasePath = trackerDatabasePath();
116 if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist))
117 return;
118
119 if (!m_database.open(databasePath)) {
120 // FIXME: What do do here?
121 LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
122 return;
123 }
124 m_database.disableThreadingChecks();
125 if (!m_database.tableExists("Origins")) {
126 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
127 // FIXME: and here
128 LOG_ERROR("Failed to create Origins table");
129 }
130 }
131 if (!m_database.tableExists("Databases")) {
132 if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
133 // FIXME: and here
134 LOG_ERROR("Failed to create Databases table");
135 }
136 }
137 }
138
canEstablishDatabase(ScriptExecutionContext * context,const String & name,const String & displayName,unsigned long estimatedSize)139 bool DatabaseTracker::canEstablishDatabase(ScriptExecutionContext* context, const String& name, const String& displayName, unsigned long estimatedSize)
140 {
141 SecurityOrigin* origin = context->securityOrigin();
142 ProposedDatabase details;
143
144 unsigned long long requirement;
145 {
146 MutexLocker lockDatabase(m_databaseGuard);
147 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
148
149 if (!canCreateDatabase(origin, name))
150 return false;
151
152 recordCreatingDatabase(origin, name);
153
154 // Since we're imminently opening a database within this context's origin, make sure this origin is being tracked by the QuotaTracker
155 // by fetching its current usage now.
156 unsigned long long usage = usageForOriginNoLock(origin);
157
158 // If a database already exists, ignore the passed-in estimated size and say it's OK.
159 if (hasEntryForDatabase(origin, name))
160 return true;
161
162 // If the database will fit, allow its creation.
163 requirement = usage + max(1UL, estimatedSize);
164 if (requirement < usage) {
165 doneCreatingDatabase(origin, name);
166 return false; // If the estimated size is so big it causes an overflow, don't allow creation.
167 }
168 if (requirement <= quotaForOriginNoLock(origin))
169 return true;
170
171 // Give the chrome client a chance to increase the quota.
172 // Temporarily make the details of the proposed database available, so the client can get at them.
173 // FIXME: We should really just pass the details into this call, rather than using m_proposedDatabases.
174 details = ProposedDatabase(origin->threadsafeCopy(), DatabaseDetails(name.threadsafeCopy(), displayName.threadsafeCopy(), estimatedSize, 0));
175 m_proposedDatabases.add(&details);
176 }
177 // Drop all locks before calling out; we don't know what they'll do.
178 context->databaseExceededQuota(name);
179
180 MutexLocker lockDatabase(m_databaseGuard);
181
182 m_proposedDatabases.remove(&details);
183
184 // If the database will fit now, allow its creation.
185 if (requirement <= quotaForOriginNoLock(origin))
186 return true;
187
188 doneCreatingDatabase(origin, name);
189
190 return false;
191 }
192
hasEntryForOriginNoLock(SecurityOrigin * origin)193 bool DatabaseTracker::hasEntryForOriginNoLock(SecurityOrigin* origin)
194 {
195 ASSERT(!m_databaseGuard.tryLock());
196 ASSERT(m_quotaMap);
197 return m_quotaMap->contains(origin);
198 }
199
hasEntryForOrigin(SecurityOrigin * origin)200 bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin)
201 {
202 MutexLocker lockDatabase(m_databaseGuard);
203 return hasEntryForOriginNoLock(origin);
204 }
205
hasEntryForDatabase(SecurityOrigin * origin,const String & databaseIdentifier)206 bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier)
207 {
208 ASSERT(!m_databaseGuard.tryLock());
209 openTrackerDatabase(false);
210 if (!m_database.isOpen())
211 return false;
212 SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
213
214 if (statement.prepare() != SQLResultOk)
215 return false;
216
217 statement.bindText(1, origin->databaseIdentifier());
218 statement.bindText(2, databaseIdentifier);
219
220 return statement.step() == SQLResultRow;
221 }
222
getMaxSizeForDatabase(const AbstractDatabase * database)223 unsigned long long DatabaseTracker::getMaxSizeForDatabase(const AbstractDatabase* database)
224 {
225 // The maximum size for a database is the full quota for its origin, minus the current usage within the origin,
226 // plus the current usage of the given database
227 MutexLocker lockDatabase(m_databaseGuard);
228 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
229 SecurityOrigin* origin = database->securityOrigin();
230 return quotaForOriginNoLock(origin) - originQuotaManager().diskUsage(origin) + SQLiteFileSystem::getDatabaseFileSize(database->fileName());
231 }
232
databaseChanged(AbstractDatabase * database)233 void DatabaseTracker::databaseChanged(AbstractDatabase* database)
234 {
235 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
236 originQuotaManager().markDatabase(database);
237 }
238
interruptAllDatabasesForContext(const ScriptExecutionContext * context)239 void DatabaseTracker::interruptAllDatabasesForContext(const ScriptExecutionContext* context)
240 {
241 Vector<RefPtr<AbstractDatabase> > openDatabases;
242 {
243 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
244
245 if (!m_openDatabaseMap)
246 return;
247
248 DatabaseNameMap* nameMap = m_openDatabaseMap->get(context->securityOrigin());
249 if (!nameMap)
250 return;
251
252 DatabaseNameMap::const_iterator dbNameMapEndIt = nameMap->end();
253 for (DatabaseNameMap::const_iterator dbNameMapIt = nameMap->begin(); dbNameMapIt != dbNameMapEndIt; ++dbNameMapIt) {
254 DatabaseSet* databaseSet = dbNameMapIt->second;
255 DatabaseSet::const_iterator dbSetEndIt = databaseSet->end();
256 for (DatabaseSet::const_iterator dbSetIt = databaseSet->begin(); dbSetIt != dbSetEndIt; ++dbSetIt) {
257 if ((*dbSetIt)->scriptExecutionContext() == context)
258 openDatabases.append(*dbSetIt);
259 }
260 }
261 }
262
263 Vector<RefPtr<AbstractDatabase> >::const_iterator openDatabasesEndIt = openDatabases.end();
264 for (Vector<RefPtr<AbstractDatabase> >::const_iterator openDatabasesIt = openDatabases.begin(); openDatabasesIt != openDatabasesEndIt; ++openDatabasesIt)
265 (*openDatabasesIt)->interrupt();
266 }
267
originPath(SecurityOrigin * origin) const268 String DatabaseTracker::originPath(SecurityOrigin* origin) const
269 {
270 return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.threadsafeCopy(), origin->databaseIdentifier());
271 }
272
fullPathForDatabaseNoLock(SecurityOrigin * origin,const String & name,bool createIfNotExists)273 String DatabaseTracker::fullPathForDatabaseNoLock(SecurityOrigin* origin, const String& name, bool createIfNotExists)
274 {
275 ASSERT(!m_databaseGuard.tryLock());
276 ASSERT(!originQuotaManager().tryLock());
277
278 for (HashSet<ProposedDatabase*>::iterator iter = m_proposedDatabases.begin(); iter != m_proposedDatabases.end(); ++iter)
279 if ((*iter)->second.name() == name && (*iter)->first->equal(origin))
280 return String();
281
282 String originIdentifier = origin->databaseIdentifier();
283 String originPath = this->originPath(origin);
284
285 // Make sure the path for this SecurityOrigin exists
286 if (createIfNotExists && !SQLiteFileSystem::ensureDatabaseDirectoryExists(originPath))
287 return String();
288
289 // See if we have a path for this database yet
290 if (!m_database.isOpen())
291 return String();
292 SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
293
294 if (statement.prepare() != SQLResultOk)
295 return String();
296
297 statement.bindText(1, originIdentifier);
298 statement.bindText(2, name);
299
300 int result = statement.step();
301
302 if (result == SQLResultRow)
303 return SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, statement.getColumnText(0));
304 if (!createIfNotExists)
305 return String();
306
307 if (result != SQLResultDone) {
308 LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", originIdentifier.ascii().data(), name.ascii().data());
309 return String();
310 }
311 statement.finalize();
312
313 String fileName = SQLiteFileSystem::getFileNameForNewDatabase(originPath, name, originIdentifier, &m_database);
314 if (!addDatabase(origin, name, fileName))
315 return String();
316
317 // If this origin's quota is being tracked (open handle to a database in this origin), add this new database
318 // to the quota manager now
319 String fullFilePath = SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, fileName);
320 if (originQuotaManager().tracksOrigin(origin))
321 originQuotaManager().addDatabase(origin, name, fullFilePath);
322
323 return fullFilePath;
324 }
325
fullPathForDatabase(SecurityOrigin * origin,const String & name,bool createIfNotExists)326 String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists)
327 {
328 MutexLocker lockDatabase(m_databaseGuard);
329 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
330
331 return fullPathForDatabaseNoLock(origin, name, createIfNotExists).threadsafeCopy();
332 }
333
populateOrigins()334 void DatabaseTracker::populateOrigins()
335 {
336 ASSERT(!m_databaseGuard.tryLock());
337 if (m_quotaMap)
338 return;
339
340 m_quotaMap = adoptPtr(new QuotaMap);
341
342 openTrackerDatabase(false);
343 if (!m_database.isOpen())
344 return;
345
346 SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
347
348 if (statement.prepare() != SQLResultOk) {
349 LOG_ERROR("Failed to prepare statement.");
350 return;
351 }
352
353 int result;
354 while ((result = statement.step()) == SQLResultRow) {
355 RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromDatabaseIdentifier(statement.getColumnText(0));
356 m_quotaMap->set(origin.get()->threadsafeCopy(), statement.getColumnInt64(1));
357 }
358
359 if (result != SQLResultDone)
360 LOG_ERROR("Failed to read in all origins from the database.");
361 }
362
origins(Vector<RefPtr<SecurityOrigin>> & result)363 void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
364 {
365 MutexLocker lockDatabase(m_databaseGuard);
366 ASSERT(m_quotaMap);
367 copyKeysToVector(*m_quotaMap, result);
368 }
369
databaseNamesForOriginNoLock(SecurityOrigin * origin,Vector<String> & resultVector)370 bool DatabaseTracker::databaseNamesForOriginNoLock(SecurityOrigin* origin, Vector<String>& resultVector)
371 {
372 ASSERT(!m_databaseGuard.tryLock());
373 openTrackerDatabase(false);
374 if (!m_database.isOpen())
375 return false;
376
377 SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
378
379 if (statement.prepare() != SQLResultOk)
380 return false;
381
382 statement.bindText(1, origin->databaseIdentifier());
383
384 int result;
385 while ((result = statement.step()) == SQLResultRow)
386 resultVector.append(statement.getColumnText(0));
387
388 if (result != SQLResultDone) {
389 LOG_ERROR("Failed to retrieve all database names for origin %s", origin->databaseIdentifier().ascii().data());
390 return false;
391 }
392
393 return true;
394 }
395
databaseNamesForOrigin(SecurityOrigin * origin,Vector<String> & resultVector)396 bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector)
397 {
398 Vector<String> temp;
399 {
400 MutexLocker lockDatabase(m_databaseGuard);
401 if (!databaseNamesForOriginNoLock(origin, temp))
402 return false;
403 }
404
405 for (Vector<String>::iterator iter = temp.begin(); iter != temp.end(); ++iter)
406 resultVector.append(iter->threadsafeCopy());
407 return true;
408 }
409
detailsForNameAndOrigin(const String & name,SecurityOrigin * origin)410 DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin)
411 {
412 String originIdentifier = origin->databaseIdentifier();
413 String displayName;
414 int64_t expectedUsage;
415
416 {
417 MutexLocker lockDatabase(m_databaseGuard);
418
419 for (HashSet<ProposedDatabase*>::iterator iter = m_proposedDatabases.begin(); iter != m_proposedDatabases.end(); ++iter)
420 if ((*iter)->second.name() == name && (*iter)->first->equal(origin)) {
421 ASSERT((*iter)->second.thread() == currentThread());
422 return (*iter)->second;
423 }
424
425 openTrackerDatabase(false);
426 if (!m_database.isOpen())
427 return DatabaseDetails();
428 SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
429 if (statement.prepare() != SQLResultOk)
430 return DatabaseDetails();
431
432 statement.bindText(1, originIdentifier);
433 statement.bindText(2, name);
434
435 int result = statement.step();
436 if (result == SQLResultDone)
437 return DatabaseDetails();
438
439 if (result != SQLResultRow) {
440 LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
441 return DatabaseDetails();
442 }
443 displayName = statement.getColumnText(0);
444 expectedUsage = statement.getColumnInt64(1);
445 }
446
447 return DatabaseDetails(name, displayName, expectedUsage, usageForDatabase(name, origin));
448 }
449
setDatabaseDetails(SecurityOrigin * origin,const String & name,const String & displayName,unsigned long estimatedSize)450 void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize)
451 {
452 String originIdentifier = origin->databaseIdentifier();
453 int64_t guid = 0;
454
455 MutexLocker lockDatabase(m_databaseGuard);
456
457 openTrackerDatabase(true);
458 if (!m_database.isOpen())
459 return;
460 SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
461 if (statement.prepare() != SQLResultOk)
462 return;
463
464 statement.bindText(1, originIdentifier);
465 statement.bindText(2, name);
466
467 int result = statement.step();
468 if (result == SQLResultRow)
469 guid = statement.getColumnInt64(0);
470 statement.finalize();
471
472 if (guid == 0) {
473 if (result != SQLResultDone)
474 LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
475 else {
476 // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
477 // 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
478 // So we'll print an error instead
479 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",
480 name.ascii().data(), originIdentifier.ascii().data());
481 }
482 return;
483 }
484
485 SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
486 if (updateStatement.prepare() != SQLResultOk)
487 return;
488
489 updateStatement.bindText(1, displayName);
490 updateStatement.bindInt64(2, estimatedSize);
491 updateStatement.bindInt64(3, guid);
492
493 if (updateStatement.step() != SQLResultDone) {
494 LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
495 return;
496 }
497
498 if (m_client)
499 m_client->dispatchDidModifyDatabase(origin, name);
500 }
501
usageForDatabase(const String & name,SecurityOrigin * origin)502 unsigned long long DatabaseTracker::usageForDatabase(const String& name, SecurityOrigin* origin)
503 {
504 String path = fullPathForDatabase(origin, name, false);
505 if (path.isEmpty())
506 return 0;
507
508 return SQLiteFileSystem::getDatabaseFileSize(path);
509 }
510
addOpenDatabase(AbstractDatabase * database)511 void DatabaseTracker::addOpenDatabase(AbstractDatabase* database)
512 {
513 if (!database)
514 return;
515
516 {
517 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
518
519 if (!m_openDatabaseMap)
520 m_openDatabaseMap = adoptPtr(new DatabaseOriginMap);
521
522 String name(database->stringIdentifier());
523 DatabaseNameMap* nameMap = m_openDatabaseMap->get(database->securityOrigin());
524 if (!nameMap) {
525 nameMap = new DatabaseNameMap;
526 m_openDatabaseMap->set(database->securityOrigin()->threadsafeCopy(), nameMap);
527 }
528
529 DatabaseSet* databaseSet = nameMap->get(name);
530 if (!databaseSet) {
531 databaseSet = new DatabaseSet;
532 nameMap->set(name.threadsafeCopy(), databaseSet);
533 }
534
535 databaseSet->add(database);
536
537 LOG(StorageAPI, "Added open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
538
539 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
540 if (!originQuotaManager().tracksOrigin(database->securityOrigin())) {
541 originQuotaManager().trackOrigin(database->securityOrigin());
542 originQuotaManager().addDatabase(database->securityOrigin(), database->stringIdentifier(), database->fileName());
543 }
544 }
545
546 MutexLocker lockDatabase(m_databaseGuard);
547 doneCreatingDatabase(database->securityOrigin(), database->stringIdentifier());
548 }
549
removeOpenDatabase(AbstractDatabase * database)550 void DatabaseTracker::removeOpenDatabase(AbstractDatabase* database)
551 {
552 if (!database)
553 return;
554
555 {
556 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
557
558 if (!m_openDatabaseMap) {
559 ASSERT_NOT_REACHED();
560 return;
561 }
562
563 String name(database->stringIdentifier());
564 DatabaseNameMap* nameMap = m_openDatabaseMap->get(database->securityOrigin());
565 if (!nameMap) {
566 ASSERT_NOT_REACHED();
567 return;
568 }
569
570 DatabaseSet* databaseSet = nameMap->get(name);
571 if (!databaseSet) {
572 ASSERT_NOT_REACHED();
573 return;
574 }
575
576 databaseSet->remove(database);
577
578 LOG(StorageAPI, "Removed open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
579
580 if (!databaseSet->isEmpty())
581 return;
582
583 nameMap->remove(name);
584 delete databaseSet;
585
586 if (!nameMap->isEmpty())
587 return;
588
589 m_openDatabaseMap->remove(database->securityOrigin());
590 delete nameMap;
591
592 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
593 originQuotaManager().removeOrigin(database->securityOrigin());
594 }
595 }
596
getOpenDatabases(SecurityOrigin * origin,const String & name,HashSet<RefPtr<AbstractDatabase>> * databases)597 void DatabaseTracker::getOpenDatabases(SecurityOrigin* origin, const String& name, HashSet<RefPtr<AbstractDatabase> >* databases)
598 {
599 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
600 if (!m_openDatabaseMap)
601 return;
602
603 DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
604 if (!nameMap)
605 return;
606
607 DatabaseSet* databaseSet = nameMap->get(name);
608 if (!databaseSet)
609 return;
610
611 for (DatabaseSet::iterator it = databaseSet->begin(); it != databaseSet->end(); ++it)
612 databases->add(*it);
613 }
614
usageForOriginNoLock(SecurityOrigin * origin)615 unsigned long long DatabaseTracker::usageForOriginNoLock(SecurityOrigin* origin)
616 {
617 ASSERT(!originQuotaManager().tryLock());
618
619 // Use the OriginQuotaManager mechanism to calculate the usage
620 if (originQuotaManager().tracksOrigin(origin))
621 return originQuotaManager().diskUsage(origin);
622
623 // If the OriginQuotaManager doesn't track this origin already, prime it to do so
624 originQuotaManager().trackOrigin(origin);
625
626 Vector<String> names;
627 databaseNamesForOriginNoLock(origin, names);
628
629 for (unsigned i = 0; i < names.size(); ++i)
630 originQuotaManager().addDatabase(origin, names[i], fullPathForDatabaseNoLock(origin, names[i], false));
631
632 if (!originQuotaManager().tracksOrigin(origin))
633 return 0;
634 return originQuotaManager().diskUsage(origin);
635 }
636
usageForOrigin(SecurityOrigin * origin)637 unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin)
638 {
639 MutexLocker lockDatabase(m_databaseGuard);
640 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
641 return usageForOriginNoLock(origin);
642 }
643
quotaForOriginNoLock(SecurityOrigin * origin)644 unsigned long long DatabaseTracker::quotaForOriginNoLock(SecurityOrigin* origin)
645 {
646 ASSERT(!m_databaseGuard.tryLock());
647 ASSERT(m_quotaMap);
648 return m_quotaMap->get(origin);
649 }
650
quotaForOrigin(SecurityOrigin * origin)651 unsigned long long DatabaseTracker::quotaForOrigin(SecurityOrigin* origin)
652 {
653 MutexLocker lockDatabase(m_databaseGuard);
654 return quotaForOriginNoLock(origin);
655 }
656
setQuota(SecurityOrigin * origin,unsigned long long quota)657 void DatabaseTracker::setQuota(SecurityOrigin* origin, unsigned long long quota)
658 {
659 MutexLocker lockDatabase(m_databaseGuard);
660
661 if (quotaForOriginNoLock(origin) == quota)
662 return;
663
664 openTrackerDatabase(true);
665 if (!m_database.isOpen())
666 return;
667
668 if (!m_quotaMap->contains(origin)) {
669 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
670 if (statement.prepare() != SQLResultOk) {
671 LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data());
672 } else {
673 statement.bindText(1, origin->databaseIdentifier());
674 statement.bindInt64(2, quota);
675
676 if (statement.step() != SQLResultDone)
677 LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data());
678 }
679 } else {
680 SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
681 bool error = statement.prepare() != SQLResultOk;
682 if (!error) {
683 statement.bindInt64(1, quota);
684 statement.bindText(2, origin->databaseIdentifier());
685
686 error = !statement.executeCommand();
687 }
688
689 if (error)
690 #if OS(WINDOWS)
691 LOG_ERROR("Failed to set quota %I64u in tracker database for origin %s", quota, origin->databaseIdentifier().ascii().data());
692 #else
693 LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin->databaseIdentifier().ascii().data());
694 #endif
695 }
696
697 // FIXME: Is it really OK to update the quota in memory if we failed to update it on disk?
698 m_quotaMap->set(origin->threadsafeCopy(), quota);
699
700 if (m_client)
701 m_client->dispatchDidModifyOrigin(origin);
702 }
703
addDatabase(SecurityOrigin * origin,const String & name,const String & path)704 bool DatabaseTracker::addDatabase(SecurityOrigin* origin, const String& name, const String& path)
705 {
706 ASSERT(!m_databaseGuard.tryLock());
707 ASSERT(m_quotaMap);
708 openTrackerDatabase(true);
709 if (!m_database.isOpen())
710 return false;
711
712 // New database should never be added until the origin has been established
713 ASSERT(hasEntryForOriginNoLock(origin));
714
715 SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
716
717 if (statement.prepare() != SQLResultOk)
718 return false;
719
720 statement.bindText(1, origin->databaseIdentifier());
721 statement.bindText(2, name);
722 statement.bindText(3, path);
723
724 if (!statement.executeCommand()) {
725 LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin->databaseIdentifier().ascii().data(), m_database.lastErrorMsg());
726 return false;
727 }
728
729 if (m_client)
730 m_client->dispatchDidModifyOrigin(origin);
731
732 return true;
733 }
734
deleteAllDatabases()735 void DatabaseTracker::deleteAllDatabases()
736 {
737 Vector<RefPtr<SecurityOrigin> > originsCopy;
738 origins(originsCopy);
739
740 for (unsigned i = 0; i < originsCopy.size(); ++i)
741 deleteOrigin(originsCopy[i].get());
742 }
743
744 // 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
745 // taking place.
deleteOrigin(SecurityOrigin * origin)746 bool DatabaseTracker::deleteOrigin(SecurityOrigin* origin)
747 {
748 Vector<String> databaseNames;
749 {
750 MutexLocker lockDatabase(m_databaseGuard);
751 openTrackerDatabase(false);
752 if (!m_database.isOpen())
753 return false;
754
755 if (!databaseNamesForOriginNoLock(origin, databaseNames)) {
756 LOG_ERROR("Unable to retrieve list of database names for origin %s", origin->databaseIdentifier().ascii().data());
757 return false;
758 }
759 if (!canDeleteOrigin(origin)) {
760 LOG_ERROR("Tried to delete an origin (%s) while either creating database in it or already deleting it", origin->databaseIdentifier().ascii().data());
761 ASSERT(false);
762 return false;
763 }
764 recordDeletingOrigin(origin);
765 }
766
767 // We drop the lock here because holding locks during a call to deleteDatabaseFile will deadlock.
768 for (unsigned i = 0; i < databaseNames.size(); ++i) {
769 if (!deleteDatabaseFile(origin, databaseNames[i])) {
770 // Even if the file can't be deleted, we want to try and delete the rest, don't return early here.
771 LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin->databaseIdentifier().ascii().data());
772 }
773 }
774
775 {
776 MutexLocker lockDatabase(m_databaseGuard);
777 doneDeletingOrigin(origin);
778
779 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
780 if (statement.prepare() != SQLResultOk) {
781 LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
782 return false;
783 }
784
785 statement.bindText(1, origin->databaseIdentifier());
786
787 if (!statement.executeCommand()) {
788 LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
789 return false;
790 }
791
792 SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?");
793 if (originStatement.prepare() != SQLResultOk) {
794 LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin->databaseIdentifier().ascii().data());
795 return false;
796 }
797
798 originStatement.bindText(1, origin->databaseIdentifier());
799
800 if (!originStatement.executeCommand()) {
801 LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
802 return false;
803 }
804
805 SQLiteFileSystem::deleteEmptyDatabaseDirectory(originPath(origin));
806
807 RefPtr<SecurityOrigin> originPossiblyLastReference = origin;
808 m_quotaMap->remove(origin);
809
810 {
811 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
812 originQuotaManager().removeOrigin(origin);
813 }
814
815 // If we removed the last origin, do some additional deletion.
816 if (m_quotaMap->isEmpty()) {
817 if (m_database.isOpen())
818 m_database.close();
819 SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
820 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_databaseDirectoryPath);
821 }
822
823 if (m_client) {
824 m_client->dispatchDidModifyOrigin(origin);
825 for (unsigned i = 0; i < databaseNames.size(); ++i)
826 m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
827 }
828 }
829 return true;
830 }
831
canCreateDatabase(SecurityOrigin * origin,const String & name)832 bool DatabaseTracker::canCreateDatabase(SecurityOrigin *origin, const String& name)
833 {
834 ASSERT(!m_databaseGuard.tryLock());
835 // Can't create a database while someone else is deleting it; there's a risk of leaving untracked database debris on the disk.
836 return !deletingDatabase(origin, name) && !deletingOrigin(origin);
837 }
838
recordCreatingDatabase(SecurityOrigin * origin,const String & name)839 void DatabaseTracker::recordCreatingDatabase(SecurityOrigin *origin, const String& name)
840 {
841 ASSERT(!m_databaseGuard.tryLock());
842 NameCountMap* nameMap = m_beingCreated.get(origin);
843 if (!nameMap) {
844 nameMap = new NameCountMap();
845 m_beingCreated.set(origin->threadsafeCopy(), nameMap);
846 }
847 long count = nameMap->get(name);
848 nameMap->set(name.threadsafeCopy(), count + 1);
849 }
850
doneCreatingDatabase(SecurityOrigin * origin,const String & name)851 void DatabaseTracker::doneCreatingDatabase(SecurityOrigin *origin, const String& name)
852 {
853 ASSERT(!m_databaseGuard.tryLock());
854 NameCountMap* nameMap = m_beingCreated.get(origin);
855 if (nameMap) {
856 long count = nameMap->get(name);
857 ASSERT(count > 0);
858 if (count <= 1) {
859 nameMap->remove(name);
860 if (nameMap->isEmpty()) {
861 m_beingCreated.remove(origin);
862 delete nameMap;
863 }
864 } else
865 nameMap->set(name, count - 1);
866 } else
867 ASSERT(false);
868 }
869
creatingDatabase(SecurityOrigin * origin,const String & name)870 bool DatabaseTracker::creatingDatabase(SecurityOrigin *origin, const String& name)
871 {
872 ASSERT(!m_databaseGuard.tryLock());
873 NameCountMap* nameMap = m_beingCreated.get(origin);
874 return nameMap && nameMap->get(name);
875 }
876
canDeleteDatabase(SecurityOrigin * origin,const String & name)877 bool DatabaseTracker::canDeleteDatabase(SecurityOrigin *origin, const String& name)
878 {
879 ASSERT(!m_databaseGuard.tryLock());
880 return !creatingDatabase(origin, name) && !deletingDatabase(origin, name);
881 }
882
recordDeletingDatabase(SecurityOrigin * origin,const String & name)883 void DatabaseTracker::recordDeletingDatabase(SecurityOrigin *origin, const String& name)
884 {
885 ASSERT(!m_databaseGuard.tryLock());
886 ASSERT(canDeleteDatabase(origin, name));
887 NameSet* nameSet = m_beingDeleted.get(origin);
888 if (!nameSet) {
889 nameSet = new NameSet();
890 m_beingDeleted.set(origin->threadsafeCopy(), nameSet);
891 }
892 ASSERT(!nameSet->contains(name));
893 nameSet->add(name.threadsafeCopy());
894 }
895
doneDeletingDatabase(SecurityOrigin * origin,const String & name)896 void DatabaseTracker::doneDeletingDatabase(SecurityOrigin *origin, const String& name)
897 {
898 ASSERT(!m_databaseGuard.tryLock());
899 NameSet* nameSet = m_beingDeleted.get(origin);
900 if (nameSet) {
901 ASSERT(nameSet->contains(name));
902 nameSet->remove(name);
903 if (nameSet->isEmpty()) {
904 m_beingDeleted.remove(origin);
905 delete nameSet;
906 }
907 } else {
908 ASSERT(false);
909 }
910 }
911
deletingDatabase(SecurityOrigin * origin,const String & name)912 bool DatabaseTracker::deletingDatabase(SecurityOrigin *origin, const String& name)
913 {
914 ASSERT(!m_databaseGuard.tryLock());
915 NameSet* nameSet = m_beingDeleted.get(origin);
916 return nameSet && nameSet->contains(name);
917 }
918
canDeleteOrigin(SecurityOrigin * origin)919 bool DatabaseTracker::canDeleteOrigin(SecurityOrigin *origin)
920 {
921 ASSERT(!m_databaseGuard.tryLock());
922 return !(deletingOrigin(origin) || m_beingCreated.get(origin));
923 }
924
deletingOrigin(SecurityOrigin * origin)925 bool DatabaseTracker::deletingOrigin(SecurityOrigin *origin)
926 {
927 ASSERT(!m_databaseGuard.tryLock());
928 return m_originsBeingDeleted.contains(origin);
929 }
930
recordDeletingOrigin(SecurityOrigin * origin)931 void DatabaseTracker::recordDeletingOrigin(SecurityOrigin *origin)
932 {
933 ASSERT(!m_databaseGuard.tryLock());
934 ASSERT(!deletingOrigin(origin));
935 m_originsBeingDeleted.add(origin->threadsafeCopy());
936 }
937
doneDeletingOrigin(SecurityOrigin * origin)938 void DatabaseTracker::doneDeletingOrigin(SecurityOrigin *origin)
939 {
940 ASSERT(!m_databaseGuard.tryLock());
941 ASSERT(deletingOrigin(origin));
942 m_originsBeingDeleted.remove(origin);
943 }
944
deleteDatabase(SecurityOrigin * origin,const String & name)945 bool DatabaseTracker::deleteDatabase(SecurityOrigin* origin, const String& name)
946 {
947 {
948 MutexLocker lockDatabase(m_databaseGuard);
949 openTrackerDatabase(false);
950 if (!m_database.isOpen())
951 return false;
952
953 if (!canDeleteDatabase(origin, name)) {
954 ASSERT(FALSE);
955 return false;
956 }
957 recordDeletingDatabase(origin, name);
958 }
959
960 // We drop the lock here because holding locks during a call to deleteDatabaseFile will deadlock.
961 if (!deleteDatabaseFile(origin, name)) {
962 LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin->databaseIdentifier().ascii().data());
963 MutexLocker lockDatabase(m_databaseGuard);
964 doneDeletingDatabase(origin, name);
965 return false;
966 }
967
968 MutexLocker lockDatabase(m_databaseGuard);
969
970 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
971 if (statement.prepare() != SQLResultOk) {
972 LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data());
973 doneDeletingDatabase(origin, name);
974 return false;
975 }
976
977 statement.bindText(1, origin->databaseIdentifier());
978 statement.bindText(2, name);
979
980 if (!statement.executeCommand()) {
981 LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data());
982 doneDeletingDatabase(origin, name);
983 return false;
984 }
985
986 {
987 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
988 originQuotaManager().removeDatabase(origin, name);
989 }
990
991 if (m_client) {
992 m_client->dispatchDidModifyOrigin(origin);
993 m_client->dispatchDidModifyDatabase(origin, name);
994 }
995 doneDeletingDatabase(origin, name);
996
997 return true;
998 }
999
1000 // deleteDatabaseFile has to release locks between looking up the list of databases to close and closing them. While this is in progress, the caller
1001 // is responsible for making sure no new databases are opened in the file to be deleted.
deleteDatabaseFile(SecurityOrigin * origin,const String & name)1002 bool DatabaseTracker::deleteDatabaseFile(SecurityOrigin* origin, const String& name)
1003 {
1004 String fullPath = fullPathForDatabase(origin, name, false);
1005 if (fullPath.isEmpty())
1006 return true;
1007
1008 #ifndef NDEBUG
1009 {
1010 MutexLocker lockDatabase(m_databaseGuard);
1011 ASSERT(deletingDatabase(origin, name) || deletingOrigin(origin));
1012 }
1013 #endif
1014
1015 Vector<RefPtr<AbstractDatabase> > deletedDatabases;
1016
1017 // Make sure not to hold the any locks when calling
1018 // Database::markAsDeletedAndClose(), since that can cause a deadlock
1019 // during the synchronous DatabaseThread call it triggers.
1020 {
1021 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
1022 if (m_openDatabaseMap) {
1023 // There are some open databases, lets check if they are for this origin.
1024 DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
1025 if (nameMap && nameMap->size()) {
1026 // There are some open databases for this origin, let's check
1027 // if they are this database by name.
1028 DatabaseSet* databaseSet = nameMap->get(name);
1029 if (databaseSet && databaseSet->size()) {
1030 // We have some database open with this name. Mark them as deleted.
1031 DatabaseSet::const_iterator end = databaseSet->end();
1032 for (DatabaseSet::const_iterator it = databaseSet->begin(); it != end; ++it)
1033 deletedDatabases.append(*it);
1034 }
1035 }
1036 }
1037 }
1038
1039 for (unsigned i = 0; i < deletedDatabases.size(); ++i)
1040 deletedDatabases[i]->markAsDeletedAndClose();
1041
1042 return SQLiteFileSystem::deleteDatabaseFile(fullPath);
1043 }
1044
setClient(DatabaseTrackerClient * client)1045 void DatabaseTracker::setClient(DatabaseTrackerClient* client)
1046 {
1047 m_client = client;
1048 }
1049
notificationMutex()1050 static Mutex& notificationMutex()
1051 {
1052 DEFINE_STATIC_LOCAL(Mutex, mutex, ());
1053 return mutex;
1054 }
1055
1056 typedef Vector<pair<RefPtr<SecurityOrigin>, String> > NotificationQueue;
1057
notificationQueue()1058 static NotificationQueue& notificationQueue()
1059 {
1060 DEFINE_STATIC_LOCAL(NotificationQueue, queue, ());
1061 return queue;
1062 }
1063
scheduleNotifyDatabaseChanged(SecurityOrigin * origin,const String & name)1064 void DatabaseTracker::scheduleNotifyDatabaseChanged(SecurityOrigin* origin, const String& name)
1065 {
1066 MutexLocker locker(notificationMutex());
1067
1068 notificationQueue().append(pair<RefPtr<SecurityOrigin>, String>(origin->threadsafeCopy(), name.crossThreadString()));
1069 scheduleForNotification();
1070 }
1071
1072 static bool notificationScheduled = false;
1073
scheduleForNotification()1074 void DatabaseTracker::scheduleForNotification()
1075 {
1076 ASSERT(!notificationMutex().tryLock());
1077
1078 if (!notificationScheduled) {
1079 callOnMainThread(DatabaseTracker::notifyDatabasesChanged, 0);
1080 notificationScheduled = true;
1081 }
1082 }
1083
notifyDatabasesChanged(void *)1084 void DatabaseTracker::notifyDatabasesChanged(void*)
1085 {
1086 // Note that if DatabaseTracker ever becomes non-singleton, we'll have to amend this notification
1087 // mechanism to include which tracker the notification goes out on as well.
1088 DatabaseTracker& theTracker(tracker());
1089
1090 NotificationQueue notifications;
1091 {
1092 MutexLocker locker(notificationMutex());
1093
1094 notifications.swap(notificationQueue());
1095
1096 notificationScheduled = false;
1097 }
1098
1099 if (!theTracker.m_client)
1100 return;
1101
1102 for (unsigned i = 0; i < notifications.size(); ++i)
1103 theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first.get(), notifications[i].second);
1104 }
1105
1106
1107 } // namespace WebCore
1108 #endif
1109