diff options
author | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-02 19:14:29 +0000 |
---|---|---|
committer | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-02 19:14:29 +0000 |
commit | 07c3f84910a6bc63a69d5f7fe0725090cf2af1ce (patch) | |
tree | 6450123619273152c71b984d2565d22565dec244 /net/tools | |
parent | 606843faf131685e7802df12f2ee97efc7cf2f3b (diff) | |
download | chromium_src-07c3f84910a6bc63a69d5f7fe0725090cf2af1ce.zip chromium_src-07c3f84910a6bc63a69d5f7fe0725090cf2af1ce.tar.gz chromium_src-07c3f84910a6bc63a69d5f7fe0725090cf2af1ce.tar.bz2 |
Disk cache: Add a tool to upgrade a set of cache files from one version to
another.
Also moves crash_cache project to the "tools folder" on the solution.
Review URL: http://codereview.chromium.org/12851
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@6226 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/tools')
-rw-r--r-- | net/tools/dump_cache/dump_cache.cc | 160 | ||||
-rw-r--r-- | net/tools/dump_cache/dump_files.cc | 300 | ||||
-rw-r--r-- | net/tools/dump_cache/upgrade.cc | 791 |
3 files changed, 1251 insertions, 0 deletions
diff --git a/net/tools/dump_cache/dump_cache.cc b/net/tools/dump_cache/dump_cache.cc new file mode 100644 index 0000000..2aa3b56 --- /dev/null +++ b/net/tools/dump_cache/dump_cache.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2008 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. + +// This command-line program dumps the contents of a set of cache files, either +// to stdout or to another set of cache files. + +#include <stdio.h> +#include <string> + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/process_util.h" +#include "base/scoped_handle.h" +#include "base/string_util.h" + +#include "net/disk_cache/disk_format.h" + +enum Errors { + GENERIC = -1, + ALL_GOOD = 0, + INVALID_ARGUMENT = 1, + FILE_ACCESS_ERROR, + UNKNOWN_VERSION, + TOOL_NOT_FOUND, +}; + +int GetMajorVersion(const std::wstring input_path); +int DumpContents(const std::wstring input_path); +int DumpHeaders(const std::wstring input_path); +int RunSlave(const std::wstring input_path, const std::wstring pipe_number); +int Upgrade(const std::wstring output_path, HANDLE pipe); +HANDLE CreateServer(std::wstring* pipe_number); + +const char kUpgradeHelp[] = + "\nIn order to use the upgrade function, a version of this tool that\n" + "understands the file format of the files to upgrade is needed. For\n" + "instance, to upgrade files saved with file format 3.4 to version 5.2,\n" + "a version of this program that was compiled with version 3.4 has to be\n" + "located beside this executable, and named dump_cache_3.exe, and this\n" + "executable should be compiled with version 5.2 being the current one."; + +// Folders to read and write cache files. +const wchar_t kInputPath[] = L"input"; +const wchar_t kOutputPath[] = L"output"; + +// Dumps the file headers to stdout. +const wchar_t kDumpHeaders[] = L"dump-headers"; + +// Dumps all entries to stdout. +const wchar_t kDumpContents[] = L"dump-contents"; + +// Upgrade an old version to the current one. +const wchar_t kUpgrade[] = L"upgrade"; + +// Internal use: +const wchar_t kSlave[] = L"slave"; +const wchar_t kPipe[] = L"pipe"; + +int Help() { + printf("warning: input files are modified by this tool\n"); + printf("dump_cache --input=path1 [--output=path2]\n"); + printf("--dump-headers: display file headers\n"); + printf("--dump-contents: display all entries\n"); + printf("--upgrade: copy contents to the output path\n"); + return INVALID_ARGUMENT; +} + +// Starts a new process, to generate the files. +int LaunchSlave(const CommandLine &command_line, const std::wstring pipe_number, + int version) { + std::wstring new_command_line = command_line.command_line_string(); + const std::wstring old_exe(L"dump_cache.exe"); + size_t to_remove = new_command_line.find(old_exe); + new_command_line.erase(to_remove, old_exe.size()); + + std::wstring new_program = StringPrintf(L"%ls%d.exe", L"dump_cache_", + version); + new_command_line.insert(to_remove, new_program); + if (command_line.HasSwitch(kUpgrade)) + CommandLine::AppendSwitch(&new_command_line, kSlave); + + CommandLine::AppendSwitchWithValue(&new_command_line, kPipe, pipe_number); + if (!base::LaunchApp(new_command_line, false, false, NULL)) { + printf("Unable to launch the needed version of this tool: %ls\n", + new_program.c_str()); + printf(kUpgradeHelp); + return TOOL_NOT_FOUND; + } + return ALL_GOOD; +} + +// ----------------------------------------------------------------------- + +int main(int argc, const char* argv[]) { + // Setup an AtExitManager so Singleton objects will be destroyed. + base::AtExitManager at_exit_manager; + + CommandLine command_line; + std::wstring input_path = command_line.GetSwitchValue(kInputPath); + if (input_path.empty()) + return Help(); + + bool upgrade = false; + bool slave_required = false; + std::wstring output_path; + if (command_line.HasSwitch(kUpgrade)) { + output_path = command_line.GetSwitchValue(kOutputPath); + if (output_path.empty()) + return Help(); + slave_required = true; + upgrade = true; + } + + int version = GetMajorVersion(input_path); + if (!version) + return FILE_ACCESS_ERROR; + + if (version != disk_cache::kCurrentVersion >> 16) { + if (command_line.HasSwitch(kSlave)) { + printf("Unknown version\n"); + return UNKNOWN_VERSION; + } + slave_required = true; + } + + std::wstring pipe_number = command_line.GetSwitchValue(kPipe); + if (command_line.HasSwitch(kSlave) && slave_required) + return RunSlave(input_path, pipe_number); + + ScopedHandle server; + if (slave_required) { + server.Set(CreateServer(&pipe_number)); + if (!server.IsValid()) { + printf("Unable to create the server pipe\n"); + return -1; + } + + int ret = LaunchSlave(command_line, pipe_number, version); + if (ret) + return ret; + } + + if (upgrade) + return Upgrade(output_path, server); + + if (slave_required) { + // Wait until the slave starts dumping data before we quit. Lazy "fix" for a + // console quirk. + Sleep(500); + return ALL_GOOD; + } + + if (command_line.HasSwitch(kDumpContents)) + return DumpContents(input_path); + if (command_line.HasSwitch(kDumpHeaders)) + return DumpHeaders(input_path); + return Help(); +} + diff --git a/net/tools/dump_cache/dump_files.cc b/net/tools/dump_cache/dump_files.cc new file mode 100644 index 0000000..67bdad2 --- /dev/null +++ b/net/tools/dump_cache/dump_files.cc @@ -0,0 +1,300 @@ +// Copyright (c) 2008 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. + +// Performs basic inspection of the disk cache files with minimal disruption +// to the actual files (they still may change if an error is detected on the +// files). + +#include <stdio.h> +#include <string> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "net/base/file_stream.h" +#include "net/disk_cache/block_files.h" +#include "net/disk_cache/disk_format.h" +#include "net/disk_cache/mapped_file.h" +#include "net/disk_cache/storage_block.h" + +namespace { + +const wchar_t kIndexName[] = L"index"; +const wchar_t kDataPrefix[] = L"data_"; + +// Reads the |header_size| bytes from the beginning of file |name|. +bool ReadHeader(const std::wstring name, char* header, int header_size) { + net::FileStream file; + file.Open(name, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ); + if (!file.IsOpen()) { + printf("Unable to open file %ls\n", name.c_str()); + return false; + } + + int read = file.Read(header, header_size, NULL); + if (read != header_size) { + printf("Unable to read file %ls\n", name.c_str()); + return false; + } + return true; +} + +int GetMajorVersionFromFile(const std::wstring name) { + disk_cache::IndexHeader header; + if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) + return 0; + + return header.version >> 16; +} + +// Dumps the contents of the Index-file header. +void DumpIndexHeader(const std::wstring name) { + disk_cache::IndexHeader header; + if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) + return; + + printf("Index file:\n"); + printf("magic: %x\n", header.magic); + printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff); + printf("entries: %d\n", header.num_entries); + printf("total bytes: %d\n", header.num_bytes); + printf("last file number: %d\n", header.last_file); + printf("current id: %d\n", header.this_id); + printf("table length: %d\n", header.table_len); + printf("-------------------------\n\n"); +} + +// Dumps the contents of a block-file header. +void DumpBlockHeader(const std::wstring name) { + disk_cache::BlockFileHeader header; + if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) + return; + + std::wstring file_name = file_util::GetFilenameFromPath(name); + + printf("Block file: %ls\n", file_name.c_str()); + printf("magic: %x\n", header.magic); + printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff); + printf("file id: %d\n", header.this_file); + printf("next file id: %d\n", header.next_file); + printf("entry size: %d\n", header.entry_size); + printf("current entries: %d\n", header.num_entries); + printf("max entries: %d\n", header.max_entries); + printf("updating: %d\n", header.updating); + printf("empty sz 1: %d\n", header.empty[0]); + printf("empty sz 2: %d\n", header.empty[1]); + printf("empty sz 3: %d\n", header.empty[2]); + printf("empty sz 4: %d\n", header.empty[3]); + printf("user 0: 0x%x\n", header.user[0]); + printf("user 1: 0x%x\n", header.user[1]); + printf("user 2: 0x%x\n", header.user[2]); + printf("user 3: 0x%x\n", header.user[3]); + printf("-------------------------\n\n"); +} + +// Simple class that interacts with the set of cache files. +class CacheDumper { + public: + explicit CacheDumper(const std::wstring path) + : path_(path), block_files_(path), index_(NULL) {} + + bool Init(); + + // Reads an entry from disk. Return false when all entries have been already + // returned. + bool GetEntry(disk_cache::EntryStore* entry); + + // Loads a specific block from the block files. + bool LoadEntry(disk_cache::CacheAddr addr, disk_cache::EntryStore* entry); + bool LoadRankings(disk_cache::CacheAddr addr, + disk_cache::RankingsNode* rankings); + + private: + std::wstring path_; + disk_cache::BlockFiles block_files_; + scoped_refptr<disk_cache::MappedFile> index_file_; + disk_cache::Index* index_; + int current_hash_; + disk_cache::CacheAddr next_addr_; + DISALLOW_COPY_AND_ASSIGN(CacheDumper); +}; + +bool CacheDumper::Init() { + if (!block_files_.Init(false)) { + printf("Unable to init block files\n"); + return false; + } + + std::wstring index_name(path_); + file_util::AppendToPath(&index_name, kIndexName); + index_file_ = new disk_cache::MappedFile; + index_ = + reinterpret_cast<disk_cache::Index*>(index_file_->Init(index_name, 0)); + if (!index_) { + printf("Unable to map index\n"); + return false; + } + + current_hash_ = 0; + next_addr_ = 0; + return true; +} + +bool CacheDumper::GetEntry(disk_cache::EntryStore* entry) { + if (next_addr_) { + if (LoadEntry(next_addr_, entry)) { + next_addr_ = entry->next; + if (!next_addr_) + current_hash_++; + return true; + } else { + printf("Unable to load entry at address 0x%x\n", next_addr_); + next_addr_ = 0; + current_hash_++; + } + } + + for (int i = current_hash_; i < index_->header.table_len; i++) { + // Yes, we'll crash if the table is shorter than expected, but only after + // dumping every entry that we can find. + if (index_->table[i]) { + current_hash_ = i; + if (LoadEntry(index_->table[i], entry)) { + next_addr_ = entry->next; + if (!next_addr_) + current_hash_++; + return true; + } else { + printf("Unable to load entry at address 0x%x\n", index_->table[i]); + } + } + } + return false; +} + +bool CacheDumper::LoadEntry(disk_cache::CacheAddr addr, + disk_cache::EntryStore* entry) { + disk_cache::Addr address(addr); + disk_cache::MappedFile* file = block_files_.GetFile(address); + if (!file) + return false; + + disk_cache::CacheEntryBlock entry_block(file, address); + if (!entry_block.Load()) + return false; + + memcpy(entry, entry_block.Data(), sizeof(*entry)); + printf("Entry at 0x%x\n", addr); + return true; +} + +bool CacheDumper::LoadRankings(disk_cache::CacheAddr addr, + disk_cache::RankingsNode* rankings) { + disk_cache::Addr address(addr); + disk_cache::MappedFile* file = block_files_.GetFile(address); + if (!file) + return false; + + disk_cache::CacheRankingsBlock rank_block(file, address); + if (!rank_block.Load()) + return false; + + memcpy(rankings, rank_block.Data(), sizeof(*rankings)); + printf("Rankings at 0x%x\n", addr); + return true; +} + +void DumpEntry(const disk_cache::EntryStore& entry) { + std::string key; + if (!entry.long_key) { + key = entry.key; + if (key.size() > 50) + key.resize(50); + } + + printf("hash: 0x%x\n", entry.hash); + printf("next entry: 0x%x\n", entry.next); + printf("rankings: 0x%x\n", entry.rankings_node); + printf("key length: %d\n", entry.key_len); + printf("key: \"%s\"\n", key.c_str()); + printf("key addr: 0x%x\n", entry.long_key); + printf("data size 0: %d\n", entry.data_size[0]); + printf("data size 1: %d\n", entry.data_size[1]); + printf("data addr 0: 0x%x\n", entry.data_addr[0]); + printf("data addr 1: 0x%x\n", entry.data_addr[1]); + printf("----------\n\n"); +} + +void DumpRankings(const disk_cache::RankingsNode& rankings) { + printf("next: 0x%x\n", rankings.next); + printf("prev: 0x%x\n", rankings.prev); + printf("entry: 0x%x\n", rankings.contents); + printf("dirty: %d\n", rankings.dirty); + printf("pointer: 0x%x\n", rankings.pointer); + printf("----------\n\n"); +} + +} // namespace. + +// ----------------------------------------------------------------------- + +int GetMajorVersion(const std::wstring input_path) { + std::wstring index_name(input_path); + file_util::AppendToPath(&index_name, kIndexName); + + int version = GetMajorVersionFromFile(index_name); + if (!version) + return 0; + + std::wstring data_name(input_path); + file_util::AppendToPath(&data_name, L"data_0"); + if (version != GetMajorVersionFromFile(data_name)) + return 0; + + data_name = input_path; + file_util::AppendToPath(&data_name, L"data_1"); + if (version != GetMajorVersionFromFile(data_name)) + return 0; + + return version; +} + +// Dumps the headers of all files. +int DumpHeaders(const std::wstring input_path) { + std::wstring index_name(input_path); + file_util::AppendToPath(&index_name, kIndexName); + DumpIndexHeader(index_name); + + std::wstring pattern(kDataPrefix); + pattern.append(L"*"); + file_util::FileEnumerator iter(input_path, false, + file_util::FileEnumerator::FILES, pattern); + for (std::wstring file = iter.Next(); !file.empty(); file = iter.Next()) { + DumpBlockHeader(file); + } + + return 0; +} + +// Dumps all entries from the cache. +int DumpContents(const std::wstring input_path) { + DumpHeaders(input_path); + + // We need a message loop, although we really don't run any task. + MessageLoop loop(MessageLoop::TYPE_IO); + CacheDumper dumper(input_path); + if (!dumper.Init()) + return -1; + + disk_cache::EntryStore entry; + while (dumper.GetEntry(&entry)) { + DumpEntry(entry); + disk_cache::RankingsNode rankings; + if (dumper.LoadRankings(entry.rankings_node, &rankings)) + DumpRankings(rankings); + } + + printf("Done.\n"); + + return 0; +} diff --git a/net/tools/dump_cache/upgrade.cc b/net/tools/dump_cache/upgrade.cc new file mode 100644 index 0000000..1495b95 --- /dev/null +++ b/net/tools/dump_cache/upgrade.cc @@ -0,0 +1,791 @@ +// Copyright (c) 2008 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 "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "net/disk_cache/backend_impl.h" +#include "net/disk_cache/entry_impl.h" + +namespace { + +const wchar_t kPipePrefix[] = L"\\\\.\\pipe\\dump_cache_"; +const int kChannelSize = 64 * 1024; +const int kNumStreams = 2; + +// Simple macro to print out formatted debug messages. It is similar to a DLOG +// except that it doesn't include a header. +#ifdef NDEBUG +#define DEBUGMSG(...) {} +#else +#define DEBUGMSG(...) { printf(__VA_ARGS__); } +#endif + +HANDLE OpenServer(const std::wstring pipe_number) { + std::wstring pipe_name(kPipePrefix); + pipe_name.append(pipe_number); + return CreateFile(pipe_name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); +} + +// This is the basic message to use between the two processes. It is intended +// to transmit a single action (like "get the key name for entry xx"), with up +// to 5 32-bit arguments and 4 64-bit arguments. After this structure, the rest +// of the message has |buffer_bytes| of length with the actual data. +struct Message { + int32 command; + int32 result; + int32 buffer_bytes; + int32 arg1; + int32 arg2; + int32 arg3; + int32 arg4; + int32 arg5; + int64 long_arg1; + int64 long_arg2; + int64 long_arg3; + int64 long_arg4; + Message() { + memset(this, 0, sizeof(*this)); + } + Message& operator= (const Message& other) { + memcpy(this, &other, sizeof(*this)); + return *this; + } +}; + +const int kBufferSize = kChannelSize - sizeof(Message); +struct IoBuffer { + Message msg; + char buffer[kBufferSize]; +}; +COMPILE_ASSERT(sizeof(IoBuffer) == kChannelSize, invalid_io_buffer); + + +// The list of commands. +// Currently, there is support for working ONLY with one entry at a time. +enum { + // Get the entry from list |arg1| that follows |long_arg1|. + // The result is placed on |long_arg1| (closes the previous one). + GET_NEXT_ENTRY = 1, + // Get the entry from list |arg1| that precedes |long_arg1|. + // The result is placed on |long_arg1| (closes the previous one). + GET_PREV_ENTRY, + // Closes the entry |long_arg1|. + CLOSE_ENTRY, + // Get the key of the entry |long_arg1|. + GET_KEY, + // Get last used (long_arg2) and last modified (long_arg3) times for the + // entry at |long_arg1|. + GET_USE_TIMES, + // Returns on |arg2| the data size in bytes if the stream |arg1| of entry at + // |long_arg1|. + GET_DATA_SIZE, + // Returns |arg2| bytes of the stream |arg1| for the entry at |long_arg1|, + // starting at offset |arg3|. + READ_DATA, + // End processing requests. + QUIT +}; + +// The list of return codes. +enum { + RESULT_OK = 0, + RESULT_UNKNOWN_COMMAND, + RESULT_INVALID_PARAMETER, + RESULT_NAME_OVERFLOW +}; + +// ----------------------------------------------------------------------- + +class BaseSM : public MessageLoopForIO::IOHandler { + public: + BaseSM(disk_cache::BackendImpl* cache, HANDLE channel); + virtual ~BaseSM(); + + protected: + bool SendMsg(const Message& msg); + bool ReceiveMsg(); + bool ConnectChannel(); + bool IsPending(); + + MessageLoopForIO::IOContext in_context_; + MessageLoopForIO::IOContext out_context_; + disk_cache::BackendImpl* cache_; + disk_cache::EntryImpl* entry_; + HANDLE channel_; + int state_; + int pending_count_; + scoped_array<char> in_buffer_; + scoped_array<char> out_buffer_; + IoBuffer* input_; + IoBuffer* output_; + DISALLOW_COPY_AND_ASSIGN(BaseSM); +}; + +BaseSM::BaseSM(disk_cache::BackendImpl* cache, HANDLE channel) + : cache_(cache), entry_(NULL), channel_(channel), state_(0), + pending_count_(0) { + in_buffer_.reset(new char[kChannelSize]); + out_buffer_.reset(new char[kChannelSize]); + input_ = reinterpret_cast<IoBuffer*>(in_buffer_.get()); + output_ = reinterpret_cast<IoBuffer*>(out_buffer_.get()); + + memset(&in_context_, 0, sizeof(in_context_)); + memset(&out_context_, 0, sizeof(out_context_)); + in_context_.handler = this; + out_context_.handler = this; + MessageLoopForIO::current()->RegisterIOHandler(channel_, this); +} + +BaseSM::~BaseSM() { + if (entry_) + entry_->Close(); +} + +bool BaseSM::SendMsg(const Message& msg) { + // Only one command will be in-flight at a time. Let's start the Read IO here + // when we know that it will be pending. + if (!ReceiveMsg()) + return false; + + output_->msg = msg; + DWORD written; + if (!WriteFile(channel_, output_, sizeof(msg) + msg.buffer_bytes, &written, + &out_context_.overlapped)) { + if (ERROR_IO_PENDING != GetLastError()) + return false; + } + pending_count_++; + return true; +} + +bool BaseSM::ReceiveMsg() { + DWORD read; + if (!ReadFile(channel_, input_, kChannelSize, &read, + &in_context_.overlapped)) { + if (ERROR_IO_PENDING != GetLastError()) + return false; + } + pending_count_++; + return true; +} + +bool BaseSM::ConnectChannel() { + if (!ConnectNamedPipe(channel_, &in_context_.overlapped)) { + DWORD error = GetLastError(); + if (ERROR_PIPE_CONNECTED == error) + return true; + // By returning true in case of a generic error, we allow the operation to + // fail while sending the first message. + if (ERROR_IO_PENDING != error) + return true; + } + pending_count_++; + return false; +} + +bool BaseSM::IsPending() { + return pending_count_ != 0; +} + +// ----------------------------------------------------------------------- + +class MasterSM : public BaseSM { + public: + MasterSM(disk_cache::BackendImpl* cache, HANDLE channel) + : BaseSM(cache, channel) {} + virtual ~MasterSM() {} + + bool DoInit(); + virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error); + + private: + enum { + MASTER_INITIAL = 0, + MASTER_CONNECT, + MASTER_GET_ENTRY, + MASTER_GET_NEXT_ENTRY, + MASTER_GET_KEY, + MASTER_GET_USE_TIMES, + MASTER_GET_DATA_SIZE, + MASTER_READ_DATA, + MASTER_END + }; + + void SendGetPrevEntry(); + void DoGetEntry(); + void DoGetKey(int bytes_read); + void DoGetUseTimes(); + void SendGetDataSize(); + void DoGetDataSize(); + void CloseEntry(); + void SendReadData(); + void DoReadData(int bytes_read); + void SendQuit(); + void DoEnd(); + void Fail(); + + base::Time last_used_; + base::Time last_modified_; + int64 remote_entry_; + int stream_; + int bytes_remaining_; + int offset_; + int copied_entries_; +}; + +void MasterSM::OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error) { + pending_count_--; + if (context == &out_context_) { + if (!error) + return; + return Fail(); + } + + int bytes_read = static_cast<int>(bytes_transfered); + if (bytes_read < sizeof(Message) && state_ != MASTER_END && + state_ != MASTER_CONNECT) { + printf("Communication breakdown\n"); + return Fail(); + } + + switch (state_) { + case MASTER_CONNECT: + SendGetPrevEntry(); + break; + case MASTER_GET_ENTRY: + DoGetEntry(); + break; + case MASTER_GET_KEY: + DoGetKey(bytes_read); + break; + case MASTER_GET_USE_TIMES: + DoGetUseTimes(); + break; + case MASTER_GET_DATA_SIZE: + DoGetDataSize(); + break; + case MASTER_READ_DATA: + DoReadData(bytes_read); + break; + case MASTER_END: + if (!IsPending()) + DoEnd(); + break; + default: + NOTREACHED(); + break; + } +} + +bool MasterSM::DoInit() { + DEBUGMSG("Master DoInit\n"); + DCHECK(state_ == MASTER_INITIAL); + + copied_entries_ = 0; + remote_entry_ = 0; + + if (ConnectChannel()) { + SendGetPrevEntry(); + // If we don't have pending operations we couldn't connect. + return IsPending(); + } + + state_ = MASTER_CONNECT; + return true; +} + +void MasterSM::SendGetPrevEntry() { + DEBUGMSG("Master SendGetPrevEntry\n"); + state_ = MASTER_GET_ENTRY; + Message msg; + msg.command = GET_PREV_ENTRY; + msg.long_arg1 = remote_entry_; + SendMsg(msg); +} + +void MasterSM::DoGetEntry() { + DEBUGMSG("Master DoGetEntry\n"); + DCHECK(state_ == MASTER_GET_ENTRY); + DCHECK(input_->msg.command == GET_PREV_ENTRY); + if (input_->msg.result != RESULT_OK) + return Fail(); + + if (!input_->msg.long_arg1) { + printf("Done: %d entries copied over.\n", copied_entries_); + return SendQuit(); + } + remote_entry_ = input_->msg.long_arg1; + state_ = MASTER_GET_KEY; + Message msg; + msg.command = GET_KEY; + msg.long_arg1 = remote_entry_; + SendMsg(msg); +} + +void MasterSM::DoGetKey(int bytes_read) { + DEBUGMSG("Master DoGetKey\n"); + DCHECK(state_ == MASTER_GET_KEY); + DCHECK(input_->msg.command == GET_KEY); + if (input_->msg.result == RESULT_NAME_OVERFLOW) + // The key is too long. Just move on. + return SendGetPrevEntry(); + + if (input_->msg.result != RESULT_OK) + return Fail(); + + std::string key(input_->buffer); + DCHECK(key.size() == input_->msg.buffer_bytes - 1); + if (!cache_->CreateEntry(key, reinterpret_cast<disk_cache::Entry**>(&entry_))) + return Fail(); + + if (key.size() < 60) { + DEBUGMSG("Entry \"%s\" created\n", key.c_str()); + } else { + DEBUGMSG("Entry (long name) created\n", key.c_str()); + } + state_ = MASTER_GET_USE_TIMES; + Message msg; + msg.command = GET_USE_TIMES; + msg.long_arg1 = remote_entry_; + SendMsg(msg); +} + +void MasterSM::DoGetUseTimes() { + DEBUGMSG("Master DoGetUseTimes\n"); + DCHECK(state_ == MASTER_GET_USE_TIMES); + DCHECK(input_->msg.command == GET_USE_TIMES); + if (input_->msg.result != RESULT_OK) + return Fail(); + + last_used_ = base::Time::FromInternalValue(input_->msg.long_arg2); + last_modified_ = base::Time::FromInternalValue(input_->msg.long_arg3); + stream_ = 0; + SendGetDataSize(); +} + +void MasterSM::SendGetDataSize() { + DEBUGMSG("Master SendGetDataSize (%d)\n", stream_); + state_ = MASTER_GET_DATA_SIZE; + Message msg; + msg.command = GET_DATA_SIZE; + msg.arg1 = stream_; + msg.long_arg1 = remote_entry_; + SendMsg(msg); +} + +void MasterSM::DoGetDataSize() { + DEBUGMSG("Master DoGetDataSize: %d\n", input_->msg.arg2); + DCHECK(state_ == MASTER_GET_DATA_SIZE); + DCHECK(input_->msg.command == GET_DATA_SIZE); + if (input_->msg.result == RESULT_INVALID_PARAMETER) + // No more streams, move to the next entry. + return CloseEntry(); + + if (input_->msg.result != RESULT_OK) + return Fail(); + + bytes_remaining_ = input_->msg.arg2; + offset_ = 0; + SendReadData(); +} + +void MasterSM::CloseEntry() { + DEBUGMSG("Master CloseEntry\n"); + printf("%c\r", copied_entries_ % 2 ? 'x' : '+'); + entry_->SetTimes(last_used_, last_modified_); + entry_->Close(); + entry_ = NULL; + copied_entries_++; + SendGetPrevEntry(); +} + +void MasterSM::SendReadData() { + int read_size = std::min(bytes_remaining_, kBufferSize); + DEBUGMSG("Master SendReadData (%d): %d bytes at %d\n", stream_, read_size, + offset_); + if (bytes_remaining_ <= 0) { + stream_++; + if (stream_ >= kNumStreams) + return CloseEntry(); + return SendGetDataSize(); + } + + state_ = MASTER_READ_DATA; + Message msg; + msg.command = READ_DATA; + msg.arg1 = stream_; + msg.arg2 = read_size; + msg.arg3 = offset_; + msg.long_arg1 = remote_entry_; + SendMsg(msg); +} + +void MasterSM::DoReadData(int bytes_read) { + DEBUGMSG("Master DoReadData: %d bytes\n", input_->msg.buffer_bytes); + DCHECK(state_ == MASTER_READ_DATA); + DCHECK(input_->msg.command == READ_DATA); + if (input_->msg.result != RESULT_OK) + return Fail(); + + int read_size = input_->msg.buffer_bytes; + if (read_size != entry_->WriteData(stream_, offset_, input_->buffer, + read_size, NULL, false)) + return Fail(); + + offset_ += read_size; + bytes_remaining_ -= read_size; + // Read some more. + SendReadData(); +} + +void MasterSM::SendQuit() { + DEBUGMSG("Master SendQuit\n"); + state_ = MASTER_END; + Message msg; + msg.command = QUIT; + SendMsg(msg); + if (!IsPending()) + DoEnd(); +} + +void MasterSM::DoEnd() { + DEBUGMSG("Master DoEnd\n"); + MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + +void MasterSM::Fail() { + DEBUGMSG("Master Fail\n"); + printf("Unexpected failure\n"); + SendQuit(); +} + +// ----------------------------------------------------------------------- + +class SlaveSM : public BaseSM { + public: + SlaveSM(disk_cache::BackendImpl* cache, HANDLE channel) + : BaseSM(cache, channel), iterator_(NULL) {} + virtual ~SlaveSM(); + + bool DoInit(); + virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error); + + private: + enum { + SLAVE_INITIAL = 0, + SLAVE_WAITING, + SLAVE_END + }; + + void DoGetNextEntry(); + void DoGetPrevEntry(); + int32 GetEntryFromList(); + void DoCloseEntry(); + void DoGetKey(); + void DoGetUseTimes(); + void DoGetDataSize(); + void DoReadData(); + void DoEnd(); + void Fail(); + + void* iterator_; +}; + +SlaveSM::~SlaveSM() { + if (iterator_) + cache_->EndEnumeration(&iterator_); +} + +void SlaveSM::OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error) { + pending_count_--; + if (state_ == SLAVE_END) { + if (IsPending()) + return; + return DoEnd(); + } + + if (context == &out_context_) { + if (!error) + return; + return Fail(); + } + + int bytes_read = static_cast<int>(bytes_transfered); + if (bytes_read < sizeof(Message)) { + printf("Communication breakdown\n"); + return Fail(); + } + DCHECK(state_ == SLAVE_WAITING); + + switch (input_->msg.command) { + case GET_NEXT_ENTRY: + DoGetNextEntry(); + break; + case GET_PREV_ENTRY: + DoGetPrevEntry(); + break; + case CLOSE_ENTRY: + DoCloseEntry(); + break; + case GET_KEY: + DoGetKey(); + break; + case GET_USE_TIMES: + DoGetUseTimes(); + break; + case GET_DATA_SIZE: + DoGetDataSize(); + break; + case READ_DATA: + DoReadData(); + break; + case QUIT: + DoEnd(); + break; + default: + NOTREACHED(); + break; + } +} + +bool SlaveSM::DoInit() { + DEBUGMSG("\t\t\tSlave DoInit\n"); + DCHECK(state_ == SLAVE_INITIAL); + state_ = SLAVE_WAITING; + return ReceiveMsg(); +} + +void SlaveSM::DoGetNextEntry() { + DEBUGMSG("\t\t\tSlave DoGetNextEntry\n"); + Message msg; + msg.command = GET_NEXT_ENTRY; + + if (input_->msg.arg1) { + // We only support one list. + msg.result = RESULT_UNKNOWN_COMMAND; + } else { + msg.result = GetEntryFromList(); + msg.long_arg1 = reinterpret_cast<int64>(entry_); + } + SendMsg(msg); +} + +void SlaveSM::DoGetPrevEntry() { + DEBUGMSG("\t\t\tSlave DoGetPrevEntry\n"); + Message msg; + msg.command = GET_PREV_ENTRY; + + if (input_->msg.arg1) { + // We only support one list. + msg.result = RESULT_UNKNOWN_COMMAND; + } else { + msg.result = GetEntryFromList(); + msg.long_arg1 = reinterpret_cast<int64>(entry_); + } + SendMsg(msg); +} + +// Move to the next or previous entry on the list. +int32 SlaveSM::GetEntryFromList() { + DEBUGMSG("\t\t\tSlave GetEntryFromList\n"); + if (input_->msg.long_arg1 != reinterpret_cast<int64>(entry_)) + return RESULT_INVALID_PARAMETER; + + // We know that the current iteration is valid. + if (entry_) + entry_->Close(); + + bool ret; + if (input_->msg.command == GET_NEXT_ENTRY) { + ret = cache_->OpenNextEntry(&iterator_, + reinterpret_cast<disk_cache::Entry**>(&entry_)); + } else { + DCHECK(input_->msg.command == GET_PREV_ENTRY); + ret = cache_->OpenPrevEntry(&iterator_, + reinterpret_cast<disk_cache::Entry**>(&entry_)); + } + + if (!ret) + entry_ = NULL; + + if (!entry_) + DEBUGMSG("\t\t\tSlave end of list\n"); + + return RESULT_OK; +} + +void SlaveSM::DoCloseEntry() { + DEBUGMSG("\t\t\tSlave DoCloseEntry\n"); + Message msg; + msg.command = GET_KEY; + + if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_)) { + msg.result = RESULT_INVALID_PARAMETER; + } else { + entry_->Close(); + entry_ = NULL; + cache_->EndEnumeration(&iterator_); + msg.result = RESULT_OK; + } + SendMsg(msg); +} + +void SlaveSM::DoGetKey() { + DEBUGMSG("\t\t\tSlave DoGetKey\n"); + Message msg; + msg.command = GET_KEY; + + if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_)) { + msg.result = RESULT_INVALID_PARAMETER; + } else { + std::string key = entry_->GetKey(); + msg.buffer_bytes = std::min(key.size() + 1, + static_cast<size_t>(kBufferSize)); + memcpy(output_->buffer, key.c_str(), msg.buffer_bytes); + if (msg.buffer_bytes != key.size() + 1) { + // We don't support moving this entry. Just tell the master. + msg.result = RESULT_NAME_OVERFLOW; + } else { + msg.result = RESULT_OK; + } + } + SendMsg(msg); +} + +void SlaveSM::DoGetUseTimes() { + DEBUGMSG("\t\t\tSlave DoGetUseTimes\n"); + Message msg; + msg.command = GET_USE_TIMES; + + if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_)) { + msg.result = RESULT_INVALID_PARAMETER; + } else { + msg.long_arg2 = entry_->GetLastUsed().ToInternalValue(); + msg.long_arg3 = entry_->GetLastModified().ToInternalValue(); + msg.result = RESULT_OK; + } + SendMsg(msg); +} + +void SlaveSM::DoGetDataSize() { + DEBUGMSG("\t\t\tSlave DoGetDataSize\n"); + Message msg; + msg.command = GET_DATA_SIZE; + + int stream = input_->msg.arg1; + if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_) || + stream < 0 || stream >= kNumStreams) { + msg.result = RESULT_INVALID_PARAMETER; + } else { + msg.arg1 = stream; + msg.arg2 = entry_->GetDataSize(stream); + msg.result = RESULT_OK; + } + SendMsg(msg); +} + +void SlaveSM::DoReadData() { + DEBUGMSG("\t\t\tSlave DoReadData\n"); + Message msg; + msg.command = READ_DATA; + + int stream = input_->msg.arg1; + int size = input_->msg.arg2; + if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_) || + stream < 0 || stream > 1 || size > kBufferSize) { + msg.result = RESULT_INVALID_PARAMETER; + } else { + int ret = entry_->ReadData(stream, input_->msg.arg3, output_->buffer, size, + NULL); + + msg.buffer_bytes = (ret < 0) ? 0 : ret; + msg.result = RESULT_OK; + } + SendMsg(msg); +} + +void SlaveSM::DoEnd() { + DEBUGMSG("\t\t\tSlave DoEnd\n"); + MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + +void SlaveSM::Fail() { + DEBUGMSG("\t\t\tSlave Fail\n"); + printf("Unexpected failure\n"); + state_ = SLAVE_END; + if (IsPending()) { + CancelIo(channel_); + } else { + DoEnd(); + } +} + +} // namespace. + +// ----------------------------------------------------------------------- + +HANDLE CreateServer(std::wstring* pipe_number) { + std::wstring pipe_name(kPipePrefix); + srand(static_cast<int>(base::Time::Now().ToInternalValue())); + *pipe_number = IntToWString(rand()); + pipe_name.append(*pipe_number); + + DWORD mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | + FILE_FLAG_OVERLAPPED; + + return CreateNamedPipe(pipe_name.c_str(), mode, 0, 1, kChannelSize, + kChannelSize, 0, NULL); +} + +// This is the controller process for an upgrade operation. +int Upgrade(const std::wstring output_path, HANDLE pipe) { + MessageLoop loop(MessageLoop::TYPE_IO); + disk_cache::BackendImpl cache(output_path); + if (!cache.Init()) { + printf("Unable to initialize new files\n"); + return -1; + } + + MasterSM master(&cache, pipe); + if (!master.DoInit()) { + printf("Unable to talk with the helper\n"); + return -1; + } + + loop.Run(); + return 0; +} + +// This process will only execute commands from the controller. +int RunSlave(const std::wstring input_path, const std::wstring pipe_number) { + MessageLoop loop(MessageLoop::TYPE_IO); + + ScopedHandle pipe(OpenServer(pipe_number)); + if (!pipe.IsValid()) { + printf("Unable to open the server pipe\n"); + return -1; + } + + disk_cache::BackendImpl cache(input_path); + if (!cache.Init()) { + printf("Unable to open cache files\n"); + return -1; + } + cache.SetUpgradeMode(); + + SlaveSM slave(&cache, pipe); + if (!slave.DoInit()) { + printf("Unable to talk with the main process\n"); + return -1; + } + + loop.Run(); + return 0; +} |