Added support for generic loopback partition name parsing

Changed SplitPartitionName/MakePartitionName functions to
support generic device partition names where partition number
is separated from the disk name with 'p'. This used to be the
case only for MMC devices (/dev/mmcblk). But now, as long
as the disk name ends with a digit, then the following partition
name is assumed to be separated by 'p'.

Also added special case for NAND devices (/dev/ubiblock) to
be split and merged too, in order to make it possible to
use the split/merge functions with KernelDeviceOfBootDevice().

Updated KernelDeviceOfBootDevice() and GetInstallDev() to use
the SplitPartitionName/MakePartitionName functions.

Finally, updated the unit tests for ubiblock devices and added
unit tests for MakePartitionName function.

BUG=chromium:354494
TEST=Added/updated unit tests. All unit tests pass.

Change-Id: I8a813e13b315d118027bd84f179a0e569d0d4014
Reviewed-on: https://chromium-review.googlesource.com/191785
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Alex Deymo <deymo@chromium.org>
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/utils.cc b/utils.cc
index e379aae..0cde048 100644
--- a/utils.cc
+++ b/utils.cc
@@ -94,33 +94,26 @@
 }
 
 
-const string KernelDeviceOfBootDevice(const std::string& boot_device) {
-  if (boot_device.empty())
-    return boot_device;
+const string KernelDeviceOfBootDevice(const string& boot_device) {
+  string kernel_partition_name;
 
-  string ubiblock_prefix("/dev/ubiblock");
-  string ret;
-  char partition_num;
-  if (StringHasPrefix(boot_device, ubiblock_prefix)) {
-    // eg: /dev/ubiblock3_0 becomes /dev/mtdblock2
-    ret = "/dev/mtdblock";
-    partition_num = boot_device[ubiblock_prefix.size()];
-  } else {
-    // eg: /dev/sda3 becomes /dev/sda2
-    // eg: /dev/mmcblk0p3 becomes /dev/mmcblk0p2
-    ret = boot_device.substr(0, boot_device.size() - 1);
-    partition_num = boot_device[boot_device.size() - 1];
+  string disk_name;
+  int partition_num;
+  if (SplitPartitionName(boot_device, &disk_name, &partition_num)) {
+    if (disk_name == "/dev/ubiblock") {
+      // Special case for NAND devices.
+      // eg: /dev/ubiblock3_0 becomes /dev/mtdblock2
+      disk_name = "/dev/mtdblock";
+    }
+    // Currently this assumes the partition number of the boot device is
+    // 3, 5, or 7, and changes it to 2, 4, or 6, respectively, to
+    // get the kernel device.
+    if (partition_num == 3 || partition_num == 5 || partition_num == 7) {
+      kernel_partition_name = MakePartitionName(disk_name, partition_num - 1);
+    }
   }
 
-  // Currently this assumes the partition number of the boot device is
-  // 3, 5, or 7, and changes it to 2, 4, or 6, respectively, to
-  // get the kernel device.
-  if (partition_num == '3' || partition_num == '5' || partition_num == '7') {
-    ret.append(1, partition_num - 1);
-    return ret;
-  }
-
-  return "";
+  return kernel_partition_name;
 }
 
 
@@ -395,32 +388,44 @@
     return false;
   }
 
-  if (StringHasPrefix(partition_name, "/dev/ubiblock")) {
-    // NAND block devices have weird naming which could be something
-    // like "/dev/ubiblock2_0". Since update engine doesn't have proper
-    // support for NAND devices yet, don't bother parsing their names for now.
-    LOG(ERROR) << "UBI block devices are not supported: " << partition_name;
+  size_t last_nondigit_pos = partition_name.find_last_not_of("0123456789");
+  if (last_nondigit_pos == string::npos ||
+      (last_nondigit_pos + 1) == partition_name.size()) {
+    LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
     return false;
   }
 
-  size_t pos = partition_name.find_last_not_of("0123456789");
-  if (pos == string::npos || (pos + 1) == partition_name.size()) {
-    LOG(ERROR) << "Unable to parse partition device name: "
-               << partition_name;
-    return false;
+  size_t partition_name_len = std::string::npos;
+  if (partition_name[last_nondigit_pos] == '_') {
+    // NAND block devices have weird naming which could be something
+    // like "/dev/ubiblock2_0". We discard "_0" in such a case.
+    size_t prev_nondigit_pos =
+        partition_name.find_last_not_of("0123456789", last_nondigit_pos - 1);
+    if (prev_nondigit_pos == string::npos ||
+        (prev_nondigit_pos + 1) == last_nondigit_pos) {
+      LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
+      return false;
+    }
+
+    partition_name_len = last_nondigit_pos - prev_nondigit_pos;
+    last_nondigit_pos = prev_nondigit_pos;
   }
 
   if (out_disk_name) {
     // Special case for MMC devices which have the following naming scheme:
     // mmcblk0p2
-    bool valid_mmc_device = StringHasPrefix(partition_name, "/dev/mmcblk") &&
-                            partition_name[pos] == 'p';
-    *out_disk_name =
-        partition_name.substr(0, valid_mmc_device ? pos : (pos + 1));
+    size_t disk_name_len = last_nondigit_pos;
+    if (partition_name[last_nondigit_pos] != 'p' ||
+        last_nondigit_pos == 0 ||
+        !isdigit(partition_name[last_nondigit_pos - 1])) {
+      disk_name_len++;
+    }
+    *out_disk_name = partition_name.substr(0, disk_name_len);
   }
 
   if (out_partition_num) {
-    std::string partition_str = partition_name.substr(pos + 1);
+    std::string partition_str = partition_name.substr(last_nondigit_pos + 1,
+                                                      partition_name_len);
     *out_partition_num = atoi(partition_str.c_str());
   }
   return true;
@@ -439,13 +444,19 @@
   }
 
   std::string partition_name = disk_name;
-  if (!StringHasPrefix(disk_name, "/dev/mmcblk")) {
-    // Special case for MMC devices. Add "p" to separate the disk name
-    // from partition number
+  if (isdigit(partition_name.back())) {
+    // Special case for devices with names ending with a digit.
+    // Add "p" to separate the disk name from partition number,
+    // e.g. "/dev/loop0p2"
     partition_name += 'p';
   }
 
-  base::StringAppendF(&partition_name, "%d", partition_num);
+  partition_name += std::to_string(partition_num);
+
+  if (StringHasPrefix(partition_name, "/dev/ubiblock")) {
+    // Special case for UBI block devieces that have "_0" suffix.
+    partition_name += "_0";
+  }
   return partition_name;
 }
 
@@ -1309,23 +1320,22 @@
 }
 
 bool GetInstallDev(const std::string& boot_dev, std::string* install_dev) {
-  TEST_AND_RETURN_FALSE(StringHasPrefix(boot_dev, "/dev/"));
-  string::iterator it;
-  string ubiblock_prefix("/dev/ubiblock");
-
-  install_dev->assign(boot_dev);
-
-  if(StringHasPrefix(boot_dev, ubiblock_prefix)) {
-    // UBI-based device
-    it = install_dev->begin() + ubiblock_prefix.length();
-  } else {
-    // non-UBI device
-    it = install_dev->end() - 1;  // last character in string
-  }
+  std::string disk_name;
+  int partition_num;
+  if (!SplitPartitionName(boot_dev, &disk_name, &partition_num))
+    return false;
 
   // Right now, we just switch '3' and '5' partition numbers.
-  TEST_AND_RETURN_FALSE(*it == '3' || *it == '5');
-  *it = (*it == '3' ? '5' : '3');
+  if (partition_num == 3) {
+    partition_num = 5;
+  } else if (partition_num == 5) {
+    partition_num = 3;
+  } else {
+    return false;
+  }
+
+  if (install_dev)
+    *install_dev = MakePartitionName(disk_name, partition_num);
 
   return true;
 }