delta_generator: Include postinstall calls in the payload major version 2.

Added a new flag --new_postinstall_config_file which takes a path to a key
value store config file and saves this information to the PartitionUpdate
field.

The config file looks like this:
RUN_POSTINSTALL_root=true
POSTINSTALL_PATH_root=postinstall
FILESYSTEM_TYPE_root=ext4

"root" can be changed to any partition name.

Bug: 24537566
TEST=Generated a payload v2 with postinstall.
TEST=Added unittest.

Change-Id: Ied3c7bc2cfb54f4bcc69207f1e5bd473f72024fe
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index dbb15bc..2ea2f5e 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -28,6 +28,7 @@
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/string_split.h>
 #include <brillo/flag_helper.h>
+#include <brillo/key_value_store.h>
 
 #include "update_engine/common/prefs.h"
 #include "update_engine/common/terminator.h"
@@ -327,6 +328,9 @@
                 "'npo-channel'. Ignored, except during delta generation.");
   DEFINE_string(new_build_version, "",
                 "The version of the build containing the new image.");
+  DEFINE_string(new_postinstall_config_file, "",
+                "A config file specifying postinstall related metadata. "
+                "Only allowed in major version 2 or newer.");
 
   brillo::FlagHelper::Init(argc, argv,
       "Generates a payload to provide to ChromeOS' update_engine.\n\n"
@@ -430,6 +434,14 @@
     }
   }
 
+  if (!FLAGS_new_postinstall_config_file.empty()) {
+    LOG_IF(FATAL, FLAGS_major_version == kChromeOSMajorPayloadVersion)
+        << "Postinstall config is only allowed in major version 2 or newer.";
+    brillo::KeyValueStore store;
+    CHECK(store.Load(base::FilePath(FLAGS_new_postinstall_config_file)));
+    CHECK(payload_config.target.LoadPostInstallConfig(store));
+  }
+
   // Use the default soft_chunk_size defined in the config.
   payload_config.hard_chunk_size = FLAGS_chunk_size;
   payload_config.block_size = kBlockSize;
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index 8b3efa6..d268ab8 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -87,6 +87,7 @@
   Partition part;
   part.name = new_conf.name;
   part.aops = aops;
+  part.postinstall = new_conf.postinstall;
   // Initialize the PartitionInfo objects if present.
   if (!old_conf.path.empty())
     TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(old_conf,
@@ -132,6 +133,13 @@
     if (major_version_ == kBrilloMajorPayloadVersion) {
       PartitionUpdate* partition = manifest_.add_partitions();
       partition->set_partition_name(part.name);
+      if (part.postinstall.run) {
+        partition->set_run_postinstall(true);
+        if (!part.postinstall.path.empty())
+          partition->set_postinstall_path(part.postinstall.path);
+        if (!part.postinstall.filesystem_type.empty())
+          partition->set_filesystem_type(part.postinstall.filesystem_type);
+      }
       for (const AnnotatedOperation& aop : part.aops) {
         *partition->add_operations() = aop.op;
       }
diff --git a/payload_generator/payload_file.h b/payload_generator/payload_file.h
index 51584d6..7cc792a 100644
--- a/payload_generator/payload_file.h
+++ b/payload_generator/payload_file.h
@@ -93,6 +93,8 @@
 
     PartitionInfo old_info;
     PartitionInfo new_info;
+
+    PostInstallConfig postinstall;
   };
 
   std::vector<Partition> part_vec_;
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index 63d1884..813e33c 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -26,6 +26,10 @@
 
 namespace chromeos_update_engine {
 
+bool PostInstallConfig::IsEmpty() const {
+  return run == false && path.empty() && filesystem_type.empty();
+}
+
 bool PartitionConfig::ValidateExists() const {
   TEST_AND_RETURN_FALSE(!path.empty());
   TEST_AND_RETURN_FALSE(utils::FileExists(path.c_str()));
@@ -79,6 +83,26 @@
   return true;
 }
 
+bool ImageConfig::LoadPostInstallConfig(const brillo::KeyValueStore& store) {
+  bool found_postinstall = false;
+  for (PartitionConfig& part : partitions) {
+    bool run_postinstall;
+    if (!store.GetBoolean("RUN_POSTINSTALL_" + part.name, &run_postinstall) ||
+        !run_postinstall)
+      continue;
+    found_postinstall = true;
+    part.postinstall.run = true;
+    store.GetString("POSTINSTALL_PATH_" + part.name, &part.postinstall.path);
+    store.GetString("FILESYSTEM_TYPE_" + part.name,
+                    &part.postinstall.filesystem_type);
+  }
+  if (!found_postinstall) {
+    LOG(ERROR) << "No valid postinstall config found.";
+    return false;
+  }
+  return true;
+}
+
 bool ImageConfig::ImageInfoIsEmpty() const {
   return image_info.board().empty()
     && image_info.key().empty()
@@ -95,6 +119,8 @@
         TEST_AND_RETURN_FALSE(part.ValidateExists());
         TEST_AND_RETURN_FALSE(part.size % block_size == 0);
       }
+      // Source partition should not have postinstall.
+      TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
     }
 
     // Check for the supported minor_version values.
@@ -118,6 +144,8 @@
     if (minor_version == kInPlaceMinorPayloadVersion &&
         part.name == kLegacyPartitionNameRoot)
       TEST_AND_RETURN_FALSE(rootfs_partition_size >= part.size);
+    if (major_version == kChromeOSMajorPayloadVersion)
+      TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
   }
 
   TEST_AND_RETURN_FALSE(hard_chunk_size == -1 ||
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index 8723680..21bb89b 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -23,12 +23,31 @@
 #include <string>
 #include <vector>
 
+#include <brillo/key_value_store.h>
+
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_generator/filesystem_interface.h"
 #include "update_engine/update_metadata.pb.h"
 
 namespace chromeos_update_engine {
 
+struct PostInstallConfig {
+  // Whether the postinstall config is empty.
+  bool IsEmpty() const;
+
+  // Whether this partition carries a filesystem with post-install program that
+  // must be run to finalize the update process.
+  bool run = false;
+
+  // The path to the post-install program relative to the root of this
+  // filesystem.
+  std::string path;
+
+  // The filesystem type used to mount the partition in order to run the
+  // post-install program.
+  std::string filesystem_type;
+};
+
 struct PartitionConfig {
   explicit PartitionConfig(std::string name) : name(name) {}
 
@@ -57,6 +76,8 @@
   std::unique_ptr<FilesystemInterface> fs_interface;
 
   std::string name;
+
+  PostInstallConfig postinstall;
 };
 
 // The ImageConfig struct describes a pair of binaries kernel and rootfs and the
@@ -72,6 +93,9 @@
   // Returns whether the image size was properly detected.
   bool LoadImageSize();
 
+  // Load postinstall config from a key value store.
+  bool LoadPostInstallConfig(const brillo::KeyValueStore& store);
+
   // Returns whether the |image_info| field is empty.
   bool ImageInfoIsEmpty() const;
 
diff --git a/payload_generator/payload_generation_config_unittest.cc b/payload_generator/payload_generation_config_unittest.cc
new file mode 100644
index 0000000..122d94a
--- /dev/null
+++ b/payload_generator/payload_generation_config_unittest.cc
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class PayloadGenerationConfigTest : public ::testing::Test {};
+
+TEST_F(PayloadGenerationConfigTest, SimpleLoadPostInstallConfigTest) {
+  ImageConfig image_config;
+  image_config.partitions.emplace_back("root");
+  brillo::KeyValueStore store;
+  EXPECT_TRUE(
+      store.LoadFromString("RUN_POSTINSTALL_root=true\n"
+                           "POSTINSTALL_PATH_root=postinstall\n"
+                           "FILESYSTEM_TYPE_root=ext4"));
+  EXPECT_TRUE(image_config.LoadPostInstallConfig(store));
+  EXPECT_FALSE(image_config.partitions[0].postinstall.IsEmpty());
+  EXPECT_EQ(true, image_config.partitions[0].postinstall.run);
+  EXPECT_EQ("postinstall", image_config.partitions[0].postinstall.path);
+  EXPECT_EQ("ext4", image_config.partitions[0].postinstall.filesystem_type);
+}
+
+TEST_F(PayloadGenerationConfigTest, LoadPostInstallConfigNameMismatchTest) {
+  ImageConfig image_config;
+  image_config.partitions.emplace_back("system");
+  brillo::KeyValueStore store;
+  EXPECT_TRUE(
+      store.LoadFromString("RUN_POSTINSTALL_root=true\n"
+                           "POSTINSTALL_PATH_root=postinstall\n"
+                           "FILESYSTEM_TYPE_root=ext4"));
+  EXPECT_FALSE(image_config.LoadPostInstallConfig(store));
+  EXPECT_TRUE(image_config.partitions[0].postinstall.IsEmpty());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine.gyp b/update_engine.gyp
index 4254e9a..47727c8 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -486,6 +486,7 @@
             'payload_generator/graph_utils_unittest.cc',
             'payload_generator/inplace_generator_unittest.cc',
             'payload_generator/payload_file_unittest.cc',
+            'payload_generator/payload_generation_config_unittest.cc',
             'payload_generator/payload_signer_unittest.cc',
             'payload_generator/tarjan_unittest.cc',
             'payload_generator/topological_sort_unittest.cc',