snapuserd: Remove test dependence on LoopDevice.
LoopDevice requires root, which is an obstacle to running this test in
automation. The test also requires memfd which is not available in our
included glibc. Create an IBackingDevice layer so we can use temporary
files instead on host tests, while keeping the block-device code for
on-device tests, which more closely matches how snapuserd runs.
Bug: 288273605
Test: snapuserd_test
Change-Id: I89b154921b6bbcf8fe213ef7f5c4da4d48322909
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 6251e5a..fc260b6 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -221,6 +221,7 @@
],
srcs: [
"testing/dm_user_harness.cpp",
+ "testing/harness.cpp",
"user-space-merge/snapuserd_test.cpp",
],
shared_libs: [
diff --git a/fs_mgr/libsnapshot/snapuserd/testing/harness.cpp b/fs_mgr/libsnapshot/snapuserd/testing/harness.cpp
new file mode 100644
index 0000000..02ae549
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/testing/harness.cpp
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 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 "harness.h"
+
+#ifdef __ANDROID__
+#include <linux/memfd.h>
+#endif
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/loop_control.h>
+#include "snapuserd_logging.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace std::chrono_literals;
+using android::base::unique_fd;
+using android::dm::LoopDevice;
+
+#ifdef __ANDROID__
+// Prefer this on device since it is a real block device, which is more similar
+// to how we use snapuserd.
+class MemoryBackedDevice final : public IBackingDevice {
+ public:
+ bool Init(uint64_t size) {
+ memfd_.reset(memfd_create("snapuserd_test", MFD_ALLOW_SEALING));
+ if (memfd_ < 0) {
+ PLOG(ERROR) << "memfd_create failed";
+ return false;
+ }
+ if (ftruncate(memfd_.get(), size) < 0) {
+ PLOG(ERROR) << "ftruncate failed";
+ return false;
+ }
+ if (fcntl(memfd_.get(), F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+ PLOG(ERROR) << "fcntl seal failed";
+ return false;
+ }
+ dev_ = std::make_unique<LoopDevice>(memfd_, 10s);
+ return dev_->valid();
+ }
+ const std::string& GetPath() override { return dev_->device(); }
+ uint64_t GetSize() override {
+ unique_fd fd(open(GetPath().c_str(), O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ PLOG(ERROR) << "open failed: " << GetPath();
+ return 0;
+ }
+ return get_block_device_size(fd.get());
+ }
+
+ private:
+ unique_fd memfd_;
+ std::unique_ptr<LoopDevice> dev_;
+};
+#endif
+
+class FileBackedDevice final : public IBackingDevice {
+ public:
+ bool Init(uint64_t size) {
+ if (temp_.fd < 0) {
+ return false;
+ }
+ if (ftruncate(temp_.fd, size) < 0) {
+ PLOG(ERROR) << "ftruncate failed: " << temp_.path;
+ return false;
+ }
+ path_ = temp_.path;
+ return true;
+ }
+
+ const std::string& GetPath() override { return path_; }
+ uint64_t GetSize() override {
+ off_t off = lseek(temp_.fd, 0, SEEK_END);
+ if (off < 0) {
+ PLOG(ERROR) << "lseek failed: " << temp_.path;
+ return 0;
+ }
+ return off;
+ }
+
+ private:
+ TemporaryFile temp_;
+ std::string path_;
+};
+
+std::unique_ptr<IBackingDevice> ITestHarness::CreateBackingDevice(uint64_t size) {
+#ifdef __ANDROID__
+ auto dev = std::make_unique<MemoryBackedDevice>();
+#else
+ auto dev = std::make_unique<FileBackedDevice>();
+#endif
+ if (!dev->Init(size)) {
+ return nullptr;
+ }
+ return dev;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/testing/harness.h b/fs_mgr/libsnapshot/snapuserd/testing/harness.h
index c0d528f..ffe9f7b 100644
--- a/fs_mgr/libsnapshot/snapuserd/testing/harness.h
+++ b/fs_mgr/libsnapshot/snapuserd/testing/harness.h
@@ -33,6 +33,14 @@
virtual bool Destroy() = 0;
};
+// Interface for an fd/temp file that is a block device when possible.
+class IBackingDevice {
+ public:
+ virtual ~IBackingDevice() {}
+ virtual const std::string& GetPath() = 0;
+ virtual uint64_t GetSize() = 0;
+};
+
class ITestHarness {
public:
virtual ~ITestHarness() {}
@@ -41,6 +49,7 @@
uint64_t num_sectors) = 0;
virtual IBlockServerFactory* GetBlockServerFactory() = 0;
virtual bool HasUserDevice() = 0;
+ virtual std::unique_ptr<IBackingDevice> CreateBackingDevice(uint64_t size);
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index c407834..7e1b2c4 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -17,7 +17,6 @@
#include <fcntl.h>
#include <linux/fs.h>
-#include <linux/memfd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
@@ -97,7 +96,7 @@
void InitDaemon();
void CreateUserDevice();
- unique_ptr<LoopDevice> base_loop_;
+ unique_ptr<IBackingDevice> base_dev_;
unique_ptr<IUserDevice> dmuser_dev_;
std::string system_device_ctrl_name_;
@@ -116,24 +115,6 @@
std::unique_ptr<ITestHarness> harness_;
};
-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 SnapuserdTest::SetUp() {
harness_ = std::make_unique<DmUserTestHarness>();
}
@@ -189,14 +170,16 @@
}
void SnapuserdTest::CreateBaseDevice() {
- unique_fd rnd_fd;
-
total_base_size_ = (size_ * 5);
- base_fd_ = CreateTempFile("base_device", total_base_size_);
+
+ base_dev_ = harness_->CreateBackingDevice(total_base_size_);
+ ASSERT_NE(base_dev_, nullptr);
+
+ base_fd_.reset(open(base_dev_->GetPath().c_str(), O_RDWR | O_CLOEXEC));
ASSERT_GE(base_fd_, 0);
- rnd_fd.reset(open("/dev/random", O_RDONLY));
- ASSERT_TRUE(rnd_fd > 0);
+ unique_fd rnd_fd(open("/dev/random", O_RDONLY));
+ ASSERT_GE(rnd_fd, 0);
std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
@@ -206,9 +189,6 @@
}
ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
-
- base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
- ASSERT_TRUE(base_loop_->valid());
}
void SnapuserdTest::ReadSnapshotDeviceAndValidate() {
@@ -544,8 +524,8 @@
auto factory = harness_->GetBlockServerFactory();
auto opener = factory->CreateOpener(system_device_ctrl_name_);
auto handler =
- handlers_.AddHandler(system_device_ctrl_name_, cow_system_->path, base_loop_->device(),
- base_loop_->device(), opener, 1, use_iouring, false);
+ handlers_.AddHandler(system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(),
+ base_dev_->GetPath(), opener, 1, use_iouring, false);
ASSERT_NE(handler, nullptr);
ASSERT_NE(handler->snapuserd(), nullptr);
#ifdef __ANDROID__
@@ -566,11 +546,8 @@
}
void SnapuserdTest::CreateUserDevice() {
- 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);
+ auto dev_sz = base_dev_->GetSize();
+ ASSERT_NE(dev_sz, 0);
cow_num_sectors_ = dev_sz >> 9;