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