Merge changes from topic "vabc-user-snapshots"

* changes:
  snapuserd: Add unit test for test merge code path
  libsnapshot: Add vts_userspace_snapshot_test
  libsnapshot: Integrate userspace snapshots APIs
This commit is contained in:
Akilesh Kailash 2021-11-22 20:16:06 +00:00 committed by Gerrit Code Review
commit 04eecd441c
28 changed files with 4231 additions and 229 deletions

View file

@ -258,11 +258,62 @@ cc_defaults {
require_root: true,
}
cc_defaults {
name: "userspace_snapshot_test_defaults",
defaults: ["libsnapshot_defaults"],
srcs: [
"partition_cow_creator_test.cpp",
"snapshot_metadata_updater_test.cpp",
"snapshot_reader_test.cpp",
"userspace_snapshot_test.cpp",
"snapshot_writer_test.cpp",
],
shared_libs: [
"libbinder",
"libcrypto",
"libhidlbase",
"libprotobuf-cpp-lite",
"libutils",
"libz",
],
static_libs: [
"android.hardware.boot@1.0",
"android.hardware.boot@1.1",
"libbrotli",
"libc++fs",
"libfs_mgr_binder",
"libgsi",
"libgmock",
"liblp",
"libsnapshot",
"libsnapshot_cow",
"libsnapshot_test_helpers",
"libsparse",
],
header_libs: [
"libstorage_literals_headers",
],
test_suites: [
"vts",
"device-tests"
],
test_options: {
min_shipping_api_level: 29,
},
auto_gen_config: true,
require_root: true,
}
cc_test {
name: "vts_libsnapshot_test",
defaults: ["libsnapshot_test_defaults"],
}
cc_test {
name: "vts_userspace_snapshot_test",
defaults: ["userspace_snapshot_test_defaults"],
}
cc_binary {
name: "snapshotctl",
srcs: [

View file

@ -194,6 +194,9 @@ message SnapshotUpdateStatus {
// Source build fingerprint.
string source_build_fingerprint = 8;
// user-space snapshots
bool userspace_snapshots = 9;
}
// Next: 10

View file

@ -35,6 +35,7 @@ class MockSnapshotManager : public ISnapshotManager {
(override));
MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override));
MOCK_METHOD(bool, UpdateUsesCompression, (), (override));
MOCK_METHOD(bool, UpdateUsesUserSnapshots, (), (override));
MOCK_METHOD(Return, CreateUpdateSnapshots,
(const chromeos_update_engine::DeltaArchiveManifest& manifest), (override));
MOCK_METHOD(bool, MapUpdateSnapshot,

View file

@ -193,6 +193,9 @@ class ISnapshotManager {
// UpdateState is None, or no snapshots have been created.
virtual bool UpdateUsesCompression() = 0;
// Returns true if userspace snapshots is enabled for the current update.
virtual bool UpdateUsesUserSnapshots() = 0;
// Create necessary COW device / files for OTA clients. New logical partitions will be added to
// group "cow" in target_metadata. Regions of partitions of current_metadata will be
// "write-protected" and snapshotted.
@ -352,6 +355,7 @@ class SnapshotManager final : public ISnapshotManager {
const std::function<bool()>& before_cancel = {}) override;
UpdateState GetUpdateState(double* progress = nullptr) override;
bool UpdateUsesCompression() override;
bool UpdateUsesUserSnapshots() override;
Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
std::string* snapshot_path) override;
@ -387,6 +391,11 @@ class SnapshotManager final : public ISnapshotManager {
// first-stage to decide whether to launch snapuserd.
bool IsSnapuserdRequired();
enum class SnapshotDriver {
DM_SNAPSHOT,
DM_USER,
};
private:
FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
FRIEND_TEST(SnapshotTest, CreateSnapshot);
@ -456,6 +465,8 @@ class SnapshotManager final : public ISnapshotManager {
};
static std::unique_ptr<LockedFile> OpenFile(const std::string& file, int lock_flags);
SnapshotDriver GetSnapshotDriver(LockedFile* lock);
// Create a new snapshot record. This creates the backing COW store and
// persists information needed to map the device. The device can be mapped
// with MapSnapshot().
@ -491,8 +502,8 @@ class SnapshotManager final : public ISnapshotManager {
// Create a dm-user device for a given snapshot.
bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file,
const std::string& base_device, const std::chrono::milliseconds& timeout_ms,
std::string* path);
const std::string& base_device, const std::string& base_path_merge,
const std::chrono::milliseconds& timeout_ms, std::string* path);
// Map the source device used for dm-user.
bool MapSourceDevice(LockedFile* lock, const std::string& name,
@ -591,7 +602,8 @@ class SnapshotManager final : public ISnapshotManager {
// Internal callback for when merging is complete.
bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
const SnapshotStatus& status);
bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
bool CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
const SnapshotStatus& status);
struct MergeResult {
explicit MergeResult(UpdateState state,
@ -689,7 +701,10 @@ class SnapshotManager final : public ISnapshotManager {
bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
// Unmap a dm-user device through snapuserd.
bool UnmapDmUserDevice(const std::string& snapshot_name);
bool UnmapDmUserDevice(const std::string& dm_user_name);
// Unmap a dm-user device for user space snapshots
bool UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name);
// If there isn't a previous update, return true. |needs_merge| is set to false.
// If there is a previous update but the device has not boot into it, tries to cancel the
@ -778,6 +793,8 @@ class SnapshotManager final : public ISnapshotManager {
// Helper of UpdateUsesCompression
bool UpdateUsesCompression(LockedFile* lock);
// Helper of UpdateUsesUsersnapshots
bool UpdateUsesUserSnapshots(LockedFile* lock);
// Wrapper around libdm, with diagnostics.
bool DeleteDeviceIfExists(const std::string& name,
@ -792,6 +809,7 @@ class SnapshotManager final : public ISnapshotManager {
std::function<bool(const std::string&)> uevent_regen_callback_;
std::unique_ptr<SnapuserdClient> snapuserd_client_;
std::unique_ptr<LpMetadata> old_partition_metadata_;
std::optional<bool> is_snapshot_userspace_;
};
} // namespace snapshot

View file

@ -35,6 +35,7 @@ class SnapshotManagerStub : public ISnapshotManager {
const std::function<bool()>& before_cancel = {}) override;
UpdateState GetUpdateState(double* progress = nullptr) override;
bool UpdateUsesCompression() override;
bool UpdateUsesUserSnapshots() override;
Return CreateUpdateSnapshots(
const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,

View file

@ -95,6 +95,7 @@ std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
if (!info) {
info = new DeviceInfo();
}
return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
}
@ -121,8 +122,34 @@ static std::string GetCowName(const std::string& snapshot_name) {
return snapshot_name + "-cow";
}
static std::string GetDmUserCowName(const std::string& snapshot_name) {
return snapshot_name + "-user-cow";
SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) {
if (UpdateUsesUserSnapshots(lock)) {
return SnapshotManager::SnapshotDriver::DM_USER;
} else {
return SnapshotManager::SnapshotDriver::DM_SNAPSHOT;
}
}
static std::string GetDmUserCowName(const std::string& snapshot_name,
SnapshotManager::SnapshotDriver driver) {
// dm-user block device will act as a snapshot device. We identify it with
// the same partition name so that when partitions can be mounted off
// dm-user.
switch (driver) {
case SnapshotManager::SnapshotDriver::DM_USER: {
return snapshot_name;
}
case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: {
return snapshot_name + "-user-cow";
}
default: {
LOG(ERROR) << "Invalid snapshot driver";
return "";
}
}
}
static std::string GetCowImageDeviceName(const std::string& snapshot_name) {
@ -398,9 +425,33 @@ Return SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name
bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
const std::string& cow_file, const std::string& base_device,
const std::string& base_path_merge,
const std::chrono::milliseconds& timeout_ms, std::string* path) {
CHECK(lock);
if (UpdateUsesUserSnapshots(lock)) {
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed...";
return false;
}
if (status.state() == SnapshotState::NONE ||
status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after merging has completed.";
return false;
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
if (update_status.state() == UpdateState::MergeCompleted ||
update_status.state() == UpdateState::MergeNeedsReboot) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after global merging has completed.";
return false;
}
}
// 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;
@ -412,18 +463,41 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
return false;
}
uint64_t base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
if (base_sectors == 0) {
LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
return false;
uint64_t base_sectors = 0;
if (!UpdateUsesUserSnapshots(lock)) {
base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
if (base_sectors == 0) {
LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
return false;
}
} else {
// For userspace snapshots, the size of the base device is taken as the
// size of the dm-user block device. Since there is no pseudo mapping
// created in the daemon, we no longer need to rely on the daemon for
// sizing the dm-user block device.
unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd < 0) {
LOG(ERROR) << "Cannot open block device: " << base_path_merge;
return false;
}
uint64_t dev_sz = get_block_device_size(fd.get());
if (!dev_sz) {
LOG(ERROR) << "Failed to find block device size: " << base_path_merge;
return false;
}
base_sectors = dev_sz >> 9;
}
DmTable table;
table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
if (!dm_.CreateDevice(name, table, path, timeout_ms)) {
LOG(ERROR) << " dm-user: CreateDevice failed... ";
return false;
}
if (!WaitForDevice(*path, timeout_ms)) {
LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name;
return false;
}
@ -432,6 +506,15 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
return false;
}
if (UpdateUsesUserSnapshots(lock)) {
// Now that the dm-user device is created, initialize the daemon and
// spin up the worker threads.
if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) {
LOG(ERROR) << "InitDmUserCow failed";
return false;
}
}
return snapuserd_client_->AttachDmUser(misc_name);
}
@ -698,13 +781,15 @@ bool SnapshotManager::InitiateMerge() {
DmTargetSnapshot::Status initial_target_values = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
return false;
if (!UpdateUsesUserSnapshots(lock.get())) {
DmTargetSnapshot::Status current_status;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
return false;
}
initial_target_values.sectors_allocated += current_status.sectors_allocated;
initial_target_values.total_sectors += current_status.total_sectors;
initial_target_values.metadata_sectors += current_status.metadata_sectors;
}
initial_target_values.sectors_allocated += current_status.sectors_allocated;
initial_target_values.total_sectors += current_status.total_sectors;
initial_target_values.metadata_sectors += current_status.metadata_sectors;
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
@ -719,11 +804,14 @@ bool SnapshotManager::InitiateMerge() {
SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
initial_status.set_state(UpdateState::Merging);
initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
initial_status.set_total_sectors(initial_target_values.total_sectors);
initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
initial_status.set_compression_enabled(compression_enabled);
if (!UpdateUsesUserSnapshots(lock.get())) {
initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
initial_status.set_total_sectors(initial_target_values.total_sectors);
initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
}
// If any partitions shrunk, we need to merge them before we merge any other
// partitions (see b/177935716). Otherwise, a merge from another partition
// may overwrite the source block of a copy operation.
@ -777,20 +865,36 @@ MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const
<< " has unexpected state: " << SnapshotState_Name(status.state());
}
// After this, we return true because we technically did switch to a merge
// target. Everything else we do here is just informational.
if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
return code;
if (UpdateUsesUserSnapshots(lock)) {
if (EnsureSnapuserdConnected()) {
// This is the point where we inform the daemon to initiate/resume
// the merge
if (!snapuserd_client_->InitiateMerge(name)) {
return MergeFailureCode::UnknownTable;
}
} else {
LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge";
return MergeFailureCode::UnknownTable;
}
} else {
// After this, we return true because we technically did switch to a merge
// target. Everything else we do here is just informational.
if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
return code;
}
}
status.set_state(SnapshotState::MERGING);
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
LOG(ERROR) << "Could not query merge status for snapshot: " << name;
if (!UpdateUsesUserSnapshots(lock)) {
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
LOG(ERROR) << "Could not query merge status for snapshot: " << name;
}
status.set_sectors_allocated(dm_status.sectors_allocated);
status.set_metadata_sectors(dm_status.metadata_sectors);
}
status.set_sectors_allocated(dm_status.sectors_allocated);
status.set_metadata_sectors(dm_status.metadata_sectors);
if (!WriteSnapshotStatus(lock, status)) {
LOG(ERROR) << "Could not update status file for snapshot: " << name;
}
@ -856,9 +960,15 @@ bool SnapshotManager::IsSnapshotDevice(const std::string& dm_name, TargetInfo* t
return false;
}
auto type = DeviceMapper::GetTargetType(snap_target.spec);
if (type != "snapshot" && type != "snapshot-merge") {
return false;
// If this is not a user-snapshot device then it should either
// be a dm-snapshot or dm-snapshot-merge target
if (type != "user") {
if (type != "snapshot" && type != "snapshot-merge") {
return false;
}
}
if (target) {
*target = std::move(snap_target);
}
@ -1094,34 +1204,86 @@ auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string&
DCHECK((current_metadata = ReadCurrentMetadata()) &&
GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated);
std::string target_type;
DmTargetSnapshot::Status status;
if (!QuerySnapshotStatus(name, &target_type, &status)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (target_type == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (target_type != "snapshot-merge") {
// We can get here if we failed to rewrite the target type in
// InitiateMerge(). If we failed to create the target in first-stage
// init, boot would not succeed.
LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
if (UpdateUsesUserSnapshots(lock)) {
std::string merge_status;
if (EnsureSnapuserdConnected()) {
// Query the snapshot status from the daemon
merge_status = snapuserd_client_->QuerySnapshotStatus(name);
} else {
MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (merge_status == "snapshot-merge-failed") {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
// This is the case when device reboots during merge. Once the device boots,
// snapuserd daemon will not resume merge immediately in first stage init.
// This is slightly different as compared to dm-snapshot-merge; In this
// case, metadata file will have "MERGING" state whereas the daemon will be
// waiting to resume the merge. Thus, we resume the merge at this point.
if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) {
if (!snapuserd_client_->InitiateMerge(name)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
return MergeResult(UpdateState::Merging);
}
if (merge_status == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (merge_status == "snapshot-merge") {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name
<< " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
if (merge_status != "snapshot-merge-complete") {
LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
} else {
// dm-snapshot in the kernel
std::string target_type;
DmTargetSnapshot::Status status;
if (!QuerySnapshotStatus(name, &target_type, &status)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (target_type == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (target_type != "snapshot-merge") {
// We can get here if we failed to rewrite the target type in
// InitiateMerge(). If we failed to create the target in first-stage
// init, boot would not succeed.
LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name
<< " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
}
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
// Merge is complete at this point
auto code = CheckMergeConsistency(lock, name, snapshot_status);
if (code != MergeFailureCode::Ok) {
@ -1311,30 +1473,40 @@ void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
if (IsSnapshotDevice(name)) {
// We are extra-cautious here, to avoid deleting the wrong table.
std::string target_type;
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
return false;
if (!UpdateUsesUserSnapshots(lock)) {
if (IsSnapshotDevice(name)) {
// We are extra-cautious here, to avoid deleting the wrong table.
std::string target_type;
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
return false;
}
if (target_type != "snapshot-merge") {
LOG(ERROR) << "Unexpected target type " << target_type
<< " for snapshot device: " << name;
return false;
}
if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
return false;
}
if (!CollapseSnapshotDevice(lock, name, status)) {
LOG(ERROR) << "Unable to collapse snapshot: " << name;
return false;
}
}
if (target_type != "snapshot-merge") {
LOG(ERROR) << "Unexpected target type " << target_type
<< " for snapshot device: " << name;
return false;
}
if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
return false;
}
if (!CollapseSnapshotDevice(name, status)) {
} else {
// Just collapse the device - no need to query again as we just did
// prior to calling this function
if (!CollapseSnapshotDevice(lock, name, status)) {
LOG(ERROR) << "Unable to collapse snapshot: " << name;
return false;
}
// Note that collapsing is implicitly an Unmap, so we don't need to
// unmap the snapshot.
}
// Note that collapsing is implicitly an Unmap, so we don't need to
// unmap the snapshot.
if (!DeleteSnapshot(lock, name)) {
LOG(ERROR) << "Could not delete snapshot: " << name;
return false;
@ -1342,23 +1514,26 @@ bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::strin
return true;
}
bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
// Verify we have a snapshot-merge device.
DeviceMapper::TargetInfo target;
if (!GetSingleTarget(name, TableQuery::Table, &target)) {
return false;
}
if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
// This should be impossible, it was checked earlier.
LOG(ERROR) << "Snapshot device has invalid target type: " << name;
return false;
}
if (!UpdateUsesUserSnapshots(lock)) {
// Verify we have a snapshot-merge device.
DeviceMapper::TargetInfo target;
if (!GetSingleTarget(name, TableQuery::Table, &target)) {
return false;
}
if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
// This should be impossible, it was checked earlier.
LOG(ERROR) << "Snapshot device has invalid target type: " << name;
return false;
}
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data;
return false;
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not parse snapshot device " << name
<< " parameters: " << target.data;
return false;
}
}
uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
@ -1386,14 +1561,32 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
return false;
}
// Attempt to delete the snapshot device if one still exists. Nothing
// should be depending on the device, and device-mapper should have
// flushed remaining I/O. We could in theory replace with dm-zero (or
// re-use the table above), but for now it's better to know why this
// would fail.
if (status.compression_enabled()) {
UnmapDmUserDevice(name);
if (!UpdateUsesUserSnapshots(lock)) {
// Attempt to delete the snapshot device if one still exists. Nothing
// should be depending on the device, and device-mapper should have
// flushed remaining I/O. We could in theory replace with dm-zero (or
// re-use the table above), but for now it's better to know why this
// would fail.
//
// Furthermore, we should not be trying to unmap for userspace snapshot
// as unmap will fail since dm-user itself was a snapshot device prior
// to switching of tables. Unmap will fail as the device will be mounted
// by system partitions
if (status.compression_enabled()) {
auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
UnmapDmUserDevice(dm_user_name);
}
}
// We can't delete base device immediately as daemon holds a reference.
// Make sure we wait for all the worker threads to terminate and release
// the reference
if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) {
if (!snapuserd_client_->WaitForDeviceDelete(name)) {
LOG(ERROR) << "Failed to wait for " << name << " control device to delete";
}
}
auto base_name = GetBaseDeviceName(name);
if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
@ -1464,10 +1657,15 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
return false;
}
if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
snapuserd_argv->emplace_back("-user_snapshot");
}
size_t num_cows = 0;
size_t ok_cows = 0;
for (const auto& snapshot : snapshots) {
std::string user_cow_name = GetDmUserCowName(snapshot);
std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get()));
if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) {
continue;
}
@ -1513,6 +1711,12 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
continue;
}
std::string base_path_merge;
if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) {
LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
continue;
}
std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status);
std::string cow_image_device;
@ -1529,8 +1733,14 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
}
if (transition == InitTransition::SELINUX_DETACH) {
auto message = misc_name + "," + cow_image_device + "," + source_device;
snapuserd_argv->emplace_back(std::move(message));
if (!UpdateUsesUserSnapshots(lock.get())) {
auto message = misc_name + "," + cow_image_device + "," + source_device;
snapuserd_argv->emplace_back(std::move(message));
} else {
auto message = misc_name + "," + cow_image_device + "," + source_device + "," +
base_path_merge;
snapuserd_argv->emplace_back(std::move(message));
}
// Do not attempt to connect to the new snapuserd yet, it hasn't
// been started. We do however want to wait for the misc device
@ -1539,8 +1749,15 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
continue;
}
uint64_t base_sectors =
snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
uint64_t base_sectors;
if (!UpdateUsesUserSnapshots(lock.get())) {
base_sectors =
snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
} else {
base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device,
source_device, base_path_merge);
}
if (base_sectors == 0) {
// Unrecoverable as metadata reads from cow device failed
LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
@ -1775,30 +1992,36 @@ UpdateState SnapshotManager::GetUpdateState(double* progress) {
return state;
}
// Sum all the snapshot states as if the system consists of a single huge
// snapshots device, then compute the merge completion percentage of that
// device.
std::vector<std::string> snapshots;
if (!ListSnapshots(lock.get(), &snapshots)) {
LOG(ERROR) << "Could not list snapshots";
return state;
if (!UpdateUsesUserSnapshots(lock.get())) {
// Sum all the snapshot states as if the system consists of a single huge
// snapshots device, then compute the merge completion percentage of that
// device.
std::vector<std::string> snapshots;
if (!ListSnapshots(lock.get(), &snapshots)) {
LOG(ERROR) << "Could not list snapshots";
return state;
}
DmTargetSnapshot::Status fake_snapshots_status = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
if (!IsSnapshotDevice(snapshot)) continue;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
fake_snapshots_status.total_sectors += current_status.total_sectors;
fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
}
*progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
update_status.sectors_allocated());
} else {
if (EnsureSnapuserdConnected()) {
*progress = snapuserd_client_->GetMergePercent();
}
}
DmTargetSnapshot::Status fake_snapshots_status = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
if (!IsSnapshotDevice(snapshot)) continue;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
fake_snapshots_status.total_sectors += current_status.total_sectors;
fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
}
*progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
update_status.sectors_allocated());
return state;
}
@ -1813,6 +2036,38 @@ bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) {
return update_status.compression_enabled();
}
bool SnapshotManager::UpdateUsesUserSnapshots() {
// This and the following function is constantly
// invoked during snapshot merge. We want to avoid
// constantly reading from disk. Hence, store this
// value in memory.
//
// Furthermore, this value in the disk is set
// only when OTA is applied and doesn't change
// during merge phase. Hence, once we know that
// the value is read from disk the very first time,
// it is safe to read successive checks from memory.
if (is_snapshot_userspace_.has_value()) {
return is_snapshot_userspace_.value();
}
auto lock = LockShared();
if (!lock) return false;
return UpdateUsesUserSnapshots(lock.get());
}
bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) {
// See UpdateUsesUserSnapshots()
if (is_snapshot_userspace_.has_value()) {
return is_snapshot_userspace_.value();
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
is_snapshot_userspace_ = update_status.userspace_snapshots();
return is_snapshot_userspace_.value();
}
bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
const std::string& suffix) {
CHECK(lock);
@ -2040,6 +2295,16 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
paths->target_device = base_path;
}
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) {
return false;
}
// Wait for the base device to appear
if (!WaitForDevice(base_path, remaining_time)) {
return false;
}
if (!live_snapshot_status.has_value()) {
created_devices.Release();
return true;
@ -2053,7 +2318,7 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
return false;
}
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
std::string cow_name;
@ -2109,10 +2374,10 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
return false;
}
auto name = GetDmUserCowName(params.GetPartitionName());
auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock));
std::string new_cow_device;
if (!MapDmUserCow(lock, name, cow_path, source_device_path, remaining_time,
if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time,
&new_cow_device)) {
LOG(ERROR) << "Could not map dm-user device for partition "
<< params.GetPartitionName();
@ -2126,21 +2391,37 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
cow_device = new_cow_device;
}
std::string path;
if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
&path)) {
LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
return false;
}
// No need to add params.GetPartitionName() to created_devices since it is immediately released.
// For userspace snapshots, dm-user block device itself will act as a
// snapshot device. There is one subtle difference - MapSnapshot will create
// either snapshot target or snapshot-merge target based on the underlying
// state of the snapshot device. If snapshot-merge target is created, merge
// will immediately start in the kernel.
//
// This is no longer true with respect to userspace snapshots. When dm-user
// block device is created, we just have the snapshots ready but daemon in
// the user-space will not start the merge. We have to explicitly inform the
// daemon to resume the merge. Check ProcessUpdateState() call stack.
if (!UpdateUsesUserSnapshots(lock)) {
std::string path;
if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
&path)) {
LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
return false;
}
// No need to add params.GetPartitionName() to created_devices since it is immediately
// released.
if (paths) {
paths->snapshot_device = path;
if (paths) {
paths->snapshot_device = path;
}
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
} else {
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at "
<< cow_device;
}
created_devices.Release();
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
return true;
}
@ -2148,8 +2429,10 @@ bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock,
const std::string& target_partition_name) {
CHECK(lock);
if (!UnmapSnapshot(lock, target_partition_name)) {
return false;
if (!UpdateUsesUserSnapshots(lock)) {
if (!UnmapSnapshot(lock, target_partition_name)) {
return false;
}
}
if (!UnmapCowDevices(lock, target_partition_name)) {
@ -2247,8 +2530,17 @@ bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name)
CHECK(lock);
if (!EnsureImageManager()) return false;
if (UpdateUsesCompression(lock) && !UnmapDmUserDevice(name)) {
return false;
if (UpdateUsesCompression(lock)) {
if (UpdateUsesUserSnapshots(lock)) {
if (!UnmapUserspaceSnapshotDevice(lock, name)) {
return false;
}
} else {
auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
if (!UnmapDmUserDevice(dm_user_name)) {
return false;
}
}
}
if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
@ -2264,8 +2556,7 @@ bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name)
return true;
}
bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
auto dm_user_name = GetDmUserCowName(snapshot_name);
bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) {
if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
return true;
}
@ -2291,6 +2582,46 @@ bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
return true;
}
bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock,
const std::string& snapshot_name) {
auto dm_user_name = GetDmUserCowName(snapshot_name, GetSnapshotDriver(lock));
if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
return true;
}
CHECK(lock);
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, snapshot_name, &snapshot_status)) {
return false;
}
// If the merge is complete, then we switch dm tables which is equivalent
// to unmap; hence, we can't be deleting the device
// as the table would be mounted off partitions and will fail.
if (snapshot_status.state() != SnapshotState::MERGE_COMPLETED) {
if (!DeleteDeviceIfExists(dm_user_name)) {
LOG(ERROR) << "Cannot unmap " << dm_user_name;
return false;
}
}
if (EnsureSnapuserdConnected()) {
if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
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.
auto control_device = "/dev/dm-user/" + dm_user_name;
if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
return false;
}
return true;
}
bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
auto lock = LockExclusive();
if (!lock) return false;
@ -2527,6 +2858,7 @@ bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state,
status.set_compression_enabled(old_status.compression_enabled());
status.set_source_build_fingerprint(old_status.source_build_fingerprint());
status.set_merge_phase(old_status.merge_phase());
status.set_userspace_snapshots(old_status.userspace_snapshots());
}
return WriteSnapshotUpdateStatus(lock, status);
}
@ -2844,6 +3176,43 @@ Return SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manife
SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
status.set_state(update_state);
status.set_compression_enabled(cow_creator.compression_enabled);
if (cow_creator.compression_enabled) {
if (!device()->IsTestDevice()) {
// Userspace snapshots is enabled only if compression is enabled
status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
if (IsUserspaceSnapshotsEnabled()) {
is_snapshot_userspace_ = true;
LOG(INFO) << "User-space snapshots enabled";
} else {
is_snapshot_userspace_ = false;
LOG(INFO) << "User-space snapshots disabled";
}
// Terminate stale daemon if any
std::unique_ptr<SnapuserdClient> snapuserd_client =
SnapuserdClient::Connect(kSnapuserdSocket, 10s);
if (snapuserd_client) {
snapuserd_client->DetachSnapuserd();
snapuserd_client->CloseConnection();
snapuserd_client = nullptr;
}
// Clear the cached client if any
if (snapuserd_client_) {
snapuserd_client_->CloseConnection();
snapuserd_client_ = nullptr;
}
} else {
status.set_userspace_snapshots(!IsDmSnapshotTestingEnabled());
if (IsDmSnapshotTestingEnabled()) {
is_snapshot_userspace_ = false;
LOG(INFO) << "User-space snapshots disabled for testing";
} else {
is_snapshot_userspace_ = true;
LOG(INFO) << "User-space snapshots enabled for testing";
}
}
}
if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
LOG(ERROR) << "Unable to write new update state";
return Return::Error();

View file

@ -121,6 +121,11 @@ bool SnapshotManagerStub::UpdateUsesCompression() {
return false;
}
bool SnapshotManagerStub::UpdateUsesUserSnapshots() {
LOG(ERROR) << __FUNCTION__ << " should never be called.";
return false;
}
class SnapshotMergeStatsStub : public ISnapshotMergeStats {
bool Start() override { return false; }
void set_state(android::snapshot::UpdateState, bool) override {}

View file

@ -45,6 +45,8 @@
#include "partition_cow_creator.h"
#include "utility.h"
#include <android-base/properties.h>
// Mock classes are not used. Header included to ensure mocked class definition aligns with the
// class itself.
#include <libsnapshot/mock_device_info.h>
@ -272,7 +274,7 @@ class SnapshotTest : public ::testing::Test {
AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
AssertionResult res = AssertionSuccess();
if (!(res = DeleteDevice(snapshot))) return res;
if (!sm->UnmapDmUserDevice(snapshot)) {
if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) {
return AssertionFailure() << "Cannot delete dm-user device for " << snapshot;
}
if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
@ -2559,5 +2561,15 @@ void SnapshotTestEnvironment::TearDown() {
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
return RUN_ALL_TESTS();
android::base::SetProperty("ctl.stop", "snapuserd");
if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
return testing::AssertionFailure()
<< "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
}
int ret = RUN_ALL_TESTS();
android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
return ret;
}

View file

@ -142,3 +142,39 @@ cc_test {
auto_gen_config: true,
require_root: false,
}
cc_test {
name: "snapuserd_test",
defaults: [
"fs_mgr_defaults",
],
srcs: [
"user-space-merge/snapuserd_test.cpp",
],
cflags: [
"-Wall",
"-Werror",
],
shared_libs: [
"libbase",
"liblog",
],
static_libs: [
"libbrotli",
"libgtest",
"libsnapshot_cow",
"libsnapshot_snapuserd",
"libcutils_sockets",
"libz",
"libfs_mgr",
"libdm",
"libext4_utils",
],
header_libs: [
"libstorage_literals_headers",
"libfiemap_headers",
],
test_min_api_level: 30,
auto_gen_config: true,
require_root: false,
}

View file

@ -63,7 +63,8 @@ class SnapuserdClient {
// The misc_name must be the "misc_name" given to dm-user in step 2.
//
uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
const std::string& backing_device);
const std::string& backing_device,
const std::string& base_path_merge = "");
bool AttachDmUser(const std::string& misc_name);
// Wait for snapuserd to disassociate with a dm-user control device. This

View file

@ -195,8 +195,16 @@ bool SnapuserdClient::AttachDmUser(const std::string& misc_name) {
}
uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
const std::string& backing_device) {
std::vector<std::string> parts = {"init", misc_name, cow_device, backing_device};
const std::string& backing_device,
const std::string& base_path_merge) {
std::vector<std::string> parts;
if (base_path_merge.empty()) {
parts = {"init", misc_name, cow_device, backing_device};
} else {
// For userspace snapshots
parts = {"init", misc_name, cow_device, backing_device, base_path_merge};
}
std::string msg = android::base::Join(parts, ",");
if (!Sendmsg(msg)) {
LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";

View file

@ -14,25 +14,95 @@
* limitations under the License.
*/
#include "snapuserd_daemon.h"
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <gflags/gflags.h>
#include <snapuserd/snapuserd_client.h>
#include "snapuserd_daemon.h"
DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
DEFINE_bool(no_socket, false,
"If true, no socket is used. Each additional argument is an INIT message.");
DEFINE_bool(socket_handoff, false,
"If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
namespace android {
namespace snapshot {
bool Daemon::StartServer(int argc, char** argv) {
bool Daemon::IsUserspaceSnapshotsEnabled() {
return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
}
bool Daemon::IsDmSnapshotTestingEnabled() {
return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
}
bool Daemon::StartDaemon(int argc, char** argv) {
int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
// Daemon launched from first stage init and during selinux transition
// will have the command line "-user_snapshot" flag set if the user-space
// snapshots are enabled.
//
// Daemon launched as a init service during "socket-handoff" and when OTA
// is applied will check for the property. This is ok as the system
// properties are valid at this point. We can't do this during first
// stage init and hence use the command line flags to get the information.
if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) {
LOG(INFO) << "Starting daemon for user-space snapshots.....";
return StartServerForUserspaceSnapshots(arg_start, argc, argv);
} else {
LOG(INFO) << "Starting daemon for dm-snapshots.....";
return StartServerForDmSnapshot(arg_start, argc, argv);
}
}
bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) {
sigfillset(&signal_mask_);
sigdelset(&signal_mask_, SIGINT);
sigdelset(&signal_mask_, SIGTERM);
sigdelset(&signal_mask_, SIGUSR1);
// Masking signals here ensure that after this point, we won't handle INT/TERM
// until after we call into ppoll()
signal(SIGINT, Daemon::SignalHandler);
signal(SIGTERM, Daemon::SignalHandler);
signal(SIGPIPE, Daemon::SignalHandler);
signal(SIGUSR1, Daemon::SignalHandler);
MaskAllSignalsExceptIntAndTerm();
if (FLAGS_socket_handoff) {
return user_server_.RunForSocketHandoff();
}
if (!FLAGS_no_socket) {
if (!user_server_.Start(FLAGS_socket)) {
return false;
}
return user_server_.Run();
}
for (int i = arg_start; i < argc; i++) {
auto parts = android::base::Split(argv[i], ",");
if (parts.size() != 4) {
LOG(ERROR) << "Malformed message, expected three sub-arguments.";
return false;
}
auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
if (!handler || !user_server_.StartHandler(handler)) {
return false;
}
}
// Skip the accept() call to avoid spurious log spam. The server will still
// run until all handlers have completed.
return user_server_.WaitForSocket();
}
bool Daemon::StartServerForDmSnapshot(int arg_start, int argc, char** argv) {
sigfillset(&signal_mask_);
sigdelset(&signal_mask_, SIGINT);
sigdelset(&signal_mask_, SIGTERM);
@ -95,11 +165,19 @@ void Daemon::MaskAllSignals() {
}
void Daemon::Interrupt() {
server_.Interrupt();
if (IsUserspaceSnapshotsEnabled()) {
user_server_.Interrupt();
} else {
server_.Interrupt();
}
}
void Daemon::ReceivedSocketSignal() {
server_.ReceivedSocketSignal();
if (IsUserspaceSnapshotsEnabled()) {
user_server_.ReceivedSocketSignal();
} else {
server_.ReceivedSocketSignal();
}
}
void Daemon::SignalHandler(int signal) {
@ -133,9 +211,10 @@ int main(int argc, char** argv) {
android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
if (!daemon.StartServer(argc, argv)) {
LOG(ERROR) << "Snapuserd daemon failed to start.";
if (!daemon.StartDaemon(argc, argv)) {
LOG(ERROR) << "Snapuserd daemon failed to start";
exit(EXIT_FAILURE);
}
return 0;
}

View file

@ -20,6 +20,7 @@
#include <vector>
#include "dm-snapshot-merge/snapuserd_server.h"
#include "user-space-merge/snapuserd_server.h"
namespace android {
namespace snapshot {
@ -35,9 +36,13 @@ class Daemon {
return instance;
}
bool StartServer(int argc, char** argv);
bool StartServerForDmSnapshot(int arg_start, int argc, char** argv);
bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv);
void Interrupt();
void ReceivedSocketSignal();
bool IsUserspaceSnapshotsEnabled();
bool IsDmSnapshotTestingEnabled();
bool StartDaemon(int argc, char** argv);
private:
// Signal mask used with ppoll()
@ -47,6 +52,7 @@ class Daemon {
void operator=(Daemon const&) = delete;
SnapuserdServer server_;
UserSnapshotServer user_server_;
void MaskAllSignalsExceptIntAndTerm();
void MaskAllSignals();
static void SignalHandler(int signal);

View file

@ -35,7 +35,7 @@ SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
}
bool SnapshotHandler::InitializeWorkers() {
for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
for (int i = 0; i < kNumWorkerThreads; i++) {
std::unique_ptr<Worker> wt =
std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
misc_name_, base_path_merge_, GetSharedPtr());

View file

@ -48,10 +48,10 @@ namespace snapshot {
using android::base::unique_fd;
using namespace std::chrono_literals;
static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20);
static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
static constexpr int NUM_THREADS_PER_PARTITION = 1;
static constexpr int kNumWorkerThreads = 4;
#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "

View file

@ -231,8 +231,8 @@ void Worker::InitializeBufsink() {
// Allocate the buffer which is used to communicate between
// daemon and dm-user. The buffer comprises of header and a fixed payload.
// If the dm-user requests a big IO, the IO will be broken into chunks
// of PAYLOAD_SIZE.
size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
// of PAYLOAD_BUFFER_SZ.
size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_BUFFER_SZ;
bufsink_.Initialize(buf_size);
}
@ -326,7 +326,7 @@ bool Worker::ReadAlignedSector(sector_t sector, size_t sz, bool header_response)
do {
// Process 1MB payload at a time
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size);
header->type = DM_USER_RESP_SUCCESS;
size_t total_bytes_read = 0;

View file

@ -81,11 +81,11 @@ bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter)
// Why 2048 ops ? We can probably increase this to bigger value but just
// need to ensure that merge makes forward progress if there are
// crashes repeatedly which is highly unlikely.
int total_ops_merged_per_commit = (PAYLOAD_SIZE / BLOCK_SZ) * 8;
int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 8;
int num_ops_merged = 0;
while (!cowop_iter->Done()) {
int num_ops = PAYLOAD_SIZE / BLOCK_SZ;
int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
std::vector<const CowOperation*> replace_zero_vec;
uint64_t source_offset;
@ -292,6 +292,7 @@ bool Worker::RunMergeThread() {
if (!Init()) {
SNAP_LOG(ERROR) << "Merge thread initialization failed...";
snapuserd_->MergeFailed();
return false;
}

View file

@ -429,7 +429,7 @@ void ReadAhead::InitializeBuffer() {
static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
// For xor ops
bufsink_.Initialize(PAYLOAD_SIZE);
bufsink_.Initialize(PAYLOAD_BUFFER_SZ);
}
} // namespace snapshot

View file

@ -44,7 +44,7 @@ using namespace std::string_literals;
using android::base::borrowed_fd;
using android::base::unique_fd;
DaemonOps SnapuserServer::Resolveop(std::string& input) {
DaemonOps UserSnapshotServer::Resolveop(std::string& input) {
if (input == "init") return DaemonOps::INIT;
if (input == "start") return DaemonOps::START;
if (input == "stop") return DaemonOps::STOP;
@ -59,14 +59,14 @@ DaemonOps SnapuserServer::Resolveop(std::string& input) {
return DaemonOps::INVALID;
}
SnapuserServer::~SnapuserServer() {
UserSnapshotServer::~UserSnapshotServer() {
// Close any client sockets that were added via AcceptClient().
for (size_t i = 1; i < watched_fds_.size(); i++) {
close(watched_fds_[i].fd);
}
}
std::string SnapuserServer::GetDaemonStatus() {
std::string UserSnapshotServer::GetDaemonStatus() {
std::string msg = "";
if (IsTerminating())
@ -77,8 +77,8 @@ std::string SnapuserServer::GetDaemonStatus() {
return msg;
}
void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
std::vector<std::string>& out) {
void UserSnapshotServer::Parsemsg(std::string const& msg, const char delim,
std::vector<std::string>& out) {
std::stringstream ss(msg);
std::string s;
@ -87,15 +87,15 @@ void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
}
}
void SnapuserServer::ShutdownThreads() {
void UserSnapshotServer::ShutdownThreads() {
terminating_ = true;
JoinAllThreads();
}
DmUserHandler::DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
UserSnapshotDmUserHandler::UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
: snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
if (ret < 0) {
PLOG(ERROR) << "Snapuserd:server: send() failed";
@ -109,8 +109,8 @@ bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& m
return true;
}
bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
char msg[MAX_PACKET_SIZE];
bool UserSnapshotServer::Recv(android::base::borrowed_fd fd, std::string* data) {
char msg[kMaxPacketSize];
ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
if (rv < 0) {
PLOG(ERROR) << "recv failed";
@ -120,7 +120,7 @@ bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
return true;
}
bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
bool UserSnapshotServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
const char delim = ',';
std::vector<std::string> out;
@ -290,7 +290,7 @@ bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string
}
}
void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
void UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler) {
LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
handler->snapuserd()->SetSocketPresent(is_socket_present_);
@ -337,7 +337,7 @@ void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
}
}
bool SnapuserServer::Start(const std::string& socketname) {
bool UserSnapshotServer::Start(const std::string& socketname) {
bool start_listening = true;
sockfd_.reset(android_get_control_socket(socketname.c_str()));
@ -353,7 +353,7 @@ bool SnapuserServer::Start(const std::string& socketname) {
return StartWithSocket(start_listening);
}
bool SnapuserServer::StartWithSocket(bool start_listening) {
bool UserSnapshotServer::StartWithSocket(bool start_listening) {
if (start_listening && listen(sockfd_.get(), 4) < 0) {
PLOG(ERROR) << "listen socket failed";
return false;
@ -374,7 +374,7 @@ bool SnapuserServer::StartWithSocket(bool start_listening) {
return true;
}
bool SnapuserServer::Run() {
bool UserSnapshotServer::Run() {
LOG(INFO) << "Now listening on snapuserd socket";
while (!IsTerminating()) {
@ -406,9 +406,9 @@ bool SnapuserServer::Run() {
return true;
}
void SnapuserServer::JoinAllThreads() {
void UserSnapshotServer::JoinAllThreads() {
// Acquire the thread list within the lock.
std::vector<std::shared_ptr<DmUserHandler>> dm_users;
std::vector<std::shared_ptr<UserSnapshotDmUserHandler>> dm_users;
{
std::lock_guard<std::mutex> guard(lock_);
dm_users = std::move(dm_users_);
@ -421,14 +421,14 @@ void SnapuserServer::JoinAllThreads() {
}
}
void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
struct pollfd p = {};
p.fd = fd.get();
p.events = events;
watched_fds_.emplace_back(std::move(p));
}
void SnapuserServer::AcceptClient() {
void UserSnapshotServer::AcceptClient() {
int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "accept4 failed";
@ -438,7 +438,7 @@ void SnapuserServer::AcceptClient() {
AddWatchedFd(fd, POLLIN);
}
bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
bool UserSnapshotServer::HandleClient(android::base::borrowed_fd fd, int revents) {
if (revents & POLLHUP) {
LOG(DEBUG) << "Snapuserd client disconnected";
return false;
@ -455,16 +455,15 @@ bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
return true;
}
void SnapuserServer::Interrupt() {
void UserSnapshotServer::Interrupt() {
// Force close the socket so poll() fails.
sockfd_ = {};
SetTerminating();
}
std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device,
const std::string& base_path_merge) {
std::shared_ptr<UserSnapshotDmUserHandler> UserSnapshotServer::AddHandler(
const std::string& misc_name, const std::string& cow_device_path,
const std::string& backing_device, const std::string& base_path_merge) {
auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
base_path_merge);
if (!snapuserd->InitCowDevice()) {
@ -477,7 +476,7 @@ std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& mis
return nullptr;
}
auto handler = std::make_shared<DmUserHandler>(snapuserd);
auto handler = std::make_shared<UserSnapshotDmUserHandler>(snapuserd);
{
std::lock_guard<std::mutex> lock(lock_);
if (FindHandler(&lock, misc_name) != dm_users_.end()) {
@ -489,7 +488,7 @@ std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& mis
return handler;
}
bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
bool UserSnapshotServer::StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
if (handler->snapuserd()->IsAttached()) {
LOG(ERROR) << "Handler already attached";
return false;
@ -497,11 +496,11 @@ bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler)
handler->snapuserd()->AttachControlDevice();
handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler));
handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
return true;
}
bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
bool UserSnapshotServer::StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
if (!handler->snapuserd()->IsAttached()) {
LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
return false;
@ -511,8 +510,8 @@ bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
return true;
}
auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
const std::string& misc_name) -> HandlerList::iterator {
auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
const std::string& misc_name) -> HandlerList::iterator {
CHECK(proof_of_lock);
for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@ -523,7 +522,7 @@ auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
return dm_users_.end();
}
void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
CHECK(proof_of_lock);
for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@ -533,11 +532,12 @@ void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of
}
}
std::string SnapuserServer::GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler) {
std::string UserSnapshotServer::GetMergeStatus(
const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
return handler->snapuserd()->GetMergeStatus();
}
double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
double UserSnapshotServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
CHECK(proof_of_lock);
double percentage = 0.0;
int n = 0;
@ -567,8 +567,8 @@ double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_
return percentage;
}
bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
std::shared_ptr<DmUserHandler> handler;
bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
std::shared_ptr<UserSnapshotDmUserHandler> handler;
{
std::lock_guard<std::mutex> lock(lock_);
@ -588,7 +588,7 @@ bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
return true;
}
bool SnapuserServer::WaitForSocket() {
bool UserSnapshotServer::WaitForSocket() {
auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@ -642,7 +642,7 @@ bool SnapuserServer::WaitForSocket() {
return Run();
}
bool SnapuserServer::RunForSocketHandoff() {
bool UserSnapshotServer::RunForSocketHandoff() {
unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
if (proxy_fd < 0) {
PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;

View file

@ -33,7 +33,7 @@
namespace android {
namespace snapshot {
static constexpr uint32_t MAX_PACKET_SIZE = 512;
static constexpr uint32_t kMaxPacketSize = 512;
enum class DaemonOps {
INIT,
@ -49,9 +49,9 @@ enum class DaemonOps {
INVALID,
};
class DmUserHandler {
class UserSnapshotDmUserHandler {
public:
explicit DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
explicit UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
void FreeResources() {
// Each worker thread holds a reference to snapuserd.
@ -76,7 +76,7 @@ class DmUserHandler {
bool thread_terminated_ = false;
};
class SnapuserServer {
class UserSnapshotServer {
private:
android::base::unique_fd sockfd_;
bool terminating_;
@ -87,7 +87,7 @@ class SnapuserServer {
std::mutex lock_;
using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
using HandlerList = std::vector<std::shared_ptr<UserSnapshotDmUserHandler>>;
HandlerList dm_users_;
void AddWatchedFd(android::base::borrowed_fd fd, int events);
@ -105,11 +105,11 @@ class SnapuserServer {
bool IsTerminating() { return terminating_; }
void RunThread(std::shared_ptr<DmUserHandler> handler);
void RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler);
void JoinAllThreads();
bool StartWithSocket(bool start_listening);
// Find a DmUserHandler within a lock.
// Find a UserSnapshotDmUserHandler within a lock.
HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
const std::string& misc_name);
@ -117,8 +117,8 @@ class SnapuserServer {
void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
public:
SnapuserServer() { terminating_ = false; }
~SnapuserServer();
UserSnapshotServer() { terminating_ = false; }
~UserSnapshotServer();
bool Start(const std::string& socketname);
bool Run();
@ -126,13 +126,13 @@ class SnapuserServer {
bool RunForSocketHandoff();
bool WaitForSocket();
std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device,
const std::string& base_path_merge);
bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
bool StartMerge(const std::shared_ptr<DmUserHandler>& handler);
std::string GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler);
std::shared_ptr<UserSnapshotDmUserHandler> AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device,
const std::string& base_path_merge);
bool StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
bool StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
std::string GetMergeStatus(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
void SetTerminating() { terminating_ = true; }
void ReceivedSocketSignal() { received_socket_signal_ = true; }

View file

@ -0,0 +1,861 @@
// 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 <linux/memfd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <chrono>
#include <iostream>
#include <memory>
#include <string_view>
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <fs_mgr/file_wait.h>
#include <gtest/gtest.h>
#include <libdm/dm.h>
#include <libdm/loop_control.h>
#include <libsnapshot/cow_writer.h>
#include <snapuserd/snapuserd_client.h>
#include <storage_literals/storage_literals.h>
#include "snapuserd_core.h"
namespace android {
namespace snapshot {
using namespace android::storage_literals;
using android::base::unique_fd;
using LoopDevice = android::dm::LoopDevice;
using namespace std::chrono_literals;
using namespace android::dm;
using namespace std;
static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
class Tempdevice {
public:
Tempdevice(const std::string& name, const DmTable& table)
: dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5));
}
Tempdevice(Tempdevice&& other) noexcept
: dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) {
other.valid_ = false;
}
~Tempdevice() {
if (valid_) {
dm_.DeleteDevice(name_);
}
}
bool Destroy() {
if (!valid_) {
return false;
}
valid_ = false;
return dm_.DeleteDevice(name_);
}
const std::string& path() const { return path_; }
const std::string& name() const { return name_; }
bool valid() const { return valid_; }
Tempdevice(const Tempdevice&) = delete;
Tempdevice& operator=(const Tempdevice&) = delete;
Tempdevice& operator=(Tempdevice&& other) noexcept {
name_ = other.name_;
valid_ = other.valid_;
other.valid_ = false;
return *this;
}
private:
DeviceMapper& dm_;
std::string name_;
std::string path_;
bool valid_;
};
class SnapuserTest final {
public:
bool Setup();
bool SetupOrderedOps();
bool SetupOrderedOpsInverted();
bool SetupCopyOverlap_1();
bool SetupCopyOverlap_2();
bool Merge();
void ValidateMerge();
void ReadSnapshotDeviceAndValidate();
void Shutdown();
void MergeInterrupt();
void MergeInterruptFixed(int duration);
void MergeInterruptRandomly(int max_duration);
void StartMerge();
void CheckMergeCompletion();
static const uint64_t kSectorSize = 512;
private:
void SetupImpl();
void SimulateDaemonRestart();
void CreateCowDevice();
void CreateCowDeviceOrderedOps();
void CreateCowDeviceOrderedOpsInverted();
void CreateCowDeviceWithCopyOverlap_1();
void CreateCowDeviceWithCopyOverlap_2();
bool SetupDaemon();
void CreateBaseDevice();
void InitCowDevice();
void SetDeviceControlName();
void InitDaemon();
void CreateDmUserDevice();
void StartSnapuserdDaemon();
unique_ptr<LoopDevice> base_loop_;
unique_ptr<Tempdevice> dmuser_dev_;
std::string system_device_ctrl_name_;
std::string system_device_name_;
unique_fd base_fd_;
std::unique_ptr<TemporaryFile> cow_system_;
std::unique_ptr<SnapuserdClient> client_;
std::unique_ptr<uint8_t[]> orig_buffer_;
std::unique_ptr<uint8_t[]> merged_buffer_;
bool setup_ok_ = false;
bool merge_ok_ = false;
size_t size_ = 100_MiB;
int cow_num_sectors_;
int total_base_size_;
};
static unique_fd CreateTempFile(const std::string& name, size_t size) {
unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
if (fd < 0) {
return {};
}
if (size) {
if (ftruncate(fd, size) < 0) {
perror("ftruncate");
return {};
}
if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
perror("fcntl");
return {};
}
}
return fd;
}
void SnapuserTest::Shutdown() {
ASSERT_TRUE(dmuser_dev_->Destroy());
auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
ASSERT_TRUE(client_->DetachSnapuserd());
}
bool SnapuserTest::Setup() {
SetupImpl();
return setup_ok_;
}
bool SnapuserTest::SetupOrderedOps() {
CreateBaseDevice();
CreateCowDeviceOrderedOps();
return SetupDaemon();
}
bool SnapuserTest::SetupOrderedOpsInverted() {
CreateBaseDevice();
CreateCowDeviceOrderedOpsInverted();
return SetupDaemon();
}
bool SnapuserTest::SetupCopyOverlap_1() {
CreateBaseDevice();
CreateCowDeviceWithCopyOverlap_1();
return SetupDaemon();
}
bool SnapuserTest::SetupCopyOverlap_2() {
CreateBaseDevice();
CreateCowDeviceWithCopyOverlap_2();
return SetupDaemon();
}
bool SnapuserTest::SetupDaemon() {
SetDeviceControlName();
StartSnapuserdDaemon();
CreateDmUserDevice();
InitCowDevice();
InitDaemon();
setup_ok_ = true;
return setup_ok_;
}
void SnapuserTest::StartSnapuserdDaemon() {
pid_t pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) {
std::string arg0 = "/system/bin/snapuserd";
std::string arg1 = "-socket="s + kSnapuserdSocketTest;
char* const argv[] = {arg0.data(), arg1.data(), nullptr};
ASSERT_GE(execv(arg0.c_str(), argv), 0);
} else {
client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
ASSERT_NE(client_, nullptr);
}
}
void SnapuserTest::CreateBaseDevice() {
unique_fd rnd_fd;
total_base_size_ = (size_ * 5);
base_fd_ = CreateTempFile("base_device", total_base_size_);
ASSERT_GE(base_fd_, 0);
rnd_fd.reset(open("/dev/random", O_RDONLY));
ASSERT_TRUE(rnd_fd > 0);
std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) {
ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true);
}
ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
ASSERT_TRUE(base_loop_->valid());
}
void SnapuserTest::ReadSnapshotDeviceAndValidate() {
unique_fd fd(open(dmuser_dev_->path().c_str(), O_RDONLY));
ASSERT_GE(fd, 0);
std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
// COPY
loff_t offset = 0;
ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
// REPLACE
offset += size_;
ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
// ZERO
offset += size_;
ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
// REPLACE
offset += size_;
ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
// XOR
offset += size_;
ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
}
void SnapuserTest::CreateCowDeviceWithCopyOverlap_2() {
std::string path = android::base::GetExecutableDirectory();
cow_system_ = std::make_unique<TemporaryFile>(path);
CowOptions options;
options.compression = "gz";
CowWriter writer(options);
ASSERT_TRUE(writer.Initialize(cow_system_->fd));
size_t num_blocks = size_ / options.block_size;
size_t x = num_blocks;
size_t blk_src_copy = 0;
// Create overlapping copy operations
while (1) {
ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
x -= 1;
if (x == 1) {
break;
}
blk_src_copy += 1;
}
// Flush operations
ASSERT_TRUE(writer.Finalize());
// Construct the buffer required for validation
orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
// Read the entire base device
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
true);
// Merged operations required for validation
int block_size = 4096;
x = num_blocks;
loff_t src_offset = block_size;
loff_t dest_offset = 0;
while (1) {
memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
block_size);
x -= 1;
if (x == 1) {
break;
}
src_offset += block_size;
dest_offset += block_size;
}
}
void SnapuserTest::CreateCowDeviceWithCopyOverlap_1() {
std::string path = android::base::GetExecutableDirectory();
cow_system_ = std::make_unique<TemporaryFile>(path);
CowOptions options;
options.compression = "gz";
CowWriter writer(options);
ASSERT_TRUE(writer.Initialize(cow_system_->fd));
size_t num_blocks = size_ / options.block_size;
size_t x = num_blocks;
size_t blk_src_copy = num_blocks - 1;
// Create overlapping copy operations
while (1) {
ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
x -= 1;
if (x == 0) {
ASSERT_EQ(blk_src_copy, 0);
break;
}
blk_src_copy -= 1;
}
// Flush operations
ASSERT_TRUE(writer.Finalize());
// Construct the buffer required for validation
orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
// Read the entire base device
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
true);
// Merged operations
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
true);
ASSERT_EQ(android::base::ReadFullyAtOffset(
base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
true);
}
void SnapuserTest::CreateCowDeviceOrderedOpsInverted() {
unique_fd rnd_fd;
loff_t offset = 0;
std::string path = android::base::GetExecutableDirectory();
cow_system_ = std::make_unique<TemporaryFile>(path);
rnd_fd.reset(open("/dev/random", O_RDONLY));
ASSERT_TRUE(rnd_fd > 0);
std::unique_ptr<uint8_t[]> random_buffer_1_ = 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);
offset += 1_MiB;
}
CowOptions options;
options.compression = "gz";
CowWriter writer(options);
ASSERT_TRUE(writer.Initialize(cow_system_->fd));
size_t num_blocks = size_ / options.block_size;
size_t blk_end_copy = num_blocks * 3;
size_t source_blk = num_blocks - 1;
size_t blk_src_copy = blk_end_copy - 1;
uint16_t xor_offset = 5;
size_t x = num_blocks;
while (1) {
ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
x -= 1;
if (x == 0) {
break;
}
source_blk -= 1;
blk_src_copy -= 1;
}
for (size_t i = num_blocks; i > 0; i--) {
ASSERT_TRUE(writer.AddXorBlocks(num_blocks + i - 1,
&random_buffer_1_.get()[options.block_size * (i - 1)],
options.block_size, 2 * num_blocks + i - 1, xor_offset));
}
// Flush operations
ASSERT_TRUE(writer.Finalize());
// Construct the buffer required for validation
orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
// Read the entire base device
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
true);
// Merged Buffer
memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
for (int i = 0; i < size_; i++) {
orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
}
}
void SnapuserTest::CreateCowDeviceOrderedOps() {
unique_fd rnd_fd;
loff_t offset = 0;
std::string path = android::base::GetExecutableDirectory();
cow_system_ = std::make_unique<TemporaryFile>(path);
rnd_fd.reset(open("/dev/random", O_RDONLY));
ASSERT_TRUE(rnd_fd > 0);
std::unique_ptr<uint8_t[]> random_buffer_1_ = 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);
offset += 1_MiB;
}
memset(random_buffer_1_.get(), 0, size_);
CowOptions options;
options.compression = "gz";
CowWriter writer(options);
ASSERT_TRUE(writer.Initialize(cow_system_->fd));
size_t num_blocks = size_ / options.block_size;
size_t x = num_blocks;
size_t source_blk = 0;
size_t blk_src_copy = 2 * num_blocks;
uint16_t xor_offset = 5;
while (1) {
ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
x -= 1;
if (x == 0) {
break;
}
source_blk += 1;
blk_src_copy += 1;
}
ASSERT_TRUE(writer.AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
xor_offset));
// Flush operations
ASSERT_TRUE(writer.Finalize());
// Construct the buffer required for validation
orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
// Read the entire base device
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
true);
// Merged Buffer
memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
for (int i = 0; i < size_; i++) {
orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
}
}
void SnapuserTest::CreateCowDevice() {
unique_fd rnd_fd;
loff_t offset = 0;
std::string path = android::base::GetExecutableDirectory();
cow_system_ = std::make_unique<TemporaryFile>(path);
rnd_fd.reset(open("/dev/random", O_RDONLY));
ASSERT_TRUE(rnd_fd > 0);
std::unique_ptr<uint8_t[]> random_buffer_1_ = 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);
offset += 1_MiB;
}
CowOptions options;
options.compression = "gz";
CowWriter writer(options);
ASSERT_TRUE(writer.Initialize(cow_system_->fd));
size_t num_blocks = size_ / options.block_size;
size_t blk_end_copy = num_blocks * 2;
size_t source_blk = num_blocks - 1;
size_t blk_src_copy = blk_end_copy - 1;
uint32_t sequence[num_blocks * 2];
// Sequence for Copy ops
for (int i = 0; i < num_blocks; i++) {
sequence[i] = num_blocks - 1 - i;
}
// Sequence for Xor ops
for (int i = 0; i < num_blocks; i++) {
sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
}
ASSERT_TRUE(writer.AddSequenceData(2 * num_blocks, sequence));
size_t x = num_blocks;
while (1) {
ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
x -= 1;
if (x == 0) {
break;
}
source_blk -= 1;
blk_src_copy -= 1;
}
source_blk = num_blocks;
blk_src_copy = blk_end_copy;
ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
size_t blk_zero_copy_start = source_blk + num_blocks;
size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
size_t blk_random2_replace_start = blk_zero_copy_end;
ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
size_t blk_xor_start = blk_random2_replace_start + num_blocks;
size_t xor_offset = BLOCK_SZ / 2;
ASSERT_TRUE(writer.AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
xor_offset));
// Flush operations
ASSERT_TRUE(writer.Finalize());
// Construct the buffer required for validation
orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
std::string zero_buffer(size_, 0);
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_,
size_ + xor_offset),
true);
for (int i = 0; i < size_; i++) {
orig_buffer_.get()[(size_ * 4) + i] =
(uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]);
}
}
void SnapuserTest::InitCowDevice() {
uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
base_loop_->device(), base_loop_->device());
ASSERT_NE(num_sectors, 0);
}
void SnapuserTest::SetDeviceControlName() {
system_device_name_.clear();
system_device_ctrl_name_.clear();
std::string str(cow_system_->path);
std::size_t found = str.find_last_of("/\\");
ASSERT_NE(found, std::string::npos);
system_device_name_ = str.substr(found + 1);
system_device_ctrl_name_ = system_device_name_ + "-ctrl";
}
void SnapuserTest::CreateDmUserDevice() {
unique_fd fd(TEMP_FAILURE_RETRY(open(base_loop_->device().c_str(), O_RDONLY | O_CLOEXEC)));
ASSERT_TRUE(fd > 0);
uint64_t dev_sz = get_block_device_size(fd.get());
ASSERT_TRUE(dev_sz > 0);
cow_num_sectors_ = dev_sz >> 9;
DmTable dmuser_table;
ASSERT_TRUE(dmuser_table.AddTarget(
std::make_unique<DmTargetUser>(0, cow_num_sectors_, system_device_ctrl_name_)));
ASSERT_TRUE(dmuser_table.valid());
dmuser_dev_ = std::make_unique<Tempdevice>(system_device_name_, dmuser_table);
ASSERT_TRUE(dmuser_dev_->valid());
ASSERT_FALSE(dmuser_dev_->path().empty());
auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
}
void SnapuserTest::InitDaemon() {
bool ok = client_->AttachDmUser(system_device_ctrl_name_);
ASSERT_TRUE(ok);
}
void SnapuserTest::CheckMergeCompletion() {
while (true) {
double percentage = client_->GetMergePercent();
if ((int)percentage == 100) {
break;
}
std::this_thread::sleep_for(1s);
}
}
void SnapuserTest::SetupImpl() {
CreateBaseDevice();
CreateCowDevice();
SetDeviceControlName();
StartSnapuserdDaemon();
CreateDmUserDevice();
InitCowDevice();
InitDaemon();
setup_ok_ = true;
}
bool SnapuserTest::Merge() {
StartMerge();
CheckMergeCompletion();
merge_ok_ = true;
return merge_ok_;
}
void SnapuserTest::StartMerge() {
bool ok = client_->InitiateMerge(system_device_ctrl_name_);
ASSERT_TRUE(ok);
}
void SnapuserTest::ValidateMerge() {
merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
true);
ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
}
void SnapuserTest::SimulateDaemonRestart() {
Shutdown();
std::this_thread::sleep_for(500ms);
SetDeviceControlName();
StartSnapuserdDaemon();
CreateDmUserDevice();
InitCowDevice();
InitDaemon();
}
void SnapuserTest::MergeInterruptRandomly(int max_duration) {
std::srand(std::time(nullptr));
StartMerge();
for (int i = 0; i < 20; i++) {
int duration = std::rand() % max_duration;
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
SimulateDaemonRestart();
StartMerge();
}
SimulateDaemonRestart();
ASSERT_TRUE(Merge());
}
void SnapuserTest::MergeInterruptFixed(int duration) {
StartMerge();
for (int i = 0; i < 25; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
SimulateDaemonRestart();
StartMerge();
}
SimulateDaemonRestart();
ASSERT_TRUE(Merge());
}
void SnapuserTest::MergeInterrupt() {
// Interrupt merge at various intervals
StartMerge();
std::this_thread::sleep_for(250ms);
SimulateDaemonRestart();
StartMerge();
std::this_thread::sleep_for(250ms);
SimulateDaemonRestart();
StartMerge();
std::this_thread::sleep_for(150ms);
SimulateDaemonRestart();
StartMerge();
std::this_thread::sleep_for(100ms);
SimulateDaemonRestart();
StartMerge();
std::this_thread::sleep_for(800ms);
SimulateDaemonRestart();
StartMerge();
std::this_thread::sleep_for(600ms);
SimulateDaemonRestart();
ASSERT_TRUE(Merge());
}
TEST(Snapuserd_Test, Snapshot_IO_TEST) {
SnapuserTest harness;
ASSERT_TRUE(harness.Setup());
// I/O before merge
harness.ReadSnapshotDeviceAndValidate();
ASSERT_TRUE(harness.Merge());
harness.ValidateMerge();
// I/O after merge - daemon should read directly
// from base device
harness.ReadSnapshotDeviceAndValidate();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST) {
SnapuserTest harness;
ASSERT_TRUE(harness.Setup());
// Issue I/O before merge begins
std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
// Start the merge
ASSERT_TRUE(harness.Merge());
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST_1) {
SnapuserTest harness;
ASSERT_TRUE(harness.Setup());
// Start the merge
harness.StartMerge();
// Issue I/O in parallel when merge is in-progress
std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
harness.CheckMergeCompletion();
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
SnapuserTest harness;
ASSERT_TRUE(harness.Setup());
harness.MergeInterrupt();
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
SnapuserTest harness;
ASSERT_TRUE(harness.SetupCopyOverlap_1());
ASSERT_TRUE(harness.Merge());
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
SnapuserTest harness;
ASSERT_TRUE(harness.SetupCopyOverlap_2());
ASSERT_TRUE(harness.Merge());
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
SnapuserTest harness;
ASSERT_TRUE(harness.SetupCopyOverlap_1());
harness.MergeInterrupt();
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Ordered) {
SnapuserTest harness;
ASSERT_TRUE(harness.SetupOrderedOps());
harness.MergeInterruptFixed(300);
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Ordered) {
SnapuserTest harness;
ASSERT_TRUE(harness.SetupOrderedOps());
harness.MergeInterruptRandomly(500);
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Inverted) {
SnapuserTest harness;
ASSERT_TRUE(harness.SetupOrderedOpsInverted());
harness.MergeInterruptFixed(50);
harness.ValidateMerge();
harness.Shutdown();
}
TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Inverted) {
SnapuserTest harness;
ASSERT_TRUE(harness.SetupOrderedOpsInverted());
harness.MergeInterruptRandomly(50);
harness.ValidateMerge();
harness.Shutdown();
}
} // namespace snapshot
} // namespace android
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -570,7 +570,6 @@ void SnapshotHandler::NotifyIOCompletion(uint64_t new_block) {
{
std::unique_lock<std::mutex> lock(blk_state->m_lock);
CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
blk_state->num_ios_in_progress -= 1;
if (blk_state->num_ios_in_progress == 0) {
pending_ios = false;

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <fs_mgr/roots.h>
@ -187,6 +188,10 @@ bool IsCompressionEnabled() {
return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
}
bool IsUserspaceSnapshotsEnabled() {
return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
}
std::string GetOtherPartitionName(const std::string& name) {
auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
CHECK(suffix == "_a" || suffix == "_b");
@ -195,5 +200,9 @@ std::string GetOtherPartitionName(const std::string& name) {
return name.substr(0, name.size() - suffix.size()) + other_suffix;
}
bool IsDmSnapshotTestingEnabled() {
return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
}
} // namespace snapshot
} // namespace android

View file

@ -131,8 +131,11 @@ void AppendExtent(google::protobuf::RepeatedPtrField<chromeos_update_engine::Ext
bool IsCompressionEnabled();
bool IsUserspaceSnapshotsEnabled();
bool IsDmSnapshotTestingEnabled();
// Swap the suffix of a partition name.
std::string GetOtherPartitionName(const std::string& name);
} // namespace snapshot
} // namespace android

View file

@ -391,7 +391,11 @@ bool FirstStageMount::CreateSnapshotPartitions(SnapshotManager* sm) {
use_snapuserd_ = sm->IsSnapuserdRequired();
if (use_snapuserd_) {
LaunchFirstStageSnapuserd();
if (sm->UpdateUsesUserSnapshots()) {
LaunchFirstStageSnapuserd(SnapshotDriver::DM_USER);
} else {
LaunchFirstStageSnapuserd(SnapshotDriver::DM_SNAPSHOT);
}
}
sm->SetUeventRegenCallback([this](const std::string& device) -> bool {

View file

@ -58,7 +58,7 @@ static constexpr char kSnapuserdFirstStageInfoVar[] = "FIRST_STAGE_SNAPUSERD_INF
static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
void LaunchFirstStageSnapuserd() {
void LaunchFirstStageSnapuserd(SnapshotDriver driver) {
SocketDescriptor socket_desc;
socket_desc.name = android::snapshot::kSnapuserdSocket;
socket_desc.type = SOCK_STREAM;
@ -80,12 +80,23 @@ void LaunchFirstStageSnapuserd() {
}
if (pid == 0) {
socket->Publish();
char arg0[] = "/system/bin/snapuserd";
char* const argv[] = {arg0, nullptr};
if (execv(arg0, argv) < 0) {
PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
if (driver == SnapshotDriver::DM_USER) {
char arg0[] = "/system/bin/snapuserd";
char arg1[] = "-user_snapshot";
char* const argv[] = {arg0, arg1, nullptr};
if (execv(arg0, argv) < 0) {
PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
}
_exit(127);
} else {
char arg0[] = "/system/bin/snapuserd";
char* const argv[] = {arg0, nullptr};
if (execv(arg0, argv) < 0) {
PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
}
_exit(127);
}
_exit(127);
}
auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s);

View file

@ -29,8 +29,13 @@
namespace android {
namespace init {
enum class SnapshotDriver {
DM_SNAPSHOT,
DM_USER,
};
// Fork and exec a new copy of snapuserd.
void LaunchFirstStageSnapuserd();
void LaunchFirstStageSnapuserd(SnapshotDriver driver);
class SnapuserdSelinuxHelper final {
using SnapshotManager = android::snapshot::SnapshotManager;