Home | History | Annotate | Download | only in quota
      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