update_engine: Change DLC metadata path

Change the location of the DLC metadata from /var/lib/dlc to
/var/lib/update_engine/dlc_prefs/ to make update_engine the owner of
metadata.

BUG=chromium:912666
TEST=cros_workon_make update_engine --test
TEST=install and uninstall DLCs on DUT. Check new prefs path.

Change-Id: I75f5506eee1abc834ad89a7cf363f42e384b695b
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2140007
Tested-by: Andrew Lassalle <andrewlassalle@chromium.org>
Commit-Queue: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/common/constants.cc b/common/constants.cc
index 793ce97..25aa9a8 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -18,8 +18,7 @@
 
 namespace chromeos_update_engine {
 
-// TODO(andrewlassalle): Move this to the prefs directory.
-const char kDlcMetadataRootpath[] = "/var/lib/dlc/";
+const char kDlcPrefsSubDir[] = "dlc";
 
 const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
 
diff --git a/common/constants.h b/common/constants.h
index 44b20b0..67519bd 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -19,9 +19,8 @@
 
 namespace chromeos_update_engine {
 
-// The root path of all DLC modules metadata.
-// Keep this in sync with the one in dlcservice.
-extern const char kDlcMetadataRootpath[];
+// The root path of all DLC metadata.
+extern const char kDlcPrefsSubDir[];
 
 // Directory for AU prefs that are preserved across powerwash.
 extern const char kPowerwashSafePrefsSubDirectory[];
diff --git a/common/prefs.cc b/common/prefs.cc
index 6d86a50..6a33037 100644
--- a/common/prefs.cc
+++ b/common/prefs.cc
@@ -18,9 +18,11 @@
 
 #include <algorithm>
 
+#include <base/files/file_enumerator.h>
 #include <base/files/file_util.h>
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
 #include <base/strings/string_util.h>
 
 #include "update_engine/common/utils.h"
@@ -29,6 +31,8 @@
 
 namespace chromeos_update_engine {
 
+const char kKeySeparator = '/';
+
 bool PrefsBase::GetString(const string& key, string* value) const {
   return storage_->GetKey(key, value);
 }
@@ -104,6 +108,13 @@
     observers_for_key.erase(observer_it);
 }
 
+string PrefsInterface::CreateSubKey(const string& name_space,
+                                    const string& sub_pref,
+                                    const string& key) {
+  return base::JoinString({name_space, sub_pref, key},
+                          string(1, kKeySeparator));
+}
+
 // Prefs
 
 bool Prefs::Init(const base::FilePath& prefs_dir) {
@@ -112,6 +123,24 @@
 
 bool Prefs::FileStorage::Init(const base::FilePath& prefs_dir) {
   prefs_dir_ = prefs_dir;
+  // Delete empty directories. Ignore errors when deleting empty directories.
+  base::FileEnumerator namespace_enum(
+      prefs_dir_, false /* recursive */, base::FileEnumerator::DIRECTORIES);
+  for (base::FilePath namespace_path = namespace_enum.Next();
+       !namespace_path.empty();
+       namespace_path = namespace_enum.Next()) {
+    base::FileEnumerator sub_pref_enum(namespace_path,
+                                       false /* recursive */,
+                                       base::FileEnumerator::DIRECTORIES);
+    for (base::FilePath sub_pref_path = sub_pref_enum.Next();
+         !sub_pref_path.empty();
+         sub_pref_path = sub_pref_enum.Next()) {
+      if (base::IsDirectoryEmpty(sub_pref_path))
+        base::DeleteFile(sub_pref_path, false);
+    }
+    if (base::IsDirectoryEmpty(namespace_path))
+      base::DeleteFile(namespace_path, false);
+  }
   return true;
 }
 
@@ -146,7 +175,7 @@
 bool Prefs::FileStorage::DeleteKey(const string& key) {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
-  TEST_AND_RETURN_FALSE(base::DeleteFile(filename, false));
+  TEST_AND_RETURN_FALSE(base::DeleteFile(filename, true));
   return true;
 }
 
@@ -157,7 +186,7 @@
   for (size_t i = 0; i < key.size(); ++i) {
     char c = key.at(i);
     TEST_AND_RETURN_FALSE(base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) ||
-                          c == '_' || c == '-');
+                          c == '_' || c == '-' || c == kKeySeparator);
   }
   *filename = prefs_dir_.Append(key);
   return true;
diff --git a/common/prefs_interface.h b/common/prefs_interface.h
index 03ae3ec..b559697 100644
--- a/common/prefs_interface.h
+++ b/common/prefs_interface.h
@@ -79,6 +79,11 @@
   // this key. Calling with non-existent keys does nothing.
   virtual bool Delete(const std::string& key) = 0;
 
+  // Creates a key which is part of a sub preference.
+  static std::string CreateSubKey(const std::string& name_space,
+                                  const std::string& sub_pref,
+                                  const std::string& key);
+
   // Add an observer to watch whenever the given |key| is modified. The
   // OnPrefSet() and OnPrefDelete() methods will be called whenever any of the
   // Set*() methods or the Delete() method are called on the given key,
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
index 3f29319..f226949 100644
--- a/common/prefs_unittest.cc
+++ b/common/prefs_unittest.cc
@@ -31,6 +31,7 @@
 
 using std::string;
 using testing::_;
+using testing::ElementsAre;
 using testing::Eq;
 
 namespace {
@@ -59,6 +60,21 @@
   Prefs prefs_;
 };
 
+TEST(Prefs, Init) {
+  Prefs prefs;
+  const string name_space = "ns";
+  const string sub_pref = "sp";
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  base::FilePath namespace_path = temp_dir.GetPath().Append(name_space);
+
+  EXPECT_TRUE(base::CreateDirectory(namespace_path.Append(sub_pref)));
+  EXPECT_TRUE(base::PathExists(namespace_path.Append(sub_pref)));
+  ASSERT_TRUE(prefs.Init(temp_dir.GetPath()));
+  EXPECT_FALSE(base::PathExists(namespace_path));
+}
+
 TEST_F(PrefsTest, GetFileNameForKey) {
   const char kAllvalidCharsKey[] =
       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-";
@@ -77,6 +93,18 @@
   EXPECT_FALSE(prefs_.file_storage_.GetFileNameForKey("", &path));
 }
 
+TEST_F(PrefsTest, CreateSubKey) {
+  const string name_space = "ns";
+  const string sub_pref1 = "sp1";
+  const string sub_pref2 = "sp2";
+  const string sub_key = "sk";
+
+  EXPECT_EQ(PrefsInterface::CreateSubKey(name_space, sub_pref1, sub_key),
+            "ns/sp1/sk");
+  EXPECT_EQ(PrefsInterface::CreateSubKey(name_space, sub_pref2, sub_key),
+            "ns/sp2/sk");
+}
+
 TEST_F(PrefsTest, GetString) {
   const string test_data = "test data";
   ASSERT_TRUE(SetValue(kKey, test_data));
@@ -279,6 +307,29 @@
   EXPECT_FALSE(prefs_.Exists(kKey));
 }
 
+TEST_F(PrefsTest, SetDeleteSubKey) {
+  const string name_space = "ns";
+  const string sub_pref = "sp";
+  const string sub_key1 = "sk1";
+  const string sub_key2 = "sk2";
+  auto key1 = prefs_.CreateSubKey(name_space, sub_pref, sub_key1);
+  auto key2 = prefs_.CreateSubKey(name_space, sub_pref, sub_key2);
+  base::FilePath sub_pref_path = prefs_dir_.Append(name_space).Append(sub_pref);
+
+  ASSERT_TRUE(prefs_.SetInt64(key1, 0));
+  ASSERT_TRUE(prefs_.SetInt64(key2, 0));
+  EXPECT_TRUE(base::PathExists(sub_pref_path.Append(sub_key1)));
+  EXPECT_TRUE(base::PathExists(sub_pref_path.Append(sub_key2)));
+
+  ASSERT_TRUE(prefs_.Delete(key1));
+  EXPECT_FALSE(base::PathExists(sub_pref_path.Append(sub_key1)));
+  EXPECT_TRUE(base::PathExists(sub_pref_path.Append(sub_key2)));
+  ASSERT_TRUE(prefs_.Delete(key2));
+  EXPECT_FALSE(base::PathExists(sub_pref_path.Append(sub_key2)));
+  prefs_.Init(prefs_dir_);
+  EXPECT_FALSE(base::PathExists(prefs_dir_.Append(name_space)));
+}
+
 class MockPrefsObserver : public PrefsInterface::ObserverInterface {
  public:
   MOCK_METHOD1(OnPrefSet, void(const string&));
@@ -299,6 +350,19 @@
   prefs_.Delete(kKey);
   testing::Mock::VerifyAndClearExpectations(&mock_obserser);
 
+  auto key1 = prefs_.CreateSubKey("ns", "sp1", "key1");
+  prefs_.AddObserver(key1, &mock_obserser);
+
+  EXPECT_CALL(mock_obserser, OnPrefSet(key1));
+  EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+  prefs_.SetString(key1, "value");
+  testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
+  EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+  EXPECT_CALL(mock_obserser, OnPrefDeleted(Eq(key1)));
+  prefs_.Delete(key1);
+  testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
   prefs_.RemoveObserver(kKey, &mock_obserser);
 }
 
@@ -359,6 +423,11 @@
   EXPECT_TRUE(prefs_.Delete(kKey));
   EXPECT_FALSE(prefs_.Exists(kKey));
   EXPECT_TRUE(prefs_.Delete(kKey));
+
+  auto key = prefs_.CreateSubKey("ns", "sp", "sk");
+  ASSERT_TRUE(prefs_.SetInt64(key, 0));
+  EXPECT_TRUE(prefs_.Exists(key));
+  EXPECT_TRUE(prefs_.Delete(kKey));
 }
 
 }  // namespace chromeos_update_engine