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

Commit c69029f2 authored by Yifan Hong's avatar Yifan Hong
Browse files

libsnapshot: APIs for all partitions

Add CreateCowForUpdate / MapSnapshotDevicesForUpdate
that update_engine and init can call them directly.

Bug: 134536978
Test: libsnapshot_test

Change-Id: If53c48855931db27454fd2893745915c77fd37f8
parent c02509b5
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -40,6 +40,10 @@ bool LinearExtent::AddTo(LpMetadata* out) const {
    return true;
}

Interval LinearExtent::AsInterval() const {
    return Interval(device_index(), physical_sector(), end_sector());
}

bool ZeroExtent::AddTo(LpMetadata* out) const {
    out->extents.emplace_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0, 0});
    return true;
@@ -96,6 +100,20 @@ void Partition::ShrinkTo(uint64_t aligned_size) {
    DCHECK(size_ == aligned_size);
}

Partition Partition::GetBeginningExtents(uint64_t aligned_size) const {
    Partition p(name_, group_name_, attributes_);
    for (const auto& extent : extents_) {
        auto le = extent->AsLinearExtent();
        if (le) {
            p.AddExtent(std::make_unique<LinearExtent>(*le));
        } else {
            p.AddExtent(std::make_unique<ZeroExtent>(extent->num_sectors()));
        }
    }
    p.ShrinkTo(aligned_size);
    return p;
}

uint64_t Partition::BytesOnDisk() const {
    uint64_t sectors = 0;
    for (const auto& extent : extents_) {
@@ -602,6 +620,10 @@ std::vector<Interval> Interval::Intersect(const std::vector<Interval>& a,
    return ret;
}

std::unique_ptr<Extent> Interval::AsExtent() const {
    return std::make_unique<LinearExtent>(length(), device_index, start);
}

bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size,
                                    const std::vector<Interval>& free_region_hint) {
    uint64_t space_needed = aligned_size - partition->size();
@@ -1168,5 +1190,9 @@ std::string MetadataBuilder::GetBlockDevicePartitionName(uint64_t index) const {
                   : "";
}

uint64_t MetadataBuilder::logical_block_size() const {
    return geometry_.logical_block_size;
}

}  // namespace fs_mgr
}  // namespace android
+15 −2
Original line number Diff line number Diff line
@@ -33,10 +33,11 @@ namespace android {
namespace fs_mgr {

class LinearExtent;
struct Interval;

// By default, partitions are aligned on a 1MiB boundary.
static const uint32_t kDefaultPartitionAlignment = 1024 * 1024;
static const uint32_t kDefaultBlockSize = 4096;
static constexpr uint32_t kDefaultPartitionAlignment = 1024 * 1024;
static constexpr uint32_t kDefaultBlockSize = 4096;

// Name of the default group in a metadata.
static constexpr std::string_view kDefaultGroup = "default";
@@ -74,6 +75,8 @@ class LinearExtent final : public Extent {
        return sector >= physical_sector_ && sector < end_sector();
    }

    Interval AsInterval() const;

  private:
    uint32_t device_index_;
    uint64_t physical_sector_;
@@ -127,6 +130,12 @@ class Partition final {
    const std::vector<std::unique_ptr<Extent>>& extents() const { return extents_; }
    uint64_t size() const { return size_; }

    // Return a copy of *this, but with extents that includes only the first
    // |aligned_size| bytes. |aligned_size| should be aligned to
    // logical_block_size() of the MetadataBuilder that this partition belongs
    // to.
    Partition GetBeginningExtents(uint64_t aligned_size) const;

  private:
    void ShrinkTo(uint64_t aligned_size);
    void set_group_name(std::string_view group_name) { group_name_ = group_name; }
@@ -156,6 +165,8 @@ struct Interval {
        return (start == other.start) ? end < other.end : start < other.start;
    }

    std::unique_ptr<Extent> AsExtent() const;

    // Intersect |a| with |b|.
    // If no intersection, result has 0 length().
    static Interval Intersect(const Interval& a, const Interval& b);
@@ -325,6 +336,8 @@ class MetadataBuilder {
    // Return the list of free regions not occupied by extents in the metadata.
    std::vector<Interval> GetFreeRegions() const;

    uint64_t logical_block_size() const;

  private:
    MetadataBuilder();
    MetadataBuilder(const MetadataBuilder&) = delete;
+6 −0
Original line number Diff line number Diff line
@@ -25,10 +25,12 @@ cc_defaults {
    shared_libs: [
        "libbase",
        "liblog",
        "liblp",
    ],
    static_libs: [
        "libdm",
        "libfs_mgr",
        "libfstab",
        "liblp",
    ],
    whole_static_libs: [
@@ -49,6 +51,7 @@ filegroup {
    name: "libsnapshot_sources",
    srcs: [
        "snapshot.cpp",
        "partition_cow_creator.cpp",
        "utility.cpp",
    ],
}
@@ -77,6 +80,7 @@ cc_test {
    defaults: ["libsnapshot_defaults"],
    srcs: [
        "snapshot_test.cpp",
        "partition_cow_creator_test.cpp",
        "test_helpers.cpp",
    ],
    shared_libs: [
@@ -90,5 +94,7 @@ cc_test {
        "libgmock",
        "liblp",
        "libsnapshot",
        "libsparse",
        "libz",
    ],
}
+29 −1
Original line number Diff line number Diff line
@@ -17,13 +17,16 @@
#include <stdint.h>

#include <chrono>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include <android-base/unique_fd.h>
#include <fs_mgr_dm_linear.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
#include <liblp/builder.h>
#include <liblp/liblp.h>

#ifndef FRIEND_TEST
@@ -45,6 +48,10 @@ class IPartitionOpener;

namespace snapshot {

struct AutoDeleteCowImage;
struct AutoDeleteSnapshot;
struct PartitionCowCreator;

enum class UpdateState : unsigned int {
    // No update or merge is in progress.
    None,
@@ -75,8 +82,9 @@ enum class UpdateState : unsigned int {

class SnapshotManager final {
    using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
    using LpMetadata = android::fs_mgr::LpMetadata;
    using IPartitionOpener = android::fs_mgr::IPartitionOpener;
    using LpMetadata = android::fs_mgr::LpMetadata;
    using MetadataBuilder = android::fs_mgr::MetadataBuilder;

  public:
    // Dependency injection for testing.
@@ -153,6 +161,20 @@ class SnapshotManager final {
    //   Other: 0
    UpdateState GetUpdateState(double* progress = nullptr);

    // Create necessary COW device / files for OTA clients. New logical partitions will be added to
    // group "cow" in target_metadata. Regions of partitions of current_metadata will be
    // "write-protected" and snapshotted.
    bool CreateUpdateSnapshots(MetadataBuilder* target_metadata, const std::string& target_suffix,
                               MetadataBuilder* current_metadata, const std::string& current_suffix,
                               const std::map<std::string, uint64_t>& cow_sizes);

    // Map a snapshotted partition for OTA clients to write to. Write-protected regions are
    // determined previously in CreateSnapshots.
    bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path);

    // Unmap a snapshot device that's previously mapped with MapUpdateSnapshot.
    bool UnmapUpdateSnapshot(const std::string& target_partition_name);

    // If this returns true, first-stage mount must call
    // CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
    bool NeedSnapshotsInFirstStageMount();
@@ -174,6 +196,9 @@ class SnapshotManager final {
    FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
    FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
    friend class SnapshotTest;
    friend struct AutoDeleteCowImage;
    friend struct AutoDeleteSnapshot;
    friend struct PartitionCowCreator;

    using DmTargetSnapshot = android::dm::DmTargetSnapshot;
    using IImageManager = android::fiemap::IImageManager;
@@ -344,6 +369,9 @@ class SnapshotManager final {
    bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params,
                                  std::string* path);

    // The reverse of MapPartitionWithSnapshot.
    bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);

    std::string gsid_dir_;
    std::string metadata_dir_;
    std::unique_ptr<IDeviceInfo> device_;
+148 −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 "partition_cow_creator.h"

#include <math.h>

#include <android-base/logging.h>

#include "utility.h"

using android::fs_mgr::Extent;
using android::fs_mgr::kDefaultBlockSize;
using android::fs_mgr::Partition;

namespace android {
namespace snapshot {

// Round |d| up to a multiple of |block_size|.
static uint64_t RoundUp(double d, uint64_t block_size) {
    uint64_t ret = ((uint64_t)ceil(d) + block_size - 1) / block_size * block_size;
    CHECK(ret >= d) << "Can't round " << d << " up to a multiple of " << block_size;
    return ret;
}

// Intersect two linear extents. If no intersection, return an extent with length 0.
static std::unique_ptr<Extent> Intersect(Extent* target_extent, Extent* existing_extent) {
    // Convert target_extent and existing_extent to linear extents. Zero extents
    // doesn't matter and doesn't result in any intersection.
    auto existing_linear_extent = existing_extent->AsLinearExtent();
    if (!existing_extent) return nullptr;

    auto target_linear_extent = target_extent->AsLinearExtent();
    if (!target_linear_extent) return nullptr;

    return android::fs_mgr::Interval::Intersect(target_linear_extent->AsInterval(),
                                                existing_linear_extent->AsInterval())
            .AsExtent();
}

// Check that partition |p| contains |e| fully. Both of them should
// be from |target_metadata|.
// Returns true as long as |e| is a subrange of any extent of |p|.
bool PartitionCowCreator::HasExtent(Partition* p, Extent* e) {
    for (auto& partition_extent : p->extents()) {
        auto intersection = Intersect(partition_extent.get(), e);
        if (intersection != nullptr && intersection->num_sectors() == e->num_sectors()) {
            return true;
        }
    }
    return false;
}

// Return the number of sectors, N, where |target_partition|[0..N] (from
// |target_metadata|) are the sectors that should be snapshotted. N is computed
// so that this range of sectors are used by partitions in |current_metadata|.
//
// The client code (update_engine) should have computed target_metadata by
// resizing partitions of current_metadata, so only the first N sectors should
// be snapshotted, not a range with start index != 0.
//
// Note that if partition A has shrunk and partition B has grown, the new
// extents of partition B may use the empty space that was used by partition A.
// In this case, that new extent cannot be written directly, as it may be used
// by the running system. Hence, all extents of the new partition B must be
// intersected with all old partitions (including old partition A and B) to get
// the region that needs to be snapshotted.
std::optional<uint64_t> PartitionCowCreator::GetSnapshotSize() {
    // Compute the number of sectors that needs to be snapshotted.
    uint64_t snapshot_sectors = 0;
    std::vector<std::unique_ptr<Extent>> intersections;
    for (const auto& extent : target_partition->extents()) {
        for (auto* existing_partition :
             ListPartitionsWithSuffix(current_metadata, current_suffix)) {
            for (const auto& existing_extent : existing_partition->extents()) {
                auto intersection = Intersect(extent.get(), existing_extent.get());
                if (intersection != nullptr && intersection->num_sectors() > 0) {
                    snapshot_sectors += intersection->num_sectors();
                    intersections.emplace_back(std::move(intersection));
                }
            }
        }
    }
    uint64_t snapshot_size = snapshot_sectors * kSectorSize;

    // Sanity check that all recorded intersections are indeed within
    // target_partition[0..snapshot_sectors].
    Partition target_partition_snapshot = target_partition->GetBeginningExtents(snapshot_size);
    for (const auto& intersection : intersections) {
        if (!HasExtent(&target_partition_snapshot, intersection.get())) {
            auto linear_intersection = intersection->AsLinearExtent();
            LOG(ERROR) << "Extent "
                       << (linear_intersection
                                   ? (std::to_string(linear_intersection->physical_sector()) + "," +
                                      std::to_string(linear_intersection->end_sector()))
                                   : "")
                       << " is not part of Partition " << target_partition->name() << "[0.."
                       << snapshot_size
                       << "]. The metadata wasn't constructed correctly. This should not happen.";
            return std::nullopt;
        }
    }

    return snapshot_size;
}

std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
    static constexpr double kCowEstimateFactor = 1.05;

    CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
          target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);

    Return ret;
    ret.snapshot_status.device_size = target_partition->size();

    auto snapshot_size = GetSnapshotSize();
    if (!snapshot_size.has_value()) return std::nullopt;

    ret.snapshot_status.snapshot_size = *snapshot_size;

    // TODO: always read from cow_size when the COW size is written in
    // update package. kCowEstimateFactor is good for prototyping but
    // we can't use that in production.
    if (!cow_size.has_value()) {
        cow_size =
                RoundUp(ret.snapshot_status.snapshot_size * kCowEstimateFactor, kDefaultBlockSize);
    }

    // TODO: create COW partition in target_metadata to save space.
    ret.snapshot_status.cow_partition_size = 0;
    ret.snapshot_status.cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size;

    return ret;
}

}  // namespace snapshot
}  // namespace android
Loading