blob: d2de09026c7a1de21279bd391e71602923a2a661 [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001/*
2 * Copyright (c) 2015 Gurjant Kalsi <me@gurjantkalsi.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24#if LK_DEBUGLEVEL > 1
25
26#include <string.h>
27#include <err.h>
28#include <stdlib.h>
29
30#include <lib/bio.h>
31#include <lib/console.h>
32#include <lib/fs/spifs.h>
33
34#define FS_NAME "spifs"
35#define MNT_PATH "/s"
36#define TEST_FILE_PATH "/s/test"
37#define TEST_PATH_MAX_SIZE 16
38
39typedef bool(*test_func)(const char *);
40
41typedef struct {
42 test_func func;
43 const char* name;
44 uint32_t toc_pages;
45} test;
46
47static bool test_empty_after_format(const char *);
48static bool test_double_create_file(const char *);
49static bool test_write_read_normal(const char *);
50static bool test_write_past_eof(const char *);
51static bool test_full_toc(const char *);
52static bool test_full_fs(const char *);
53static bool test_write_past_end_of_capacity(const char *);
54static bool test_rm_reclaim(const char *);
55static bool test_corrupt_toc(const char *);
56static bool test_write_with_offset(const char *);
57static bool test_read_write_big(const char *);
58static bool test_rm_active_dirent(const char *);
59
60static test tests[] = {
61 {&test_empty_after_format, "Test no files in ToC after format.", 1},
62 {&test_write_read_normal, "Test the normal read/write file paths.", 1},
63 {&test_double_create_file, "Test file cannot be created if it already exists.", 1},
64 {&test_write_past_eof, "Test that file can grow up to capacity.", 1},
65 {&test_full_toc, "Test that files cannot be created once the ToC is full.", 2},
66 {&test_full_fs, "Test that files cannot be created once the device is full.", 1},
67 {&test_rm_reclaim, "Test that files can be deleted and that used space is reclaimed.", 1},
68 {&test_write_past_end_of_capacity, "Test that we cannot write past the capacity of a file.", 1},
69 {&test_corrupt_toc, "Test that FS can be mounted with one corrupt ToC.", 1},
70 {&test_write_with_offset, "Test that files can be written to at an offset.", 1},
71 {&test_read_write_big, "Test that an unaligned ~10kb buffer can be written and read.", 1},
72 {&test_rm_active_dirent, "Test that we can remove a file with an open dirent.", 1},
73};
74
75bool test_setup(const char *dev_name, uint32_t toc_pages)
76{
77 spifs_format_args_t args = {
78 .toc_pages = toc_pages,
79 };
80
81 status_t res = fs_format_device(FS_NAME, dev_name, (void*)&args);
82 if (res != NO_ERROR) {
83 printf("spifs_format failed dev = %s, toc_pages = %u, retcode = %d\n",
84 dev_name, toc_pages, res);
85 return false;
86 }
87
88 res = fs_mount(MNT_PATH, FS_NAME, dev_name);
89 if (res != NO_ERROR) {
90 printf("fs_mount failed path = %s, fs name = %s, dev name = %s,"
91 " retcode = %d\n", MNT_PATH, FS_NAME, dev_name, res);
92 return false;
93 }
94
95 return true;
96}
97
98bool test_teardown(void)
99{
100 if (fs_unmount(MNT_PATH) != NO_ERROR) {
101 printf("Unmount failed\n");
102 return false;
103 }
104
105 return true;;
106}
107
108static bool test_empty_after_format(const char *dev_name)
109{
110 dirhandle *dhandle;
111 status_t err = fs_open_dir(MNT_PATH, &dhandle);
112 if (err != NO_ERROR) {
113 return false;
114 }
115
116 struct dirent ent;
117 if (fs_read_dir(dhandle, &ent) >= 0) {
118 fs_close_dir(dhandle);
119 return false;
120 }
121
122 fs_close_dir(dhandle);
123 return true;
124}
125
126static bool test_double_create_file(const char *dev_name)
127{
128 status_t status;
129
130 struct dirent *ent = malloc(sizeof(*ent));
131 size_t num_files = 0;
132
133 filehandle *handle;
134 status = fs_create_file(TEST_FILE_PATH, &handle, 10);
135 if (status != NO_ERROR) {
136 goto err;
137 }
138 fs_close_file(handle);
139
140 filehandle *duphandle;
141 status = fs_create_file(TEST_FILE_PATH, &duphandle, 20);
142 if (status != ERR_ALREADY_EXISTS) {
143 goto err;
144 }
145
146 dirhandle *dhandle;
147 status = fs_open_dir(MNT_PATH, &dhandle);
148 if (status != NO_ERROR) {
149 goto err;
150 }
151
152 while ((status = fs_read_dir(dhandle, ent)) >= 0) {
153 num_files++;
154 }
155
156 status = NO_ERROR;
157
158 fs_close_dir(dhandle);
159
160
161err:
162 free(ent);
163
164 return status == NO_ERROR ? num_files == 1 : false;
165}
166
167static bool test_write_read_normal(const char *dev_name)
168{
169 char test_message[] = "spifs test";
170 char test_buf[sizeof(test_message)];
171
172 bdev_t *dev = bio_open(dev_name);
173 if (!dev) {
174 return false;
175 }
176 uint8_t erase_byte = dev->erase_byte;
177 bio_close(dev);
178
179 filehandle *handle;
180 status_t status =
181 fs_create_file(TEST_FILE_PATH, &handle, sizeof(test_message));
182 if (status != NO_ERROR) {
183 return false;
184 }
185
186 ssize_t bytes;
187
188 // New files should be initialized to 'erase_byte'
189 bytes = fs_read_file(handle, test_buf, 0, sizeof(test_buf));
190 if (bytes != sizeof(test_buf)) {
191 return false;
192 }
193
194 for (size_t i = 0; i < sizeof(test_buf); i++) {
195 if (test_buf[i] != erase_byte) {
196 return false;
197 }
198 }
199
200 bytes = fs_write_file(handle, test_message, 0, sizeof(test_message));
201 if (bytes != sizeof(test_message)) {
202 return false;
203 }
204
205 bytes = fs_read_file(handle, test_buf, 0, sizeof(test_buf));
206 if (bytes != sizeof(test_buf)) {
207 return false;
208 }
209
210 status = fs_close_file(handle);
211 if (status != NO_ERROR) {
212 return false;
213 }
214
215 return strncmp(test_message, test_buf, sizeof(test_message)) == 0;
216}
217
218static bool test_write_past_eof(const char *dev_name)
219{
220 char test_message[] = "spifs test";
221
222 // Create a 0 length file.
223 filehandle *handle;
224 status_t status =
225 fs_create_file(TEST_FILE_PATH, &handle, 0);
226 if (status != NO_ERROR) {
227 return false;
228 }
229
230 ssize_t bytes = fs_write_file(handle, test_message, 0, sizeof(test_message));
231 if (bytes != sizeof(test_message)) {
232 return false;
233 }
234
235 // Make sure the file grows.
236 struct file_stat stat;
237 fs_stat_file(handle, &stat);
238 if (stat.is_dir != false && stat.size != sizeof(test_message)) {
239 return false;
240 }
241
242 fs_close_file(handle);
243
244 return true;
245}
246
247static bool test_full_toc(const char *dev_name)
248{
249 struct fs_stat stat;
250
251 fs_stat_fs(MNT_PATH, &stat);
252
253 char test_file_name[TEST_PATH_MAX_SIZE];
254
255 filehandle *handle;
256 for (size_t i = 0; i < stat.free_inodes; i++) {
257 memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
258
259 char filenum[] = "000";
260 filenum[0] += i / 100;
261 filenum[1] += (i / 10) % 10;
262 filenum[2] += i % 10;
263
264 strlcat(test_file_name, MNT_PATH, sizeof(test_file_name));
265 strlcat(test_file_name, "/", sizeof(test_file_name));
266 strlcat(test_file_name, filenum, sizeof(test_file_name));
267
268 status_t status =
269 fs_create_file(test_file_name, &handle, 1);
270
271 if (status != NO_ERROR) {
272 return false;
273 }
274
275 fs_close_file(handle);
276 }
277
278 // There shouldn't be enough space for this file since we've exhausted all
279 // the inodes.
280
281 status_t status = fs_create_file(TEST_FILE_PATH, &handle, 1);
282 if (status != ERR_TOO_BIG) {
283 return false;
284 }
285
286 return true;
287}
288
289static bool test_rm_reclaim(const char *dev_name)
290{
291 // Create some number of files that's a power of 2;
292 size_t n_files = 4;
293
294 struct fs_stat stat;
295
296 fs_stat_fs(MNT_PATH, &stat);
297
298 size_t file_size = stat.free_space / (n_files + 1);
299
300 char test_file_name[TEST_PATH_MAX_SIZE];
301
302 filehandle *handle;
303 for (size_t i = 0; i < n_files; i++) {
304 memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
305
306 char filenum[] = "000";
307 filenum[0] += i / 100;
308 filenum[1] += (i / 10) % 10;
309 filenum[2] += i % 10;
310
311 strcat(test_file_name, MNT_PATH);
312 strcat(test_file_name, filenum);
313
314 status_t status =
315 fs_create_file(test_file_name, &handle, file_size);
316
317 if (status != NO_ERROR) {
318 return false;
319 }
320
321 fs_close_file(handle);
322 }
323
324 // Try to create a new Big file.
325 char filename[] = "BIGFILE";
326 memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
327 strcat(test_file_name, MNT_PATH);
328 strcat(test_file_name, filename);
329
330 status_t status;
331
332 // This should fail because there's no more space.
333 fs_stat_fs(MNT_PATH, &stat);
334 status = fs_create_file(test_file_name, &handle, stat.free_space + 1);
335 if (status != ERR_TOO_BIG) {
336 return false;
337 }
338
339 // Delete an existing file to make space for the new file.
340 char existing_filename[] = "001";
341 memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
342 strcat(test_file_name, MNT_PATH);
343 strcat(test_file_name, existing_filename);
344
345 status = fs_remove_file(test_file_name);
346 if (status != NO_ERROR) {
347 return false;
348 }
349
350
351 // Now this should go through because we've reclaimed the space.
352 status = fs_create_file(test_file_name, &handle, stat.free_space + 1);
353 if (status != NO_ERROR) {
354 return false;
355 }
356
357 fs_close_file(handle);
358 return true;
359}
360
361static bool test_full_fs(const char *dev_name)
362{
363 struct fs_stat stat;
364
365 fs_stat_fs(MNT_PATH, &stat);
366
367 char second_file_path[TEST_PATH_MAX_SIZE];
368 memset(second_file_path, 0, TEST_PATH_MAX_SIZE);
369 strcpy(second_file_path, MNT_PATH);
370 strcat(second_file_path, "/fail");
371
372 filehandle *handle;
373 status_t status = fs_create_file(TEST_FILE_PATH, &handle, stat.free_space);
374 if (status != NO_ERROR) {
375 return false;
376 }
377
378 fs_close_file(handle);
379
380 // There shouldn't be enough space for this file since we've used all the
381 // space.
382 status = fs_create_file(second_file_path, &handle, 1);
383 if (status != ERR_TOO_BIG) {
384 return false;
385 }
386
387 return true;
388}
389
390static bool test_write_past_end_of_capacity(const char *dev_name)
391{
392 filehandle *handle;
393 status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
394 if (status != NO_ERROR) {
395 return false;
396 }
397
398 struct file_stat stat;
399 status = fs_stat_file(handle, &stat);
400 if (status != NO_ERROR) {
401 goto finish;
402 }
403
404 // We shouldn't be able to write past the capacity of a file.
405 char buf[1];
406 status = fs_write_file(handle, buf, stat.capacity, 1);
407 if (status == ERR_OUT_OF_RANGE) {
408 status = NO_ERROR;
409 } else {
410 status = ERR_IO;
411 }
412
413finish:
414 fs_close_file(handle);
415 return status == NO_ERROR;
416}
417
418static bool test_corrupt_toc(const char *dev_name)
419{
420 // Create a zero byte file.
421 filehandle *handle;
422 status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
423 if (status != NO_ERROR) {
424 return false;
425 }
426
427 // Grow the file to one byte. This should trigger a ToC flush. Now the
428 // ToC record for this file should exist in both ToCs. Therefore corrupting
429 // either of the ToCs will still yield this file readable.
430 char buf[1] = { 'a' };
431 status = fs_write_file(handle, buf, 0, 1);
432 if (status != 1) {
433 return false;
434 }
435
436 status = fs_close_file(handle);
437 if (status != NO_ERROR) {
438 return false;
439 }
440
441 status = fs_unmount(MNT_PATH);
442 if (status != NO_ERROR) {
443 return false;
444 }
445
446 // Now we're going to manually corrupt one of the ToCs
447 bdev_t *dev = bio_open(dev_name);
448 if (!dev) {
449 return false;
450 }
451
452 // Directly write 0s to the block that contains the front-ToC.
453 size_t block_size = dev->block_size;
454 uint8_t *block_buf = memalign(CACHE_LINE, block_size);
455 if (!block_buf) {
456 return false;
457 }
458 memset(block_buf, 0, block_size);
459
460 ssize_t bytes = bio_write_block(dev, block_buf, 0, 1);
461
462 free(block_buf);
463
464 bio_close(dev);
465
466 if (bytes != (ssize_t)block_size) {
467 return false;
468 }
469
470 // Mount the FS again and make sure that the file we created is still there.
471 status = fs_mount(MNT_PATH, FS_NAME, dev_name);
472 if (status != NO_ERROR) {
473 return false;
474 }
475
476 status = fs_open_file(TEST_FILE_PATH, &handle);
477 if (status != NO_ERROR) {
478 return false;
479 }
480
481 struct file_stat stat;
482 status = fs_stat_file(handle, &stat);
483 if (status != NO_ERROR) {
484 return false;
485 }
486
487 status = fs_close_file(handle);
488 if (status != NO_ERROR) {
489 return false;
490 }
491
492 return true;
493}
494
495static bool test_write_with_offset(const char *dev_name)
496{
497 size_t repeats = 3;
498 char test_message[] = "test";
499 size_t msg_len = strnlen(test_message, sizeof(test_message));
500 char test_buf[msg_len * repeats];
501
502 filehandle *handle;
503 status_t status = fs_create_file(TEST_FILE_PATH, &handle, msg_len);
504 if (status != NO_ERROR) {
505 return false;
506 }
507
508 ssize_t bytes;
509 for (size_t pos = 0; pos < repeats; pos++) {
510 bytes = fs_write_file(handle, test_message, pos * msg_len, msg_len);
511 if ((size_t)bytes != msg_len) {
512 return false;
513 }
514 }
515
516 bytes = fs_read_file(handle, test_buf, 0, msg_len * repeats);
517 if ((size_t)bytes != msg_len * repeats) {
518 return false;
519 }
520
521 status = fs_close_file(handle);
522 if (status != NO_ERROR) {
523 return false;
524 }
525
526 bool success = true;
527 for (size_t i = 0; i < repeats; i++) {
528 success &= (memcmp(test_message,
529 test_buf + i * msg_len,
530 msg_len) == 0);
531 }
532 return success;
533}
534
535static bool test_read_write_big(const char *dev_name)
536{
537 bool success = true;
538
539 size_t buflen = 10013;
540
541 uint8_t *rbuf = malloc(buflen);
542 if (!rbuf) {
543 return false;
544 }
545
546 uint8_t *wbuf = malloc(buflen);
547 if (!wbuf) {
548 free(rbuf);
549 return false;
550 }
551
552 for (size_t i = 0; i < buflen; i++) {
553 wbuf[i] = rand() % sizeof(uint8_t);
554 }
555
556 filehandle *handle;
557 status_t status = fs_create_file(TEST_FILE_PATH, &handle, buflen);
558 if (status != NO_ERROR) {
559 success = false;
560 goto err;
561 }
562
563 ssize_t bytes = fs_write_file(handle, wbuf, 0, buflen);
564 if ((size_t)bytes != buflen) {
565 success = false;
566 goto err;
567 }
568
569 bytes = fs_read_file(handle, rbuf, 0, buflen);
570 if ((size_t)bytes != buflen) {
571 success = false;
572 goto err;
573 }
574
575 for (size_t i = 0; i < buflen; i++) {
576 if (wbuf[i] != rbuf[i]) {
577 success = false;
578 break;
579 }
580 }
581
582err:
583 success &= fs_close_file(handle) == NO_ERROR;
584
585 free(rbuf);
586 free(wbuf);
587 return success;
588}
589
590static bool test_rm_active_dirent(const char *dev_name)
591{
592 filehandle *handle;
593 status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
594 if (status != NO_ERROR) {
595 return false;
596 }
597
598 status = fs_close_file(handle);
599 if (status != NO_ERROR) {
600 return false;
601 }
602
603 dirhandle *dhandle;
604 status = fs_open_dir(MNT_PATH, &dhandle);
605 if (status != NO_ERROR) {
606 return false;
607 }
608
609 // Dir handle should now be pointing to the only file in our FS.
610 status = fs_remove_file(TEST_FILE_PATH);
611 if (status != NO_ERROR) {
612 fs_close_dir(dhandle);
613 return false;
614 }
615
616 bool success = true;
617 struct dirent *ent = malloc(sizeof(*ent));
618 if (fs_read_dir(dhandle, ent) >= 0) {
619 success = false;
620 }
621
622 success &= fs_close_dir(dhandle) == NO_ERROR;
623 free(ent);
624
625 return success;
626}
627
628static int cmd_spifs(int argc, const cmd_args *argv)
629{
630 if (argc < 3) {
631notenoughargs:
632 printf("not enough arguments:\n");
633usage:
634 printf("%s test <device>\n", argv[0].str);
635 return -1;
636 }
637
638 if (strcmp(argv[1].str, "test")) {
639 goto usage;
640 }
641
642 // Make sure this block device is legit.
643 bdev_t *dev = bio_open(argv[2].str);
644 if (!dev) {
645 printf("error: could not open block device %s\n", argv[2].str);
646 return -1;
647 }
648 bio_close(dev);
649
650 size_t passed = 0;
651 size_t attempted = 0;
652 for (size_t i = 0; i < countof(tests); i++) {
653 ++attempted;
654 if (!test_setup(argv[2].str, tests[i].toc_pages)) {
655 printf("Test Setup failed before %s. Exiting.\n", tests[i].name);
656 break;
657 }
658
659 if (tests[i].func(argv[2].str)) {
660 printf(" [Passed] %s\n", tests[i].name);
661 ++passed;
662 } else {
663 printf(" [Failed] %s\n", tests[i].name);
664 }
665
666 if (!test_teardown()) {
667 printf("Test teardown failed after %s. Exiting.\n", tests[i].name);
668 break;
669 }
670 }
671 printf("\nPassed %u of %u tests.\n", passed, attempted);
672
673 if (attempted != countof(tests)) {
674 printf("(Skipped %u)\n", countof(tests) - attempted);
675 }
676
677 return countof(tests) - passed;
678}
679
680STATIC_COMMAND_START
681STATIC_COMMAND("spifs", "commands related to the spifs implementation.", &cmd_spifs)
682STATIC_COMMAND_END(spifs);
683
684#endif // LK_DEBUGLEVEL > 1