Chromium Code Reviews| Index: webkit/fileapi/obfuscated_name_map.cc |
| =================================================================== |
| --- webkit/fileapi/obfuscated_name_map.cc (revision 0) |
| +++ webkit/fileapi/obfuscated_name_map.cc (revision 0) |
| @@ -0,0 +1,309 @@ |
| +// Copyright (c) 2011 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 "webkit/fileapi/obfuscated_name_map.h" |
| + |
| +#if defined(OS_WIN) |
| +#include <errno.h> |
| +#endif |
| + |
| +#include "base/file_util.h" |
| +#include "base/format_macros.h" |
| +#include "base/logging.h" |
| +#include "base/stringprintf.h" |
| +#include "base/sys_string_conversions.h" |
| + |
| +namespace fileapi { |
| + |
| +ObfuscatedNameMap::ObfuscatedNameMap() |
| + : directory_(), |
| + map_valid_(false), |
| + map_next_valid_(false) { |
| +} |
| + |
| +void ObfuscatedNameMap::Init(const FilePath& directory) { |
| + Rollback(); |
| + map_valid_ = false; |
| + map_.clear(); |
| + map_next_valid_ = false; |
| + map_next_.clear(); |
| + directory_ = directory; |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::ClearNameFromObfuscatedName( |
| + StringType obfuscated_name, |
| + StringType* clear_name) { |
| + CHECK(clear_name); |
| + PlatformFileError error = LoadFromFileIfNeeded(); |
| + if (base::PLATFORM_FILE_OK != error) |
| + return error; |
| + MapType::iterator found = map_.find(obfuscated_name); |
| + if (found == map_.end()) |
| + return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| + *clear_name = found->second; |
| + return base::PLATFORM_FILE_OK; |
| +} |
| + |
| +StringType ObfuscatedNameMap::ObfuscatedNameFromClearName( |
| + StringType clear_name) { |
| + // FIXME: This is a hack to get around the fact that PRIxS produces an 8-bit |
| + // string on all platforms, and the macro that would fix that gets evaluated |
| + // *before* PRIxS does, so it can't be used. |
| + FilePath temp; |
| + return base::StringPrintf( |
| + temp.AppendASCII("%08" PRIxS).BaseName().value().c_str(), |
| + hash(FilePath(clear_name))); |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::PrepareAdd( |
| + const StringType& clear_name, const StringType& obfuscated_name) { |
| + PlatformFileError error = InitMapNext(); |
| + if (base::PLATFORM_FILE_OK != error) |
| + return error; |
| +#ifndef NDEBUG |
| + StringType test_name(ObfuscatedNameFromClearName(clear_name)); |
| + CHECK(test_name == obfuscated_name); |
| +#endif |
| + if (map_next_.find(obfuscated_name) != map_next_.end()) |
| + return base::PLATFORM_FILE_ERROR_EXISTS; |
| + map_next_[obfuscated_name] = clear_name; |
| + return WriteToNextFile(); |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::PrepareDelete( |
| + const StringType& clear_name, const StringType& obfuscated_name) { |
| + PlatformFileError error = InitMapNext(); |
| + if (base::PLATFORM_FILE_OK != error) |
| + return error; |
| +#ifndef NDEBUG |
| + StringType test_name(ObfuscatedNameFromClearName(clear_name)); |
| + CHECK(test_name == obfuscated_name); |
| +#endif |
| + |
| + MapType::iterator found = map_next_.find(obfuscated_name); |
| + if (found == map_next_.end()) |
| + return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| + map_next_.erase(found); |
| + return WriteToNextFile(); |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::PrepareMove( |
| + const StringType& old_clear_name, |
| + const StringType& old_obfuscated_name, |
| + const StringType& new_clear_name, |
| + const StringType& new_obfuscated_name) { |
| + PlatformFileError error = InitMapNext(); |
| + if (base::PLATFORM_FILE_OK != error) |
| + return error; |
| +#ifndef NDEBUG |
| + StringType test_name(ObfuscatedNameFromClearName(old_clear_name)); |
| + CHECK(test_name == old_obfuscated_name); |
| + test_name = ObfuscatedNameFromClearName(new_clear_name); |
| + CHECK(test_name == new_obfuscated_name); |
| +#endif |
| + if (map_next_.find(new_obfuscated_name) != map_next_.end()) |
| + return base::PLATFORM_FILE_ERROR_EXISTS; |
| + MapType::iterator old_found = map_next_.find(old_obfuscated_name); |
| + if (old_found == map_next_.end()) |
| + return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| + map_next_.erase(old_found); |
| + map_next_[new_obfuscated_name] = new_clear_name; |
| + return WriteToNextFile(); |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::Commit() { |
| + CHECK(map_next_valid_); |
| + if (file_util::Move(GetDictionaryNextLocation(), GetDictionaryLocation())) { |
| + map_.swap(map_next_); |
| + map_valid_ = map_next_valid_; |
| + map_next_valid_ = false; |
| + map_next_.clear(); |
| + return base::PLATFORM_FILE_OK; |
| + } else { |
| + return base::PLATFORM_FILE_ERROR_FAILED; |
| + } |
| +} |
| + |
| +void ObfuscatedNameMap::Rollback() { |
| + if (map_next_valid_) { |
| + map_next_valid_ = false; |
| + map_next_.clear(); |
| + // The file may or may not exist; we delete it and ignore errors. |
| + file_util::Delete(GetDictionaryNextLocation(), false); |
| + } |
| +} |
| + |
| +size_t ObfuscatedNameMap::hash(const FilePath& f) { |
| + // TODO(ericu): This is temporary--find a real SHA1 implementation or the |
| + // like somewhere in our existing codebase. |
| +#if defined(COMPILER_GCC) |
| + struct __gnu__cxx::hash<FilePath> gcc_hash; |
|
Dai Mikurube (google.com)
2011/02/22 02:57:35
s/__gnu__cxx/__gnu_cxx/
|
| + return gcc_hash(f); |
| +#elif defined(COMPILER_MSVC) |
| + return stdext::hash_value(f); |
| +#else |
| +#error // Must define hashing function |
| +#endif |
| +} |
| + |
| +FilePath ObfuscatedNameMap::GetDictionaryLocation() { |
| + return directory_.Append(StringType(DICTIONARY_NAME)); |
| +} |
| + |
| +FilePath ObfuscatedNameMap::GetDictionaryNextLocation() { |
| + return directory_.Append(StringType(DICTIONARY_NEXT_NAME)); |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::LoadFromFileIfNeeded() { |
| + if (map_valid_) |
| + return base::PLATFORM_FILE_OK; |
| + |
| + PlatformFileError error = RecoverIfNeeded(); |
| + if (base::PLATFORM_FILE_OK != error) |
| + return error; |
| + |
| + if (!file_util::PathExists(GetDictionaryLocation())) { |
| + map_valid_ = true; // It's just empty. |
| + return base::PLATFORM_FILE_OK; |
| + } |
| + |
| + std::string contents; |
| + if (!file_util::ReadFileToString(GetDictionaryLocation(), &contents)) |
| + return base::PLATFORM_FILE_ERROR_FAILED; |
| + StringType data; |
| +#if defined(OS_POSIX) |
| + data = contents; |
| +#elif defined(OS_WIN) |
| + errno_t local_errno; |
| + size_t size = 0; |
| + |
| + local_errno = mbstowcs_s(&size, NULL, 0, contents.c_str(), 0); |
| + // FIXME: If either of these two happen, the dictionary is corrupt or |
| + // otherwise unusable. We should probably log an error and continue, but I |
| + // won't do that yet. |
| + // Max entries + entry length + a little padding. |
| + if (local_errno || size > 4000 * (256 + 120)) |
| + return base::PLATFORM_FILE_ERROR_FAILED; |
| + scoped_ptr<wchar_t> buffer(new wchar_t[size]); |
| + local_errno = |
| + mbstowcs_s(&size, buffer.get(), size, contents.c_str(), size - 1); |
| + if (local_errno) { |
| + return base::PLATFORM_FILE_ERROR_FAILED; // This should never happen. |
| + } |
| + data = buffer.get(); |
| +#else |
| +#error // Must define the appropriate conversion here. |
| +#endif |
| + LoadMapFromString(&map_, data); |
| + return base::PLATFORM_FILE_OK; |
| +} |
| + |
| +void ObfuscatedNameMap::LoadMapFromString( |
| + MapType* map, const StringType& serialized) { |
| + // Format: ([a-z0-0]+:[$legal_set_of_filename_chars]+:\n)* |
| + // Empty is legal; it's easier than deleting the dict when we remove the |
| + // last file from the dir, probably? |
| + // Note that ':' is the separator *and* the terminator [along with the |
| + // newline], since a newline could legally be in the filename. |
| + StringType hash, name; |
| + MapType temp_map; |
| + size_t i; |
| + for (i = 0; i < serialized.length(); ++i) { |
| + size_t hash_start = i; |
| + i = serialized.find(SEPARATOR, i); |
| + if (i == StringType::npos) |
| + break; |
| + size_t hash_end = i; |
| + size_t name_start = i + SEPARATOR_LENGTH; |
| + size_t name_end = serialized.find(TERMINATOR, name_start); |
| + if (name_end == StringType::npos) |
| + break; // TODO(ericu): Log an error here, but continue? |
| + i = name_end + TERMINATOR_LENGTH; |
| + hash = serialized.substr(hash_start, hash_end - hash_start); |
| + name = serialized.substr(name_start, name_end - name_start); |
| + temp_map[hash] = name; |
| + } |
| +// if (i != serialized.length()) |
| +// ; // TODO(ericu): Log an error here, but continue? |
| + map->swap(temp_map); |
| +} |
| + |
| +StringType ObfuscatedNameMap::ConvertMapToString(const MapType& map) { |
| + StringType serialized; |
| + for (MapType::const_iterator iter = map.begin(); |
| + iter != map.end(); ++iter) { |
| + serialized += iter->first + SEPARATOR + iter->second + TERMINATOR; |
| + } |
| + return serialized; |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::InitMapNext() { |
| + CHECK(!map_next_valid_); |
| + PlatformFileError error = LoadFromFileIfNeeded(); |
| + if (base::PLATFORM_FILE_OK != error) |
| + return error; |
| + map_next_ = map_; |
| + map_next_valid_ = true; |
| + return base::PLATFORM_FILE_OK; |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::RecoverIfNeeded() { |
| + // TODO(ericu): if DICT_NEXT doesn't exist, return base::PLATFORM_FILE_OK. |
| + // TODO(ericu): read map_ from DICT, map_next from DICT_NEXT. |
| + // TODO(ericu): find the *one* element that differs. |
| + // TODO(ericu): if it's not exactly one, goto handle_error |
| + // TODO(ericu): |
| + // if it's a deletion: |
| + // if the file's gone, commit the dict, else revert the dict |
| + // else if it's an addition: |
| + // if the file's there, commit the dict, else revert the dict |
| + // else // it's a move: |
| + // if the old file's gone and the new file's there, commit the dict. |
| + // if the old file's there and the new file's not, revert the dict. |
| + // else goto handle_error |
| + // return base::PLATFORM_FILE_OK; |
| + // handle_error: either return an error here, or revert the dict so that |
| + // progress can be made; it's a policy decision. |
| + // either way, make sure that map_valid_ is valid. |
| + // |
| + return base::PLATFORM_FILE_OK; |
| +} |
| + |
| +PlatformFileError ObfuscatedNameMap::WriteToNextFile() { |
| + StringType contents = ConvertMapToString(map_next_); |
| + std::string data; |
| +#if defined(OS_POSIX) |
| + data.swap(contents); |
| +#elif defined(OS_WIN) |
| + data = base::SysWideToUTF8(contents); |
| +#else |
| +#error // Must define the appropriate conversion here. |
| +#endif |
| + size_t bytes_written = 0; |
| + FilePath location = GetDictionaryNextLocation(); |
| + while (bytes_written < data.length()) { |
| + int write_response = file_util::WriteFile( |
| + location, data.c_str() + bytes_written, data.length()); |
| + if (write_response <= 0) { |
| + if (bytes_written) |
| + file_util::Delete(GetDictionaryNextLocation(), false /* recursive */); |
| + return base::PLATFORM_FILE_ERROR_FAILED; |
| + } |
| + bytes_written += write_response; |
| + } |
| + return base::PLATFORM_FILE_OK; |
| +} |
| + |
| +const StringType::value_type ObfuscatedNameMap::DICTIONARY_NAME[] = |
| + FILE_PATH_LITERAL("dict"); |
| +const StringType::value_type ObfuscatedNameMap::DICTIONARY_NEXT_NAME[] = |
| + FILE_PATH_LITERAL("dict_next"); |
| +const StringType::value_type ObfuscatedNameMap::SEPARATOR[] = |
| + FILE_PATH_LITERAL(":"); |
| +const size_t ObfuscatedNameMap::SEPARATOR_LENGTH = 1; |
| +const StringType::value_type ObfuscatedNameMap::TERMINATOR[] = |
| + FILE_PATH_LITERAL(":\n"); |
| +const size_t ObfuscatedNameMap::TERMINATOR_LENGTH = 2; |
| + |
| +} // namespace fileapi |
| Property changes on: webkit\fileapi\obfuscated_name_map.cc |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |