update_engine: Add support for DLC packages.

The update_engine can handle downloading multipe apckages per an App
ID. Currently each DLC is tied to an AppID and currently we have only
one image per DLC. This basically restricts our ability to support
multiple images per DLC in the future and if not fixed now, it would be
near impossible to add it in the future.

Currently, in order to find the location of a DLC module, the
location (dlc-id) is inferred from the paritition name structured like
dlc_<dlc-id>. In order to handled multiple packages for each DLC, we can
add another argument dlc-package in the partition name which allows us
to identify different packages. The new format for the partition name is
as follows:
dlc/<dlc-id>/<dlc-package>

BUG=chromium:908994
TEST=unittest
CQ-DEPEND=CL:1532852, CL:1532770, CL:1532851, CL:1531833, CL:1531834

Change-Id: Ie9d03e23b5a44a963ab9a088e66f3d6bbbb9d664
Reviewed-on: https://chromium-review.googlesource.com/1532771
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Xiaochu Liu <xiaochu@chromium.org>
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
index b390f61..3f1eac4 100644
--- a/boot_control_chromeos.cc
+++ b/boot_control_chromeos.cc
@@ -19,10 +19,12 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include <base/bind.h>
 #include <base/files/file_path.h>
 #include <base/files/file_util.h>
+#include <base/strings/string_split.h>
 #include <base/strings/string_util.h>
 #include <chromeos/constants/imageloader.h>
 #include <rootdev/rootdev.h>
@@ -36,6 +38,7 @@
 #include "update_engine/common/utils.h"
 
 using std::string;
+using std::vector;
 
 namespace {
 
@@ -44,7 +47,7 @@
 const char* kAndroidPartitionNameKernel = "boot";
 const char* kAndroidPartitionNameRoot = "system";
 
-const char kPartitionNamePrefixDlc[] = "dlc_";
+const char kPartitionNamePrefixDlc[] = "dlc";
 const char kPartitionNameDlcA[] = "dlc_a";
 const char kPartitionNameDlcB[] = "dlc_b";
 const char kPartitionNameDlcImage[] = "dlc.img";
@@ -145,6 +148,31 @@
   return current_slot_;
 }
 
+bool BootControlChromeOS::ParseDlcPartitionName(
+    const std::string partition_name,
+    std::string* dlc_id,
+    std::string* dlc_package) const {
+  CHECK_NE(dlc_id, nullptr);
+  CHECK_NE(dlc_package, nullptr);
+
+  vector<string> tokens = base::SplitString(
+      partition_name, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (tokens.size() != 3 || tokens[0] != kPartitionNamePrefixDlc) {
+    LOG(ERROR) << "DLC partition name (" << partition_name
+               << ") is not well formatted.";
+    return false;
+  }
+  if (tokens[1].empty() || tokens[2].empty()) {
+    LOG(ERROR) << " partition name does not contain valid DLC ID (" << tokens[1]
+               << ") or package (" << tokens[2] << ")";
+    return false;
+  }
+
+  *dlc_id = tokens[1];
+  *dlc_package = tokens[2];
+  return true;
+}
+
 bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
                                              unsigned int slot,
                                              string* device) const {
@@ -152,17 +180,13 @@
   if (base::StartsWith(partition_name,
                        kPartitionNamePrefixDlc,
                        base::CompareCase::SENSITIVE)) {
-    // Extract DLC module ID from partition_name (DLC module ID is the string
-    // after |kPartitionNamePrefixDlc| in partition_name).
-    const auto dlc_module_id =
-        partition_name.substr(strlen(kPartitionNamePrefixDlc));
-    if (dlc_module_id.empty()) {
-      LOG(ERROR) << " partition name does not contain DLC module ID:"
-                 << partition_name;
+    string dlc_id, dlc_package;
+    if (!ParseDlcPartitionName(partition_name, &dlc_id, &dlc_package))
       return false;
-    }
+
     *device = base::FilePath(imageloader::kDlcImageRootpath)
-                  .Append(dlc_module_id)
+                  .Append(dlc_id)
+                  .Append(dlc_package)
                   .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
                   .Append(kPartitionNameDlcImage)
                   .value();
diff --git a/boot_control_chromeos.h b/boot_control_chromeos.h
index f3682e9..109197f 100644
--- a/boot_control_chromeos.h
+++ b/boot_control_chromeos.h
@@ -59,6 +59,7 @@
   friend class BootControlChromeOSTest;
   FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest);
   FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest);
+  FRIEND_TEST(BootControlChromeOSTest, ParseDlcPartitionNameTest);
 
   // Returns the sysfs block device for a root block device. For example,
   // SysfsBlockDevice("/dev/sda") returns "/sys/block/sda". Returns an empty
@@ -74,6 +75,13 @@
   int GetPartitionNumber(const std::string partition_name,
                          BootControlInterface::Slot slot) const;
 
+  // Extracts DLC module ID and package ID from partition name. The structure of
+  // the partition name is dlc/<dlc-id>/<dlc-package>. For example:
+  // dlc/dummy-dlc/dummy-package
+  bool ParseDlcPartitionName(const std::string partition_name,
+                             std::string* dlc_id,
+                             std::string* dlc_package) const;
+
   // Cached values for GetNumSlots() and GetCurrentSlot().
   BootControlInterface::Slot num_slots_{1};
   BootControlInterface::Slot current_slot_{BootControlInterface::kInvalidSlot};
diff --git a/boot_control_chromeos_unittest.cc b/boot_control_chromeos_unittest.cc
index 6a60009..1c40dce 100644
--- a/boot_control_chromeos_unittest.cc
+++ b/boot_control_chromeos_unittest.cc
@@ -18,6 +18,8 @@
 
 #include <gtest/gtest.h>
 
+using std::string;
+
 namespace chromeos_update_engine {
 
 class BootControlChromeOSTest : public ::testing::Test {
@@ -67,4 +69,22 @@
   EXPECT_EQ(-1, bootctl_.GetPartitionNumber("A little panda", 0));
 }
 
+TEST_F(BootControlChromeOSTest, ParseDlcPartitionNameTest) {
+  string id, package;
+
+  EXPECT_TRUE(bootctl_.ParseDlcPartitionName("dlc/id/package", &id, &package));
+  EXPECT_EQ(id, "id");
+  EXPECT_EQ(package, "package");
+
+  EXPECT_FALSE(
+      bootctl_.ParseDlcPartitionName("dlc-foo/id/package", &id, &package));
+  EXPECT_FALSE(
+      bootctl_.ParseDlcPartitionName("dlc-foo/id/package/", &id, &package));
+  EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc/id", &id, &package));
+  EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc/id/", &id, &package));
+  EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc//package", &id, &package));
+  EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc", &id, &package));
+  EXPECT_FALSE(bootctl_.ParseDlcPartitionName("foo", &id, &package));
+}
+
 }  // namespace chromeos_update_engine