1 /*
2 * Copyright (c) 2022 Travis Geiselbrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8
9 #include <endian.h>
10 #include <lib/bio.h>
11 #include <lib/fs.h>
12 #include <lib/unittest.h>
13 #include <lk/cpp.h>
14 #include <lk/debug.h>
15 #include <lk/err.h>
16 #include <lk/trace.h>
17 #include <malloc.h>
18 #include <string.h>
19
20 #define LOCAL_TRACE 0
21
22 // A set of test cases run against a block device image created from the test script
23 // in the same directory as this. It should contain a set of known directories and
24 // files for the test to work with.
25
26 // pull in a few test files into rodata to test against
27 INCFILE(test_file_hello, test_file_hello_size, LOCAL_DIR "/hello.txt");
28 INCFILE(test_file_license, test_file_license_size, LOCAL_DIR "/LICENSE");
29
30 namespace {
31
32 // TODO: make this much less hard coded
33 const char *test_device_name = "virtio0";
34 #define test_path "/fat"
35
test_fat_mount()36 bool test_fat_mount() {
37 BEGIN_TEST;
38
39 LTRACEF("mounting filesystem on device '%s'\n", test_device_name);
40
41 ASSERT_EQ(NO_ERROR, fs_mount(test_path, "fat", test_device_name));
42 ASSERT_EQ(NO_ERROR, fs_unmount(test_path));
43
44 END_TEST;
45 }
46
test_fat_dir_root()47 bool test_fat_dir_root() {
48 BEGIN_TEST;
49
50 ASSERT_EQ(NO_ERROR, fs_mount(test_path, "fat", test_device_name));
51
52 // clean up by unmounting no matter what happens here
53 auto unmount_cleanup = lk::make_auto_call([]() { fs_unmount(test_path); });
54
55 // open and then close the root dir
56 dirhandle *handle;
57 ASSERT_EQ(NO_ERROR, fs_open_dir(test_path, &handle));
58 ASSERT_NONNULL(handle);
59 ASSERT_EQ(NO_ERROR, fs_close_dir(handle));
60
61 // open it again
62 ASSERT_EQ(NO_ERROR, fs_open_dir(test_path, &handle));
63 ASSERT_NONNULL(handle);
64
65 // close the dir handle if we abort from here on out
66 auto closedir_cleanup = lk::make_auto_call([&]() { fs_close_dir(handle); });
67
68 // read an entry
69 dirent ent;
70 ASSERT_EQ(NO_ERROR, fs_read_dir(handle, &ent));
71 LTRACEF("read entry '%s'\n", ent.name);
72
73 // read all of the entries until we hit an EOD
74 int count = 1;
75 for (;;) {
76 auto err = fs_read_dir(handle, &ent);
77 bool valid = (err == NO_ERROR || err == ERR_NOT_FOUND);
78 ASSERT_TRUE(valid);
79 count++;
80 if (err == ERR_NOT_FOUND) {
81 break;
82 }
83 LTRACEF("read entry '%s'\n", ent.name);
84 }
85 // make sure we saw at least 3 entries
86 ASSERT_LT(2, count);
87
88 closedir_cleanup.cancel();
89 ASSERT_EQ(NO_ERROR, fs_close_dir(handle));
90
91 unmount_cleanup.cancel();
92 ASSERT_EQ(NO_ERROR, fs_unmount(test_path));
93
94 END_TEST;
95 }
96
97 // helper routine for the read file test routine below
test_file_read(const char * path,const unsigned char * test_file_buffer,size_t test_file_size)98 bool test_file_read(const char *path, const unsigned char *test_file_buffer, size_t test_file_size) {
99 BEGIN_TEST;
100
101 // open the file
102 filehandle *handle = nullptr;
103 ASSERT_EQ(NO_ERROR, fs_open_file(path, &handle));
104 auto closefile_cleanup = lk::make_auto_call([&]() { fs_close_file(handle); });
105
106 ASSERT_NONNULL(handle);
107
108 const size_t buflen = test_file_size * 2; // should be somewhat larger than test_file_size
109 char *buf = new char[buflen];
110 auto delete_buffer = lk::make_auto_call([&]() { delete[] buf; });
111
112 // try to read the file in and make sure it reads exactly the target size of bytes
113 ssize_t read_len = fs_read_file(handle, buf, 0, buflen);
114 ASSERT_LT(0, read_len);
115 ASSERT_EQ(test_file_size, (size_t)read_len);
116
117 EXPECT_EQ(0, memcmp(buf, test_file_buffer, read_len));
118 if (memcmp(buf, test_file_buffer, read_len)) {
119 printf("\nfailure in comparison\nexpected:\n");
120 hexdump8(test_file_buffer, read_len);
121 printf("read:\n");
122 hexdump8(buf, read_len);
123 }
124
125 // close the file
126 closefile_cleanup.cancel();
127 ASSERT_EQ(NO_ERROR, fs_close_file(handle));
128
129 END_TEST;
130 }
131
test_fat_read_file()132 bool test_fat_read_file() {
133 BEGIN_TEST;
134
135 ASSERT_EQ(NO_ERROR, fs_mount(test_path, "fat", test_device_name));
136 // clean up by unmounting no matter what happens here
137 auto unmount_cleanup = lk::make_auto_call([]() { fs_unmount(test_path); });
138
139 // read in a few files and validate their contents
140 EXPECT_TRUE(test_file_read(test_path "/hello.txt", test_file_hello, test_file_hello_size));
141 EXPECT_TRUE(test_file_read(test_path "/license", test_file_license, test_file_license_size));
142 EXPECT_TRUE(test_file_read(test_path "/long_filename_hello.txt", test_file_hello, test_file_hello_size));
143 EXPECT_TRUE(test_file_read(test_path "/a_very_long_filename_hello_that_uses_at_least_a_few_entries.txt", test_file_hello, test_file_hello_size));
144 EXPECT_TRUE(test_file_read(test_path "/dir.a/long_filename_hello.txt", test_file_hello, test_file_hello_size));
145
146 // unmount the fs
147 unmount_cleanup.cancel();
148 ASSERT_EQ(NO_ERROR, fs_unmount(test_path));
149
150 END_TEST;
151 }
152
test_fat_multi_open()153 bool test_fat_multi_open() {
154 BEGIN_TEST;
155
156 ASSERT_EQ(NO_ERROR, fs_mount(test_path, "fat", test_device_name));
157 // clean up by unmounting no matter what happens here
158 auto unmount_cleanup = lk::make_auto_call([]() { fs_unmount(test_path); });
159
160 // open a file three times simultaneously
161 {
162 filehandle *handle1 = nullptr;
163 ASSERT_EQ(NO_ERROR, fs_open_file(test_path "/hello.txt", &handle1));
164 auto closefile_cleanup1 = lk::make_auto_call([&]() { fs_close_file(handle1); });
165
166 filehandle *handle2 = nullptr;
167 ASSERT_EQ(NO_ERROR, fs_open_file(test_path "/hello.txt", &handle2));
168 auto closefile_cleanup2 = lk::make_auto_call([&]() { fs_close_file(handle2); });
169
170 filehandle *handle3 = nullptr;
171 ASSERT_EQ(NO_ERROR, fs_open_file(test_path "/hello.txt", &handle3));
172
173 // close the files in reverse order
174 closefile_cleanup1.cancel();
175 ASSERT_EQ(NO_ERROR, fs_close_file(handle1));
176 closefile_cleanup2.cancel();
177 ASSERT_EQ(NO_ERROR, fs_close_file(handle2));
178 ASSERT_EQ(NO_ERROR, fs_close_file(handle3));
179 }
180
181 // open a dir three times simultaneously
182 {
183 dirhandle *handle1 = nullptr;
184 ASSERT_EQ(NO_ERROR, fs_open_dir(test_path "/dir.a", &handle1));
185 auto closedir_cleanup1 = lk::make_auto_call([&]() { fs_close_dir(handle1); });
186
187 dirhandle *handle2 = nullptr;
188 ASSERT_EQ(NO_ERROR, fs_open_dir(test_path "/dir.a", &handle2));
189 auto closedir_cleanup2 = lk::make_auto_call([&]() { fs_close_dir(handle2); });
190
191 dirhandle *handle3 = nullptr;
192 ASSERT_EQ(NO_ERROR, fs_open_dir(test_path "/dir.a", &handle3));
193
194 // close the dirs in reverse order
195 closedir_cleanup1.cancel();
196 ASSERT_EQ(NO_ERROR, fs_close_dir(handle1));
197 closedir_cleanup2.cancel();
198 ASSERT_EQ(NO_ERROR, fs_close_dir(handle2));
199 ASSERT_EQ(NO_ERROR, fs_close_dir(handle3));
200 }
201
202 // unmount the fs
203 unmount_cleanup.cancel();
204 ASSERT_EQ(NO_ERROR, fs_unmount(test_path));
205
206 END_TEST;
207 }
208
209 BEGIN_TEST_CASE(fat)
210 RUN_TEST(test_fat_mount)
211 RUN_TEST(test_fat_dir_root)
212 RUN_TEST(test_fat_read_file)
213 RUN_TEST(test_fat_multi_open)
214 END_TEST_CASE(fat)
215
216 } // namespace
217
218