blob: d2de09026c7a1de21279bd391e71602923a2a661 [file] [log] [blame]
/*
* Copyright (c) 2015 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if LK_DEBUGLEVEL > 1
#include <string.h>
#include <err.h>
#include <stdlib.h>
#include <lib/bio.h>
#include <lib/console.h>
#include <lib/fs/spifs.h>
#define FS_NAME "spifs"
#define MNT_PATH "/s"
#define TEST_FILE_PATH "/s/test"
#define TEST_PATH_MAX_SIZE 16
typedef bool(*test_func)(const char *);
typedef struct {
test_func func;
const char* name;
uint32_t toc_pages;
} test;
static bool test_empty_after_format(const char *);
static bool test_double_create_file(const char *);
static bool test_write_read_normal(const char *);
static bool test_write_past_eof(const char *);
static bool test_full_toc(const char *);
static bool test_full_fs(const char *);
static bool test_write_past_end_of_capacity(const char *);
static bool test_rm_reclaim(const char *);
static bool test_corrupt_toc(const char *);
static bool test_write_with_offset(const char *);
static bool test_read_write_big(const char *);
static bool test_rm_active_dirent(const char *);
static test tests[] = {
{&test_empty_after_format, "Test no files in ToC after format.", 1},
{&test_write_read_normal, "Test the normal read/write file paths.", 1},
{&test_double_create_file, "Test file cannot be created if it already exists.", 1},
{&test_write_past_eof, "Test that file can grow up to capacity.", 1},
{&test_full_toc, "Test that files cannot be created once the ToC is full.", 2},
{&test_full_fs, "Test that files cannot be created once the device is full.", 1},
{&test_rm_reclaim, "Test that files can be deleted and that used space is reclaimed.", 1},
{&test_write_past_end_of_capacity, "Test that we cannot write past the capacity of a file.", 1},
{&test_corrupt_toc, "Test that FS can be mounted with one corrupt ToC.", 1},
{&test_write_with_offset, "Test that files can be written to at an offset.", 1},
{&test_read_write_big, "Test that an unaligned ~10kb buffer can be written and read.", 1},
{&test_rm_active_dirent, "Test that we can remove a file with an open dirent.", 1},
};
bool test_setup(const char *dev_name, uint32_t toc_pages)
{
spifs_format_args_t args = {
.toc_pages = toc_pages,
};
status_t res = fs_format_device(FS_NAME, dev_name, (void*)&args);
if (res != NO_ERROR) {
printf("spifs_format failed dev = %s, toc_pages = %u, retcode = %d\n",
dev_name, toc_pages, res);
return false;
}
res = fs_mount(MNT_PATH, FS_NAME, dev_name);
if (res != NO_ERROR) {
printf("fs_mount failed path = %s, fs name = %s, dev name = %s,"
" retcode = %d\n", MNT_PATH, FS_NAME, dev_name, res);
return false;
}
return true;
}
bool test_teardown(void)
{
if (fs_unmount(MNT_PATH) != NO_ERROR) {
printf("Unmount failed\n");
return false;
}
return true;;
}
static bool test_empty_after_format(const char *dev_name)
{
dirhandle *dhandle;
status_t err = fs_open_dir(MNT_PATH, &dhandle);
if (err != NO_ERROR) {
return false;
}
struct dirent ent;
if (fs_read_dir(dhandle, &ent) >= 0) {
fs_close_dir(dhandle);
return false;
}
fs_close_dir(dhandle);
return true;
}
static bool test_double_create_file(const char *dev_name)
{
status_t status;
struct dirent *ent = malloc(sizeof(*ent));
size_t num_files = 0;
filehandle *handle;
status = fs_create_file(TEST_FILE_PATH, &handle, 10);
if (status != NO_ERROR) {
goto err;
}
fs_close_file(handle);
filehandle *duphandle;
status = fs_create_file(TEST_FILE_PATH, &duphandle, 20);
if (status != ERR_ALREADY_EXISTS) {
goto err;
}
dirhandle *dhandle;
status = fs_open_dir(MNT_PATH, &dhandle);
if (status != NO_ERROR) {
goto err;
}
while ((status = fs_read_dir(dhandle, ent)) >= 0) {
num_files++;
}
status = NO_ERROR;
fs_close_dir(dhandle);
err:
free(ent);
return status == NO_ERROR ? num_files == 1 : false;
}
static bool test_write_read_normal(const char *dev_name)
{
char test_message[] = "spifs test";
char test_buf[sizeof(test_message)];
bdev_t *dev = bio_open(dev_name);
if (!dev) {
return false;
}
uint8_t erase_byte = dev->erase_byte;
bio_close(dev);
filehandle *handle;
status_t status =
fs_create_file(TEST_FILE_PATH, &handle, sizeof(test_message));
if (status != NO_ERROR) {
return false;
}
ssize_t bytes;
// New files should be initialized to 'erase_byte'
bytes = fs_read_file(handle, test_buf, 0, sizeof(test_buf));
if (bytes != sizeof(test_buf)) {
return false;
}
for (size_t i = 0; i < sizeof(test_buf); i++) {
if (test_buf[i] != erase_byte) {
return false;
}
}
bytes = fs_write_file(handle, test_message, 0, sizeof(test_message));
if (bytes != sizeof(test_message)) {
return false;
}
bytes = fs_read_file(handle, test_buf, 0, sizeof(test_buf));
if (bytes != sizeof(test_buf)) {
return false;
}
status = fs_close_file(handle);
if (status != NO_ERROR) {
return false;
}
return strncmp(test_message, test_buf, sizeof(test_message)) == 0;
}
static bool test_write_past_eof(const char *dev_name)
{
char test_message[] = "spifs test";
// Create a 0 length file.
filehandle *handle;
status_t status =
fs_create_file(TEST_FILE_PATH, &handle, 0);
if (status != NO_ERROR) {
return false;
}
ssize_t bytes = fs_write_file(handle, test_message, 0, sizeof(test_message));
if (bytes != sizeof(test_message)) {
return false;
}
// Make sure the file grows.
struct file_stat stat;
fs_stat_file(handle, &stat);
if (stat.is_dir != false && stat.size != sizeof(test_message)) {
return false;
}
fs_close_file(handle);
return true;
}
static bool test_full_toc(const char *dev_name)
{
struct fs_stat stat;
fs_stat_fs(MNT_PATH, &stat);
char test_file_name[TEST_PATH_MAX_SIZE];
filehandle *handle;
for (size_t i = 0; i < stat.free_inodes; i++) {
memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
char filenum[] = "000";
filenum[0] += i / 100;
filenum[1] += (i / 10) % 10;
filenum[2] += i % 10;
strlcat(test_file_name, MNT_PATH, sizeof(test_file_name));
strlcat(test_file_name, "/", sizeof(test_file_name));
strlcat(test_file_name, filenum, sizeof(test_file_name));
status_t status =
fs_create_file(test_file_name, &handle, 1);
if (status != NO_ERROR) {
return false;
}
fs_close_file(handle);
}
// There shouldn't be enough space for this file since we've exhausted all
// the inodes.
status_t status = fs_create_file(TEST_FILE_PATH, &handle, 1);
if (status != ERR_TOO_BIG) {
return false;
}
return true;
}
static bool test_rm_reclaim(const char *dev_name)
{
// Create some number of files that's a power of 2;
size_t n_files = 4;
struct fs_stat stat;
fs_stat_fs(MNT_PATH, &stat);
size_t file_size = stat.free_space / (n_files + 1);
char test_file_name[TEST_PATH_MAX_SIZE];
filehandle *handle;
for (size_t i = 0; i < n_files; i++) {
memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
char filenum[] = "000";
filenum[0] += i / 100;
filenum[1] += (i / 10) % 10;
filenum[2] += i % 10;
strcat(test_file_name, MNT_PATH);
strcat(test_file_name, filenum);
status_t status =
fs_create_file(test_file_name, &handle, file_size);
if (status != NO_ERROR) {
return false;
}
fs_close_file(handle);
}
// Try to create a new Big file.
char filename[] = "BIGFILE";
memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
strcat(test_file_name, MNT_PATH);
strcat(test_file_name, filename);
status_t status;
// This should fail because there's no more space.
fs_stat_fs(MNT_PATH, &stat);
status = fs_create_file(test_file_name, &handle, stat.free_space + 1);
if (status != ERR_TOO_BIG) {
return false;
}
// Delete an existing file to make space for the new file.
char existing_filename[] = "001";
memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
strcat(test_file_name, MNT_PATH);
strcat(test_file_name, existing_filename);
status = fs_remove_file(test_file_name);
if (status != NO_ERROR) {
return false;
}
// Now this should go through because we've reclaimed the space.
status = fs_create_file(test_file_name, &handle, stat.free_space + 1);
if (status != NO_ERROR) {
return false;
}
fs_close_file(handle);
return true;
}
static bool test_full_fs(const char *dev_name)
{
struct fs_stat stat;
fs_stat_fs(MNT_PATH, &stat);
char second_file_path[TEST_PATH_MAX_SIZE];
memset(second_file_path, 0, TEST_PATH_MAX_SIZE);
strcpy(second_file_path, MNT_PATH);
strcat(second_file_path, "/fail");
filehandle *handle;
status_t status = fs_create_file(TEST_FILE_PATH, &handle, stat.free_space);
if (status != NO_ERROR) {
return false;
}
fs_close_file(handle);
// There shouldn't be enough space for this file since we've used all the
// space.
status = fs_create_file(second_file_path, &handle, 1);
if (status != ERR_TOO_BIG) {
return false;
}
return true;
}
static bool test_write_past_end_of_capacity(const char *dev_name)
{
filehandle *handle;
status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
if (status != NO_ERROR) {
return false;
}
struct file_stat stat;
status = fs_stat_file(handle, &stat);
if (status != NO_ERROR) {
goto finish;
}
// We shouldn't be able to write past the capacity of a file.
char buf[1];
status = fs_write_file(handle, buf, stat.capacity, 1);
if (status == ERR_OUT_OF_RANGE) {
status = NO_ERROR;
} else {
status = ERR_IO;
}
finish:
fs_close_file(handle);
return status == NO_ERROR;
}
static bool test_corrupt_toc(const char *dev_name)
{
// Create a zero byte file.
filehandle *handle;
status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
if (status != NO_ERROR) {
return false;
}
// Grow the file to one byte. This should trigger a ToC flush. Now the
// ToC record for this file should exist in both ToCs. Therefore corrupting
// either of the ToCs will still yield this file readable.
char buf[1] = { 'a' };
status = fs_write_file(handle, buf, 0, 1);
if (status != 1) {
return false;
}
status = fs_close_file(handle);
if (status != NO_ERROR) {
return false;
}
status = fs_unmount(MNT_PATH);
if (status != NO_ERROR) {
return false;
}
// Now we're going to manually corrupt one of the ToCs
bdev_t *dev = bio_open(dev_name);
if (!dev) {
return false;
}
// Directly write 0s to the block that contains the front-ToC.
size_t block_size = dev->block_size;
uint8_t *block_buf = memalign(CACHE_LINE, block_size);
if (!block_buf) {
return false;
}
memset(block_buf, 0, block_size);
ssize_t bytes = bio_write_block(dev, block_buf, 0, 1);
free(block_buf);
bio_close(dev);
if (bytes != (ssize_t)block_size) {
return false;
}
// Mount the FS again and make sure that the file we created is still there.
status = fs_mount(MNT_PATH, FS_NAME, dev_name);
if (status != NO_ERROR) {
return false;
}
status = fs_open_file(TEST_FILE_PATH, &handle);
if (status != NO_ERROR) {
return false;
}
struct file_stat stat;
status = fs_stat_file(handle, &stat);
if (status != NO_ERROR) {
return false;
}
status = fs_close_file(handle);
if (status != NO_ERROR) {
return false;
}
return true;
}
static bool test_write_with_offset(const char *dev_name)
{
size_t repeats = 3;
char test_message[] = "test";
size_t msg_len = strnlen(test_message, sizeof(test_message));
char test_buf[msg_len * repeats];
filehandle *handle;
status_t status = fs_create_file(TEST_FILE_PATH, &handle, msg_len);
if (status != NO_ERROR) {
return false;
}
ssize_t bytes;
for (size_t pos = 0; pos < repeats; pos++) {
bytes = fs_write_file(handle, test_message, pos * msg_len, msg_len);
if ((size_t)bytes != msg_len) {
return false;
}
}
bytes = fs_read_file(handle, test_buf, 0, msg_len * repeats);
if ((size_t)bytes != msg_len * repeats) {
return false;
}
status = fs_close_file(handle);
if (status != NO_ERROR) {
return false;
}
bool success = true;
for (size_t i = 0; i < repeats; i++) {
success &= (memcmp(test_message,
test_buf + i * msg_len,
msg_len) == 0);
}
return success;
}
static bool test_read_write_big(const char *dev_name)
{
bool success = true;
size_t buflen = 10013;
uint8_t *rbuf = malloc(buflen);
if (!rbuf) {
return false;
}
uint8_t *wbuf = malloc(buflen);
if (!wbuf) {
free(rbuf);
return false;
}
for (size_t i = 0; i < buflen; i++) {
wbuf[i] = rand() % sizeof(uint8_t);
}
filehandle *handle;
status_t status = fs_create_file(TEST_FILE_PATH, &handle, buflen);
if (status != NO_ERROR) {
success = false;
goto err;
}
ssize_t bytes = fs_write_file(handle, wbuf, 0, buflen);
if ((size_t)bytes != buflen) {
success = false;
goto err;
}
bytes = fs_read_file(handle, rbuf, 0, buflen);
if ((size_t)bytes != buflen) {
success = false;
goto err;
}
for (size_t i = 0; i < buflen; i++) {
if (wbuf[i] != rbuf[i]) {
success = false;
break;
}
}
err:
success &= fs_close_file(handle) == NO_ERROR;
free(rbuf);
free(wbuf);
return success;
}
static bool test_rm_active_dirent(const char *dev_name)
{
filehandle *handle;
status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
if (status != NO_ERROR) {
return false;
}
status = fs_close_file(handle);
if (status != NO_ERROR) {
return false;
}
dirhandle *dhandle;
status = fs_open_dir(MNT_PATH, &dhandle);
if (status != NO_ERROR) {
return false;
}
// Dir handle should now be pointing to the only file in our FS.
status = fs_remove_file(TEST_FILE_PATH);
if (status != NO_ERROR) {
fs_close_dir(dhandle);
return false;
}
bool success = true;
struct dirent *ent = malloc(sizeof(*ent));
if (fs_read_dir(dhandle, ent) >= 0) {
success = false;
}
success &= fs_close_dir(dhandle) == NO_ERROR;
free(ent);
return success;
}
static int cmd_spifs(int argc, const cmd_args *argv)
{
if (argc < 3) {
notenoughargs:
printf("not enough arguments:\n");
usage:
printf("%s test <device>\n", argv[0].str);
return -1;
}
if (strcmp(argv[1].str, "test")) {
goto usage;
}
// Make sure this block device is legit.
bdev_t *dev = bio_open(argv[2].str);
if (!dev) {
printf("error: could not open block device %s\n", argv[2].str);
return -1;
}
bio_close(dev);
size_t passed = 0;
size_t attempted = 0;
for (size_t i = 0; i < countof(tests); i++) {
++attempted;
if (!test_setup(argv[2].str, tests[i].toc_pages)) {
printf("Test Setup failed before %s. Exiting.\n", tests[i].name);
break;
}
if (tests[i].func(argv[2].str)) {
printf(" [Passed] %s\n", tests[i].name);
++passed;
} else {
printf(" [Failed] %s\n", tests[i].name);
}
if (!test_teardown()) {
printf("Test teardown failed after %s. Exiting.\n", tests[i].name);
break;
}
}
printf("\nPassed %u of %u tests.\n", passed, attempted);
if (attempted != countof(tests)) {
printf("(Skipped %u)\n", countof(tests) - attempted);
}
return countof(tests) - passed;
}
STATIC_COMMAND_START
STATIC_COMMAND("spifs", "commands related to the spifs implementation.", &cmd_spifs)
STATIC_COMMAND_END(spifs);
#endif // LK_DEBUGLEVEL > 1