Parse postinstall parameters from the payload metadata.

Payload v2 includes a description of the post-install command it should
run, while in payload v1 we use the default values. This patch mounts
the partition on the new top-level directory called /postinstall that
should already be created.

Bug: 27177071
TEST=FEATURES=test emerge-link update_engine

Change-Id: Iaedf3b01e5e1ad57c68bd316b4b6e79cbab35bb6
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index f95679c..f490c08 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -790,6 +790,12 @@
     install_part.name = partition.partition_name();
     install_part.run_postinstall =
         partition.has_run_postinstall() && partition.run_postinstall();
+    if (install_part.run_postinstall) {
+      install_part.postinstall_path =
+          (partition.has_postinstall_path() ? partition.postinstall_path()
+                                            : kPostinstallDefaultScript);
+      install_part.filesystem_type = partition.filesystem_type();
+    }
 
     if (partition.has_old_partition_info()) {
       const PartitionInfo& info = partition.old_partition_info();
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index 572ff41..51e85b3 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -59,9 +59,13 @@
 void InstallPlan::Dump() const {
   string partitions_str;
   for (const auto& partition : partitions) {
-    partitions_str += base::StringPrintf(
-        ", part: %s (source_size: %" PRIu64 ", target_size %" PRIu64 ")",
-        partition.name.c_str(), partition.source_size, partition.target_size);
+    partitions_str +=
+        base::StringPrintf(", part: %s (source_size: %" PRIu64
+                           ", target_size %" PRIu64 ", postinst:%s)",
+                           partition.name.c_str(),
+                           partition.source_size,
+                           partition.target_size,
+                           utils::ToString(partition.run_postinstall).c_str());
   }
 
   LOG(INFO) << "InstallPlan: "
@@ -109,7 +113,9 @@
           target_path == that.target_path &&
           target_size == that.target_size &&
           target_hash == that.target_hash &&
-          run_postinstall == that.run_postinstall);
+          run_postinstall == that.run_postinstall &&
+          postinstall_path == that.postinstall_path &&
+          filesystem_type == that.filesystem_type);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index d2f15fa..454dd78 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -90,8 +90,11 @@
     uint64_t target_size{0};
     brillo::Blob target_hash;
 
-    // Whether we should run the postinstall script from this partition.
+    // Whether we should run the postinstall script from this partition and the
+    // postinstall parameters.
     bool run_postinstall{false};
+    std::string postinstall_path;
+    std::string filesystem_type;
   };
   std::vector<Partition> partitions;
 
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index 84ca398..fe468cc 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -23,6 +23,7 @@
 #include <base/bind.h>
 #include <base/files/file_path.h>
 #include <base/files/file_util.h>
+#include <base/strings/string_util.h>
 
 #include "update_engine/common/action_processor.h"
 #include "update_engine/common/boot_control_interface.h"
@@ -34,16 +35,6 @@
 using std::string;
 using std::vector;
 
-namespace {
-// The absolute path to the post install command.
-const char kPostinstallScript[] = "/postinst";
-
-// Path to the binary file used by kPostinstallScript. Used to get and log the
-// file format of the binary to debug issues when the ELF format on the update
-// doesn't match the one on the current system. This path is not executed.
-const char kDebugPostinstallBinaryPath[] = "/usr/bin/cros_installer";
-}
-
 void PostinstallRunnerAction::PerformAction() {
   CHECK(HasInputObject());
   install_plan_ = GetInputObject();
@@ -60,6 +51,11 @@
 }
 
 void PostinstallRunnerAction::PerformPartitionPostinstall() {
+  if (install_plan_.download_url.empty()) {
+    LOG(INFO) << "Skipping post-install during rollback";
+    return CompletePostinstall(ErrorCode::kSuccess);
+  }
+
   // Skip all the partitions that don't have a post-install step.
   while (current_partition_ < install_plan_.partitions.size() &&
          !install_plan_.partitions[current_partition_].run_postinstall) {
@@ -83,38 +79,44 @@
   // Perform post-install for the current_partition_ partition. At this point we
   // need to call CompletePartitionPostinstall to complete the operation and
   // cleanup.
+#ifdef __ANDROID__
+  fs_mount_dir_ = "/postinstall";
+#else   // __ANDROID__
   TEST_AND_RETURN(
-      utils::MakeTempDirectory("au_postint_mount.XXXXXX", &temp_rootfs_dir_));
+      utils::MakeTempDirectory("au_postint_mount.XXXXXX", &fs_mount_dir_));
+#endif  // __ANDROID__
 
-  if (!utils::MountFilesystem(mountable_device, temp_rootfs_dir_, MS_RDONLY)) {
+  string abs_path = base::FilePath(fs_mount_dir_)
+                        .AppendASCII(partition.postinstall_path)
+                        .value();
+  if (!base::StartsWith(
+          abs_path, fs_mount_dir_, base::CompareCase::SENSITIVE)) {
+    LOG(ERROR) << "Invalid relative postinstall path: "
+               << partition.postinstall_path;
+    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
+  }
+
+  if (!utils::MountFilesystem(mountable_device,
+                              fs_mount_dir_,
+                              MS_RDONLY,
+                              partition.filesystem_type)) {
     return CompletePartitionPostinstall(
         1, "Error mounting the device " + mountable_device);
   }
 
-  LOG(INFO) << "Performing postinst (" << kPostinstallScript
-            << ") installed on device " << partition.target_path
+  LOG(INFO) << "Performing postinst (" << partition.postinstall_path << " at "
+            << abs_path << ") installed on device " << partition.target_path
             << " and mountable device " << mountable_device;
 
   // Logs the file format of the postinstall script we are about to run. This
   // will help debug when the postinstall script doesn't match the architecture
   // of our build.
-  LOG(INFO) << "Format file for new " <<  kPostinstallScript << " is: "
-            << utils::GetFileFormat(temp_rootfs_dir_ + kPostinstallScript);
-  LOG(INFO) << "Format file for new " <<  kDebugPostinstallBinaryPath << " is: "
-            << utils::GetFileFormat(
-                temp_rootfs_dir_ + kDebugPostinstallBinaryPath);
+  LOG(INFO) << "Format file for new " << partition.postinstall_path
+            << " is: " << utils::GetFileFormat(abs_path);
 
   // Runs the postinstall script asynchronously to free up the main loop while
   // it's running.
-  vector<string> command;
-  if (!install_plan_.download_url.empty()) {
-    command.push_back(temp_rootfs_dir_ + kPostinstallScript);
-  } else {
-    // TODO(sosa): crbug.com/366207.
-    // If we're doing a rollback, just run our own postinstall.
-    command.push_back(kPostinstallScript);
-  }
-  command.push_back(partition.target_path);
+  vector<string> command = {abs_path, partition.target_path};
   if (!Subprocess::Get().Exec(
           command,
           base::Bind(
@@ -127,11 +129,13 @@
 void PostinstallRunnerAction::CompletePartitionPostinstall(
     int return_code,
     const string& output) {
-  utils::UnmountFilesystem(temp_rootfs_dir_);
-  if (!base::DeleteFile(base::FilePath(temp_rootfs_dir_), false)) {
-    PLOG(WARNING) << "Not removing mountpoint " << temp_rootfs_dir_;
+  utils::UnmountFilesystem(fs_mount_dir_);
+#ifndef ANDROID
+  if (!base::DeleteFile(base::FilePath(fs_mount_dir_), false)) {
+    PLOG(WARNING) << "Not removing temporary mountpoint " << fs_mount_dir_;
   }
-  temp_rootfs_dir_.clear();
+#endif  // !ANDROID
+  fs_mount_dir_.clear();
 
   if (return_code != 0) {
     LOG(ERROR) << "Postinst command failed with code: " << return_code;
diff --git a/payload_consumer/postinstall_runner_action.h b/payload_consumer/postinstall_runner_action.h
index ab267b8..b4defae 100644
--- a/payload_consumer/postinstall_runner_action.h
+++ b/payload_consumer/postinstall_runner_action.h
@@ -63,7 +63,9 @@
   void CompletePostinstall(ErrorCode error_code);
 
   InstallPlan install_plan_;
-  std::string temp_rootfs_dir_;
+
+  // The path where the filesystem will be mounted during post-install.
+  std::string fs_mount_dir_;
 
   // The partition being processed on the list of partitions specified in the
   // InstallPlan.
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index c4c68b1..85535d7 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -191,6 +191,7 @@
   part.name = "part";
   part.target_path = dev;
   part.run_postinstall = true;
+  part.postinstall_path = kPostinstallDefaultScript;
   InstallPlan install_plan;
   install_plan.partitions = {part};
   install_plan.download_url = "http://devserver:8080/update";
@@ -215,7 +216,7 @@
   EXPECT_TRUE(delegate.code_set_);
   EXPECT_EQ(should_succeed, delegate.code_ == ErrorCode::kSuccess);
   if (should_succeed)
-    EXPECT_TRUE(install_plan == collector_action.object());
+    EXPECT_EQ(install_plan, collector_action.object());
 
   const base::FilePath kPowerwashMarkerPath(powerwash_marker_file);
   string actual_cmd;