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

Commit 8f5783d3 authored by Akilesh Kailash's avatar Akilesh Kailash Committed by Automerger Merge Worker
Browse files

Merge "libsnapshot:Snapuserd: IO path support with dm-snapshot target" am:...

Merge "libsnapshot:Snapuserd: IO path support with dm-snapshot target" am: c7c72522 am: 246a83b9

Original change: https://android-review.googlesource.com/c/platform/system/core/+/1411487

Change-Id: I431414c5d90b00cc73d3abdfcf175c60cf674d3c
parents 9ed828c8 246a83b9
Loading
Loading
Loading
Loading
+35 −2
Original line number Diff line number Diff line
@@ -148,12 +148,12 @@ cc_library_static {
    recovery_available: true,
    shared_libs: [
        "libbase",
        "libcrypto",
        "liblog",
    ],
    static_libs: [
        "libz",
    ],
    ramdisk_available: true,
}

cc_library_static {
@@ -347,6 +347,9 @@ cc_test {

cc_defaults {
    name: "snapuserd_defaults",
    defaults: [
        "fs_mgr_defaults",
    ],
    srcs: [
        "snapuserd.cpp",
    ],
@@ -360,6 +363,8 @@ cc_defaults {
        "libbase",
        "liblog",
        "libdm",
	"libz",
	"libsnapshot_cow",
    ],
}

@@ -375,7 +380,6 @@ cc_binary {

    ramdisk: true,
    static_executable: true,
    system_shared_libs: [],
}

cc_test {
@@ -473,3 +477,32 @@ cc_binary {
        },
    },
}

cc_test {
    name: "cow_snapuserd_test",
    defaults: [
        "fs_mgr_defaults",
    ],
    srcs: [
        "cow_snapuserd_test.cpp",
    ],
    cflags: [
        "-Wall",
        "-Werror",
    ],
    shared_libs: [
        "libbase",
        "liblog",
        "libz",
    ],
    static_libs: [
        "libgtest",
        "libsnapshot_cow",
    ],
    header_libs: [
        "libstorage_literals_headers",
    ],
    test_min_api_level: 30,
    auto_gen_config: true,
    require_root: false,
}
+24 −2
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <libsnapshot/cow_reader.h>
#include <openssl/sha.h>
#include <zlib.h>

namespace android {
@@ -28,11 +27,13 @@ namespace snapshot {

CowReader::CowReader() : fd_(-1), header_(), fd_size_(0) {}

static void SHA256(const void* data, size_t length, uint8_t out[32]) {
static void SHA256(const void*, size_t, uint8_t[]) {
#if 0
    SHA256_CTX c;
    SHA256_Init(&c);
    SHA256_Update(&c, data, length);
    SHA256_Final(out, &c);
#endif
}

bool CowReader::Parse(android::base::unique_fd&& fd) {
@@ -69,16 +70,35 @@ bool CowReader::Parse(android::base::borrowed_fd fd) {
        return false;
    }

    if (header_.magic != kCowMagicNumber) {
        LOG(ERROR) << "Header Magic corrupted. Magic: " << header_.magic
                   << "Expected: " << kCowMagicNumber;
        return false;
    }

    if ((header_.major_version != kCowVersionMajor) ||
        (header_.minor_version != kCowVersionMinor)) {
        LOG(ERROR) << "Header version mismatch";
        LOG(ERROR) << "Major version: " << header_.major_version
                   << "Expected: " << kCowVersionMajor;
        LOG(ERROR) << "Minor version: " << header_.minor_version
                   << "Expected: " << kCowVersionMinor;
        return false;
    }

    uint8_t header_csum[32];
    {
        CowHeader tmp = header_;
        memset(&tmp.header_checksum, 0, sizeof(tmp.header_checksum));
        memset(header_csum, 0, sizeof(uint8_t) * 32);

        SHA256(&tmp, sizeof(tmp), header_csum);
    }
    if (memcmp(header_csum, header_.header_checksum, sizeof(header_csum)) != 0) {
        LOG(ERROR) << "header checksum is invalid";
        return false;
    }

    return true;
}

@@ -140,6 +160,8 @@ std::unique_ptr<ICowOpIter> CowReader::GetOpIter() {
    }

    uint8_t csum[32];
    memset(csum, 0, sizeof(uint8_t) * 32);

    SHA256(ops_buffer.get(), header_.ops_size, csum);
    if (memcmp(csum, header_.ops_checksum, sizeof(csum)) != 0) {
        LOG(ERROR) << "ops checksum does not match";
+255 −0
Original line number Diff line number Diff line
// Copyright (C) 2018 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 <fcntl.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <iostream>
#include <memory>
#include <string_view>

#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <libsnapshot/cow_writer.h>
#include <storage_literals/storage_literals.h>

namespace android {
namespace snapshot {

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

class SnapuserdTest : public ::testing::Test {
  protected:
    void SetUp() override {
        cow_ = std::make_unique<TemporaryFile>();
        ASSERT_GE(cow_->fd, 0) << strerror(errno);
    }

    void TearDown() override { cow_ = nullptr; }

    std::unique_ptr<TemporaryFile> cow_;
};

TEST_F(SnapuserdTest, ReadWrite) {
    loff_t offset = 0;
    size_t size = 100_MiB;
    unique_fd rnd_fd;
    unique_fd sys_fd;
    unique_fd snapshot_fd;
    unique_fd system_a_fd;
    std::string cmd;

    rnd_fd.reset(open("/dev/random", O_RDONLY));
    ASSERT_TRUE(rnd_fd > 0);

    std::unique_ptr<uint8_t[]> random_buffer_1;
    std::unique_ptr<uint8_t[]> random_buffer_2;
    std::unique_ptr<uint8_t[]> system_buffer;

    random_buffer_1 = std::make_unique<uint8_t[]>(size);

    random_buffer_2 = std::make_unique<uint8_t[]>(size);

    system_buffer = std::make_unique<uint8_t[]>(size);

    // Fill random data
    for (size_t j = 0; j < (size / 1_MiB); j++) {
        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1.get() + offset, 1_MiB, 0), true);

        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_2.get() + offset, 1_MiB, 0), true);

        offset += 1_MiB;
    }

    sys_fd.reset(open("/dev/block/mapper/system_a", O_RDONLY));
    ASSERT_TRUE(sys_fd > 0);

    // Read from system partition from offset 0 of size 100MB
    ASSERT_EQ(ReadFullyAtOffset(sys_fd, system_buffer.get(), size, 0), true);

    //================Create a COW file with the following operations===========
    //
    // Create COW file which is gz compressed
    //
    // 0-100 MB of replace operation with random data
    // 100-200 MB of copy operation
    // 200-300 MB of zero operation
    // 300-400 MB of replace operation with random data

    CowOptions options;
    options.compression = "gz";
    CowWriter writer(options);

    ASSERT_TRUE(writer.Initialize(cow_->fd));

    // Write 100MB random data to COW file which is gz compressed from block 0
    ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1.get(), size));

    size_t num_blocks = size / options.block_size;
    size_t blk_start_copy = num_blocks;
    size_t blk_end_copy = blk_start_copy + num_blocks;
    size_t source_blk = 0;

    // Copy blocks - source_blk starts from 0 as snapuserd
    // has to read from block 0 in system_a partition
    //
    // This initializes copy operation from block 0 of size 100 MB from
    // /dev/block/mapper/system_a
    for (size_t i = blk_start_copy; i < blk_end_copy; i++) {
        ASSERT_TRUE(writer.AddCopy(i, source_blk));
        source_blk += 1;
    }

    size_t blk_zero_copy_start = blk_end_copy;
    size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;

    // 100 MB filled with zeroes
    ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));

    // Final 100MB filled with random data which is gz compressed
    size_t blk_random2_replace_start = blk_zero_copy_end;

    ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_2.get(), size));

    // Flush operations
    ASSERT_TRUE(writer.Finalize());

    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);

    //================Setup dm-snapshot and start snapuserd daemon===========

    // Create a COW device. Number of sectors is chosen random which can
    // hold at least 400MB of data

    system_a_fd.reset(open("/dev/block/mapper/system_a", O_RDONLY));
    ASSERT_TRUE(system_a_fd > 0);

    int blksize;
    int err = ioctl(system_a_fd.get(), BLKGETSIZE, &blksize);
    if (err < 0) {
        ASSERT_TRUE(0);
    }

    cmd = "dmctl create system_cow user 0 " + std::to_string(blksize);
    system(cmd.c_str());

    // Start the snapuserd daemon
    if (fork() == 0) {
        const char* argv[] = {"/system/bin/snapuserd", cow_->path, "/dev/block/mapper/system_a",
                              nullptr};
        if (execv(argv[0], const_cast<char**>(argv))) {
            ASSERT_TRUE(0);
        }
    }

    cmd.clear();

    cmd = "dmctl create system-snapshot -ro snapshot 0 " + std::to_string(blksize);
    cmd += " /dev/block/mapper/system_a /dev/block/mapper/system_cow ";
    cmd += "P 8";
    system(cmd.c_str());

    // Wait so that snapshot device is created
    sleep(5);
    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size);

    offset = 0;

    snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY));
    ASSERT_TRUE(snapshot_fd > 0);

    //================Start IO operation on dm-snapshot device=================
    // This will test the following paths:
    //
    // 1: IO path for all three operations and interleaving of operations.
    // 2: Merging of blocks in kernel during metadata read
    // 3: Bulk IO issued by kernel duing merge operation

    // Read from snapshot device of size 100MB from offset 0. This tests the
    // 1st replace operation.
    //
    // IO path:
    //
    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
    // op)->decompress_cow->return

    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true);

    // Update the offset
    offset += size;

    // Compare data with random_buffer_1.
    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1.get(), size), 0);

    // Clear the buffer
    memset(snapuserd_buffer.get(), 0, size);

    // Read from snapshot device of size 100MB from offset 100MB. This tests the
    // copy operation.
    //
    // IO path:
    //
    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_from_system_a_partition
    // (copy op) -> return
    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true);

    // Update the offset
    offset += size;

    // Compare data with system_buffer.
    ASSERT_EQ(memcmp(snapuserd_buffer.get(), system_buffer.get(), size), 0);

    // Read from snapshot device of size 100MB from offset 200MB. This tests the
    // zero operation.
    //
    // IO path:
    //
    // dm-snap->dm-snap-persistent->dm-user->snapuserd->fill_memory_with_zero
    // (zero op) -> return
    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true);

    // Fill the random_buffer_1 with zero as we no longer need it
    memset(random_buffer_1.get(), 0, size);

    // Compare data with zero filled buffer
    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1.get(), size), 0);

    // Update the offset
    offset += size;

    // Read from snapshot device of size 100MB from offset 300MB. This tests the
    // final replace operation.
    //
    // IO path:
    //
    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
    // op)->decompress_cow->return
    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true);

    // Compare data with random_buffer_2.
    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_2.get(), size), 0);
}

}  // namespace snapshot
}  // namespace android

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
+8 −2
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <libsnapshot/cow_writer.h>
#include <openssl/sha.h>
#include <zlib.h>

namespace android {
@@ -179,11 +178,15 @@ std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length)
    return {};
}

static void SHA256(const void* data, size_t length, uint8_t out[32]) {
// TODO: Fix compilation issues when linking libcrypto library
// when snapuserd is compiled as part of ramdisk.
static void SHA256(const void*, size_t, uint8_t[]) {
#if 0
    SHA256_CTX c;
    SHA256_Init(&c);
    SHA256_Update(&c, data, length);
    SHA256_Final(out, &c);
#endif
}

bool CowWriter::Finalize() {
@@ -199,6 +202,9 @@ bool CowWriter::Finalize() {
    header_.ops_offset = offs;
    header_.ops_size = ops_.size();

    memset(header_.ops_checksum, 0, sizeof(uint8_t) * 32);
    memset(header_.header_checksum, 0, sizeof(uint8_t) * 32);

    SHA256(ops_.data(), ops_.size(), header_.ops_checksum);
    SHA256(&header_, sizeof(header_), header_.header_checksum);

+4 −0
Original line number Diff line number Diff line
@@ -92,6 +92,10 @@ class CowReader : public ICowReader {
    bool Parse(android::base::borrowed_fd fd);

    bool GetHeader(CowHeader* header) override;

    // Create a CowOpIter object which contains header_.num_ops
    // CowOperation objects. Get() returns a unique CowOperation object
    // whose lifeteime depends on the CowOpIter object
    std::unique_ptr<ICowOpIter> GetOpIter() override;
    bool GetRawBytes(uint64_t offset, void* buffer, size_t len) override;
    bool ReadData(const CowOperation& op, IByteSink* sink) override;
Loading