diff options
Diffstat (limited to 'chrome/browser/sync/util')
46 files changed, 4794 insertions, 0 deletions
diff --git a/chrome/browser/sync/util/character_set_converters-linux.cc b/chrome/browser/sync/util/character_set_converters-linux.cc new file mode 100644 index 0000000..96ad1b6 --- /dev/null +++ b/chrome/browser/sync/util/character_set_converters-linux.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/character_set_converters.h" + +#include <string> + +using std::string; + +namespace browser_sync { + +// Converts input_string to UTF8 and appends the result into output_string. +void AppendPathStringToUTF8(const PathChar *wide, int size, + string* output_string) { + output_string->append(wide, size); +} + +bool AppendUTF8ToPathString(const char* utf8, size_t size, + PathString* output_string) { + output_string->append(utf8, size); + return true; +} + +void TrimPathStringToValidCharacter(PathString* string) { + // Constants from http://en.wikipedia.org/wiki/UTF-8 + CHECK(string); + if (string->empty()) + return; + if (0 == (string->at(string->length() - 1) & 0x080)) + return; + int partial_enc_bytes = 0; + for (partial_enc_bytes = 0 ; true ; ++partial_enc_bytes) { + if (4 == partial_enc_bytes || partial_enc_bytes == string->length()) { + // original string was broken, garbage in, garbage out. + return; + } + PathChar c = string->at(string->length() - 1 - partial_enc_bytes); + if ((c & 0x0c0) == 0x080) // utf continuation char; + continue; + if ((c & 0x0e0) == 0x0e0) // 2-byte encoded char. + if (1 == partial_enc_bytes) + return; + else + break; + if ((c & 0x0f0) == 0xc0) // 3-byte encoded char. + if (2 == partial_enc_bytes) + return; + else + break; + if ((c & 0x0f8) == 0x0f0) // 4-byte encoded char. + if (3 == partial_enc_bytes) + return; + else + break; + } + string->resize(string->length() - 1 - partial_enc_bytes); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/character_set_converters-win32.cc b/chrome/browser/sync/util/character_set_converters-win32.cc new file mode 100644 index 0000000..79e9281 --- /dev/null +++ b/chrome/browser/sync/util/character_set_converters-win32.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/character_set_converters.h" + +#include <windows.h> + +#include <string> + +using std::string; + +namespace browser_sync { + +// Converts input_string to UTF8 and appends the result into to output_string +void AppendPathStringToUTF8(const PathChar* wide, int size, + string* output_string) { + CHECK(output_string); + if (0 == size) + return; + + int needed_space = ::WideCharToMultiByte(CP_UTF8, 0, wide, size, 0, 0, 0, 0); + // TODO(sync): This should flag an error when we move to an api that can let + // utf-16 -> utf-8 fail. + CHECK(0 != needed_space); + string::size_type current_size = output_string->size(); + output_string->resize(current_size + needed_space); + CHECK(0 != ::WideCharToMultiByte(CP_UTF8, 0, wide, size, + &(*output_string)[current_size], needed_space, 0, 0)); +} + +bool AppendUTF8ToPathString(const char* utf8, size_t size, + PathString* output_string) { + CHECK(output_string); + if (0 == size) + return true; + // TODO(sync): Do we want to force precomposed characters here? + int needed_wide_chars = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + utf8, size, 0, 0); + if (0 == needed_wide_chars) { + DWORD err = ::GetLastError(); + if (MB_ERR_INVALID_CHARS == err) + return false; + CHECK(0 == err); + } + PathString::size_type current_length = output_string->size(); + output_string->resize(current_length + needed_wide_chars); + CHECK(0 != ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, size, + &(*output_string)[current_length], needed_wide_chars)); + return true; +} + +void TrimPathStringToValidCharacter(PathString* string) { + CHECK(string); + // Constants from http://en.wikipedia.org/wiki/UTF-16 + if (string->empty()) + return; + if (0x0dc00 == (string->at(string->length() - 1) & 0x0fc00)) + string->resize(string->length() - 1); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/character_set_converters.cc b/chrome/browser/sync/util/character_set_converters.cc new file mode 100644 index 0000000..a9114cf3 --- /dev/null +++ b/chrome/browser/sync/util/character_set_converters.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/character_set_converters.h" + +#include <string> + +using std::string; + +namespace browser_sync { + +void PathStringToUTF8(const PathChar* wide, int size, + std::string* output_string) { + CHECK(output_string); + output_string->clear(); + AppendPathStringToUTF8(wide, size, output_string); +} + +bool UTF8ToPathString(const char* utf8, size_t size, + PathString* output_string) { + CHECK(output_string); + output_string->clear(); + return AppendUTF8ToPathString(utf8, size, output_string); +}; + +ToUTF8::ToUTF8(const PathChar* wide, size_t size) { + PathStringToUTF8(wide, size, &result_); +} + +ToUTF8::ToUTF8(const PathString& wide) { + PathStringToUTF8(wide.data(), wide.length(), &result_); +} + +ToUTF8::ToUTF8(const PathChar* wide) { + PathStringToUTF8(wide, PathLen(wide), &result_); +} + +ToPathString::ToPathString(const char* utf8, size_t size) { + good_ = UTF8ToPathString(utf8, size, &result_); + good_checked_ = false; +} + +ToPathString::ToPathString(const std::string& utf8) { + good_ = UTF8ToPathString(utf8.data(), utf8.length(), &result_); + good_checked_ = false; +} + +ToPathString::ToPathString(const char* utf8) { + good_ = UTF8ToPathString(utf8, strlen(utf8), &result_); + good_checked_ = false; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/character_set_converters.h b/chrome/browser/sync/util/character_set_converters.h new file mode 100644 index 0000000..3e614c3 --- /dev/null +++ b/chrome/browser/sync/util/character_set_converters.h @@ -0,0 +1,236 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_CHARACTER_SET_CONVERTERS_H_ +#define CHROME_BROWSER_SYNC_UTIL_CHARACTER_SET_CONVERTERS_H_ + +// A pair of classes to convert UTF8 <-> UCS2 character strings. +// +// Note that the current implementation is limited to UCS2, whereas the +// interface is agnostic to the wide encoding used. +// +// Also note that UCS2 is different from UTF-16, in that UTF-16 can encode all +// the code points in the Unicode character set by multi-character encodings, +// while UCS2 is limited to encoding < 2^16 code points. +// +// It appears that Windows support UTF-16, which means we have to be careful +// what we feed this class. +// +// Usage: +// string utf8; +// CHECK(browser_sync::Append(wide_string, &utf8)); +// PathString bob; +// CHECK(browser_sync::Append(utf8, &bob)); +// PathString fred = bob; + +#ifdef OS_LINUX +#include <glib.h> +#endif + +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string16.h" +#include "chrome/browser/sync/util/sync_types.h" + +// Need to cast literals (Linux, OSX) +#define STRING16_UGLY_DOUBLE_DEFINE_HACK(s) \ + reinterpret_cast<const char16*>(L##s) +#define STRING16(s) STRING16_UGLY_DOUBLE_DEFINE_HACK(s) + +using std::string; + +namespace browser_sync { + +// These 2 classes are deprecated. Instead, prefer the Append() functions. + +// A class to convert wide -> UTF8. +class ToUTF8 { + public: + explicit ToUTF8(const PathChar* wide); + ToUTF8(const PathChar* wide, PathString::size_type size); + explicit ToUTF8(const PathString& wide); + + // cast operators + operator const std::string&() const; + operator const char*() const; + + // accessors + const std::string& get_string() const; + const char* data() const; + std::string::size_type byte_length() const; + + private: + std::string result_; +}; + +// A class to convert UTF8 -> wide. +class ToPathString { + public: + explicit ToPathString(const char*); + ToPathString(const char*, size_t size); + explicit ToPathString(const std::string&); + + // true iff UTF-8 to wide conversion succeeded in constructor. + bool good() { + good_checked_ = true; + return good_; + } + + // It's invalid to invoke the accessors or the cast operators unless the + // string is good and good() has been invoked at least once. + + // Implicit casts to const PathString& and const PathChar* + operator const PathString&() const; + operator const PathChar*() const; + + // Accessors + const PathString& get_string16() const; + const PathChar* data() const; + PathString::size_type length() const; + + private: + PathString result_; + + // Conversion succeeded. + bool good_; + // good() has been invoked at least once. + bool good_checked_; +}; + +// Converts the UCS2 string "wide" to UTF8 encoding and stores the result in +// output_string. +void PathStringToUTF8(const PathChar* wide, int size, + std::string* output_string); + +// Converts UCS2 string wide to UTF8 encoding and appends the result to +// output_string. +void AppendPathStringToUTF8(const PathChar* wide, int size, + std::string* output_string); + +// Converts the UTF8 encoded string "utf8" to UCS16 and stores the result in +// output_string. +// +// Returns true iff conversion was successful, false otherwise. +bool UTF8ToPathString(const char* utf8, size_t size, + PathString* output_string); + +// Converts the UTF8 encoded string "utf8" to UCS2 and appends the result in +// output_string. +// +// Returns true iff conversion was successful, false otherwise. +bool AppendUTF8ToPathString(const char* utf8, size_t size, + PathString* output_string); + +// Converts the UTF8 encoded string "utf8" to UCS2 and appends the result in +// output_string. +// +// @returns true iff conversion was successful, false otherwise. +inline bool AppendUTF8ToPathString(const std::string& utf8, + PathString* output_string) { + return AppendUTF8ToPathString(utf8.data(), utf8.length(), output_string); +} + +// Converts UCS2 string wide to UTF8 encoding and appends the result to +// output_string. +inline void AppendPathStringToUTF8(const PathString& wide, + std::string* output_string) { + return AppendPathStringToUTF8(wide.data(), wide.length(), output_string); +} + + +inline bool Append(const PathChar* wide, int size, + std::string* output_string) { + AppendPathStringToUTF8(wide, size, output_string); + return true; +} + +inline bool Append(const PathChar* wide, std::string* output_string) { + AppendPathStringToUTF8(wide, PathLen(wide), output_string); + return true; +} + +inline bool Append(const std::string& utf8, PathString* output_string) { + return AppendUTF8ToPathString(utf8.data(), utf8.length(), output_string); +} + +#if !PATHSTRING_IS_STD_STRING +inline bool Append(const char* utf8, size_t size, PathString* output_string) { + return AppendUTF8ToPathString(utf8, size, output_string); +} + +inline bool Append(const char* s, int size, std::string* output_string) { + output_string->append(s, size); + return true; +} + +inline bool Append(const char* utf8, PathString* output_string) { + return AppendUTF8ToPathString(utf8, strlen(utf8), output_string); +} + +inline bool Append(const char* s, std::string* output_string) { + output_string->append(s); + return true; +} + +inline bool Append(const PathString& wide, std::string* output_string) { + return Append(wide.data(), wide.length(), output_string); +} + +inline bool Append(const std::string& s, std::string* output_string) { + return Append(s.data(), s.length(), output_string); +} +#endif + +inline ToUTF8::operator const std::string&() const { + return result_; +} + +inline ToUTF8::operator const char*() const { + return result_.c_str(); +} + +inline const std::string& ToUTF8::get_string() const { + return result_; +} + +inline const char* ToUTF8::data() const { + return result_.data(); +} + +inline std::string::size_type ToUTF8::byte_length() const { + return result_.size(); +} + +inline ToPathString::operator const PathString&() const { + DCHECK(good_ && good_checked_); + return result_; +} + +inline ToPathString::operator const PathChar*() const { + DCHECK(good_ && good_checked_); + return result_.c_str(); +} + +inline const PathString& ToPathString::get_string16() const { + DCHECK(good_ && good_checked_); + return result_; +} + +inline const PathChar* ToPathString::data() const { + DCHECK(good_ && good_checked_); + return result_.data(); +} + +inline PathString::size_type ToPathString::length() const { + DCHECK(good_ && good_checked_); + return result_.length(); +} + +void TrimPathStringToValidCharacter(PathString* string); + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_UTIL_CHARACTER_SET_CONVERTERS_H_ diff --git a/chrome/browser/sync/util/character_set_converters_unittest.cc b/chrome/browser/sync/util/character_set_converters_unittest.cc new file mode 100644 index 0000000..838bbd1 --- /dev/null +++ b/chrome/browser/sync/util/character_set_converters_unittest.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/character_set_converters.h" + +#include <string> + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +using browser_sync::ToPathString; +using browser_sync::ToUTF8; +using browser_sync::AppendPathStringToUTF8; +using browser_sync::AppendUTF8ToPathString; +using browser_sync::PathStringToUTF8; +using browser_sync::UTF8ToPathString; +using std::string; + +class CharacterSetConverterTest : public testing::Test { +}; + +TEST_F(CharacterSetConverterTest, ASCIIConversionTest) { + string ascii = "Test String"; + PathString wide = PSTR("Test String"); + ToPathString to_wide(ascii); + ASSERT_TRUE(to_wide.good()); + ToUTF8 to_utf8(wide); + + // Using == as gunit doesn't handle PathString equality tests correctly (it + // tries to print the PathString and fails). + ASSERT_TRUE(PathString(wide) == to_wide.get_string16()); + ASSERT_EQ(string(ascii), to_utf8.get_string()); + ToPathString to_16(ascii); + ASSERT_TRUE(to_16.good()); + ASSERT_TRUE(PathString(wide) == to_16.get_string16()); +#ifdef OS_WINDOWS + // On Linux, PathString is already UTF8 + ASSERT_EQ(string(ascii), static_cast<string>(ToUTF8(wide))); +#endif + // The next line fails the good_checked_ test. It would be a good death test + // but they don't work on Windows. + // ASSERT_TRUE(wide == ToPathString(utf8).get_string16()); +} + +#ifdef OS_WINDOWS + // On Linux, PathString is already UTF8 +TEST_F(CharacterSetConverterTest, UnicodeConversionText) { + // Source data obtained by running od -b on files saved in utf-8 and unicode + // from a text editor. + const char* utf8 = "\357\273\277\150\145\154\154\157\040\303\250\303\251" + "\302\251\342\202\254\302\243\302\245\302\256\342\204\242"; +// #ifdef IS_LITTLE_ENDIAN + const PathChar* wide = reinterpret_cast<const PathChar*>("\377\376\150\000" + "\145\000\154\000\154\000\157\000\040\000\350\000\351\000\251\000\254\040" + "\243\000\245\000\256\000\042\041"); +// #else +// // This should work, but on Windows we don't have the endian +// // macros. Since we only do conversion between 16<->8 on Windows, +// // it's safe to assume little endian. +// const PathChar* wide = +// reinterpret_cast<PathChar*>("\376\377\000\150\000\145\000" +// "\154\000\154\000\157\000\040\000\350\000\351\000\251\040\254\000\243" +// "\000\245\000\256\041\042"); +// #endif + + ToPathString to_wide(utf8); + ASSERT_TRUE(to_wide.good()); + ToUTF8 to_utf8(wide); + + // Using == as gunit doesn't handle PathString equality tests correctly (it + // tries to print the PathString and fails). + ASSERT_TRUE(wide == to_wide.get_string16()); + ASSERT_EQ(string(utf8), to_utf8.get_string()); + ToPathString to_16(utf8); + ASSERT_TRUE(to_16.good()); + ASSERT_TRUE(wide == to_16.get_string16()); + ASSERT_EQ(string(utf8), reinterpret_cast<const string&>(ToUTF8(wide))); +} +#endif + +TEST_F(CharacterSetConverterTest, AppendUTF8Tests) { + PathString one = PSTR("one"); + PathString two = PSTR("two"); + PathString three = PSTR("three"); + string out; + AppendPathStringToUTF8(one.data(), one.length(), &out); + AppendPathStringToUTF8(two.data(), two.length(), &out); + AppendPathStringToUTF8(three.data(), three.length(), &out); + ASSERT_EQ(out, "onetwothree"); + PathString onetwothree = PSTR("onetwothree"); + PathStringToUTF8(onetwothree.data(), onetwothree.length(), &out); + ASSERT_EQ(out, "onetwothree"); +} + +TEST_F(CharacterSetConverterTest, AppendPathStringTests) { + string one = "one"; + string two = "two"; + string three = "three"; + PathString out; + AppendUTF8ToPathString(one.data(), one.length(), &out); + AppendUTF8ToPathString(two.data(), two.length(), &out); + AppendUTF8ToPathString(three.data(), three.length(), &out); + ASSERT_TRUE(out == PathString(PSTR("onetwothree"))); + string onetwothree = "onetwothree"; + UTF8ToPathString(onetwothree.data(), onetwothree.length(), &out); + ASSERT_TRUE(out == PathString(PSTR("onetwothree"))); +} + +#ifdef OS_WINDOWS +namespace { +// See http://en.wikipedia.org/wiki/UTF-16 for an explanation of UTF16. +// For a test case we use the UTF-8 and UTF-16 encoding of char 119070 +// (hex 1D11E), which is musical G clef. +const unsigned char utf8_test_string[] = { + 0xEF, 0xBB, 0xBF, // BOM + 0xE6, 0xB0, 0xB4, // water, Chinese (0x6C34) + 0x7A, // lower case z + 0xF0, 0x9D, 0x84, 0x9E, // musical G clef (0x1D11E) + 0x00, +}; +const PathChar utf16_test_string[] = { + 0xFEFF, // BOM + 0x6C34, // water, Chinese + 0x007A, // lower case z + 0xD834, 0xDD1E, // musical G clef (0x1D11E) + 0x0000, +}; +} + +TEST_F(CharacterSetConverterTest, UTF16ToUTF8Test) { + // Avoid truncation warning. + const char* utf8_test_string_pointer = + reinterpret_cast<const char*>(utf8_test_string); + ASSERT_STREQ(utf8_test_string_pointer, ToUTF8(utf16_test_string)); +} + +TEST_F(CharacterSetConverterTest, utf8_test_stringToUTF16Test) { + // Avoid truncation warning. + const char* utf8_test_string_pointer = + reinterpret_cast<const char*>(utf8_test_string); + ToPathString converted_utf8(utf8_test_string_pointer); + ASSERT_TRUE(converted_utf8.good()); + ASSERT_EQ(wcscmp(utf16_test_string, converted_utf8), 0); +} + +TEST(NameTruncation, WindowsNameTruncation) { + using browser_sync::TrimPathStringToValidCharacter; + PathChar array[] = {'1', '2', 0xD950, 0xDF21, '3', '4', 0}; + PathString message = array; + ASSERT_EQ(message.length(), arraysize(array) - 1); + int old_length = message.length(); + while (old_length != 0) { + TrimPathStringToValidCharacter(&message); + if (old_length == 4) + EXPECT_EQ(3, message.length()); + else + EXPECT_EQ(old_length, message.length()); + message.resize(message.length() - 1); + old_length = message.length(); + } + TrimPathStringToValidCharacter(&message); +} +#else + +// TODO(zork): Add unittests here once we're running these tests on linux. + +#endif diff --git a/chrome/browser/sync/util/closure.h b/chrome/browser/sync/util/closure.h new file mode 100644 index 0000000..b282bc4 --- /dev/null +++ b/chrome/browser/sync/util/closure.h @@ -0,0 +1,12 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_CLOSURE_H_ +#define CHROME_BROWSER_SYNC_UTIL_CLOSURE_H_ + +#include "base/task.h" + +typedef CallbackRunner<Tuple0> Closure; + +#endif // CHROME_BROWSER_SYNC_UTIL_CLOSURE_H_ diff --git a/chrome/browser/sync/util/compat-file-posix.cc b/chrome/browser/sync/util/compat-file-posix.cc new file mode 100644 index 0000000..66582fa --- /dev/null +++ b/chrome/browser/sync/util/compat-file-posix.cc @@ -0,0 +1,12 @@ +// Copyright (c) 2009 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. + +#if ((!defined(OS_LINUX)) && (!defined(OS_MACOSX))) +#error Compile this file on Mac OS X or Linux only. +#endif + +#include "chrome/browser/sync/util/compat-file.h" + +const char* const kPathSeparator = "/"; + diff --git a/chrome/browser/sync/util/compat-file-win.cc b/chrome/browser/sync/util/compat-file-win.cc new file mode 100644 index 0000000..d812d68 --- /dev/null +++ b/chrome/browser/sync/util/compat-file-win.cc @@ -0,0 +1,14 @@ +// Copyright (c) 2009 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. + +#ifndef OS_WINDOWS +#error Compile this file on Windows only. +#endif + +#include "chrome/browser/sync/util/compat-file.h" + +#include <windows.h> + +const wchar_t* const kPathSeparator = L"\\"; + diff --git a/chrome/browser/sync/util/compat-file.h b/chrome/browser/sync/util/compat-file.h new file mode 100644 index 0000000..273e3cb --- /dev/null +++ b/chrome/browser/sync/util/compat-file.h @@ -0,0 +1,31 @@ +// Copyright (c) 2009 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. +// +// File compatability routines. Useful to delete database files with. +// +#ifndef CHROME_BROWSER_SYNC_UTIL_COMPAT_FILE_H_ +#define CHROME_BROWSER_SYNC_UTIL_COMPAT_FILE_H_ + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include "chrome/browser/sync/util/sync_types.h" + +extern const PathChar* const kPathSeparator; + +// Path calls for all OSes. +// Returns 0 on success, non-zero on failure. +int PathRemove(const PathString& path); + +#ifdef OS_WINDOWS +inline int PathRemove(const PathString& path) { + return _wremove(path.c_str()); +} +#elif (defined(OS_MACOSX) || defined(OS_LINUX)) +inline int PathRemove(const PathString& path) { + return unlink(path.c_str()); +} +#endif + +#endif // CHROME_BROWSER_SYNC_UTIL_COMPAT_FILE_H_ diff --git a/chrome/browser/sync/util/compat-pthread.h b/chrome/browser/sync/util/compat-pthread.h new file mode 100644 index 0000000..e5817af --- /dev/null +++ b/chrome/browser/sync/util/compat-pthread.h @@ -0,0 +1,38 @@ +// Copyright (c) 2009 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. +// +// Pthread compatability routines. + +#ifndef CHROME_BROWSER_SYNC_UTIL_COMPAT_PTHREAD_H_ +#define CHROME_BROWSER_SYNC_UTIL_COMPAT_PTHREAD_H_ + +// TODO(timsteele): This file is deprecated. Use PlatformThread. +#include "base/platform_thread.h" + +#define ThreadId PlatformThreadId + +#ifndef OS_WINDOWS +inline ThreadId GetCurrentThreadId() { + return PlatformThread::CurrentId(); +} +#endif // OS_WINDOWS + +#if (!defined(OS_WINDOWS) && !defined(OS_MACOSX)) +// TODO(timsteele): What the heck is this? +inline int sem_post_multiple(sem_t * sem, int number) { + int i; + int r = 0; + for (i = 0; i < number; i++) { + r = sem_post(sem); + if (r != 0) { + LOG_IF(ERROR, i > 0) << "sem_post() failed on iteration #" << i << + " of " << number; + return r; + } + } + return 0; +} +#endif // (!defined(OS_WINDOWS) && !defined(OS_MACOSX)) + +#endif // CHROME_BROWSER_SYNC_UTIL_COMPAT_PTHREAD_H_ diff --git a/chrome/browser/sync/util/crypto_helpers.cc b/chrome/browser/sync/util/crypto_helpers.cc new file mode 100644 index 0000000..c84bfaf --- /dev/null +++ b/chrome/browser/sync/util/crypto_helpers.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/crypto_helpers.h" + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/string_util.h" + +using std::string; +using std::vector; + +MD5Calculator::MD5Calculator() { + MD5Init(&context_); +} + +void MD5Calculator::AddData(const unsigned char* data, int length) { + CHECK(bin_digest_.empty()); + MD5Update(&context_, data, length); +} + +void MD5Calculator::CalcDigest() { + if (bin_digest_.empty()) { + MD5Digest digest; + MD5Final(&digest, &context_); + bin_digest_.assign(digest.a, digest.a + arraysize(digest.a)); + } +} + +vector<uint8> MD5Calculator::GetDigest() { + CalcDigest(); + return bin_digest_; +} + +PathString MD5Calculator::GetHexDigest() { + CalcDigest(); + string hex = HexEncode(reinterpret_cast<char*>(&bin_digest_.front()), + bin_digest_.size()); + StringToLowerASCII(&hex); + return PathString(hex.begin(), hex.end()); +} + +void GetRandomBytes(char* output, int output_length) { + for (int i = 0; i < output_length; i++) { + // TODO(chron): replace this with something less stupid. + output[i] = static_cast<char>(base::RandUint64()); + } +} + +string Generate128BitRandomHexString() { + int64 chunk1 = static_cast<int64>(base::RandUint64()); + int64 chunk2 = static_cast<int64>(base::RandUint64()); + + return StringPrintf("%016" PRId64 "x%016" PRId64 "x", + chunk1, chunk2); +} diff --git a/chrome/browser/sync/util/crypto_helpers.h b/chrome/browser/sync/util/crypto_helpers.h new file mode 100644 index 0000000..c31a278 --- /dev/null +++ b/chrome/browser/sync/util/crypto_helpers.h @@ -0,0 +1,40 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_CRYPTO_HELPERS_H_ +#define CHROME_BROWSER_SYNC_UTIL_CRYPTO_HELPERS_H_ + +#include <string> +#include <vector> + +// An object to handle calculation of MD5 sums. +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/md5.h" +#include "base/port.h" +#include "chrome/browser/sync/util/sync_types.h" + +class MD5Calculator { + protected: + MD5Context context_; + std::vector<uint8> bin_digest_; + + void CalcDigest(); + public: + MD5Calculator(); + ~MD5Calculator() {} + void AddData(const uint8* data, int length); + void AddData(const char* data, int length) { + AddData(reinterpret_cast<const uint8*>(data), length); + } + PathString GetHexDigest(); + std::vector<uint8> GetDigest(); + private: + DISALLOW_COPY_AND_ASSIGN(MD5Calculator); +}; + +void GetRandomBytes(char* output, int output_length); +std::string Generate128BitRandomHexString(); + +#endif // CHROME_BROWSER_SYNC_UTIL_CRYPTO_HELPERS_H_ diff --git a/chrome/browser/sync/util/crypto_helpers_unittest.cc b/chrome/browser/sync/util/crypto_helpers_unittest.cc new file mode 100644 index 0000000..1d2dd60 --- /dev/null +++ b/chrome/browser/sync/util/crypto_helpers_unittest.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/crypto_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(ChecksumTest, MD5ChecksumTest) { + uint8 buffer[256]; + for (unsigned int i = 0; i < arraysize(buffer); ++i) { + buffer[i] = i; + } + MD5Calculator md5; + md5.AddData(buffer, arraysize(buffer)); + PathString checksum(PSTR("e2c865db4162bed963bfaa9ef6ac18f0")); + ASSERT_EQ(checksum, md5.GetHexDigest()); +} diff --git a/chrome/browser/sync/util/data_encryption.cc b/chrome/browser/sync/util/data_encryption.cc new file mode 100644 index 0000000..b835147 --- /dev/null +++ b/chrome/browser/sync/util/data_encryption.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2009 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. +// +// NOTE: this file is Winodws specific. + +#include "chrome/browser/sync/util/data_encryption.h" + +#include <windows.h> +#include <wincrypt.h> + +#include <cstddef> +#include <string> +#include <vector> + +using std::string; +using std::vector; + +vector<uint8> EncryptData(const string& data) { + DATA_BLOB unencrypted_data, encrypted_data; + unencrypted_data.pbData = (BYTE*)(data.data()); + unencrypted_data.cbData = data.size(); + + if (!CryptProtectData(&unencrypted_data, L"", NULL, NULL, NULL, 0, + &encrypted_data)) + LOG(ERROR) << "Encryption fails: " << data; + + vector<uint8> result(encrypted_data.pbData, + encrypted_data.pbData + encrypted_data.cbData); + LocalFree(encrypted_data.pbData); + return result; +} + +bool DecryptData(const vector<uint8>& in_data, string* out_data) { + DATA_BLOB encrypted_data, decrypted_data; + encrypted_data.pbData = + (in_data.empty() ? NULL : const_cast<BYTE*>(&in_data[0])); + encrypted_data.cbData = in_data.size(); + LPWSTR descrip = L""; + + if (!CryptUnprotectData(&encrypted_data, &descrip, NULL, NULL, NULL, 0, + &decrypted_data)) { + LOG(ERROR) << "Decryption fails: "; + return false; + } else { + out_data->assign(reinterpret_cast<const char*>(decrypted_data.pbData), + decrypted_data.cbData); + LocalFree(decrypted_data.pbData); + return true; + } +} diff --git a/chrome/browser/sync/util/data_encryption.h b/chrome/browser/sync/util/data_encryption.h new file mode 100644 index 0000000..b62a14a --- /dev/null +++ b/chrome/browser/sync/util/data_encryption.h @@ -0,0 +1,21 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_DATA_ENCRYPTION_H_ +#define CHROME_BROWSER_SYNC_UTIL_DATA_ENCRYPTION_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chrome/browser/sync/util/sync_types.h" + +using std::string; +using std::vector; + +vector<uint8> EncryptData(const string& data); +bool DecryptData(const vector<uint8>& in_data, string* out_data); + +#endif // CHROME_BROWSER_SYNC_UTIL_DATA_ENCRYPTION_H_ diff --git a/chrome/browser/sync/util/data_encryption_unittest.cc b/chrome/browser/sync/util/data_encryption_unittest.cc new file mode 100644 index 0000000..63bfda5 --- /dev/null +++ b/chrome/browser/sync/util/data_encryption_unittest.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/data_encryption.h" + +#include <string> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; +using std::vector; + +namespace { + +TEST(DataEncryption, TestEncryptDecryptOfSampleString) { + vector<uint8> example(EncryptData("example")); + ASSERT_FALSE(example.empty()); + string result; + ASSERT_TRUE(DecryptData(example, &result)); + ASSERT_TRUE(result == "example"); +} + +TEST(DataEncryption, TestDecryptFailure) { + vector<uint8> example(0, 0); + string result; + ASSERT_FALSE(DecryptData(example, &result)); +} + +} // namespace diff --git a/chrome/browser/sync/util/dbgq.h b/chrome/browser/sync/util/dbgq.h new file mode 100644 index 0000000..65ebb5c --- /dev/null +++ b/chrome/browser/sync/util/dbgq.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_DBGQ_H_ +#define CHROME_BROWSER_SYNC_UTIL_DBGQ_H_ + +#include "base/basictypes.h" // for COMPILE_ASSERT + +// A circular queue that is designed to be easily inspectable in a debugger. It +// puts the elements into the array in reverse, so you can just look at the i_ +// pointer for a recent history. +template <typename T, size_t size> +class DebugQueue { + COMPILE_ASSERT(size > 0, DebugQueue_size_must_be_greater_than_zero); + public: + DebugQueue() : i_(array_) { } + void Push(const T& t) { + i_ = (array_ == i_ ? array_ + size - 1 : i_ - 1); + *i_ = t; + } + protected: + T* i_; // Points to the newest element in the queue. + T array_[size]; +}; + +#endif // CHROME_BROWSER_SYNC_UTIL_DBGQ_H_ diff --git a/chrome/browser/sync/util/event_sys-inl.h b/chrome/browser/sync/util/event_sys-inl.h new file mode 100644 index 0000000..c361528 --- /dev/null +++ b/chrome/browser/sync/util/event_sys-inl.h @@ -0,0 +1,340 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_EVENT_SYS_INL_H_ +#define CHROME_BROWSER_SYNC_UTIL_EVENT_SYS_INL_H_ + +#include <map> + +#include "base/atomicops.h" +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/port.h" +#include "chrome/browser/sync/util/compat-pthread.h" +#include "chrome/browser/sync/util/event_sys.h" +#include "chrome/browser/sync/util/pthread_helpers.h" +#include "chrome/browser/sync/util/sync_types.h" + +// How to use Channels: + +// 0. Assume Bob is the name of the class from which you want to broadcast +// events. +// 1. Choose an EventType. This could be an Enum or something more complicated. +// 2. Create an EventTraits class for your EventType. It must have +// two members: +// +// typedef x EventType; +// static bool IsChannelShutdownEvent(const EventType& event); +// +// 3. Add an EventChannel<MyEventTraits>* instance and event_channel() const; +// accessor to Bob. +// Delete the channel ordinarily in Bob's destructor, or whenever you want. +// 4. To broadcast events, call bob->event_channel()->NotifyListeners(event). +// 5. Only call NotifyListeners from a single thread at a time. + +// How to use Listeners/Hookups: + +// 0. Assume you want a class called Lisa to listen to events from Bob. +// 1. Create an event handler method in Lisa. Its single argument should be of +// your event type. +// 2. Add a EventListenerHookup* hookup_ member to Lisa. +// 3. Initialize the hookup by calling: +// +// hookup_ = NewEventListenerHookup(bob->event_channel(), +// this, +// &Lisa::HandleEvent); +// +// 4. Delete hookup_ in Lisa's destructor, or anytime sooner to stop receiving +// events. + +// An Event Channel is a source, or broadcaster of events. Listeners subscribe +// by calling the AddListener() method. The owner of the channel calls the +// NotifyListeners() method. +// +// Don't inherit from this class. Just make an event_channel member and an +// event_channel() accessor. + +// No reason why CallbackWaiters has to be templatized. +class CallbackWaiters { + public: + CallbackWaiters() : waiter_count_(0), callback_done_(false) { + } + ~CallbackWaiters() { + DCHECK_EQ(0, waiter_count_); + } + void WaitForCallbackToComplete(PThreadMutex* listeners_mutex) { + { + PThreadScopedLock<PThreadMutex> lock(&mutex_); + waiter_count_ += 1; + listeners_mutex->Unlock(); + while (!callback_done_) + pthread_cond_wait(&condvar_.condvar_, &mutex_.mutex_); + waiter_count_ -= 1; + if (0 != waiter_count_) + return; + } + delete this; + } + + void Signal() { + PThreadScopedLock<PThreadMutex> lock(&mutex_); + callback_done_ = true; + pthread_cond_broadcast(&condvar_.condvar_); + } + + protected: + int waiter_count_; + bool callback_done_; + PThreadMutex mutex_; + PThreadCondVar condvar_; +}; + +template <typename EventTraitsType, typename NotifyLock, + typename ScopedNotifyLocker> +class EventChannel { + public: + typedef EventTraitsType EventTraits; + typedef typename EventTraits::EventType EventType; + typedef EventListener<EventType> Listener; + + protected: + typedef std::map<Listener*, bool> Listeners; + typedef PThreadScopedLock<PThreadMutex> ScopedListenersLock; + + public: + // The shutdown event gets send in the EventChannel's destructor. + explicit EventChannel(const EventType& shutdown_event) + : callback_waiters_(NULL), shutdown_event_(shutdown_event), + current_listener_callback_(NULL) { + } + + ~EventChannel() { + // Tell all the listeners that the channel is being deleted. + NotifyListeners(shutdown_event_); + + // Make sure all the listeners have been disconnected. Otherwise, they + // will try to call RemoveListener() at a later date. +#ifdef DEBUG + ScopedListenersLock lock(&listeners_mutex_); + for (typename Listeners::iterator i = listeners_.begin(); + i != listeners_.end(); ++i) { + DCHECK(i->second) << "Listener not disconnected"; + } +#endif + } + + // Never call this twice for the same listener. + // + // Thread safe. + void AddListener(Listener* listener) { + ScopedListenersLock lock(&listeners_mutex_); + typename Listeners::iterator found = listeners_.find(listener); + if (found == listeners_.end()) { + listeners_.insert(std::make_pair(listener, + false)); // Not dead yet. + } else { + DCHECK(found->second) << "Attempted to add the same listener twice."; + found->second = false; // Not dead yet. + } + } + + // If listener's callback is currently executing, this method waits until the + // callback completes before returning. + // + // Thread safe. + void RemoveListener(Listener* listener) { + bool wait = false; + listeners_mutex_.Lock(); + typename Listeners::iterator found = listeners_.find(listener); + if (found != listeners_.end()) { + found->second = true; // Mark as dead. + wait = (found->first == current_listener_callback_ && + (!pthread_equal(current_listener_callback_thread_id_, + pthread_self()))); + } + if (!wait) { + listeners_mutex_.Unlock(); + return; + } + if (NULL == callback_waiters_) + callback_waiters_ = new CallbackWaiters; + callback_waiters_->WaitForCallbackToComplete(&listeners_mutex_); + } + + // Blocks until all listeners have been notified. + // + // NOT thread safe. Must only be called by one thread at a time. + void NotifyListeners(const EventType& event) { + ScopedNotifyLocker lock_notify(¬ify_lock_); + listeners_mutex_.Lock(); + DCHECK(NULL == current_listener_callback_); + current_listener_callback_thread_id_ = pthread_self(); + typename Listeners::iterator i = listeners_.begin(); + while (i != listeners_.end()) { + if (i->second) { // Clean out dead listeners + listeners_.erase(i++); + continue; + } + current_listener_callback_ = i->first; + listeners_mutex_.Unlock(); + + i->first->HandleEvent(event); + + listeners_mutex_.Lock(); + current_listener_callback_ = NULL; + if (NULL != callback_waiters_) { + callback_waiters_->Signal(); + callback_waiters_ = NULL; + } + + ++i; + } + listeners_mutex_.Unlock(); + } + + // A map iterator remains valid until the element it points to gets removed + // from the map, so a map is perfect for our needs. + // + // Map value is a bool, true means the Listener is dead. + Listeners listeners_; + // NULL means no callback is currently being called. + Listener* current_listener_callback_; + // Only valid when current_listener is not NULL. + // The thread on which the callback is executing. + pthread_t current_listener_callback_thread_id_; + // Win32 Event that is usually NULL. Only created when another thread calls + // Remove while in callback. Owned and closed by the thread calling Remove(). + CallbackWaiters* callback_waiters_; + + PThreadMutex listeners_mutex_; // Protects all members above. + const EventType shutdown_event_; + NotifyLock notify_lock_; + + DISALLOW_COPY_AND_ASSIGN(EventChannel); +}; + +// An EventListenerHookup hooks up a method in your class to an EventChannel. +// Deleting the hookup disconnects from the EventChannel. +// +// Contains complexity of inheriting from Listener class and managing lifetimes. +// +// Create using NewEventListenerHookup() to avoid explicit template arguments. +class EventListenerHookup { + public: + virtual ~EventListenerHookup() { } +}; + +template <typename EventChannel, typename EventTraits, + class Derived> +class EventListenerHookupImpl : public EventListenerHookup, +public EventListener<typename EventTraits::EventType> { + public: + explicit EventListenerHookupImpl(EventChannel* channel) + : channel_(channel), deleted_(NULL) { + channel->AddListener(this); + connected_ = true; + } + + ~EventListenerHookupImpl() { + if (NULL != deleted_) + *deleted_ = true; + if (connected_) + channel_->RemoveListener(this); + } + + typedef typename EventTraits::EventType EventType; + virtual void HandleEvent(const EventType& event) { + DCHECK(connected_); + bool deleted = false; + deleted_ = &deleted; + static_cast<Derived*>(this)->Callback(event); + if (deleted) // The callback (legally) deleted this. + return; // The only safe thing to do. + deleted_ = NULL; + if (EventTraits::IsChannelShutdownEvent(event)) { + channel_->RemoveListener(this); + connected_ = false; + } + } + + protected: + EventChannel* const channel_; + bool connected_; + bool* deleted_; // Allows the handler to delete the hookup. +}; + +// SimpleHookup just passes the event to the callback message. +template <typename EventChannel, typename EventTraits, + typename CallbackObject, typename CallbackMethod> +class SimpleHookup + : public EventListenerHookupImpl<EventChannel, EventTraits, + SimpleHookup<EventChannel, + EventTraits, + CallbackObject, + CallbackMethod> > { + public: + SimpleHookup(EventChannel* channel, CallbackObject* cbobject, + CallbackMethod cbmethod) + : EventListenerHookupImpl<EventChannel, EventTraits, + SimpleHookup>(channel), cbobject_(cbobject), + cbmethod_(cbmethod) { } + + typedef typename EventTraits::EventType EventType; + void Callback(const EventType& event) { + (cbobject_->*cbmethod_)(event); + } + CallbackObject* const cbobject_; + CallbackMethod const cbmethod_; +}; + +// ArgHookup also passes an additional arg to the callback method. +template <typename EventChannel, typename EventTraits, + typename CallbackObject, typename CallbackMethod, + typename CallbackArg0> +class ArgHookup : + public EventListenerHookupImpl<EventChannel, EventTraits, + ArgHookup<EventChannel, EventTraits, + CallbackObject, + CallbackMethod, + CallbackArg0> > { + public: + ArgHookup(EventChannel* channel, CallbackObject* cbobject, + CallbackMethod cbmethod, CallbackArg0 arg0) + : EventListenerHookupImpl<EventChannel, EventTraits, + ArgHookup>(channel), cbobject_(cbobject), + cbmethod_(cbmethod), arg0_(arg0) { } + + typedef typename EventTraits::EventType EventType; + void Callback(const EventType& event) { + (cbobject_->*cbmethod_)(arg0_, event); + } + CallbackObject* const cbobject_; + CallbackMethod const cbmethod_; + CallbackArg0 const arg0_; +}; + + +template <typename EventChannel, typename CallbackObject, + typename CallbackMethod> +EventListenerHookup* NewEventListenerHookup(EventChannel* channel, + CallbackObject* cbobject, + CallbackMethod cbmethod) { + return new SimpleHookup<EventChannel, + typename EventChannel::EventTraits, + CallbackObject, CallbackMethod>(channel, cbobject, cbmethod); +} + +template <typename EventChannel, typename CallbackObject, + typename CallbackMethod, typename CallbackArg0> +EventListenerHookup* NewEventListenerHookup(EventChannel* channel, + CallbackObject* cbobject, + CallbackMethod cbmethod, + CallbackArg0 arg0) { + return new ArgHookup<EventChannel, + typename EventChannel::EventTraits, + CallbackObject, CallbackMethod, CallbackArg0>(channel, cbobject, + cbmethod, arg0); +} + +#endif // CHROME_BROWSER_SYNC_UTIL_EVENT_SYS_INL_H_ diff --git a/chrome/browser/sync/util/event_sys.h b/chrome/browser/sync/util/event_sys.h new file mode 100644 index 0000000..5dcf44a --- /dev/null +++ b/chrome/browser/sync/util/event_sys.h @@ -0,0 +1,41 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_EVENT_SYS_H_ +#define CHROME_BROWSER_SYNC_UTIL_EVENT_SYS_H_ + +#include "chrome/browser/sync/util/pthread_helpers_fwd.h" + +// An abstract base class for listening to events. +// +// Don't inherit from this class yourself. Using NewEventListenerHookup() is +// much easier. +template <typename EventType> +class EventListener { + public: + virtual void HandleEvent(const EventType& event) = 0; +}; + +// See the -inl.h for details about the following. + +template <typename EventTraits, typename NotifyLock = PThreadNoLock, + typename ScopedNotifyLocker = PThreadScopedLock<NotifyLock> > +class EventChannel; + +class EventListenerHookup; + +template <typename EventChannel, typename CallbackObject, + typename CallbackMethod> +EventListenerHookup* NewEventListenerHookup(EventChannel* channel, + CallbackObject* cbobject, + CallbackMethod cbmethod); + +template <typename EventChannel, typename CallbackObject, + typename CallbackMethod, typename CallbackArg0> +EventListenerHookup* NewEventListenerHookup(EventChannel* channel, + CallbackObject* cbobject, + CallbackMethod cbmethod, + CallbackArg0 arg0); + +#endif // CHROME_BROWSER_SYNC_UTIL_EVENT_SYS_H_ diff --git a/chrome/browser/sync/util/event_sys_unittest.cc b/chrome/browser/sync/util/event_sys_unittest.cc new file mode 100644 index 0000000..5e521b1 --- /dev/null +++ b/chrome/browser/sync/util/event_sys_unittest.cc @@ -0,0 +1,271 @@ +// Copyright (c) 2009 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 <iosfwd> +#include <sstream> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/port.h" +#include "chrome/browser/sync/util/event_sys-inl.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::endl; +using std::ostream; +using std::string; +using std::stringstream; +using std::vector; + +namespace { + +class Pair; + +struct TestEvent { + Pair* source; + enum { + A_CHANGED, B_CHANGED, PAIR_BEING_DELETED, + } what_happened; + int old_value; +}; + +struct TestEventTraits { + typedef TestEvent EventType; + static bool IsChannelShutdownEvent(const TestEvent& event) { + return TestEvent::PAIR_BEING_DELETED == event.what_happened; + } +}; + +class Pair { + public: + typedef EventChannel<TestEventTraits> Channel; + explicit Pair(const string& name) : name_(name), a_(0), b_(0) { + TestEvent shutdown = { this, TestEvent::PAIR_BEING_DELETED, 0 }; + event_channel_ = new Channel(shutdown); + } + ~Pair() { + delete event_channel_; + } + void set_a(int n) { + TestEvent event = { this, TestEvent::A_CHANGED, a_ }; + a_ = n; + event_channel_->NotifyListeners(event); + } + void set_b(int n) { + TestEvent event = { this, TestEvent::B_CHANGED, b_ }; + b_ = n; + event_channel_->NotifyListeners(event); + } + int a() const { return a_; } + int b() const { return b_; } + const string& name() { return name_; } + Channel* event_channel() const { return event_channel_; } + + protected: + const string name_; + int a_; + int b_; + Channel* event_channel_; +}; + +class EventLogger { + public: + explicit EventLogger(ostream& out) : out_(out) { } + ~EventLogger() { + for (Hookups::iterator i = hookups_.begin(); i != hookups_.end(); ++i) + delete *i; + } + + void Hookup(const string name, Pair::Channel* channel) { + hookups_.push_back(NewEventListenerHookup(channel, this, + &EventLogger::HandlePairEvent, + name)); + } + + void HandlePairEvent(const string& name, const TestEvent& event) { + const char* what_changed; + int new_value; + Hookups::iterator dead; + switch (event.what_happened) { + case TestEvent::A_CHANGED: + what_changed = "A"; + new_value = event.source->a(); + break; + case TestEvent::B_CHANGED: + what_changed = "B"; + new_value = event.source->b(); + break; + case TestEvent::PAIR_BEING_DELETED: + out_ << name << " heard " << event.source->name() << " being deleted." + << endl; + return; + default: + LOG(FATAL) << "Bad event.what_happened: " << event.what_happened; + break; + } + out_ << name << " heard " << event.source->name() << "'s " << what_changed + << " change from " + << event.old_value << " to " << new_value << endl; + } + + typedef vector<EventListenerHookup*> Hookups; + Hookups hookups_; + ostream& out_; +}; + +const char golden_result[] = "Larry heard Sally's B change from 0 to 2\n" +"Larry heard Sally's A change from 1 to 3\n" +"Lewis heard Sam's B change from 0 to 5\n" +"Larry heard Sally's A change from 3 to 6\n" +"Larry heard Sally being deleted.\n"; + +TEST(EventSys, Basic) { + Pair sally("Sally"), sam("Sam"); + sally.set_a(1); + stringstream log; + EventLogger logger(log); + logger.Hookup("Larry", sally.event_channel()); + sally.set_b(2); + sally.set_a(3); + sam.set_a(4); + logger.Hookup("Lewis", sam.event_channel()); + sam.set_b(5); + sally.set_a(6); + // Test that disconnect within callback doesn't deadlock. + TestEvent event = {&sally, TestEvent::PAIR_BEING_DELETED, 0 }; + sally.event_channel()->NotifyListeners(event); + sally.set_a(7); + ASSERT_EQ(log.str(), golden_result); +} + + +// This goes pretty far beyond the normal use pattern, so don't use +// ThreadTester as an example of what to do. +class ThreadTester : public EventListener<TestEvent> { + public: + explicit ThreadTester(Pair* pair) + : pair_(pair), remove_event_bool_(false) { + pair_->event_channel()->AddListener(this); + } + ~ThreadTester() { + pair_->event_channel()->RemoveListener(this); + for (int i = 0; i < threads_.size(); i++) { + CHECK(pthread_join(threads_[i].thread, NULL) == 0); + delete threads_[i].completed; + } + } + + struct ThreadInfo { + pthread_t thread; + bool *completed; + }; + + struct ThreadArgs { + ThreadTester* self; + pthread_cond_t *thread_running_cond; + pthread_mutex_t *thread_running_mutex; + bool *thread_running; + bool *completed; + }; + + pthread_t Go() { + PThreadCondVar thread_running_cond; + PThreadMutex thread_running_mutex; + ThreadArgs args; + ThreadInfo info; + info.completed = new bool(false); + args.self = this; + args.completed = info.completed; + args.thread_running_cond = &(thread_running_cond.condvar_); + args.thread_running_mutex = &(thread_running_mutex.mutex_); + args.thread_running = new bool(false); + CHECK(0 == + pthread_create(&info.thread, NULL, ThreadTester::ThreadMain, &args)); + thread_running_mutex.Lock(); + while ((*args.thread_running) == false) { + pthread_cond_wait(&(thread_running_cond.condvar_), + &(thread_running_mutex.mutex_)); + } + thread_running_mutex.Unlock(); + delete args.thread_running; + threads_.push_back(info); + return info.thread; + } + + static void* ThreadMain(void* arg) { + ThreadArgs args = *reinterpret_cast<ThreadArgs*>(arg); + pthread_mutex_lock(args.thread_running_mutex); + *args.thread_running = true; + pthread_cond_signal(args.thread_running_cond); + pthread_mutex_unlock(args.thread_running_mutex); + + args.self->remove_event_mutex_.Lock(); + while (args.self->remove_event_bool_ == false) { + pthread_cond_wait(&args.self->remove_event_.condvar_, + &args.self->remove_event_mutex_.mutex_); + } + args.self->remove_event_mutex_.Unlock(); + + // Normally, you'd just delete the hookup. This is very bad style, but + // necessary for the test. + args.self->pair_->event_channel()->RemoveListener(args.self); + *args.completed = true; + return 0; + } + + void HandleEvent(const TestEvent& event) { + remove_event_mutex_.Lock(); + remove_event_bool_ = true; + pthread_cond_broadcast(&remove_event_.condvar_); + remove_event_mutex_.Unlock(); + + // Windows and posix use different functions to sleep. +#ifdef OS_WINDOWS + Sleep(1); +#else + sleep(1); +#endif + + for (int i = 0; i < threads_.size(); i++) { + if (*(threads_[i].completed)) + LOG(FATAL) << "A test thread exited too early."; + } + } + + Pair* pair_; + PThreadCondVar remove_event_; + PThreadMutex remove_event_mutex_; + bool remove_event_bool_; + vector<ThreadInfo> threads_; +}; + +TEST(EventSys, Multithreaded) { + Pair sally("Sally"); + ThreadTester a(&sally); + for (int i = 0; i < 3; ++i) + a.Go(); + sally.set_b(99); +} + +class HookupDeleter { + public: + void HandleEvent(const TestEvent& event) { + delete hookup_; + hookup_ = NULL; + } + EventListenerHookup* hookup_; +}; + +TEST(EventSys, InHandlerDeletion) { + Pair sally("Sally"); + HookupDeleter deleter; + deleter.hookup_ = NewEventListenerHookup(sally.event_channel(), + &deleter, + &HookupDeleter::HandleEvent); + sally.set_a(1); + ASSERT_TRUE(NULL == deleter.hookup_); +} + +} // namespace diff --git a/chrome/browser/sync/util/fast_dump.h b/chrome/browser/sync/util/fast_dump.h new file mode 100644 index 0000000..9266f0e --- /dev/null +++ b/chrome/browser/sync/util/fast_dump.h @@ -0,0 +1,60 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_FAST_DUMP_H_ +#define CHROME_BROWSER_SYNC_UTIL_FAST_DUMP_H_ + +#include <ostream> +#include <streambuf> +#include <string> + +#include "base/string_util.h" + +using std::ostream; +using std::streambuf; +using std::string; + +// This seems totally gratuitous, and it would be if std::ostream was fast, but +// std::ostream is slow. It's slow because it creates a ostream::sentry (mutex) +// for every little << operator. When you want to dump a whole lot of stuff +// under a single mutex lock, use this FastDump class. +namespace browser_sync { +class FastDump { + public: + explicit FastDump(ostream* outs) : sentry_(*outs), out_(outs->rdbuf()) { + } + ostream::sentry sentry_; + streambuf* const out_; +}; +} // namespace browser_sync + +inline browser_sync::FastDump& operator << + (browser_sync::FastDump& dump, int64 n) { + string numbuf(Int64ToString(n)); + const char* number = numbuf.c_str(); + dump.out_->sputn(number, numbuf.length()); + return dump; +} + +inline browser_sync::FastDump& operator << + (browser_sync::FastDump& dump, int32 n) { + string numbuf(IntToString(n)); + const char* number = numbuf.c_str(); + dump.out_->sputn(number, numbuf.length()); + return dump; +} + +inline browser_sync::FastDump& operator << + (browser_sync::FastDump& dump, const char* s) { + dump.out_->sputn(s, strlen(s)); + return dump; +} + +inline browser_sync::FastDump& operator << + (browser_sync::FastDump& dump, const string& s) { + dump.out_->sputn(s.data(), s.size()); + return dump; +} + +#endif // CHROME_BROWSER_SYNC_UTIL_FAST_DUMP_H_ diff --git a/chrome/browser/sync/util/highres_timer-linux.cc b/chrome/browser/sync/util/highres_timer-linux.cc new file mode 100644 index 0000000..8fed343 --- /dev/null +++ b/chrome/browser/sync/util/highres_timer-linux.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2009 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. + +// High resolution timer functions for use in Linux. + +#include "chrome/browser/sync/util/highres_timer.h" + +const uint64 MICROS_IN_MILLI = 1000L; +const uint64 MICROS_IN_HALF_MILLI = 500L; +const uint64 MICROS_IN_HALF_SECOND = 500000L; + +uint64 HighresTimer::GetElapsedMs() const { + uint64 end_time = GetCurrentTicks(); + + // Scale to ms and round to nearest ms - rounding is important because + // otherwise the truncation error may accumulate e.g. in sums. + return (uint64(end_time - start_ticks_) + MICROS_IN_HALF_MILLI) / + MICROS_IN_MILLI; +} + +uint64 HighresTimer::GetElapsedSec() const { + uint64 end_time = GetCurrentTicks(); + + // Scale to ms and round to nearest ms - rounding is important because + // otherwise the truncation error may accumulate e.g. in sums. + return (uint64(end_time - start_ticks_) + MICROS_IN_HALF_SECOND) / + MICROS_IN_SECOND; +} diff --git a/chrome/browser/sync/util/highres_timer-linux.h b/chrome/browser/sync/util/highres_timer-linux.h new file mode 100644 index 0000000..01a022d --- /dev/null +++ b/chrome/browser/sync/util/highres_timer-linux.h @@ -0,0 +1,79 @@ +// Copyright (c) 2009 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. +// +// High resolution timer functions for use in Linux. + +#ifndef CHROME_BROWSER_SYNC_UTIL_HIGHRES_TIMER_LINUX_H_ +#define CHROME_BROWSER_SYNC_UTIL_HIGHRES_TIMER_LINUX_H_ + +#include "base/basictypes.h" + +#include <sys/time.h> + +const uint64 MICROS_IN_SECOND = 1000000L; + +// A handy class for reliably measuring wall-clock time with decent resolution. +// +// We want to measure time with high resolution on Linux. What to do? +// +// RDTSC? Sure, but how do you convert it to wall clock time? +// clock_gettime? It's not in all Linuxes. +// +// Let's just use gettimeofday; it's good to the microsecond. +class HighresTimer { + public: + // Captures the current start time. + HighresTimer(); + + // Captures the current tick, can be used to reset a timer for reuse. + void Start(); + + // Returns the elapsed ticks with full resolution. + uint64 GetElapsedTicks() const; + + // Returns the elapsed time in milliseconds, rounded to the nearest + // millisecond. + uint64 GetElapsedMs() const; + + // Returns the elapsed time in seconds, rounded to the nearest second. + uint64 GetElapsedSec() const; + + uint64 start_ticks() const { return start_ticks_; } + + // Returns timer frequency from cache, should be less overhead than + // ::QueryPerformanceFrequency. + static uint64 GetTimerFrequency(); + // Returns current ticks. + static uint64 GetCurrentTicks(); + + private: + // Captured start time. + uint64 start_ticks_; +}; + +inline HighresTimer::HighresTimer() { + Start(); +} + +inline void HighresTimer::Start() { + start_ticks_ = GetCurrentTicks(); +} + +inline uint64 HighresTimer::GetTimerFrequency() { + // Fixed; one "tick" is one microsecond. + return MICROS_IN_SECOND; +} + +inline uint64 HighresTimer::GetCurrentTicks() { + timeval tv; + gettimeofday(&tv, 0); + + return tv.tv_sec * MICROS_IN_SECOND + tv.tv_usec; +} + +inline uint64 HighresTimer::GetElapsedTicks() const { + return start_ticks_ - GetCurrentTicks(); +} + +#endif // CHROME_BROWSER_SYNC_UTIL_HIGHRES_TIMER_LINUX_H_ diff --git a/chrome/browser/sync/util/highres_timer-win32.cc b/chrome/browser/sync/util/highres_timer-win32.cc new file mode 100644 index 0000000..0d7323a --- /dev/null +++ b/chrome/browser/sync/util/highres_timer-win32.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2009 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. +// +// High resolution timer functions for use in Windows. + +#include "chrome/browser/sync/util/highres_timer.h" + +bool HighresTimer::perf_freq_collected_ = false; +ULONGLONG HighresTimer::perf_freq_ = 0; + +ULONGLONG HighresTimer::GetElapsedMs() const { + ULONGLONG end_time = GetCurrentTicks(); + + // Scale to ms and round to nearest ms - rounding is important because + // otherwise the truncation error may accumulate e.g. in sums. + // + // Given infinite resolution, this expression could be written as: + // trunc((end - start (units:freq*sec))/freq (units:sec) * + // 1000 (unit:ms) + 1/2 (unit:ms)) + ULONGLONG freq = GetTimerFrequency(); + return ((end_time - start_ticks_) * 1000L + freq / 2) / freq; +} + +ULONGLONG HighresTimer::GetElapsedSec() const { + ULONGLONG end_time = GetCurrentTicks(); + + // Round to nearest ms - rounding is important because otherwise the + // truncation error may accumulate e.g. in sums. + // + // Given infinite resolution, this expression could be written as: + // trunc((end - start (units:freq*sec))/freq (unit:sec) + 1/2 (unit:sec)) + ULONGLONG freq = GetTimerFrequency(); + return ((end_time - start_ticks_) + freq / 2) / freq; +} + +void HighresTimer::CollectPerfFreq() { + LARGE_INTEGER freq; + + // Note that this is racy. It's OK, however, because even concurrent + // executions of this are idempotent. + if (::QueryPerformanceFrequency(&freq)) { + perf_freq_ = freq.QuadPart; + perf_freq_collected_ = true; + } +} diff --git a/chrome/browser/sync/util/highres_timer-win32.h b/chrome/browser/sync/util/highres_timer-win32.h new file mode 100644 index 0000000..6e87ce9 --- /dev/null +++ b/chrome/browser/sync/util/highres_timer-win32.h @@ -0,0 +1,78 @@ +// Copyright (c) 2009 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. +// +// High resolution timer functions for use in Windows. + +#ifndef CHROME_BROWSER_SYNC_UTIL_HIGHRES_TIMER_WIN32_H_ +#define CHROME_BROWSER_SYNC_UTIL_HIGHRES_TIMER_WIN32_H_ + +#include <windows.h> + +// A handy class for reliably measuring wall-clock time with decent resolution, +// even on multi-processor machines and on laptops (where RDTSC potentially +// returns different results on different processors and/or the RDTSC timer +// clocks at different rates depending on the power state of the CPU, +// respectively). +class HighresTimer { + public: + // Captures the current start time. + HighresTimer(); + + // Captures the current tick, can be used to reset a timer for reuse. + void Start(); + + // Returns the elapsed ticks with full resolution. + ULONGLONG GetElapsedTicks() const; + + // Returns the elapsed time in milliseconds, rounded to the nearest + // millisecond. + ULONGLONG GetElapsedMs() const; + + // Returns the elapsed time in seconds, rounded to the nearest second. + ULONGLONG GetElapsedSec() const; + + ULONGLONG start_ticks() const { return start_ticks_; } + + // Returns timer frequency from cache, should be less + // overhead than ::QueryPerformanceFrequency. + static ULONGLONG GetTimerFrequency(); + // Returns current ticks. + static ULONGLONG GetCurrentTicks(); + + private: + static void CollectPerfFreq(); + + // Captured start time. + ULONGLONG start_ticks_; + + // Captured performance counter frequency. + static bool perf_freq_collected_; + static ULONGLONG perf_freq_; +}; + +inline HighresTimer::HighresTimer() { + Start(); +} + +inline void HighresTimer::Start() { + start_ticks_ = GetCurrentTicks(); +} + +inline ULONGLONG HighresTimer::GetTimerFrequency() { + if (!perf_freq_collected_) + CollectPerfFreq(); + return perf_freq_; +} + +inline ULONGLONG HighresTimer::GetCurrentTicks() { + LARGE_INTEGER ticks; + ::QueryPerformanceCounter(&ticks); + return ticks.QuadPart; +} + +inline ULONGLONG HighresTimer::GetElapsedTicks() const { + return start_ticks_ - GetCurrentTicks(); +} + +#endif // CHROME_BROWSER_SYNC_UTIL_HIGHRES_TIMER_WIN32_H_ diff --git a/chrome/browser/sync/util/highres_timer.h b/chrome/browser/sync/util/highres_timer.h new file mode 100644 index 0000000..e2bde4e --- /dev/null +++ b/chrome/browser/sync/util/highres_timer.h @@ -0,0 +1,13 @@ +// Copyright (c) 2009 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. +// +// High resolution timer functions defined for each OS. + +#if defined(OS_WINDOWS) +#include "chrome/browser/sync/util/highres_timer-win32.h" +#elif defined(OS_MACOSX) +#error "Mac timer functions are missing." +#else +#include "chrome/browser/sync/util/highres_timer-linux.h" +#endif diff --git a/chrome/browser/sync/util/highres_timer_unittest.cc b/chrome/browser/sync/util/highres_timer_unittest.cc new file mode 100644 index 0000000..5723e7f8 --- /dev/null +++ b/chrome/browser/sync/util/highres_timer_unittest.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2009 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. +// +// High resolution timer unit tests. + +#include "base/basictypes.h" +#include "chrome/browser/sync/util/highres_timer.h" +#include "testing/gtest/include/gtest/gtest.h" + +// These unittests have proven to be flaky on buildbot. While we don't want +// them breaking the build, we still build them to guard against bitrot. +// On dev machines during local builds we can enable them. +TEST(HighresTimer, DISABLED_MillisecondClock) { + HighresTimer timer; + + // note: this could fail if we context switch between initializing the timer + // and here. Very unlikely however. + EXPECT_EQ(0, timer.GetElapsedMs()); + timer.Start(); + uint64 half_ms = HighresTimer::GetTimerFrequency() / 2000; + // busy wait for half a millisecond + while (timer.start_ticks() + half_ms > HighresTimer::GetCurrentTicks()) { + // Nothing + } + EXPECT_EQ(1, timer.GetElapsedMs()); +} + +TEST(HighresTimer, DISABLED_SecondClock) { + HighresTimer timer; + + EXPECT_EQ(0, timer.GetElapsedSec()); +#ifdef OS_WINDOWS + ::Sleep(250); +#else + struct timespec ts1 = {0, 250000000}; + nanosleep(&ts1, 0); +#endif + EXPECT_EQ(0, timer.GetElapsedSec()); + EXPECT_LE(230, timer.GetElapsedMs()); + EXPECT_GE(270, timer.GetElapsedMs()); +#ifdef OS_WINDOWS + ::Sleep(251); +#else + struct timespec ts2 = {0, 251000000}; + nanosleep(&ts2, 0); +#endif + EXPECT_EQ(1, timer.GetElapsedSec()); +} diff --git a/chrome/browser/sync/util/path_helpers-linux.cc b/chrome/browser/sync/util/path_helpers-linux.cc new file mode 100644 index 0000000..4f4543d --- /dev/null +++ b/chrome/browser/sync/util/path_helpers-linux.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2009 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 <sys/types.h> + +#include <glib.h> +#include <string.h> + +#include "base/logging.h" +#include "base/port.h" +#include "chrome/browser/sync/util/character_set_converters.h" +#include "chrome/browser/sync/util/path_helpers.h" + +#ifndef OS_LINUX +#error Compile this file on Linux only. +#endif + +string LastPathSegment(const string& path) { + string str(path); + string::size_type final_slash = str.find_last_of('/'); + if (string::npos != final_slash && final_slash == str.length() - 1 + && str.length() > 1) { + str.erase(final_slash); + final_slash = str.find_last_of('/'); + } + if (string::npos == final_slash) + return str; + str.erase(0, final_slash+1); + return str; +} + +PathString GetFullPath(const PathString& path) { + // TODO(sync): Not sure what the base of the relative path should be on + // linux. + return path; +} + +PathString AppendSlash(const PathString& path) { + if ((!path.empty()) && (*path.rbegin() != '/')) { + return path + '/'; + } + return path; +} + +PathString LowercasePath(const PathString& path) { + gchar *ret = g_utf8_strdown(path.c_str(), -1); + PathString retstr(ret); + g_free(ret); + return retstr; +} diff --git a/chrome/browser/sync/util/path_helpers-posix.cc b/chrome/browser/sync/util/path_helpers-posix.cc new file mode 100644 index 0000000..02726db --- /dev/null +++ b/chrome/browser/sync/util/path_helpers-posix.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2009 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 <pwd.h> + +#include <string.h> + +#include "base/port.h" +#include "chrome/browser/sync/util/path_helpers.h" +#include "strings/strutil.h" + +#if ((!defined(OS_LINUX)) && (!defined(OS_MACOSX))) +#error Compile this file on Mac OS X or Linux only. +#endif + +PathString ExpandTilde(const PathString& path) { + if (path.empty()) + return path; + if ('~' != path[0]) + return path; + PathString ret; + // TODO(sync): Consider using getpwuid_r. + ret.insert(0, getpwuid(getuid())->pw_dir); + ret.append(++path.begin(), path.end()); + return ret; +} + +namespace { +// TODO(sync): We really should use char[]. +string cache_dir_; +} + +void set_cache_dir(string cache_dir) { + CHECK(cache_dir_.empty()); + cache_dir_ = cache_dir; +} + +string get_cache_dir() { + CHECK(!cache_dir_.empty()); + return cache_dir_; +} + +// On Posix, PathStrings are UTF-8, not UTF-16 as they are on Windows. +// Thus, this function is different from the Windows version. +PathString TruncatePathString(const PathString& original, int length) { + if (original.size() <= length) + return original; + if (length <= 0) + return original; + PathString ret(original.begin(), original.begin() + length); + COMPILE_ASSERT(sizeof(PathChar) == sizeof(uint8), PathStringNotUTF8); + PathString::reverse_iterator last_char = ret.rbegin(); + + // Values taken from + // http://en.wikipedia.org/w/index.php?title=UTF-8&oldid=252875566 + if (0 == (*last_char & 0x80)) + return ret; + + for (; last_char != ret.rend(); ++last_char) { + if (0 == (*last_char & 0x80)) { + // got malformed UTF-8; bail + return ret; + } + if (0 == (*last_char & 0x40)) { + // got another trailing byte + continue; + } + break; + } + + if (ret.rend() == last_char) { + // We hit the beginning of the string. bail. + return ret; + } + + int last_codepoint_len = last_char - ret.rbegin() + 1; + + if (((0xC0 == (*last_char & 0xE0)) && (2 == last_codepoint_len)) || + ((0xE0 == (*last_char & 0xF0)) && (3 == last_codepoint_len)) || + ((0xF0 == (*last_char & 0xF8)) && (4 == last_codepoint_len))) { + // Valid utf-8. + return ret; + } + + // Invalid utf-8. chop off last "codepoint" and return. + ret.resize(ret.size() - last_codepoint_len); + return ret; +} + +// Convert /s to :s . +PathString MakePathComponentOSLegal(const PathString& component) { + if (PathString::npos == component.find("/")) + return PSTR(""); + PathString new_name; + new_name.reserve(component.size()); + StringReplace(component, "/", ":", true, &new_name); + return new_name; +} diff --git a/chrome/browser/sync/util/path_helpers.cc b/chrome/browser/sync/util/path_helpers.cc new file mode 100644 index 0000000..1cf8d4e --- /dev/null +++ b/chrome/browser/sync/util/path_helpers.cc @@ -0,0 +1,153 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/path_helpers.h" + +#include <Shlwapi.h> +#include <stdlib.h> + +#include "base/logging.h" +#include "base/port.h" +#include "chrome/browser/sync/syncable/syncable.h" + +#ifndef OS_WINDOWS +#error Compile this file on Windows only. +#endif + +using std::string; + +#if OS_WINDOWS +const char PATH_SEPARATOR = '\\'; +#else +const char PATH_SEPARATOR = '/'; +#endif // OS_WINDOWS + + +static PathString RemoveTrailingSlashes16(PathString str) { + while ((str.length() > 0) && (str.at(str.length() - 1) == kPathSeparator[0])) + str.resize(str.length() - 1); + return str; +} + +static string RemoveTrailingSlashes(string str) { + while ((str.length() > 0) && (str.at(str.length() - 1) == PATH_SEPARATOR)) + str.resize(str.length() - 1); + return str; +} + +PathString LastPathSegment(const PathString& path) { + return RemoveTrailingSlashes16(PathFindFileNameW(path.c_str())); +} + +string LastPathSegment(const string& path) { + return RemoveTrailingSlashes(PathFindFileNameA(path.c_str())); +} + +PathString GetFullPath(const PathString& path) { + PathChar buffer[MAX_PATH]; + PathChar* file_part; + DWORD const size = GetFullPathName(path.c_str(), ARRAYSIZE(buffer), buffer, + &file_part); + return PathString(buffer, size); +} + +PathString AppendSlash(const PathString& path) { + PathString result(path); + if (!result.empty()) { + if (*result.rbegin() == '/') + *result.rbegin() = '\\'; + else if (*result.rbegin() != '\\') + result.push_back('\\'); + } + return result; +} + +PathString ExpandTilde(const PathString& path) { + // Do nothing on windows. + return path; +} + +// Returns a string with length or fewer elements, careful to +// not truncate a string mid-surrogate pair. +PathString TruncatePathString(const PathString& original, int length) { + if (original.size() <= length) + return original; + if (length <= 0) + return original; + PathString ret(original.begin(), original.begin() + length); + COMPILE_ASSERT(sizeof(PathChar) == sizeof(uint16), PathStringNotUTF16); + PathChar last_char = *ret.rbegin(); + + // Values taken from + // http://en.wikipedia.org/w/index.php?title=UTF-16/UCS-2&oldid=248072840 + if (last_char >= 0xD800 && last_char <= 0xDBFF) + ret.resize(ret.size() - 1); + return ret; +} + +namespace { +const PathString kWindowsIllegalBaseFilenames[] = { + L"CON", L"PRN", L"AUX", L"NUL", L"COM1", L"COM2", + L"COM3", L"COM4", L"COM5", L"COM6", L"COM7", + L"COM8", L"COM9", L"LPT1", L"LPT2", L"LPT3", + L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", + L"LPT9" }; +} + +// See: http://msdn.microsoft.com/library/default.asp?url=/library/ +// en-us/fileio/fs/naming_a_file.asp +// note that * and ? are not listed on the page as illegal characters, +// but they are. +PathString MakePathComponentOSLegal(const PathString& component) { + CHECK(!component.empty()); + PathString mutable_component = component; + + // Remove illegal characters. + for (PathString::iterator i = mutable_component.begin(); + i != mutable_component.end();) { + if ((PathString::npos != PathString(L"<>:\"/\\|*?").find(*i)) || + ((static_cast<unsigned short>(*i) >= 0) && + (static_cast<unsigned short>(*i) <= 31))) { + mutable_component.erase(i); + } else { + ++i; + } + } + + // Remove trailing spaces or periods. + while (mutable_component.size() && + ((mutable_component.at(mutable_component.size() - 1) == L' ') || + (mutable_component.at(mutable_component.size() - 1) == L'.'))) + mutable_component.resize(mutable_component.size() - 1, L' '); + + // Remove a bunch of forbidden names. windows only seems to mind if + // a forbidden name matches our name exactly (e.g. "prn") or if the + // name is the forbidden name, followed by a dot, followed by anything + // (e.g., "prn.anything.foo.bar") + + // From this point out, we break mutable_component into two strings, and + // use them this way: we save anything after and including the first dot + // (usually the extension) and only mess with stuff before the first dot. + PathString::size_type first_dot = mutable_component.find_first_of(L'.'); + if (PathString::npos == first_dot) + first_dot = mutable_component.size(); + PathString sub = mutable_component.substr(0, first_dot); + PathString postsub = mutable_component.substr(first_dot); + CHECK(sub + postsub == mutable_component); + for (int i = 0; i < ARRAYSIZE(kWindowsIllegalBaseFilenames); i++) { + // ComparePathNames(a, b) == 0 -> same + if (syncable::ComparePathNames(kWindowsIllegalBaseFilenames[i], sub) == 0) { + sub.append(L"~1"); + break; + } + } + if ((L"" == sub) && (L"" == postsub)) { + sub = L"~1"; + } + + // Return the new name, only if it differs from the original. + if ((sub + postsub) == component) + return L""; + return (sub + postsub); +} diff --git a/chrome/browser/sync/util/path_helpers.h b/chrome/browser/sync/util/path_helpers.h new file mode 100644 index 0000000..d8b4663 --- /dev/null +++ b/chrome/browser/sync/util/path_helpers.h @@ -0,0 +1,105 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_PATH_HELPERS_H_ +#define CHROME_BROWSER_SYNC_UTIL_PATH_HELPERS_H_ + +#include <algorithm> +#include <iterator> +#include <string> + +#include "chrome/browser/sync/util/compat-file.h" +#include "chrome/browser/sync/util/sync_types.h" + +template <typename StringType> +class PathSegmentIterator : public std::iterator<std::forward_iterator_tag, + StringType> { + public: + explicit PathSegmentIterator(const StringType& path) : + path_(path), segment_begin_(0), segment_end_(0) { + ++(*this); + } + + PathSegmentIterator() : segment_begin_(0), segment_end_(0) { } + + // Default copy constructors, constructors, etc. will all do the right thing. + PathSegmentIterator& operator ++() { + segment_begin_ = + std::min(path_.size(), + path_.find_first_not_of(kPathSeparator, segment_end_)); + segment_end_ = + std::min(path_.size(), + path_.find_first_of(kPathSeparator, segment_begin_)); + value_.assign(path_, segment_begin_, segment_end_ - segment_begin_); + return *this; + } + + PathSegmentIterator operator ++(int) { + PathSegmentIterator i(*this); + return ++i; + } + + const StringType& operator * () const { + return value_; + } + const StringType* operator -> () const { + return &value_; + } + + // If the current value and remaining path are equal, then we + // call the iterators equal. + bool operator == (const PathSegmentIterator& i) const { + return 0 == path_.compare(segment_begin_, + path_.size() - segment_begin_, + i.path_, i.segment_begin_, i.path_.size() - i.segment_begin_); + } + + bool operator != (const PathSegmentIterator& i) const { + return !(*this == i); + } + + protected: + StringType path_; + typename StringType::size_type segment_begin_; + typename StringType::size_type segment_end_; + StringType value_; +}; + +// NOTE: The functions (Strip)LastPathSegment always return values without a +// trailing slash. +PathString LastPathSegment(const PathString& path); +std::string LastPathSegment(const std::string& path); +PathString AppendSlash(const PathString& path); +PathString GetFullPath(const PathString& path); +PathString LowercasePath(const PathString& path); +PathString ExpandTilde(const PathString& path); + +inline bool HasSuffixPathString(const PathString& str, + const PathString& suffix) { + return str.find(suffix, str.size() - suffix.size()) != PathString::npos; +} + +inline PathString StripSuffixPathString(const PathString& str, + const PathString& suffix) { + PathString ret(str); + if (HasSuffixPathString(str, suffix)) { + ret.resize(str.size() - suffix.size()); + } + return ret; +} + +// Returns a string with length or fewer elements, careful to +// not truncate a string mid-surrogate pair. +PathString TruncatePathString(const PathString& original, int length); + +// Makes a path component legal for your OS, but doesn't handle collisions +// with other files in the same directory. it can do this by removing +// illegal characters and adding ~1 before the first '.' in the filename. +// returns PSTR("") if the name is fine as-is +// on mac/linux we let names stay unicode normalization form C in the system +// and convert to another normal form in fuse handlers. but, if a '/' is in +// a filename, we handle it here. +PathString MakePathComponentOSLegal(const PathString& component); + +#endif // CHROME_BROWSER_SYNC_UTIL_PATH_HELPERS_H_ diff --git a/chrome/browser/sync/util/path_helpers_unittest.cc b/chrome/browser/sync/util/path_helpers_unittest.cc new file mode 100644 index 0000000..75a81a2 --- /dev/null +++ b/chrome/browser/sync/util/path_helpers_unittest.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2009 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/port.h" +#include "chrome/browser/sync/syncable/path_name_cmp.h" +#include "chrome/browser/sync/util/path_helpers.h" +#include "chrome/browser/sync/util/sync_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncable { + +class PathHelpersTest : public testing::Test { +}; + +TEST(PathHelpersTest, TruncatePathStringTest) { + // Simple case. + PathString str = PSTR("12345"); + EXPECT_EQ(PSTR("123"), TruncatePathString(str, 3)); + EXPECT_EQ(str, TruncatePathString(str, str.size())); + + // abcg is "abc" + musical g clef U+1D11E +#if PATHSTRING_IS_STD_STRING + // UTF-8 + PathChar abcg[] = {'a', 'b', 'c', 0xF0, 0x9D, 0x84, 0x9E, '\0'}; +#else // PATHSTRING_IS_STD_STRING + // UTF-16 + PathChar abcg[] = {'a', 'b', 'c', 0xD834, 0xDD1E, '\0'}; +#endif // PATHSTRING_IS_STD_STRING + + EXPECT_EQ(PSTR("abc"), TruncatePathString(abcg, 4)); + + // Further utf-8 tests. +#if PATHSTRING_IS_STD_STRING + // UTF-8 + + EXPECT_EQ(PSTR("abc"), TruncatePathString(abcg, 4)); + EXPECT_EQ(PSTR("abc"), TruncatePathString(abcg, 5)); + EXPECT_EQ(PSTR("abc"), TruncatePathString(abcg, 6)); + EXPECT_EQ(PathString(abcg), TruncatePathString(abcg, 7)); + + PathChar abc2[] = {'a', 'b', 'c', 0xC3, 0xB1, '\0'}; // abc(n w/ tilde) + EXPECT_EQ(PSTR("abc"), TruncatePathString(abc2, 3)); + EXPECT_EQ(PSTR("abc"), TruncatePathString(abc2, 4)); + EXPECT_EQ(PathString(abc2), TruncatePathString(abc2, 5)); + + PathChar abc3[] = {'a', 'b', 'c', 0xE2, 0x82, 0xAC, '\0'}; // abc(euro) + EXPECT_EQ(PSTR("abc"), TruncatePathString(abc3, 3)); + EXPECT_EQ(PSTR("abc"), TruncatePathString(abc3, 4)); + EXPECT_EQ(PSTR("abc"), TruncatePathString(abc3, 5)); + EXPECT_EQ(PathString(abc3), TruncatePathString(abc3, 6)); +#endif +} + +TEST(PathHelpersTest, PathStrutil) { + PathString big = PSTR("abcdef"); + PathString suffix = PSTR("def"); + PathString other = PSTR("x"); + EXPECT_TRUE(HasSuffixPathString(big, suffix)); + EXPECT_FALSE(HasSuffixPathString(suffix, big)); + EXPECT_FALSE(HasSuffixPathString(big, other)); + EXPECT_EQ(PSTR("abc"), StripSuffixPathString(big, suffix)); +} + +TEST(PathHelpersTest, SanitizePathComponent) { +#ifdef OS_WINDOWS + EXPECT_EQ(MakePathComponentOSLegal(L"bar"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"bar <"), L"bar"); + EXPECT_EQ(MakePathComponentOSLegal(L"bar.<"), L"bar"); + EXPECT_EQ(MakePathComponentOSLegal(L"prn"), L"prn~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"pr>n"), L"prn~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"ab:c"), L"abc"); + EXPECT_EQ(MakePathComponentOSLegal(L"a|bc"), L"abc"); + EXPECT_EQ(MakePathComponentOSLegal(L"baz~9"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"\007"), L"~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"com1.txt.bat"), L"com1~1.txt.bat"); + EXPECT_EQ(MakePathComponentOSLegal(L"foo.com1.bat"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"\010gg"), L"gg"); + EXPECT_EQ(MakePathComponentOSLegal(L"<"), L"~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"col:on"), L"colon"); + EXPECT_EQ(MakePathComponentOSLegal(L"q\""), L"q"); + EXPECT_EQ(MakePathComponentOSLegal(L"back\\slAsh"), L"backslAsh"); + EXPECT_EQ(MakePathComponentOSLegal(L"sla/sh "), L"slash"); + EXPECT_EQ(MakePathComponentOSLegal(L"s|laSh"), L"slaSh"); + EXPECT_EQ(MakePathComponentOSLegal(L"CON"), L"CON~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"PRN"), L"PRN~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"AUX"), L"AUX~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"NUL"), L"NUL~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM1"), L"COM1~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM2"), L"COM2~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM3"), L"COM3~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM4"), L"COM4~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM5"), L"COM5~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM6"), L"COM6~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM7"), L"COM7~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM8"), L"COM8~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"COM9"), L"COM9~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT1"), L"LPT1~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT2"), L"LPT2~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT3"), L"LPT3~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT4"), L"LPT4~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT5"), L"LPT5~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT6"), L"LPT6~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT7"), L"LPT7~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT8"), L"LPT8~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"LPT9"), L"LPT9~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"bar~bar"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"adlr~-3"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"tilde~"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"mytext.txt"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"mytext|.txt"), L"mytext.txt"); + EXPECT_EQ(MakePathComponentOSLegal(L"okay.com1.txt"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"software-3.tar.gz"), L""); + EXPECT_EQ(MakePathComponentOSLegal(L"<"), L"~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"<.<"), L"~1"); + EXPECT_EQ(MakePathComponentOSLegal(L"<.<txt"), L".txt"); + EXPECT_EQ(MakePathComponentOSLegal(L"txt<.<"), L"txt"); +#else // OS_WINDOWS + + EXPECT_EQ(MakePathComponentOSLegal("bar"), ""); + EXPECT_EQ(MakePathComponentOSLegal("b"), ""); + EXPECT_EQ(MakePathComponentOSLegal("A"), ""); + EXPECT_EQ(MakePathComponentOSLegal("<'|"), ""); + EXPECT_EQ(MakePathComponentOSLegal("/"), ":"); + EXPECT_EQ(MakePathComponentOSLegal(":"), ""); + +#endif // OS_WINDOWS +} + +} // namespace syncable diff --git a/chrome/browser/sync/util/pthread_helpers.cc b/chrome/browser/sync/util/pthread_helpers.cc new file mode 100644 index 0000000..4dadc55 --- /dev/null +++ b/chrome/browser/sync/util/pthread_helpers.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/pthread_helpers.h" + +#if (defined(OS_LINUX) || defined(OS_MACOSX)) +#include <sys/time.h> +#endif // (defined(OS_LINUX) || defined(OS_MACOSX)) + +#include "base/atomicops.h" +#include "base/logging.h" +#include "base/port.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/sync/protocol/service_constants.h" + +#ifdef OS_WINDOWS + +namespace { + +// Ensure that we don't bug the user more than once about the process being +// terminated. +base::subtle::AtomicWord g_process_terminating = 0; + +struct ThreadStartParams { + void *(*start) (void* payload); + void* param; +}; + +void* ThreadMainProc(void* parameter) { + ThreadStartParams* tsp = reinterpret_cast<ThreadStartParams*>(parameter); + void *(*start) (void *) = tsp->start; + void* param = tsp->param; + + delete tsp; + + void* result = NULL; + __try { + result = start(param); + } __except(EXCEPTION_CONTINUE_SEARCH) { + // Make sure only one thread complains and exits the process. Other + // faulting threads simply return. + if (0 == base::subtle::NoBarrier_CompareAndSwap( + &g_process_terminating, 0, 1)) { + // Service notification means we don't have a recursive event loop inside + // this call, and so won't suffer recursive exceptions. + ::MessageBox(NULL, + PRODUCT_NAME_STRING + L" has suffered a non-recoverable\n" + L"exception, and must exit immediately", + L"Nonrecoverable Exception", + MB_OK | MB_APPLMODAL | MB_SERVICE_NOTIFICATION); + + ::ExitProcess(GetExceptionCode()); + } + } + + return result; +} + +} // namespace + +#endif + +thread_handle CreatePThread(void *(*start) (void *), void* parameter) { +#ifdef OS_WINDOWS + scoped_ptr<ThreadStartParams> param(new ThreadStartParams); + if (NULL == param.get()) + return NULL; + + param->start = start; + param->param = parameter; + + pthread_t pthread; + if (0 != pthread_create(&pthread, NULL, ThreadMainProc, param.get())) + return NULL; + + // ownership has passed to the new thread + param.release(); + + const HANDLE thread_handle = pthread_getw32threadhandle_np(pthread); + HANDLE thread_copy; + // Have to duplicate the thread handle, because when we call + // pthread_detach(), the handle will get closed as soon as the thread exits. + // We want to keep the handle indefinitely. + CHECK(DuplicateHandle(GetCurrentProcess(), thread_handle, + GetCurrentProcess(), &thread_copy, 0, FALSE, + DUPLICATE_SAME_ACCESS)) << + "DuplicateHandle() failed with error " << GetLastError(); + pthread_detach(pthread); + return thread_copy; +#else + pthread_t handle; + + int result = pthread_create(&handle, NULL, start, parameter); + if (result == 0) { + return handle; + } else { + return 0; + } +#endif +} + +struct timespec GetPThreadAbsoluteTime(uint32 ms) { +#ifdef OS_WINDOWS + FILETIME filenow; + GetSystemTimeAsFileTime(&filenow); + ULARGE_INTEGER n; + n.LowPart = filenow.dwLowDateTime; + n.HighPart = filenow.dwHighDateTime; + // Filetime unit is 100-nanosecond intervals + const int64 ms_ftime = 10000; + n.QuadPart += ms_ftime * ms; + + // The number of 100 nanosecond intervals from Jan 1, 1601 'til Jan 1, 1970. + const int64 kOffset = GG_LONGLONG(116444736000000000); + timespec result; + result.tv_sec = (n.QuadPart - kOffset) / 10000000; + result.tv_nsec = (n.QuadPart - kOffset - + (result.tv_sec * GG_LONGLONG(10000000))) * 100; + return result; +#else + struct timeval now; + struct timezone zone; + gettimeofday(&now, &zone); + struct timespec deadline = { now.tv_sec }; + // microseconds to nanoseconds. + // and add the ms delay. + ms += now.tv_usec / 1000; + deadline.tv_sec += ms / 1000; + deadline.tv_nsec = (ms % 1000) * 1000000; + return deadline; +#endif +} + +void NameCurrentThreadForDebugging(char* name) { +#if defined(OS_WINDOWS) + // This implementation is taken from Chromium's platform_thread framework. + // The information on how to set the thread name comes from a MSDN article: + // http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx + const DWORD kVCThreadNameException = 0x406D1388; + typedef struct tagTHREADNAME_INFO { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + } THREADNAME_INFO; + + // The debugger needs to be around to catch the name in the exception. If + // there isn't a debugger, we are just needlessly throwing an exception. + if (!::IsDebuggerPresent()) + return; + + THREADNAME_INFO info = { 0x1000, name, GetCurrentThreadId(), 0 }; + + __try { + RaiseException(kVCThreadNameException, 0, sizeof(info)/sizeof(DWORD), + reinterpret_cast<DWORD_PTR*>(&info)); + } __except(EXCEPTION_CONTINUE_EXECUTION) { + } +#endif // defined(OS_WINDOWS) +} diff --git a/chrome/browser/sync/util/pthread_helpers.h b/chrome/browser/sync/util/pthread_helpers.h new file mode 100644 index 0000000..26defe0 --- /dev/null +++ b/chrome/browser/sync/util/pthread_helpers.h @@ -0,0 +1,259 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_PTHREAD_HELPERS_H_ +#define CHROME_BROWSER_SYNC_UTIL_PTHREAD_HELPERS_H_ + +#include <pthread.h> +#include "base/logging.h" + +#ifdef OS_WINDOWS +typedef void* thread_handle; +#else +typedef pthread_t thread_handle; +#endif + +// Creates a pthread, detaches from it, and returns a Win32 HANDLE for it that +// the caller must CloseHandle(). +thread_handle CreatePThread(void* (*start)(void* payload), void* parameter); + +class PThreadRWLock { + public: + inline PThreadRWLock() { + int result = pthread_rwlock_init(&rwlock_, 0); + DCHECK_EQ(0, result); + } + ~PThreadRWLock() { + int result = pthread_rwlock_destroy(&rwlock_); + DCHECK_EQ(0, result); + } + pthread_rwlock_t rwlock_; + + DISALLOW_COPY_AND_ASSIGN(PThreadRWLock); +}; + +// ScopedReadLock attempts to acquire a write lock in its constructor and then +// releases it in its destructor. +class ScopedWriteLock { + public: + explicit ScopedWriteLock(pthread_rwlock_t* rwlock) : rwlock_(rwlock) { + int result = pthread_rwlock_wrlock(rwlock_); + DCHECK_EQ(0, result); + } + + explicit ScopedWriteLock(PThreadRWLock* rwlock) : rwlock_(&rwlock->rwlock_) { + int result = pthread_rwlock_wrlock(rwlock_); + DCHECK_EQ(0, result); + } + + ~ScopedWriteLock() { + int result = pthread_rwlock_unlock(rwlock_); + DCHECK_EQ(0, result); + } + + protected: + pthread_rwlock_t* rwlock_; + private: + DISALLOW_COPY_AND_ASSIGN(ScopedWriteLock); +}; + +// ScopedReadLock attempts to acquire a read lock in its constructor and then +// releases it in its destructor. +class ScopedReadLock { + public: + explicit ScopedReadLock(pthread_rwlock_t* rwlock) : rwlock_(rwlock) { + int result = pthread_rwlock_rdlock(rwlock_); + DCHECK_EQ(0, result); + } + + explicit ScopedReadLock(PThreadRWLock* rwlock) : rwlock_(&rwlock->rwlock_) { + int result = pthread_rwlock_rdlock(rwlock_); + DCHECK_EQ(0, result); + } + + ~ScopedReadLock() { + int result = pthread_rwlock_unlock(rwlock_); + DCHECK_EQ(0, result); + } + protected: + pthread_rwlock_t* rwlock_; + private: + DISALLOW_COPY_AND_ASSIGN(ScopedReadLock); +}; + +template <typename LockType> +class PThreadScopedLock { + public: + explicit inline PThreadScopedLock(LockType* lock) : lock_(lock) { + if (lock_) + lock->Lock(); + } + inline ~PThreadScopedLock() { + Unlock(); + } + inline void Unlock() { + if (lock_) { + lock_->Unlock(); + lock_ = NULL; + } + } + LockType* lock_; + + private: + DISALLOW_COPY_AND_ASSIGN(PThreadScopedLock); +}; + +class PThreadNoLock { + public: + inline void Lock() { } + inline void Unlock() { } +}; + +// On win32, the pthread mutex implementation is about as efficient a critical +// section. It uses atomic operations and only needs kernel calls on +// contention. +class PThreadMutex { + public: + inline PThreadMutex() { + pthread_mutexattr_t* attributes = NULL; +#ifndef NDEBUG + private_attributes_in_use_ = true; + pthread_mutexattr_init(&mutex_attributes_); + pthread_mutexattr_settype(&mutex_attributes_, PTHREAD_MUTEX_ERRORCHECK); + attributes = &mutex_attributes_; +#endif + int result = pthread_mutex_init(&mutex_, attributes); + DCHECK_EQ(0, result); + } + inline explicit PThreadMutex(const pthread_mutexattr_t* attr) { +#ifndef NDEBUG + private_attributes_in_use_ = false; +#endif + int result = pthread_mutex_init(&mutex_, attr); + DCHECK_EQ(0, result); + } + inline ~PThreadMutex() { + int result = pthread_mutex_destroy(&mutex_); + DCHECK_EQ(0, result); +#ifndef NDEBUG + if (private_attributes_in_use_) { + pthread_mutexattr_destroy(&mutex_attributes_); + } +#endif + } + inline void Lock() { + int result = pthread_mutex_lock(&mutex_); + DCHECK_EQ(0, result); + } + inline void Unlock() { + int result = pthread_mutex_unlock(&mutex_); + DCHECK_EQ(0, result); + } + pthread_mutex_t mutex_; + +#ifndef NDEBUG + pthread_mutexattr_t mutex_attributes_; + bool private_attributes_in_use_; +#endif + + private: + DISALLOW_COPY_AND_ASSIGN(PThreadMutex); +}; + +class PThreadMutexAttr { + public: + pthread_mutexattr_t attr; + inline PThreadMutexAttr(int type) { + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, type); + } + inline ~PThreadMutexAttr() { + pthread_mutexattr_destroy(&attr); + } + private: + DISALLOW_COPY_AND_ASSIGN(PThreadMutexAttr); +}; + +class PThreadRecursiveMutex : public PThreadMutex { + public: + inline PThreadRecursiveMutex() : PThreadMutex(recursive_attr()) {} + private: + static inline pthread_mutexattr_t* recursive_attr() { + static PThreadMutexAttr recursive(PTHREAD_MUTEX_RECURSIVE); + return &recursive.attr; + } + DISALLOW_COPY_AND_ASSIGN(PThreadRecursiveMutex); +}; + + +class PThreadScopedDisabledCancellation { + public: + inline PThreadScopedDisabledCancellation() { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state_); + } + inline ~PThreadScopedDisabledCancellation() { + pthread_setcancelstate(old_cancel_state_, NULL); + } + private: + int old_cancel_state_; + + DISALLOW_COPY_AND_ASSIGN(PThreadScopedDisabledCancellation); +}; + +class PThreadCondVar { + public: + inline PThreadCondVar() { + int result = pthread_cond_init(&condvar_, 0); + DCHECK_EQ(0, result); + } + ~PThreadCondVar() { + int result = pthread_cond_destroy(&condvar_); + DCHECK_EQ(0, result); + } + inline void Signal() { + int result = pthread_cond_signal(&condvar_); + DCHECK_EQ(0, result); + } + inline void Broadcast() { + int result = pthread_cond_broadcast(&condvar_); + DCHECK_EQ(0, result); + } + pthread_cond_t condvar_; + + private: + DISALLOW_COPY_AND_ASSIGN(PThreadCondVar); +}; + +template <typename ValueType> +class PThreadThreadVar { + public: + PThreadThreadVar(void (*destructor)(void* payload)) { + int result = pthread_key_create(&thread_key_, destructor); + DCHECK_EQ(0, result); + } + ~PThreadThreadVar() { + int result = pthread_key_delete(thread_key_); + DCHECK_EQ(0, result); + } + void SetValue(ValueType value) { + int result = pthread_setspecific(thread_key_, static_cast<void*>(value)); + DCHECK_EQ(0, result); + } + ValueType GetValue() { + return static_cast<ValueType>(pthread_getspecific(thread_key_)); + } + private: + pthread_key_t thread_key_; + DISALLOW_COPY_AND_ASSIGN(PThreadThreadVar); +}; + +// Returns the absolute time ms milliseconds from now. Useful for passing +// result to pthread_cond_timedwait(). +struct timespec GetPThreadAbsoluteTime(uint32 ms_from_now); + +// Assign a descriptive label to the current thread. This is useful to see +// in a GUI debugger, but may not be implementable on all platforms. +void NameCurrentThreadForDebugging(char* name); + +#endif // CHROME_BROWSER_SYNC_UTIL_PTHREAD_HELPERS_H_ diff --git a/chrome/browser/sync/util/pthread_helpers_fwd.h b/chrome/browser/sync/util/pthread_helpers_fwd.h new file mode 100644 index 0000000..2756fceb --- /dev/null +++ b/chrome/browser/sync/util/pthread_helpers_fwd.h @@ -0,0 +1,13 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_PTHREAD_HELPERS_FWD_H_ +#define CHROME_BROWSER_SYNC_UTIL_PTHREAD_HELPERS_FWD_H_ + +template <typename LockType> +class PThreadScopedLock; +class PThreadNoLock; +class PThreadMutex; + +#endif // CHROME_BROWSER_SYNC_UTIL_PTHREAD_HELPERS_FWD_H_ diff --git a/chrome/browser/sync/util/query_helpers.cc b/chrome/browser/sync/util/query_helpers.cc new file mode 100644 index 0000000..e640a6c --- /dev/null +++ b/chrome/browser/sync/util/query_helpers.cc @@ -0,0 +1,282 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/query_helpers.h" + +#if defined(OS_WINDOWS) +#include <windows.h> +#endif + +#include <limits> +#include <string> +#include <vector> + +#include "chrome/browser/sync/util/sync_types.h" + +using std::numeric_limits; +using std::string; +using std::vector; + +sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query) { + sqlite3_stmt* statement = NULL; + const char* query_tail; + if (SQLITE_OK != sqlite3_prepare(dbhandle, query, + CountBytes(query), &statement, + &query_tail)) { + LOG(ERROR) << query << "\n" << sqlite3_errmsg(dbhandle); + } + return statement; +} + +void ExecOrDie(sqlite3* dbhandle, const char* query) { + return ExecOrDie(dbhandle, query, PrepareQuery(dbhandle, query)); +} + +// Finalizes (deletes) the query before returning. +void ExecOrDie(sqlite3* dbhandle, const char* query, sqlite3_stmt* statement) { + int result = Exec(dbhandle, query, statement); + if (SQLITE_DONE != result) { + LOG(FATAL) << query << "\n" << sqlite3_errmsg(dbhandle); + } +} + +int Exec(sqlite3* dbhandle, const char* query) { + return Exec(dbhandle, query, PrepareQuery(dbhandle, query)); +} + +// Finalizes (deletes) the query before returning. +int Exec(sqlite3* dbhandle, const char* query, sqlite3_stmt* statement) { + int result; + do { + result = sqlite3_step(statement); + } while (SQLITE_ROW == result); + int finalize_result = sqlite3_finalize(statement); + return SQLITE_OK == finalize_result ? result : finalize_result; +} + +int SqliteOpen(PathString filename, sqlite3** db) { + int result = +#if PATHSTRING_IS_STD_STRING + sqlite3_open +#else + sqlite3_open16 +#endif + (filename.c_str(), db); + LOG_IF(ERROR, SQLITE_OK != result) << "Error opening " << filename << ": " + << result; +#ifdef OS_WINDOWS + if (SQLITE_OK == result) { + // Make sure we mark the db file as not indexed so since if any other app + // opens it, it can break our db locking. + DWORD attrs = GetFileAttributes(filename.c_str()); + if (FILE_ATTRIBUTE_NORMAL == attrs) + attrs = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + else + attrs = attrs | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributes(filename.c_str(), attrs); + } +#endif + // Be patient as we set pragmas. + sqlite3_busy_timeout(*db, numeric_limits<int>::max()); +#ifndef DISABLE_SQLITE_FULL_FSYNC + ExecOrDie(*db, "PRAGMA fullfsync = 1"); +#endif // DISABLE_SQLITE_FULL_FSYNC + ExecOrDie(*db, "PRAGMA synchronous = 2"); + sqlite3_busy_timeout(*db, 0); + return SQLITE_OK; +} + +#if !PATHSTRING_IS_STD_STRING +sqlite3_stmt* BindArg(sqlite3_stmt* statement, const PathString& s, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_text16(statement, index, s.data(), + CountBytes(s), SQLITE_TRANSIENT)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, const PathChar* s, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_text16(statement, + index, + s, + -1, // -1 means s is zero-terminated + SQLITE_TRANSIENT)); + return statement; +} +#endif + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, const string& s, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_text(statement, + index, + s.data(), + CountBytes(s), + SQLITE_TRANSIENT)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, const char* s, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_text(statement, + index, + s, + -1, // -1 means s is zero-terminated + SQLITE_TRANSIENT)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, int32 n, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_int(statement, index, n)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, int64 n, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_int64(statement, index, n)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, double n, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_double(statement, index, n)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, bool b, int index) { + if (NULL == statement) + return statement; + int32 n = b ? 1 : 0; + CHECK(SQLITE_OK == sqlite3_bind_int(statement, index, n)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, const vector<uint8>& v, + int index) { + if (NULL == statement) + return statement; + uint8* blob = v.empty() ? NULL : const_cast<uint8*>(&v[0]); + CHECK(SQLITE_OK == sqlite3_bind_blob(statement, + index, + blob, + v.size(), + SQLITE_TRANSIENT)); + return statement; +} + +sqlite3_stmt* BindArg(sqlite3_stmt* statement, SqliteNullType, int index) { + if (NULL == statement) + return statement; + CHECK(SQLITE_OK == sqlite3_bind_null(statement, index)); + return statement; +} + + +#if !PATHSTRING_IS_STD_STRING +void GetColumn(sqlite3_stmt* statement, int index, PathString* value) { + if (sqlite3_column_type(statement, index) == SQLITE_NULL) { + value->clear(); + } else { + value->assign( + static_cast<const PathChar*>(sqlite3_column_text16(statement, index)), + sqlite3_column_bytes16(statement, index) / sizeof(PathChar)); + } +} +#endif + +void GetColumn(sqlite3_stmt* statement, int index, string* value) { + if (sqlite3_column_type(statement, index) == SQLITE_NULL) { + value->clear(); + } else { + value->assign( + reinterpret_cast<const char*>(sqlite3_column_text(statement, index)), + sqlite3_column_bytes(statement, index)); + } +} + +void GetColumn(sqlite3_stmt* statement, int index, int32* value) { + *value = sqlite3_column_int(statement, index); +} + +void GetColumn(sqlite3_stmt* statement, int index, int64* value) { + *value = sqlite3_column_int64(statement, index); +} + +void GetColumn(sqlite3_stmt* statement, int index, double* value) { + *value = sqlite3_column_double(statement, index); +} + +void GetColumn(sqlite3_stmt* statement, int index, bool* value) { + *value = (0 != sqlite3_column_int(statement, index)); +} + +void GetColumn(sqlite3_stmt* statement, int index, std::vector<uint8>* value) { + if (sqlite3_column_type(statement, index) == SQLITE_NULL) { + value->clear(); + } else { + const uint8* blob = + reinterpret_cast<const uint8*>(sqlite3_column_blob(statement, index)); + for (int i = 0; i < sqlite3_column_bytes(statement, index); i++) + value->push_back(blob[i]); + } +} + +bool DoesTableExist(sqlite3 *dbhandle, const string &table_name) { + ScopedStatement count_query + (PrepareQuery(dbhandle, + "SELECT count(*) from sqlite_master where name = ?", + table_name)); + + int query_result = sqlite3_step(count_query.get()); + CHECK(SQLITE_ROW == query_result); + int count = sqlite3_column_int(count_query.get(), 0); + + return 1 == count; +} + +void ScopedStatement::reset(sqlite3_stmt* statement) { + if (NULL != statement_) + sqlite3_finalize(statement_); + statement_ = statement; +} + +ScopedStatement::~ScopedStatement() { + reset(NULL); +} + +ScopedStatementResetter::~ScopedStatementResetter() { + sqlite3_reset(statement_); +} + +// Useful for encoding any sequence of bytes into a string that can be used in +// a table name. Kind of like hex encoding, except that A is zero and P is 15. +string APEncode(const string& in) { + string result; + result.reserve(in.size() * 2); + for (string::const_iterator i = in.begin(); i != in.end(); ++i) { + unsigned int c = static_cast<unsigned char>(*i); + result.push_back((c & 0x0F) + 'A'); + result.push_back(((c >> 4) & 0x0F) + 'A'); + } + return result; +} + +string APDecode(const string& in) { + string result; + result.reserve(in.size() / 2); + for (string::const_iterator i = in.begin(); i != in.end(); ++i) { + unsigned int c = *i - 'A'; + if (++i != in.end()) + c = c | (static_cast<unsigned char>(*i - 'A') << 4); + result.push_back(c); + } + return result; +} diff --git a/chrome/browser/sync/util/query_helpers.h b/chrome/browser/sync/util/query_helpers.h new file mode 100644 index 0000000..73aa422 --- /dev/null +++ b/chrome/browser/sync/util/query_helpers.h @@ -0,0 +1,698 @@ +// Copyright (c) 2009 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. +// +// Typesafe composition of SQL query strings. + +#ifndef CHROME_BROWSER_SYNC_UTIL_QUERY_HELPERS_H_ +#define CHROME_BROWSER_SYNC_UTIL_QUERY_HELPERS_H_ + +#include <limits> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chrome/browser/sync/util/sync_types.h" +#include "third_party/sqlite/preprocessed/sqlite3.h" + +// Sometimes threads contend on the DB lock itself, especially when one thread +// is calling SaveChanges. In the worst case scenario, the user can put his +// laptop to sleep during db contention, and wake up the laptop days later, so +// infinity seems like the best choice here. +const int kDirectoryBackingStoreBusyTimeoutMs = std::numeric_limits<int>::max(); + +enum SqliteNullType { + SQLITE_NULL_VALUE +}; + +int SqliteOpen(PathString filename, sqlite3** ppDb); + +sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query); +#if !PATHSTRING_IS_STD_STRING +sqlite3_stmt* BindArg(sqlite3_stmt*, const PathString&, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, const PathChar*, int index); +#endif +sqlite3_stmt* BindArg(sqlite3_stmt*, const std::string&, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, const char*, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, int32, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, int64, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, double, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, bool, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, const std::vector<uint8>&, int index); +sqlite3_stmt* BindArg(sqlite3_stmt*, SqliteNullType, int index); + +#if !PATHSTRING_IS_STD_STRING +void GetColumn(sqlite3_stmt*, int index, PathString* value); +#endif +void GetColumn(sqlite3_stmt*, int index, std::string* value); +void GetColumn(sqlite3_stmt*, int index, int32* value); +void GetColumn(sqlite3_stmt*, int index, int64* value); +void GetColumn(sqlite3_stmt*, int index, double* value); +void GetColumn(sqlite3_stmt*, int index, bool* value); +void GetColumn(sqlite3_stmt*, int index, std::vector<uint8>* value); + +bool DoesTableExist(sqlite3* dbhandle, const std::string& tablename); + +// Prepares a query with a WHERE clause that filters the values by the items +// passed inside of the Vector. +// Example: +// +// vector<PathString> v; +// v.push_back("abc"); +// v.push_back("123"); +// PrepareQuery(dbhandle, "SELECT * FROM table", "column_name", v.begin(), +// v.end(), "ORDER BY id"); +// +// will produce the following query. +// +// SELECT * FROM table WHERE column_name = 'abc' OR column_name = '123' ORDER BY +// id. +// +template<typename ItemIterator> +sqlite3_stmt* PrepareQueryWhereColumnIn(sqlite3* dbhandle, + const std::string& query_head, + const std::string& filtername, + ItemIterator begin, ItemIterator end, + const std::string& query_options) { + std::string query; + query.reserve(512); + query += query_head; + const char* joiner = " WHERE "; + for (ItemIterator it = begin; it != end; ++it) { + query += joiner; + query += filtername; + query += " = ?"; + joiner = " OR "; + } + query += " "; + query += query_options; + sqlite3_stmt* statement = NULL; + const char* query_tail; + if (SQLITE_OK != sqlite3_prepare(dbhandle, query.data(), + CountBytes(query), &statement, + &query_tail)) { + LOG(ERROR) << query << "\n" << sqlite3_errmsg(dbhandle); + } + int index = 1; + for (ItemIterator it = begin; it != end; ++it) { + BindArg(statement, *it, index); + ++index; + } + return statement; +} + +template <typename Type1> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1) { + return BindArg(PrepareQuery(dbhandle, query), arg1, 1); +} + +template <typename Type1, typename Type2> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2) { + return BindArg(PrepareQuery(dbhandle, query, arg1), arg2, 2); +} + +template <typename Type1, typename Type2, typename Type3> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2), arg3, 3); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3), arg4, 4); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4), + arg5, 5); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5), + arg6, 6); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6), + arg7, 7); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7), + arg8, 8); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8), + arg9, 9); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9), + arg10, 10); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10), + arg11, 11); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11), + arg12, 12); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12), + arg13, 13); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13), + arg14, 14); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14, typename Type15> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14, + const Type15& arg15) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13, arg14), + arg15, 15); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14, typename Type15, typename Type16> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14, + const Type15& arg15, const Type16& arg16) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13, arg14, arg15), + arg16, 16); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14, typename Type15, typename Type16, + typename Type17> +inline sqlite3_stmt* PrepareQuery(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14, + const Type15& arg15, const Type16& arg16, + const Type17& arg17) { + return BindArg(PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13, arg14, arg15, arg16), + arg17, 17); +} + +void ExecOrDie(sqlite3* dbhandle, const char* query); + +// Finalizes (deletes) the query before returning. +void ExecOrDie(sqlite3* dbhandle, const char* query, sqlite3_stmt* statement); + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10) { + return ExecOrDie(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9) { + return ExecOrDie(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8) { + return ExecOrDie(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7) { + return ExecOrDie(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6) { + return ExecOrDie(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3, arg4, arg5, arg6)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5) { + return ExecOrDie(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3, arg4, arg5)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4) { + return ExecOrDie(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3, arg4)); +} + +template <typename Type1, typename Type2, typename Type3> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3) { + return ExecOrDie(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3)); +} + +template <typename Type1, typename Type2> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2) { + return ExecOrDie(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2)); +} + +template <typename Type1> +inline void ExecOrDie(sqlite3* dbhandle, const char* query, + const Type1& arg1) { + return ExecOrDie(dbhandle, query, PrepareQuery(dbhandle, query, arg1)); +} + + +int Exec(sqlite3* dbhandle, const char* query); +// Finalizes (deletes) the query before returning. +int Exec(sqlite3* dbhandle, const char* query, sqlite3_stmt* statement); + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14, typename Type15, typename Type16, + typename Type17> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14, + const Type15& arg15, const Type16& arg16, + const Type17& arg17) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, + arg14, arg15, arg16, arg17)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14, typename Type15, typename Type16> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14, + const Type15& arg15, const Type16& arg16) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, + arg14, arg15, arg16)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14, typename Type15> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14, + const Type15& arg15) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, + arg14, arg15)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13, typename Type14> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13, const Type14& arg14) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, + arg14)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12, + typename Type13> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12, + const Type13& arg13) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11, typename Type12> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11, const Type12& arg12) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11, arg12)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10, typename Type11> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10, + const Type11& arg11) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10, arg11)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9, typename Type10> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9, const Type10& arg10) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9, arg10)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8, + typename Type9> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8, + const Type9& arg9) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7, typename Type8> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7, const Type8& arg8) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6, typename Type7> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6, + const Type7& arg7) { + return Exec(dbhandle, query, + PrepareQuery(dbhandle, query, arg1, arg2, arg3, arg4, arg5, + arg6, arg7)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5, typename Type6> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5, const Type6& arg6) { + return Exec(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3, arg4, arg5, arg6)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4, + typename Type5> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4, + const Type5& arg5) { + return Exec(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3, arg4, arg5)); +} + +template <typename Type1, typename Type2, typename Type3, typename Type4> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3, const Type4& arg4) { + return Exec(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3, arg4)); +} + +template <typename Type1, typename Type2, typename Type3> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2, + const Type3& arg3) { + return Exec(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2, + arg3)); +} + +template <typename Type1, typename Type2> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1, const Type2& arg2) { + return Exec(dbhandle, query, PrepareQuery(dbhandle, query, arg1, arg2)); +} + +template <typename Type1> +inline int Exec(sqlite3* dbhandle, const char* query, + const Type1& arg1) { + return Exec(dbhandle, query, PrepareQuery(dbhandle, query, arg1)); +} + + +// Holds an sqlite3_stmt* and automatically finalizes when passes out of scope. +class ScopedStatement { + public: + explicit ScopedStatement(sqlite3_stmt* statement = 0) + : statement_(statement) { } + ~ScopedStatement(); + + sqlite3_stmt* get() const { return statement_; } + + // Finalizes currently held statement and sets to new one. + void reset(sqlite3_stmt* statement); + protected: + sqlite3_stmt* statement_; + + DISALLOW_COPY_AND_ASSIGN(ScopedStatement); +}; + + +// Holds an sqlite3_stmt* and automatically resets when passes out of scope. +class ScopedStatementResetter { + public: + explicit ScopedStatementResetter(sqlite3_stmt* statement) + : statement_(statement) { } + ~ScopedStatementResetter(); + + protected: + sqlite3_stmt* const statement_; + + DISALLOW_COPY_AND_ASSIGN(ScopedStatementResetter); +}; + +// Useful for encoding any sequence of bytes into a string that can be used in +// a table name. Kind of like hex encoding, except that A is zero and P is 15. +std::string APEncode(const std::string& in); +std::string APDecode(const std::string& in); + +#endif // CHROME_BROWSER_SYNC_UTIL_QUERY_HELPERS_H_ diff --git a/chrome/browser/sync/util/query_helpers_unittest.cc b/chrome/browser/sync/util/query_helpers_unittest.cc new file mode 100644 index 0000000..8be295d --- /dev/null +++ b/chrome/browser/sync/util/query_helpers_unittest.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/query_helpers.h" + +#include <limits> +#include <string> + +#include "chrome/browser/sync/util/compat-file.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::numeric_limits; +using std::string; + +TEST(QueryHelpers, APEncode) { + string test; + char i; + for (i = numeric_limits<char>::min(); i < numeric_limits<char>::max(); ++i) + test.push_back(i); + test.push_back(i); + const string encoded = APEncode(test); + const string decoded = APDecode(encoded); + ASSERT_EQ(test, decoded); +} + +TEST(QueryHelpers, TestExecFailure) { + sqlite3* database; + const PathString test_database(PSTR("queryhelper_test.sqlite3")); + PathRemove(test_database); + ASSERT_EQ(SQLITE_OK, SqliteOpen(test_database, &database)); + EXPECT_EQ(SQLITE_DONE, Exec(database, "CREATE TABLE test_table (idx int)")); + EXPECT_NE(SQLITE_DONE, Exec(database, "ALTER TABLE test_table ADD COLUMN " + "broken int32 default ?", -1)); + PathRemove(test_database); +} diff --git a/chrome/browser/sync/util/row_iterator.h b/chrome/browser/sync/util/row_iterator.h new file mode 100644 index 0000000..73748ee --- /dev/null +++ b/chrome/browser/sync/util/row_iterator.h @@ -0,0 +1,122 @@ +// Copyright (c) 2009 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. + +// A handy type for iterating through query results. +// +// Just define your Traits type with +// +// RowType +// Extract(statement*, RowType); +// +// and then pass an sqlite3_stmt into the constructor for begin, +// and use the no-arg constructor for an end iterator. Ex: +// +// for (RowIterator<SomeTraits> i(statement), end; i != end; ++i) +// ... + +#ifndef CHROME_BROWSER_SYNC_UTIL_ROW_ITERATOR_H_ +#define CHROME_BROWSER_SYNC_UTIL_ROW_ITERATOR_H_ + +#include "base/logging.h" +#include "third_party/sqlite/preprocessed/sqlite3.h" + +template <typename ColumnType, int index = 0> +struct SingleColumnTraits { + typedef ColumnType RowType; + inline void Extract(sqlite3_stmt* statement, ColumnType* x) const { + GetColumn(statement, index, x); + } +}; + +template <typename RowTraits> +class RowIterator : public std::iterator<std::input_iterator_tag, + const typename RowTraits::RowType> { + public: + typedef typename RowTraits::RowType RowType; + // Statement must have been prepared, but not yet stepped: + RowIterator(sqlite3_stmt* statement, RowTraits traits = RowTraits()) { + kernel_ = new Kernel; + kernel_->done = false; + kernel_->refcount = 1; + kernel_->statement = statement; + kernel_->row_traits = traits; + ++(*this); + } + RowIterator() : kernel_(NULL) { } // creates end iterator + + RowIterator(const RowIterator& i) + : kernel_(NULL) { + *this = i; + } + + ~RowIterator() { + if (kernel_ && 0 == --(kernel_->refcount)) { + sqlite3_finalize(kernel_->statement); + delete kernel_; + } + } + + RowIterator& operator = (const RowIterator& i) { + if (kernel_ && (0 == --(kernel_->refcount))) { + sqlite3_finalize(kernel_->statement); + delete kernel_; + } + kernel_ = i.kernel_; + if (kernel_) + kernel_->refcount += 1; + return *this; + } + + RowIterator operator ++(int) { + RowIterator i(*this); + return ++i; + } + + RowIterator& operator ++() { + DCHECK(NULL != kernel_); + if (SQLITE_ROW == sqlite3_step(kernel_->statement)) { + kernel_->row_traits.Extract(kernel_->statement, &kernel_->row); + } else { + kernel_->done = true; + } + return *this; + } + + const RowType& operator *() const { + return *(operator -> ()); + } + + const RowType* operator ->() const { + DCHECK(NULL != kernel_); + DCHECK(!kernel_->done); + return &(kernel_->row); + } + + bool operator == (const RowIterator& i) const { + if (kernel_ == i.kernel_) + return true; + if (NULL == kernel_ && i.kernel_->done) + return true; + if (NULL == i.kernel_ && kernel_->done) + return true; + return false; + } + + bool operator != (const RowIterator& i) const { + return !(*this == i); + } + + protected: + struct Kernel { + int refcount; + bool done; + RowType row; + sqlite3_stmt* statement; + RowTraits row_traits; + }; + + Kernel* kernel_; +}; + +#endif // CHROME_BROWSER_SYNC_UTIL_ROW_ITERATOR_H_ diff --git a/chrome/browser/sync/util/signin.h b/chrome/browser/sync/util/signin.h new file mode 100644 index 0000000..0664d38 --- /dev/null +++ b/chrome/browser/sync/util/signin.h @@ -0,0 +1,15 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_SIGNIN_H_ +#define CHROME_BROWSER_SYNC_UTIL_SIGNIN_H_ + +// This enumeration is here since we used to support hosted and non-hosted +// accounts, but now only the latter is supported. +enum SignIn { + // The account foo@domain is authenticated as a consumer account. + GMAIL_SIGNIN +}; + +#endif // CHROME_BROWSER_SYNC_UTIL_SIGNIN_H_ diff --git a/chrome/browser/sync/util/sync_types.h b/chrome/browser/sync/util/sync_types.h new file mode 100644 index 0000000..7a08575 --- /dev/null +++ b/chrome/browser/sync/util/sync_types.h @@ -0,0 +1,75 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_SYNC_TYPES_H_ +#define CHROME_BROWSER_SYNC_UTIL_SYNC_TYPES_H_ + +#include <iosfwd> +#include <string> + +#include "base/basictypes.h" +#include "base/string_util.h" + +// TODO(timsteele): Use base/file_path.h instead of PathString. +#ifdef OS_WINDOWS +#define PATHSTRING_IS_STD_STRING 0 +typedef std::wstring PathString; + +// This ugly double define hack is needed to allow the following pattern on +// Windows: +// +// #define FOO "Foo" +// #define FOO_PATH_STRING PSTR("Foo") +// +// TODO(sync): find out if we can avoid this. +#define PSTR_UGLY_DOUBLE_DEFINE_HACK(s) L##s +#define PSTR(s) PSTR_UGLY_DOUBLE_DEFINE_HACK(s) +#define PSTR_CHAR wchar_t + +inline size_t PathLen(const wchar_t* s) { + return wcslen(s); +} + +#else // Mac and Linux +#define PATHSTRING_IS_STD_STRING 1 +#define PSTR_CHAR char +typedef string PathString; +#define PSTR(s) s +inline size_t PathLen(const char* s) { + return strlen(s); +} +// Mac OS X typedef's BOOL to signed char, so we do that on Linux too. +typedef signed char BOOL; +typedef int32 LONG; +typedef uint32 DWORD; +typedef int64 LONGLONG; +typedef uint64 ULONGLONG; + +#define MAX_PATH PATH_MAX +#if !defined(TRUE) +const BOOL TRUE = 1; +#endif +#if !defined(FALSE) +const BOOL FALSE = 0; +#endif +#endif + +typedef PathString::value_type PathChar; + +inline size_t CountBytes(const std::wstring& s) { + return s.size() * sizeof(std::wstring::value_type); +} + +inline size_t CountBytes(const std::string &s) { + return s.size() * sizeof(std::string::value_type); +} + +inline PathString IntToPathString(int digit) { + std::string tmp = StringPrintf("%d", digit); + return PathString(tmp.begin(), tmp.end()); +} + +const int kSyncProtocolMaxNameLengthBytes = 255; + +#endif // CHROME_BROWSER_SYNC_UTIL_SYNC_TYPES_H_ diff --git a/chrome/browser/sync/util/user_settings-posix.cc b/chrome/browser/sync/util/user_settings-posix.cc new file mode 100644 index 0000000..091e7e3 --- /dev/null +++ b/chrome/browser/sync/util/user_settings-posix.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2009 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 entry. + +// Implement the storage of service tokens in memory + +#include "chrome/browser/sync/util/user_settings.h" + +namespace browser_sync { + +void UserSettings::ClearAllServiceTokens() { + service_tokens_.clear(); +} + +void UserSettings::SetAuthTokenForService(const string& email, + const string& service_name, const string& long_lived_service_token) { + service_tokens_[service_name] = long_lived_service_token; +} + +bool UserSettings::GetLastUserAndServiceToken(const string& service_name, + string* username, + string* service_token) { + ServiceTokenMap::const_iterator iter = service_tokens_.find(service_name); + + if (iter != service_tokens_.end()) { + *username = email_; + *service_token = iter->second; + return true; + } + + return false; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/user_settings-win32.cc b/chrome/browser/sync/util/user_settings-win32.cc new file mode 100644 index 0000000..dac7f21 --- /dev/null +++ b/chrome/browser/sync/util/user_settings-win32.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2009 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 entry. + +#include "chrome/browser/sync/util/user_settings.h" + +#include <string> + +#include "chrome/browser/sync/util/crypto_helpers.h" +#include "chrome/browser/sync/util/data_encryption.h" +#include "chrome/browser/sync/util/query_helpers.h" + +using std::string; + +namespace browser_sync { + +bool UserSettings::GetLastUser(string* username) { + ScopedDBHandle dbhandle(this); + ScopedStatement query(PrepareQuery(dbhandle.get(), + "SELECT email FROM cookies")); + if (SQLITE_ROW == sqlite3_step(query.get())) { + GetColumn(query.get(), 0, username); + return true; + } else { + return false; + } +} + +void UserSettings::ClearAllServiceTokens() { + ScopedDBHandle dbhandle(this); + ExecOrDie(dbhandle.get(), "DELETE FROM cookies"); +} + +void UserSettings::SetAuthTokenForService(const string& email, + const string& service_name, const string& long_lived_service_token) { + ScopedDBHandle dbhandle(this); + ExecOrDie(dbhandle.get(), "INSERT INTO cookies " + "(email, service_name, service_token) " + "values (?, ?, ?)", email, service_name, + EncryptData(long_lived_service_token)); +} + +// Returns the username whose credentials have been persisted as well as +// a service token for the named service. +bool UserSettings::GetLastUserAndServiceToken(const string& service_name, + string* username, + string* service_token) { + ScopedDBHandle dbhandle(this); + ScopedStatement query(PrepareQuery( + dbhandle.get(), + "SELECT email, service_token FROM cookies WHERE service_name = ?", + service_name)); + + if (SQLITE_ROW == sqlite3_step(query.get())) { + GetColumn(query.get(), 0, username); + + std::vector<uint8> encrypted_service_token; + GetColumn(query.get(), 1, &encrypted_service_token); + DecryptData(encrypted_service_token, service_token); + return true; + } + + return false; +} + +} // namespace browser_sync + diff --git a/chrome/browser/sync/util/user_settings.cc b/chrome/browser/sync/util/user_settings.cc new file mode 100644 index 0000000..573365a --- /dev/null +++ b/chrome/browser/sync/util/user_settings.cc @@ -0,0 +1,350 @@ +// Copyright (c) 2009 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 entry. + +// This class isn't pretty. It's just a step better than globals, which is what +// these were previously. + +#include "chrome/browser/sync/util/user_settings.h" + +#if defined(OS_WINDOWS) +#include <windows.h> +#endif + +#include <string> +#include <limits> +#include <vector> + +#include "base/file_util.h" +#include "base/string_util.h" +#include "chrome/browser/sync/syncable/directory_manager.h" // For migration. +#include "chrome/browser/sync/util/crypto_helpers.h" +#include "chrome/browser/sync/util/data_encryption.h" +#include "chrome/browser/sync/util/path_helpers.h" +#include "chrome/browser/sync/util/query_helpers.h" + +using std::numeric_limits; +using std::string; +using std::vector; + +using syncable::DirectoryManager; + +namespace browser_sync { + +static const char PASSWORD_HASH[] = "password_hash2"; +static const char SALT[] = "salt2"; + +static const int kSaltSize = 20; +static const int kCurrentDBVersion = 11; + +UserSettings::ScopedDBHandle::ScopedDBHandle(UserSettings* settings) : + mutex_lock_(&settings->dbhandle_mutex_), handle_(&settings->dbhandle_) { +} + +UserSettings::UserSettings() : + dbhandle_(NULL) { +} + +string UserSettings::email() const { + ScopedLock lock(&mutex_); + return email_; +} + +static void MakeSigninsTable(sqlite3* const dbhandle) { + // Multiple email addresses can map to the same Google Account. + // This table keeps a map of sign-in email addresses to primary + // Google Account email addresses. + ExecOrDie(dbhandle, "CREATE TABLE signins" + " (signin, primary_email, " + " PRIMARY KEY(signin, primary_email) ON CONFLICT REPLACE)"); +} + +void UserSettings::MigrateOldVersionsAsNeeded(sqlite3* const handle, + int current_version) { + switch (current_version) { + // Versions 1-9 are unhandled. Version numbers greater than + // kCurrentDBVersion should have already been weeded out by the caller. + default: + // When the version is too old, we just try to continue anyway. There + // should not be a released product that makes a database too old for us + // to handle. + LOG(WARNING) << "UserSettings database version " << current_version << + " is too old to handle."; + return; + case 10: + { + // Scrape the 'shares' table to find the syncable DB. 'shares' + // had a pair of string columns that mapped the username to the filename + // of the sync data sqlite3 file. Version 11 switched to a constant + // filename, so here we read the string, copy the file to the new name, + // delete the old one, and then drop the unused shares table. + ScopedStatement share_query(PrepareQuery(handle, + "SELECT share_name, file_name FROM shares")); + int query_result = sqlite3_step(share_query.get()); + CHECK(SQLITE_ROW == query_result); + PathString share_name, file_name; + GetColumn(share_query.get(), 0, &share_name); + GetColumn(share_query.get(), 1, &file_name); + + if (!file_util::Move(file_name, + DirectoryManager::GetSyncDataDatabaseFilename())) { + LOG(WARNING) << "Unable to upgrade UserSettings from v10"; + return; + } + } + ExecOrDie(handle, "DROP TABLE shares"); + ExecOrDie(handle, "UPDATE db_version SET version = 11"); + // FALL THROUGH + case kCurrentDBVersion: + // Nothing to migrate. + return; + } +} + +static void MakeCookiesTable(sqlite3* const dbhandle) { + // This table keeps a list of auth tokens for each signed in account. There + // will be as many rows as there are auth tokens per sign in. + // The service_token column will store encrypted values. + ExecOrDie(dbhandle, "CREATE TABLE cookies" + " (email, service_name, service_token, " + " PRIMARY KEY(email, service_name) ON CONFLICT REPLACE)"); +} + +static void MakeSigninTypesTable(sqlite3* const dbhandle) { + // With every successful gaia authentication, remember if it was + // a hosted domain or not. + ExecOrDie(dbhandle, "CREATE TABLE signin_types" + " (signin, signin_type, " + " PRIMARY KEY(signin, signin_type) ON CONFLICT REPLACE)"); +} + +static void MakeClientIDTable(sqlite3* const dbhandle) { + // Stores a single client ID value that can be used as the client id, + // if there's not another such ID provided on the install. + ExecOrDie(dbhandle, "CREATE TABLE client_id (id) "); + ExecOrDie(dbhandle, "INSERT INTO client_id values ( ? )", + Generate128BitRandomHexString()); +} + +bool UserSettings::Init(const PathString& settings_path) { + { // Scope the handle + ScopedDBHandle dbhandle(this); + if (dbhandle_) + sqlite3_close(dbhandle_); + CHECK(SQLITE_OK == SqliteOpen(settings_path.c_str(), &dbhandle_)); + // In the worst case scenario, the user may hibernate his computer during + // one of our transactions. + sqlite3_busy_timeout(dbhandle_, numeric_limits<int>::max()); + + int sqlite_result = Exec(dbhandle.get(), "BEGIN EXCLUSIVE TRANSACTION"); + CHECK(SQLITE_DONE == sqlite_result); + ScopedStatement table_query(PrepareQuery(dbhandle.get(), + "select count(*) from sqlite_master where type = 'table'" + " and name = 'db_version'")); + int query_result = sqlite3_step(table_query.get()); + CHECK(SQLITE_ROW == query_result); + int table_count = 0; + GetColumn(table_query.get(), 0, &table_count); + table_query.reset(NULL); + if (table_count > 0) { + ScopedStatement version_query(PrepareQuery(dbhandle.get(), + "SELECT version FROM db_version")); + query_result = sqlite3_step(version_query.get()); + CHECK(SQLITE_ROW == query_result); + const int version = sqlite3_column_int(version_query.get(), 0); + version_query.reset(NULL); + if (version > kCurrentDBVersion) { + LOG(WARNING) << "UserSettings database is too new."; + return false; + } + + MigrateOldVersionsAsNeeded(dbhandle.get(), version); + } else { + // Create settings table. + ExecOrDie(dbhandle.get(), "CREATE TABLE settings" + " (email, key, value, " + " PRIMARY KEY(email, key) ON CONFLICT REPLACE)"); + + // Create and populate version table. + ExecOrDie(dbhandle.get(), "CREATE TABLE db_version ( version )"); + ExecOrDie(dbhandle.get(), "INSERT INTO db_version values ( ? )", + kCurrentDBVersion); + + MakeSigninsTable(dbhandle.get()); + MakeCookiesTable(dbhandle.get()); + MakeSigninTypesTable(dbhandle.get()); + MakeClientIDTable(dbhandle.get()); + } + ExecOrDie(dbhandle.get(), "COMMIT TRANSACTION"); + } +#ifdef OS_WINDOWS + // Do not index this file. Scanning can occur every time we close the file, + // which causes long delays in SQLite's file locking. + const DWORD attrs = GetFileAttributes(settings_path.c_str()); + const BOOL attrs_set = + SetFileAttributes(settings_path.c_str(), + attrs | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); +#endif + return true; +} + + +UserSettings::~UserSettings() { + if (dbhandle_) + sqlite3_close(dbhandle_); +} + +const int32 kInvalidHash = 0xFFFFFFFF; + +// We use 10 bits of data from the MD5 digest as the hash. +const int32 kHashMask = 0x3FF; + +int32 GetHashFromDigest(const vector<uint8>& digest) { + int32 hash = 0; + int32 mask = kHashMask; + for (vector<uint8>::const_iterator i = digest.begin(); i != digest.end(); + ++i) { + hash = hash << 8; + hash = hash | (*i & kHashMask); + mask = mask >> 8; + if (0 == mask) + break; + } + return hash; +} + +void UserSettings::StoreEmailForSignin(const string& signin, + const string& primary_email) { + ScopedDBHandle dbhandle(this); + ExecOrDie(dbhandle.get(), "BEGIN TRANSACTION"); + ScopedStatement query(PrepareQuery(dbhandle.get(), + "SELECT COUNT(*) FROM signins" + " WHERE signin = ? AND primary_email = ?", + signin, primary_email)); + int query_result = sqlite3_step(query.get()); + CHECK(SQLITE_ROW == query_result); + int32 count = 0; + GetColumn(query.get(), 0, &count); + query.reset(NULL); + if (0 == count) { + // Migrate any settings the user might have from earlier versions. + ExecOrDie(dbhandle.get(), "UPDATE settings SET email = ? WHERE email = ?", + primary_email, signin); + // Store this signin:email mapping. + ExecOrDie(dbhandle.get(), "INSERT INTO signins(signin, primary_email)" + " values ( ?, ? )", signin, primary_email); + } + ExecOrDie(dbhandle.get(), "COMMIT TRANSACTION"); +} + +bool UserSettings::GetEmailForSignin(/*in, out*/string* signin) { + ScopedDBHandle dbhandle(this); + string result; + ScopedStatement query(PrepareQuery(dbhandle.get(), + "SELECT primary_email FROM signins" + " WHERE signin = ?", *signin)); + int query_result = sqlite3_step(query.get()); + if (SQLITE_ROW == query_result) { + GetColumn(query.get(), 0, &result); + if (!result.empty()) { + swap(result, *signin); + return true; + } + } + return false; +} + +void UserSettings::StoreHashedPassword(const string& email, + const string& password) { + // Save one-way hashed password: + char binary_salt[kSaltSize]; + { + ScopedLock lock(&mutex_); + GetRandomBytes(binary_salt, sizeof(binary_salt)); + } + const string salt = APEncode(string(binary_salt, sizeof(binary_salt))); + MD5Calculator md5; + md5.AddData(salt.data(), salt.size()); + md5.AddData(password.data(), password.size()); + ScopedDBHandle dbhandle(this); + ExecOrDie(dbhandle.get(), "BEGIN TRANSACTION"); + ExecOrDie(dbhandle.get(), "INSERT INTO settings(email, key, value )" + " values ( ?, ?, ? )", email, PASSWORD_HASH, + GetHashFromDigest(md5.GetDigest())); + ExecOrDie(dbhandle.get(), "INSERT INTO settings(email, key, value )" + " values ( ?, ?, ? )", email, SALT, salt); + ExecOrDie(dbhandle.get(), "COMMIT TRANSACTION"); +} + +bool UserSettings::VerifyAgainstStoredHash(const string& email, + const string& password) { + ScopedDBHandle dbhandle(this); + string salt_and_digest; + + ScopedStatement query(PrepareQuery(dbhandle.get(), + "SELECT key, value FROM settings" + " WHERE email = ? AND" + " (key = ? OR key = ?)", + email, PASSWORD_HASH, SALT)); + int query_result = sqlite3_step(query.get()); + string salt; + int32 hash = kInvalidHash; + while (SQLITE_ROW == query_result) { + string key; + GetColumn(query.get(), 0, &key); + if (key == SALT) + GetColumn(query.get(), 1, &salt); + else + GetColumn(query.get(), 1, &hash); + query_result = sqlite3_step(query.get()); + } + CHECK(SQLITE_DONE == query_result); + if (salt.empty() || hash == kInvalidHash) + return false; + MD5Calculator md5; + md5.AddData(salt.data(), salt.size()); + md5.AddData(password.data(), password.size()); + return hash == GetHashFromDigest(md5.GetDigest()); +} + +void UserSettings::SwitchUser(const string& username) { + { + ScopedLock lock(&mutex_); + email_ = username; + } +} + +void UserSettings::RememberSigninType(const string& signin, SignIn signin_type) +{ + ScopedDBHandle dbhandle(this); + ExecOrDie(dbhandle.get(), "INSERT INTO signin_types(signin, signin_type)" + " values ( ?, ? )", signin, static_cast<int>(signin_type)); +} + +SignIn UserSettings::RecallSigninType(const string& signin, SignIn default_type) +{ + ScopedDBHandle dbhandle(this); + ScopedStatement query(PrepareQuery(dbhandle.get(), + "SELECT signin_type from signin_types" + " WHERE signin = ?", signin)); + int query_result = sqlite3_step(query.get()); + if (SQLITE_ROW == query_result) { + int signin_type; + GetColumn(query.get(), 0, &signin_type); + return static_cast<SignIn>(signin_type); + } + return default_type; +} + +string UserSettings::GetClientId() { + ScopedDBHandle dbhandle(this); + ScopedStatement query(PrepareQuery(dbhandle.get(), + "SELECT id FROM client_id")); + int query_result = sqlite3_step(query.get()); + string client_id; + if (query_result == SQLITE_ROW) + GetColumn(query.get(), 0, &client_id); + return client_id; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/user_settings.h b/chrome/browser/sync/util/user_settings.h new file mode 100644 index 0000000..45116a5 --- /dev/null +++ b/chrome/browser/sync/util/user_settings.h @@ -0,0 +1,114 @@ +// Copyright (c) 2006-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. + +#ifndef CHROME_BROWSER_SYNC_UTIL_USER_SETTINGS_H_ +#define CHROME_BROWSER_SYNC_UTIL_USER_SETTINGS_H_ + +#include <map> +#include <set> +#include <string> + +#include "chrome/browser/sync/util/pthread_helpers.h" +#include "chrome/browser/sync/util/signin.h" +#include "chrome/browser/sync/util/sync_types.h" + +extern "C" struct sqlite3; + +namespace browser_sync { + +class URLFactory; + +class UserSettings { + public: + // db_path is used for the main user settings. + // passwords_file contains hashes of passwords. + UserSettings(); + ~UserSettings(); + // Returns false (failure) if the db is a newer version. + bool Init(const PathString& settings_path); + void StoreHashedPassword(const std::string& email, + const std::string& password); + bool VerifyAgainstStoredHash(const std::string& email, + const std::string& password); + + // Set the username. + void SwitchUser(const std::string& email); + + // Saves the email address and the named service token for the given user. + // Call this multiple times with the same email parameter to save + // multiple service tokens. + void SetAuthTokenForService(const std::string& email, + const std::string& service_name, + const std::string& long_lived_service_token); + // Erases all saved service tokens. + void ClearAllServiceTokens(); + + // Returns the user name whose credentials have been persisted. + bool GetLastUser(std::string* username); + + // Returns the user name whose credentials have been persisted as well as + // a service token for the named service + bool GetLastUserAndServiceToken(const std::string& service_name, + std::string* username, + std::string* service_token); + + void RememberSigninType(const std::string& signin, SignIn signin_type); + SignIn RecallSigninType(const std::string& signin, SignIn default_type); + + void RemoveAllGuestSettings(); + + void RemoveShare(const PathString& share_path); + + void StoreEmailForSignin(const std::string& signin, + const std::string& primary_email); + + // Multiple email addresses can map to the same Google Account. This method + // returns the primary Google Account email associated with |signin|, which + // is used as both input and output. + bool GetEmailForSignin(std::string* signin); + + + std::string email() const; + + // Get a unique ID suitable for use as the client ID. This ID + // has the lifetime of the user settings database. You may use this ID if + // your operating environment does not provide its own unique client ID. + std::string GetClientId(); + + protected: + struct ScopedDBHandle { + ScopedDBHandle(UserSettings* settings); + inline sqlite3* get() const { return *handle_; } + PThreadScopedLock<PThreadMutex> mutex_lock_; + sqlite3** const handle_; + }; + + friend struct ScopedDBHandle; + friend class URLFactory; + + void MigrateOldVersionsAsNeeded(sqlite3* const handle, int current_version); + + private: + std::string email_; + mutable PThreadMutex mutex_; // protects email_ + typedef PThreadScopedLock<PThreadMutex> ScopedLock; + + // We keep a single dbhandle. + sqlite3* dbhandle_; + PThreadMutex dbhandle_mutex_; + + // TODO(sync): Use in-memory cache for service auth tokens on posix. + // Have someone competent in Windows switch it over to not use Sqlite in the + // future. +#ifndef OS_WINDOWS + typedef std::map<std::string, std::string> ServiceTokenMap; + ServiceTokenMap service_tokens_; +#endif // OS_WINDOWS + + DISALLOW_COPY_AND_ASSIGN(UserSettings); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_UTIL_USER_SETTINGS_H_ diff --git a/chrome/browser/sync/util/user_settings_unittest.cc b/chrome/browser/sync/util/user_settings_unittest.cc new file mode 100644 index 0000000..56c761d --- /dev/null +++ b/chrome/browser/sync/util/user_settings_unittest.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2009 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 entry. + +#include "base/file_util.h" +#include "base/test_file_util.h" +#include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/browser/sync/util/user_settings.h" +#include "chrome/browser/sync/util/query_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +using browser_sync::UserSettings; + +static const wchar_t* kV10UserSettingsDB = L"Version10Settings.sqlite3"; +static const wchar_t* kOldStyleSyncDataDB = L"OldStyleSyncData.sqlite3"; + +class UserSettingsTest : public testing::Test { + public: + UserSettingsTest() : sync_data_("Some sync data") { } + void SetUpVersion10Databases() { + CleanUpVersion10Databases(); + sqlite3* primer_handle = NULL; + ASSERT_EQ(SQLITE_OK, SqliteOpen(kV10UserSettingsDB, + &primer_handle)); + FilePath old_sync_data(kOldStyleSyncDataDB); + + ASSERT_EQ(sync_data_.length(), file_util::WriteFile( + old_sync_data, sync_data_.data(), sync_data_.length())); + + // Create settings table. + ExecOrDie(primer_handle, "CREATE TABLE settings" + " (email, key, value, " + " PRIMARY KEY(email, key) ON CONFLICT REPLACE)"); + + // Create and populate version table. + ExecOrDie(primer_handle, "CREATE TABLE db_version ( version )"); + ExecOrDie(primer_handle, "INSERT INTO db_version values ( ? )", 10); + // Create shares table. + ExecOrDie(primer_handle, "CREATE TABLE shares" + " (email, share_name, file_name," + " PRIMARY KEY(email, share_name) ON CONFLICT REPLACE)"); + // Populate a share. + ExecOrDie(primer_handle, "INSERT INTO shares values ( ?, ?, ?)", + "foo@foo.com", "foo@foo.com", WideToUTF8(kOldStyleSyncDataDB)); + sqlite3_close(primer_handle); + } + + void CleanUpVersion10Databases() { + ASSERT_TRUE(file_util::DieFileDie(FilePath(kV10UserSettingsDB), false)); + ASSERT_TRUE(file_util::DieFileDie(FilePath(kOldStyleSyncDataDB), false)); + ASSERT_TRUE(file_util::DieFileDie(FilePath(L"SyncData.sqlite3"), false)); + } + + const std::string& sync_data() const { return sync_data_; } + + private: + std::string sync_data_; +}; + +TEST_F(UserSettingsTest, MigrateFromV10ToV11) { + SetUpVersion10Databases(); + { + // Create a UserSettings, which should trigger migration code. + // We do this inside a scoped block so it closes itself and we can poke + // around to see what happened later. + UserSettings settings; + settings.Init(kV10UserSettingsDB); + } + + // Now poke around using sqlite to see if UserSettings migrated properly. + sqlite3* handle = NULL; + ASSERT_EQ(SQLITE_OK, SqliteOpen(kV10UserSettingsDB, &handle)); + ScopedStatement version_query(PrepareQuery(handle, + "SELECT version FROM db_version")); + ASSERT_EQ(SQLITE_ROW, sqlite3_step(version_query.get())); + + const int version = sqlite3_column_int(version_query.get(), 0); + EXPECT_EQ(11, version); + EXPECT_FALSE(file_util::PathExists(kOldStyleSyncDataDB)); + + std::wstring path(syncable::DirectoryManager::GetSyncDataDatabaseFilename()); + + std::string contents; + ASSERT_TRUE(file_util::ReadFileToString(path, &contents)); + EXPECT_EQ(sync_data(), contents); +} |