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

Commit 8ef2a47c authored by Akilesh Kailash's avatar Akilesh Kailash Committed by Kalesh Singh
Browse files

snapuserd: Update verification



Initiate update verification from daemon. This will help
in two ways:

1: We avoid reading everything into page-cache. Since,
low end devices are already short on memory, we don't
want to read and populate page-cache which can slow
down boot.

2: During boot, once the selinux transition is done, daemon
is all ready to kick off the verification since verity is
already setup. Note that we are still guarded by update_verifier.
Update_verifier will still block marking new slot as
boot success until the verification is completed. So, there
is no change in the behavior.

Bug: 193863442
Bug: 261913544
Test: Full and incremental OTA on Pixel 6

Incremental OTA of 500M (Monthly OTA)

Boot-time (Without this patch): 38 seconds
Boot-time (With this patch): 32 seconds

Full OTA of 2.2G:

Boot-time (Without this patch): 27 seconds
Boot-time (With this patch): 21 seconds

Signed-off-by: default avatarAkilesh Kailash <akailash@google.com>
Merged-In: I4f17db19bdd0dd261902c670be6212862d861fe1
Change-Id: I4f17db19bdd0dd261902c670be6212862d861fe1
parent 4762c22a
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line Diff line number Diff line
@@ -47,6 +47,7 @@ cc_library_static {
        "libbase",
        "libbase",
        "liblog",
        "liblog",
    ],
    ],
    export_include_dirs: ["include"],
    ramdisk_available: true,
    ramdisk_available: true,
}
}


@@ -68,6 +69,7 @@ cc_defaults {
        "user-space-merge/snapuserd_readahead.cpp",
        "user-space-merge/snapuserd_readahead.cpp",
        "user-space-merge/snapuserd_transitions.cpp",
        "user-space-merge/snapuserd_transitions.cpp",
        "user-space-merge/snapuserd_server.cpp",
        "user-space-merge/snapuserd_server.cpp",
        "user-space-merge/snapuserd_verify.cpp",
    ],
    ],


    cflags: [
    cflags: [
@@ -88,6 +90,11 @@ cc_defaults {
        "libext4_utils",
        "libext4_utils",
        "liburing",
        "liburing",
    ],
    ],

    header_libs: [
        "libstorage_literals_headers",
    ],

    include_dirs: ["bionic/libc/kernel"],
    include_dirs: ["bionic/libc/kernel"],
}
}


+4 −0
Original line number Original line Diff line number Diff line
@@ -89,6 +89,10 @@ class SnapuserdClient {


    // Return the status of the snapshot
    // Return the status of the snapshot
    std::string QuerySnapshotStatus(const std::string& misc_name);
    std::string QuerySnapshotStatus(const std::string& misc_name);

    // Check the update verification status - invoked by update_verifier during
    // boot
    bool QueryUpdateVerification();
};
};


}  // namespace snapshot
}  // namespace snapshot
+10 −0
Original line number Original line Diff line number Diff line
@@ -269,5 +269,15 @@ std::string SnapuserdClient::QuerySnapshotStatus(const std::string& misc_name) {
    return Receivemsg();
    return Receivemsg();
}
}


bool SnapuserdClient::QueryUpdateVerification() {
    std::string msg = "update-verify";
    if (!Sendmsg(msg)) {
        LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
        return false;
    }
    std::string response = Receivemsg();
    return response == "success";
}

}  // namespace snapshot
}  // namespace snapshot
}  // namespace android
}  // namespace android
+13 −229
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@


#include <sys/utsname.h>
#include <sys/utsname.h>


#include <android-base/chrono_utils.h>
#include <android-base/properties.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <android-base/strings.h>
@@ -70,6 +71,9 @@ bool SnapshotHandler::InitializeWorkers() {


    read_ahead_thread_ = std::make_unique<ReadAhead>(cow_device_, backing_store_device_, misc_name_,
    read_ahead_thread_ = std::make_unique<ReadAhead>(cow_device_, backing_store_device_, misc_name_,
                                                     GetSharedPtr());
                                                     GetSharedPtr());

    update_verify_ = std::make_unique<UpdateVerify>(misc_name_);

    return true;
    return true;
}
}


@@ -307,206 +311,6 @@ bool SnapshotHandler::InitCowDevice() {
    return ReadMetadata();
    return ReadMetadata();
}
}


void SnapshotHandler::FinalizeIouring() {
    io_uring_queue_exit(ring_.get());
}

bool SnapshotHandler::InitializeIouring(int io_depth) {
    ring_ = std::make_unique<struct io_uring>();

    int ret = io_uring_queue_init(io_depth, ring_.get(), 0);
    if (ret) {
        LOG(ERROR) << "io_uring_queue_init failed with ret: " << ret;
        return false;
    }

    LOG(INFO) << "io_uring_queue_init success with io_depth: " << io_depth;
    return true;
}

bool SnapshotHandler::ReadBlocksAsync(const std::string& dm_block_device,
                                      const std::string& partition_name, size_t size) {
    // 64k block size with io_depth of 64 is optimal
    // for a single thread. We just need a single thread
    // to read all the blocks from all dynamic partitions.
    size_t io_depth = 64;
    size_t bs = (64 * 1024);

    if (!InitializeIouring(io_depth)) {
        return false;
    }

    LOG(INFO) << "ReadBlockAsync start "
              << " Block-device: " << dm_block_device << " Partition-name: " << partition_name
              << " Size: " << size;

    auto scope_guard = android::base::make_scope_guard([this]() -> void { FinalizeIouring(); });

    std::vector<std::unique_ptr<struct iovec>> vecs;
    using AlignedBuf = std::unique_ptr<void, decltype(free)*>;
    std::vector<AlignedBuf> alignedBufVector;

    /*
     * TODO: We need aligned memory for DIRECT-IO. However, if we do
     * a DIRECT-IO and verify the blocks then we need to inform
     * update-verifier that block verification has been done and
     * there is no need to repeat the same. We are not there yet
     * as we need to see if there are any boot time improvements doing
     * a DIRECT-IO.
     *
     * Also, we could you the same function post merge for block verification;
     * again, we can do a DIRECT-IO instead of thrashing page-cache and
     * hurting other applications.
     *
     * For now, we will just create aligned buffers but rely on buffered
     * I/O until we have perf numbers to justify DIRECT-IO.
     */
    for (int i = 0; i < io_depth; i++) {
        auto iovec = std::make_unique<struct iovec>();
        vecs.push_back(std::move(iovec));

        struct iovec* iovec_ptr = vecs[i].get();

        if (posix_memalign(&iovec_ptr->iov_base, BLOCK_SZ, bs)) {
            LOG(ERROR) << "posix_memalign failed";
            return false;
        }

        iovec_ptr->iov_len = bs;
        alignedBufVector.push_back(
                std::unique_ptr<void, decltype(free)*>(iovec_ptr->iov_base, free));
    }

    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
    if (fd.get() == -1) {
        SNAP_PLOG(ERROR) << "File open failed - block-device " << dm_block_device
                         << " partition-name: " << partition_name;
        return false;
    }

    loff_t offset = 0;
    size_t remain = size;
    size_t read_sz = io_depth * bs;

    while (remain > 0) {
        size_t to_read = std::min(remain, read_sz);
        size_t queue_size = to_read / bs;

        for (int i = 0; i < queue_size; i++) {
            struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get());
            if (!sqe) {
                SNAP_LOG(ERROR) << "io_uring_get_sqe() failed";
                return false;
            }

            struct iovec* iovec_ptr = vecs[i].get();

            io_uring_prep_read(sqe, fd.get(), iovec_ptr->iov_base, iovec_ptr->iov_len, offset);
            sqe->flags |= IOSQE_ASYNC;
            offset += bs;
        }

        int ret = io_uring_submit(ring_.get());
        if (ret != queue_size) {
            SNAP_LOG(ERROR) << "submit got: " << ret << " wanted: " << queue_size;
            return false;
        }

        for (int i = 0; i < queue_size; i++) {
            struct io_uring_cqe* cqe;

            int ret = io_uring_wait_cqe(ring_.get(), &cqe);
            if (ret) {
                SNAP_PLOG(ERROR) << "wait_cqe failed" << ret;
                return false;
            }

            if (cqe->res < 0) {
                SNAP_LOG(ERROR) << "io failed with res: " << cqe->res;
                return false;
            }
            io_uring_cqe_seen(ring_.get(), cqe);
        }

        remain -= to_read;
    }

    LOG(INFO) << "ReadBlockAsync complete: "
              << " Block-device: " << dm_block_device << " Partition-name: " << partition_name
              << " Size: " << size;
    return true;
}

void SnapshotHandler::ReadBlocksToCache(const std::string& dm_block_device,
                                        const std::string& partition_name, off_t offset,
                                        size_t size) {
    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
    if (fd.get() == -1) {
        SNAP_PLOG(ERROR) << "Error reading " << dm_block_device
                         << " partition-name: " << partition_name;
        return;
    }

    size_t remain = size;
    off_t file_offset = offset;
    // We pick 4M I/O size based on the fact that the current
    // update_verifier has a similar I/O size.
    size_t read_sz = 1024 * BLOCK_SZ;
    std::vector<uint8_t> buf(read_sz);

    while (remain > 0) {
        size_t to_read = std::min(remain, read_sz);

        if (!android::base::ReadFullyAtOffset(fd.get(), buf.data(), to_read, file_offset)) {
            SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
                             << " at offset: " << file_offset
                             << " partition-name: " << partition_name << " total-size: " << size
                             << " remain_size: " << remain;
            return;
        }

        file_offset += to_read;
        remain -= to_read;
    }

    SNAP_LOG(INFO) << "Finished reading block-device: " << dm_block_device
                   << " partition: " << partition_name << " size: " << size
                   << " offset: " << offset;
}

void SnapshotHandler::ReadBlocks(const std::string partition_name,
                                 const std::string& dm_block_device) {
    SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
                    << " Block-Device: " << dm_block_device;

    uint64_t dev_sz = 0;

    unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_CLOEXEC)));
    if (fd < 0) {
        SNAP_LOG(ERROR) << "Cannot open block device";
        return;
    }

    dev_sz = get_block_device_size(fd.get());
    if (!dev_sz) {
        SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
        return;
    }

    int num_threads = 2;
    size_t num_blocks = dev_sz >> BLOCK_SHIFT;
    size_t num_blocks_per_thread = num_blocks / num_threads;
    size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
    off_t offset = 0;

    for (int i = 0; i < num_threads; i++) {
        std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
                   partition_name, offset, read_sz_per_thread);

        offset += read_sz_per_thread;
    }
}

/*
/*
 * Entry point to launch threads
 * Entry point to launch threads
 */
 */
@@ -527,42 +331,22 @@ bool SnapshotHandler::Start() {
                std::async(std::launch::async, &Worker::RunThread, worker_threads_[i].get()));
                std::async(std::launch::async, &Worker::RunThread, worker_threads_[i].get()));
    }
    }


    bool second_stage_init = true;
    bool partition_verification = true;


    // We don't want to read the blocks during first stage init.
    // We don't want to read the blocks during first stage init or
    // during post-install phase.
    if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
    if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
        second_stage_init = false;
        partition_verification = false;
    }

    if (second_stage_init) {
        SNAP_LOG(INFO) << "Reading blocks to cache....";
        auto& dm = DeviceMapper::Instance();
        auto dm_block_devices = dm.FindDmPartitions();
        if (dm_block_devices.empty()) {
            SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
        } else {
            auto parts = android::base::Split(misc_name_, "-");
            std::string partition_name = parts[0];

            const char* suffix_b = "_b";
            const char* suffix_a = "_a";

            partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
            partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);

            if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
                SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
            } else {
                ReadBlocks(partition_name, dm_block_devices.at(partition_name));
            }
        }
    } else {
        SNAP_LOG(INFO) << "Not reading block device into cache";
    }
    }


    std::future<bool> merge_thread =
    std::future<bool> merge_thread =
            std::async(std::launch::async, &Worker::RunMergeThread, merge_thread_.get());
            std::async(std::launch::async, &Worker::RunMergeThread, merge_thread_.get());


    // Now that the worker threads are up, scan the partitions.
    if (partition_verification) {
        update_verify_->VerifyUpdatePartition();
    }

    bool ret = true;
    bool ret = true;
    for (auto& t : threads) {
    for (auto& t : threads) {
        ret = t.get() && ret;
        ret = t.get() && ret;
+35 −10
Original line number Original line Diff line number Diff line
@@ -45,12 +45,14 @@
#include <liburing.h>
#include <liburing.h>
#include <snapuserd/snapuserd_buffer.h>
#include <snapuserd/snapuserd_buffer.h>
#include <snapuserd/snapuserd_kernel.h>
#include <snapuserd/snapuserd_kernel.h>
#include <storage_literals/storage_literals.h>


namespace android {
namespace android {
namespace snapshot {
namespace snapshot {


using android::base::unique_fd;
using android::base::unique_fd;
using namespace std::chrono_literals;
using namespace std::chrono_literals;
using namespace android::storage_literals;


static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20);
static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20);
static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
@@ -170,6 +172,36 @@ class ReadAhead {
    std::unique_ptr<struct io_uring> ring_;
    std::unique_ptr<struct io_uring> ring_;
};
};


class UpdateVerify {
  public:
    UpdateVerify(const std::string& misc_name);
    void VerifyUpdatePartition();
    bool CheckPartitionVerification();

  private:
    enum class UpdateVerifyState {
        VERIFY_UNKNOWN,
        VERIFY_FAILED,
        VERIFY_SUCCESS,
    };

    std::string misc_name_;
    UpdateVerifyState state_;
    std::mutex m_lock_;
    std::condition_variable m_cv_;

    int kMinThreadsToVerify = 1;
    int kMaxThreadsToVerify = 4;
    uint64_t kThresholdSize = 512_MiB;
    uint64_t kBlockSizeVerify = 1_MiB;

    bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
    void UpdatePartitionVerificationState(UpdateVerifyState state);
    bool VerifyPartition(const std::string& partition_name, const std::string& dm_block_device);
    bool VerifyBlocks(const std::string& partition_name, const std::string& dm_block_device,
                      off_t offset, int skip_blocks, uint64_t dev_sz);
};

class Worker {
class Worker {
  public:
  public:
    Worker(const std::string& cow_device, const std::string& backing_device,
    Worker(const std::string& cow_device, const std::string& backing_device,
@@ -352,24 +384,16 @@ class SnapshotHandler : public std::enable_shared_from_this<SnapshotHandler> {
    MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);
    MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);


    bool IsIouringSupported();
    bool IsIouringSupported();
    bool CheckPartitionVerification() { return update_verify_->CheckPartitionVerification(); }


  private:
  private:
    bool ReadMetadata();
    bool ReadMetadata();
    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
    bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
    bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
    struct BufferState* GetBufferState();
    struct BufferState* GetBufferState();
    void UpdateMergeCompletionPercentage();
    void UpdateMergeCompletionPercentage();


    void ReadBlocks(const std::string partition_name, const std::string& dm_block_device);
    void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
                           off_t offset, size_t size);

    bool InitializeIouring(int io_depth);
    void FinalizeIouring();
    bool ReadBlocksAsync(const std::string& dm_block_device, const std::string& partition_name,
                         size_t size);

    // COW device
    // COW device
    std::string cow_device_;
    std::string cow_device_;
    // Source device
    // Source device
@@ -422,6 +446,7 @@ class SnapshotHandler : public std::enable_shared_from_this<SnapshotHandler> {
    bool scratch_space_ = false;
    bool scratch_space_ = false;


    std::unique_ptr<struct io_uring> ring_;
    std::unique_ptr<struct io_uring> ring_;
    std::unique_ptr<UpdateVerify> update_verify_;
};
};


}  // namespace snapshot
}  // namespace snapshot
Loading