Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8ff84d7c authored by Yifan Hong's avatar Yifan Hong
Browse files

updater: add functions to modify dynamic partition metadata

Test: sideload full OTA on cuttlefish
Test: sideload incremental OTA on cuttlefish (that grows
      system, shrinks vendor, and move vendor to group foo)
Test: verify that /cache/recovery/cc46ebfd04058569d0c6c1431c6af6c1328458e4
      exists (sha1sum of "system")

Bug: 111801737

Change-Id: Ibdf6565bc1b60f3665c01739b4c95a85f0261ae5
parent 7a0b6547
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -26,12 +26,14 @@ cc_defaults {
        "libedify",
        "libotautil",
        "libext4_utils",
        "libdm",
        "libfec",
        "libfec_rs",
        "libverity_tree",
        "libfs_mgr",
        "libgtest_prod",
        "liblog",
        "liblp",
        "libselinux",
        "libsparse",
        "libsquashfs_utils",
@@ -66,6 +68,7 @@ cc_library_static {
    srcs: [
        "blockimg.cpp",
        "commands.cpp",
        "dynamic_partitions.cpp",
        "install.cpp",
    ],

+2 −0
Original line number Diff line number Diff line
@@ -29,12 +29,14 @@ updater_common_static_libraries := \
    libedify \
    libotautil \
    libext4_utils \
    libdm \
    libfec \
    libfec_rs \
    libverity_tree \
    libfs_mgr \
    libgtest_prod \
    liblog \
    liblp \
    libselinux \
    libsparse \
    libsquashfs_utils \
+52 −9
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@
static constexpr size_t BLOCKSIZE = 4096;
static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
static constexpr mode_t STASH_FILE_MODE = 0600;
static constexpr mode_t MARKER_DIRECTORY_MODE = 0700;

static CauseCode failure_type = kNoCause;
static bool is_retry = false;
@@ -167,15 +168,22 @@ static bool UpdateLastCommandIndex(size_t command_index, const std::string& comm
  return true;
}

static bool SetPartitionUpdatedMarker(const std::string& marker) {
bool SetUpdatedMarker(const std::string& marker) {
  auto dirname = android::base::Dirname(marker);
  auto res = mkdir(dirname.c_str(), MARKER_DIRECTORY_MODE);
  if (res == -1 && errno != EEXIST) {
    PLOG(ERROR) << "Failed to create directory for marker: " << dirname;
    return false;
  }

  if (!android::base::WriteStringToFile("", marker)) {
    PLOG(ERROR) << "Failed to write to marker file " << marker;
    return false;
  }
  if (!FsyncDir(android::base::Dirname(marker))) {
  if (!FsyncDir(dirname)) {
    return false;
  }
  LOG(INFO) << "Wrote partition updated marker to " << marker;
  LOG(INFO) << "Wrote updated marker to " << marker;
  return true;
}

@@ -1573,6 +1581,43 @@ using CommandFunction = std::function<int(CommandParameters&)>;

using CommandMap = std::unordered_map<Command::Type, CommandFunction>;

static bool Sha1DevicePath(const std::string& path, uint8_t digest[SHA_DIGEST_LENGTH]) {
  auto device_name = android::base::Basename(path);
  auto dm_target_name_path = "/sys/block/" + device_name + "/dm/name";

  struct stat sb;
  if (stat(dm_target_name_path.c_str(), &sb) == 0) {
    // This is a device mapper target. Use partition name as part of the hash instead. Do not
    // include extents as part of the hash, because the size of a partition may be shrunk after
    // the patches are applied.
    std::string dm_target_name;
    if (!android::base::ReadFileToString(dm_target_name_path, &dm_target_name)) {
      PLOG(ERROR) << "Cannot read " << dm_target_name_path;
      return false;
    }
    SHA1(reinterpret_cast<const uint8_t*>(dm_target_name.data()), dm_target_name.size(), digest);
    return true;
  }

  if (errno != ENOENT) {
    // This is a device mapper target, but its name cannot be retrieved.
    PLOG(ERROR) << "Cannot get dm target name for " << path;
    return false;
  }

  // This doesn't appear to be a device mapper target, but if its name starts with dm-, something
  // else might have gone wrong.
  if (android::base::StartsWith(device_name, "dm-")) {
    LOG(WARNING) << "Device " << path << " starts with dm- but is not mapped by device-mapper.";
  }

  // Stash directory should be different for each partition to avoid conflicts when updating
  // multiple partitions at the same time, so we use the hash of the block device name as the base
  // directory.
  SHA1(reinterpret_cast<const uint8_t*>(path.data()), path.size(), digest);
  return true;
}

static Value* PerformBlockImageUpdate(const char* name, State* state,
                                      const std::vector<std::unique_ptr<Expr>>& argv,
                                      const CommandMap& command_map, bool dryrun) {
@@ -1657,12 +1702,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
    return StringValue("");
  }

  // Stash directory should be different for each partition to avoid conflicts when updating
  // multiple partitions at the same time, so we use the hash of the block device name as the base
  // directory.
  uint8_t digest[SHA_DIGEST_LENGTH];
  SHA1(reinterpret_cast<const uint8_t*>(blockdev_filename->data.data()),
       blockdev_filename->data.size(), digest);
  if (!Sha1DevicePath(blockdev_filename->data, digest)) {
    return StringValue("");
  }
  params.stashbase = print_sha1(digest);

  // Possibly do return early on retry, by checking the marker. If the update on this partition has
@@ -1884,7 +1927,7 @@ pbiudone:
      // Create a marker on /cache partition, which allows skipping the update on this partition on
      // retry. The marker will be removed once booting into normal boot, or before starting next
      // fresh install.
      if (!SetPartitionUpdatedMarker(updated_marker)) {
      if (!SetUpdatedMarker(updated_marker)) {
        LOG(WARNING) << "Failed to set updated marker; continuing";
      }
    }
+435 −0
Original line number Diff line number Diff line
/*
 * 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/dynamic_partitions.h"

#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 "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) {
  if (argv.size() != arg_names.size()) {
    ErrorAbort(state, kArgsParsingFailure, "%s expects %zu arguments, got %zu", name,
               arg_names.size(), argv.size());
    return {};
  }

  std::vector<std::unique_ptr<Value>> args;
  if (!ReadValueArgs(state, argv, &args)) {
    return {};
  }

  CHECK_EQ(args.size(), arg_names.size());

  for (size_t i = 0; i < arg_names.size(); ++i) {
    if (args[i]->type != Value::Type::STRING) {
      ErrorAbort(state, kArgsParsingFailure, "%s argument to %s must be string",
                 arg_names[i].c_str(), name);
      return {};
    }
  }

  std::vector<std::string> ret;
  std::transform(args.begin(), args.end(), std::back_inserter(ret),
                 [](const auto& arg) { return arg->data; });
  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("");
}

Value* MapPartitionFn(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("");

  std::string path;
  bool result = 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

Value* UpdateDynamicPartitionsFn(const char* name, State* state,
                                 const std::vector<std::unique_ptr<Expr>>& argv) {
  if (argv.size() != 1) {
    ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size());
    return StringValue("");
  }
  std::vector<std::unique_ptr<Value>> args;
  if (!ReadValueArgs(state, argv, &args)) {
    return nullptr;
  }
  const std::unique_ptr<Value>& op_list_value = args[0];
  if (op_list_value->type != Value::Type::BLOB) {
    ErrorAbort(state, kArgsParsingFailure, "op_list argument to %s must be blob", name);
    return StringValue("");
  }

  std::string updated_marker = Paths::Get().stash_directory_base() + kMetadataUpdatedMarker;
  if (state->is_retry) {
    struct stat sb;
    int result = stat(updated_marker.c_str(), &sb);
    if (result == 0) {
      LOG(INFO) << "Skipping already updated dynamic partition metadata based on marker";
      return StringValue("t");
    }
  } else {
    // Delete the obsolete marker if any.
    std::string err;
    if (!android::base::RemoveFileIfExists(updated_marker, &err)) {
      LOG(ERROR) << "Failed to remove dynamic partition metadata updated marker " << updated_marker
                 << ": " << err;
      return StringValue("");
    }
  }

  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.";
    return StringValue("");
  }

  if (!SetUpdatedMarker(updated_marker)) {
    LOG(ERROR) << "Failed to set metadata updated marker.";
    return StringValue("");
  }

  return StringValue("t");
}

void RegisterDynamicPartitionsFunctions() {
  RegisterFunction("unmap_partition", UnmapPartitionFn);
  RegisterFunction("map_partition", MapPartitionFn);
  RegisterFunction("update_dynamic_partitions", UpdateDynamicPartitionsFn);
}
+21 −0
Original line number Diff line number Diff line
/*
 * 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>

bool SetUpdatedMarker(const std::string& marker);
Loading