Launcher unpacks boot image.

The extraction was being performed by crun on glinux and
fetch_artifacts on gce, so it needs to be done in the launcher to stop
depending on crun.

fetch_artifacts should continue to unpack boot images on gce to ensure
the host packages remain capable of booting older builds.

Bug: 110478603
Test: local and gce
Change-Id: I0b925e700ef8b1693f7dc942bf3fa42b9f4daff8
diff --git a/host/commands/launch/Android.bp b/host/commands/launch/Android.bp
index c39aada..9d8a8e4 100644
--- a/host/commands/launch/Android.bp
+++ b/host/commands/launch/Android.bp
@@ -21,6 +21,7 @@
         "ril_region_handler.cc",
         "vsoc_shared_memory.cc",
         "wifi_region_handler.cc",
+        "boot_image_unpacker.cc",
     ],
     header_libs: [
         "cuttlefish_glog",
diff --git a/host/commands/launch/boot_image_unpacker.cc b/host/commands/launch/boot_image_unpacker.cc
new file mode 100644
index 0000000..adc6c5b
--- /dev/null
+++ b/host/commands/launch/boot_image_unpacker.cc
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018 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 "host/commands/launch/boot_image_unpacker.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <sstream>
+
+#include <glog/logging.h>
+
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/launch/bootimg.h"
+
+namespace cvd {
+
+namespace {
+
+// Extracts size bytes from file, starting at offset bytes from the beginning to
+// path.
+bool ExtractFile(SharedFD source, off_t offset, size_t size,
+                 const std::string& path) {
+  auto dest = SharedFD::Open(path.c_str(), O_CREAT | O_RDWR, 0755);
+  if (!dest->IsOpen()) {
+    LOG(ERROR) << "Unable to open " << path;
+    return false;
+  }
+  auto off = source->LSeek(offset, SEEK_SET);
+  if (off != offset) {
+    LOG(ERROR) << "Failed to lseek: " << source->StrError();
+    return false;
+  }
+  return dest->CopyFrom(*source, size);
+}
+}  // namespace
+
+std::unique_ptr<BootImageUnpacker> BootImageUnpacker::FromImage(
+    const std::string& path) {
+  auto boot_img = SharedFD::Open(path.c_str(), O_RDONLY);
+  if (!boot_img->IsOpen()) {
+    LOG(ERROR) << "Unable to open boot image (" << path
+               << "): " << boot_img->StrError();
+    return nullptr;
+  }
+  boot_img_hdr header;
+  auto bytes_read = boot_img->Read(&header, sizeof(header));
+  if (bytes_read != sizeof(header)) {
+    LOG(ERROR) << "Error reading boot image header";
+    return nullptr;
+  }
+
+  std::ostringstream cmdline;
+  cmdline << reinterpret_cast<char*>(&header.cmdline[0]);
+  if (header.extra_cmdline[0] != '\0') {
+    cmdline << " ";
+    cmdline << reinterpret_cast<char*>(&header.extra_cmdline[0]);
+  }
+
+  uint32_t page_size = header.page_size;
+  // See system/core/mkbootimg/include/mkbootimg/bootimg.h for the origin of
+  // these offset calculations
+  uint32_t kernel_offset = page_size;
+  uint32_t ramdisk_offset =
+      kernel_offset +
+      ((header.kernel_size + page_size - 1) / page_size) * page_size;
+
+  std::unique_ptr<BootImageUnpacker> ret(new BootImageUnpacker(
+      boot_img, cmdline.str(), header.kernel_size, kernel_offset,
+      header.ramdisk_size, ramdisk_offset));
+
+  return ret;
+}
+
+std::string BootImageUnpacker::kernel_command_line() const {
+  return kernel_command_line_;
+}
+
+bool BootImageUnpacker::ExtractKernelImage(const std::string& path) const {
+  if (kernel_image_size_ == 0) return false;
+  return ExtractFile(boot_image_, kernel_image_offset_, kernel_image_size_,
+                     path);
+}
+bool BootImageUnpacker::ExtractRamdiskImage(const std::string& path) const {
+  if (ramdisk_image_size_ == 0) return false;
+  return ExtractFile(boot_image_, ramdisk_image_offset_, ramdisk_image_size_,
+                     path);
+}
+
+}  // namespace cvd
diff --git a/host/commands/launch/boot_image_unpacker.h b/host/commands/launch/boot_image_unpacker.h
new file mode 100644
index 0000000..856c1bc
--- /dev/null
+++ b/host/commands/launch/boot_image_unpacker.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cvd {
+
+// Unpacks the boot image and extracts kernel, ramdisk and kernel arguments
+class BootImageUnpacker {
+ public:
+  // Reads header section of boot image at path and returns a BootImageUnpacker
+  // preloaded with all the metadata.
+  static std::unique_ptr<BootImageUnpacker> FromImage(const std::string& path);
+
+  ~BootImageUnpacker() = default;
+
+  std::string kernel_command_line() const;
+
+  bool HasKernelImage() const { return kernel_image_size_ > 0; }
+  bool HasRamdiskImage() const { return ramdisk_image_size_ > 0; }
+
+  // Extracts the kernel image to the given path
+  bool ExtractKernelImage(const std::string& path) const;
+  // Extracts the ramdisk image to the given path. It may return false if the
+  // boot image does not contain a ramdisk, which is the case when having system
+  // as root.
+  bool ExtractRamdiskImage(const std::string& path) const;
+
+ private:
+  BootImageUnpacker(SharedFD boot_image, const std::string& cmdline,
+                    uint32_t kernel_image_size, uint32_t kernel_image_offset,
+                    uint32_t ramdisk_image_size, uint32_t ramdisk_image_offset)
+      : boot_image_(boot_image),
+        kernel_command_line_(cmdline),
+        kernel_image_size_(kernel_image_size),
+        kernel_image_offset_(kernel_image_offset),
+        ramdisk_image_size_(ramdisk_image_size),
+        ramdisk_image_offset_(ramdisk_image_offset) {}
+
+  // Mutable because we only read from the fd, but do not modify its contents
+  mutable SharedFD boot_image_;
+  std::string kernel_command_line_;
+  // When buidling the boot image a particular page size is assumed, which may
+  // not match the actual page size of the system.
+  uint32_t kernel_image_size_;
+  uint32_t kernel_image_offset_;
+  uint32_t ramdisk_image_size_;
+  uint32_t ramdisk_image_offset_;
+};
+
+}  // namespace cvd
diff --git a/host/commands/launch/bootimg.h b/host/commands/launch/bootimg.h
new file mode 100644
index 0000000..7b8fb28
--- /dev/null
+++ b/host/commands/launch/bootimg.h
@@ -0,0 +1,167 @@
+/* tools/mkbootimg/bootimg.h
+**
+** Copyright 2007, 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.
+*/
+
+// This file is a clone of the one found on system/core/mkbootimg. We've made a
+// clone because the location of the original header has changed over time.
+
+#include <stdint.h>
+
+#ifndef CUTTLEFISH_LAUNCH_BOOT_IMAGE_H_
+#define CUTTLEFISH_LAUNCH_BOOT_IMAGE_H_
+
+#define BOOT_MAGIC "ANDROID!"
+#define BOOT_MAGIC_SIZE 8
+#define BOOT_NAME_SIZE 16
+#define BOOT_ARGS_SIZE 512
+#define BOOT_EXTRA_ARGS_SIZE 1024
+
+#define BOOT_HEADER_VERSION_ZERO 0
+/*
+ *  Bootloader expects the structure of boot_img_hdr with header version
+ *  BOOT_HEADER_VERSION_ZERO to be as follows:
+ */
+struct boot_img_hdr_v0 {
+    uint8_t magic[BOOT_MAGIC_SIZE];
+
+    uint32_t kernel_size; /* size in bytes */
+    uint32_t kernel_addr; /* physical load addr */
+
+    uint32_t ramdisk_size; /* size in bytes */
+    uint32_t ramdisk_addr; /* physical load addr */
+
+    uint32_t second_size; /* size in bytes */
+    uint32_t second_addr; /* physical load addr */
+
+    uint32_t tags_addr; /* physical addr for kernel tags */
+    uint32_t page_size; /* flash page size we assume */
+    /*
+     * version for the boot image header.
+     */
+    uint32_t header_version;
+
+    /* operating system version and security patch level; for
+     * version "A.B.C" and patch level "Y-M-D":
+     * ver = A << 14 | B << 7 | C         (7 bits for each of A, B, C)
+     * lvl = ((Y - 2000) & 127) << 4 | M  (7 bits for Y, 4 bits for M)
+     * os_version = ver << 11 | lvl */
+    uint32_t os_version;
+
+    uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
+
+    uint8_t cmdline[BOOT_ARGS_SIZE];
+
+    uint32_t id[8]; /* timestamp / checksum / sha1 / etc */
+
+    /* Supplemental command line data; kept here to maintain
+     * binary compatibility with older versions of mkbootimg */
+    uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
+} __attribute__((packed));
+
+/*
+ * It is expected that callers would explicitly specify which version of the
+ * boot image header they need to use.
+ */
+typedef struct boot_img_hdr_v0 boot_img_hdr;
+
+/* When a boot header is of version BOOT_HEADER_VERSION_ZERO, the structure of boot image is as
+ * follows:
+ *
+ * +-----------------+
+ * | boot header     | 1 page
+ * +-----------------+
+ * | kernel          | n pages
+ * +-----------------+
+ * | ramdisk         | m pages
+ * +-----------------+
+ * | second stage    | o pages
+ * +-----------------+
+ *
+ * n = (kernel_size + page_size - 1) / page_size
+ * m = (ramdisk_size + page_size - 1) / page_size
+ * o = (second_size + page_size - 1) / page_size
+ *
+ * 0. all entities are page_size aligned in flash
+ * 1. kernel and ramdisk are required (size != 0)
+ * 2. second is optional (second_size == 0 -> no second)
+ * 3. load each element (kernel, ramdisk, second) at
+ *    the specified physical address (kernel_addr, etc)
+ * 4. prepare tags at tag_addr.  kernel_args[] is
+ *    appended to the kernel commandline in the tags.
+ * 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
+ * 6. if second_size != 0: jump to second_addr
+ *    else: jump to kernel_addr
+ */
+
+#define BOOT_HEADER_VERSION_ONE 1
+
+struct boot_img_hdr_v1 : public boot_img_hdr_v0 {
+    uint32_t recovery_dtbo_size;   /* size in bytes for recovery DTBO image */
+    uint64_t recovery_dtbo_offset; /* physical load addr */
+    uint32_t header_size;
+} __attribute__((packed));
+
+/* When the boot image header has a version of BOOT_HEADER_VERSION_ONE, the structure of the boot
+ * image is as follows:
+ *
+ * +-----------------+
+ * | boot header     | 1 page
+ * +-----------------+
+ * | kernel          | n pages
+ * +-----------------+
+ * | ramdisk         | m pages
+ * +-----------------+
+ * | second stage    | o pages
+ * +-----------------+
+ * | recovery dtbo   | p pages
+ * +-----------------+
+ * n = (kernel_size + page_size - 1) / page_size
+ * m = (ramdisk_size + page_size - 1) / page_size
+ * o = (second_size + page_size - 1) / page_size
+ * p = (recovery_dtbo_size + page_size - 1) / page_size
+ *
+ * 0. all entities are page_size aligned in flash
+ * 1. kernel and ramdisk are required (size != 0)
+ * 2. recovery_dtbo is required for recovery.img in non-A/B devices(recovery_dtbo_size != 0)
+ * 3. second is optional (second_size == 0 -> no second)
+ * 4. load each element (kernel, ramdisk, second, recovery_dtbo) at
+ *    the specified physical address (kernel_addr, etc)
+ * 5. prepare tags at tag_addr.  kernel_args[] is
+ *    appended to the kernel commandline in the tags.
+ * 6. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
+ * 7. if second_size != 0: jump to second_addr
+ *    else: jump to kernel_addr
+ */
+
+#if 0
+typedef struct ptentry ptentry;
+
+struct ptentry {
+    char name[16];      /* asciiz partition name    */
+    unsigned start;     /* starting block number    */
+    unsigned length;    /* length in blocks         */
+    unsigned flags;     /* set to zero              */
+};
+
+/* MSM Partition Table ATAG
+**
+** length: 2 + 7 * n
+** atag:   0x4d534d70
+**         <ptentry> x n
+*/
+#endif
+
+#endif
diff --git a/host/commands/launch/main.cc b/host/commands/launch/main.cc
index b41de1f..30b890d 100644
--- a/host/commands/launch/main.cc
+++ b/host/commands/launch/main.cc
@@ -42,6 +42,7 @@
 #include "common/libs/utils/size_utils.h"
 #include "common/vsoc/lib/vsoc_memory.h"
 #include "common/vsoc/shm/screen_layout.h"
+#include "host/commands/launch/boot_image_unpacker.h"
 #include "host/commands/launch/pre_launch_initializers.h"
 #include "host/commands/launch/vsoc_shared_memory.h"
 #include "host/libs/config/cuttlefish_config.h"
@@ -78,10 +79,7 @@
             "Disable DAC security in libvirt. For debug only.");
 DEFINE_string(extra_kernel_command_line, "",
               "Additional flags to put on the kernel command line");
-DEFINE_string(initrd, "", "Location of cuttlefish initrd file.");
-DEFINE_string(kernel, "", "Location of cuttlefish kernel file.");
-DEFINE_string(kernel_command_line, "",
-              "Location of a text file with the kernel command line.");
+DEFINE_string(boot_image, "", "Location of cuttlefish boot image.");
 DEFINE_int32(memory_mb, 2048,
              "Total amount of memory available for guest, MB.");
 std::string g_default_mempath{GetPerInstanceDefault("/var/run/shm/cvd-")};
@@ -432,27 +430,11 @@
 
   // If user did not specify location of either of these files, expect them to
   // be placed in --system_image_dir location.
-  if (FLAGS_kernel.empty()) {
-    FLAGS_kernel = FLAGS_system_image_dir + "/kernel";
-  }
-  if (FLAGS_kernel_command_line.empty()) {
-    FLAGS_kernel_command_line = FLAGS_system_image_dir + "/cmdline";
-  }
   if (FLAGS_system_image.empty()) {
     FLAGS_system_image = FLAGS_system_image_dir + "/system.img";
   }
-  if (FLAGS_initrd.empty()) {
-    FLAGS_initrd = FLAGS_system_image_dir + "/ramdisk.img";
-  }
-  if (!FileHasContent(FLAGS_initrd.c_str())) {
-    FLAGS_initrd.clear();
-  }
-  if (FLAGS_dtb.empty()) {
-    if (FLAGS_initrd.empty()) {
-      FLAGS_dtb = vsoc::DefaultHostArtifactsPath("config/system-root.dtb");
-    } else {
-      FLAGS_dtb = vsoc::DefaultHostArtifactsPath("config/initrd-root.dtb");
-    }
+  if (FLAGS_boot_image.empty()) {
+    FLAGS_boot_image = FLAGS_system_image_dir + "/boot.img";
   }
   if (FLAGS_cache_image.empty()) {
     FLAGS_cache_image = FLAGS_system_image_dir + "/cache.img";
@@ -471,8 +453,8 @@
 
   // Check that the files exist
   for (const auto& file :
-       {FLAGS_system_image, FLAGS_vendor_image, FLAGS_cache_image, FLAGS_kernel,
-        FLAGS_data_image, FLAGS_kernel_command_line}) {
+       {FLAGS_system_image, FLAGS_vendor_image, FLAGS_cache_image,
+        FLAGS_data_image, FLAGS_boot_image}) {
     if (!FileHasContent(file.c_str())) {
       LOG(FATAL) << "File not found: " << file;
       return false;
@@ -481,10 +463,28 @@
   return true;
 }
 
-bool SetUpGlobalConfiguration() {
-  if (!ResolveInstanceFiles()) {
+bool UnpackBootImage(const cvd::BootImageUnpacker& boot_image_unpacker) {
+  auto config = vsoc::CuttlefishConfig::Get();
+  if (boot_image_unpacker.HasRamdiskImage()) {
+    if (!boot_image_unpacker.ExtractRamdiskImage(config->ramdisk_image_path())) {
+      LOG(FATAL) << "Error extracting ramdisk from boot image";
+      return false;
+    }
+  }
+  if (boot_image_unpacker.HasKernelImage()) {
+    if (!boot_image_unpacker.ExtractKernelImage(config->kernel_image_path())) {
+      LOG(FATAL) << "Error extracting kernel from boot image";
+      return false;
+    }
+  } else {
+    LOG(FATAL) << "No kernel found on boot image";
     return false;
   }
+  return true;
+}
+
+bool SetUpGlobalConfiguration(
+    const cvd::BootImageUnpacker& boot_image_unpacker) {
   auto& memory_layout = *vsoc::VSoCMemoryLayout::Get();
   auto config = vsoc::CuttlefishConfig::Get();
   // Set this first so that calls to PerInstancePath below are correct
@@ -503,20 +503,38 @@
   config->set_y_res(FLAGS_y_res);
   config->set_refresh_rate_hz(FLAGS_refresh_rate_hz);
 
-  config->set_kernel_image_path(FLAGS_kernel);
-  std::ostringstream extra_cmdline;
-  if (FLAGS_initrd.empty()) {
-    extra_cmdline << " root=/dev/vda init=/init";
-  }
-  extra_cmdline << " androidboot.serialno=" << FLAGS_serial_number;
-  extra_cmdline << " androidboot.lcd_density=" << FLAGS_dpi;
-  if (FLAGS_extra_kernel_command_line.size()) {
-    extra_cmdline << " " << FLAGS_extra_kernel_command_line;
-  }
-  config->ReadKernelArgs(FLAGS_kernel_command_line.c_str(),
-                         extra_cmdline.str());
+  config->set_kernel_image_path(config->PerInstancePath("kernel"));
 
-  config->set_ramdisk_image_path(FLAGS_initrd);
+  auto ramdisk_path = config->PerInstancePath("ramdisk.img");
+  bool use_ramdisk = boot_image_unpacker.HasRamdiskImage();
+  if (!use_ramdisk) {
+    LOG(INFO) << "No ramdisk present; assuming system-as-root build";
+    ramdisk_path = "";
+  }
+
+  // This needs to be done here because the dtb path depends on the presence of
+  // the ramdisk
+  if (FLAGS_dtb.empty()) {
+    if (use_ramdisk) {
+      FLAGS_dtb = vsoc::DefaultHostArtifactsPath("config/initrd-root.dtb");
+    } else {
+      FLAGS_dtb = vsoc::DefaultHostArtifactsPath("config/system-root.dtb");
+    }
+  }
+
+  std::ostringstream kernel_cmdline;
+  kernel_cmdline << boot_image_unpacker.kernel_command_line();
+  if (!use_ramdisk) {
+    kernel_cmdline << " root=/dev/vda init=/init";
+  }
+  kernel_cmdline << " androidboot.serialno=" << FLAGS_serial_number;
+  kernel_cmdline << " androidboot.lcd_density=" << FLAGS_dpi;
+  if (FLAGS_extra_kernel_command_line.size()) {
+    kernel_cmdline << " " << FLAGS_extra_kernel_command_line;
+  }
+  config->set_kernel_args(kernel_cmdline.str());
+
+  config->set_ramdisk_image_path(ramdisk_path);
   config->set_system_image_path(FLAGS_system_image);
   config->set_cache_image_path(FLAGS_cache_image);
   config->set_data_image_path(FLAGS_data_image);
@@ -626,8 +644,12 @@
   ::android::base::InitLogging(argv, android::base::StderrLogger);
   ParseCommandLineFlags(argc, argv);
 
+  if (!ResolveInstanceFiles()) {
+    return -1;
+  }
+  auto boot_img_unpacker = cvd::BootImageUnpacker::FromImage(FLAGS_boot_image);
   // Do this early so that the config object is ready for anything that needs it
-  if (!SetUpGlobalConfiguration()) {
+  if (!SetUpGlobalConfiguration(*boot_img_unpacker)) {
     return -1;
   }
 
@@ -635,6 +657,11 @@
     LOG(FATAL) << "Failed to clean prior files";
   }
 
+  if (!UnpackBootImage(*boot_img_unpacker)) {
+    LOG(ERROR) << "Failed to unpack boot image";
+    return -1;
+  }
+
   if (!WriteCuttlefishEnvironment()) {
     LOG(ERROR) << "Unable to write cuttlefish environment file";
   }
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 2ef2058..18a17f3 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -414,24 +414,6 @@
   return GetPerInstanceDefault("cvd-");
 }
 
-bool CuttlefishConfig::ReadKernelArgs(const std::string& cmdline_file,
-                                      const std::string& extra_args) {
-  std::ostringstream kernel_args;
-  std::ifstream cmd_stream(cmdline_file);
-  if (!cmd_stream) {
-    LOG(WARNING) << "Unable to open " << cmdline_file;
-    return false;
-  } else {
-    kernel_args << cmd_stream.rdbuf();
-    cmd_stream.close();
-  }
-  if (!extra_args.empty()) {
-    kernel_args << " " << extra_args;
-  }
-  set_kernel_args(kernel_args.str());
-  return true;
-}
-
 int GetInstance() {
   static int instance_id = InstanceFromEnvironment();
   return instance_id;
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 3eb5532..e5f7817 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -44,10 +44,6 @@
     set_usb_v1_socket_name("");
   }
 
-  // Reads the kernel command line from a file and appends extra arguments.
-  bool ReadKernelArgs(const std::string& cmdline_file,
-                      const std::string& extra_args);
-
   std::string instance_dir() const;
   void set_instance_dir(const std::string& instance_dir);