blob: d07cdbd9c90d84229564b8a572caaa364e67d9a8 [file] [log] [blame]
// 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