Mark the new kernel invalid when starting an update.

Before we overwrite the new kernel, mark it as unbootable by setting the GPT
flags "successful" and "tries" to 0. This is good, but not critical as a
general behavior because it prevents the firmware from even trying a kernel
we think will be bad.

It's more useful, because it gives us a definitive way to know if the other
kernel is expected to be valid for purposes of things like rollback. There
will be a future CL to use it for preventing rollback to a known invalid
installation.

Also adds a MockHardware implementation backed by the FakeHardware
implementation, and switches MockSystemState to use it.

BUG=chromium:280816
TEST=Manual watching of flags, and multiple updates.
CQ-DEPEND=CL:176177

Change-Id: Idb083279cd1438a555c5165c69b25c351207e382
Reviewed-on: https://chromium-review.googlesource.com/176169
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Tested-by: Don Garrett <dgarrett@chromium.org>
Reviewed-by: David Zeuthen <zeuthen@chromium.org>
Commit-Queue: Don Garrett <dgarrett@chromium.org>
diff --git a/hardware.cc b/hardware.cc
index 6ab0f22..d547c2d 100644
--- a/hardware.cc
+++ b/hardware.cc
@@ -10,6 +10,16 @@
 #include <rootdev/rootdev.h>
 #include <vboot/crossystem.h>
 
+extern "C" {
+#include "vboot/vboot_host.h"
+}
+
+// We don't use these variables, but libcgpt needs them defined to link.
+// TODO(dgarrett) chromium:318536
+const char* progname = "";
+const char* command = "";
+void (*uuid_generator)(uint8_t* buffer) = NULL;
+
 #include "update_engine/subprocess.h"
 #include "update_engine/utils.h"
 
@@ -18,6 +28,10 @@
 
 namespace chromeos_update_engine {
 
+const string Hardware::BootKernelDevice() {
+  return utils::KernelDeviceOfBootDevice(Hardware::BootDevice());
+}
+
 const string Hardware::BootDevice() {
   char boot_path[PATH_MAX];
   // Resolve the boot device path fully, including dereferencing
@@ -35,6 +49,60 @@
   return boot_path;
 }
 
+bool Hardware::IsKernelBootable(const std::string& kernel_device,
+                                bool* bootable) {
+
+  if (kernel_device == BootKernelDevice()) {
+    LOG(ERROR) << "Refusing to mark current kernel as unbootable.";
+    return false;
+  }
+
+  CgptAddParams params;
+  memset(&params, '\0', sizeof(params));
+
+  string root_dev = utils::RootDevice(kernel_device);
+  string partition_number_str = utils::PartitionNumber(kernel_device);
+  uint32_t partition_number = atoi(partition_number_str.c_str());
+
+  params.drive_name = const_cast<char *>(root_dev.c_str());
+  params.partition = partition_number;
+
+  int retval = CgptGetPartitionDetails(&params);
+  if (retval != CGPT_OK)
+    return false;
+
+  *bootable = params.successful || (params.tries > 0);
+  return true;
+}
+
+bool Hardware::MarkKernelUnbootable(const std::string& kernel_device) {
+  LOG(INFO) << "MarkPartitionUnbootable: " << kernel_device;
+
+  string root_dev = utils::RootDevice(kernel_device);
+  string partition_number_str = utils::PartitionNumber(kernel_device);
+  uint32_t partition_number = atoi(partition_number_str.c_str());
+
+  CgptAddParams params;
+  memset(&params, 0, sizeof(params));
+
+  params.drive_name = const_cast<char *>(root_dev.c_str());
+  params.partition = partition_number;
+
+  params.successful = false;
+  params.set_successful = true;
+
+  params.tries = 0;
+  params.set_tries = true;
+
+  int retval = CgptSetAttributes(&params);
+  if (retval != CGPT_OK) {
+    LOG(ERROR) << "Marking kernel unbootable failed.";
+    return false;
+  }
+
+  return true;
+}
+
 bool Hardware::IsOfficialBuild() {
   return VbGetSystemPropertyInt("debug_build") == 0;
 }