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