• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
VerifyValidQuotaConfig(const char * key)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 {
Appendquota::QuotaDatabase::QuotaTableImporter83   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.
QuotaTableEntry()91 QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
92     : type(kStorageTypeUnknown),
93       quota(0) {
94 }
95 
QuotaTableEntry(const std::string & host,StorageType type,int64 quota)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 
OriginInfoTableEntry()105 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
106     : type(kStorageTypeUnknown),
107       used_count(0) {
108 }
109 
OriginInfoTableEntry(const GURL & origin,StorageType type,int used_count,const base::Time & last_access_time,const base::Time & last_modified_time)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 ------------------------------------------------------------
QuotaDatabase(const base::FilePath & path)124 QuotaDatabase::QuotaDatabase(const base::FilePath& path)
125     : db_file_path_(path),
126       is_recreating_(false),
127       is_disabled_(false) {
128 }
129 
~QuotaDatabase()130 QuotaDatabase::~QuotaDatabase() {
131   if (db_) {
132     db_->CommitTransaction();
133   }
134 }
135 
CloseConnection()136 void QuotaDatabase::CloseConnection() {
137   meta_table_.reset();
138   db_.reset();
139 }
140 
GetHostQuota(const std::string & host,StorageType type,int64 * quota)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 
SetHostQuota(const std::string & host,StorageType type,int64 quota)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 
SetOriginLastAccessTime(const GURL & origin,StorageType type,base::Time last_access_time)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 
SetOriginLastModifiedTime(const GURL & origin,StorageType type,base::Time last_modified_time)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 
RegisterInitialOriginInfo(const std::set<GURL> & origins,StorageType type)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 
DeleteHostQuota(const std::string & host,StorageType type)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 
DeleteOriginInfo(const GURL & origin,StorageType type)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 
GetQuotaConfigValue(const char * key,int64 * value)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 
SetQuotaConfigValue(const char * key,int64 value)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 
GetLRUOrigin(StorageType type,const std::set<GURL> & exceptions,SpecialStoragePolicy * special_storage_policy,GURL * origin)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 
GetOriginsModifiedSince(StorageType type,std::set<GURL> * origins,base::Time modified_since)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 
IsOriginDatabaseBootstrapped()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 
SetOriginDatabaseBootstrapped(bool bootstrap_flag)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 
Commit()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 
ScheduleCommit()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 
FindOriginUsedCount(const GURL & origin,StorageType type,int * used_count)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 
LazyOpen(bool create_if_needed)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 
EnsureDatabaseVersion()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
CreateSchema(sql::Connection * database,sql::MetaTable * meta_table,int schema_version,int compatible_version,const TableSchema * tables,size_t tables_size,const IndexSchema * indexes,size_t indexes_size)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 
ResetSchema()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 
UpgradeSchema(int current_version)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 
DumpQuotaTable(QuotaTableCallback * callback)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 
DumpOriginInfoTable(OriginInfoTableCallback * callback)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 
operator <(const QuotaDatabase::QuotaTableEntry & lhs,const QuotaDatabase::QuotaTableEntry & rhs)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 
operator <(const QuotaDatabase::OriginInfoTableEntry & lhs,const QuotaDatabase::OriginInfoTableEntry & rhs)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