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 "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 (!file_util::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