1/* 2 * Copyright (C) 2010 Google 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 "AbstractDatabase.h" 31 32#if ENABLE(DATABASE) 33#include "DatabaseAuthorizer.h" 34#include "DatabaseTracker.h" 35#include "Logging.h" 36#include "SQLiteStatement.h" 37#include "ScriptExecutionContext.h" 38#include "SecurityOrigin.h" 39#include <wtf/HashMap.h> 40#include <wtf/HashSet.h> 41#include <wtf/PassRefPtr.h> 42#include <wtf/RefPtr.h> 43#include <wtf/StdLibExtras.h> 44#include <wtf/text/CString.h> 45#include <wtf/text/StringHash.h> 46 47namespace WebCore { 48 49static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString) 50{ 51 SQLiteStatement statement(db, query); 52 int result = statement.prepare(); 53 54 if (result != SQLResultOk) { 55 LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data()); 56 return false; 57 } 58 59 result = statement.step(); 60 if (result == SQLResultRow) { 61 resultString = statement.getColumnText(0); 62 return true; 63 } 64 if (result == SQLResultDone) { 65 resultString = String(); 66 return true; 67 } 68 69 LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data()); 70 return false; 71} 72 73static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value) 74{ 75 SQLiteStatement statement(db, query); 76 int result = statement.prepare(); 77 78 if (result != SQLResultOk) { 79 LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data()); 80 return false; 81 } 82 83 statement.bindText(1, value); 84 85 result = statement.step(); 86 if (result != SQLResultDone) { 87 LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data()); 88 return false; 89 } 90 91 return true; 92} 93 94// FIXME: move all guid-related functions to a DatabaseVersionTracker class. 95static Mutex& guidMutex() 96{ 97 // Note: We don't have to use AtomicallyInitializedStatic here because 98 // this function is called once in the constructor on the main thread 99 // before any other threads that call this function are used. 100 DEFINE_STATIC_LOCAL(Mutex, mutex, ()); 101 return mutex; 102} 103 104typedef HashMap<int, String> GuidVersionMap; 105static GuidVersionMap& guidToVersionMap() 106{ 107 DEFINE_STATIC_LOCAL(GuidVersionMap, map, ()); 108 return map; 109} 110 111// NOTE: Caller must lock guidMutex(). 112static inline void updateGuidVersionMap(int guid, String newVersion) 113{ 114 // Ensure the the mutex is locked. 115 ASSERT(!guidMutex().tryLock()); 116 117 // Note: It is not safe to put an empty string into the guidToVersionMap() map. 118 // That's because the map is cross-thread, but empty strings are per-thread. 119 // The copy() function makes a version of the string you can use on the current 120 // thread, but we need a string we can keep in a cross-thread data structure. 121 // FIXME: This is a quite-awkward restriction to have to program with. 122 123 // Map null string to empty string (see comment above). 124 guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy()); 125} 126 127typedef HashMap<int, HashSet<AbstractDatabase*>*> GuidDatabaseMap; 128static GuidDatabaseMap& guidToDatabaseMap() 129{ 130 DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ()); 131 return map; 132} 133 134static int guidForOriginAndName(const String& origin, const String& name) 135{ 136 String stringID = origin + "/" + name; 137 138 // Note: We don't have to use AtomicallyInitializedStatic here because 139 // this function is called once in the constructor on the main thread 140 // before any other threads that call this function are used. 141 DEFINE_STATIC_LOCAL(Mutex, stringIdentifierMutex, ()); 142 MutexLocker locker(stringIdentifierMutex); 143 typedef HashMap<String, int> IDGuidMap; 144 DEFINE_STATIC_LOCAL(IDGuidMap, stringIdentifierToGUIDMap, ()); 145 int guid = stringIdentifierToGUIDMap.get(stringID); 146 if (!guid) { 147 static int currentNewGUID = 1; 148 guid = currentNewGUID++; 149 stringIdentifierToGUIDMap.set(stringID, guid); 150 } 151 152 return guid; 153} 154 155static bool isDatabaseAvailable = true; 156 157bool AbstractDatabase::isAvailable() 158{ 159 return isDatabaseAvailable; 160} 161 162void AbstractDatabase::setIsAvailable(bool available) 163{ 164 isDatabaseAvailable = available; 165} 166 167// static 168const String& AbstractDatabase::databaseInfoTableName() 169{ 170 DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__")); 171 return name; 172} 173 174AbstractDatabase::AbstractDatabase(ScriptExecutionContext* context, const String& name, const String& expectedVersion, 175 const String& displayName, unsigned long estimatedSize) 176 : m_scriptExecutionContext(context) 177 , m_name(name.crossThreadString()) 178 , m_expectedVersion(expectedVersion.crossThreadString()) 179 , m_displayName(displayName.crossThreadString()) 180 , m_estimatedSize(estimatedSize) 181 , m_guid(0) 182 , m_opened(false) 183 , m_new(false) 184{ 185 ASSERT(context->isContextThread()); 186 m_contextThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin(); 187 188 m_databaseAuthorizer = DatabaseAuthorizer::create(databaseInfoTableName()); 189 190 if (m_name.isNull()) 191 m_name = ""; 192 193 m_guid = guidForOriginAndName(securityOrigin()->toString(), name); 194 { 195 MutexLocker locker(guidMutex()); 196 197 HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid); 198 if (!hashSet) { 199 hashSet = new HashSet<AbstractDatabase*>; 200 guidToDatabaseMap().set(m_guid, hashSet); 201 } 202 203 hashSet->add(this); 204 } 205 206 m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name); 207 DatabaseTracker::tracker().addOpenDatabase(this); 208} 209 210AbstractDatabase::~AbstractDatabase() 211{ 212} 213 214void AbstractDatabase::closeDatabase() 215{ 216 if (!m_opened) 217 return; 218 219 m_sqliteDatabase.close(); 220 m_opened = false; 221 { 222 MutexLocker locker(guidMutex()); 223 224 HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid); 225 ASSERT(hashSet); 226 ASSERT(hashSet->contains(this)); 227 hashSet->remove(this); 228 if (hashSet->isEmpty()) { 229 guidToDatabaseMap().remove(m_guid); 230 delete hashSet; 231 guidToVersionMap().remove(m_guid); 232 } 233 } 234} 235 236String AbstractDatabase::version() const 237{ 238 MutexLocker locker(guidMutex()); 239 return guidToVersionMap().get(m_guid).threadsafeCopy(); 240} 241 242static const int maxSqliteBusyWaitTime = 30000; 243bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec) 244{ 245 if (!m_sqliteDatabase.open(m_filename, true)) { 246 LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data()); 247 ec = INVALID_STATE_ERR; 248 return false; 249 } 250 if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum()) 251 LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data()); 252 253 ASSERT(m_databaseAuthorizer); 254 m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); 255 m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); 256 257 String currentVersion; 258 { 259 MutexLocker locker(guidMutex()); 260 261 GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid); 262 if (entry != guidToVersionMap().end()) { 263 // Map null string to empty string (see updateGuidVersionMap()). 264 currentVersion = entry->second.isNull() ? String("") : entry->second; 265 LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data()); 266 } else { 267 LOG(StorageAPI, "No cached version for guid %i", m_guid); 268 269 if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) { 270 m_new = true; 271 272 if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { 273 LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data()); 274 ec = INVALID_STATE_ERR; 275 // Close the handle to the database file. 276 m_sqliteDatabase.close(); 277 return false; 278 } 279 } 280 281 if (!getVersionFromDatabase(currentVersion)) { 282 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data()); 283 ec = INVALID_STATE_ERR; 284 // Close the handle to the database file. 285 m_sqliteDatabase.close(); 286 return false; 287 } 288 if (currentVersion.length()) { 289 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); 290 } else if (!m_new || shouldSetVersionInNewDatabase) { 291 LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); 292 if (!setVersionInDatabase(m_expectedVersion)) { 293 LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); 294 ec = INVALID_STATE_ERR; 295 // Close the handle to the database file. 296 m_sqliteDatabase.close(); 297 return false; 298 } 299 currentVersion = m_expectedVersion; 300 } 301 302 updateGuidVersionMap(m_guid, currentVersion); 303 } 304 } 305 306 if (currentVersion.isNull()) { 307 LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data()); 308 currentVersion = ""; 309 } 310 311 // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception. 312 // If the expected version is the empty string, then we always return with whatever version of the database we have. 313 if ((!m_new || shouldSetVersionInNewDatabase) && m_expectedVersion.length() && m_expectedVersion != currentVersion) { 314 LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(), 315 databaseDebugName().ascii().data(), currentVersion.ascii().data()); 316 ec = INVALID_STATE_ERR; 317 // Close the handle to the database file. 318 m_sqliteDatabase.close(); 319 return false; 320 } 321 322 m_opened = true; 323 324 return true; 325} 326 327ScriptExecutionContext* AbstractDatabase::scriptExecutionContext() const 328{ 329 return m_scriptExecutionContext.get(); 330} 331 332SecurityOrigin* AbstractDatabase::securityOrigin() const 333{ 334 return m_contextThreadSecurityOrigin.get(); 335} 336 337String AbstractDatabase::stringIdentifier() const 338{ 339 // Return a deep copy for ref counting thread safety 340 return m_name.threadsafeCopy(); 341} 342 343String AbstractDatabase::displayName() const 344{ 345 // Return a deep copy for ref counting thread safety 346 return m_displayName.threadsafeCopy(); 347} 348 349unsigned long AbstractDatabase::estimatedSize() const 350{ 351 return m_estimatedSize; 352} 353 354String AbstractDatabase::fileName() const 355{ 356 // Return a deep copy for ref counting thread safety 357 return m_filename.threadsafeCopy(); 358} 359 360// static 361const String& AbstractDatabase::databaseVersionKey() 362{ 363 DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey")); 364 return key; 365} 366 367bool AbstractDatabase::getVersionFromDatabase(String& version) 368{ 369 DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';")); 370 371 m_databaseAuthorizer->disable(); 372 373 bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version); 374 if (!result) 375 LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); 376 377 m_databaseAuthorizer->enable(); 378 379 return result; 380} 381 382bool AbstractDatabase::setVersionInDatabase(const String& version) 383{ 384 // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE 385 // clause in the CREATE statement (see Database::performOpenAndVerify()). 386 DEFINE_STATIC_LOCAL(String, setVersionQuery, ("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);")); 387 388 m_databaseAuthorizer->disable(); 389 390 bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version); 391 if (!result) 392 LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data()); 393 394 m_databaseAuthorizer->enable(); 395 396 return result; 397} 398 399bool AbstractDatabase::versionMatchesExpected() const 400{ 401 if (!m_expectedVersion.isEmpty()) { 402 MutexLocker locker(guidMutex()); 403 return m_expectedVersion == guidToVersionMap().get(m_guid); 404 } 405 406 return true; 407} 408 409void AbstractDatabase::setExpectedVersion(const String& version) 410{ 411 m_expectedVersion = version.threadsafeCopy(); 412 // Update the in memory database version map. 413 MutexLocker locker(guidMutex()); 414 updateGuidVersionMap(m_guid, version); 415} 416 417void AbstractDatabase::disableAuthorizer() 418{ 419 ASSERT(m_databaseAuthorizer); 420 m_databaseAuthorizer->disable(); 421} 422 423void AbstractDatabase::enableAuthorizer() 424{ 425 ASSERT(m_databaseAuthorizer); 426 m_databaseAuthorizer->enable(); 427} 428 429void AbstractDatabase::setAuthorizerReadOnly() 430{ 431 ASSERT(m_databaseAuthorizer); 432 m_databaseAuthorizer->setReadOnly(); 433} 434 435void AbstractDatabase::setAuthorizerPermissions(int permissions) 436{ 437 ASSERT(m_databaseAuthorizer); 438 m_databaseAuthorizer->setPermissions(permissions); 439} 440 441bool AbstractDatabase::lastActionChangedDatabase() 442{ 443 ASSERT(m_databaseAuthorizer); 444 return m_databaseAuthorizer->lastActionChangedDatabase(); 445} 446 447bool AbstractDatabase::lastActionWasInsert() 448{ 449 ASSERT(m_databaseAuthorizer); 450 return m_databaseAuthorizer->lastActionWasInsert(); 451} 452 453void AbstractDatabase::resetDeletes() 454{ 455 ASSERT(m_databaseAuthorizer); 456 m_databaseAuthorizer->resetDeletes(); 457} 458 459bool AbstractDatabase::hadDeletes() 460{ 461 ASSERT(m_databaseAuthorizer); 462 return m_databaseAuthorizer->hadDeletes(); 463} 464 465void AbstractDatabase::resetAuthorizer() 466{ 467 if (m_databaseAuthorizer) 468 m_databaseAuthorizer->reset(); 469} 470 471unsigned long long AbstractDatabase::maximumSize() const 472{ 473 return DatabaseTracker::tracker().getMaxSizeForDatabase(this); 474} 475 476void AbstractDatabase::incrementalVacuumIfNeeded() 477{ 478 int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize(); 479 int64_t totalSize = m_sqliteDatabase.totalSize(); 480 if (totalSize <= 10 * freeSpaceSize) 481 m_sqliteDatabase.runIncrementalVacuumCommand(); 482} 483 484void AbstractDatabase::interrupt() 485{ 486 m_sqliteDatabase.interrupt(); 487} 488 489bool AbstractDatabase::isInterrupted() 490{ 491 MutexLocker locker(m_sqliteDatabase.databaseMutex()); 492 return m_sqliteDatabase.isInterrupted(); 493} 494 495} // namespace WebCore 496 497#endif // ENABLE(DATABASE) 498