1 // Copyright 2014 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 "content/browser/service_worker/service_worker_database.h"
6
7 #include <string>
8
9 #include "base/file_util.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "content/browser/service_worker/service_worker_database.pb.h"
19 #include "content/common/service_worker/service_worker_types.h"
20 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
21 #include "third_party/leveldatabase/src/include/leveldb/db.h"
22 #include "third_party/leveldatabase/src/include/leveldb/env.h"
23 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
24
25 // LevelDB database schema
26 // =======================
27 //
28 // NOTE
29 // - int64 value is serialized as a string by base::Int64ToString().
30 // - GURL value is serialized as a string by GURL::spec().
31 //
32 // Version 1 (in sorted order)
33 // key: "INITDATA_DB_VERSION"
34 // value: "1"
35 //
36 // key: "INITDATA_NEXT_REGISTRATION_ID"
37 // value: <int64 'next_available_registration_id'>
38 //
39 // key: "INITDATA_NEXT_RESOURCE_ID"
40 // value: <int64 'next_available_resource_id'>
41 //
42 // key: "INITDATA_NEXT_VERSION_ID"
43 // value: <int64 'next_available_version_id'>
44 //
45 // key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin'>
46 // value: <empty>
47 //
48 // key: "PRES:" + <int64 'purgeable_resource_id'>
49 // value: <empty>
50 //
51 // key: "REG:" + <GURL 'origin'> + '\x00' + <int64 'registration_id'>
52 // (ex. "REG:http://example.com\x00123456")
53 // value: <ServiceWorkerRegistrationData serialized as a string>
54 //
55 // key: "RES:" + <int64 'version_id'> + '\x00' + <int64 'resource_id'>
56 // (ex. "RES:123456\x00654321")
57 // value: <ServiceWorkerResourceRecord serialized as a string>
58 //
59 // key: "URES:" + <int64 'uncommitted_resource_id'>
60 // value: <empty>
61
62 namespace content {
63
64 namespace {
65
66 const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION";
67 const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID";
68 const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID";
69 const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID";
70 const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:";
71
72 const char kRegKeyPrefix[] = "REG:";
73 const char kResKeyPrefix[] = "RES:";
74 const char kKeySeparator = '\x00';
75
76 const char kUncommittedResIdKeyPrefix[] = "URES:";
77 const char kPurgeableResIdKeyPrefix[] = "PRES:";
78
79 const int64 kCurrentSchemaVersion = 1;
80
81 // For histogram.
82 const char kOpenResultHistogramLabel[] =
83 "ServiceWorker.Database.OpenResult";
84 const char kReadResultHistogramLabel[] =
85 "ServiceWorker.Database.ReadResult";
86 const char kWriteResultHistogramLabel[] =
87 "ServiceWorker.Database.WriteResult";
88
RemovePrefix(const std::string & str,const std::string & prefix,std::string * out)89 bool RemovePrefix(const std::string& str,
90 const std::string& prefix,
91 std::string* out) {
92 if (!StartsWithASCII(str, prefix, true))
93 return false;
94 if (out)
95 *out = str.substr(prefix.size());
96 return true;
97 }
98
CreateRegistrationKey(int64 registration_id,const GURL & origin)99 std::string CreateRegistrationKey(int64 registration_id,
100 const GURL& origin) {
101 return base::StringPrintf("%s%s%c%s",
102 kRegKeyPrefix,
103 origin.spec().c_str(),
104 kKeySeparator,
105 base::Int64ToString(registration_id).c_str());
106 }
107
CreateResourceRecordKeyPrefix(int64 version_id)108 std::string CreateResourceRecordKeyPrefix(int64 version_id) {
109 return base::StringPrintf("%s%s%c",
110 kResKeyPrefix,
111 base::Int64ToString(version_id).c_str(),
112 kKeySeparator);
113 }
114
CreateResourceRecordKey(int64 version_id,int64 resource_id)115 std::string CreateResourceRecordKey(int64 version_id,
116 int64 resource_id) {
117 return CreateResourceRecordKeyPrefix(version_id).append(
118 base::Int64ToString(resource_id));
119 }
120
CreateUniqueOriginKey(const GURL & origin)121 std::string CreateUniqueOriginKey(const GURL& origin) {
122 return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str());
123 }
124
CreateResourceIdKey(const char * key_prefix,int64 resource_id)125 std::string CreateResourceIdKey(const char* key_prefix, int64 resource_id) {
126 return base::StringPrintf(
127 "%s%s", key_prefix, base::Int64ToString(resource_id).c_str());
128 }
129
PutRegistrationDataToBatch(const ServiceWorkerDatabase::RegistrationData & input,leveldb::WriteBatch * batch)130 void PutRegistrationDataToBatch(
131 const ServiceWorkerDatabase::RegistrationData& input,
132 leveldb::WriteBatch* batch) {
133 DCHECK(batch);
134
135 // Convert RegistrationData to ServiceWorkerRegistrationData.
136 ServiceWorkerRegistrationData data;
137 data.set_registration_id(input.registration_id);
138 data.set_scope_url(input.scope.spec());
139 data.set_script_url(input.script.spec());
140 data.set_version_id(input.version_id);
141 data.set_is_active(input.is_active);
142 data.set_has_fetch_handler(input.has_fetch_handler);
143 data.set_last_update_check_time(input.last_update_check.ToInternalValue());
144
145 std::string value;
146 bool success = data.SerializeToString(&value);
147 DCHECK(success);
148 GURL origin = input.scope.GetOrigin();
149 batch->Put(CreateRegistrationKey(data.registration_id(), origin), value);
150 }
151
PutResourceRecordToBatch(const ServiceWorkerDatabase::ResourceRecord & input,int64 version_id,leveldb::WriteBatch * batch)152 void PutResourceRecordToBatch(
153 const ServiceWorkerDatabase::ResourceRecord& input,
154 int64 version_id,
155 leveldb::WriteBatch* batch) {
156 DCHECK(batch);
157
158 // Convert ResourceRecord to ServiceWorkerResourceRecord.
159 ServiceWorkerResourceRecord record;
160 record.set_resource_id(input.resource_id);
161 record.set_url(input.url.spec());
162
163 std::string value;
164 bool success = record.SerializeToString(&value);
165 DCHECK(success);
166 batch->Put(CreateResourceRecordKey(version_id, input.resource_id), value);
167 }
168
PutUniqueOriginToBatch(const GURL & origin,leveldb::WriteBatch * batch)169 void PutUniqueOriginToBatch(const GURL& origin,
170 leveldb::WriteBatch* batch) {
171 // Value should be empty.
172 batch->Put(CreateUniqueOriginKey(origin), "");
173 }
174
PutPurgeableResourceIdToBatch(int64 resource_id,leveldb::WriteBatch * batch)175 void PutPurgeableResourceIdToBatch(int64 resource_id,
176 leveldb::WriteBatch* batch) {
177 // Value should be empty.
178 batch->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix, resource_id), "");
179 }
180
ParseId(const std::string & serialized,int64 * out)181 ServiceWorkerDatabase::Status ParseId(
182 const std::string& serialized,
183 int64* out) {
184 DCHECK(out);
185 int64 id;
186 if (!base::StringToInt64(serialized, &id) || id < 0)
187 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
188 *out = id;
189 return ServiceWorkerDatabase::STATUS_OK;
190 }
191
ParseDatabaseVersion(const std::string & serialized,int64 * out)192 ServiceWorkerDatabase::Status ParseDatabaseVersion(
193 const std::string& serialized,
194 int64* out) {
195 DCHECK(out);
196 const int kFirstValidVersion = 1;
197 int64 version;
198 if (!base::StringToInt64(serialized, &version) ||
199 version < kFirstValidVersion) {
200 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
201 }
202 if (kCurrentSchemaVersion < version) {
203 DLOG(ERROR) << "ServiceWorkerDatabase has newer schema version"
204 << " than the current latest version: "
205 << version << " vs " << kCurrentSchemaVersion;
206 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
207 }
208 *out = version;
209 return ServiceWorkerDatabase::STATUS_OK;
210 }
211
ParseRegistrationData(const std::string & serialized,ServiceWorkerDatabase::RegistrationData * out)212 ServiceWorkerDatabase::Status ParseRegistrationData(
213 const std::string& serialized,
214 ServiceWorkerDatabase::RegistrationData* out) {
215 DCHECK(out);
216 ServiceWorkerRegistrationData data;
217 if (!data.ParseFromString(serialized))
218 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
219
220 GURL scope_url(data.scope_url());
221 GURL script_url(data.script_url());
222 if (!scope_url.is_valid() ||
223 !script_url.is_valid() ||
224 scope_url.GetOrigin() != script_url.GetOrigin()) {
225 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
226 }
227
228 // Convert ServiceWorkerRegistrationData to RegistrationData.
229 out->registration_id = data.registration_id();
230 out->scope = scope_url;
231 out->script = script_url;
232 out->version_id = data.version_id();
233 out->is_active = data.is_active();
234 out->has_fetch_handler = data.has_fetch_handler();
235 out->last_update_check =
236 base::Time::FromInternalValue(data.last_update_check_time());
237 return ServiceWorkerDatabase::STATUS_OK;
238 }
239
ParseResourceRecord(const std::string & serialized,ServiceWorkerDatabase::ResourceRecord * out)240 ServiceWorkerDatabase::Status ParseResourceRecord(
241 const std::string& serialized,
242 ServiceWorkerDatabase::ResourceRecord* out) {
243 DCHECK(out);
244 ServiceWorkerResourceRecord record;
245 if (!record.ParseFromString(serialized))
246 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
247
248 GURL url(record.url());
249 if (!url.is_valid())
250 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
251
252 // Convert ServiceWorkerResourceRecord to ResourceRecord.
253 out->resource_id = record.resource_id();
254 out->url = url;
255 return ServiceWorkerDatabase::STATUS_OK;
256 }
257
LevelDBStatusToStatus(const leveldb::Status & status)258 ServiceWorkerDatabase::Status LevelDBStatusToStatus(
259 const leveldb::Status& status) {
260 if (status.ok())
261 return ServiceWorkerDatabase::STATUS_OK;
262 else if (status.IsNotFound())
263 return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND;
264 else if (status.IsIOError())
265 return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR;
266 else if (status.IsCorruption())
267 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
268 else
269 return ServiceWorkerDatabase::STATUS_ERROR_FAILED;
270 }
271
StatusToString(ServiceWorkerDatabase::Status status)272 const char* StatusToString(ServiceWorkerDatabase::Status status) {
273 switch (status) {
274 case ServiceWorkerDatabase::STATUS_OK:
275 return "Database OK";
276 case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND:
277 return "Database not found";
278 case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR:
279 return "Database IO error";
280 case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED:
281 return "Database corrupted";
282 case ServiceWorkerDatabase::STATUS_ERROR_FAILED:
283 return "Database operation failed";
284 case ServiceWorkerDatabase::STATUS_ERROR_MAX:
285 NOTREACHED();
286 return "Database unknown error";
287 }
288 NOTREACHED();
289 return "Database unknown error";
290 }
291
292 } // namespace
293
RegistrationData()294 ServiceWorkerDatabase::RegistrationData::RegistrationData()
295 : registration_id(kInvalidServiceWorkerRegistrationId),
296 version_id(kInvalidServiceWorkerVersionId),
297 is_active(false),
298 has_fetch_handler(false) {
299 }
300
~RegistrationData()301 ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
302 }
303
ServiceWorkerDatabase(const base::FilePath & path)304 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path)
305 : path_(path),
306 next_avail_registration_id_(0),
307 next_avail_resource_id_(0),
308 next_avail_version_id_(0),
309 state_(UNINITIALIZED) {
310 sequence_checker_.DetachFromSequence();
311 }
312
~ServiceWorkerDatabase()313 ServiceWorkerDatabase::~ServiceWorkerDatabase() {
314 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
315 db_.reset();
316 }
317
GetNextAvailableIds(int64 * next_avail_registration_id,int64 * next_avail_version_id,int64 * next_avail_resource_id)318 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetNextAvailableIds(
319 int64* next_avail_registration_id,
320 int64* next_avail_version_id,
321 int64* next_avail_resource_id) {
322 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
323 DCHECK(next_avail_registration_id);
324 DCHECK(next_avail_version_id);
325 DCHECK(next_avail_resource_id);
326
327 Status status = LazyOpen(false);
328 if (IsNewOrNonexistentDatabase(status)) {
329 *next_avail_registration_id = 0;
330 *next_avail_version_id = 0;
331 *next_avail_resource_id = 0;
332 return STATUS_OK;
333 }
334 if (status != STATUS_OK)
335 return status;
336
337 status = ReadNextAvailableId(kNextRegIdKey, &next_avail_registration_id_);
338 if (status != STATUS_OK)
339 return status;
340 status = ReadNextAvailableId(kNextVerIdKey, &next_avail_version_id_);
341 if (status != STATUS_OK)
342 return status;
343 status = ReadNextAvailableId(kNextResIdKey, &next_avail_resource_id_);
344 if (status != STATUS_OK)
345 return status;
346
347 *next_avail_registration_id = next_avail_registration_id_;
348 *next_avail_version_id = next_avail_version_id_;
349 *next_avail_resource_id = next_avail_resource_id_;
350 return STATUS_OK;
351 }
352
353 ServiceWorkerDatabase::Status
GetOriginsWithRegistrations(std::set<GURL> * origins)354 ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set<GURL>* origins) {
355 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
356 DCHECK(origins->empty());
357
358 Status status = LazyOpen(false);
359 if (IsNewOrNonexistentDatabase(status))
360 return STATUS_OK;
361 if (status != STATUS_OK)
362 return status;
363
364 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
365 for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) {
366 status = LevelDBStatusToStatus(itr->status());
367 if (status != STATUS_OK) {
368 HandleReadResult(FROM_HERE, status);
369 origins->clear();
370 return status;
371 }
372
373 std::string origin;
374 if (!RemovePrefix(itr->key().ToString(), kUniqueOriginKey, &origin))
375 break;
376 origins->insert(GURL(origin));
377 }
378
379 HandleReadResult(FROM_HERE, status);
380 return status;
381 }
382
GetRegistrationsForOrigin(const GURL & origin,std::vector<RegistrationData> * registrations)383 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin(
384 const GURL& origin,
385 std::vector<RegistrationData>* registrations) {
386 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
387 DCHECK(registrations->empty());
388
389 Status status = LazyOpen(false);
390 if (IsNewOrNonexistentDatabase(status))
391 return STATUS_OK;
392 if (status != STATUS_OK)
393 return status;
394
395 // Create a key prefix for registrations.
396 std::string prefix = base::StringPrintf(
397 "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator);
398
399 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
400 for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
401 status = LevelDBStatusToStatus(itr->status());
402 if (status != STATUS_OK) {
403 HandleReadResult(FROM_HERE, status);
404 registrations->clear();
405 return status;
406 }
407
408 if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
409 break;
410
411 RegistrationData registration;
412 status = ParseRegistrationData(itr->value().ToString(), ®istration);
413 if (status != STATUS_OK) {
414 HandleReadResult(FROM_HERE, status);
415 registrations->clear();
416 return status;
417 }
418 registrations->push_back(registration);
419 }
420
421 HandleReadResult(FROM_HERE, status);
422 return status;
423 }
424
GetAllRegistrations(std::vector<RegistrationData> * registrations)425 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations(
426 std::vector<RegistrationData>* registrations) {
427 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
428 DCHECK(registrations->empty());
429
430 Status status = LazyOpen(false);
431 if (IsNewOrNonexistentDatabase(status))
432 return STATUS_OK;
433 if (status != STATUS_OK)
434 return status;
435
436 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
437 for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) {
438 status = LevelDBStatusToStatus(itr->status());
439 if (status != STATUS_OK) {
440 HandleReadResult(FROM_HERE, status);
441 registrations->clear();
442 return status;
443 }
444
445 if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, NULL))
446 break;
447
448 RegistrationData registration;
449 status = ParseRegistrationData(itr->value().ToString(), ®istration);
450 if (status != STATUS_OK) {
451 HandleReadResult(FROM_HERE, status);
452 registrations->clear();
453 return status;
454 }
455 registrations->push_back(registration);
456 }
457
458 HandleReadResult(FROM_HERE, status);
459 return status;
460 }
461
ReadRegistration(int64 registration_id,const GURL & origin,RegistrationData * registration,std::vector<ResourceRecord> * resources)462 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistration(
463 int64 registration_id,
464 const GURL& origin,
465 RegistrationData* registration,
466 std::vector<ResourceRecord>* resources) {
467 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
468 DCHECK(registration);
469 DCHECK(resources);
470
471 Status status = LazyOpen(false);
472 if (IsNewOrNonexistentDatabase(status) || status != STATUS_OK)
473 return status;
474
475 RegistrationData value;
476 status = ReadRegistrationData(registration_id, origin, &value);
477 if (status != STATUS_OK)
478 return status;
479
480 status = ReadResourceRecords(value.version_id, resources);
481 if (status != STATUS_OK)
482 return status;
483
484 *registration = value;
485 return STATUS_OK;
486 }
487
WriteRegistration(const RegistrationData & registration,const std::vector<ResourceRecord> & resources,std::vector<int64> * newly_purgeable_resources)488 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteRegistration(
489 const RegistrationData& registration,
490 const std::vector<ResourceRecord>& resources,
491 std::vector<int64>* newly_purgeable_resources) {
492 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
493 Status status = LazyOpen(true);
494 if (status != STATUS_OK)
495 return status;
496
497 leveldb::WriteBatch batch;
498 BumpNextRegistrationIdIfNeeded(registration.registration_id, &batch);
499 BumpNextVersionIdIfNeeded(registration.version_id, &batch);
500
501 PutUniqueOriginToBatch(registration.scope.GetOrigin(), &batch);
502 PutRegistrationDataToBatch(registration, &batch);
503
504 // Used for avoiding multiple writes for the same resource id or url.
505 std::set<int64> pushed_resources;
506 std::set<GURL> pushed_urls;
507 for (std::vector<ResourceRecord>::const_iterator itr = resources.begin();
508 itr != resources.end(); ++itr) {
509 if (!itr->url.is_valid())
510 return STATUS_ERROR_FAILED;
511
512 // Duplicated resource id or url should not exist.
513 DCHECK(pushed_resources.insert(itr->resource_id).second);
514 DCHECK(pushed_urls.insert(itr->url).second);
515
516 PutResourceRecordToBatch(*itr, registration.version_id, &batch);
517
518 // Delete a resource from the uncommitted list.
519 batch.Delete(CreateResourceIdKey(
520 kUncommittedResIdKeyPrefix, itr->resource_id));
521 }
522
523 // Retrieve a previous version to sweep purgeable resources.
524 RegistrationData old_registration;
525 status = ReadRegistrationData(registration.registration_id,
526 registration.scope.GetOrigin(),
527 &old_registration);
528 if (status != STATUS_OK && status != STATUS_ERROR_NOT_FOUND)
529 return status;
530 if (status == STATUS_OK) {
531 DCHECK_LT(old_registration.version_id, registration.version_id);
532 status = DeleteResourceRecords(
533 old_registration.version_id, newly_purgeable_resources, &batch);
534 if (status != STATUS_OK)
535 return status;
536
537 // Currently resource sharing across versions and registrations is not
538 // supported, so resource ids should not be overlapped between
539 // |registration| and |old_registration|.
540 std::set<int64> deleted_resources(newly_purgeable_resources->begin(),
541 newly_purgeable_resources->end());
542 DCHECK(base::STLSetIntersection<std::set<int64> >(
543 pushed_resources, deleted_resources).empty());
544 }
545
546 return WriteBatch(&batch);
547 }
548
UpdateVersionToActive(int64 registration_id,const GURL & origin)549 ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateVersionToActive(
550 int64 registration_id,
551 const GURL& origin) {
552 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
553 ServiceWorkerDatabase::Status status = LazyOpen(false);
554 if (IsNewOrNonexistentDatabase(status))
555 return STATUS_ERROR_NOT_FOUND;
556 if (status != STATUS_OK)
557 return status;
558 if (!origin.is_valid())
559 return STATUS_ERROR_FAILED;
560
561 RegistrationData registration;
562 status = ReadRegistrationData(registration_id, origin, ®istration);
563 if (status != STATUS_OK)
564 return status;
565
566 registration.is_active = true;
567
568 leveldb::WriteBatch batch;
569 PutRegistrationDataToBatch(registration, &batch);
570 return WriteBatch(&batch);
571 }
572
UpdateLastCheckTime(int64 registration_id,const GURL & origin,const base::Time & time)573 ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateLastCheckTime(
574 int64 registration_id,
575 const GURL& origin,
576 const base::Time& time) {
577 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
578 ServiceWorkerDatabase::Status status = LazyOpen(false);
579 if (IsNewOrNonexistentDatabase(status))
580 return STATUS_ERROR_NOT_FOUND;
581 if (status != STATUS_OK)
582 return status;
583 if (!origin.is_valid())
584 return STATUS_ERROR_FAILED;
585
586 RegistrationData registration;
587 status = ReadRegistrationData(registration_id, origin, ®istration);
588 if (status != STATUS_OK)
589 return status;
590
591 registration.last_update_check = time;
592
593 leveldb::WriteBatch batch;
594 PutRegistrationDataToBatch(registration, &batch);
595 return WriteBatch(&batch);
596 }
597
DeleteRegistration(int64 registration_id,const GURL & origin,std::vector<int64> * newly_purgeable_resources)598 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteRegistration(
599 int64 registration_id,
600 const GURL& origin,
601 std::vector<int64>* newly_purgeable_resources) {
602 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
603 Status status = LazyOpen(false);
604 if (IsNewOrNonexistentDatabase(status))
605 return STATUS_OK;
606 if (status != STATUS_OK)
607 return status;
608 if (!origin.is_valid())
609 return STATUS_ERROR_FAILED;
610
611 leveldb::WriteBatch batch;
612
613 // Remove |origin| from unique origins if a registration specified by
614 // |registration_id| is the only one for |origin|.
615 // TODO(nhiroki): Check the uniqueness by more efficient way.
616 std::vector<RegistrationData> registrations;
617 status = GetRegistrationsForOrigin(origin, ®istrations);
618 if (status != STATUS_OK)
619 return status;
620
621 if (registrations.size() == 1 &&
622 registrations[0].registration_id == registration_id) {
623 batch.Delete(CreateUniqueOriginKey(origin));
624 }
625
626 // Delete a registration specified by |registration_id|.
627 batch.Delete(CreateRegistrationKey(registration_id, origin));
628
629 // Delete resource records associated with the registration.
630 for (std::vector<RegistrationData>::const_iterator itr =
631 registrations.begin(); itr != registrations.end(); ++itr) {
632 if (itr->registration_id == registration_id) {
633 status = DeleteResourceRecords(
634 itr->version_id, newly_purgeable_resources, &batch);
635 if (status != STATUS_OK)
636 return status;
637 break;
638 }
639 }
640
641 return WriteBatch(&batch);
642 }
643
644 ServiceWorkerDatabase::Status
GetUncommittedResourceIds(std::set<int64> * ids)645 ServiceWorkerDatabase::GetUncommittedResourceIds(std::set<int64>* ids) {
646 return ReadResourceIds(kUncommittedResIdKeyPrefix, ids);
647 }
648
649 ServiceWorkerDatabase::Status
WriteUncommittedResourceIds(const std::set<int64> & ids)650 ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set<int64>& ids) {
651 return WriteResourceIds(kUncommittedResIdKeyPrefix, ids);
652 }
653
654 ServiceWorkerDatabase::Status
ClearUncommittedResourceIds(const std::set<int64> & ids)655 ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set<int64>& ids) {
656 return DeleteResourceIds(kUncommittedResIdKeyPrefix, ids);
657 }
658
659 ServiceWorkerDatabase::Status
GetPurgeableResourceIds(std::set<int64> * ids)660 ServiceWorkerDatabase::GetPurgeableResourceIds(std::set<int64>* ids) {
661 return ReadResourceIds(kPurgeableResIdKeyPrefix, ids);
662 }
663
664 ServiceWorkerDatabase::Status
WritePurgeableResourceIds(const std::set<int64> & ids)665 ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set<int64>& ids) {
666 return WriteResourceIds(kPurgeableResIdKeyPrefix, ids);
667 }
668
669 ServiceWorkerDatabase::Status
ClearPurgeableResourceIds(const std::set<int64> & ids)670 ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set<int64>& ids) {
671 return DeleteResourceIds(kPurgeableResIdKeyPrefix, ids);
672 }
673
674 ServiceWorkerDatabase::Status
PurgeUncommittedResourceIds(const std::set<int64> & ids)675 ServiceWorkerDatabase::PurgeUncommittedResourceIds(
676 const std::set<int64>& ids) {
677 leveldb::WriteBatch batch;
678 Status status = DeleteResourceIdsInBatch(
679 kUncommittedResIdKeyPrefix, ids, &batch);
680 if (status != STATUS_OK)
681 return status;
682 status = WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix, ids, &batch);
683 if (status != STATUS_OK)
684 return status;
685 return WriteBatch(&batch);
686 }
687
DeleteAllDataForOrigin(const GURL & origin,std::vector<int64> * newly_purgeable_resources)688 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteAllDataForOrigin(
689 const GURL& origin,
690 std::vector<int64>* newly_purgeable_resources) {
691 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
692 Status status = LazyOpen(false);
693 if (IsNewOrNonexistentDatabase(status))
694 return STATUS_OK;
695 if (status != STATUS_OK)
696 return status;
697 if (!origin.is_valid())
698 return STATUS_ERROR_FAILED;
699
700 leveldb::WriteBatch batch;
701
702 // Delete from the unique origin list.
703 batch.Delete(CreateUniqueOriginKey(origin));
704
705 std::vector<RegistrationData> registrations;
706 status = GetRegistrationsForOrigin(origin, ®istrations);
707 if (status != STATUS_OK)
708 return status;
709
710 // Delete registrations and resource records.
711 for (std::vector<RegistrationData>::const_iterator itr =
712 registrations.begin(); itr != registrations.end(); ++itr) {
713 batch.Delete(CreateRegistrationKey(itr->registration_id, origin));
714 status = DeleteResourceRecords(
715 itr->version_id, newly_purgeable_resources, &batch);
716 if (status != STATUS_OK)
717 return status;
718 }
719
720 return WriteBatch(&batch);
721 }
722
DestroyDatabase()723 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DestroyDatabase() {
724 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
725 Disable(FROM_HERE, STATUS_OK);
726 return LevelDBStatusToStatus(
727 leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options()));
728 }
729
LazyOpen(bool create_if_missing)730 ServiceWorkerDatabase::Status ServiceWorkerDatabase::LazyOpen(
731 bool create_if_missing) {
732 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
733
734 // Do not try to open a database if we tried and failed once.
735 if (state_ == DISABLED)
736 return STATUS_ERROR_FAILED;
737 if (IsOpen())
738 return STATUS_OK;
739
740 // When |path_| is empty, open a database in-memory.
741 bool use_in_memory_db = path_.empty();
742
743 if (!create_if_missing) {
744 // Avoid opening a database if it does not exist at the |path_|.
745 if (use_in_memory_db ||
746 !base::PathExists(path_) ||
747 base::IsDirectoryEmpty(path_)) {
748 return STATUS_ERROR_NOT_FOUND;
749 }
750 }
751
752 leveldb::Options options;
753 options.create_if_missing = create_if_missing;
754 if (use_in_memory_db) {
755 env_.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
756 options.env = env_.get();
757 }
758
759 leveldb::DB* db = NULL;
760 Status status = LevelDBStatusToStatus(
761 leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db));
762 HandleOpenResult(FROM_HERE, status);
763 if (status != STATUS_OK) {
764 DCHECK(!db);
765 // TODO(nhiroki): Should we retry to open the database?
766 return status;
767 }
768 db_.reset(db);
769
770 int64 db_version;
771 status = ReadDatabaseVersion(&db_version);
772 if (status != STATUS_OK)
773 return status;
774 DCHECK_LE(0, db_version);
775 if (db_version > 0)
776 state_ = INITIALIZED;
777 return STATUS_OK;
778 }
779
IsNewOrNonexistentDatabase(ServiceWorkerDatabase::Status status)780 bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
781 ServiceWorkerDatabase::Status status) {
782 if (status == STATUS_ERROR_NOT_FOUND)
783 return true;
784 if (status == STATUS_OK && state_ == UNINITIALIZED)
785 return true;
786 return false;
787 }
788
ReadNextAvailableId(const char * id_key,int64 * next_avail_id)789 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadNextAvailableId(
790 const char* id_key,
791 int64* next_avail_id) {
792 DCHECK(id_key);
793 DCHECK(next_avail_id);
794
795 std::string value;
796 Status status = LevelDBStatusToStatus(
797 db_->Get(leveldb::ReadOptions(), id_key, &value));
798 if (status == STATUS_ERROR_NOT_FOUND) {
799 // Nobody has gotten the next resource id for |id_key|.
800 *next_avail_id = 0;
801 HandleReadResult(FROM_HERE, STATUS_OK);
802 return STATUS_OK;
803 } else if (status != STATUS_OK) {
804 HandleReadResult(FROM_HERE, status);
805 return status;
806 }
807
808 status = ParseId(value, next_avail_id);
809 HandleReadResult(FROM_HERE, status);
810 return status;
811 }
812
ReadRegistrationData(int64 registration_id,const GURL & origin,RegistrationData * registration)813 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData(
814 int64 registration_id,
815 const GURL& origin,
816 RegistrationData* registration) {
817 DCHECK(registration);
818
819 const std::string key = CreateRegistrationKey(registration_id, origin);
820 std::string value;
821 Status status = LevelDBStatusToStatus(
822 db_->Get(leveldb::ReadOptions(), key, &value));
823 if (status != STATUS_OK) {
824 HandleReadResult(
825 FROM_HERE,
826 status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status);
827 return status;
828 }
829
830 status = ParseRegistrationData(value, registration);
831 HandleReadResult(FROM_HERE, status);
832 return status;
833 }
834
ReadResourceRecords(int64 version_id,std::vector<ResourceRecord> * resources)835 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceRecords(
836 int64 version_id,
837 std::vector<ResourceRecord>* resources) {
838 DCHECK(resources->empty());
839
840 Status status = STATUS_OK;
841 const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
842
843 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
844 for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
845 Status status = LevelDBStatusToStatus(itr->status());
846 if (status != STATUS_OK) {
847 HandleReadResult(FROM_HERE, status);
848 resources->clear();
849 return status;
850 }
851
852 if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
853 break;
854
855 ResourceRecord resource;
856 status = ParseResourceRecord(itr->value().ToString(), &resource);
857 if (status != STATUS_OK) {
858 HandleReadResult(FROM_HERE, status);
859 resources->clear();
860 return status;
861 }
862 resources->push_back(resource);
863 }
864
865 HandleReadResult(FROM_HERE, status);
866 return status;
867 }
868
DeleteResourceRecords(int64 version_id,std::vector<int64> * newly_purgeable_resources,leveldb::WriteBatch * batch)869 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords(
870 int64 version_id,
871 std::vector<int64>* newly_purgeable_resources,
872 leveldb::WriteBatch* batch) {
873 DCHECK(batch);
874
875 Status status = STATUS_OK;
876 const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
877
878 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
879 for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
880 status = LevelDBStatusToStatus(itr->status());
881 if (status != STATUS_OK) {
882 HandleReadResult(FROM_HERE, status);
883 return status;
884 }
885
886 const std::string key = itr->key().ToString();
887 std::string unprefixed;
888 if (!RemovePrefix(key, prefix, &unprefixed))
889 break;
890
891 int64 resource_id;
892 status = ParseId(unprefixed, &resource_id);
893 if (status != STATUS_OK) {
894 HandleReadResult(FROM_HERE, status);
895 return status;
896 }
897
898 // Remove a resource record.
899 batch->Delete(key);
900
901 // Currently resource sharing across versions and registrations is not
902 // supported, so we can purge this without caring about it.
903 PutPurgeableResourceIdToBatch(resource_id, batch);
904 newly_purgeable_resources->push_back(resource_id);
905 }
906
907 HandleReadResult(FROM_HERE, status);
908 return status;
909 }
910
ReadResourceIds(const char * id_key_prefix,std::set<int64> * ids)911 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds(
912 const char* id_key_prefix,
913 std::set<int64>* ids) {
914 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
915 DCHECK(id_key_prefix);
916 DCHECK(ids->empty());
917
918 Status status = LazyOpen(false);
919 if (IsNewOrNonexistentDatabase(status))
920 return STATUS_OK;
921 if (status != STATUS_OK)
922 return status;
923
924 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
925 for (itr->Seek(id_key_prefix); itr->Valid(); itr->Next()) {
926 status = LevelDBStatusToStatus(itr->status());
927 if (status != STATUS_OK) {
928 HandleReadResult(FROM_HERE, status);
929 ids->clear();
930 return status;
931 }
932
933 std::string unprefixed;
934 if (!RemovePrefix(itr->key().ToString(), id_key_prefix, &unprefixed))
935 break;
936
937 int64 resource_id;
938 status = ParseId(unprefixed, &resource_id);
939 if (status != STATUS_OK) {
940 HandleReadResult(FROM_HERE, status);
941 ids->clear();
942 return status;
943 }
944 ids->insert(resource_id);
945 }
946
947 HandleReadResult(FROM_HERE, status);
948 return status;
949 }
950
WriteResourceIds(const char * id_key_prefix,const std::set<int64> & ids)951 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIds(
952 const char* id_key_prefix,
953 const std::set<int64>& ids) {
954 leveldb::WriteBatch batch;
955 Status status = WriteResourceIdsInBatch(id_key_prefix, ids, &batch);
956 if (status != STATUS_OK)
957 return status;
958 return WriteBatch(&batch);
959 }
960
WriteResourceIdsInBatch(const char * id_key_prefix,const std::set<int64> & ids,leveldb::WriteBatch * batch)961 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIdsInBatch(
962 const char* id_key_prefix,
963 const std::set<int64>& ids,
964 leveldb::WriteBatch* batch) {
965 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
966 DCHECK(id_key_prefix);
967
968 Status status = LazyOpen(true);
969 if (status != STATUS_OK)
970 return status;
971
972 for (std::set<int64>::const_iterator itr = ids.begin();
973 itr != ids.end(); ++itr) {
974 // Value should be empty.
975 batch->Put(CreateResourceIdKey(id_key_prefix, *itr), "");
976 }
977 return STATUS_OK;
978 }
979
DeleteResourceIds(const char * id_key_prefix,const std::set<int64> & ids)980 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIds(
981 const char* id_key_prefix,
982 const std::set<int64>& ids) {
983 leveldb::WriteBatch batch;
984 Status status = DeleteResourceIdsInBatch(id_key_prefix, ids, &batch);
985 if (status != STATUS_OK)
986 return status;
987 return WriteBatch(&batch);
988 }
989
DeleteResourceIdsInBatch(const char * id_key_prefix,const std::set<int64> & ids,leveldb::WriteBatch * batch)990 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIdsInBatch(
991 const char* id_key_prefix,
992 const std::set<int64>& ids,
993 leveldb::WriteBatch* batch) {
994 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
995 DCHECK(id_key_prefix);
996
997 Status status = LazyOpen(false);
998 if (IsNewOrNonexistentDatabase(status))
999 return STATUS_OK;
1000 if (status != STATUS_OK)
1001 return status;
1002
1003 for (std::set<int64>::const_iterator itr = ids.begin();
1004 itr != ids.end(); ++itr) {
1005 batch->Delete(CreateResourceIdKey(id_key_prefix, *itr));
1006 }
1007 return STATUS_OK;
1008 }
1009
ReadDatabaseVersion(int64 * db_version)1010 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadDatabaseVersion(
1011 int64* db_version) {
1012 std::string value;
1013 Status status = LevelDBStatusToStatus(
1014 db_->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value));
1015 if (status == STATUS_ERROR_NOT_FOUND) {
1016 // The database hasn't been initialized yet.
1017 *db_version = 0;
1018 HandleReadResult(FROM_HERE, STATUS_OK);
1019 return STATUS_OK;
1020 }
1021
1022 if (status != STATUS_OK) {
1023 HandleReadResult(FROM_HERE, status);
1024 return status;
1025 }
1026
1027 status = ParseDatabaseVersion(value, db_version);
1028 HandleReadResult(FROM_HERE, status);
1029 return status;
1030 }
1031
WriteBatch(leveldb::WriteBatch * batch)1032 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch(
1033 leveldb::WriteBatch* batch) {
1034 DCHECK(batch);
1035 DCHECK_NE(DISABLED, state_);
1036
1037 if (state_ == UNINITIALIZED) {
1038 // Write the database schema version.
1039 batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion));
1040 state_ = INITIALIZED;
1041 }
1042
1043 Status status = LevelDBStatusToStatus(
1044 db_->Write(leveldb::WriteOptions(), batch));
1045 HandleWriteResult(FROM_HERE, status);
1046 return status;
1047 }
1048
BumpNextRegistrationIdIfNeeded(int64 used_id,leveldb::WriteBatch * batch)1049 void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1050 int64 used_id, leveldb::WriteBatch* batch) {
1051 DCHECK(batch);
1052 if (next_avail_registration_id_ <= used_id) {
1053 next_avail_registration_id_ = used_id + 1;
1054 batch->Put(kNextRegIdKey, base::Int64ToString(next_avail_registration_id_));
1055 }
1056 }
1057
BumpNextVersionIdIfNeeded(int64 used_id,leveldb::WriteBatch * batch)1058 void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded(
1059 int64 used_id, leveldb::WriteBatch* batch) {
1060 DCHECK(batch);
1061 if (next_avail_version_id_ <= used_id) {
1062 next_avail_version_id_ = used_id + 1;
1063 batch->Put(kNextVerIdKey, base::Int64ToString(next_avail_version_id_));
1064 }
1065 }
1066
IsOpen()1067 bool ServiceWorkerDatabase::IsOpen() {
1068 return db_ != NULL;
1069 }
1070
Disable(const tracked_objects::Location & from_here,Status status)1071 void ServiceWorkerDatabase::Disable(
1072 const tracked_objects::Location& from_here,
1073 Status status) {
1074 if (status != STATUS_OK) {
1075 DLOG(ERROR) << "Failed at: " << from_here.ToString()
1076 << " with error: " << StatusToString(status);
1077 DLOG(ERROR) << "ServiceWorkerDatabase is disabled.";
1078 }
1079 state_ = DISABLED;
1080 db_.reset();
1081 }
1082
HandleOpenResult(const tracked_objects::Location & from_here,Status status)1083 void ServiceWorkerDatabase::HandleOpenResult(
1084 const tracked_objects::Location& from_here,
1085 Status status) {
1086 if (status != ServiceWorkerDatabase::STATUS_OK)
1087 Disable(from_here, status);
1088 UMA_HISTOGRAM_ENUMERATION(kOpenResultHistogramLabel,
1089 status,
1090 ServiceWorkerDatabase::STATUS_ERROR_MAX);
1091 }
1092
HandleReadResult(const tracked_objects::Location & from_here,Status status)1093 void ServiceWorkerDatabase::HandleReadResult(
1094 const tracked_objects::Location& from_here,
1095 Status status) {
1096 if (status != ServiceWorkerDatabase::STATUS_OK)
1097 Disable(from_here, status);
1098 UMA_HISTOGRAM_ENUMERATION(kReadResultHistogramLabel,
1099 status,
1100 ServiceWorkerDatabase::STATUS_ERROR_MAX);
1101 }
1102
HandleWriteResult(const tracked_objects::Location & from_here,Status status)1103 void ServiceWorkerDatabase::HandleWriteResult(
1104 const tracked_objects::Location& from_here,
1105 Status status) {
1106 if (status != ServiceWorkerDatabase::STATUS_OK)
1107 Disable(from_here, status);
1108 UMA_HISTOGRAM_ENUMERATION(kWriteResultHistogramLabel,
1109 status,
1110 ServiceWorkerDatabase::STATUS_ERROR_MAX);
1111 }
1112
1113 } // namespace content
1114