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/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());
+}