summaryrefslogblamecommitdiffstats
path: root/updater/updater_runtime_dynamic_partitions.cpp
blob: be9250a81ecf2808731d35ba952d978d23dc211c (plain) (tree)




































                                                                           
                                                    

















                                                                             
                                                   









                                                                                  







                                                











































































































































































































































































                                                                                                    
/*
 * 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::CreateLogicalPartitionParams;
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);
  }
  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) {
    CreateLogicalPartitionParams params = {
      .block_device = GetSuperDevice(),
      .metadata_slot = 0,
      .partition_name = partition_name,
      .force_writable = true,
      .timeout_ms = kMapTimeout,
    };
    return CreateLogicalPartition(params, 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;
}