- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / safe_browsing / safe_browsing_store_file.cc
1 // Copyright (c) 2012 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 "chrome/browser/safe_browsing/safe_browsing_store_file.h"
6
7 #include "base/md5.h"
8 #include "base/metrics/histogram.h"
9
10 namespace {
11
12 // NOTE(shess): kFileMagic should not be a byte-wise palindrome, so
13 // that byte-order changes force corruption.
14 const int32 kFileMagic = 0x600D71FE;
15 const int32 kFileVersion = 7;  // SQLite storage was 6...
16
17 // Header at the front of the main database file.
18 struct FileHeader {
19   int32 magic, version;
20   uint32 add_chunk_count, sub_chunk_count;
21   uint32 add_prefix_count, sub_prefix_count;
22   uint32 add_hash_count, sub_hash_count;
23 };
24
25 // Header for each chunk in the chunk-accumulation file.
26 struct ChunkHeader {
27   uint32 add_prefix_count, sub_prefix_count;
28   uint32 add_hash_count, sub_hash_count;
29 };
30
31 // Rewind the file.  Using fseek(2) because rewind(3) errors are
32 // weird.
33 bool FileRewind(FILE* fp) {
34   int rv = fseek(fp, 0, SEEK_SET);
35   DCHECK_EQ(rv, 0);
36   return rv == 0;
37 }
38
39 // Move file read pointer forward by |bytes| relative to current position.
40 bool FileSkip(size_t bytes, FILE* fp) {
41   // Although fseek takes negative values, for this case, we only want
42   // to skip forward.
43   DCHECK(static_cast<long>(bytes) >= 0);
44   if (static_cast<long>(bytes) < 0)
45     return false;
46   int rv = fseek(fp, static_cast<long>(bytes), SEEK_CUR);
47   DCHECK_EQ(rv, 0);
48   return rv == 0;
49 }
50
51 // Read from |fp| into |item|, and fold the input data into the
52 // checksum in |context|, if non-NULL.  Return true on success.
53 template <class T>
54 bool ReadItem(T* item, FILE* fp, base::MD5Context* context) {
55   const size_t ret = fread(item, sizeof(T), 1, fp);
56   if (ret != 1)
57     return false;
58
59   if (context) {
60     base::MD5Update(context,
61                     base::StringPiece(reinterpret_cast<char*>(item),
62                                       sizeof(T)));
63   }
64   return true;
65 }
66
67 // Write |item| to |fp|, and fold the output data into the checksum in
68 // |context|, if non-NULL.  Return true on success.
69 template <class T>
70 bool WriteItem(const T& item, FILE* fp, base::MD5Context* context) {
71   const size_t ret = fwrite(&item, sizeof(T), 1, fp);
72   if (ret != 1)
73     return false;
74
75   if (context) {
76     base::MD5Update(context,
77                     base::StringPiece(reinterpret_cast<const char*>(&item),
78                                       sizeof(T)));
79   }
80
81   return true;
82 }
83
84 // Read |count| items into |values| from |fp|, and fold them into the
85 // checksum in |context|.  Returns true on success.
86 template <typename CT>
87 bool ReadToContainer(CT* values, size_t count, FILE* fp,
88                      base::MD5Context* context) {
89   if (!count)
90     return true;
91
92   for (size_t i = 0; i < count; ++i) {
93     typename CT::value_type value;
94     if (!ReadItem(&value, fp, context))
95       return false;
96
97     // push_back() is more obvious, but coded this way std::set can
98     // also be read.
99     values->insert(values->end(), value);
100   }
101
102   return true;
103 }
104
105 // Write all of |values| to |fp|, and fold the data into the checksum
106 // in |context|, if non-NULL.  Returns true on succsess.
107 template <typename CT>
108 bool WriteContainer(const CT& values, FILE* fp,
109                     base::MD5Context* context) {
110   if (values.empty())
111     return true;
112
113   for (typename CT::const_iterator iter = values.begin();
114        iter != values.end(); ++iter) {
115     if (!WriteItem(*iter, fp, context))
116       return false;
117   }
118   return true;
119 }
120
121 // Delete the chunks in |deleted| from |chunks|.
122 void DeleteChunksFromSet(const base::hash_set<int32>& deleted,
123                          std::set<int32>* chunks) {
124   for (std::set<int32>::iterator iter = chunks->begin();
125        iter != chunks->end();) {
126     std::set<int32>::iterator prev = iter++;
127     if (deleted.count(*prev) > 0)
128       chunks->erase(prev);
129   }
130 }
131
132 // Sanity-check the header against the file's size to make sure our
133 // vectors aren't gigantic.  This doubles as a cheap way to detect
134 // corruption without having to checksum the entire file.
135 bool FileHeaderSanityCheck(const base::FilePath& filename,
136                            const FileHeader& header) {
137   int64 size = 0;
138   if (!file_util::GetFileSize(filename, &size))
139     return false;
140
141   int64 expected_size = sizeof(FileHeader);
142   expected_size += header.add_chunk_count * sizeof(int32);
143   expected_size += header.sub_chunk_count * sizeof(int32);
144   expected_size += header.add_prefix_count * sizeof(SBAddPrefix);
145   expected_size += header.sub_prefix_count * sizeof(SBSubPrefix);
146   expected_size += header.add_hash_count * sizeof(SBAddFullHash);
147   expected_size += header.sub_hash_count * sizeof(SBSubFullHash);
148   expected_size += sizeof(base::MD5Digest);
149   if (size != expected_size)
150     return false;
151
152   return true;
153 }
154
155 // This a helper function that reads header to |header|. Returns true if the
156 // magic number is correct and santiy check passes.
157 bool ReadAndVerifyHeader(const base::FilePath& filename,
158                          FILE* fp,
159                          FileHeader* header,
160                          base::MD5Context* context) {
161   if (!ReadItem(header, fp, context))
162     return false;
163   if (header->magic != kFileMagic || header->version != kFileVersion)
164     return false;
165   if (!FileHeaderSanityCheck(filename, *header))
166     return false;
167   return true;
168 }
169
170 }  // namespace
171
172 // static
173 void SafeBrowsingStoreFile::RecordFormatEvent(FormatEventType event_type) {
174   UMA_HISTOGRAM_ENUMERATION("SB2.FormatEvent", event_type, FORMAT_EVENT_MAX);
175 }
176
177 // static
178 void SafeBrowsingStoreFile::CheckForOriginalAndDelete(
179     const base::FilePath& current_filename) {
180   const base::FilePath original_filename(
181       current_filename.DirName().AppendASCII("Safe Browsing"));
182   if (base::PathExists(original_filename)) {
183     int64 size = 0;
184     if (file_util::GetFileSize(original_filename, &size)) {
185       UMA_HISTOGRAM_COUNTS("SB2.OldDatabaseKilobytes",
186                            static_cast<int>(size / 1024));
187     }
188
189     if (base::DeleteFile(original_filename, false)) {
190       RecordFormatEvent(FORMAT_EVENT_DELETED_ORIGINAL);
191     } else {
192       RecordFormatEvent(FORMAT_EVENT_DELETED_ORIGINAL_FAILED);
193     }
194
195     // Just best-effort on the journal file, don't want to get lost in
196     // the weeds.
197     const base::FilePath journal_filename(
198         current_filename.DirName().AppendASCII("Safe Browsing-journal"));
199     base::DeleteFile(journal_filename, false);
200   }
201 }
202
203 SafeBrowsingStoreFile::SafeBrowsingStoreFile()
204     : chunks_written_(0), empty_(false), corruption_seen_(false) {}
205
206 SafeBrowsingStoreFile::~SafeBrowsingStoreFile() {
207   Close();
208 }
209
210 bool SafeBrowsingStoreFile::Delete() {
211   // The database should not be open at this point.  But, just in
212   // case, close everything before deleting.
213   if (!Close()) {
214     NOTREACHED();
215     return false;
216   }
217
218   return DeleteStore(filename_);
219 }
220
221 bool SafeBrowsingStoreFile::CheckValidity() {
222   // The file was either empty or never opened.  The empty case is
223   // presumed not to be invalid.  The never-opened case can happen if
224   // BeginUpdate() fails for any databases, and should already have
225   // caused the corruption callback to fire.
226   if (!file_.get())
227     return true;
228
229   if (!FileRewind(file_.get()))
230     return OnCorruptDatabase();
231
232   int64 size = 0;
233   if (!file_util::GetFileSize(filename_, &size))
234     return OnCorruptDatabase();
235
236   base::MD5Context context;
237   base::MD5Init(&context);
238
239   // Read everything except the final digest.
240   size_t bytes_left = static_cast<size_t>(size);
241   CHECK(size == static_cast<int64>(bytes_left));
242   if (bytes_left < sizeof(base::MD5Digest))
243     return OnCorruptDatabase();
244   bytes_left -= sizeof(base::MD5Digest);
245
246   // Fold the contents of the file into the checksum.
247   while (bytes_left > 0) {
248     char buf[4096];
249     const size_t c = std::min(sizeof(buf), bytes_left);
250     const size_t ret = fread(buf, 1, c, file_.get());
251
252     // The file's size changed while reading, give up.
253     if (ret != c)
254       return OnCorruptDatabase();
255     base::MD5Update(&context, base::StringPiece(buf, c));
256     bytes_left -= c;
257   }
258
259   // Calculate the digest to this point.
260   base::MD5Digest calculated_digest;
261   base::MD5Final(&calculated_digest, &context);
262
263   // Read the stored digest and verify it.
264   base::MD5Digest file_digest;
265   if (!ReadItem(&file_digest, file_.get(), NULL))
266     return OnCorruptDatabase();
267   if (0 != memcmp(&file_digest, &calculated_digest, sizeof(file_digest))) {
268     RecordFormatEvent(FORMAT_EVENT_VALIDITY_CHECKSUM_FAILURE);
269     return OnCorruptDatabase();
270   }
271
272   return true;
273 }
274
275 void SafeBrowsingStoreFile::Init(
276     const base::FilePath& filename,
277     const base::Closure& corruption_callback
278 ) {
279   filename_ = filename;
280   corruption_callback_ = corruption_callback;
281 }
282
283 bool SafeBrowsingStoreFile::BeginChunk() {
284   return ClearChunkBuffers();
285 }
286
287 bool SafeBrowsingStoreFile::WriteAddPrefix(int32 chunk_id, SBPrefix prefix) {
288   add_prefixes_.push_back(SBAddPrefix(chunk_id, prefix));
289   return true;
290 }
291
292 bool SafeBrowsingStoreFile::GetAddPrefixes(SBAddPrefixes* add_prefixes) {
293   add_prefixes->clear();
294
295   file_util::ScopedFILE file(file_util::OpenFile(filename_, "rb"));
296   if (file.get() == NULL) return false;
297
298   FileHeader header;
299   if (!ReadAndVerifyHeader(filename_, file.get(), &header, NULL))
300     return OnCorruptDatabase();
301
302   size_t add_prefix_offset = header.add_chunk_count * sizeof(int32) +
303       header.sub_chunk_count * sizeof(int32);
304   if (!FileSkip(add_prefix_offset, file.get()))
305     return false;
306
307   if (!ReadToContainer(add_prefixes, header.add_prefix_count, file.get(), NULL))
308     return false;
309
310   return true;
311 }
312
313 bool SafeBrowsingStoreFile::GetAddFullHashes(
314     std::vector<SBAddFullHash>* add_full_hashes) {
315   add_full_hashes->clear();
316
317   file_util::ScopedFILE file(file_util::OpenFile(filename_, "rb"));
318   if (file.get() == NULL) return false;
319
320   FileHeader header;
321   if (!ReadAndVerifyHeader(filename_, file.get(), &header, NULL))
322     return OnCorruptDatabase();
323
324   size_t offset =
325       header.add_chunk_count * sizeof(int32) +
326       header.sub_chunk_count * sizeof(int32) +
327       header.add_prefix_count * sizeof(SBAddPrefix) +
328       header.sub_prefix_count * sizeof(SBSubPrefix);
329   if (!FileSkip(offset, file.get()))
330     return false;
331
332   return ReadToContainer(add_full_hashes,
333                          header.add_hash_count,
334                          file.get(),
335                          NULL);
336 }
337
338 bool SafeBrowsingStoreFile::WriteAddHash(int32 chunk_id,
339                                          base::Time receive_time,
340                                          const SBFullHash& full_hash) {
341   add_hashes_.push_back(SBAddFullHash(chunk_id, receive_time, full_hash));
342   return true;
343 }
344
345 bool SafeBrowsingStoreFile::WriteSubPrefix(int32 chunk_id,
346                                            int32 add_chunk_id,
347                                            SBPrefix prefix) {
348   sub_prefixes_.push_back(SBSubPrefix(chunk_id, add_chunk_id, prefix));
349   return true;
350 }
351
352 bool SafeBrowsingStoreFile::WriteSubHash(int32 chunk_id, int32 add_chunk_id,
353                                          const SBFullHash& full_hash) {
354   sub_hashes_.push_back(SBSubFullHash(chunk_id, add_chunk_id, full_hash));
355   return true;
356 }
357
358 bool SafeBrowsingStoreFile::OnCorruptDatabase() {
359   if (!corruption_seen_)
360     RecordFormatEvent(FORMAT_EVENT_FILE_CORRUPT);
361   corruption_seen_ = true;
362
363   corruption_callback_.Run();
364
365   // Return false as a convenience to callers.
366   return false;
367 }
368
369 bool SafeBrowsingStoreFile::Close() {
370   ClearUpdateBuffers();
371
372   // Make sure the files are closed.
373   file_.reset();
374   new_file_.reset();
375   return true;
376 }
377
378 bool SafeBrowsingStoreFile::BeginUpdate() {
379   DCHECK(!file_.get() && !new_file_.get());
380
381   // Structures should all be clear unless something bad happened.
382   DCHECK(add_chunks_cache_.empty());
383   DCHECK(sub_chunks_cache_.empty());
384   DCHECK(add_del_cache_.empty());
385   DCHECK(sub_del_cache_.empty());
386   DCHECK(add_prefixes_.empty());
387   DCHECK(sub_prefixes_.empty());
388   DCHECK(add_hashes_.empty());
389   DCHECK(sub_hashes_.empty());
390   DCHECK_EQ(chunks_written_, 0);
391
392   // Since the following code will already hit the profile looking for
393   // database files, this is a reasonable to time delete any old
394   // files.
395   CheckForOriginalAndDelete(filename_);
396
397   corruption_seen_ = false;
398
399   const base::FilePath new_filename = TemporaryFileForFilename(filename_);
400   file_util::ScopedFILE new_file(file_util::OpenFile(new_filename, "wb+"));
401   if (new_file.get() == NULL)
402     return false;
403
404   file_util::ScopedFILE file(file_util::OpenFile(filename_, "rb"));
405   empty_ = (file.get() == NULL);
406   if (empty_) {
407     // If the file exists but cannot be opened, try to delete it (not
408     // deleting directly, the bloom filter needs to be deleted, too).
409     if (base::PathExists(filename_))
410       return OnCorruptDatabase();
411
412     new_file_.swap(new_file);
413     return true;
414   }
415
416   FileHeader header;
417   if (!ReadItem(&header, file.get(), NULL))
418       return OnCorruptDatabase();
419
420   if (header.magic != kFileMagic || header.version != kFileVersion) {
421     if (!strcmp(reinterpret_cast<char*>(&header.magic), "SQLite format 3")) {
422       RecordFormatEvent(FORMAT_EVENT_FOUND_SQLITE);
423     } else {
424       RecordFormatEvent(FORMAT_EVENT_FOUND_UNKNOWN);
425     }
426
427     // Close the file so that it can be deleted.
428     file.reset();
429
430     return OnCorruptDatabase();
431   }
432
433   // TODO(shess): Under POSIX it is possible that this could size a
434   // file different from the file which was opened.
435   if (!FileHeaderSanityCheck(filename_, header))
436     return OnCorruptDatabase();
437
438   // Pull in the chunks-seen data for purposes of implementing
439   // |GetAddChunks()| and |GetSubChunks()|.  This data is sent up to
440   // the server at the beginning of an update.
441   if (!ReadToContainer(&add_chunks_cache_, header.add_chunk_count,
442                        file.get(), NULL) ||
443       !ReadToContainer(&sub_chunks_cache_, header.sub_chunk_count,
444                        file.get(), NULL))
445     return OnCorruptDatabase();
446
447   file_.swap(file);
448   new_file_.swap(new_file);
449   return true;
450 }
451
452 bool SafeBrowsingStoreFile::FinishChunk() {
453   if (!add_prefixes_.size() && !sub_prefixes_.size() &&
454       !add_hashes_.size() && !sub_hashes_.size())
455     return true;
456
457   ChunkHeader header;
458   header.add_prefix_count = add_prefixes_.size();
459   header.sub_prefix_count = sub_prefixes_.size();
460   header.add_hash_count = add_hashes_.size();
461   header.sub_hash_count = sub_hashes_.size();
462   if (!WriteItem(header, new_file_.get(), NULL))
463     return false;
464
465   if (!WriteContainer(add_prefixes_, new_file_.get(), NULL) ||
466       !WriteContainer(sub_prefixes_, new_file_.get(), NULL) ||
467       !WriteContainer(add_hashes_, new_file_.get(), NULL) ||
468       !WriteContainer(sub_hashes_, new_file_.get(), NULL))
469     return false;
470
471   ++chunks_written_;
472
473   // Clear everything to save memory.
474   return ClearChunkBuffers();
475 }
476
477 bool SafeBrowsingStoreFile::DoUpdate(
478     const std::vector<SBAddFullHash>& pending_adds,
479     const std::set<SBPrefix>& prefix_misses,
480     SBAddPrefixes* add_prefixes_result,
481     std::vector<SBAddFullHash>* add_full_hashes_result) {
482   DCHECK(file_.get() || empty_);
483   DCHECK(new_file_.get());
484   CHECK(add_prefixes_result);
485   CHECK(add_full_hashes_result);
486
487   SBAddPrefixes add_prefixes;
488   SBSubPrefixes sub_prefixes;
489   std::vector<SBAddFullHash> add_full_hashes;
490   std::vector<SBSubFullHash> sub_full_hashes;
491
492   // Read original data into the vectors.
493   if (!empty_) {
494     DCHECK(file_.get());
495
496     if (!FileRewind(file_.get()))
497       return OnCorruptDatabase();
498
499     base::MD5Context context;
500     base::MD5Init(&context);
501
502     // Read the file header and make sure it looks right.
503     FileHeader header;
504     if (!ReadAndVerifyHeader(filename_, file_.get(), &header, &context))
505       return OnCorruptDatabase();
506
507     // Re-read the chunks-seen data to get to the later data in the
508     // file and calculate the checksum.  No new elements should be
509     // added to the sets.
510     if (!ReadToContainer(&add_chunks_cache_, header.add_chunk_count,
511                          file_.get(), &context) ||
512         !ReadToContainer(&sub_chunks_cache_, header.sub_chunk_count,
513                          file_.get(), &context))
514       return OnCorruptDatabase();
515
516     if (!ReadToContainer(&add_prefixes, header.add_prefix_count,
517                          file_.get(), &context) ||
518         !ReadToContainer(&sub_prefixes, header.sub_prefix_count,
519                          file_.get(), &context) ||
520         !ReadToContainer(&add_full_hashes, header.add_hash_count,
521                          file_.get(), &context) ||
522         !ReadToContainer(&sub_full_hashes, header.sub_hash_count,
523                          file_.get(), &context))
524       return OnCorruptDatabase();
525
526     // Calculate the digest to this point.
527     base::MD5Digest calculated_digest;
528     base::MD5Final(&calculated_digest, &context);
529
530     // Read the stored checksum and verify it.
531     base::MD5Digest file_digest;
532     if (!ReadItem(&file_digest, file_.get(), NULL))
533       return OnCorruptDatabase();
534
535     if (0 != memcmp(&file_digest, &calculated_digest, sizeof(file_digest))) {
536       RecordFormatEvent(FORMAT_EVENT_UPDATE_CHECKSUM_FAILURE);
537       return OnCorruptDatabase();
538     }
539
540     // Close the file so we can later rename over it.
541     file_.reset();
542   }
543   DCHECK(!file_.get());
544
545   // Rewind the temporary storage.
546   if (!FileRewind(new_file_.get()))
547     return false;
548
549   // Get chunk file's size for validating counts.
550   int64 size = 0;
551   if (!file_util::GetFileSize(TemporaryFileForFilename(filename_), &size))
552     return OnCorruptDatabase();
553
554   // Track update size to answer questions at http://crbug.com/72216 .
555   // Log small updates as 1k so that the 0 (underflow) bucket can be
556   // used for "empty" in SafeBrowsingDatabase.
557   UMA_HISTOGRAM_COUNTS("SB2.DatabaseUpdateKilobytes",
558                        std::max(static_cast<int>(size / 1024), 1));
559
560   // Append the accumulated chunks onto the vectors read from |file_|.
561   for (int i = 0; i < chunks_written_; ++i) {
562     ChunkHeader header;
563
564     int64 ofs = ftell(new_file_.get());
565     if (ofs == -1)
566       return false;
567
568     if (!ReadItem(&header, new_file_.get(), NULL))
569       return false;
570
571     // As a safety measure, make sure that the header describes a sane
572     // chunk, given the remaining file size.
573     int64 expected_size = ofs + sizeof(ChunkHeader);
574     expected_size += header.add_prefix_count * sizeof(SBAddPrefix);
575     expected_size += header.sub_prefix_count * sizeof(SBSubPrefix);
576     expected_size += header.add_hash_count * sizeof(SBAddFullHash);
577     expected_size += header.sub_hash_count * sizeof(SBSubFullHash);
578     if (expected_size > size)
579       return false;
580
581     // TODO(shess): If the vectors were kept sorted, then this code
582     // could use std::inplace_merge() to merge everything together in
583     // sorted order.  That might still be slower than just sorting at
584     // the end if there were a large number of chunks.  In that case
585     // some sort of recursive binary merge might be in order (merge
586     // chunks pairwise, merge those chunks pairwise, and so on, then
587     // merge the result with the main list).
588     if (!ReadToContainer(&add_prefixes, header.add_prefix_count,
589                          new_file_.get(), NULL) ||
590         !ReadToContainer(&sub_prefixes, header.sub_prefix_count,
591                          new_file_.get(), NULL) ||
592         !ReadToContainer(&add_full_hashes, header.add_hash_count,
593                          new_file_.get(), NULL) ||
594         !ReadToContainer(&sub_full_hashes, header.sub_hash_count,
595                          new_file_.get(), NULL))
596       return false;
597   }
598
599   // Append items from |pending_adds|.
600   add_full_hashes.insert(add_full_hashes.end(),
601                          pending_adds.begin(), pending_adds.end());
602
603   // Check how often a prefix was checked which wasn't in the
604   // database.
605   SBCheckPrefixMisses(add_prefixes, prefix_misses);
606
607   // Knock the subs from the adds and process deleted chunks.
608   SBProcessSubs(&add_prefixes, &sub_prefixes,
609                 &add_full_hashes, &sub_full_hashes,
610                 add_del_cache_, sub_del_cache_);
611
612   // We no longer need to track deleted chunks.
613   DeleteChunksFromSet(add_del_cache_, &add_chunks_cache_);
614   DeleteChunksFromSet(sub_del_cache_, &sub_chunks_cache_);
615
616   // Write the new data to new_file_.
617   if (!FileRewind(new_file_.get()))
618     return false;
619
620   base::MD5Context context;
621   base::MD5Init(&context);
622
623   // Write a file header.
624   FileHeader header;
625   header.magic = kFileMagic;
626   header.version = kFileVersion;
627   header.add_chunk_count = add_chunks_cache_.size();
628   header.sub_chunk_count = sub_chunks_cache_.size();
629   header.add_prefix_count = add_prefixes.size();
630   header.sub_prefix_count = sub_prefixes.size();
631   header.add_hash_count = add_full_hashes.size();
632   header.sub_hash_count = sub_full_hashes.size();
633   if (!WriteItem(header, new_file_.get(), &context))
634     return false;
635
636   // Write all the chunk data.
637   if (!WriteContainer(add_chunks_cache_, new_file_.get(), &context) ||
638       !WriteContainer(sub_chunks_cache_, new_file_.get(), &context) ||
639       !WriteContainer(add_prefixes, new_file_.get(), &context) ||
640       !WriteContainer(sub_prefixes, new_file_.get(), &context) ||
641       !WriteContainer(add_full_hashes, new_file_.get(), &context) ||
642       !WriteContainer(sub_full_hashes, new_file_.get(), &context))
643     return false;
644
645   // Write the checksum at the end.
646   base::MD5Digest digest;
647   base::MD5Final(&digest, &context);
648   if (!WriteItem(digest, new_file_.get(), NULL))
649     return false;
650
651   // Trim any excess left over from the temporary chunk data.
652   if (!file_util::TruncateFile(new_file_.get()))
653     return false;
654
655   // Close the file handle and swizzle the file into place.
656   new_file_.reset();
657   if (!base::DeleteFile(filename_, false) &&
658       base::PathExists(filename_))
659     return false;
660
661   const base::FilePath new_filename = TemporaryFileForFilename(filename_);
662   if (!base::Move(new_filename, filename_))
663     return false;
664
665   // Record counts before swapping to caller.
666   UMA_HISTOGRAM_COUNTS("SB2.AddPrefixes", add_prefixes.size());
667   UMA_HISTOGRAM_COUNTS("SB2.SubPrefixes", sub_prefixes.size());
668
669   // Pass the resulting data off to the caller.
670   add_prefixes_result->swap(add_prefixes);
671   add_full_hashes_result->swap(add_full_hashes);
672
673   return true;
674 }
675
676 bool SafeBrowsingStoreFile::FinishUpdate(
677     const std::vector<SBAddFullHash>& pending_adds,
678     const std::set<SBPrefix>& prefix_misses,
679     SBAddPrefixes* add_prefixes_result,
680     std::vector<SBAddFullHash>* add_full_hashes_result) {
681   DCHECK(add_prefixes_result);
682   DCHECK(add_full_hashes_result);
683
684   bool ret = DoUpdate(pending_adds, prefix_misses,
685                       add_prefixes_result, add_full_hashes_result);
686
687   if (!ret) {
688     CancelUpdate();
689     return false;
690   }
691
692   DCHECK(!new_file_.get());
693   DCHECK(!file_.get());
694
695   return Close();
696 }
697
698 bool SafeBrowsingStoreFile::CancelUpdate() {
699   return Close();
700 }
701
702 void SafeBrowsingStoreFile::SetAddChunk(int32 chunk_id) {
703   add_chunks_cache_.insert(chunk_id);
704 }
705
706 bool SafeBrowsingStoreFile::CheckAddChunk(int32 chunk_id) {
707   return add_chunks_cache_.count(chunk_id) > 0;
708 }
709
710 void SafeBrowsingStoreFile::GetAddChunks(std::vector<int32>* out) {
711   out->clear();
712   out->insert(out->end(), add_chunks_cache_.begin(), add_chunks_cache_.end());
713 }
714
715 void SafeBrowsingStoreFile::SetSubChunk(int32 chunk_id) {
716   sub_chunks_cache_.insert(chunk_id);
717 }
718
719 bool SafeBrowsingStoreFile::CheckSubChunk(int32 chunk_id) {
720   return sub_chunks_cache_.count(chunk_id) > 0;
721 }
722
723 void SafeBrowsingStoreFile::GetSubChunks(std::vector<int32>* out) {
724   out->clear();
725   out->insert(out->end(), sub_chunks_cache_.begin(), sub_chunks_cache_.end());
726 }
727
728 void SafeBrowsingStoreFile::DeleteAddChunk(int32 chunk_id) {
729   add_del_cache_.insert(chunk_id);
730 }
731
732 void SafeBrowsingStoreFile::DeleteSubChunk(int32 chunk_id) {
733   sub_del_cache_.insert(chunk_id);
734 }
735
736 // static
737 bool SafeBrowsingStoreFile::DeleteStore(const base::FilePath& basename) {
738   if (!base::DeleteFile(basename, false) &&
739       base::PathExists(basename)) {
740     NOTREACHED();
741     return false;
742   }
743
744   const base::FilePath new_filename = TemporaryFileForFilename(basename);
745   if (!base::DeleteFile(new_filename, false) &&
746       base::PathExists(new_filename)) {
747     NOTREACHED();
748     return false;
749   }
750
751   // With SQLite support gone, one way to get to this code is if the
752   // existing file is a SQLite file.  Make sure the journal file is
753   // also removed.
754   const base::FilePath journal_filename(
755       basename.value() + FILE_PATH_LITERAL("-journal"));
756   if (base::PathExists(journal_filename))
757     base::DeleteFile(journal_filename, false);
758
759   return true;
760 }