Home | History | Annotate | Download | only in safe_browsing
      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 }
    761