blob: 9adc6551b050dd4d4f4ad0de8baa299e11dbb9f4 [file] [log] [blame] [edit]
// 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 <libsnapshot/snapshot.h>
#include <unordered_set>
#include <android-base/file.h>
#include <gtest/gtest.h>
#include <libsnapshot/cow_writer.h>
#include <payload_consumer/file_descriptor.h>
namespace android {
namespace snapshot {
using android::base::unique_fd;
using chromeos_update_engine::FileDescriptor;
static constexpr uint32_t kBlockSize = 4096;
static constexpr size_t kBlockCount = 10;
class OfflineSnapshotTest : public ::testing::Test {
protected:
virtual void SetUp() override {
base_ = std::make_unique<TemporaryFile>();
ASSERT_GE(base_->fd, 0) << strerror(errno);
cow_ = std::make_unique<TemporaryFile>();
ASSERT_GE(cow_->fd, 0) << strerror(errno);
WriteBaseDevice();
}
virtual void TearDown() override {
base_ = nullptr;
cow_ = nullptr;
base_blocks_ = {};
}
void WriteBaseDevice() {
unique_fd random(open("/dev/urandom", O_RDONLY));
ASSERT_GE(random, 0);
for (size_t i = 0; i < kBlockCount; i++) {
std::string block(kBlockSize, 0);
ASSERT_TRUE(android::base::ReadFully(random, block.data(), block.size()));
ASSERT_TRUE(android::base::WriteFully(base_->fd, block.data(), block.size()));
base_blocks_.emplace_back(std::move(block));
}
ASSERT_EQ(fsync(base_->fd), 0);
}
void WriteCow(ISnapshotWriter* writer) {
std::string new_block = MakeNewBlockString();
std::string xor_block = MakeXorBlockString();
ASSERT_TRUE(writer->AddXorBlocks(1, xor_block.data(), xor_block.size(), 0, kBlockSize / 2));
ASSERT_TRUE(writer->AddCopy(3, 0));
ASSERT_TRUE(writer->AddRawBlocks(5, new_block.data(), new_block.size()));
ASSERT_TRUE(writer->AddZeroBlocks(7, 2));
ASSERT_TRUE(writer->Finalize());
}
void TestBlockReads(ISnapshotWriter* writer) {
auto reader = writer->OpenReader();
ASSERT_NE(reader, nullptr);
// Test that unchanged blocks are not modified.
std::unordered_set<size_t> changed_blocks = {1, 3, 5, 7, 8};
for (size_t i = 0; i < kBlockCount; i++) {
if (changed_blocks.count(i)) {
continue;
}
std::string block(kBlockSize, 0);
ASSERT_EQ(reader->Seek(i * kBlockSize, SEEK_SET), i * kBlockSize);
ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize);
ASSERT_EQ(block, base_blocks_[i]);
}
// Test that we can read back our modified blocks.
std::string data(kBlockSize, 0);
std::string offsetblock = base_blocks_[0].substr(kBlockSize / 2, kBlockSize / 2) +
base_blocks_[1].substr(0, kBlockSize / 2);
ASSERT_EQ(offsetblock.size(), kBlockSize);
ASSERT_EQ(reader->Seek(1 * kBlockSize, SEEK_SET), 1 * kBlockSize);
ASSERT_EQ(reader->Read(data.data(), data.size()), kBlockSize);
for (int i = 0; i < 100; i++) {
data[i] = (char)~(data[i]);
}
ASSERT_EQ(data, offsetblock);
std::string block(kBlockSize, 0);
ASSERT_EQ(reader->Seek(3 * kBlockSize, SEEK_SET), 3 * kBlockSize);
ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize);
ASSERT_EQ(block, base_blocks_[0]);
ASSERT_EQ(reader->Seek(5 * kBlockSize, SEEK_SET), 5 * kBlockSize);
ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize);
ASSERT_EQ(block, MakeNewBlockString());
std::string two_blocks(kBlockSize * 2, 0x7f);
std::string zeroes(kBlockSize * 2, 0);
ASSERT_EQ(reader->Seek(7 * kBlockSize, SEEK_SET), 7 * kBlockSize);
ASSERT_EQ(reader->Read(two_blocks.data(), two_blocks.size()), two_blocks.size());
ASSERT_EQ(two_blocks, zeroes);
}
void TestByteReads(ISnapshotWriter* writer) {
auto reader = writer->OpenReader();
ASSERT_NE(reader, nullptr);
std::string blob(kBlockSize * 3, 'x');
// Test that we can read in the middle of a block.
static constexpr size_t kOffset = 970;
off64_t offset = 3 * kBlockSize + kOffset;
ASSERT_EQ(reader->Seek(0, SEEK_SET), 0);
ASSERT_EQ(reader->Seek(offset, SEEK_CUR), offset);
ASSERT_EQ(reader->Read(blob.data(), blob.size()), blob.size());
ASSERT_EQ(blob.substr(0, 100), base_blocks_[0].substr(kOffset, 100));
ASSERT_EQ(blob.substr(kBlockSize - kOffset, kBlockSize), base_blocks_[4]);
ASSERT_EQ(blob.substr(kBlockSize * 2 - kOffset, 100), MakeNewBlockString().substr(0, 100));
ASSERT_EQ(blob.substr(blob.size() - kOffset), base_blocks_[6].substr(0, kOffset));
// Pull a random byte from the compressed block.
char value;
offset = 5 * kBlockSize + 1000;
ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
ASSERT_EQ(value, MakeNewBlockString()[1000]);
}
void TestReads(ISnapshotWriter* writer) {
ASSERT_NO_FATAL_FAILURE(TestBlockReads(writer));
ASSERT_NO_FATAL_FAILURE(TestByteReads(writer));
}
std::string MakeNewBlockString() {
std::string new_block = "This is a new block";
new_block.resize(kBlockSize / 2, '*');
new_block.resize(kBlockSize, '!');
return new_block;
}
std::string MakeXorBlockString() {
std::string data(kBlockSize, 0);
memset(data.data(), 0xff, 100);
return data;
}
std::unique_ptr<TemporaryFile> base_;
std::unique_ptr<TemporaryFile> cow_;
std::vector<std::string> base_blocks_;
};
TEST_F(OfflineSnapshotTest, CompressedSnapshot) {
CowOptions options;
options.compression = "gz";
options.max_blocks = {kBlockCount};
options.scratch_space = false;
unique_fd cow_fd(dup(cow_->fd));
ASSERT_GE(cow_fd, 0);
auto writer = std::make_unique<CompressedSnapshotWriter>(options);
writer->SetSourceDevice(base_->path);
ASSERT_TRUE(writer->SetCowDevice(std::move(cow_fd)));
ASSERT_TRUE(writer->Initialize());
ASSERT_NO_FATAL_FAILURE(WriteCow(writer.get()));
ASSERT_NO_FATAL_FAILURE(TestReads(writer.get()));
}
} // namespace snapshot
} // namespace android