blob: ced59cd2919c40e83421553d59310b5daeb077a4 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/disk_cache/simple/simple_synchronous_entry.h"
#include <algorithm>
#include <cstring>
#include <functional>
#include <limits>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/hash.h"
#include "base/location.h"
#include "base/metrics/histogram.h"
#include "base/sha1.h"
#include "base/strings/stringprintf.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/simple/simple_util.h"
#include "third_party/zlib/zlib.h"
using base::kInvalidPlatformFileValue;
using base::ClosePlatformFile;
using base::FilePath;
using base::GetPlatformFileInfo;
using base::PlatformFileError;
using base::PlatformFileInfo;
using base::PLATFORM_FILE_CREATE;
using base::PLATFORM_FILE_ERROR_EXISTS;
using base::PLATFORM_FILE_OK;
using base::PLATFORM_FILE_OPEN;
using base::PLATFORM_FILE_READ;
using base::PLATFORM_FILE_WRITE;
using base::ReadPlatformFile;
using base::Time;
using base::TruncatePlatformFile;
using base::WritePlatformFile;
namespace {
// Used in histograms, please only add entries at the end.
enum OpenEntryResult {
OPEN_ENTRY_SUCCESS = 0,
OPEN_ENTRY_PLATFORM_FILE_ERROR = 1,
OPEN_ENTRY_CANT_READ_HEADER = 2,
OPEN_ENTRY_BAD_MAGIC_NUMBER = 3,
OPEN_ENTRY_BAD_VERSION = 4,
OPEN_ENTRY_CANT_READ_KEY = 5,
// OPEN_ENTRY_KEY_MISMATCH = 6, Deprecated.
OPEN_ENTRY_KEY_HASH_MISMATCH = 7,
OPEN_ENTRY_MAX = 8,
};
// Used in histograms, please only add entries at the end.
enum CreateEntryResult {
CREATE_ENTRY_SUCCESS = 0,
CREATE_ENTRY_PLATFORM_FILE_ERROR = 1,
CREATE_ENTRY_CANT_WRITE_HEADER = 2,
CREATE_ENTRY_CANT_WRITE_KEY = 3,
CREATE_ENTRY_MAX = 4,
};
// Used in histograms, please only add entries at the end.
enum WriteResult {
WRITE_RESULT_SUCCESS = 0,
WRITE_RESULT_PRETRUNCATE_FAILURE,
WRITE_RESULT_WRITE_FAILURE,
WRITE_RESULT_TRUNCATE_FAILURE,
WRITE_RESULT_MAX,
};
// Used in histograms, please only add entries at the end.
enum CheckEOFResult {
CHECK_EOF_RESULT_SUCCESS,
CHECK_EOF_RESULT_READ_FAILURE,
CHECK_EOF_RESULT_MAGIC_NUMBER_MISMATCH,
CHECK_EOF_RESULT_CRC_MISMATCH,
CHECK_EOF_RESULT_MAX,
};
// Used in histograms, please only add entries at the end.
enum CloseResult {
CLOSE_RESULT_SUCCESS,
CLOSE_RESULT_WRITE_FAILURE,
};
void RecordSyncOpenResult(OpenEntryResult result, bool had_index) {
DCHECK_GT(OPEN_ENTRY_MAX, result);
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncOpenResult", result, OPEN_ENTRY_MAX);
if (had_index) {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncOpenResult_WithIndex", result, OPEN_ENTRY_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncOpenResult_WithoutIndex", result, OPEN_ENTRY_MAX);
}
}
void RecordSyncCreateResult(CreateEntryResult result, bool had_index) {
DCHECK_GT(CREATE_ENTRY_MAX, result);
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncCreateResult", result, CREATE_ENTRY_MAX);
if (had_index) {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncCreateResult_WithIndex", result, CREATE_ENTRY_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncCreateResult_WithoutIndex", result, CREATE_ENTRY_MAX);
}
}
void RecordWriteResult(WriteResult result) {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncWriteResult", result, WRITE_RESULT_MAX);
}
void RecordCheckEOFResult(CheckEOFResult result) {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncCheckEOFResult", result, CHECK_EOF_RESULT_MAX);
}
void RecordCloseResult(CloseResult result) {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncCloseResult", result, WRITE_RESULT_MAX);
}
} // namespace
namespace disk_cache {
using simple_util::ConvertEntryHashKeyToHexString;
using simple_util::GetEntryHashKey;
using simple_util::GetFilenameFromEntryHashAndIndex;
using simple_util::GetDataSizeFromKeyAndFileSize;
using simple_util::GetFileSizeFromKeyAndDataSize;
using simple_util::GetFileOffsetFromKeyAndDataOffset;
SimpleEntryStat::SimpleEntryStat() {}
SimpleEntryStat::SimpleEntryStat(base::Time last_used_p,
base::Time last_modified_p,
const int32 data_size_p[])
: last_used(last_used_p),
last_modified(last_modified_p) {
memcpy(data_size, data_size_p, sizeof(data_size));
}
SimpleEntryCreationResults::SimpleEntryCreationResults(
SimpleEntryStat entry_stat)
: sync_entry(NULL),
entry_stat(entry_stat),
result(net::OK) {
}
SimpleEntryCreationResults::~SimpleEntryCreationResults() {
}
SimpleSynchronousEntry::CRCRecord::CRCRecord() : index(-1),
has_crc32(false),
data_crc32(0) {
}
SimpleSynchronousEntry::CRCRecord::CRCRecord(int index_p,
bool has_crc32_p,
uint32 data_crc32_p)
: index(index_p),
has_crc32(has_crc32_p),
data_crc32(data_crc32_p) {}
SimpleSynchronousEntry::EntryOperationData::EntryOperationData(int index_p,
int offset_p,
int buf_len_p)
: index(index_p),
offset(offset_p),
buf_len(buf_len_p) {}
SimpleSynchronousEntry::EntryOperationData::EntryOperationData(int index_p,
int offset_p,
int buf_len_p,
bool truncate_p)
: index(index_p),
offset(offset_p),
buf_len(buf_len_p),
truncate(truncate_p) {}
// static
void SimpleSynchronousEntry::OpenEntry(
const FilePath& path,
const uint64 entry_hash,
bool had_index,
SimpleEntryCreationResults *out_results) {
SimpleSynchronousEntry* sync_entry = new SimpleSynchronousEntry(path, "",
entry_hash);
out_results->result = sync_entry->InitializeForOpen(
had_index, &out_results->entry_stat);
if (out_results->result != net::OK) {
sync_entry->Doom();
delete sync_entry;
out_results->sync_entry = NULL;
return;
}
out_results->sync_entry = sync_entry;
}
// static
void SimpleSynchronousEntry::CreateEntry(
const FilePath& path,
const std::string& key,
const uint64 entry_hash,
bool had_index,
SimpleEntryCreationResults *out_results) {
DCHECK_EQ(entry_hash, GetEntryHashKey(key));
SimpleSynchronousEntry* sync_entry = new SimpleSynchronousEntry(path, key,
entry_hash);
out_results->result = sync_entry->InitializeForCreate(
had_index, &out_results->entry_stat);
if (out_results->result != net::OK) {
if (out_results->result != net::ERR_FILE_EXISTS)
sync_entry->Doom();
delete sync_entry;
out_results->sync_entry = NULL;
return;
}
out_results->sync_entry = sync_entry;
}
// TODO(gavinp): Move this function to its correct location in this .cc file.
// static
bool SimpleSynchronousEntry::DeleteFilesForEntryHash(
const FilePath& path,
const uint64 entry_hash) {
bool result = true;
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
FilePath to_delete = path.AppendASCII(
GetFilenameFromEntryHashAndIndex(entry_hash, i));
if (!base::DeleteFile(to_delete, false)) {
result = false;
DLOG(ERROR) << "Could not delete " << to_delete.MaybeAsASCII();
}
}
return result;
}
// static
void SimpleSynchronousEntry::DoomEntry(
const FilePath& path,
const std::string& key,
uint64 entry_hash,
int* out_result) {
DCHECK_EQ(entry_hash, GetEntryHashKey(key));
bool deleted_well = DeleteFilesForEntryHash(path, entry_hash);
*out_result = deleted_well ? net::OK : net::ERR_FAILED;
}
// static
int SimpleSynchronousEntry::DoomEntrySet(
scoped_ptr<std::vector<uint64> > key_hashes,
const FilePath& path) {
const size_t did_delete_count = std::count_if(
key_hashes->begin(), key_hashes->end(), std::bind1st(
std::ptr_fun(SimpleSynchronousEntry::DeleteFilesForEntryHash), path));
return (did_delete_count == key_hashes->size()) ? net::OK : net::ERR_FAILED;
}
void SimpleSynchronousEntry::ReadData(const EntryOperationData& in_entry_op,
net::IOBuffer* out_buf,
uint32* out_crc32,
base::Time* out_last_used,
int* out_result) const {
DCHECK(initialized_);
int64 file_offset =
GetFileOffsetFromKeyAndDataOffset(key_, in_entry_op.offset);
int bytes_read = ReadPlatformFile(files_[in_entry_op.index],
file_offset,
out_buf->data(),
in_entry_op.buf_len);
if (bytes_read > 0) {
*out_last_used = Time::Now();
*out_crc32 = crc32(crc32(0L, Z_NULL, 0),
reinterpret_cast<const Bytef*>(out_buf->data()),
bytes_read);
}
if (bytes_read >= 0) {
*out_result = bytes_read;
} else {
*out_result = net::ERR_CACHE_READ_FAILURE;
Doom();
}
}
void SimpleSynchronousEntry::WriteData(const EntryOperationData& in_entry_op,
net::IOBuffer* in_buf,
SimpleEntryStat* out_entry_stat,
int* out_result) const {
DCHECK(initialized_);
int index = in_entry_op.index;
int offset = in_entry_op.offset;
int buf_len = in_entry_op.buf_len;
int truncate = in_entry_op.truncate;
bool extending_by_write = offset + buf_len > out_entry_stat->data_size[index];
if (extending_by_write) {
// We are extending the file, and need to insure the EOF record is zeroed.
const int64 file_eof_offset = GetFileOffsetFromKeyAndDataOffset(
key_, out_entry_stat->data_size[index]);
if (!TruncatePlatformFile(files_[index], file_eof_offset)) {
RecordWriteResult(WRITE_RESULT_PRETRUNCATE_FAILURE);
Doom();
*out_result = net::ERR_CACHE_WRITE_FAILURE;
return;
}
}
const int64 file_offset = GetFileOffsetFromKeyAndDataOffset(key_, offset);
if (buf_len > 0) {
if (WritePlatformFile(
files_[index], file_offset, in_buf->data(), buf_len) != buf_len) {
RecordWriteResult(WRITE_RESULT_WRITE_FAILURE);
Doom();
*out_result = net::ERR_CACHE_WRITE_FAILURE;
return;
}
}
if (!truncate && (buf_len > 0 || !extending_by_write)) {
out_entry_stat->data_size[index] =
std::max(out_entry_stat->data_size[index], offset + buf_len);
} else {
if (!TruncatePlatformFile(files_[index], file_offset + buf_len)) {
RecordWriteResult(WRITE_RESULT_TRUNCATE_FAILURE);
Doom();
*out_result = net::ERR_CACHE_WRITE_FAILURE;
return;
}
out_entry_stat->data_size[index] = offset + buf_len;
}
RecordWriteResult(WRITE_RESULT_SUCCESS);
out_entry_stat->last_used = out_entry_stat->last_modified = Time::Now();
*out_result = buf_len;
}
void SimpleSynchronousEntry::CheckEOFRecord(int index,
int32 data_size,
uint32 expected_crc32,
int* out_result) const {
DCHECK(initialized_);
SimpleFileEOF eof_record;
int64 file_offset = GetFileOffsetFromKeyAndDataOffset(key_, data_size);
if (ReadPlatformFile(files_[index],
file_offset,
reinterpret_cast<char*>(&eof_record),
sizeof(eof_record)) != sizeof(eof_record)) {
RecordCheckEOFResult(CHECK_EOF_RESULT_READ_FAILURE);
Doom();
*out_result = net::ERR_CACHE_CHECKSUM_READ_FAILURE;
return;
}
if (eof_record.final_magic_number != kSimpleFinalMagicNumber) {
RecordCheckEOFResult(CHECK_EOF_RESULT_MAGIC_NUMBER_MISMATCH);
DLOG(INFO) << "eof record had bad magic number.";
Doom();
*out_result = net::ERR_CACHE_CHECKSUM_READ_FAILURE;
return;
}
const bool has_crc = (eof_record.flags & SimpleFileEOF::FLAG_HAS_CRC32) ==
SimpleFileEOF::FLAG_HAS_CRC32;
UMA_HISTOGRAM_BOOLEAN("SimpleCache.SyncCheckEOFHasCrc", has_crc);
if (has_crc && eof_record.data_crc32 != expected_crc32) {
RecordCheckEOFResult(CHECK_EOF_RESULT_CRC_MISMATCH);
DLOG(INFO) << "eof record had bad crc.";
Doom();
*out_result = net::ERR_CACHE_CHECKSUM_MISMATCH;
return;
}
RecordCheckEOFResult(CHECK_EOF_RESULT_SUCCESS);
*out_result = net::OK;
}
void SimpleSynchronousEntry::Close(
const SimpleEntryStat& entry_stat,
scoped_ptr<std::vector<CRCRecord> > crc32s_to_write) {
for (std::vector<CRCRecord>::const_iterator it = crc32s_to_write->begin();
it != crc32s_to_write->end(); ++it) {
SimpleFileEOF eof_record;
eof_record.final_magic_number = kSimpleFinalMagicNumber;
eof_record.flags = 0;
if (it->has_crc32)
eof_record.flags |= SimpleFileEOF::FLAG_HAS_CRC32;
eof_record.data_crc32 = it->data_crc32;
int64 file_offset = GetFileOffsetFromKeyAndDataOffset(
key_, entry_stat.data_size[it->index]);
if (WritePlatformFile(files_[it->index],
file_offset,
reinterpret_cast<const char*>(&eof_record),
sizeof(eof_record)) != sizeof(eof_record)) {
RecordCloseResult(CLOSE_RESULT_WRITE_FAILURE);
DLOG(INFO) << "Could not write eof record.";
Doom();
break;
}
const int64 file_size = file_offset + sizeof(eof_record);
UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.LastClusterSize",
file_size % 4096, 0, 4097, 50);
const int64 cluster_loss = file_size % 4096 ? 4096 - file_size % 4096 : 0;
UMA_HISTOGRAM_PERCENTAGE("SimpleCache.LastClusterLossPercent",
cluster_loss * 100 / (cluster_loss + file_size));
}
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
bool did_close_file = ClosePlatformFile(files_[i]);
CHECK(did_close_file);
}
RecordCloseResult(CLOSE_RESULT_SUCCESS);
have_open_files_ = false;
delete this;
}
SimpleSynchronousEntry::SimpleSynchronousEntry(const FilePath& path,
const std::string& key,
const uint64 entry_hash)
: path_(path),
entry_hash_(entry_hash),
key_(key),
have_open_files_(false),
initialized_(false) {
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
files_[i] = kInvalidPlatformFileValue;
}
}
SimpleSynchronousEntry::~SimpleSynchronousEntry() {
DCHECK(!(have_open_files_ && initialized_));
if (have_open_files_)
CloseFiles();
}
bool SimpleSynchronousEntry::OpenOrCreateFiles(
bool create,
bool had_index,
SimpleEntryStat* out_entry_stat) {
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
FilePath filename = path_.AppendASCII(
GetFilenameFromEntryHashAndIndex(entry_hash_, i));
int flags = PLATFORM_FILE_READ | PLATFORM_FILE_WRITE;
if (create)
flags |= PLATFORM_FILE_CREATE;
else
flags |= PLATFORM_FILE_OPEN;
PlatformFileError error;
files_[i] = CreatePlatformFile(filename, flags, NULL, &error);
if (error != PLATFORM_FILE_OK) {
// TODO(ttuttle,gavinp): Remove one each of these triplets of histograms.
// We can calculate the third as the sum or difference of the other two.
if (create) {
RecordSyncCreateResult(CREATE_ENTRY_PLATFORM_FILE_ERROR, had_index);
UMA_HISTOGRAM_ENUMERATION("SimpleCache.SyncCreatePlatformFileError",
-error, -base::PLATFORM_FILE_ERROR_MAX);
if (had_index) {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncCreatePlatformFileError_WithIndex",
-error, -base::PLATFORM_FILE_ERROR_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncCreatePlatformFileError_WithoutIndex",
-error, -base::PLATFORM_FILE_ERROR_MAX);
}
} else {
RecordSyncOpenResult(OPEN_ENTRY_PLATFORM_FILE_ERROR, had_index);
UMA_HISTOGRAM_ENUMERATION("SimpleCache.SyncOpenPlatformFileError",
-error, -base::PLATFORM_FILE_ERROR_MAX);
if (had_index) {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncOpenPlatformFileError_WithIndex",
-error, -base::PLATFORM_FILE_ERROR_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION(
"SimpleCache.SyncOpenPlatformFileError_WithoutIndex",
-error, -base::PLATFORM_FILE_ERROR_MAX);
}
}
while (--i >= 0) {
bool ALLOW_UNUSED did_close = ClosePlatformFile(files_[i]);
DLOG_IF(INFO, !did_close) << "Could not close file "
<< filename.MaybeAsASCII();
}
return false;
}
}
have_open_files_ = true;
if (create) {
out_entry_stat->last_modified = out_entry_stat->last_used = Time::Now();
for (int i = 0; i < kSimpleEntryFileCount; ++i)
out_entry_stat->data_size[i] = 0;
} else {
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
PlatformFileInfo file_info;
bool success = GetPlatformFileInfo(files_[i], &file_info);
if (!success) {
DLOG(WARNING) << "Could not get platform file info.";
continue;
}
out_entry_stat->last_used = file_info.last_accessed;
out_entry_stat->last_modified = file_info.last_modified;
base::TimeDelta entry_age =
base::Time::Now() - out_entry_stat->last_modified;
UMA_HISTOGRAM_CUSTOM_COUNTS(
"SimpleCache.SyncOpenEntryAge", entry_age.InHours(), 1, 1000, 50);
// Keep the file size in |data size_| briefly until the key is initialized
// properly.
out_entry_stat->data_size[i] = file_info.size;
}
}
return true;
}
void SimpleSynchronousEntry::CloseFiles() {
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
DCHECK_NE(kInvalidPlatformFileValue, files_[i]);
bool did_close = ClosePlatformFile(files_[i]);
DCHECK(did_close);
}
}
int SimpleSynchronousEntry::InitializeForOpen(bool had_index,
SimpleEntryStat* out_entry_stat) {
DCHECK(!initialized_);
if (!OpenOrCreateFiles(false, had_index, out_entry_stat))
return net::ERR_FAILED;
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
SimpleFileHeader header;
int header_read_result =
ReadPlatformFile(files_[i], 0, reinterpret_cast<char*>(&header),
sizeof(header));
if (header_read_result != sizeof(header)) {
DLOG(WARNING) << "Cannot read header from entry.";
RecordSyncOpenResult(OPEN_ENTRY_CANT_READ_HEADER, had_index);
return net::ERR_FAILED;
}
if (header.initial_magic_number != kSimpleInitialMagicNumber) {
// TODO(gavinp): This seems very bad; for now we log at WARNING, but we
// should give consideration to not saturating the log with these if that
// becomes a problem.
DLOG(WARNING) << "Magic number did not match.";
RecordSyncOpenResult(OPEN_ENTRY_BAD_MAGIC_NUMBER, had_index);
return net::ERR_FAILED;
}
if (header.version != kSimpleVersion) {
DLOG(WARNING) << "Unreadable version.";
RecordSyncOpenResult(OPEN_ENTRY_BAD_VERSION, had_index);
return net::ERR_FAILED;
}
scoped_ptr<char[]> key(new char[header.key_length]);
int key_read_result = ReadPlatformFile(files_[i], sizeof(header),
key.get(), header.key_length);
if (key_read_result != implicit_cast<int>(header.key_length)) {
DLOG(WARNING) << "Cannot read key from entry.";
RecordSyncOpenResult(OPEN_ENTRY_CANT_READ_KEY, had_index);
return net::ERR_FAILED;
}
key_ = std::string(key.get(), header.key_length);
out_entry_stat->data_size[i] =
GetDataSizeFromKeyAndFileSize(key_, out_entry_stat->data_size[i]);
if (out_entry_stat->data_size[i] < 0) {
// This entry can't possibly be valid, as it does not have enough space to
// store a valid SimpleFileEOF record.
return net::ERR_FAILED;
}
if (base::Hash(key.get(), header.key_length) != header.key_hash) {
DLOG(WARNING) << "Hash mismatch on key.";
RecordSyncOpenResult(OPEN_ENTRY_KEY_HASH_MISMATCH, had_index);
return net::ERR_FAILED;
}
}
RecordSyncOpenResult(OPEN_ENTRY_SUCCESS, had_index);
initialized_ = true;
return net::OK;
}
int SimpleSynchronousEntry::InitializeForCreate(
bool had_index,
SimpleEntryStat* out_entry_stat) {
DCHECK(!initialized_);
if (!OpenOrCreateFiles(true, had_index, out_entry_stat)) {
DLOG(WARNING) << "Could not create platform files.";
return net::ERR_FILE_EXISTS;
}
for (int i = 0; i < kSimpleEntryFileCount; ++i) {
SimpleFileHeader header;
header.initial_magic_number = kSimpleInitialMagicNumber;
header.version = kSimpleVersion;
header.key_length = key_.size();
header.key_hash = base::Hash(key_);
if (WritePlatformFile(files_[i], 0, reinterpret_cast<char*>(&header),
sizeof(header)) != sizeof(header)) {
DLOG(WARNING) << "Could not write headers to new cache entry.";
RecordSyncCreateResult(CREATE_ENTRY_CANT_WRITE_HEADER, had_index);
return net::ERR_FAILED;
}
if (WritePlatformFile(files_[i], sizeof(header), key_.data(),
key_.size()) != implicit_cast<int>(key_.size())) {
DLOG(WARNING) << "Could not write keys to new cache entry.";
RecordSyncCreateResult(CREATE_ENTRY_CANT_WRITE_KEY, had_index);
return net::ERR_FAILED;
}
}
RecordSyncCreateResult(CREATE_ENTRY_SUCCESS, had_index);
initialized_ = true;
return net::OK;
}
void SimpleSynchronousEntry::Doom() const {
// TODO(gavinp): Consider if we should guard against redundant Doom() calls.
DeleteFilesForEntryHash(path_, entry_hash_);
}
} // namespace disk_cache