Merge "recovery: report compliant reboot reason"
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index e76305f..8d72a11 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -14,10 +14,8 @@
 // limitations under the License.
 //
 
-cc_library {
-    name: "libbootloader_message",
-    recovery_available: true,
-    host_supported: true,
+cc_defaults {
+    name: "libbootloader_message_defaults",
     srcs: ["bootloader_message.cpp"],
     cflags: [
         "-Wall",
@@ -26,24 +24,36 @@
     shared_libs: [
         "libbase",
     ],
+    static_libs: [
+        "libfstab",
+    ],
     export_include_dirs: ["include"],
+}
+
+cc_library {
+    name: "libbootloader_message",
+    defaults: [
+        "libbootloader_message_defaults",
+    ],
+    recovery_available: true,
+    host_supported: true,
 
     target: {
-        android: {
-            shared_libs: [
-                "libfs_mgr",
-            ],
-        },
         host: {
             shared_libs: [
                 "libcutils", // for strlcpy
             ],
-            static_libs: [
-                "libfstab",
-            ],
         },
         darwin: {
             enabled: false,
         },
     }
 }
+
+cc_library_static {
+    name: "libbootloader_message_vendor",
+    defaults: [
+        "libbootloader_message_defaults",
+    ],
+    vendor: true,
+}
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index 331a42b..e684abb 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -21,6 +21,7 @@
 #include <string.h>
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <android-base/file.h>
@@ -36,7 +37,17 @@
 using android::fs_mgr::Fstab;
 using android::fs_mgr::ReadDefaultFstab;
 
+static std::string g_misc_device_for_test;
+
+// Exposed for test purpose.
+void SetMiscBlockDeviceForTest(std::string_view misc_device) {
+  g_misc_device_for_test = misc_device;
+}
+
 static std::string get_misc_blk_device(std::string* err) {
+  if (!g_misc_device_for_test.empty()) {
+    return g_misc_device_for_test;
+  }
   Fstab fstab;
   if (!ReadDefaultFstab(&fstab)) {
     *err = "failed to read default fstab";
@@ -242,6 +253,37 @@
                               WIPE_PACKAGE_OFFSET_IN_MISC, err);
 }
 
+static bool OffsetAndSizeInVendorSpace(size_t offset, size_t size) {
+  auto total_size = WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC;
+  return size <= total_size && offset <= total_size - size;
+}
+
+bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err) {
+  if (!OffsetAndSizeInVendorSpace(offset, size)) {
+    *err = android::base::StringPrintf("Out of bound read (offset %zu size %zu)", offset, size);
+    return false;
+  }
+  auto misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return read_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset,
+                             err);
+}
+
+bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err) {
+  if (!OffsetAndSizeInVendorSpace(offset, size)) {
+    *err = android::base::StringPrintf("Out of bound write (offset %zu size %zu)", offset, size);
+    return false;
+  }
+  auto misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return write_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset,
+                              err);
+}
+
 extern "C" bool write_reboot_bootloader(void) {
   std::string err;
   return write_reboot_bootloader(&err);
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 2207d4c..5c0a450 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -28,8 +28,9 @@
 // 16K - 64K    Used by uncrypt and recovery to store wipe_package for A/B devices
 // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they
 // are not configurable without changing all of them.
-static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
-static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
+constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
+constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024;
+constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
 
 /* Bootloader Message (2-KiB)
  *
@@ -233,6 +234,14 @@
 // Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC).
 bool write_wipe_package(const std::string& package_data, std::string* err);
 
+// Reads data from the vendor space in /misc partition, with the given offset and size. Note that
+// offset is in relative to the start of vendor space.
+bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err);
+
+// Writes the given data to the vendor space in /misc partition, at the given offset. Note that
+// offset is in relative to the start of the vendor space.
+bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err);
+
 #else
 
 #include <stdbool.h>
diff --git a/edify/expr.cpp b/edify/expr.cpp
index c090eb2..e5e0e24 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -421,5 +421,5 @@
   return nullptr;
 }
 
-State::State(const std::string& script, void* cookie)
-    : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {}
+State::State(const std::string& script, UpdaterInterface* interface)
+    : script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {}
diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h
index 5cbd5e1..cd9c701 100644
--- a/edify/include/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -23,19 +23,20 @@
 #include <string>
 #include <vector>
 
+#include "edify/updater_interface.h"
+
 // Forward declaration to avoid including "otautil/error_code.h".
 enum ErrorCode : int;
 enum CauseCode : int;
 
 struct State {
-  State(const std::string& script, void* cookie);
+  State(const std::string& script, UpdaterInterface* cookie);
 
   // The source of the original script.
   const std::string& script;
 
-  // Optional pointer to app-specific data; the core of edify never
-  // uses this value.
-  void* cookie;
+  // A pointer to app-specific data; the libedify doesn't use this value.
+  UpdaterInterface* updater;
 
   // The error message (if any) returned if the evaluation aborts.
   // Should be empty initially, will be either empty or a string that
diff --git a/edify/include/edify/updater_interface.h b/edify/include/edify/updater_interface.h
new file mode 100644
index 0000000..a4d581e
--- /dev/null
+++ b/edify/include/edify/updater_interface.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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 <string>
+#include <string_view>
+
+struct ZipArchive;
+typedef ZipArchive* ZipArchiveHandle;
+
+class UpdaterRuntimeInterface;
+
+class UpdaterInterface {
+ public:
+  virtual ~UpdaterInterface() = default;
+
+  // Writes the message to command pipe, adds a new line in the end.
+  virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0;
+
+  // Sends over the message to recovery to print it on the screen.
+  virtual void UiPrint(const std::string_view message) const = 0;
+
+  // Given the name of the block device, returns |name| for updates on the device; or the file path
+  // to the fake block device for simulations.
+  virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+  virtual UpdaterRuntimeInterface* GetRuntime() const = 0;
+  virtual ZipArchiveHandle GetPackageHandle() const = 0;
+  virtual std::string GetResult() const = 0;
+  virtual uint8_t* GetMappedPackageAddress() const = 0;
+};
diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h
new file mode 100644
index 0000000..15ccd83
--- /dev/null
+++ b/edify/include/edify/updater_runtime_interface.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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 <string>
+#include <string_view>
+#include <vector>
+
+// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and
+// updates on device and host simulations can have different implementations. e.g. block devices
+// during host simulation merely a temporary file. With this class, the caller side in registered
+// updater's functions will stay the same for both update and simulation.
+class UpdaterRuntimeInterface {
+ public:
+  virtual ~UpdaterRuntimeInterface() = default;
+
+  // Returns true if it's a runtime instance for simulation.
+  virtual bool IsSimulator() const = 0;
+
+  // Returns the value of system property |key|. If the property doesn't exist, returns
+  // |default_value|.
+  virtual std::string GetProperty(const std::string_view key,
+                                  const std::string_view default_value) const = 0;
+
+  // Given the name of the block device, returns |name| for updates on the device; or the file path
+  // to the fake block device for simulations.
+  virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+  // Mounts the |location| on |mount_point|. Returns 0 on success.
+  virtual int Mount(const std::string_view location, const std::string_view mount_point,
+                    const std::string_view fs_type, const std::string_view mount_options) = 0;
+
+  // Returns true if |mount_point| is mounted.
+  virtual bool IsMounted(const std::string_view mount_point) const = 0;
+
+  // Unmounts the |mount_point|. Returns a pair of results with the first value indicating
+  // if the |mount_point| is mounted, and the second value indicating the result of umount(2).
+  virtual std::pair<bool, int> Unmount(const std::string_view mount_point) = 0;
+
+  // Reads |filename| and puts its value to |content|.
+  virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0;
+
+  // Updates the content of |filename| with |content|.
+  virtual bool WriteStringToFile(const std::string_view content,
+                                 const std::string_view filename) const = 0;
+
+  // Wipes the first |len| bytes of block device in |filename|.
+  virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0;
+
+  // Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true.
+  virtual int RunProgram(const std::vector<std::string>& args, bool is_vfork) const = 0;
+
+  // Runs tune2fs with arguments |args|.
+  virtual int Tune2Fs(const std::vector<std::string>& args) const = 0;
+};
\ No newline at end of file
diff --git a/misc_writer/Android.bp b/misc_writer/Android.bp
new file mode 100644
index 0000000..567143c
--- /dev/null
+++ b/misc_writer/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2019 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.
+//
+
+cc_binary {
+    name: "misc_writer",
+    vendor: true,
+
+    srcs: [
+        "misc_writer.cpp",
+    ],
+
+    cpp_std: "experimental",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    shared_libs: [
+        "libbase",
+    ],
+
+    static_libs: [
+        "libbootloader_message_vendor",
+        "libfstab",
+    ],
+}
diff --git a/misc_writer/misc_writer.cpp b/misc_writer/misc_writer.cpp
new file mode 100644
index 0000000..1d9702e
--- /dev/null
+++ b/misc_writer/misc_writer.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 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 <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <bootloader_message/bootloader_message.h>
+
+using namespace std::string_literals;
+
+static std::vector<uint8_t> ParseHexString(std::string_view hex_string) {
+  auto length = hex_string.size();
+  if (length % 2 != 0 || length == 0) {
+    return {};
+  }
+
+  std::vector<uint8_t> result(length / 2);
+  for (size_t i = 0; i < length / 2; i++) {
+    auto sub = "0x" + std::string(hex_string.substr(i * 2, 2));
+    if (!android::base::ParseUint(sub, &result[i])) {
+      return {};
+    }
+  }
+  return result;
+}
+
+static int Usage(std::string_view name) {
+  std::cerr << name << " usage:\n";
+  std::cerr << name << " [--vendor-space-offset <offset>] --hex-string 0xABCDEF\n";
+  std::cerr << "Writes the given hex string to the specified offset in vendor space in /misc "
+               "partition. Offset defaults to 0 if unspecified.\n";
+  return EXIT_FAILURE;
+}
+
+// misc_writer is a vendor tool that writes data to the vendor space in /misc.
+int main(int argc, char** argv) {
+  constexpr struct option OPTIONS[] = {
+    { "vendor-space-offset", required_argument, nullptr, 0 },
+    { "hex-string", required_argument, nullptr, 0 },
+    { nullptr, 0, nullptr, 0 },
+  };
+
+  // Offset defaults to 0 if unspecified.
+  size_t offset = 0;
+  std::string_view hex_string;
+
+  int arg;
+  int option_index;
+  while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+    if (arg != 0) {
+      LOG(ERROR) << "Invalid command argument";
+      return Usage(argv[0]);
+    }
+    auto option_name = OPTIONS[option_index].name;
+    if (option_name == "vendor-space-offset"s) {
+      if (!android::base::ParseUint(optarg, &offset)) {
+        LOG(ERROR) << "Failed to parse the offset: " << optarg;
+        return Usage(argv[0]);
+      }
+    } else if (option_name == "hex-string"s) {
+      hex_string = optarg;
+    }
+  }
+
+  if (hex_string.starts_with("0x") || hex_string.starts_with("0X")) {
+    hex_string = hex_string.substr(2);
+  }
+  if (hex_string.empty()) {
+    LOG(ERROR) << "Invalid input hex string: " << hex_string;
+    return Usage(argv[0]);
+  }
+
+  auto data = ParseHexString(hex_string);
+  if (data.empty()) {
+    LOG(ERROR) << "Failed to parse the input hex string: " << hex_string;
+    return EXIT_FAILURE;
+  }
+  if (std::string err; !WriteMiscPartitionVendorSpace(data.data(), data.size(), offset, &err)) {
+    LOG(ERROR) << "Failed to write to misc partition: " << err;
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}
diff --git a/tests/unit/bootloader_message_test.cpp b/tests/unit/bootloader_message_test.cpp
index b005d19..95d875e 100644
--- a/tests/unit/bootloader_message_test.cpp
+++ b/tests/unit/bootloader_message_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <android-base/file.h>
@@ -22,6 +23,10 @@
 #include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
 
+using namespace std::string_literals;
+
+extern void SetMiscBlockDeviceForTest(std::string_view misc_device);
+
 TEST(BootloaderMessageTest, read_and_write_bootloader_message) {
   TemporaryFile temp_misc;
 
@@ -114,3 +119,36 @@
             std::string(boot.reserved, sizeof(boot.reserved)));
 }
 
+TEST(BootloaderMessageTest, WriteMiscPartitionVendorSpace) {
+  TemporaryFile temp_misc;
+  ASSERT_TRUE(android::base::WriteStringToFile(std::string(4096, '\x00'), temp_misc.path));
+  SetMiscBlockDeviceForTest(temp_misc.path);
+
+  constexpr std::string_view kTestMessage = "kTestMessage";
+  std::string err;
+  ASSERT_TRUE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), 0, &err));
+
+  std::string message;
+  message.resize(kTestMessage.size());
+  ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err));
+  ASSERT_EQ(kTestMessage, message);
+
+  // Write with an offset.
+  ASSERT_TRUE(WriteMiscPartitionVendorSpace("\x00\x00", 2, 5, &err));
+  ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err));
+  ASSERT_EQ("kTest\x00\x00ssage"s, message);
+
+  // Write with the right size.
+  auto start_offset =
+      WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC - kTestMessage.size();
+  ASSERT_TRUE(
+      WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), start_offset, &err));
+
+  // Out-of-bound write.
+  ASSERT_FALSE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(),
+                                             start_offset + 1, &err));
+
+  // Message won't fit.
+  std::string long_message(WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC + 1, 'a');
+  ASSERT_FALSE(WriteMiscPartitionVendorSpace(long_message.data(), long_message.size(), 0, &err));
+}
diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp
index 4a8d1e6..81229f5 100644
--- a/tests/unit/updater_test.cpp
+++ b/tests/unit/updater_test.cpp
@@ -52,13 +52,14 @@
 #include "updater/blockimg.h"
 #include "updater/install.h"
 #include "updater/updater.h"
+#include "updater/updater_runtime.h"
 
 using namespace std::string_literals;
 
 using PackageEntries = std::unordered_map<std::string, std::string>;
 
 static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code,
-                   Updater* updater = nullptr) {
+                   Updater* updater) {
   std::unique_ptr<Expr> e;
   int error_count = 0;
   ASSERT_EQ(0, ParseString(expr_str, &e, &error_count));
@@ -83,6 +84,11 @@
   ASSERT_EQ(cause_code, state.cause_code);
 }
 
+static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code) {
+  Updater updater;
+  expect(expected, expr_str, cause_code, &updater);
+}
+
 static void BuildUpdatePackage(const PackageEntries& entries, int fd) {
   FILE* zip_file_ptr = fdopen(fd, "wb");
   ZipWriter zip_writer(zip_file_ptr);
@@ -168,9 +174,9 @@
 
     // Set up the handler, command_pipe, patch offset & length.
     TemporaryFile temp_pipe;
-    ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false, nullptr));
+    ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false));
     ASSERT_TRUE(updater_.RunUpdate());
-    ASSERT_EQ(result, updater_.result());
+    ASSERT_EQ(result, updater_.GetResult());
 
     // Parse the cause code written to the command pipe.
     int received_cause_code = kNoCause;
diff --git a/updater/Android.bp b/updater/Android.bp
index daf7e32..72f8bc9 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -71,6 +71,7 @@
         "dynamic_partitions.cpp",
         "install.cpp",
         "updater.cpp",
+        "updater_runtime.cpp",
     ],
 
     include_dirs: [
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 3b2b2c0..55218b0 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -54,6 +54,7 @@
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
+#include "edify/updater_interface.h"
 #include "otautil/dirutil.h"
 #include "otautil/error_code.h"
 #include "otautil/paths.h"
@@ -61,7 +62,6 @@
 #include "otautil/rangeset.h"
 #include "private/commands.h"
 #include "updater/install.h"
-#include "updater/updater.h"
 
 // Set this to 0 to interpret 'erase' transfers to mean do a
 // BLKDISCARD ioctl (the normal behavior).  Set to 1 to interpret
@@ -1669,8 +1669,15 @@
     return StringValue("");
   }
 
-  auto updater = static_cast<Updater*>(state->cookie);
-  ZipArchiveHandle za = updater->package_handle();
+  auto updater = state->updater;
+  auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
+  ZipArchiveHandle za = updater->GetPackageHandle();
   if (za == nullptr) {
     return StringValue("");
   }
@@ -1690,15 +1697,15 @@
     return StringValue("");
   }
 
-  params.fd.reset(TEMP_FAILURE_RETRY(open(blockdev_filename->data.c_str(), O_RDWR)));
+  params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR)));
   if (params.fd == -1) {
     failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
-    PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
+    PLOG(ERROR) << "open \"" << block_device_path << "\" failed";
     return StringValue("");
   }
 
   uint8_t digest[SHA_DIGEST_LENGTH];
-  if (!Sha1DevicePath(blockdev_filename->data, digest)) {
+  if (!Sha1DevicePath(block_device_path, digest)) {
     return StringValue("");
   }
   params.stashbase = print_sha1(digest);
@@ -1711,8 +1718,7 @@
     struct stat sb;
     int result = stat(updated_marker.c_str(), &sb);
     if (result == 0) {
-      LOG(INFO) << "Skipping already updated partition " << blockdev_filename->data
-                << " based on marker";
+      LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker";
       return StringValue("t");
     }
   } else {
@@ -1910,7 +1916,7 @@
       LOG(INFO) << "stashed " << params.stashed << " blocks";
       LOG(INFO) << "max alloc needed was " << params.buffer.size();
 
-      const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+      const char* partition = strrchr(block_device_path.c_str(), '/');
       if (partition != nullptr && *(partition + 1) != 0) {
         updater->WriteToCommandPipe(
             android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1,
@@ -2078,10 +2084,17 @@
     return StringValue("");
   }
 
-  android::base::unique_fd fd(open(blockdev_filename->data.c_str(), O_RDWR));
+  auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
+  android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR));
   if (fd == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
-    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", blockdev_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2095,7 +2108,7 @@
   std::vector<uint8_t> buffer(BLOCKSIZE);
   for (const auto& [begin, end] : rs) {
     if (!check_lseek(fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) {
-      ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(),
+      ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(),
                  strerror(errno));
       return StringValue("");
     }
@@ -2103,7 +2116,7 @@
     for (size_t j = begin; j < end; ++j) {
       if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) {
         CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
-        ErrorAbort(state, cause_code, "failed to read %s: %s", blockdev_filename->data.c_str(),
+        ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
                    strerror(errno));
         return StringValue("");
       }
@@ -2142,10 +2155,17 @@
     return StringValue("");
   }
 
-  android::base::unique_fd fd(open(arg_filename->data.c_str(), O_RDONLY));
+  auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
+  android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY));
   if (fd == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
-    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", arg_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2155,7 +2175,7 @@
 
   if (ReadBlocks(blk0, &block0_buffer, fd) == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
-    ErrorAbort(state, cause_code, "failed to read %s: %s", arg_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2171,10 +2191,9 @@
   uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]);
 
   if (mount_count > 0) {
-    auto updater = static_cast<Updater*>(state->cookie);
-    updater->UiPrint(
+    state->updater->UiPrint(
         android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count));
-    updater->UiPrint(
+    state->updater->UiPrint(
         android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time)));
   }
 
@@ -2211,14 +2230,21 @@
     return StringValue("");
   }
 
+  auto block_device_path = state->updater->FindBlockDeviceName(filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
   // Output notice to log when recover is attempted
-  LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
+  LOG(INFO) << block_device_path << " image corrupted, attempting to recover...";
 
   // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
-  fec::io fh(filename->data, O_RDWR);
+  fec::io fh(block_device_path, O_RDWR);
 
   if (!fh) {
-    ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
+    ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2244,7 +2270,7 @@
 
       if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) {
         ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
-                   filename->data.c_str(), j, strerror(errno));
+                   block_device_path.c_str(), j, strerror(errno));
         return StringValue("");
       }
 
@@ -2260,7 +2286,7 @@
       //     read and check if the errors field value has increased.
     }
   }
-  LOG(INFO) << "..." << filename->data << " image recovered successfully.";
+  LOG(INFO) << "..." << block_device_path << " image recovered successfully.";
   return StringValue("t");
 }
 
diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h
index d546829..7bbecbc 100644
--- a/updater/include/updater/updater.h
+++ b/updater/include/updater/updater.h
@@ -21,45 +21,53 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
+#include "edify/updater_interface.h"
 #include "otautil/error_code.h"
 #include "otautil/sysutil.h"
 
-struct selabel_handle;
+class UpdaterRuntime;
 
-class Updater {
+class Updater : public UpdaterInterface {
  public:
-  ~Updater();
+  explicit Updater(std::unique_ptr<UpdaterRuntimeInterface> run_time)
+      : runtime_(std::move(run_time)) {}
+
+  Updater();
+
+  ~Updater() override;
 
   // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and
-  // selabel handle. TODO(xunchang) implement a run time environment class and move sehandle there.
-  bool Init(int fd, const std::string& package_filename, bool is_retry,
-            struct selabel_handle* sehandle);
+  // UpdaterRuntime.
+  bool Init(int fd, const std::string_view package_filename, bool is_retry);
 
   // Parses and evaluates the updater-script in the OTA package. Reports the error code if the
   // evaluation fails.
   bool RunUpdate();
 
   // Writes the message to command pipe, adds a new line in the end.
-  void WriteToCommandPipe(const std::string& message, bool flush = false) const;
+  void WriteToCommandPipe(const std::string_view message, bool flush = false) const override;
 
   // Sends over the message to recovery to print it on the screen.
-  void UiPrint(const std::string& message) const;
+  void UiPrint(const std::string_view message) const override;
 
-  ZipArchiveHandle package_handle() const {
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  UpdaterRuntimeInterface* GetRuntime() const override {
+    return runtime_.get();
+  }
+  ZipArchiveHandle GetPackageHandle() const override {
     return package_handle_;
   }
-  struct selabel_handle* sehandle() const {
-    return sehandle_;
-  }
-  std::string result() const {
+  std::string GetResult() const override {
     return result_;
   }
 
-  uint8_t* GetMappedPackageAddress() const {
+  uint8_t* GetMappedPackageAddress() const override {
     return mapped_package_.addr;
   }
 
@@ -76,13 +84,15 @@
   // Parses the error code embedded in state->errmsg; and reports the error code and cause code.
   void ParseAndReportErrorCode(State* state);
 
+  std::unique_ptr<UpdaterRuntimeInterface> runtime_;
+
   MemMapping mapped_package_;
   ZipArchiveHandle package_handle_{ nullptr };
   std::string updater_script_;
 
   bool is_retry_{ false };
   std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
-  struct selabel_handle* sehandle_{ nullptr };
 
   std::string result_;
+  std::vector<std::string> skipped_functions_;
 };
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
new file mode 100644
index 0000000..6cd0ffb
--- /dev/null
+++ b/updater/include/updater/updater_runtime.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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 <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+
+struct selabel_handle;
+struct Partition;
+
+class UpdaterRuntime : public UpdaterRuntimeInterface {
+ public:
+  explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
+  ~UpdaterRuntime() override = default;
+
+  bool IsSimulator() const override {
+    return false;
+  }
+
+  std::string GetProperty(const std::string_view key,
+                          const std::string_view default_value) const override;
+
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  int Mount(const std::string_view location, const std::string_view mount_point,
+            const std::string_view fs_type, const std::string_view mount_options) override;
+  bool IsMounted(const std::string_view mount_point) const override;
+  std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+  bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+  bool WriteStringToFile(const std::string_view content,
+                         const std::string_view filename) const override;
+
+  int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+  int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+  int Tune2Fs(const std::vector<std::string>& args) const override;
+
+  struct selabel_handle* sehandle_{ nullptr };
+};
diff --git a/updater/install.cpp b/updater/install.cpp
index b4d8840..6b15eaa 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -57,12 +57,25 @@
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "edify/updater_runtime_interface.h"
 #include "otautil/dirutil.h"
 #include "otautil/error_code.h"
 #include "otautil/mounts.h"
 #include "otautil/print_sha1.h"
 #include "otautil/sysutil.h"
-#include "updater/updater.h"
+
+static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) {
+  CHECK(updater);
+  std::string name = updater->FindBlockDeviceName(partition->name);
+  if (name.empty()) {
+    LOG(ERROR) << "Failed to find the block device " << partition->name;
+    return false;
+  }
+
+  partition->name = std::move(name);
+  return true;
+}
 
 // This is the updater side handler for ui_print() in edify script. Contents will be sent over to
 // the recovery side for on-screen display.
@@ -73,7 +86,7 @@
   }
 
   std::string buffer = android::base::Join(args, "");
-  static_cast<Updater*>(state->cookie)->UiPrint(buffer);
+  state->updater->UiPrint(buffer);
   return StringValue(buffer);
 }
 
@@ -99,7 +112,7 @@
     const std::string& zip_path = args[0];
     const std::string& dest_path = args[1];
 
-    ZipArchiveHandle za = static_cast<Updater*>(state->cookie)->package_handle();
+    ZipArchiveHandle za = state->updater->GetPackageHandle();
     ZipEntry entry;
     if (FindEntry(za, zip_path, &entry) != 0) {
       LOG(ERROR) << name << ": no " << zip_path << " in package";
@@ -142,7 +155,7 @@
     }
     const std::string& zip_path = args[0];
 
-    ZipArchiveHandle za = static_cast<Updater*>(state->cookie)->package_handle();
+    ZipArchiveHandle za = state->updater->GetPackageHandle();
     ZipEntry entry;
     if (FindEntry(za, zip_path, &entry) != 0) {
       return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
@@ -197,6 +210,11 @@
                       args[1].c_str(), err.c_str());
   }
 
+  if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+      !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+    return StringValue("");
+  }
+
   bool result = PatchPartitionCheck(target, source);
   return StringValue(result ? "t" : "");
 }
@@ -238,6 +256,11 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
   }
 
+  if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+      !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+    return StringValue("");
+  }
+
   bool result = PatchPartition(target, source, *values[0], nullptr);
   return StringValue(result ? "t" : "");
 }
@@ -281,24 +304,8 @@
                       name);
   }
 
-  auto updater = static_cast<Updater*>(state->cookie);
-  {
-    char* secontext = nullptr;
-    if (updater->sehandle()) {
-      selabel_lookup(updater->sehandle(), &secontext, mount_point.c_str(), 0755);
-      setfscreatecon(secontext);
-    }
-
-    mkdir(mount_point.c_str(), 0755);
-
-    if (secontext) {
-      freecon(secontext);
-      setfscreatecon(nullptr);
-    }
-  }
-
-  if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(),
-            MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) {
+  auto updater = state->updater;
+  if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) {
     updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name,
                                                  location.c_str(), mount_point.c_str(),
                                                  strerror(errno)));
@@ -324,9 +331,8 @@
                       "mount_point argument to unmount() can't be empty");
   }
 
-  scan_mounted_volumes();
-  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
-  if (vol == nullptr) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->IsMounted(mount_point)) {
     return StringValue("");
   }
 
@@ -347,42 +353,20 @@
                       "mount_point argument to unmount() can't be empty");
   }
 
-  auto updater = static_cast<Updater*>(state->cookie);
-  scan_mounted_volumes();
-  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
-  if (vol == nullptr) {
+  auto updater = state->updater;
+  auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point);
+  if (!mounted) {
     updater->UiPrint(
         android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str()));
     return nullptr;
-  } else {
-    int ret = unmount_mounted_volume(vol);
-    if (ret != 0) {
-      updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(),
-                                                   strerror(errno)));
-    }
+  } else if (result != 0) {
+    updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(),
+                                                 strerror(errno)));
   }
 
   return StringValue(mount_point);
 }
 
-static int exec_cmd(const std::vector<std::string>& args) {
-  CHECK(!args.empty());
-  auto argv = StringVectorToNullTerminatedArray(args);
-
-  pid_t child;
-  if ((child = vfork()) == 0) {
-    execv(argv[0], argv.data());
-    _exit(EXIT_FAILURE);
-  }
-
-  int status;
-  waitpid(child, &status, 0);
-  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-    LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
-  }
-  return WEXITSTATUS(status);
-}
-
 // format(fs_type, partition_type, location, fs_size, mount_point)
 //
 //    fs_type="ext4"  partition_type="EMMC"  location=device  fs_size=<bytes> mount_point=<location>
@@ -427,6 +411,7 @@
                       fs_size.c_str());
   }
 
+  auto updater_runtime = state->updater->GetRuntime();
   if (fs_type == "ext4") {
     std::vector<std::string> mke2fs_args = {
       "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
@@ -435,12 +420,13 @@
       mke2fs_args.push_back(std::to_string(size / 4096LL));
     }
 
-    if (auto status = exec_cmd(mke2fs_args); status != 0) {
+    if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) {
       LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
       return StringValue("");
     }
 
-    if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location });
+    if (auto status = updater_runtime->RunProgram(
+            { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true);
         status != 0) {
       LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
       return StringValue("");
@@ -459,12 +445,13 @@
     if (size >= 512) {
       f2fs_args.push_back(std::to_string(size / 512));
     }
-    if (auto status = exec_cmd(f2fs_args); status != 0) {
+    if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) {
       LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
       return StringValue("");
     }
 
-    if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location });
+    if (auto status = updater_runtime->RunProgram(
+            { "/system/bin/sload_f2fs", "-t", mount_point, location }, true);
         status != 0) {
       LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
       return StringValue("");
@@ -503,8 +490,7 @@
                       sec_str.c_str());
   }
 
-  auto updater = static_cast<Updater*>(state->cookie);
-  updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
+  state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
 
   return StringValue(frac_str);
 }
@@ -527,8 +513,7 @@
                       frac_str.c_str());
   }
 
-  auto updater = static_cast<Updater*>(state->cookie);
-  updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
+  state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
 
   return StringValue(frac_str);
 }
@@ -541,7 +526,9 @@
   if (!Evaluate(state, argv[0], &key)) {
     return nullptr;
   }
-  std::string value = android::base::GetProperty(key, "");
+
+  auto updater_runtime = state->updater->GetRuntime();
+  std::string value = updater_runtime->GetProperty(key, "");
 
   return StringValue(value);
 }
@@ -566,7 +553,8 @@
   const std::string& key = args[1];
 
   std::string buffer;
-  if (!android::base::ReadFileToString(filename, &buffer)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->ReadFileToString(filename, &buffer)) {
     ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str());
     return nullptr;
   }
@@ -628,7 +616,7 @@
                       argv.size());
   }
 
-  static_cast<Updater*>(state->cookie)->WriteToCommandPipe("wipe_cache");
+  state->updater->WriteToCommandPipe("wipe_cache");
   return StringValue("t");
 }
 
@@ -642,26 +630,8 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
 
-  auto exec_args = StringVectorToNullTerminatedArray(args);
-  LOG(INFO) << "about to run program [" << exec_args[0] << "] with " << argv.size() << " args";
-
-  pid_t child = fork();
-  if (child == 0) {
-    execv(exec_args[0], exec_args.data());
-    PLOG(ERROR) << "run_program: execv failed";
-    _exit(EXIT_FAILURE);
-  }
-
-  int status;
-  waitpid(child, &status, 0);
-  if (WIFEXITED(status)) {
-    if (WEXITSTATUS(status) != 0) {
-      LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
-    }
-  } else if (WIFSIGNALED(status)) {
-    LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
-  }
-
+  auto updater_runtime = state->updater->GetRuntime();
+  auto status = updater_runtime->RunProgram(args, false);
   return StringValue(std::to_string(status));
 }
 
@@ -679,7 +649,8 @@
   const std::string& filename = args[0];
 
   std::string contents;
-  if (android::base::ReadFileToString(filename, &contents)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (updater_runtime->ReadFileToString(filename, &contents)) {
     return new Value(Value::Type::STRING, std::move(contents));
   }
 
@@ -708,12 +679,12 @@
   }
 
   const std::string& value = args[0];
-  if (!android::base::WriteStringToFile(value, filename)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->WriteStringToFile(value, filename)) {
     PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\"";
     return StringValue("");
-  } else {
-    return StringValue("t");
   }
+  return StringValue("t");
 }
 
 // Immediately reboot the device.  Recovery is not finished normally,
@@ -839,16 +810,10 @@
   if (!android::base::ParseUint(len_str.c_str(), &len)) {
     return nullptr;
   }
-  android::base::unique_fd fd(open(filename.c_str(), O_WRONLY));
-  if (fd == -1) {
-    PLOG(ERROR) << "Failed to open " << filename;
-    return StringValue("");
-  }
 
-  // The wipe_block_device function in ext4_utils returns 0 on success and 1
-  // for failure.
-  int status = wipe_block_device(fd, len);
-  return StringValue((status == 0) ? "t" : "");
+  auto updater_runtime = state->updater->GetRuntime();
+  int status = updater_runtime->WipeBlockDevice(filename, len);
+  return StringValue(status == 0 ? "t" : "");
 }
 
 Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -856,7 +821,7 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
                       argv.size());
   }
-  static_cast<Updater*>(state->cookie)->WriteToCommandPipe("enable_reboot");
+  state->updater->WriteToCommandPipe("enable_reboot");
   return StringValue("t");
 }
 
@@ -872,10 +837,8 @@
 
   // tune2fs expects the program name as its first arg.
   args.insert(args.begin(), "tune2fs");
-  auto tune2fs_args = StringVectorToNullTerminatedArray(args);
-
-  // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
-  if (auto result = tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); result != 0) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (auto result = updater_runtime->Tune2Fs(args); result != 0) {
     return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
   }
   return StringValue("t");
diff --git a/updater/updater.cpp b/updater/updater.cpp
index e0679fb..dbfa2f4 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -24,14 +24,17 @@
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 
+#include "updater/updater_runtime.h"
+
+Updater::Updater() : Updater(std::make_unique<UpdaterRuntime>(nullptr)) {}
+
 Updater::~Updater() {
   if (package_handle_) {
     CloseArchive(package_handle_);
   }
 }
 
-bool Updater::Init(int fd, const std::string& package_filename, bool is_retry,
-                   struct selabel_handle* sehandle) {
+bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) {
   // Set up the pipe for sending commands back to the parent process.
   cmd_pipe_.reset(fdopen(fd, "wb"));
   if (!cmd_pipe_) {
@@ -41,12 +44,12 @@
 
   setlinebuf(cmd_pipe_.get());
 
-  if (!mapped_package_.MapFile(package_filename)) {
+  if (!mapped_package_.MapFile(std::string(package_filename))) {
     LOG(ERROR) << "failed to map package " << package_filename;
     return false;
   }
   if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
-                                           package_filename.c_str(), &package_handle_);
+                                           std::string(package_filename).c_str(), &package_handle_);
       open_err != 0) {
     LOG(ERROR) << "failed to open package " << package_filename << ": "
                << ErrorCodeString(open_err);
@@ -58,14 +61,12 @@
 
   is_retry_ = is_retry;
 
-  sehandle_ = sehandle;
-  if (!sehandle_) {
-    fprintf(cmd_pipe_.get(), "ui_print Warning: No file_contexts\n");
-  }
   return true;
 }
 
 bool Updater::RunUpdate() {
+  CHECK(runtime_);
+
   // Parse the script.
   std::unique_ptr<Expr> root;
   int error_count = 0;
@@ -86,6 +87,9 @@
     if (result_.empty() && state.cause_code != kNoCause) {
       fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code);
     }
+    for (const auto& func : skipped_functions_) {
+      LOG(WARNING) << "Skipped executing function " << func;
+    }
     return true;
   }
 
@@ -93,17 +97,17 @@
   return false;
 }
 
-void Updater::WriteToCommandPipe(const std::string& message, bool flush) const {
-  fprintf(cmd_pipe_.get(), "%s\n", message.c_str());
+void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const {
+  fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str());
   if (flush) {
     fflush(cmd_pipe_.get());
   }
 }
 
-void Updater::UiPrint(const std::string& message) const {
+void Updater::UiPrint(const std::string_view message) const {
   // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
   // so skip sending empty strings to ui.
-  std::vector<std::string> lines = android::base::Split(message, "\n");
+  std::vector<std::string> lines = android::base::Split(std::string(message), "\n");
   for (const auto& line : lines) {
     if (!line.empty()) {
       fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
@@ -116,6 +120,10 @@
   LOG(INFO) << message;
 }
 
+std::string Updater::FindBlockDeviceName(const std::string_view name) const {
+  return runtime_->FindBlockDeviceName(name);
+}
+
 void Updater::ParseAndReportErrorCode(State* state) {
   CHECK(state);
   if (state->errmsg.empty()) {
diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp
index dd22c13..055a8ac 100644
--- a/updater/updater_main.cpp
+++ b/updater/updater_main.cpp
@@ -31,6 +31,7 @@
 #include "updater/dynamic_partitions.h"
 #include "updater/install.h"
 #include "updater/updater.h"
+#include "updater/updater_runtime.h"
 
 // Generated by the makefile, this function defines the
 // RegisterDeviceExtensions() function, which calls all the
@@ -95,8 +96,8 @@
   auto sehandle = selinux_android_file_context_handle();
   selinux_android_set_sehandle(sehandle);
 
-  Updater updater;
-  if (!updater.Init(fd, package_name, is_retry, sehandle)) {
+  Updater updater(std::make_unique<UpdaterRuntime>(sehandle));
+  if (!updater.Init(fd, package_name, is_retry)) {
     return 1;
   }
 
diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp
new file mode 100644
index 0000000..761f999
--- /dev/null
+++ b/updater/updater_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 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 "updater/updater_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+#include <tune2fs.h>
+
+#include "otautil/mounts.h"
+#include "otautil/sysutil.h"
+
+std::string UpdaterRuntime::GetProperty(const std::string_view key,
+                                        const std::string_view default_value) const {
+  return android::base::GetProperty(std::string(key), std::string(default_value));
+}
+
+std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const {
+  return std::string(name);
+}
+
+int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+                          const std::string_view fs_type, const std::string_view mount_options) {
+  std::string mount_point_string(mount_point);
+  char* secontext = nullptr;
+  if (sehandle_) {
+    selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755);
+    setfscreatecon(secontext);
+  }
+
+  mkdir(mount_point_string.c_str(), 0755);
+
+  if (secontext) {
+    freecon(secontext);
+    setfscreatecon(nullptr);
+  }
+
+  return mount(std::string(location).c_str(), mount_point_string.c_str(),
+               std::string(fs_type).c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME,
+               std::string(mount_options).c_str());
+}
+
+bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const {
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+  return vol != nullptr;
+}
+
+std::pair<bool, int> UpdaterRuntime::Unmount(const std::string_view mount_point) {
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+  if (vol == nullptr) {
+    return { false, -1 };
+  }
+
+  int ret = unmount_mounted_volume(vol);
+  return { true, ret };
+}
+
+bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const {
+  return android::base::ReadFileToString(std::string(filename), content);
+}
+
+bool UpdaterRuntime::WriteStringToFile(const std::string_view content,
+                                       const std::string_view filename) const {
+  return android::base::WriteStringToFile(std::string(content), std::string(filename));
+}
+
+int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const {
+  android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << filename;
+    return false;
+  }
+  // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure.
+  return wipe_block_device(fd, len);
+}
+
+int UpdaterRuntime::RunProgram(const std::vector<std::string>& args, bool is_vfork) const {
+  CHECK(!args.empty());
+  auto argv = StringVectorToNullTerminatedArray(args);
+  LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args";
+
+  pid_t child = is_vfork ? vfork() : fork();
+  if (child == 0) {
+    execv(argv[0], argv.data());
+    PLOG(ERROR) << "run_program: execv failed";
+    _exit(EXIT_FAILURE);
+  }
+
+  int status;
+  waitpid(child, &status, 0);
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != 0) {
+      LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
+    }
+  } else if (WIFSIGNALED(status)) {
+    LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
+  }
+
+  return status;
+}
+
+int UpdaterRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+  auto tune2fs_args = StringVectorToNullTerminatedArray(args);
+  // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
+  return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data());
+}