Extract the elf image from a compressed kernel image.

If the --decompress_kernel flag is given to the launcher, the kernel
image will be decompressed and stored in ${runtime_dir}/vmlinux. This
is the default (and required) behavior for crosvm.

Bug: 122978436
Test: build & run locally
Change-Id: I47e70d4cae20a398bc46772c3f5cb0dd9e3b5dcc
diff --git a/host/commands/launch/flags.cc b/host/commands/launch/flags.cc
index 5b67f2b..76cb1c8 100644
--- a/host/commands/launch/flags.cc
+++ b/host/commands/launch/flags.cc
@@ -43,6 +43,11 @@
 DEFINE_int32(num_screen_buffers, 3, "The number of screen buffers");
 DEFINE_string(kernel_path, "",
               "Path to the kernel. Overrides the one from the boot image");
+DEFINE_bool(decompress_kernel, false,
+            "Whether to decompress the kernel image. Required for crosvm.");
+DEFINE_string(kernel_decompresser_executable,
+              vsoc::DefaultHostArtifactsPath("bin/extract-vmlinux"),
+             "Path to the extract-vmlinux executable.");
 DEFINE_string(extra_kernel_cmdline, "",
               "Additional flags to put on the kernel command line");
 DEFINE_int32(loop_max_part, 7, "Maximum number of loop partitions");
@@ -245,6 +250,11 @@
         tmp_config_obj.PerInstancePath("kernel"));
     tmp_config_obj.set_use_unpacked_kernel(true);
   }
+  tmp_config_obj.set_decompress_kernel(FLAGS_decompress_kernel);
+  if (FLAGS_decompress_kernel) {
+    tmp_config_obj.set_decompressed_kernel_image_path(
+        tmp_config_obj.PerInstancePath("vmlinux"));
+  }
 
   auto ramdisk_path = tmp_config_obj.PerInstancePath("ramdisk.img");
   bool use_ramdisk = boot_image_unpacker.HasRamdiskImage();
@@ -430,6 +440,8 @@
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
   SetCommandLineOptionWithMode("adb_mode", "tunnel",
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
+  SetCommandLineOptionWithMode("decompress_kernel", "false",
+                               google::FlagSettingMode::SET_FLAGS_DEFAULT);
 }
 
 void SetDefaultFlagsForCrosvm() {
@@ -459,6 +471,8 @@
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
   SetCommandLineOptionWithMode("adb_mode", "vsock_tunnel",
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
+  SetCommandLineOptionWithMode("decompress_kernel", "true",
+                               google::FlagSettingMode::SET_FLAGS_DEFAULT);
 }
 
 bool ParseCommandLineFlags(int* argc, char*** argv) {
@@ -513,6 +527,20 @@
   }
   return true;
 }
+
+bool DecompressKernel(const std::string& src, const std::string& dst) {
+  cvd::Command decomp_cmd(FLAGS_kernel_decompresser_executable);
+  decomp_cmd.AddParameter(src);
+  auto output_file = cvd::SharedFD::Creat(dst.c_str(), 0666);
+  if (!output_file->IsOpen()) {
+    LOG(ERROR) << "Unable to create decompressed image file: "
+               << output_file->StrError();
+    return false;
+  }
+  decomp_cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdOut, output_file);
+  auto decomp_proc = decomp_cmd.Start(false);
+  return decomp_proc.Started() && decomp_proc.Wait() == 0;
+}
 } // namespace
 
 vsoc::CuttlefishConfig* InitFilesystemAndCreateConfig(int* argc, char*** argv) {
@@ -563,6 +591,14 @@
     exit(LauncherExitCodes::kBootImageUnpackError);
   }
 
+  if (config->decompress_kernel()) {
+    if (!DecompressKernel(config->kernel_image_path(),
+        config->decompressed_kernel_image_path())) {
+      LOG(ERROR) << "Failed to decompress kernel";
+      exit(LauncherExitCodes::kKernelDecompressError);
+    }
+  }
+
   ValidateAdbModeFlag(*config);
 
   // Create data if necessary
diff --git a/host/commands/launch/launcher_defs.h b/host/commands/launch/launcher_defs.h
index 1f52497..ccf859c 100644
--- a/host/commands/launch/launcher_defs.h
+++ b/host/commands/launch/launcher_defs.h
@@ -35,6 +35,7 @@
   kServerError = 14,
   kUsbV1SocketError = 15,
   kE2eTestFailed = 16,
+  kKernelDecompressError = 17,
 };
 
 // Actions supported by the launcher server
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 0a3be9c..dc923e1 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -82,6 +82,8 @@
 
 const char* kKernelImagePath = "kernel_image_path";
 const char* kUseUnpackedKernel = "use_unpacked_kernel";
+const char* kDecompressedKernelImagePath = "decompressed_kernel_image_path";
+const char* kDecompressKernel = "decompress_kernel";
 const char* kGdbFlag = "gdb_flag";
 const char* kKernelCmdline = "kernel_cmdline";
 const char* kRamdiskImagePath = "ramdisk_image_path";
@@ -239,6 +241,21 @@
   (*dictionary_)[kUseUnpackedKernel] = use_unpacked_kernel;
 }
 
+bool CuttlefishConfig::decompress_kernel() const {
+  return (*dictionary_)[kDecompressKernel].asBool();
+}
+void CuttlefishConfig::set_decompress_kernel(bool decompress_kernel) {
+  (*dictionary_)[kDecompressKernel] = decompress_kernel;
+}
+
+std::string CuttlefishConfig::decompressed_kernel_image_path() const {
+  return (*dictionary_)[kDecompressedKernelImagePath].asString();
+}
+void CuttlefishConfig::set_decompressed_kernel_image_path(
+    const std::string& path) {
+  SetPath(kDecompressedKernelImagePath, path);
+}
+
 std::string CuttlefishConfig::gdb_flag() const {
   return (*dictionary_)[kGdbFlag].asString();
 }
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 300250e..71aff38 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -84,9 +84,27 @@
   int refresh_rate_hz() const;
   void set_refresh_rate_hz(int refresh_rate_hz);
 
+  // Returns kernel image extracted from the boot image or the user-provided one
+  // if given by command line to the launcher. This function should not be used
+  // to get the kernel image the vmm should boot, GetKernelImageToUse() should
+  // be used instead.
   std::string kernel_image_path() const;
   void set_kernel_image_path(const std::string& kernel_image_path);
 
+  bool decompress_kernel() const;
+  void set_decompress_kernel(bool decompress_kernel);
+
+  // Returns the path to the kernel image that should be given to the vm manager
+  // to boot, takes into account whether the original image was decompressed or
+  // not.
+  std::string GetKernelImageToUse() const {
+    return decompress_kernel() ? decompressed_kernel_image_path()
+                               : kernel_image_path();
+  }
+
+  std::string decompressed_kernel_image_path() const;
+  void set_decompressed_kernel_image_path(const std::string& path);
+
   bool use_unpacked_kernel() const;
   void set_use_unpacked_kernel(bool use_unpacked_kernel);
 
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 78286f9..ba09257 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -91,7 +91,7 @@
   }
 
   // This needs to be the last parameter
-  command.AddParameter(config_->kernel_image_path());
+  command.AddParameter(config_->GetKernelImageToUse());
 
   return command;
 }
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index 4a64a11..751c68c 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -66,7 +66,7 @@
   LogAndSetEnv("cpus", std::to_string(config_->cpus()));
   LogAndSetEnv("uuid", config_->uuid());
   LogAndSetEnv("monitor_path", GetMonitorPath(config_));
-  LogAndSetEnv("kernel_image_path", config_->kernel_image_path());
+  LogAndSetEnv("kernel_image_path", config_->GetKernelImageToUse());
   LogAndSetEnv("gdb_flag", config_->gdb_flag());
   LogAndSetEnv("ramdisk_image_path", config_->ramdisk_image_path());
   LogAndSetEnv("kernel_cmdline", config_->kernel_cmdline_as_string());