diff options
Diffstat (limited to 'net/tools/dump_cache/upgrade.cc')
-rw-r--r-- | net/tools/dump_cache/upgrade.cc | 791 |
1 files changed, 791 insertions, 0 deletions
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; +} |