Implement updater runtime for dynamic partitions

The simulator skips executing the operations for dynamic partitions, and
will use the logical images under target_files/IMAGES directly. (Similar
to the targets without DAP enabled)

Bug: 131911365
Test: run update on cuttlefish, run simulator
Change-Id: Id318d97ece4560df9f20dc5cabeb8b2e261bdf9c
diff --git a/updater/Android.bp b/updater/Android.bp
index 93eeece..063366e 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -104,6 +104,7 @@
     srcs: [
         "dynamic_partitions.cpp",
         "updater_runtime.cpp",
+        "updater_runtime_dynamic_partitions.cpp",
     ],
 
     static_libs: [
@@ -129,6 +130,7 @@
 
     srcs: [
         "build_info.cpp",
+        "dynamic_partitions.cpp",
         "simulator_runtime.cpp",
         "target_files.cpp",
     ],
diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp
index b50dd75..a340116 100644
--- a/updater/dynamic_partitions.cpp
+++ b/updater/dynamic_partitions.cpp
@@ -19,46 +19,20 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <algorithm>
-#include <chrono>
-#include <iterator>
 #include <memory>
-#include <optional>
 #include <string>
-#include <type_traits>
 #include <vector>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
-#include <android-base/parseint.h>
 #include <android-base/strings.h>
-#include <fs_mgr.h>
-#include <fs_mgr_dm_linear.h>
-#include <libdm/dm.h>
-#include <liblp/builder.h>
 
 #include "edify/expr.h"
+#include "edify/updater_runtime_interface.h"
 #include "otautil/error_code.h"
 #include "otautil/paths.h"
 #include "private/utils.h"
 
-using android::base::ParseUint;
-using android::dm::DeviceMapper;
-using android::dm::DmDeviceState;
-using android::fs_mgr::CreateLogicalPartition;
-using android::fs_mgr::DestroyLogicalPartition;
-using android::fs_mgr::LpMetadata;
-using android::fs_mgr::MetadataBuilder;
-using android::fs_mgr::Partition;
-using android::fs_mgr::PartitionOpener;
-
-static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
-static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
-
-static std::string GetSuperDevice() {
-  return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
-}
-
 static std::vector<std::string> ReadStringArgs(const char* name, State* state,
                                                const std::vector<std::unique_ptr<Expr>>& argv,
                                                const std::vector<std::string>& arg_names) {
@@ -89,40 +63,14 @@
   return ret;
 }
 
-static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
-  auto state = DeviceMapper::Instance().GetState(partition_name);
-  if (state == DmDeviceState::INVALID) {
-    return true;
-  }
-  if (state == DmDeviceState::ACTIVE) {
-    return DestroyLogicalPartition(partition_name, kMapTimeout);
-  }
-  LOG(ERROR) << "Unknown device mapper state: "
-             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
-  return false;
-}
-
-static bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) {
-  auto state = DeviceMapper::Instance().GetState(partition_name);
-  if (state == DmDeviceState::INVALID) {
-    return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name,
-                                  true /* force writable */, kMapTimeout, path);
-  }
-
-  if (state == DmDeviceState::ACTIVE) {
-    return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
-  }
-  LOG(ERROR) << "Unknown device mapper state: "
-             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
-  return false;
-}
-
 Value* UnmapPartitionFn(const char* name, State* state,
                         const std::vector<std::unique_ptr<Expr>>& argv) {
   auto args = ReadStringArgs(name, state, argv, { "name" });
   if (args.empty()) return StringValue("");
 
-  return UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") : StringValue("");
+  auto updater_runtime = state->updater->GetRuntime();
+  return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t")
+                                                                : StringValue("");
 }
 
 Value* MapPartitionFn(const char* name, State* state,
@@ -131,207 +79,12 @@
   if (args.empty()) return StringValue("");
 
   std::string path;
-  bool result = MapPartitionOnDeviceMapper(args[0], &path);
+  auto updater_runtime = state->updater->GetRuntime();
+  bool result = updater_runtime->MapPartitionOnDeviceMapper(args[0], &path);
   return result ? StringValue(path) : StringValue("");
 }
 
-namespace {  // Ops
-
-struct OpParameters {
-  std::vector<std::string> tokens;
-  MetadataBuilder* builder;
-
-  bool ExpectArgSize(size_t size) const {
-    CHECK(!tokens.empty());
-    auto actual = tokens.size() - 1;
-    if (actual != size) {
-      LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
-      return false;
-    }
-    return true;
-  }
-  const std::string& op() const {
-    CHECK(!tokens.empty());
-    return tokens[0];
-  }
-  const std::string& arg(size_t pos) const {
-    CHECK_LE(pos + 1, tokens.size());
-    return tokens[pos + 1];
-  }
-  std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
-    auto str = arg(pos);
-    uint64_t ret;
-    if (!ParseUint(str, &ret)) {
-      LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
-      return std::nullopt;
-    }
-    return ret;
-  }
-};
-
-using OpFunction = std::function<bool(const OpParameters&)>;
-using OpMap = std::map<std::string, OpFunction>;
-
-bool PerformOpResize(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& partition_name = params.arg(0);
-  auto size = params.uint_arg(1, "size");
-  if (!size.has_value()) return false;
-
-  auto partition = params.builder->FindPartition(partition_name);
-  if (partition == nullptr) {
-    LOG(ERROR) << "Failed to find partition " << partition_name
-               << " in dynamic partition metadata.";
-    return false;
-  }
-  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
-    LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
-    return false;
-  }
-  if (!params.builder->ResizePartition(partition, size.value())) {
-    LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
-    return false;
-  }
-  return true;
-}
-
-bool PerformOpRemove(const OpParameters& params) {
-  if (!params.ExpectArgSize(1)) return false;
-  const auto& partition_name = params.arg(0);
-
-  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
-    LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
-    return false;
-  }
-  params.builder->RemovePartition(partition_name);
-  return true;
-}
-
-bool PerformOpAdd(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& partition_name = params.arg(0);
-  const auto& group_name = params.arg(1);
-
-  if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
-      nullptr) {
-    LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
-    return false;
-  }
-  return true;
-}
-
-bool PerformOpMove(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& partition_name = params.arg(0);
-  const auto& new_group = params.arg(1);
-
-  auto partition = params.builder->FindPartition(partition_name);
-  if (partition == nullptr) {
-    LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
-               << " because it is not found.";
-    return false;
-  }
-
-  auto old_group = partition->group_name();
-  if (old_group != new_group) {
-    if (!params.builder->ChangePartitionGroup(partition, new_group)) {
-      LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
-                 << " to group " << new_group << ".";
-      return false;
-    }
-  }
-  return true;
-}
-
-bool PerformOpAddGroup(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& group_name = params.arg(0);
-  auto maximum_size = params.uint_arg(1, "maximum_size");
-  if (!maximum_size.has_value()) return false;
-
-  auto group = params.builder->FindGroup(group_name);
-  if (group != nullptr) {
-    LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
-    return false;
-  }
-
-  if (maximum_size.value() == 0) {
-    LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
-  }
-
-  if (!params.builder->AddGroup(group_name, maximum_size.value())) {
-    LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
-               << maximum_size.value() << ".";
-    return false;
-  }
-  return true;
-}
-
-bool PerformOpResizeGroup(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& group_name = params.arg(0);
-  auto new_size = params.uint_arg(1, "maximum_size");
-  if (!new_size.has_value()) return false;
-
-  auto group = params.builder->FindGroup(group_name);
-  if (group == nullptr) {
-    LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
-    return false;
-  }
-
-  auto old_size = group->maximum_size();
-  if (old_size != new_size.value()) {
-    if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
-      LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
-                 << new_size.value() << ".";
-      return false;
-    }
-  }
-  return true;
-}
-
-std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
-                                                   const std::string& group_name) {
-  auto partitions = builder->ListPartitionsInGroup(group_name);
-  std::vector<std::string> partition_names;
-  std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
-                 [](Partition* partition) { return partition->name(); });
-  return partition_names;
-}
-
-bool PerformOpRemoveGroup(const OpParameters& params) {
-  if (!params.ExpectArgSize(1)) return false;
-  const auto& group_name = params.arg(0);
-
-  auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
-  if (!partition_names.empty()) {
-    LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
-               << android::base::Join(partition_names, ", ") << "]";
-    return false;
-  }
-  params.builder->RemoveGroupAndPartitions(group_name);
-  return true;
-}
-
-bool PerformOpRemoveAllGroups(const OpParameters& params) {
-  if (!params.ExpectArgSize(0)) return false;
-
-  auto group_names = params.builder->ListGroups();
-  for (const auto& group_name : group_names) {
-    auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
-    for (const auto& partition_name : partition_names) {
-      if (!UnmapPartitionOnDeviceMapper(partition_name)) {
-        LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
-                   << ".";
-        return false;
-      }
-    }
-    params.builder->RemoveGroupAndPartitions(group_name);
-  }
-  return true;
-}
-
-}  // namespace
+static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
 
 Value* UpdateDynamicPartitionsFn(const char* name, State* state,
                                  const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -367,56 +120,8 @@
     }
   }
 
-  auto super_device = GetSuperDevice();
-  auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
-  if (builder == nullptr) {
-    LOG(ERROR) << "Failed to load dynamic partition metadata.";
-    return StringValue("");
-  }
-
-  static const OpMap op_map{
-    // clang-format off
-    {"resize",                PerformOpResize},
-    {"remove",                PerformOpRemove},
-    {"add",                   PerformOpAdd},
-    {"move",                  PerformOpMove},
-    {"add_group",             PerformOpAddGroup},
-    {"resize_group",          PerformOpResizeGroup},
-    {"remove_group",          PerformOpRemoveGroup},
-    {"remove_all_groups",     PerformOpRemoveAllGroups},
-    // clang-format on
-  };
-
-  std::vector<std::string> lines = android::base::Split(op_list_value->data, "\n");
-  for (const auto& line : lines) {
-    auto comment_idx = line.find('#');
-    auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
-    op_and_args = android::base::Trim(op_and_args);
-    if (op_and_args.empty()) continue;
-
-    auto tokens = android::base::Split(op_and_args, " ");
-    const auto& op = tokens[0];
-    auto it = op_map.find(op);
-    if (it == op_map.end()) {
-      LOG(ERROR) << "Unknown operation in op_list: " << op;
-      return StringValue("");
-    }
-    OpParameters params;
-    params.tokens = tokens;
-    params.builder = builder.get();
-    if (!it->second(params)) {
-      return StringValue("");
-    }
-  }
-
-  auto metadata = builder->Export();
-  if (metadata == nullptr) {
-    LOG(ERROR) << "Failed to export metadata.";
-    return StringValue("");
-  }
-
-  if (!UpdatePartitionTable(super_device, *metadata, 0)) {
-    LOG(ERROR) << "Failed to write metadata.";
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) {
     return StringValue("");
   }
 
diff --git a/updater/include/updater/simulator_runtime.h b/updater/include/updater/simulator_runtime.h
index 6290958..9f7847b 100644
--- a/updater/include/updater/simulator_runtime.h
+++ b/updater/include/updater/simulator_runtime.h
@@ -50,6 +50,10 @@
   int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
   int Tune2Fs(const std::vector<std::string>& args) const override;
 
+  bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+  bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+  bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
  private:
   std::string FindBlockDeviceName(const std::string_view name) const override;
 
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
index e97eb49..8fc066f 100644
--- a/updater/include/updater/updater_runtime.h
+++ b/updater/include/updater/updater_runtime.h
@@ -53,5 +53,10 @@
   int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
   int Tune2Fs(const std::vector<std::string>& args) const override;
 
+  bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+  bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+  bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
+ private:
   struct selabel_handle* sehandle_{ nullptr };
 };
diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp
index c3b3d95..c8718c7 100644
--- a/updater/simulator_runtime.cpp
+++ b/updater/simulator_runtime.cpp
@@ -22,6 +22,8 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <unordered_set>
+
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
@@ -95,3 +97,32 @@
   LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename;
   return true;
 }
+
+bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+                                                  std::string* path) {
+  *path = partition_name;
+  return true;
+}
+
+bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+  LOG(INFO) << "Skip unmapping " << partition_name;
+  return true;
+}
+
+bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+  const std::unordered_set<std::string> commands{
+    "resize",    "remove",       "add",          "move",
+    "add_group", "resize_group", "remove_group", "remove_all_groups",
+  };
+
+  std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
+  for (const auto& line : lines) {
+    if (line.empty() || line[0] == '#') continue;
+    auto tokens = android::base::Split(line, " ");
+    if (commands.find(tokens[0]) == commands.end()) {
+      LOG(ERROR) << "Unknown operation in op_list: " << line;
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp
index 94924e7..019c404 100644
--- a/updater/update_simulator_main.cpp
+++ b/updater/update_simulator_main.cpp
@@ -48,6 +48,7 @@
   RegisterBuiltins();
   RegisterInstallFunctions();
   RegisterBlockImageFunctions();
+  RegisterDynamicPartitionsFunctions();
 
   TemporaryFile temp_saved_source;
   TemporaryFile temp_last_command;
diff --git a/updater/updater_runtime_dynamic_partitions.cpp b/updater/updater_runtime_dynamic_partitions.cpp
new file mode 100644
index 0000000..e9f4c97
--- /dev/null
+++ b/updater/updater_runtime_dynamic_partitions.cpp
@@ -0,0 +1,336 @@
+/*
+ * 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 <algorithm>
+#include <chrono>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/dm.h>
+#include <liblp/builder.h>
+
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::LpMetadata;
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+using android::fs_mgr::PartitionOpener;
+
+static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
+
+static std::string GetSuperDevice() {
+  return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
+}
+
+static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+  auto state = DeviceMapper::Instance().GetState(partition_name);
+  if (state == DmDeviceState::INVALID) {
+    return true;
+  }
+  if (state == DmDeviceState::ACTIVE) {
+    return DestroyLogicalPartition(partition_name, kMapTimeout);
+  }
+  LOG(ERROR) << "Unknown device mapper state: "
+             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+  return false;
+}
+
+bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+                                                std::string* path) {
+  auto state = DeviceMapper::Instance().GetState(partition_name);
+  if (state == DmDeviceState::INVALID) {
+    return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name,
+                                  true /* force writable */, kMapTimeout, path);
+  }
+
+  if (state == DmDeviceState::ACTIVE) {
+    return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
+  }
+  LOG(ERROR) << "Unknown device mapper state: "
+             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+  return false;
+}
+
+bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+  return ::UnmapPartitionOnDeviceMapper(partition_name);
+}
+
+namespace {  // Ops
+
+struct OpParameters {
+  std::vector<std::string> tokens;
+  MetadataBuilder* builder;
+
+  bool ExpectArgSize(size_t size) const {
+    CHECK(!tokens.empty());
+    auto actual = tokens.size() - 1;
+    if (actual != size) {
+      LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
+      return false;
+    }
+    return true;
+  }
+  const std::string& op() const {
+    CHECK(!tokens.empty());
+    return tokens[0];
+  }
+  const std::string& arg(size_t pos) const {
+    CHECK_LE(pos + 1, tokens.size());
+    return tokens[pos + 1];
+  }
+  std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
+    auto str = arg(pos);
+    uint64_t ret;
+    if (!android::base::ParseUint(str, &ret)) {
+      LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
+      return std::nullopt;
+    }
+    return ret;
+  }
+};
+
+using OpFunction = std::function<bool(const OpParameters&)>;
+using OpMap = std::map<std::string, OpFunction>;
+
+bool PerformOpResize(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& partition_name = params.arg(0);
+  auto size = params.uint_arg(1, "size");
+  if (!size.has_value()) return false;
+
+  auto partition = params.builder->FindPartition(partition_name);
+  if (partition == nullptr) {
+    LOG(ERROR) << "Failed to find partition " << partition_name
+               << " in dynamic partition metadata.";
+    return false;
+  }
+  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+    LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
+    return false;
+  }
+  if (!params.builder->ResizePartition(partition, size.value())) {
+    LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
+    return false;
+  }
+  return true;
+}
+
+bool PerformOpRemove(const OpParameters& params) {
+  if (!params.ExpectArgSize(1)) return false;
+  const auto& partition_name = params.arg(0);
+
+  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+    LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
+    return false;
+  }
+  params.builder->RemovePartition(partition_name);
+  return true;
+}
+
+bool PerformOpAdd(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& partition_name = params.arg(0);
+  const auto& group_name = params.arg(1);
+
+  if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
+      nullptr) {
+    LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
+    return false;
+  }
+  return true;
+}
+
+bool PerformOpMove(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& partition_name = params.arg(0);
+  const auto& new_group = params.arg(1);
+
+  auto partition = params.builder->FindPartition(partition_name);
+  if (partition == nullptr) {
+    LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
+               << " because it is not found.";
+    return false;
+  }
+
+  auto old_group = partition->group_name();
+  if (old_group != new_group) {
+    if (!params.builder->ChangePartitionGroup(partition, new_group)) {
+      LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
+                 << " to group " << new_group << ".";
+      return false;
+    }
+  }
+  return true;
+}
+
+bool PerformOpAddGroup(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& group_name = params.arg(0);
+  auto maximum_size = params.uint_arg(1, "maximum_size");
+  if (!maximum_size.has_value()) return false;
+
+  auto group = params.builder->FindGroup(group_name);
+  if (group != nullptr) {
+    LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
+    return false;
+  }
+
+  if (maximum_size.value() == 0) {
+    LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
+  }
+
+  if (!params.builder->AddGroup(group_name, maximum_size.value())) {
+    LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
+               << maximum_size.value() << ".";
+    return false;
+  }
+  return true;
+}
+
+bool PerformOpResizeGroup(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& group_name = params.arg(0);
+  auto new_size = params.uint_arg(1, "maximum_size");
+  if (!new_size.has_value()) return false;
+
+  auto group = params.builder->FindGroup(group_name);
+  if (group == nullptr) {
+    LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
+    return false;
+  }
+
+  auto old_size = group->maximum_size();
+  if (old_size != new_size.value()) {
+    if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
+      LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
+                 << new_size.value() << ".";
+      return false;
+    }
+  }
+  return true;
+}
+
+std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
+                                                   const std::string& group_name) {
+  auto partitions = builder->ListPartitionsInGroup(group_name);
+  std::vector<std::string> partition_names;
+  std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
+                 [](Partition* partition) { return partition->name(); });
+  return partition_names;
+}
+
+bool PerformOpRemoveGroup(const OpParameters& params) {
+  if (!params.ExpectArgSize(1)) return false;
+  const auto& group_name = params.arg(0);
+
+  auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+  if (!partition_names.empty()) {
+    LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
+               << android::base::Join(partition_names, ", ") << "]";
+    return false;
+  }
+  params.builder->RemoveGroupAndPartitions(group_name);
+  return true;
+}
+
+bool PerformOpRemoveAllGroups(const OpParameters& params) {
+  if (!params.ExpectArgSize(0)) return false;
+
+  auto group_names = params.builder->ListGroups();
+  for (const auto& group_name : group_names) {
+    auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+    for (const auto& partition_name : partition_names) {
+      if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+        LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
+                   << ".";
+        return false;
+      }
+    }
+    params.builder->RemoveGroupAndPartitions(group_name);
+  }
+  return true;
+}
+
+}  // namespace
+
+bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+  auto super_device = GetSuperDevice();
+  auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
+  if (builder == nullptr) {
+    LOG(ERROR) << "Failed to load dynamic partition metadata.";
+    return false;
+  }
+
+  static const OpMap op_map{
+    // clang-format off
+    {"resize",                PerformOpResize},
+    {"remove",                PerformOpRemove},
+    {"add",                   PerformOpAdd},
+    {"move",                  PerformOpMove},
+    {"add_group",             PerformOpAddGroup},
+    {"resize_group",          PerformOpResizeGroup},
+    {"remove_group",          PerformOpRemoveGroup},
+    {"remove_all_groups",     PerformOpRemoveAllGroups},
+    // clang-format on
+  };
+
+  std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
+  for (const auto& line : lines) {
+    auto comment_idx = line.find('#');
+    auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
+    op_and_args = android::base::Trim(op_and_args);
+    if (op_and_args.empty()) continue;
+
+    auto tokens = android::base::Split(op_and_args, " ");
+    const auto& op = tokens[0];
+    auto it = op_map.find(op);
+    if (it == op_map.end()) {
+      LOG(ERROR) << "Unknown operation in op_list: " << op;
+      return false;
+    }
+    OpParameters params;
+    params.tokens = tokens;
+    params.builder = builder.get();
+    if (!it->second(params)) {
+      return false;
+    }
+  }
+
+  auto metadata = builder->Export();
+  if (metadata == nullptr) {
+    LOG(ERROR) << "Failed to export metadata.";
+    return false;
+  }
+
+  if (!UpdatePartitionTable(super_device, *metadata, 0)) {
+    LOG(ERROR) << "Failed to write metadata.";
+    return false;
+  }
+
+  return true;
+}