shill: Basic persistent store interface and key-file implementation.

The current interface and implementation support all functionality required by
the legacy network manager. The implementation uses glib's key file parser for
legacy reasons. The interface-based implementation allows us to switch to a
different format in the future, if necessary.

BUG=chromium-os:16897
TEST=unit test

Change-Id: I8fd54f47e7309c603b66ba86bbecb8d5092e8674
Reviewed-on: http://gerrit.chromium.org/gerrit/3160
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/Makefile b/Makefile
index b1ad0f1..23fda97 100644
--- a/Makefile
+++ b/Makefile
@@ -59,6 +59,7 @@
 	glib.o \
 	glib_io_handler.o \
 	ipconfig.o \
+	key_file_store.o \
 	manager.o \
 	manager_dbus_adaptor.o \
 	property_store.o \
@@ -85,6 +86,7 @@
 	dhcp_config_unittest.o \
 	dhcp_provider_unittest.o \
 	ipconfig_unittest.o \
+	key_file_store_unittest.o \
 	manager_unittest.o \
 	mock_control.o \
 	mock_device.o \
diff --git a/glib.cc b/glib.cc
index 13545ac..51ee0cd 100644
--- a/glib.cc
+++ b/glib.cc
@@ -2,18 +2,120 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <base/stringprintf.h>
+
 #include "shill/glib.h"
 
 namespace shill {
 
 GLib::~GLib() {}
 
+std::string GLib::ConvertErrorToMessage(GError *error) {
+  if (!error) {
+    return "Unknown GLib error.";
+  }
+  std::string message =
+      base::StringPrintf("GError(%d): %s", error->code, error->message);
+  g_error_free(error);
+  return message;
+}
+
 guint GLib::ChildWatchAdd(GPid pid,
                           GChildWatchFunc function,
                           gpointer data) {
   return g_child_watch_add(pid, function, data);
 }
 
+void GLib::Free(gpointer mem) {
+  g_free(mem);
+}
+
+void GLib::KeyFileFree(GKeyFile *key_file) {
+  g_key_file_free(key_file);
+}
+
+gboolean GLib::KeyFileLoadFromFile(GKeyFile *key_file,
+                                   const gchar *file,
+                                   GKeyFileFlags flags,
+                                   GError **error) {
+  return g_key_file_load_from_file(key_file, file, flags, error);
+}
+
+gboolean GLib::KeyFileGetBoolean(GKeyFile *key_file,
+                                 const gchar *group_name,
+                                 const gchar *key,
+                                 GError **error) {
+  return g_key_file_get_boolean(key_file, group_name, key, error);
+}
+
+gchar **GLib::KeyFileGetGroups(GKeyFile *key_file,
+                               gsize *length) {
+  return g_key_file_get_groups(key_file, length);
+}
+
+gint GLib::KeyFileGetInteger(GKeyFile *key_file,
+                             const gchar *group_name,
+                             const gchar *key,
+                             GError **error) {
+  return g_key_file_get_integer(key_file, group_name, key, error);
+}
+
+gchar *GLib::KeyFileGetString(GKeyFile *key_file,
+                              const gchar *group_name,
+                              const gchar *key,
+                              GError **error) {
+  return g_key_file_get_string(key_file, group_name, key, error);
+}
+
+gboolean GLib::KeyFileHasGroup(GKeyFile *key_file,
+                               const gchar *group_name) {
+  return g_key_file_has_group(key_file, group_name);
+}
+
+GKeyFile *GLib::KeyFileNew() {
+  return g_key_file_new();
+}
+
+void GLib::KeyFileRemoveGroup(GKeyFile *key_file,
+                              const gchar *group_name,
+                              GError **error) {
+  g_key_file_remove_group(key_file, group_name, error);
+}
+
+void GLib::KeyFileRemoveKey(GKeyFile *key_file,
+                            const gchar *group_name,
+                            const gchar *key,
+                            GError **error) {
+  g_key_file_remove_key(key_file, group_name, key, error);
+}
+
+void GLib::KeyFileSetBoolean(GKeyFile *key_file,
+                             const gchar *group_name,
+                             const gchar *key,
+                             gboolean value) {
+  g_key_file_set_boolean(key_file, group_name, key, value);
+}
+
+void GLib::KeyFileSetInteger(GKeyFile *key_file,
+                             const gchar *group_name,
+                             const gchar *key,
+                             gint value) {
+  g_key_file_set_integer(key_file, group_name, key, value);
+}
+
+void GLib::KeyFileSetString(GKeyFile *key_file,
+                            const gchar *group_name,
+                            const gchar *key,
+                            const gchar *string) {
+  g_key_file_set_string(key_file, group_name, key, string);
+}
+
+gchar *GLib::KeyFileToData(GKeyFile *key_file,
+                           gsize *length,
+                           GError **error) {
+  return g_key_file_to_data(key_file, length, error);
+}
+
 gboolean GLib::SourceRemove(guint tag) {
   return g_source_remove(tag);
 }
@@ -40,4 +142,8 @@
   g_spawn_close_pid(pid);
 }
 
+void GLib::Strfreev(gchar **str_array) {
+  g_strfreev(str_array);
+}
+
 }  // namespace shill
diff --git a/glib.h b/glib.h
index fea6056..186c075 100644
--- a/glib.h
+++ b/glib.h
@@ -5,6 +5,8 @@
 #ifndef SHILL_GLIB_H_
 #define SHILL_GLIB_H_
 
+#include <string>
+
 #include <glib.h>
 
 namespace shill {
@@ -14,10 +16,73 @@
  public:
   virtual ~GLib();
 
+  // Converts GLib's |error| to a string message and frees the GError object.
+  virtual std::string ConvertErrorToMessage(GError *error);
+
   // g_child_watch_add
   virtual guint ChildWatchAdd(GPid pid,
                               GChildWatchFunc function,
                               gpointer data);
+  // g_free
+  virtual void Free(gpointer mem);
+  // g_key_file_free
+  virtual void KeyFileFree(GKeyFile *key_file);
+  // g_key_file_get_boolean
+  virtual gboolean KeyFileGetBoolean(GKeyFile *key_file,
+                                     const gchar *group_name,
+                                     const gchar *key,
+                                     GError **error);
+  // g_key_file_get_groups
+  virtual gchar **KeyFileGetGroups(GKeyFile *key_file,
+                                   gsize *length);
+  // g_key_file_get_integer
+  virtual gint KeyFileGetInteger(GKeyFile *key_file,
+                                 const gchar *group_name,
+                                 const gchar *key,
+                                 GError **error);
+  // g_key_file_get_string
+  virtual gchar *KeyFileGetString(GKeyFile *key_file,
+                                  const gchar *group_name,
+                                  const gchar *key,
+                                  GError **error);
+  // g_key_file_has_group
+  virtual gboolean KeyFileHasGroup(GKeyFile *key_file,
+                                   const gchar *group_name);
+  // g_key_file_load_from_file
+  virtual gboolean KeyFileLoadFromFile(GKeyFile *key_file,
+                                       const gchar *file,
+                                       GKeyFileFlags flags,
+                                       GError **error);
+  // g_key_file_new
+  virtual GKeyFile *KeyFileNew();
+  // g_key_file_remove_group
+  virtual void KeyFileRemoveGroup(GKeyFile *key_file,
+                                  const gchar *group_name,
+                                  GError **error);
+  // g_key_file_remove_key
+  virtual void KeyFileRemoveKey(GKeyFile *key_file,
+                                const gchar *group_name,
+                                const gchar *key,
+                                GError **error);
+  // g_key_file_set_boolean
+  virtual void KeyFileSetBoolean(GKeyFile *key_file,
+                                 const gchar *group_name,
+                                 const gchar *key,
+                                 gboolean value);
+  // g_key_file_set_integer
+  virtual void KeyFileSetInteger(GKeyFile *key_file,
+                                 const gchar *group_name,
+                                 const gchar *key,
+                                 gint value);
+  // g_key_file_set_string
+  virtual void KeyFileSetString(GKeyFile *key_file,
+                                const gchar *group_name,
+                                const gchar *key,
+                                const gchar *string);
+  // g_key_file_to_data
+  virtual gchar *KeyFileToData(GKeyFile *key_file,
+                               gsize *length,
+                               GError **error);
   // g_source_remove
   virtual gboolean SourceRemove(guint tag);
   // g_spawn_async
@@ -31,6 +96,8 @@
                               GError **error);
   // g_spawn_close_pid
   virtual void SpawnClosePID(GPid pid);
+  // g_strfreev
+  virtual void Strfreev(gchar **str_array);
 };
 
 }  // namespace shill
diff --git a/key_file_store.cc b/key_file_store.cc
new file mode 100644
index 0000000..d844e7e
--- /dev/null
+++ b/key_file_store.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2011 The Chromium OS 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 "shill/key_file_store.h"
+
+#include <base/file_util.h>
+#include <base/logging.h>
+
+using std::set;
+using std::string;
+
+namespace shill {
+
+KeyFileStore::KeyFileStore(GLib *glib) : glib_(glib), key_file_(NULL) {}
+
+KeyFileStore::~KeyFileStore() {
+  ReleaseKeyFile();
+}
+
+void KeyFileStore::ReleaseKeyFile() {
+  if (key_file_) {
+    glib_->KeyFileFree(key_file_);
+    key_file_ = NULL;
+  }
+}
+
+bool KeyFileStore::Open() {
+  CHECK(!path_.empty());
+  CHECK(!key_file_);
+  key_file_ = glib_->KeyFileNew();
+  int64 file_size = 0;
+  if (!file_util::GetFileSize(path_, &file_size) || file_size == 0) {
+    LOG(INFO) << "Creating a new key file at " << path_.value();
+    return true;
+  }
+  GError *error = NULL;
+  if (glib_->KeyFileLoadFromFile(
+          key_file_,
+          path_.value().c_str(),
+          static_cast<GKeyFileFlags>(G_KEY_FILE_KEEP_COMMENTS |
+                                     G_KEY_FILE_KEEP_TRANSLATIONS),
+          &error)) {
+    return true;
+  }
+  LOG(ERROR) << "Failed to load key file from " << path_.value() << ": "
+             << glib_->ConvertErrorToMessage(error);
+  ReleaseKeyFile();
+  return false;
+}
+
+bool KeyFileStore::Close() {
+  CHECK(key_file_);
+  GError *error = NULL;
+  gsize length = 0;
+  gchar *data = glib_->KeyFileToData(key_file_, &length, &error);
+  ReleaseKeyFile();
+
+  bool success = true;
+  if (path_.empty()) {
+    LOG(ERROR) << "Empty key file path.";
+    success = false;
+  }
+  if (success && (!data || error)) {
+    LOG(ERROR) << "Failed to convert key file to string: "
+               << glib_->ConvertErrorToMessage(error);
+    success = false;
+  }
+  if (success && file_util::WriteFile(path_, data, length) != length) {
+    LOG(ERROR) << "Failed to store key file: " << path_.value();
+    success = false;
+  }
+  glib_->Free(data);
+  return success;
+}
+
+set<string> KeyFileStore::GetGroups() {
+  CHECK(key_file_);
+  gsize length = 0;
+  gchar **groups = g_key_file_get_groups(key_file_, &length);
+  if (!groups) {
+    LOG(ERROR) << "Unable to obtain groups.";
+    return set<string>();
+  }
+  set<string> group_set(groups, groups + length);
+  glib_->Strfreev(groups);
+  return group_set;
+}
+
+bool KeyFileStore::ContainsGroup(const string &group) {
+  CHECK(key_file_);
+  return glib_->KeyFileHasGroup(key_file_, group.c_str());
+}
+
+bool KeyFileStore::DeleteKey(const string &group, const string &key) {
+  CHECK(key_file_);
+  GError *error = NULL;
+  glib_->KeyFileRemoveKey(key_file_, group.c_str(), key.c_str(), &error);
+  if (error) {
+    LOG(ERROR) << "Failed to delete (" << group << ":" << key << "): "
+               << glib_->ConvertErrorToMessage(error);
+    return false;
+  }
+  return true;
+}
+
+bool KeyFileStore::DeleteGroup(const string &group) {
+  CHECK(key_file_);
+  GError *error = NULL;
+  glib_->KeyFileRemoveGroup(key_file_, group.c_str(), &error);
+  if (error) {
+    LOG(ERROR) << "Failed to delete group " << group << "): "
+               << glib_->ConvertErrorToMessage(error);
+    return false;
+  }
+  return true;
+}
+
+bool KeyFileStore::GetString(const string &group,
+                             const string &key,
+                             string *value) {
+  CHECK(key_file_);
+  GError *error = NULL;
+  gchar *data =
+      glib_->KeyFileGetString(key_file_, group.c_str(), key.c_str(), &error);
+  if (!data) {
+    LOG(ERROR) << "Failed to lookup (" << group << ":" << key << "): "
+               << glib_->ConvertErrorToMessage(error);
+    return false;
+  }
+  if (value) {
+    *value = data;
+  }
+  glib_->Free(data);
+  return true;
+}
+
+bool KeyFileStore::SetString(const string &group,
+                             const string &key,
+                             const string &value) {
+  CHECK(key_file_);
+  glib_->KeyFileSetString(key_file_, group.c_str(), key.c_str(), value.c_str());
+  return true;
+}
+
+bool KeyFileStore::GetBool(const string &group,
+                           const string &key,
+                           bool *value) {
+  CHECK(key_file_);
+  GError *error = NULL;
+  gboolean data =
+      glib_->KeyFileGetBoolean(key_file_, group.c_str(), key.c_str(), &error);
+  if (error) {
+    LOG(ERROR) << "Failed to lookup (" << group << ":" << key << "): "
+               << glib_->ConvertErrorToMessage(error);
+    return false;
+  }
+  if (value) {
+    *value = data;
+  }
+  return true;
+}
+
+bool KeyFileStore::SetBool(const string &group, const string &key, bool value) {
+  CHECK(key_file_);
+  glib_->KeyFileSetBoolean(key_file_,
+                           group.c_str(),
+                           key.c_str(),
+                           value ? TRUE : FALSE);
+  return true;
+}
+
+bool KeyFileStore::GetInt(const string &group, const string &key, int *value) {
+  CHECK(key_file_);
+  GError *error = NULL;
+  gint data =
+      glib_->KeyFileGetInteger(key_file_, group.c_str(), key.c_str(), &error);
+  if (error) {
+    LOG(ERROR) << "Failed to lookup (" << group << ":" << key << "): "
+               << glib_->ConvertErrorToMessage(error);
+    return false;
+  }
+  if (value) {
+    *value = data;
+  }
+  return true;
+}
+
+bool KeyFileStore::SetInt(const string &group, const string &key, int value) {
+  CHECK(key_file_);
+  glib_->KeyFileSetInteger(key_file_, group.c_str(), key.c_str(), value);
+  return true;
+}
+
+}  // namespace shill
diff --git a/key_file_store.h b/key_file_store.h
new file mode 100644
index 0000000..a9e2854
--- /dev/null
+++ b/key_file_store.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011 The Chromium OS 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 SHILL_KEY_FILE_STORE_
+#define SHILL_KEY_FILE_STORE_
+
+#include <base/file_path.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/glib.h"
+#include "shill/store_interface.h"
+
+namespace shill {
+
+// A key file store implementation of the store interface. See
+// http://www.gtk.org/api/2.6/glib/glib-Key-value-file-parser.html for details
+// of the key file format.
+class KeyFileStore : public StoreInterface {
+ public:
+  KeyFileStore(GLib *glib);
+  virtual ~KeyFileStore();
+
+  void set_path(const FilePath &path) { path_ = path; }
+  const FilePath &path() const { return path_; }
+
+  // Inherited from StoreInterface.
+  virtual bool Open();
+  virtual bool Close();
+  virtual std::set<std::string> GetGroups();
+  virtual bool ContainsGroup(const std::string &group);
+  virtual bool DeleteKey(const std::string &group, const std::string &key);
+  virtual bool DeleteGroup(const std::string &group);
+  virtual bool GetString(const std::string &group,
+                         const std::string &key,
+                         std::string *value);
+  virtual bool SetString(const std::string &group,
+                         const std::string &key,
+                         const std::string &value);
+  virtual bool GetBool(const std::string &group,
+                       const std::string &key,
+                       bool *value);
+  virtual bool SetBool(const std::string &group,
+                       const std::string &key,
+                       bool value);
+  virtual bool GetInt(const std::string &group,
+                      const std::string &key,
+                      int *value);
+  virtual bool SetInt(const std::string &group,
+                      const std::string &key,
+                      int value);
+
+ private:
+  FRIEND_TEST(KeyFileStoreTest, OpenClose);
+  FRIEND_TEST(KeyFileStoreTest, OpenFail);
+
+  void ReleaseKeyFile();
+
+  GLib *glib_;
+  GKeyFile *key_file_;
+  FilePath path_;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_KEY_FILE_STORE_
diff --git a/key_file_store_unittest.cc b/key_file_store_unittest.cc
new file mode 100644
index 0000000..9e52558
--- /dev/null
+++ b/key_file_store_unittest.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2011 The Chromium OS 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/file_util.h>
+#include <base/stl_util-inl.h>
+#include <base/stringprintf.h>
+#include <base/memory/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "shill/key_file_store.h"
+
+using std::set;
+using std::string;
+using testing::Test;
+
+namespace shill {
+
+class KeyFileStoreTest : public Test {
+ public:
+  KeyFileStoreTest() : store_(&glib_) {}
+
+  virtual void SetUp() {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    store_.set_path(temp_dir_.path().Append("test-key-file-store"));
+  }
+
+  virtual void TearDown() {
+    store_.set_path(FilePath(""));  // Don't try to save the store.
+    ASSERT_TRUE(temp_dir_.Delete());
+  }
+
+ protected:
+  string ReadKeyFile();
+  void WriteKeyFile(string data);
+
+  GLib glib_;  // Use real GLib for testing KeyFileStore.
+  ScopedTempDir temp_dir_;
+  KeyFileStore store_;
+};
+
+string KeyFileStoreTest::ReadKeyFile() {
+  string data;
+  EXPECT_TRUE(file_util::ReadFileToString(store_.path(), &data));
+  return data;
+}
+
+void KeyFileStoreTest::WriteKeyFile(string data) {
+  EXPECT_EQ(data.size(),
+            file_util::WriteFile(store_.path(), data.data(), data.size()));
+}
+
+TEST_F(KeyFileStoreTest, OpenClose) {
+  EXPECT_FALSE(store_.key_file_);
+
+  ASSERT_TRUE(store_.Open());
+  EXPECT_TRUE(store_.key_file_);
+  ASSERT_TRUE(store_.Close());
+  EXPECT_FALSE(store_.key_file_);
+
+  ASSERT_TRUE(store_.Open());
+  EXPECT_TRUE(store_.key_file_);
+  ASSERT_TRUE(store_.Close());
+  EXPECT_FALSE(store_.key_file_);
+
+  ASSERT_TRUE(store_.Open());
+  store_.set_path(FilePath(""));
+  ASSERT_FALSE(store_.Close());
+  EXPECT_FALSE(store_.key_file_);
+}
+
+TEST_F(KeyFileStoreTest, OpenFail) {
+  WriteKeyFile("garbage\n");
+  EXPECT_FALSE(store_.Open());
+  EXPECT_FALSE(store_.key_file_);
+}
+
+TEST_F(KeyFileStoreTest, GetGroups) {
+  static const char kGroupA[] = "g-a";
+  static const char kGroupB[] = "g-b";
+  static const char kGroupC[] = "g-c";
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "[%s]\n"
+                                  "[%s]\n",
+                                  kGroupA, kGroupB, kGroupC));
+  ASSERT_TRUE(store_.Open());
+  set<string> groups = store_.GetGroups();
+  EXPECT_EQ(3, groups.size());
+  EXPECT_TRUE(ContainsKey(groups, kGroupA));
+  EXPECT_TRUE(ContainsKey(groups, kGroupB));
+  EXPECT_TRUE(ContainsKey(groups, kGroupC));
+  EXPECT_FALSE(ContainsKey(groups, "g-x"));
+  ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, ContainsGroup) {
+  static const char kGroupA[] = "group-a";
+  static const char kGroupB[] = "group-b";
+  static const char kGroupC[] = "group-c";
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "[%s]\n"
+                                  "[%s]\n",
+                                  kGroupA, kGroupB, kGroupC));
+  ASSERT_TRUE(store_.Open());
+  EXPECT_TRUE(store_.ContainsGroup(kGroupA));
+  EXPECT_TRUE(store_.ContainsGroup(kGroupB));
+  EXPECT_TRUE(store_.ContainsGroup(kGroupC));
+  EXPECT_FALSE(store_.ContainsGroup("group-d"));
+  ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, DeleteKey) {
+  static const char kGroup[] = "the-group";
+  static const char kKeyDead[] = "dead";
+  static const char kKeyAlive[] = "alive";
+  const int kValueAlive = 3;
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "%s=5\n"
+                                  "%s=%d\n",
+                                  kGroup, kKeyDead, kKeyAlive, kValueAlive));
+  ASSERT_TRUE(store_.Open());
+  EXPECT_TRUE(store_.DeleteKey(kGroup, kKeyDead));
+  EXPECT_FALSE(store_.DeleteKey(kGroup, "random-key"));
+  EXPECT_FALSE(store_.DeleteKey("random-group", kKeyAlive));
+  ASSERT_TRUE(store_.Close());
+  EXPECT_EQ(base::StringPrintf("\n"
+                               "[%s]\n"
+                               "%s=%d\n",
+                               kGroup, kKeyAlive, kValueAlive),
+            ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, DeleteGroup) {
+  static const char kGroupA[] = "group-a";
+  static const char kGroupB[] = "group-b";
+  static const char kGroupC[] = "group-c";
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "[%s]\n"
+                                  "key-to-be-deleted=true\n"
+                                  "[%s]\n",
+                                  kGroupA, kGroupB, kGroupC));
+  ASSERT_TRUE(store_.Open());
+  EXPECT_TRUE(store_.DeleteGroup(kGroupB));
+  EXPECT_FALSE(store_.DeleteGroup("group-d"));
+  ASSERT_TRUE(store_.Close());
+  EXPECT_EQ(base::StringPrintf("\n"
+                               "[%s]\n"
+                               "\n"
+                               "[%s]\n",
+                               kGroupA, kGroupC),
+            ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, GetString) {
+  static const char kGroup[] = "something";
+  static const char kKey[] = "foo";
+  static const char kValue[] = "bar";
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "%s=%s\n",
+                                  kGroup, kKey, kValue));
+  ASSERT_TRUE(store_.Open());
+  string value;
+  EXPECT_TRUE(store_.GetString(kGroup, kKey, &value));
+  EXPECT_EQ(kValue, value);
+  EXPECT_FALSE(store_.GetString("something-else", kKey, &value));
+  EXPECT_FALSE(store_.GetString(kGroup, "bar", &value));
+  EXPECT_TRUE(store_.GetString(kGroup, kKey, NULL));
+  ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, SetString) {
+  static const char kGroup[] = "string-group";
+  static const char kKey1[] = "test-string";
+  static const char kValue1[] = "foo";
+  static const char kKey2[] = "empty-string";
+  static const char kValue2[] = "";
+  ASSERT_TRUE(store_.Open());
+  ASSERT_TRUE(store_.SetString(kGroup, kKey1, kValue1));
+  ASSERT_TRUE(store_.SetString(kGroup, kKey2, kValue2));
+  ASSERT_TRUE(store_.Close());
+  EXPECT_EQ(base::StringPrintf("\n"
+                               "[%s]\n"
+                               "%s=%s\n"
+                               "%s=%s\n",
+                               kGroup, kKey1, kValue1, kKey2, kValue2),
+            ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, GetBool) {
+  static const char kGroup[] = "boo";
+  static const char kKeyTrue[] = "foo";
+  static const char kKeyFalse[] = "bar";
+  static const char kKeyBad[] = "zoo";
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "%s=true\n"
+                                  "%s=false\n"
+                                  "%s=moo\n",
+                                  kGroup, kKeyTrue, kKeyFalse, kKeyBad));
+  ASSERT_TRUE(store_.Open());
+  {
+    bool value = true;
+    EXPECT_TRUE(store_.GetBool(kGroup, kKeyFalse, &value));
+    EXPECT_FALSE(value);
+  }
+  {
+    bool value = false;
+    EXPECT_TRUE(store_.GetBool(kGroup, kKeyTrue, &value));
+    EXPECT_TRUE(value);
+  }
+  {
+    bool value;
+    EXPECT_FALSE(store_.GetBool(kGroup, kKeyBad, &value));
+    EXPECT_FALSE(store_.GetBool(kGroup, "unknown", &value));
+    EXPECT_FALSE(store_.GetBool("unknown", kKeyTrue, &value));
+  }
+  EXPECT_TRUE(store_.GetBool(kGroup, kKeyFalse, NULL));
+  ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, SetBool) {
+  static const char kGroup[] = "bool-group";
+  static const char kKeyTrue[] = "test-true-bool";
+  static const char kKeyFalse[] = "test-false-bool";
+  ASSERT_TRUE(store_.Open());
+  ASSERT_TRUE(store_.SetBool(kGroup, kKeyTrue, true));
+  ASSERT_TRUE(store_.SetBool(kGroup, kKeyFalse, false));
+  ASSERT_TRUE(store_.Close());
+  EXPECT_EQ(base::StringPrintf("\n"
+                               "[%s]\n"
+                               "%s=true\n"
+                               "%s=false\n",
+                               kGroup, kKeyTrue, kKeyFalse),
+            ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, GetInt) {
+  static const char kGroup[] = "numbers";
+  static const char kKeyPos[] = "pos";
+  static const char kKeyNeg[] = "neg";
+  static const char kKeyBad[] = "bad";
+  const int kValuePos = 50;
+  const int kValueNeg = -20;
+  static const char kValueBad[] = "nan";
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "%s=%d\n"
+                                  "%s=%d\n"
+                                  "%s=%s\n",
+                                  kGroup,
+                                  kKeyPos, kValuePos,
+                                  kKeyNeg, kValueNeg,
+                                  kKeyBad, kValueBad));
+  ASSERT_TRUE(store_.Open());
+  {
+    int value = 0;
+    EXPECT_TRUE(store_.GetInt(kGroup, kKeyNeg, &value));
+    EXPECT_EQ(kValueNeg, value);
+  }
+  {
+    int value = 0;
+    EXPECT_TRUE(store_.GetInt(kGroup, kKeyPos, &value));
+    EXPECT_EQ(kValuePos, value);
+  }
+  {
+    int value;
+    EXPECT_FALSE(store_.GetInt(kGroup, kKeyBad, &value));
+    EXPECT_FALSE(store_.GetInt(kGroup, "invalid", &value));
+    EXPECT_FALSE(store_.GetInt("invalid", kKeyPos, &value));
+  }
+  EXPECT_TRUE(store_.GetInt(kGroup, kKeyPos, NULL));
+  ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, SetInt) {
+  static const char kGroup[] = "int-group";
+  static const char kKey1[] = "test-int";
+  static const char kKey2[] = "test-negative";
+  const int kValue1 = 5;
+  const int kValue2 = -10;
+  ASSERT_TRUE(store_.Open());
+  ASSERT_TRUE(store_.SetInt(kGroup, kKey1, kValue1));
+  ASSERT_TRUE(store_.SetInt(kGroup, kKey2, kValue2));
+  ASSERT_TRUE(store_.Close());
+  EXPECT_EQ(base::StringPrintf("\n"
+                               "[%s]\n"
+                               "%s=%d\n"
+                               "%s=%d\n",
+                               kGroup, kKey1, kValue1, kKey2, kValue2),
+            ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, Combo) {
+  static const char kGroupA[] = "square";
+  static const char kGroupB[] = "circle";
+  static const char kGroupC[] = "triangle";
+  static const char kGroupX[] = "pentagon";
+  static const char kKeyString[] = "color";
+  static const char kKeyInt[] = "area";
+  static const char kKeyBool[] = "visible";
+  static const char kValueStringA[] = "blue";
+  static const char kValueStringB[] = "red";
+  static const char kValueStringC[] = "yellow";
+  static const char kValueStringCNew[] = "purple";
+  const int kValueIntA = 5;
+  const int kValueIntB = 10;
+  const int kValueIntBNew = 333;
+  WriteKeyFile(base::StringPrintf("[%s]\n"
+                                  "%s=%s\n"
+                                  "%s=%d\n"
+                                  "[%s]\n"
+                                  "%s=%s\n"
+                                  "%s=%d\n"
+                                  "%s=true\n"
+                                  "[%s]\n"
+                                  "%s=%s\n"
+                                  "%s=false\n",
+                                  kGroupA,
+                                  kKeyString, kValueStringA,
+                                  kKeyInt, kValueIntA,
+                                  kGroupB,
+                                  kKeyString, kValueStringB,
+                                  kKeyInt, kValueIntB,
+                                  kKeyBool,
+                                  kGroupC,
+                                  kKeyString, kValueStringC,
+                                  kKeyBool));
+  ASSERT_TRUE(store_.Open());
+
+  EXPECT_TRUE(store_.ContainsGroup(kGroupA));
+  EXPECT_TRUE(store_.ContainsGroup(kGroupB));
+  EXPECT_TRUE(store_.ContainsGroup(kGroupC));
+  EXPECT_FALSE(store_.ContainsGroup(kGroupX));
+
+  set<string> groups = store_.GetGroups();
+  EXPECT_EQ(3, groups.size());
+  EXPECT_TRUE(ContainsKey(groups, kGroupA));
+  EXPECT_TRUE(ContainsKey(groups, kGroupB));
+  EXPECT_TRUE(ContainsKey(groups, kGroupC));
+  EXPECT_FALSE(ContainsKey(groups, kGroupX));
+
+  {
+    string value;
+    EXPECT_TRUE(store_.GetString(kGroupB, kKeyString, &value));
+    EXPECT_EQ(kValueStringB, value);
+    EXPECT_TRUE(store_.GetString(kGroupA, kKeyString, &value));
+    EXPECT_EQ(kValueStringA, value);
+    EXPECT_TRUE(store_.GetString(kGroupC, kKeyString, &value));
+    EXPECT_EQ(kValueStringC, value);
+  }
+  {
+    int value = 0;
+    EXPECT_TRUE(store_.GetInt(kGroupB, kKeyInt, &value));
+    EXPECT_EQ(kValueIntB, value);
+    EXPECT_TRUE(store_.GetInt(kGroupA, kKeyInt, &value));
+    EXPECT_EQ(kValueIntA, value);
+    EXPECT_FALSE(store_.GetInt(kGroupC, kKeyInt, &value));
+  }
+  {
+    bool value = false;
+    EXPECT_TRUE(store_.GetBool(kGroupB, kKeyBool, &value));
+    EXPECT_TRUE(value);
+    EXPECT_TRUE(store_.GetBool(kGroupC, kKeyBool, &value));
+    EXPECT_FALSE(value);
+    EXPECT_FALSE(store_.GetBool(kGroupA, kKeyBool, &value));
+  }
+
+  EXPECT_TRUE(store_.DeleteGroup(kGroupA));
+  EXPECT_FALSE(store_.DeleteGroup(kGroupA));
+
+  EXPECT_FALSE(store_.ContainsGroup(kGroupA));
+  EXPECT_TRUE(store_.ContainsGroup(kGroupB));
+  EXPECT_TRUE(store_.ContainsGroup(kGroupC));
+
+  groups = store_.GetGroups();
+  EXPECT_EQ(2, groups.size());
+  EXPECT_FALSE(ContainsKey(groups, kGroupA));
+  EXPECT_TRUE(ContainsKey(groups, kGroupB));
+  EXPECT_TRUE(ContainsKey(groups, kGroupC));
+
+  EXPECT_TRUE(store_.SetBool(kGroupB, kKeyBool, false));
+  EXPECT_TRUE(store_.SetInt(kGroupB, kKeyInt, kValueIntBNew));
+  EXPECT_TRUE(store_.SetString(kGroupC, kKeyString, kValueStringCNew));
+
+  EXPECT_TRUE(store_.DeleteKey(kGroupB, kKeyString));
+  EXPECT_FALSE(store_.DeleteKey(kGroupB, kKeyString));
+
+  {
+    string value;
+    EXPECT_FALSE(store_.GetString(kGroupB, kKeyString, &value));
+    EXPECT_FALSE(store_.GetString(kGroupA, kKeyString, &value));
+    EXPECT_TRUE(store_.GetString(kGroupC, kKeyString, &value));
+    EXPECT_EQ(kValueStringCNew, value);
+  }
+
+  {
+    int value = 0;
+    EXPECT_TRUE(store_.GetInt(kGroupB, kKeyInt, &value));
+    EXPECT_EQ(kValueIntBNew, value);
+    EXPECT_FALSE(store_.GetInt(kGroupA, kKeyInt, &value));
+    EXPECT_FALSE(store_.GetInt(kGroupC, kKeyInt, &value));
+  }
+
+  {
+    bool value = false;
+    EXPECT_TRUE(store_.GetBool(kGroupB, kKeyBool, &value));
+    EXPECT_FALSE(value);
+    EXPECT_TRUE(store_.GetBool(kGroupC, kKeyBool, &value));
+    EXPECT_FALSE(value);
+    EXPECT_FALSE(store_.GetBool(kGroupA, kKeyBool, &value));
+  }
+
+  ASSERT_TRUE(store_.Close());
+  EXPECT_EQ(base::StringPrintf("\n"
+                               "[%s]\n"
+                               "%s=%d\n"
+                               "%s=false\n"
+                               "\n"
+                               "[%s]\n"
+                               "%s=%s\n"
+                               "%s=false\n",
+                               kGroupB,
+                               kKeyInt, kValueIntBNew,
+                               kKeyBool,
+                               kGroupC,
+                               kKeyString, kValueStringCNew,
+                               kKeyBool),
+            ReadKeyFile());
+}
+
+}  // namespace shill
diff --git a/mock_glib.h b/mock_glib.h
index 0fb00a4..99229ad 100644
--- a/mock_glib.h
+++ b/mock_glib.h
@@ -16,6 +16,51 @@
   MOCK_METHOD3(ChildWatchAdd, guint(GPid pid,
                                     GChildWatchFunc function,
                                     gpointer data));
+  MOCK_METHOD1(Free, void(gpointer mem));
+  MOCK_METHOD1(KeyFileFree, void(GKeyFile *key_file));
+  MOCK_METHOD4(KeyFileGetBoolean, gboolean(GKeyFile *key_file,
+                                           const gchar *group_name,
+                                           const gchar *key,
+                                           GError **error));
+  MOCK_METHOD2(KeyFileGetGroups, gchar **(GKeyFile *key_file,
+                                          gsize *length));
+  MOCK_METHOD4(KeyFileGetInteger, gint(GKeyFile *key_file,
+                                       const gchar *group_name,
+                                       const gchar *key,
+                                       GError **error));
+  MOCK_METHOD4(KeyFileGetString, gchar *(GKeyFile *key_file,
+                                         const gchar *group_name,
+                                         const gchar *key,
+                                         GError **error));
+  MOCK_METHOD2(KeyFileHasGroup, gboolean(GKeyFile *key_file,
+                                         const gchar *group_name));
+  MOCK_METHOD4(KeyFileLoadFromFile, gboolean(GKeyFile *key_file,
+                                             const gchar *file,
+                                             GKeyFileFlags flags,
+                                             GError **error));
+  MOCK_METHOD0(KeyFileNew, GKeyFile *());
+  MOCK_METHOD3(KeyFileRemoveGroup, void(GKeyFile *key_file,
+                                        const gchar *group_name,
+                                        GError **error));
+  MOCK_METHOD4(KeyFileRemoveKey, void(GKeyFile *key_file,
+                                      const gchar *group_name,
+                                      const gchar *key,
+                                      GError **error));
+  MOCK_METHOD4(KeyFileSetBoolean, void(GKeyFile *key_file,
+                                       const gchar *group_name,
+                                       const gchar *key,
+                                       gboolean value));
+  MOCK_METHOD4(KeyFileSetInteger, void(GKeyFile *key_file,
+                                       const gchar *group_name,
+                                       const gchar *key,
+                                       gint value));
+  MOCK_METHOD4(KeyFileSetString, void(GKeyFile *key_file,
+                                      const gchar *group_name,
+                                      const gchar *key,
+                                      const gchar *string));
+  MOCK_METHOD3(KeyFileToData, gchar *(GKeyFile *key_file,
+                                      gsize *length,
+                                      GError **error));
   MOCK_METHOD1(SourceRemove, gboolean(guint tag));
   MOCK_METHOD8(SpawnAsync, gboolean(const gchar *working_directory,
                                     gchar **argv,
@@ -26,6 +71,7 @@
                                     GPid *child_pid,
                                     GError **error));
   MOCK_METHOD1(SpawnClosePID, void(GPid pid));
+  MOCK_METHOD1(Strfreev, void(gchar **str_array));
 };
 
 }  // namespace shill
diff --git a/store_interface.h b/store_interface.h
new file mode 100644
index 0000000..cd0fbbd
--- /dev/null
+++ b/store_interface.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2011 The Chromium OS 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 SHILL_STORE_INTERFACE_
+#define SHILL_STORE_INTERFACE_
+
+#include <set>
+#include <string>
+
+namespace shill {
+
+// An interface to a persistent store implementation.
+class StoreInterface {
+ public:
+  virtual ~StoreInterface() {}
+
+  // Opens the store. Returns true on success. This method must be invoked
+  // before using any of the getters or setters. This method is not required
+  // complete gracefully if invoked on a store that has been opened already but
+  // not closed yet.
+  virtual bool Open() = 0;
+
+  // Closes the store and flushes it to persistent storage. Returns true on
+  // success. Note that the store is considered closed even if Close returns
+  // false. This method is not required to complete gracefully if invoked on a
+  // store that has not been opened successfully or has been closed already.
+  virtual bool Close() = 0;
+
+  // Returns a set of all groups contained in the store.
+  virtual std::set<std::string> GetGroups() = 0;
+
+  // Returns true if the store contains |group|, false otherwise.
+  virtual bool ContainsGroup(const std::string &group) = 0;
+
+  // Deletes |group|:|key|. Returns true on success.
+  virtual bool DeleteKey(const std::string &group, const std::string &key) = 0;
+
+  // Deletes |group|. Returns true on success.
+  virtual bool DeleteGroup(const std::string &group) = 0;
+
+  // Gets a string |value| associated with |group|:|key|. Returns true on
+  // success and false on failure (including when |group|:|key| is not present
+  // in the store).
+  virtual bool GetString(const std::string &group,
+                         const std::string &key,
+                         std::string *value) = 0;
+
+  // Associates |group|:|key| with a string |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetString(const std::string &group,
+                         const std::string &key,
+                         const std::string &value) = 0;
+
+  // Gets a boolean |value| associated with |group|:|key|. Returns true on
+  // success and false on failure (including when the |group|:|key| is not
+  // present in the store).
+  virtual bool GetBool(const std::string &group,
+                       const std::string &key,
+                       bool *value) = 0;
+
+  // Associates |group|:|key| with a boolean |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetBool(const std::string &group,
+                       const std::string &key,
+                       bool value) = 0;
+
+  // Gets a integer |value| associated with |group|:|key|. Returns true on
+  // success and false on failure (including when the |group|:|key| is not
+  // present in the store).
+  virtual bool GetInt(const std::string &group,
+                      const std::string &key,
+                      int *value) = 0;
+
+  // Associates |group|:|key| with an integer |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetInt(const std::string &group,
+                      const std::string &key,
+                      int value) = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_STORE_INTERFACE_