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

Commit 2a7592bf authored by Yifan Hong's avatar Yifan Hong Committed by android-build-merger
Browse files

Merge "libsnapshot: Add helper for first-stage init mounting"

am: ee9b49d7

Change-Id: Id35392b3366503bf6f2e25974e132e8c78323fd1
parents a0ff368b ee9b49d7
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -212,6 +212,17 @@ bool CreateLogicalPartition(const CreateLogicalPartitionParams& params, std::str
    return true;
}

std::string CreateLogicalPartitionParams::GetDeviceName() const {
    if (!device_name.empty()) return device_name;
    return GetPartitionName();
}

std::string CreateLogicalPartitionParams::GetPartitionName() const {
    if (!partition_name.empty()) return partition_name;
    if (partition) return android::fs_mgr::GetPartitionName(*partition);
    return "<unknown partition>";
}

bool UnmapDevice(const std::string& name) {
    DeviceMapper& dm = DeviceMapper::Instance();
    if (!dm.DeleteDevice(name)) {
+4 −0
Original line number Diff line number Diff line
@@ -77,6 +77,10 @@ struct CreateLogicalPartitionParams {
    // If non-null, this will use the specified IPartitionOpener rather than
    // the default one.
    const IPartitionOpener* partition_opener = nullptr;

    // Helpers for determining the effective partition and device name.
    std::string GetPartitionName() const;
    std::string GetDeviceName() const;
};

bool CreateLogicalPartition(const CreateLogicalPartitionParams& params, std::string* path);
+2 −0
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ cc_defaults {
    ],
    static_libs: [
        "libdm",
        "libfs_mgr",
        "liblp",
    ],
    whole_static_libs: [
        "libext2_uuid",
+37 −3
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <android-base/unique_fd.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
#include <liblp/liblp.h>

#ifndef FRIEND_TEST
#define FRIEND_TEST(test_set_name, individual_test) \
@@ -37,6 +38,11 @@ namespace fiemap {
class IImageManager;
}  // namespace fiemap

namespace fs_mgr {
struct CreateLogicalPartitionParams;
class IPartitionOpener;
}  // namespace fs_mgr

namespace snapshot {

enum class UpdateState : unsigned int {
@@ -64,6 +70,10 @@ 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;

  public:
    // Dependency injection for testing.
    class IDeviceInfo {
@@ -72,6 +82,7 @@ class SnapshotManager final {
        virtual std::string GetGsidDir() const = 0;
        virtual std::string GetMetadataDir() const = 0;
        virtual std::string GetSlotSuffix() const = 0;
        virtual const IPartitionOpener& GetPartitionOpener() const = 0;
    };

    ~SnapshotManager();
@@ -81,6 +92,14 @@ class SnapshotManager final {
    // instance will be created.
    static std::unique_ptr<SnapshotManager> New(IDeviceInfo* device = nullptr);

    // This is similar to New(), except designed specifically for first-stage
    // init.
    static std::unique_ptr<SnapshotManager> NewForFirstStageMount(IDeviceInfo* device = nullptr);

    // Helper function for first-stage init to check whether a SnapshotManager
    // might be needed to perform first-stage mounts.
    static bool IsSnapshotManagerNeeded();

    // Begin an update. This must be called before creating any snapshots. It
    // will fail if GetUpdateState() != None.
    bool BeginUpdate();
@@ -126,13 +145,24 @@ class SnapshotManager final {
    //   Other: 0
    UpdateState GetUpdateState(double* progress = nullptr);

    // If this returns true, first-stage mount must call
    // CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
    bool NeedSnapshotsInFirstStageMount();

    // Perform first-stage mapping of snapshot targets. This replaces init's
    // call to CreateLogicalPartitions when snapshots are present.
    bool CreateLogicalAndSnapshotPartitions(const std::string& super_device);

  private:
    FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
    FRIEND_TEST(SnapshotTest, CreateSnapshot);
    FRIEND_TEST(SnapshotTest, MapSnapshot);
    FRIEND_TEST(SnapshotTest, FirstStageMountAfterRollback);
    FRIEND_TEST(SnapshotTest, FirstStageMountAndMerge);
    FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
    FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
    FRIEND_TEST(SnapshotTest, MapSnapshot);
    FRIEND_TEST(SnapshotTest, Merge);
    FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
    FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
    friend class SnapshotTest;

    using DmTargetSnapshot = android::dm::DmTargetSnapshot;
@@ -141,9 +171,12 @@ class SnapshotManager final {

    explicit SnapshotManager(IDeviceInfo* info);

    // This is created lazily since it connects via binder.
    // This is created lazily since it can connect via binder.
    bool EnsureImageManager();

    // Helper for first-stage init.
    bool ForceLocalImageManager();

    // Helper function for tests.
    IImageManager* image_manager() const { return images_.get(); }

@@ -278,6 +311,7 @@ class SnapshotManager final {
    std::string metadata_dir_;
    std::unique_ptr<IDeviceInfo> device_;
    std::unique_ptr<IImageManager> images_;
    bool has_local_image_manager_ = false;
};

}  // namespace snapshot
+169 −7
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <sys/unistd.h>

#include <thread>
#include <unordered_set>

#include <android-base/file.h>
#include <android-base/logging.h>
@@ -27,9 +28,11 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr_dm_linear.h>
#include <fstab/fstab.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
#include <liblp/liblp.h>

namespace android {
namespace snapshot {
@@ -43,22 +46,30 @@ using android::dm::DmTargetSnapshot;
using android::dm::kSectorSize;
using android::dm::SnapshotStorageMode;
using android::fiemap::IImageManager;
using android::fs_mgr::CreateLogicalPartition;
using android::fs_mgr::CreateLogicalPartitionParams;
using android::fs_mgr::GetPartitionName;
using android::fs_mgr::LpMetadata;
using android::fs_mgr::SlotNumberForSlotSuffix;
using namespace std::chrono_literals;
using namespace std::string_literals;

// Unit is sectors, this is a 4K chunk.
static constexpr uint32_t kSnapshotChunkSize = 8;

static constexpr char kSnapshotBootIndicatorFile[] = "snapshot-boot";
static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";

class DeviceInfo final : public SnapshotManager::IDeviceInfo {
  public:
    std::string GetGsidDir() const override { return "ota"s; }
    std::string GetMetadataDir() const override { return "/metadata/ota"s; }
    std::string GetSlotSuffix() const override { return fs_mgr_get_slot_suffix(); }
    const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const { return opener_; }

  private:
    android::fs_mgr::PartitionOpener opener_;
};

// Note: IIMageManager is an incomplete type in the header, so the default
// Note: IImageManager is an incomplete type in the header, so the default
// destructor doesn't work.
SnapshotManager::~SnapshotManager() {}

@@ -69,6 +80,14 @@ std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
    return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
}

std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) {
    auto sm = New(info);
    if (!sm || !sm->ForceLocalImageManager()) {
        return nullptr;
    }
    return sm;
}

SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
    gsid_dir_ = device_->GetGsidDir();
    metadata_dir_ = device_->GetMetadataDir();
@@ -78,6 +97,10 @@ static std::string GetCowName(const std::string& snapshot_name) {
    return snapshot_name + "-cow";
}

static std::string GetBaseDeviceName(const std::string& partition_name) {
    return partition_name + "-base";
}

bool SnapshotManager::BeginUpdate() {
    auto file = LockExclusive();
    if (!file) return false;
@@ -197,8 +220,8 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
    }

    // Validate the block device size, as well as the requested snapshot size.
    // During this we also compute the linear sector region if any.
    {
    // Note that during first-stage init, we don't have the device paths.
    if (android::base::StartsWith(base_device, "/")) {
        unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
        if (fd < 0) {
            PLOG(ERROR) << "open failed: " << base_device;
@@ -228,8 +251,17 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,

    auto cow_name = GetCowName(name);

    bool ok;
    std::string cow_dev;
    if (!images_->MapImageDevice(cow_name, timeout_ms, &cow_dev)) {
    if (has_local_image_manager_) {
        // If we forced a local image manager, it means we don't have binder,
        // which means first-stage init. We must use device-mapper.
        const auto& opener = device_->GetPartitionOpener();
        ok = images_->MapImageWithDeviceMapper(opener, cow_name, &cow_dev);
    } else {
        ok = images_->MapImageDevice(cow_name, timeout_ms, &cow_dev);
    }
    if (!ok) {
        LOG(ERROR) << "Could not map image device: " << cow_name;
        return false;
    }
@@ -705,7 +737,7 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::
}

std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
    return metadata_dir_ + "/" + kSnapshotBootIndicatorFile;
    return metadata_dir_ + "/" + android::base::Basename(kBootIndicatorPath);
}

void SnapshotManager::RemoveSnapshotBootIndicator() {
@@ -931,6 +963,126 @@ bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>*
    return true;
}

bool SnapshotManager::IsSnapshotManagerNeeded() {
    return access(kBootIndicatorPath, F_OK) == 0;
}

bool SnapshotManager::NeedSnapshotsInFirstStageMount() {
    // If we fail to read, we'll wind up using CreateLogicalPartitions, which
    // will create devices that look like the old slot, except with extra
    // content at the end of each device. This will confuse dm-verity, and
    // ultimately we'll fail to boot. Why not make it a fatal error and have
    // the reason be clearer? Because the indicator file still exists, and
    // if this was FATAL, reverting to the old slot would be broken.
    std::string old_slot;
    auto boot_file = GetSnapshotBootIndicatorPath();
    if (!android::base::ReadFileToString(boot_file, &old_slot)) {
        PLOG(ERROR) << "Unable to read the snapshot indicator file: " << boot_file;
        return false;
    }
    if (device_->GetSlotSuffix() == old_slot) {
        LOG(INFO) << "Detected slot rollback, will not mount snapshots.";
        return false;
    }

    // If we can't read the update state, it's unlikely anything else will
    // succeed, so this is a fatal error. We'll eventually exhaust boot
    // attempts and revert to the old slot.
    auto lock = LockShared();
    if (!lock) {
        LOG(FATAL) << "Could not read update state to determine snapshot status";
        return false;
    }
    switch (ReadUpdateState(lock.get())) {
        case UpdateState::Unverified:
        case UpdateState::Merging:
        case UpdateState::MergeFailed:
            return true;
        default:
            return false;
    }
}

bool SnapshotManager::CreateLogicalAndSnapshotPartitions(const std::string& super_device) {
    LOG(INFO) << "Creating logical partitions with snapshots as needed";

    auto lock = LockExclusive();
    if (!lock) return false;

    std::vector<std::string> snapshot_list;
    if (!ListSnapshots(lock.get(), &snapshot_list)) {
        return false;
    }

    std::unordered_set<std::string> live_snapshots;
    for (const auto& snapshot : snapshot_list) {
        SnapshotStatus status;
        if (!ReadSnapshotStatus(lock.get(), snapshot, &status)) {
            return false;
        }
        if (status.state != SnapshotState::MergeCompleted) {
            live_snapshots.emplace(snapshot);
        }
    }

    const auto& opener = device_->GetPartitionOpener();
    uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
    auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
    if (!metadata) {
        LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
        return false;
    }

    // Map logical partitions.
    auto& dm = DeviceMapper::Instance();
    for (const auto& partition : metadata->partitions) {
        auto partition_name = GetPartitionName(partition);
        if (!partition.num_extents) {
            LOG(INFO) << "Skipping zero-length logical partition: " << partition_name;
            continue;
        }

        CreateLogicalPartitionParams params = {
                .block_device = super_device,
                .metadata = metadata.get(),
                .partition = &partition,
                .partition_opener = &opener,
        };

        if (auto iter = live_snapshots.find(partition_name); iter != live_snapshots.end()) {
            // If the device has a snapshot, it'll need to be writable, and
            // we'll need to create the logical partition with a marked-up name
            // (since the snapshot will use the partition name).
            params.force_writable = true;
            params.device_name = GetBaseDeviceName(partition_name);
        }

        std::string ignore_path;
        if (!CreateLogicalPartition(params, &ignore_path)) {
            LOG(ERROR) << "Could not create logical partition " << partition_name << " as device "
                       << params.GetDeviceName();
            return false;
        }
        if (!params.force_writable) {
            // No snapshot.
            continue;
        }

        // We don't have ueventd in first-stage init, so use device major:minor
        // strings instead.
        std::string base_device;
        if (!dm.GetDeviceString(params.GetDeviceName(), &base_device)) {
            LOG(ERROR) << "Could not determine major/minor for: " << params.GetDeviceName();
            return false;
        }
        if (!MapSnapshot(lock.get(), partition_name, base_device, {}, &ignore_path)) {
            LOG(ERROR) << "Could not map snapshot for partition: " << partition_name;
            return false;
        }
    }
    return true;
}

auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags)
        -> std::unique_ptr<LockedFile> {
    unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660));
@@ -1173,5 +1325,15 @@ bool SnapshotManager::EnsureImageManager() {
    return true;
}

bool SnapshotManager::ForceLocalImageManager() {
    images_ = android::fiemap::ImageManager::Open(gsid_dir_);
    if (!images_) {
        LOG(ERROR) << "Could not open ImageManager";
        return false;
    }
    has_local_image_manager_ = true;
    return true;
}

}  // namespace snapshot
}  // namespace android
Loading