Rework postinstall unittests to pass on Android.
Postinstall unittests were creating and mounting an image on each test
run. This patch adds several test scripts to one of the pre-generated
images and uses that image during postinstall testing instead.
To workaround problems with mount/umount of loop devices on Android,
this patch rewrites the `losetup` logic to make the appropriate
syscalls and create the loop device with mknod if it doesn't exists.
The tests require some extra SELinux policies to run in enforcing mode.
Bug: 26955860
TEST=Ran all Postinstall unittests.
Change-Id: I47a56b80b97596bc65ffe30cbc8118f05faff0ae
diff --git a/common/test_utils.cc b/common/test_utils.cc
index f4d203c..0e7a8ef 100644
--- a/common/test_utils.cc
+++ b/common/test_utils.cc
@@ -18,8 +18,12 @@
#include <dirent.h>
#include <errno.h>
+#include <fcntl.h>
+#include <linux/loop.h>
+#include <linux/major.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/xattr.h>
@@ -137,27 +141,76 @@
return utils::WriteFile(path.c_str(), data.data(), data.size());
}
-// Binds provided |filename| to an unused loopback device, whose name is written
-// to the string pointed to by |lo_dev_name_p|. Returns true on success, false
-// otherwise (along with corresponding test failures), in which case the content
-// of |lo_dev_name_p| is unknown.
-bool BindToUnusedLoopDevice(const string& filename, string* lo_dev_name_p) {
- CHECK(lo_dev_name_p);
+bool BindToUnusedLoopDevice(const string& filename,
+ bool writable,
+ string* out_lo_dev_name) {
+ CHECK(out_lo_dev_name);
+ // Get the next available loop-device.
+ int control_fd =
+ HANDLE_EINTR(open("/dev/loop-control", O_RDWR | O_LARGEFILE));
+ TEST_AND_RETURN_FALSE_ERRNO(control_fd >= 0);
+ int loop_number = ioctl(control_fd, LOOP_CTL_GET_FREE);
+ IGNORE_EINTR(close(control_fd));
+ *out_lo_dev_name = StringPrintf("/dev/loop%d", loop_number);
- // Bind to an unused loopback device, sanity check the device name.
- lo_dev_name_p->clear();
- if (!(utils::ReadPipe("losetup --show -f " + filename, lo_dev_name_p) &&
- base::StartsWith(*lo_dev_name_p, "/dev/loop",
- base::CompareCase::SENSITIVE))) {
- ADD_FAILURE();
+ // Double check that the loop exists and is free.
+ int loop_device_fd =
+ HANDLE_EINTR(open(out_lo_dev_name->c_str(), O_RDWR | O_LARGEFILE));
+ if (loop_device_fd == -1 && errno == ENOENT) {
+ // Workaround the case when the loop device doesn't exist.
+ TEST_AND_RETURN_FALSE_ERRNO(mknod(out_lo_dev_name->c_str(),
+ S_IFBLK | 0660,
+ makedev(LOOP_MAJOR, loop_number)) == 0);
+ loop_device_fd =
+ HANDLE_EINTR(open(out_lo_dev_name->c_str(), O_RDWR | O_LARGEFILE));
+ }
+ TEST_AND_RETURN_FALSE_ERRNO(loop_device_fd != -1);
+ ScopedFdCloser loop_device_fd_closer(&loop_device_fd);
+
+ struct loop_info64 device_info;
+ if (ioctl(loop_device_fd, LOOP_GET_STATUS64, &device_info) != -1 ||
+ errno != ENXIO) {
+ PLOG(ERROR) << "Loop device " << out_lo_dev_name->c_str()
+ << " already in use";
return false;
}
- // Strip anything from the first newline char.
- size_t newline_pos = lo_dev_name_p->find('\n');
- if (newline_pos != string::npos)
- lo_dev_name_p->erase(newline_pos);
+ // Open our data file and assign it to the loop device.
+ int data_fd = open(filename.c_str(),
+ (writable ? O_RDWR : O_RDONLY) | O_LARGEFILE | O_CLOEXEC);
+ TEST_AND_RETURN_FALSE_ERRNO(data_fd >= 0);
+ ScopedFdCloser data_fd_closer(&data_fd);
+ TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_SET_FD, data_fd) == 0);
+ memset(&device_info, 0, sizeof(device_info));
+ device_info.lo_offset = 0;
+ device_info.lo_sizelimit = 0; // 0 means whole file.
+ device_info.lo_flags = (writable ? 0 : LO_FLAGS_READ_ONLY);
+ device_info.lo_number = loop_number;
+ strncpy(reinterpret_cast<char*>(device_info.lo_file_name),
+ base::FilePath(filename).BaseName().value().c_str(),
+ LO_NAME_SIZE - 1);
+ device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0';
+ TEST_AND_RETURN_FALSE_ERRNO(
+ ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0);
+ return true;
+}
+
+bool UnbindLoopDevice(const string& lo_dev_name) {
+ int loop_device_fd =
+ HANDLE_EINTR(open(lo_dev_name.c_str(), O_RDWR | O_LARGEFILE));
+ if (loop_device_fd == -1 && errno == ENOENT)
+ return true;
+ TEST_AND_RETURN_FALSE_ERRNO(loop_device_fd != -1);
+ ScopedFdCloser loop_device_fd_closer(&loop_device_fd);
+
+ struct loop_info64 device_info;
+ // Check if the device is bound before trying to unbind it.
+ int get_stat_err = ioctl(loop_device_fd, LOOP_GET_STATUS64, &device_info);
+ if (get_stat_err == -1 && errno == ENXIO)
+ return true;
+
+ TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_CLR_FD) == 0);
return true;
}
@@ -258,7 +311,8 @@
dir_remover_.reset(new ScopedDirRemover(*mnt_path));
string loop_dev;
- loop_binder_.reset(new ScopedLoopbackDeviceBinder(file_path, &loop_dev));
+ loop_binder_.reset(
+ new ScopedLoopbackDeviceBinder(file_path, true, &loop_dev));
EXPECT_TRUE(utils::MountFilesystem(loop_dev, *mnt_path, flags, "", ""));
unmounter_.reset(new ScopedFilesystemUnmounter(*mnt_path));