Check the enrollment in /home/chronos/Local State in addition to the VPD.

This will allow us to migrate the remaining CfMs to the -cfm flavors of
their board images.

BUG=b:157901191
TEST=unit tests, compared behavior with fizz on a Teemo with and without
requisition in VPD, with and without enrollment in Local State JSON.

Cq-Depend: chromium:2239007
Change-Id: I99b05b8530265d4ef4c81472d0be6ba251f7049c
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2242361
Tested-by: Matthew Ziegelbaum <ziegs@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
Commit-Queue: Matthew Ziegelbaum <ziegs@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index c37d5b9..5d2e498 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -212,6 +212,7 @@
     "payload_state.cc",
     "power_manager_chromeos.cc",
     "real_system_state.cc",
+    "requisition_util.cc",
     "shill_proxy.cc",
     "update_attempter.cc",
     "update_boot_flags_action.cc",
@@ -509,6 +510,7 @@
       "payload_generator/squashfs_filesystem_unittest.cc",
       "payload_generator/zip_unittest.cc",
       "payload_state_unittest.cc",
+      "requisition_util_unittest.cc",
       "testrunner.cc",
       "update_attempter_unittest.cc",
       "update_boot_flags_action_unittest.cc",
diff --git a/common/utils.cc b/common/utils.cc
index 644493d..50b45fa 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -910,6 +910,25 @@
   return true;
 }
 
+bool GetVpdValue(string key, string* result) {
+  int exit_code = 0;
+  string value, error;
+  vector<string> cmd = {"vpd_get_value", key};
+  if (!chromeos_update_engine::Subprocess::SynchronousExec(
+          cmd, &exit_code, &value, &error) ||
+      exit_code) {
+    LOG(ERROR) << "Failed to get vpd key for " << value
+               << " with exit code: " << exit_code << " and error: " << error;
+    return false;
+  } else if (!error.empty()) {
+    LOG(INFO) << "vpd_get_value succeeded but with following errors: " << error;
+  }
+
+  base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
+  *result = value;
+  return true;
+}
+
 bool GetBootId(string* boot_id) {
   TEST_AND_RETURN_FALSE(
       base::ReadFileToString(base::FilePath(kBootIdPath), boot_id));
diff --git a/common/utils.h b/common/utils.h
index ee2dce0..b6880ed 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -291,6 +291,10 @@
 // reboot. Returns whether it succeeded getting the boot_id.
 bool GetBootId(std::string* boot_id);
 
+// Gets a string value from the vpd for a given key using the `vpd_get_value`
+// shell command. Returns true on success.
+bool GetVpdValue(std::string key, std::string* result);
+
 // Divide |x| by |y| and round up to the nearest integer.
 constexpr uint64_t DivRoundUp(uint64_t x, uint64_t y) {
   return (x + y - 1) / y;
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index 916b2e5..5c32648 100644
--- a/hardware_chromeos.cc
+++ b/hardware_chromeos.cc
@@ -38,6 +38,9 @@
 #include "update_engine/common/subprocess.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/dbus_connection.h"
+#if USE_CFM
+#include "update_engine/requisition_util.h"
+#endif
 
 using std::string;
 using std::vector;
@@ -81,31 +84,6 @@
 
 const char* kActivePingKey = "first_active_omaha_ping_sent";
 
-#if USE_CFM
-const char* kOemRequisitionKey = "oem_device_requisition";
-#endif
-
-// Gets a string value from the vpd for a given key using the `vpd_get_value`
-// shell command. Returns true on success.
-int GetVpdValue(string key, string* result) {
-  int exit_code = 0;
-  string value, error;
-  vector<string> cmd = {"vpd_get_value", key};
-  if (!chromeos_update_engine::Subprocess::SynchronousExec(
-          cmd, &exit_code, &value, &error) ||
-      exit_code) {
-    LOG(ERROR) << "Failed to get vpd key for " << value
-               << " with exit code: " << exit_code << " and error: " << error;
-    return false;
-  } else if (!error.empty()) {
-    LOG(INFO) << "vpd_get_value succeeded but with following errors: " << error;
-  }
-
-  base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
-  *result = value;
-  return true;
-}
-
 }  // namespace
 
 namespace chromeos_update_engine {
@@ -217,11 +195,12 @@
 }
 
 string HardwareChromeOS::GetDeviceRequisition() const {
-  string requisition;
 #if USE_CFM
-  GetVpdValue(kOemRequisitionKey, &requisition);
+  const char* kLocalStatePath = "/home/chronos/Local State";
+  return ReadDeviceRequisition(base::FilePath(kLocalStatePath));
+#else
+  return "";
 #endif
-  return requisition;
 }
 
 int HardwareChromeOS::GetMinKernelKeyVersion() const {
@@ -346,7 +325,7 @@
 
 bool HardwareChromeOS::GetFirstActiveOmahaPingSent() const {
   string active_ping_str;
-  if (!GetVpdValue(kActivePingKey, &active_ping_str)) {
+  if (!utils::GetVpdValue(kActivePingKey, &active_ping_str)) {
     return false;
   }
 
diff --git a/requisition_util.cc b/requisition_util.cc
new file mode 100644
index 0000000..5445bce
--- /dev/null
+++ b/requisition_util.cc
@@ -0,0 +1,69 @@
+//
+// Copyright (C) 2020 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/requisition_util.h"
+
+#include <memory>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/json/json_file_value_serializer.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+constexpr char kOemRequisitionKey[] = "oem_device_requisition";
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+string ReadDeviceRequisition(const base::FilePath& local_state) {
+  string requisition;
+  bool vpd_retval = utils::GetVpdValue(kOemRequisitionKey, &requisition);
+
+  // Some users manually convert non-CfM hardware at enrollment time, so VPD
+  // value may be missing. So check the Local State JSON as well.
+  if ((requisition.empty() || !vpd_retval) && base::PathExists(local_state)) {
+    int error_code;
+    std::string error_msg;
+    JSONFileValueDeserializer deserializer(local_state);
+    std::unique_ptr<base::Value> root =
+        deserializer.Deserialize(&error_code, &error_msg);
+    if (!root) {
+      if (error_code != 0) {
+        LOG(ERROR) << "Unable to deserialize Local State with exit code: "
+                   << error_code << " and error: " << error_msg;
+      }
+      return "";
+    }
+    auto* path = root->FindPath({"enrollment", "device_requisition"});
+    if (!path || !path->is_string()) {
+      return "";
+    }
+    path->GetAsString(&requisition);
+  }
+  return requisition;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/requisition_util.h b/requisition_util.h
new file mode 100644
index 0000000..8577ee7
--- /dev/null
+++ b/requisition_util.h
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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.
+//
+
+#ifndef UPDATE_ENGINE_REQUISITION_UTIL_H_
+#define UPDATE_ENGINE_REQUISITION_UTIL_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+
+namespace chromeos_update_engine {
+
+// Checks the VPD and Local State for the device's requisition and returns it,
+// or an empty string if the device has no requisition.
+std::string ReadDeviceRequisition(const base::FilePath& local_state);
+
+}  // namespace chromeos_update_engine
+
+#endif  //  UPDATE_ENGINE_REQUISITION_UTIL_H_
diff --git a/requisition_util_unittest.cc b/requisition_util_unittest.cc
new file mode 100644
index 0000000..c21c9c7
--- /dev/null
+++ b/requisition_util_unittest.cc
@@ -0,0 +1,94 @@
+//
+// Copyright (C) 2020 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/requisition_util.h"
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace {
+
+const char kRemoraJSON[] =
+    "{\n"
+    "   \"the_list\": [ \"val1\", \"val2\" ],\n"
+    "   \"enrollment\": {\n"
+    "      \"autostart\": true,\n"
+    "      \"can_exit\": false,\n"
+    "      \"device_requisition\": \"remora\"\n"
+    "   },\n"
+    "   \"some_String\": \"1337\",\n"
+    "   \"some_int\": 42\n"
+    "}\n";
+
+const char kNoEnrollmentJSON[] =
+    "{\n"
+    "   \"the_list\": [ \"val1\", \"val2\" ],\n"
+    "   \"enrollment\": {\n"
+    "      \"autostart\": true,\n"
+    "      \"can_exit\": false,\n"
+    "      \"device_requisition\": \"\"\n"
+    "   },\n"
+    "   \"some_String\": \"1337\",\n"
+    "   \"some_int\": 42\n"
+    "}\n";
+}  // namespace
+
+namespace chromeos_update_engine {
+
+class RequisitionUtilTest : public ::testing::Test {
+ protected:
+  void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); }
+
+  void WriteJsonToFile(const string& json) {
+    path_ =
+        base::FilePath(root_dir_.GetPath().value() + "/chronos/Local State");
+    ASSERT_TRUE(base::CreateDirectory(path_.DirName()));
+    ASSERT_TRUE(WriteFileString(path_.value(), json));
+  }
+
+  base::ScopedTempDir root_dir_;
+  base::FilePath path_;
+};
+
+TEST_F(RequisitionUtilTest, BadJsonReturnsEmpty) {
+  WriteJsonToFile("this isn't JSON");
+  EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, NoFileReturnsEmpty) {
+  EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, EnrollmentRequisition) {
+  WriteJsonToFile(kRemoraJSON);
+  EXPECT_EQ("remora", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, BlankEnrollment) {
+  WriteJsonToFile(kNoEnrollmentJSON);
+  EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+}  // namespace chromeos_update_engine