blob: 4903224c077eca8133db90bd81559e0d4e826e38 [file] [log] [blame]
// 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 <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <chrono>
#include <iostream>
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
namespace android {
namespace snapshot {
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
using namespace std::chrono_literals;
using namespace std::string_literals;
class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
public:
std::string GetGsidDir() const override { return "ota/test"s; }
std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
bool IsRunningSnapshot() const override { return is_running_snapshot_; }
void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; }
private:
bool is_running_snapshot_;
};
std::unique_ptr<SnapshotManager> sm;
TestDeviceInfo* test_device = nullptr;
class SnapshotTest : public ::testing::Test {
public:
SnapshotTest() : dm_(DeviceMapper::Instance()) {}
protected:
void SetUp() override {
test_device->set_is_running_snapshot(false);
if (sm->GetUpdateState() != UpdateState::None) {
CleanupTestArtifacts();
}
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->EnsureImageManager());
image_manager_ = sm->image_manager();
ASSERT_NE(image_manager_, nullptr);
}
void TearDown() override {
lock_ = nullptr;
CleanupTestArtifacts();
}
void CleanupTestArtifacts() {
// Normally cancelling inside a merge is not allowed. Since these
// are tests, we don't care, destroy everything that might exist.
std::vector<std::string> snapshots = {"test-snapshot"};
for (const auto& snapshot : snapshots) {
DeleteSnapshotDevice(snapshot);
temp_images_.emplace_back(snapshot + "-cow");
auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
android::base::RemoveFileIfExists(status_file);
}
// Remove all images.
temp_images_.emplace_back("test-snapshot-cow");
for (const auto& temp_image : temp_images_) {
image_manager_->UnmapImageDevice(temp_image);
image_manager_->DeleteBackingImage(temp_image);
}
if (sm->GetUpdateState() != UpdateState::None) {
auto state_file = sm->GetStateFilePath();
unlink(state_file.c_str());
}
}
bool AcquireLock() {
lock_ = sm->OpenStateFile(O_RDWR, LOCK_EX);
return !!lock_;
}
bool CreateTempDevice(const std::string& name, uint64_t size, std::string* path) {
if (!image_manager_->CreateBackingImage(name, size, false)) {
return false;
}
temp_images_.emplace_back(name);
return image_manager_->MapImageDevice(name, 10s, path);
}
bool DeleteSnapshotDevice(const std::string& snapshot) {
if (dm_.GetState(snapshot) != DmDeviceState::INVALID) {
if (!dm_.DeleteDevice(snapshot)) return false;
}
if (dm_.GetState(snapshot + "-inner") != DmDeviceState::INVALID) {
if (!dm_.DeleteDevice(snapshot + "-inner")) return false;
}
return true;
}
DeviceMapper& dm_;
std::unique_ptr<SnapshotManager::LockedFile> lock_;
std::vector<std::string> temp_images_;
android::fiemap::IImageManager* image_manager_ = nullptr;
};
TEST_F(SnapshotTest, CreateSnapshot) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
kDeviceSize));
std::vector<std::string> snapshots;
ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
ASSERT_EQ(snapshots.size(), 1);
ASSERT_EQ(snapshots[0], "test-snapshot");
// Scope so delete can re-acquire the snapshot file lock.
{
SnapshotManager::SnapshotStatus status;
ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status));
ASSERT_EQ(status.state, "created");
ASSERT_EQ(status.device_size, kDeviceSize);
ASSERT_EQ(status.snapshot_size, kDeviceSize);
}
ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot"));
}
TEST_F(SnapshotTest, MapSnapshot) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
kDeviceSize));
std::string base_device;
ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
std::string snap_device;
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
}
TEST_F(SnapshotTest, MapPartialSnapshot) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kSnapshotSize = 1024 * 1024;
static const uint64_t kDeviceSize = 1024 * 1024 * 2;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kSnapshotSize,
kSnapshotSize));
std::string base_device;
ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
std::string snap_device;
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
}
TEST_F(SnapshotTest, NoMergeBeforeReboot) {
ASSERT_TRUE(AcquireLock());
// Set the state to Unverified, as if we finished an update.
ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
// Release the lock.
lock_ = nullptr;
// Merge should fail, since we didn't mark the device as rebooted.
ASSERT_FALSE(sm->InitiateMerge());
}
TEST_F(SnapshotTest, Merge) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
kDeviceSize));
std::string base_device, snap_device;
ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
std::string test_string = "This is a test string.";
{
unique_fd fd(open(snap_device.c_str(), O_RDWR | O_CLOEXEC | O_SYNC));
ASSERT_GE(fd, 0);
ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size()));
}
// Note: we know the name of the device is test-snapshot because we didn't
// request a linear segment.
DeviceMapper::TargetInfo target;
ASSERT_TRUE(sm->IsSnapshotDevice("test-snapshot", &target));
ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
// Set the state to Unverified, as if we finished an update.
ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
// Release the lock.
lock_ = nullptr;
test_device->set_is_running_snapshot(true);
ASSERT_TRUE(sm->InitiateMerge());
// The device should have been switched to a snapshot-merge target.
ASSERT_TRUE(sm->IsSnapshotDevice("test-snapshot", &target));
ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
// We should not be able to cancel an update now.
ASSERT_FALSE(sm->CancelUpdate());
ASSERT_EQ(sm->WaitForMerge(), UpdateState::MergeCompleted);
ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
// The device should no longer be a snapshot or snapshot-merge.
ASSERT_FALSE(sm->IsSnapshotDevice("test-snapshot"));
// Test that we can read back the string we wrote to the snapshot.
unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
ASSERT_GE(fd, 0);
std::string buffer(test_string.size(), '\0');
ASSERT_TRUE(android::base::ReadFully(fd, buffer.data(), buffer.size()));
ASSERT_EQ(test_string, buffer);
}
TEST_F(SnapshotTest, MergeCannotRemoveCow) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
kDeviceSize));
std::string base_device, snap_device;
ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
// Keep an open handle to the cow device. This should cause the merge to
// be incomplete.
auto cow_path = android::base::GetProperty("gsid.mapped_image.test-snapshot-cow", "");
unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC));
ASSERT_GE(fd, 0);
// Set the state to Unverified, as if we finished an update.
ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
// Release the lock.
lock_ = nullptr;
test_device->set_is_running_snapshot(true);
ASSERT_TRUE(sm->InitiateMerge());
// COW cannot be removed due to open fd, so expect a soft failure.
ASSERT_EQ(sm->WaitForMerge(), UpdateState::MergeNeedsReboot);
// Forcefully delete the snapshot device, so it looks like we just rebooted.
ASSERT_TRUE(DeleteSnapshotDevice("test-snapshot"));
// Map snapshot should fail now, because we're in a merge-complete state.
ASSERT_TRUE(AcquireLock());
ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
// Release everything and now the merge should complete.
fd = {};
lock_ = nullptr;
ASSERT_EQ(sm->WaitForMerge(), UpdateState::MergeCompleted);
}
} // namespace snapshot
} // namespace android
using namespace android::snapshot;
bool Mkdir(const std::string& path) {
if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
return false;
}
return true;
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
std::vector<std::string> paths = {
"/data/gsi/ota/test",
"/metadata/gsi/ota/test",
"/metadata/ota/test",
"/metadata/ota/test/snapshots",
};
for (const auto& path : paths) {
if (!Mkdir(path)) {
return 1;
}
}
// Create this once, otherwise, gsid will start/stop between each test.
test_device = new TestDeviceInfo();
sm = SnapshotManager::New(test_device);
if (!sm) {
std::cerr << "Could not create snapshot manager";
return 1;
}
return RUN_ALL_TESTS();
}