aboutsummaryrefslogtreecommitdiff
path: root/kernel/src/filesystem/ext2/filesystem.tests.cpp
blob: b13ebf35c37ff67e868342a587f264a5c6174fb9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#include "kernel/filesystem/ext2/filesystem.hpp"

#include "kernel/devices/storage/management.hpp"
#include "kernel/filesystem/ext2/inode.hpp"
#include "kernel/filesystem/filesystem.hpp"
#include "kernel/test_support/devices/block_device.hpp"
#include "kernel/test_support/filesystem/ext2.hpp"
#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"

#include <kstd/memory>
#include <kstd/vector>

#include <catch2/catch_test_macros.hpp>

#include <array>
#include <cstdint>
#include <filesystem>

SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
                "Ext2 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]")
{
  auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";

  GIVEN("a mounted ext2 filesystem from a real image")
  {
    REQUIRE(std::filesystem::exists(image_path));
    REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));

    auto boot_device = kernel::devices::storage::management::get().determine_boot_device();
    REQUIRE(boot_device != nullptr);

    auto fs = kernel::filesystem::ext2::filesystem{};
    REQUIRE(fs.mount(boot_device) == kernel::filesystem::filesystem::operation_result::success);

    THEN("the root inode is available and is a directory")
    {
      REQUIRE(fs.root_inode() != nullptr);
      REQUIRE(fs.root_inode()->is_directory());
    }

    THEN("lookup resolves known entries from the image")
    {
      auto information = fs.lookup(fs.root_inode(), "information");
      REQUIRE(information != nullptr);
      REQUIRE(information->is_directory());

      auto info_1 = fs.lookup(information, "info_1.txt");
      REQUIRE(info_1 != nullptr);
      REQUIRE(info_1->is_regular());
    }

    THEN("lookup returns null for invalid inputs")
    {
      REQUIRE(fs.lookup(nullptr, "information") == nullptr);

      auto information = fs.lookup(fs.root_inode(), "information");
      REQUIRE(information != nullptr);
      auto info_1 = fs.lookup(information, "info_1.txt");
      REQUIRE(info_1 != nullptr);

      REQUIRE(fs.lookup(info_1, "anything") == nullptr);
      REQUIRE(fs.lookup(fs.root_inode(), "does_not_exist") == nullptr);
    }
  }
}

SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]")
{
  auto const block_size = 1024;
  GIVEN("a block device that does not contain an ext2 superblock")
  {
    auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 2 * block_size);
    REQUIRE(device != nullptr);

    auto fs = kernel::filesystem::ext2::filesystem{};

    THEN("mount fails with invalid_magic_number")
    {
      REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::invalid_magic_number);
    }
  }
}

SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]")
{
  auto const block_size = 1024;

  GIVEN("a minimally valid ext2 layout with configured indirect block tables")
  {
    auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 128 * block_size);
    REQUIRE(device != nullptr);

    kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device);

    auto fs = kernel::filesystem::ext2::filesystem{};
    REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success);

    auto inode_data = kernel::filesystem::ext2::inode_data{};
    inode_data.block[0] = 7;

    inode_data.block[12] = 30;
    kernel::tests::filesystem::ext2::write_u32(*device, 30 * block_size, 31);

    inode_data.block[13] = 40;
    kernel::tests::filesystem::ext2::write_u32(*device, 40 * block_size, 41);
    kernel::tests::filesystem::ext2::write_u32(*device, 41 * block_size, 42);

    inode_data.block[14] = 50;
    kernel::tests::filesystem::ext2::write_u32(*device, 50 * block_size, 51);
    kernel::tests::filesystem::ext2::write_u32(*device, 51 * block_size, 52);
    kernel::tests::filesystem::ext2::write_u32(*device, 52 * block_size, 53);

    auto const numbers_per_block = static_cast<uint32_t>(block_size / sizeof(uint32_t));
    auto const singly_start = static_cast<uint32_t>(kernel::filesystem::ext2::constants::direct_block_count);
    auto const doubly_start = singly_start + numbers_per_block;
    auto const triply_start = doubly_start + numbers_per_block * numbers_per_block;

    THEN("mapping resolves direct, singly, doubly and triply indirect indexes")
    {
      REQUIRE(fs.map_inode_block_index_to_global_block_number(0, inode_data) == 7);
      REQUIRE(fs.map_inode_block_index_to_global_block_number(singly_start, inode_data) == 31);
      REQUIRE(fs.map_inode_block_index_to_global_block_number(doubly_start, inode_data) == 42);
      REQUIRE(fs.map_inode_block_index_to_global_block_number(triply_start, inode_data) == 53);
    }

    THEN("mapping returns zero for out-of-range indexes")
    {
      auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block;
      REQUIRE(fs.map_inode_block_index_to_global_block_number(beyond_triply, inode_data) == 0);
    }
  }
}