Add UpdaterRuntime class

This class adds a wrapper to the runtime dependent functions. Therefore,
the behavior of update on device stays the same, while simulators can
have their own implementations. Also change the caller side of the
registered updater functions to call these runtime wrappers.

Bug: 131911365
Test: unit tests pass, sideload an update on cuttlefish
Change-Id: Ib3ab67132991d67fc132f27120e4152439d16ac5
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/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());
+}