1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "webkit/browser/quota/quota_database.h" 6 7 #include <string> 8 9 #include "base/auto_reset.h" 10 #include "base/bind.h" 11 #include "base/file_util.h" 12 #include "base/time/time.h" 13 #include "sql/connection.h" 14 #include "sql/meta_table.h" 15 #include "sql/statement.h" 16 #include "sql/transaction.h" 17 #include "url/gurl.h" 18 #include "webkit/browser/quota/special_storage_policy.h" 19 20 namespace quota { 21 namespace { 22 23 // Definitions for database schema. 24 25 const int kCurrentVersion = 4; 26 const int kCompatibleVersion = 2; 27 28 const char kHostQuotaTable[] = "HostQuotaTable"; 29 const char kOriginInfoTable[] = "OriginInfoTable"; 30 const char kIsOriginTableBootstrapped[] = "IsOriginTableBootstrapped"; 31 32 bool VerifyValidQuotaConfig(const char* key) { 33 return (key != NULL && 34 (!strcmp(key, QuotaDatabase::kDesiredAvailableSpaceKey) || 35 !strcmp(key, QuotaDatabase::kTemporaryQuotaOverrideKey))); 36 } 37 38 const int kCommitIntervalMs = 30000; 39 40 } // anonymous namespace 41 42 // static 43 const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace"; 44 const char QuotaDatabase::kTemporaryQuotaOverrideKey[] = 45 "TemporaryQuotaOverride"; 46 47 const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = { 48 { kHostQuotaTable, 49 "(host TEXT NOT NULL," 50 " type INTEGER NOT NULL," 51 " quota INTEGER DEFAULT 0," 52 " UNIQUE(host, type))" }, 53 { kOriginInfoTable, 54 "(origin TEXT NOT NULL," 55 " type INTEGER NOT NULL," 56 " used_count INTEGER DEFAULT 0," 57 " last_access_time INTEGER DEFAULT 0," 58 " last_modified_time INTEGER DEFAULT 0," 59 " UNIQUE(origin, type))" }, 60 }; 61 62 // static 63 const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = { 64 { "HostIndex", 65 kHostQuotaTable, 66 "(host)", 67 false }, 68 { "OriginInfoIndex", 69 kOriginInfoTable, 70 "(origin)", 71 false }, 72 { "OriginLastAccessTimeIndex", 73 kOriginInfoTable, 74 "(last_access_time)", 75 false }, 76 { "OriginLastModifiedTimeIndex", 77 kOriginInfoTable, 78 "(last_modified_time)", 79 false }, 80 }; 81 82 struct QuotaDatabase::QuotaTableImporter { 83 bool Append(const QuotaTableEntry& entry) { 84 entries.push_back(entry); 85 return true; 86 } 87 std::vector<QuotaTableEntry> entries; 88 }; 89 90 // Clang requires explicit out-of-line constructors for them. 91 QuotaDatabase::QuotaTableEntry::QuotaTableEntry() 92 : type(kStorageTypeUnknown), 93 quota(0) { 94 } 95 96 QuotaDatabase::QuotaTableEntry::QuotaTableEntry( 97 const std::string& host, 98 StorageType type, 99 int64 quota) 100 : host(host), 101 type(type), 102 quota(quota) { 103 } 104 105 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry() 106 : type(kStorageTypeUnknown), 107 used_count(0) { 108 } 109 110 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry( 111 const GURL& origin, 112 StorageType type, 113 int used_count, 114 const base::Time& last_access_time, 115 const base::Time& last_modified_time) 116 : origin(origin), 117 type(type), 118 used_count(used_count), 119 last_access_time(last_access_time), 120 last_modified_time(last_modified_time) { 121 } 122 123 // QuotaDatabase ------------------------------------------------------------ 124 QuotaDatabase::QuotaDatabase(const base::FilePath& path) 125 : db_file_path_(path), 126 is_recreating_(false), 127 is_disabled_(false) { 128 } 129 130 QuotaDatabase::~QuotaDatabase() { 131 if (db_) { 132 db_->CommitTransaction(); 133 } 134 } 135 136 void QuotaDatabase::CloseConnection() { 137 meta_table_.reset(); 138 db_.reset(); 139 } 140 141 bool QuotaDatabase::GetHostQuota( 142 const std::string& host, StorageType type, int64* quota) { 143 DCHECK(quota); 144 if (!LazyOpen(false)) 145 return false; 146 147 const char* kSql = 148 "SELECT quota" 149 " FROM HostQuotaTable" 150 " WHERE host = ? AND type = ?"; 151 152 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 153 statement.BindString(0, host); 154 statement.BindInt(1, static_cast<int>(type)); 155 156 if (!statement.Step()) 157 return false; 158 159 *quota = statement.ColumnInt64(0); 160 return true; 161 } 162 163 bool QuotaDatabase::SetHostQuota( 164 const std::string& host, StorageType type, int64 quota) { 165 DCHECK_GE(quota, 0); 166 if (!LazyOpen(true)) 167 return false; 168 169 const char* kSql = 170 "INSERT OR REPLACE INTO HostQuotaTable" 171 " (quota, host, type)" 172 " VALUES (?, ?, ?)"; 173 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 174 statement.BindInt64(0, quota); 175 statement.BindString(1, host); 176 statement.BindInt(2, static_cast<int>(type)); 177 178 if (!statement.Run()) 179 return false; 180 181 ScheduleCommit(); 182 return true; 183 } 184 185 bool QuotaDatabase::SetOriginLastAccessTime( 186 const GURL& origin, StorageType type, base::Time last_access_time) { 187 if (!LazyOpen(true)) 188 return false; 189 190 sql::Statement statement; 191 192 int used_count = 1; 193 if (FindOriginUsedCount(origin, type, &used_count)) { 194 ++used_count; 195 const char* kSql = 196 "UPDATE OriginInfoTable" 197 " SET used_count = ?, last_access_time = ?" 198 " WHERE origin = ? AND type = ?"; 199 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 200 } else { 201 const char* kSql = 202 "INSERT INTO OriginInfoTable" 203 " (used_count, last_access_time, origin, type)" 204 " VALUES (?, ?, ?, ?)"; 205 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 206 } 207 statement.BindInt(0, used_count); 208 statement.BindInt64(1, last_access_time.ToInternalValue()); 209 statement.BindString(2, origin.spec()); 210 statement.BindInt(3, static_cast<int>(type)); 211 212 if (!statement.Run()) 213 return false; 214 215 ScheduleCommit(); 216 return true; 217 } 218 219 bool QuotaDatabase::SetOriginLastModifiedTime( 220 const GURL& origin, StorageType type, base::Time last_modified_time) { 221 if (!LazyOpen(true)) 222 return false; 223 224 sql::Statement statement; 225 226 int dummy; 227 if (FindOriginUsedCount(origin, type, &dummy)) { 228 const char* kSql = 229 "UPDATE OriginInfoTable" 230 " SET last_modified_time = ?" 231 " WHERE origin = ? AND type = ?"; 232 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 233 } else { 234 const char* kSql = 235 "INSERT INTO OriginInfoTable" 236 " (last_modified_time, origin, type) VALUES (?, ?, ?)"; 237 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 238 } 239 statement.BindInt64(0, last_modified_time.ToInternalValue()); 240 statement.BindString(1, origin.spec()); 241 statement.BindInt(2, static_cast<int>(type)); 242 243 if (!statement.Run()) 244 return false; 245 246 ScheduleCommit(); 247 return true; 248 } 249 250 bool QuotaDatabase::RegisterInitialOriginInfo( 251 const std::set<GURL>& origins, StorageType type) { 252 if (!LazyOpen(true)) 253 return false; 254 255 typedef std::set<GURL>::const_iterator itr_type; 256 for (itr_type itr = origins.begin(), end = origins.end(); 257 itr != end; ++itr) { 258 const char* kSql = 259 "INSERT OR IGNORE INTO OriginInfoTable" 260 " (origin, type) VALUES (?, ?)"; 261 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 262 statement.BindString(0, itr->spec()); 263 statement.BindInt(1, static_cast<int>(type)); 264 265 if (!statement.Run()) 266 return false; 267 } 268 269 ScheduleCommit(); 270 return true; 271 } 272 273 bool QuotaDatabase::DeleteHostQuota( 274 const std::string& host, StorageType type) { 275 if (!LazyOpen(false)) 276 return false; 277 278 const char* kSql = 279 "DELETE FROM HostQuotaTable" 280 " WHERE host = ? AND type = ?"; 281 282 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 283 statement.BindString(0, host); 284 statement.BindInt(1, static_cast<int>(type)); 285 286 if (!statement.Run()) 287 return false; 288 289 ScheduleCommit(); 290 return true; 291 } 292 293 bool QuotaDatabase::DeleteOriginInfo( 294 const GURL& origin, StorageType type) { 295 if (!LazyOpen(false)) 296 return false; 297 298 const char* kSql = 299 "DELETE FROM OriginInfoTable" 300 " WHERE origin = ? AND type = ?"; 301 302 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 303 statement.BindString(0, origin.spec()); 304 statement.BindInt(1, static_cast<int>(type)); 305 306 if (!statement.Run()) 307 return false; 308 309 ScheduleCommit(); 310 return true; 311 } 312 313 bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64* value) { 314 if (!LazyOpen(false)) 315 return false; 316 DCHECK(VerifyValidQuotaConfig(key)); 317 return meta_table_->GetValue(key, value); 318 } 319 320 bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64 value) { 321 if (!LazyOpen(true)) 322 return false; 323 DCHECK(VerifyValidQuotaConfig(key)); 324 return meta_table_->SetValue(key, value); 325 } 326 327 bool QuotaDatabase::GetLRUOrigin( 328 StorageType type, 329 const std::set<GURL>& exceptions, 330 SpecialStoragePolicy* special_storage_policy, 331 GURL* origin) { 332 DCHECK(origin); 333 if (!LazyOpen(false)) 334 return false; 335 336 const char* kSql = "SELECT origin FROM OriginInfoTable" 337 " WHERE type = ?" 338 " ORDER BY last_access_time ASC"; 339 340 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 341 statement.BindInt(0, static_cast<int>(type)); 342 343 while (statement.Step()) { 344 GURL url(statement.ColumnString(0)); 345 if (exceptions.find(url) != exceptions.end()) 346 continue; 347 if (special_storage_policy && 348 special_storage_policy->IsStorageUnlimited(url)) 349 continue; 350 *origin = url; 351 return true; 352 } 353 354 *origin = GURL(); 355 return statement.Succeeded(); 356 } 357 358 bool QuotaDatabase::GetOriginsModifiedSince( 359 StorageType type, std::set<GURL>* origins, base::Time modified_since) { 360 DCHECK(origins); 361 if (!LazyOpen(false)) 362 return false; 363 364 const char* kSql = "SELECT origin FROM OriginInfoTable" 365 " WHERE type = ? AND last_modified_time >= ?"; 366 367 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 368 statement.BindInt(0, static_cast<int>(type)); 369 statement.BindInt64(1, modified_since.ToInternalValue()); 370 371 origins->clear(); 372 while (statement.Step()) 373 origins->insert(GURL(statement.ColumnString(0))); 374 375 return statement.Succeeded(); 376 } 377 378 bool QuotaDatabase::IsOriginDatabaseBootstrapped() { 379 if (!LazyOpen(true)) 380 return false; 381 382 int flag = 0; 383 return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag; 384 } 385 386 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) { 387 if (!LazyOpen(true)) 388 return false; 389 390 return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag); 391 } 392 393 void QuotaDatabase::Commit() { 394 if (!db_) 395 return; 396 397 if (timer_.IsRunning()) 398 timer_.Stop(); 399 400 db_->CommitTransaction(); 401 db_->BeginTransaction(); 402 } 403 404 void QuotaDatabase::ScheduleCommit() { 405 if (timer_.IsRunning()) 406 return; 407 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs), 408 this, &QuotaDatabase::Commit); 409 } 410 411 bool QuotaDatabase::FindOriginUsedCount( 412 const GURL& origin, StorageType type, int* used_count) { 413 DCHECK(used_count); 414 if (!LazyOpen(false)) 415 return false; 416 417 const char* kSql = 418 "SELECT used_count FROM OriginInfoTable" 419 " WHERE origin = ? AND type = ?"; 420 421 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 422 statement.BindString(0, origin.spec()); 423 statement.BindInt(1, static_cast<int>(type)); 424 425 if (!statement.Step()) 426 return false; 427 428 *used_count = statement.ColumnInt(0); 429 return true; 430 } 431 432 bool QuotaDatabase::LazyOpen(bool create_if_needed) { 433 if (db_) 434 return true; 435 436 // If we tried and failed once, don't try again in the same session 437 // to avoid creating an incoherent mess on disk. 438 if (is_disabled_) 439 return false; 440 441 bool in_memory_only = db_file_path_.empty(); 442 if (!create_if_needed && 443 (in_memory_only || !base::PathExists(db_file_path_))) { 444 return false; 445 } 446 447 db_.reset(new sql::Connection); 448 meta_table_.reset(new sql::MetaTable); 449 450 db_->set_histogram_tag("Quota"); 451 452 bool opened = false; 453 if (in_memory_only) { 454 opened = db_->OpenInMemory(); 455 } else if (!base::CreateDirectory(db_file_path_.DirName())) { 456 LOG(ERROR) << "Failed to create quota database directory."; 457 } else { 458 opened = db_->Open(db_file_path_); 459 if (opened) 460 db_->Preload(); 461 } 462 463 if (!opened || !EnsureDatabaseVersion()) { 464 LOG(ERROR) << "Failed to open the quota database."; 465 is_disabled_ = true; 466 db_.reset(); 467 meta_table_.reset(); 468 return false; 469 } 470 471 // Start a long-running transaction. 472 db_->BeginTransaction(); 473 474 return true; 475 } 476 477 bool QuotaDatabase::EnsureDatabaseVersion() { 478 static const size_t kTableCount = ARRAYSIZE_UNSAFE(kTables); 479 static const size_t kIndexCount = ARRAYSIZE_UNSAFE(kIndexes); 480 if (!sql::MetaTable::DoesTableExist(db_.get())) 481 return CreateSchema(db_.get(), meta_table_.get(), 482 kCurrentVersion, kCompatibleVersion, 483 kTables, kTableCount, 484 kIndexes, kIndexCount); 485 486 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion)) 487 return false; 488 489 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) { 490 LOG(WARNING) << "Quota database is too new."; 491 return false; 492 } 493 494 if (meta_table_->GetVersionNumber() < kCurrentVersion) { 495 if (!UpgradeSchema(meta_table_->GetVersionNumber())) 496 return ResetSchema(); 497 } 498 499 #ifndef NDEBUG 500 DCHECK(sql::MetaTable::DoesTableExist(db_.get())); 501 for (size_t i = 0; i < kTableCount; ++i) { 502 DCHECK(db_->DoesTableExist(kTables[i].table_name)); 503 } 504 #endif 505 506 return true; 507 } 508 509 // static 510 bool QuotaDatabase::CreateSchema( 511 sql::Connection* database, 512 sql::MetaTable* meta_table, 513 int schema_version, int compatible_version, 514 const TableSchema* tables, size_t tables_size, 515 const IndexSchema* indexes, size_t indexes_size) { 516 // TODO(kinuko): Factor out the common code to create databases. 517 sql::Transaction transaction(database); 518 if (!transaction.Begin()) 519 return false; 520 521 if (!meta_table->Init(database, schema_version, compatible_version)) 522 return false; 523 524 for (size_t i = 0; i < tables_size; ++i) { 525 std::string sql("CREATE TABLE "); 526 sql += tables[i].table_name; 527 sql += tables[i].columns; 528 if (!database->Execute(sql.c_str())) { 529 VLOG(1) << "Failed to execute " << sql; 530 return false; 531 } 532 } 533 534 for (size_t i = 0; i < indexes_size; ++i) { 535 std::string sql; 536 if (indexes[i].unique) 537 sql += "CREATE UNIQUE INDEX "; 538 else 539 sql += "CREATE INDEX "; 540 sql += indexes[i].index_name; 541 sql += " ON "; 542 sql += indexes[i].table_name; 543 sql += indexes[i].columns; 544 if (!database->Execute(sql.c_str())) { 545 VLOG(1) << "Failed to execute " << sql; 546 return false; 547 } 548 } 549 550 return transaction.Commit(); 551 } 552 553 bool QuotaDatabase::ResetSchema() { 554 DCHECK(!db_file_path_.empty()); 555 DCHECK(base::PathExists(db_file_path_)); 556 VLOG(1) << "Deleting existing quota data and starting over."; 557 558 db_.reset(); 559 meta_table_.reset(); 560 561 if (!sql::Connection::Delete(db_file_path_)) 562 return false; 563 564 // So we can't go recursive. 565 if (is_recreating_) 566 return false; 567 568 base::AutoReset<bool> auto_reset(&is_recreating_, true); 569 return LazyOpen(true); 570 } 571 572 bool QuotaDatabase::UpgradeSchema(int current_version) { 573 if (current_version == 2) { 574 QuotaTableImporter importer; 575 typedef std::vector<QuotaTableEntry> QuotaTableEntries; 576 if (!DumpQuotaTable(new QuotaTableCallback(base::Bind( 577 &QuotaTableImporter::Append, base::Unretained(&importer))))) 578 return false; 579 ResetSchema(); 580 for (QuotaTableEntries::const_iterator iter = importer.entries.begin(); 581 iter != importer.entries.end(); ++iter) { 582 if (!SetHostQuota(iter->host, iter->type, iter->quota)) 583 return false; 584 } 585 Commit(); 586 return true; 587 } 588 return false; 589 } 590 591 bool QuotaDatabase::DumpQuotaTable(QuotaTableCallback* callback) { 592 scoped_ptr<QuotaTableCallback> callback_deleter(callback); 593 if (!LazyOpen(true)) 594 return false; 595 596 const char* kSql = "SELECT * FROM HostQuotaTable"; 597 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 598 599 while (statement.Step()) { 600 QuotaTableEntry entry = QuotaTableEntry( 601 statement.ColumnString(0), 602 static_cast<StorageType>(statement.ColumnInt(1)), 603 statement.ColumnInt64(2)); 604 605 if (!callback->Run(entry)) 606 return true; 607 } 608 609 return statement.Succeeded(); 610 } 611 612 bool QuotaDatabase::DumpOriginInfoTable( 613 OriginInfoTableCallback* callback) { 614 scoped_ptr<OriginInfoTableCallback> callback_deleter(callback); 615 616 if (!LazyOpen(true)) 617 return false; 618 619 const char* kSql = "SELECT * FROM OriginInfoTable"; 620 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); 621 622 while (statement.Step()) { 623 OriginInfoTableEntry entry( 624 GURL(statement.ColumnString(0)), 625 static_cast<StorageType>(statement.ColumnInt(1)), 626 statement.ColumnInt(2), 627 base::Time::FromInternalValue(statement.ColumnInt64(3)), 628 base::Time::FromInternalValue(statement.ColumnInt64(4))); 629 630 if (!callback->Run(entry)) 631 return true; 632 } 633 634 return statement.Succeeded(); 635 } 636 637 bool operator<(const QuotaDatabase::QuotaTableEntry& lhs, 638 const QuotaDatabase::QuotaTableEntry& rhs) { 639 if (lhs.host < rhs.host) return true; 640 if (rhs.host < lhs.host) return false; 641 if (lhs.type < rhs.type) return true; 642 if (rhs.type < lhs.type) return false; 643 return lhs.quota < rhs.quota; 644 } 645 646 bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs, 647 const QuotaDatabase::OriginInfoTableEntry& rhs) { 648 if (lhs.origin < rhs.origin) return true; 649 if (rhs.origin < lhs.origin) return false; 650 if (lhs.type < rhs.type) return true; 651 if (rhs.type < lhs.type) return false; 652 if (lhs.used_count < rhs.used_count) return true; 653 if (rhs.used_count < lhs.used_count) return false; 654 return lhs.last_access_time < rhs.last_access_time; 655 } 656 657 } // quota namespace 658