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