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

Commit f57fd97d authored by David Anderson's avatar David Anderson
Browse files

libsnapshot: Add support for first-to-second-stage transitions of snapuserd.

This patch introduces the fundamentals needed to support booting off
dm-user. First, a method has been added to start snapuserd in
first-stage init. It simply forks and execs, creates a specially named
first-stage socket, then waits for requests.

Next, a new method has been added to SnapshotManager to perform a
second-stage handoff. This works by first launching a second copy of
snapuserd using init's normal service management functionality. The new
snapuserd runs alongside the original, but has correct privileges and a
correct selinux context. Next, we inspect each COW device, and if its
table uses dm-user, we replace the table with a renamed control
device. The new control device is bound to the new snapuserd.

device-mapper guarantees that such a table swap is safe. It flushes I/O
to the old table and then replaces it with the new table. Once the new
table is in place, the old dm-user control devices are automatically
destroyed. Thus, once all dm-user devices has been transitioned, the
first-stage daemon is idle and can gracefully exit.

This patch does not modify init. A few changes will be needed on top of
this patch:

(1) CreateLogicalAndSnapshotPartitions will need further changes to
start the first-stage daemon and track its pid. Additionally, it will
need to ensure the named socket file is deleted, so there is no further
IPC allowed after partitions are completed.
(2) init will need to propagate the pid to second-stage init so the
process can be killed (or signalled).
(3) first-stage snapuserd will need to gracefully exit once it has no
active handler threads.
(4) second-stage init will need to invoke the transition helper on
SnapshotMaanager, ideally as soon as feasible.

Bug: 168259959
Test: manual test
Change-Id: I54dec2edf85ed95f11ab4518eb3d7dbaf0bdcbfd
parent 4e4cff7e
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -31,15 +31,17 @@ cc_defaults {
        "libbrotli",
        "libdm",
        "libfstab",
        "libsnapshot_cow",
        "update_metadata-protos",
    ],
    whole_static_libs: [
        "libbrotli",
        "libcutils",
        "libext2_uuid",
        "libext4_utils",
        "libfstab",
        "libsnapshot_cow",
        "libsnapshot_snapuserd",
        "libz",
    ],
    header_libs: [
        "libchrome",
@@ -432,6 +434,7 @@ cc_binary {
    init_rc: [
        "snapuserd.rc",
    ],
    static_executable: true,
}

cc_binary {
+17 −2
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#pragma once

#include <stdint.h>
#include <unistd.h>

#include <chrono>
#include <map>
@@ -77,6 +78,7 @@ class SnapshotMergeStats;
class SnapshotStatus;

static constexpr const std::string_view kCowGroupName = "cow";
static constexpr char kVirtualAbCompressionProp[] = "ro.virtual_ab.compression.enabled";

bool OptimizeSourceCopyOperation(const chromeos_update_engine::InstallOperation& operation,
                                 chromeos_update_engine::InstallOperation* optimized);
@@ -104,6 +106,7 @@ class ISnapshotManager {
                android::hardware::boot::V1_1::MergeStatus status) = 0;
        virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
        virtual bool IsRecovery() const = 0;
        virtual bool IsTestDevice() const { return false; }
    };
    virtual ~ISnapshotManager() = default;

@@ -303,6 +306,14 @@ class SnapshotManager final : public ISnapshotManager {
    // Helper function for second stage init to restorecon on the rollback indicator.
    static std::string GetGlobalRollbackIndicatorPath();

    // Initiate the transition from first-stage to second-stage snapuserd. This
    // process involves re-creating the dm-user table entries for each device,
    // so that they connect to the new daemon. Once all new tables have been
    // activated, we ask the first-stage daemon to cleanly exit.
    //
    // The caller must pass a function which starts snapuserd.
    bool PerformSecondStageTransition();

    // ISnapshotManager overrides.
    bool BeginUpdate() override;
    bool CancelUpdate() override;
@@ -345,6 +356,7 @@ class SnapshotManager final : public ISnapshotManager {
    FRIEND_TEST(SnapshotTest, Merge);
    FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
    FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
    FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
    FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
    FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
    FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
@@ -372,11 +384,13 @@ class SnapshotManager final : public ISnapshotManager {
    // Ensure we're connected to snapuserd.
    bool EnsureSnapuserdConnected();

    // Helper for first-stage init.
    // Helpers for first-stage init.
    bool ForceLocalImageManager();
    const std::unique_ptr<IDeviceInfo>& device() const { return device_; }

    // Helper function for tests.
    // Helper functions for tests.
    IImageManager* image_manager() const { return images_.get(); }
    void set_use_first_stage_snapuserd(bool value) { use_first_stage_snapuserd_ = value; }

    // Since libsnapshot is included into multiple processes, we flock() our
    // files for simple synchronization. LockedFile is a helper to assist with
@@ -660,6 +674,7 @@ class SnapshotManager final : public ISnapshotManager {
    std::unique_ptr<IDeviceInfo> device_;
    std::unique_ptr<IImageManager> images_;
    bool has_local_image_manager_ = false;
    bool use_first_stage_snapuserd_ = false;
    bool in_factory_data_reset_ = false;
    std::unique_ptr<SnapuserdClient> snapuserd_client_;
};
+8 −0
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@

#pragma once

#include <unistd.h>

#include <chrono>
#include <cstring>
#include <iostream>
@@ -31,9 +33,15 @@ static constexpr uint32_t PACKET_SIZE = 512;
static constexpr char kSnapuserdSocketFirstStage[] = "snapuserd_first_stage";
static constexpr char kSnapuserdSocket[] = "snapuserd";

static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID";

// Ensure that the second-stage daemon for snapuserd is running.
bool EnsureSnapuserdStarted();

// Start the first-stage version of snapuserd, returning its pid. This is used
// by first-stage init, as well as vts_libsnapshot_test. On failure, -1 is returned.
pid_t StartFirstStageSnapuserd();

class SnapuserdClient {
  private:
    android::base::unique_fd sockfd_;
+1 −0
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
        unbootable_slots_.insert(slot);
        return true;
    }
    bool IsTestDevice() const override { return true; }

    bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }

+181 −13
Original line number Diff line number Diff line
@@ -31,8 +31,10 @@
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
#include <fs_mgr/file_wait.h>
#include <fs_mgr_dm_linear.h>
#include <fstab/fstab.h>
#include <libdm/dm.h>
@@ -100,6 +102,12 @@ std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceI
    if (!sm || !sm->ForceLocalImageManager()) {
        return nullptr;
    }

    // The first-stage version of snapuserd is explicitly started by init. Do
    // not attempt to using it during tests (which run in normal AOSP).
    if (!sm->device()->IsTestDevice()) {
        sm->use_first_stage_snapuserd_ = true;
    }
    return sm;
}

@@ -400,8 +408,15 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
        base_sectors = dev_size / kSectorSize;
    }

    // Use an extra decoration for first-stage init, so we can transition
    // to a new table entry in second-stage.
    std::string misc_name = name;
    if (use_first_stage_snapuserd_) {
        misc_name += "-init";
    }

    DmTable table;
    table.Emplace<DmTargetUser>(0, base_sectors, name);
    table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
    if (!dm.CreateDevice(name, table, path, timeout_ms)) {
        return false;
    }
@@ -410,7 +425,7 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
        return false;
    }

    auto control_device = "/dev/dm-user/" + name;
    auto control_device = "/dev/dm-user/" + misc_name;
    return snapuserd_client_->InitializeSnapuserd(cow_file, base_device, control_device);
}

@@ -1284,6 +1299,107 @@ bool SnapshotManager::HandleCancelledUpdate(LockedFile* lock,
    return RemoveAllUpdateState(lock, before_cancel);
}

bool SnapshotManager::PerformSecondStageTransition() {
    LOG(INFO) << "Performing second-stage transition for snapuserd.";

    // Don't use EnsuerSnapuserdConnected() because this is called from init,
    // and attempting to do so will deadlock.
    if (!snapuserd_client_) {
        snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
        if (!snapuserd_client_) {
            LOG(ERROR) << "Unable to connect to snapuserd";
            return false;
        }
    }

    auto& dm = DeviceMapper::Instance();

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

    std::vector<std::string> snapshots;
    if (!ListSnapshots(lock.get(), &snapshots)) {
        LOG(ERROR) << "Failed to list snapshots.";
        return false;
    }

    size_t num_cows = 0;
    size_t ok_cows = 0;
    for (const auto& snapshot : snapshots) {
        std::string cow_name = GetDmUserCowName(snapshot);
        if (dm.GetState(cow_name) == DmDeviceState::INVALID) {
            continue;
        }

        DeviceMapper::TargetInfo target;
        if (!GetSingleTarget(cow_name, TableQuery::Table, &target)) {
            continue;
        }

        auto target_type = DeviceMapper::GetTargetType(target.spec);
        if (target_type != "user") {
            LOG(ERROR) << "Unexpected target type for " << cow_name << ": " << target_type;
            continue;
        }

        num_cows++;

        DmTable table;
        table.Emplace<DmTargetUser>(0, target.spec.length, cow_name);
        if (!dm.LoadTableAndActivate(cow_name, table)) {
            LOG(ERROR) << "Unable to swap tables for " << cow_name;
            continue;
        }

        std::string backing_device;
        if (!dm.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &backing_device)) {
            LOG(ERROR) << "Could not get device path for " << GetBaseDeviceName(snapshot);
            continue;
        }

        std::string cow_device;
        if (!dm.GetDmDevicePathByName(GetCowName(snapshot), &cow_device)) {
            LOG(ERROR) << "Could not get device path for " << GetCowName(snapshot);
            continue;
        }

        // Wait for ueventd to acknowledge and create the control device node.
        std::string control_device = "/dev/dm-user/" + cow_name;
        if (!android::fs_mgr::WaitForFile(control_device, 10s)) {
            LOG(ERROR) << "Could not find control device: " << control_device;
            continue;
        }

        if (!snapuserd_client_->InitializeSnapuserd(cow_device, backing_device, control_device)) {
            // This error is unrecoverable. We cannot proceed because reads to
            // the underlying device will fail.
            LOG(FATAL) << "Could not initialize snapuserd for " << cow_name;
            return false;
        }

        ok_cows++;
    }

    if (ok_cows != num_cows) {
        LOG(ERROR) << "Could not transition all snapuserd consumers.";
        return false;
    }

    int pid;
    const char* pid_str = getenv(kSnapuserdFirstStagePidVar);
    if (pid_str && android::base::ParseInt(pid_str, &pid)) {
        if (kill(pid, SIGTERM) < 0 && errno != ESRCH) {
            LOG(ERROR) << "kill snapuserd failed";
            return false;
        }
    } else {
        LOG(ERROR) << "Could not find or parse " << kSnapuserdFirstStagePidVar
                   << " for snapuserd pid";
        return false;
    }
    return true;
}

std::unique_ptr<LpMetadata> SnapshotManager::ReadCurrentMetadata() {
    const auto& opener = device_->GetPartitionOpener();
    uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
@@ -1621,6 +1737,15 @@ bool SnapshotManager::CreateLogicalAndSnapshotPartitions(
        }
    }

    if (use_first_stage_snapuserd_) {
        // Remove the first-stage socket as a precaution, there is no need to
        // access the daemon anymore and we'll be killing it once second-stage
        // is running.
        auto socket = ANDROID_SOCKET_DIR + "/"s + kSnapuserdSocketFirstStage;
        snapuserd_client_ = nullptr;
        unlink(socket.c_str());
    }

    LOG(INFO) << "Created logical partitions with snapshot.";
    return true;
}
@@ -1925,10 +2050,18 @@ bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name)
            LOG(ERROR) << "Cannot unmap " << dm_user_name;
            return false;
        }
        if (!snapuserd_client_->WaitForDeviceDelete("/dev/dm-user/" + dm_user_name)) {

        auto control_device = "/dev/dm-user/" + dm_user_name;
        if (!snapuserd_client_->WaitForDeviceDelete(control_device)) {
            LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
            return false;
        }

        // Ensure the control device is gone so we don't run into ABA problems.
        if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
            LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
            return false;
        }
    }

    auto cow_name = GetCowName(name);
@@ -2212,16 +2345,36 @@ bool SnapshotManager::EnsureImageManager() {
}

bool SnapshotManager::EnsureSnapuserdConnected() {
    if (!snapuserd_client_) {
    if (snapuserd_client_) {
        return true;
    }

    std::string socket;
    if (use_first_stage_snapuserd_) {
        auto pid = StartFirstStageSnapuserd();
        if (pid < 0) {
            LOG(ERROR) << "Failed to start snapuserd";
            return false;
        }

        auto pid_str = std::to_string(static_cast<int>(pid));
        if (setenv(kSnapuserdFirstStagePidVar, pid_str.c_str(), 1) < 0) {
            PLOG(ERROR) << "setenv failed storing the snapuserd pid";
        }

        socket = kSnapuserdSocketFirstStage;
    } else {
        if (!EnsureSnapuserdStarted()) {
            return false;
        }
        snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
        socket = kSnapuserdSocket;
    }

    snapuserd_client_ = SnapuserdClient::Connect(socket, 10s);
    if (!snapuserd_client_) {
        LOG(ERROR) << "Unable to connect to snapuserd";
        return false;
    }
    }
    return true;
}

@@ -2538,12 +2691,27 @@ Return SnapshotManager::InitializeUpdateSnapshots(
            return Return::Error();
        }

        auto ret = InitializeCow(cow_path);
        if (IsCompressionEnabled()) {
            unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
            if (fd < 0) {
                PLOG(ERROR) << "open " << cow_path << " failed for snapshot "
                            << cow_params.partition_name;
                return Return::Error();
            }

            CowWriter writer(CowOptions{});
            if (!writer.Initialize(fd) || !writer.Finalize()) {
                LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
                return Return::Error();
            }
        } else {
            auto ret = InitializeKernelCow(cow_path);
            if (!ret.is_ok()) {
                LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
                           << cow_path;
                return AddRequiredSpace(ret, all_snapshot_status);
            }
        }
        // Let destructor of created_devices_for_cow to unmap the COW devices.
    };
    return Return::Ok();
Loading