• Home
  • History
  • Annotate
  • 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 "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 
VerifyValidQuotaConfig(const char * key)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 {
Appendstorage::QuotaDatabase::QuotaTableImporter84   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.
QuotaTableEntry()92 QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
93     : type(kStorageTypeUnknown),
94       quota(0) {
95 }
96 
QuotaTableEntry(const std::string & host,StorageType type,int64 quota)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 
OriginInfoTableEntry()106 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
107     : type(kStorageTypeUnknown),
108       used_count(0) {
109 }
110 
OriginInfoTableEntry(const GURL & origin,StorageType type,int used_count,const base::Time & last_access_time,const base::Time & last_modified_time)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 ------------------------------------------------------------
QuotaDatabase(const base::FilePath & path)125 QuotaDatabase::QuotaDatabase(const base::FilePath& path)
126     : db_file_path_(path),
127       is_recreating_(false),
128       is_disabled_(false) {
129 }
130 
~QuotaDatabase()131 QuotaDatabase::~QuotaDatabase() {
132   if (db_) {
133     db_->CommitTransaction();
134   }
135 }
136 
CloseConnection()137 void QuotaDatabase::CloseConnection() {
138   meta_table_.reset();
139   db_.reset();
140 }
141 
GetHostQuota(const std::string & host,StorageType type,int64 * quota)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 
SetHostQuota(const std::string & host,StorageType type,int64 quota)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 
SetOriginLastAccessTime(const GURL & origin,StorageType type,base::Time last_access_time)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 
SetOriginLastModifiedTime(const GURL & origin,StorageType type,base::Time last_modified_time)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 
RegisterInitialOriginInfo(const std::set<GURL> & origins,StorageType type)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 
DeleteHostQuota(const std::string & host,StorageType type)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 
DeleteOriginInfo(const GURL & origin,StorageType type)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 
GetQuotaConfigValue(const char * key,int64 * value)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 
SetQuotaConfigValue(const char * key,int64 value)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 
GetLRUOrigin(StorageType type,const std::set<GURL> & exceptions,SpecialStoragePolicy * special_storage_policy,GURL * origin)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 
GetOriginsModifiedSince(StorageType type,std::set<GURL> * origins,base::Time modified_since)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 
IsOriginDatabaseBootstrapped()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 
SetOriginDatabaseBootstrapped(bool bootstrap_flag)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 
Commit()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 
ScheduleCommit()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 
FindOriginUsedCount(const GURL & origin,StorageType type,int * used_count)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 
LazyOpen(bool create_if_needed)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 
EnsureDatabaseVersion()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
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)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 
ResetSchema()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 
UpgradeSchema(int current_version)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 
DumpQuotaTable(const QuotaTableCallback & callback)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 
DumpOriginInfoTable(const OriginInfoTableCallback & callback)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 
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 }  // namespace storage
658