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:
commit
04eecd441c
28 changed files with 4231 additions and 229 deletions
|
@ -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: [
|
||||
|
|
|
@ -194,6 +194,9 @@ message SnapshotUpdateStatus {
|
|||
|
||||
// Source build fingerprint.
|
||||
string source_build_fingerprint = 8;
|
||||
|
||||
// user-space snapshots
|
||||
bool userspace_snapshots = 9;
|
||||
}
|
||||
|
||||
// Next: 10
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, ¤t_status)) {
|
||||
return false;
|
||||
if (!UpdateUsesUserSnapshots(lock.get())) {
|
||||
DmTargetSnapshot::Status current_status;
|
||||
if (!QuerySnapshotStatus(snapshot, nullptr, ¤t_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, ¤t_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, ¤t_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();
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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_ << ": "
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
|
|
861
fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
Normal file
861
fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
Normal 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();
|
||||
}
|
|
@ -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;
|
||||
|
|
2519
fs_mgr/libsnapshot/userspace_snapshot_test.cpp
Normal file
2519
fs_mgr/libsnapshot/userspace_snapshot_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue