| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <ftw.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| |
| #include <cerrno> |
| #include <fstream> |
| #include <iostream> |
| |
| #include <gtest/gtest.h> |
| #include <linux/loop.h> |
| |
| #include "src/starnix/tests/syscalls/cpp/syscall_matchers.h" |
| #include "src/starnix/tests/syscalls/cpp/test_helper.h" |
| |
| #ifndef LOOP_CONFIGURE |
| struct loop_config { |
| uint32_t fd; |
| uint32_t block_size; |
| struct loop_info64 info; |
| uint64_t __reserved[8]; |
| }; |
| |
| #define LOOP_CONFIGURE 0x4C0A |
| #endif // LOOP_CONFIGURE |
| |
| namespace { |
| |
| bool skip_loop_tests = false; |
| |
| class LoopTest : public ::testing::Test { |
| public: |
| static void SetUpTestSuite() { |
| // TODO(https://fxbug.dev/317285180) don't skip on baseline |
| int fd = open("/dev/loop-control", O_RDWR, 0777); |
| if (fd == -1 && (errno == EACCES || errno == ENOENT)) { |
| // GTest does not support GTEST_SKIP() from a suite setup, so record that we want to skip |
| // every test here and skip in SetUp(). |
| skip_loop_tests = true; |
| return; |
| } |
| ASSERT_TRUE(fd >= 0) << "open(\"/dev/loop-control\") failed: " << strerror(errno) << "(" |
| << errno << ")"; |
| } |
| |
| void SetUp() override { |
| if (skip_loop_tests) { |
| GTEST_SKIP() << "Permission denied for /dev/loop-control, skipping suite."; |
| } |
| loop_control_ = fbl::unique_fd(open("/dev/loop-control", O_RDWR)); |
| ASSERT_TRUE(loop_control_.is_valid()); |
| } |
| |
| int GetFreeLoopDeviceNumber() { |
| int free_loop_device_num(ioctl(loop_control_.get(), LOOP_CTL_GET_FREE, nullptr)); |
| EXPECT_TRUE(free_loop_device_num >= 0); |
| return free_loop_device_num; |
| } |
| |
| int RemoveLoopDevice(int loop_device_num) { |
| int removed_loop_device_num(ioctl(loop_control_.get(), LOOP_CTL_REMOVE, loop_device_num)); |
| EXPECT_TRUE(removed_loop_device_num >= 0); |
| return removed_loop_device_num; |
| } |
| |
| private: |
| fbl::unique_fd loop_control_; |
| }; |
| |
| #define ASSERT_SUCCESS(call) ASSERT_THAT((call), SyscallSucceeds()) |
| |
| TEST_F(LoopTest, ReopeningDevicePreservesOffset) { |
| std::string loop_device_path = "/dev/loop" + std::to_string(GetFreeLoopDeviceNumber()); |
| fbl::unique_fd free_loop_device(open(loop_device_path.c_str(), O_RDONLY, 0644)); |
| ASSERT_TRUE(free_loop_device.is_valid()); |
| |
| fbl::unique_fd backing_file(open("data/tests/deps/hello_world.txt", O_RDONLY, 0644)); |
| ASSERT_TRUE(backing_file.is_valid()); |
| |
| // Configure an offset that we'll check for after re-opening the device. |
| loop_config config = {.fd = static_cast<__u32>(backing_file.get()), |
| .block_size = 4096, |
| .info = {.lo_offset = 4096}}; |
| ASSERT_SUCCESS(ioctl(free_loop_device.get(), LOOP_CONFIGURE, &config)); |
| |
| loop_info64 first_observed_info; |
| ASSERT_SUCCESS(ioctl(free_loop_device.get(), LOOP_GET_STATUS64, &first_observed_info)); |
| |
| // Close the loop device fd and reopen it, confirming that the offset and other configuration are |
| // the same and preserved even when there are no open files to the device. |
| free_loop_device = fbl::unique_fd(open(loop_device_path.c_str(), O_RDONLY, 0644)); |
| loop_info64 second_observed_info; |
| ASSERT_SUCCESS(ioctl(free_loop_device.get(), LOOP_GET_STATUS64, &second_observed_info)); |
| EXPECT_EQ(first_observed_info.lo_offset, second_observed_info.lo_offset); |
| } |
| |
| TEST_F(LoopTest, RemoveLoopDeviceFromKernelDeviceRegistry) { |
| int free_loop_device_num = GetFreeLoopDeviceNumber(); |
| int removed_loop_device_num = RemoveLoopDevice(free_loop_device_num); |
| EXPECT_EQ(removed_loop_device_num, free_loop_device_num); |
| std::string devfs_path = "/dev/"; |
| DIR* dir = opendir(devfs_path.c_str()); |
| ASSERT_TRUE(dir); |
| while (struct dirent* entry = readdir(dir)) { |
| std::string name = entry->d_name; |
| if (name.find("loop") != std::string::npos) { |
| std::string device_path = "/dev/" + name; |
| int device_fd = open(device_path.c_str(), O_RDONLY); |
| ASSERT_GT(device_fd, 0) << "device path is: " << device_path << strerror(errno); |
| } |
| } |
| closedir(dir); |
| } |
| |
| TEST_F(LoopTest, BackingFile) { |
| auto loop_nr = GetFreeLoopDeviceNumber(); |
| std::string loop_device_path = "/dev/loop" + std::to_string(loop_nr); |
| fbl::unique_fd free_loop_device(open(loop_device_path.c_str(), O_RDONLY, 0644)); |
| ASSERT_TRUE(free_loop_device.is_valid()); |
| |
| fbl::unique_fd backing_file(open("data/tests/deps/hello_world.txt", O_RDONLY, 0644)); |
| ASSERT_TRUE(backing_file.is_valid()); |
| |
| loop_config config = { |
| .fd = static_cast<__u32>(backing_file.get()), .block_size = 4096, .info = {.lo_offset = 0}}; |
| ASSERT_SUCCESS(ioctl(free_loop_device.get(), LOOP_CONFIGURE, &config)); |
| |
| loop_info64 first_observed_info; |
| ASSERT_SUCCESS(ioctl(free_loop_device.get(), LOOP_GET_STATUS64, &first_observed_info)); |
| |
| std::string sys_backing_file_path = |
| "/sys/block/loop" + std::to_string(loop_nr) + "/loop/backing_file"; |
| std::ifstream in(sys_backing_file_path); |
| ASSERT_TRUE(in.is_open()); |
| std::stringstream buffer; |
| buffer << in.rdbuf(); |
| // The actual path is prefixed with container namespace, which includes a dynamically |
| // generated component identifier. Since we don't want a change-detector test, just match |
| // the suffix. |
| ASSERT_TRUE(buffer.str().ends_with("/data/tests/deps/hello_world.txt\n")); |
| } |
| |
| } // namespace |