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