diff --git a/Makefile b/Makefile index ed1afb5..bb449ca 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ OBJ := $(SRC:.c=.o) DEP := $(SRC:.c=.d) ASM := $(SRC:.c=.s) +TEST := $(wildcard tests/test_*) + ifdef DEBUG CFLAGS += -O0 -g3 else @@ -28,6 +30,9 @@ asm: $(ASM) size: $(OBJ) $(SIZE) -t $^ +test: + for t in $(TEST) ; do ./$$t ; done + -include $(DEP) $(TARGET): $(OBJ) diff --git a/emubd/lfs_cfg.c b/emubd/lfs_cfg.c deleted file mode 100644 index 82dd624..0000000 --- a/emubd/lfs_cfg.c +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Simple config parser - * - * Copyright (c) 2017 Christopher Haster - * Distributed under the MIT license - */ -#include "emubd/lfs_cfg.h" - -#include -#include -#include -#include - - -static int lfs_cfg_buffer(lfs_cfg_t *cfg, char c) { - // Amortize double - if (cfg->blen == cfg->bsize) { - size_t nsize = cfg->bsize * 2; - char *nbuf = malloc(nsize); - if (!nbuf) { - return -ENOMEM; - } - - memcpy(nbuf, cfg->buf, cfg->bsize); - free(cfg->buf); - cfg->buf = nbuf; - cfg->bsize = nsize; - } - - cfg->buf[cfg->blen] = c; - cfg->blen += 1; - return 0; -} - -static int lfs_cfg_attr(lfs_cfg_t *cfg, unsigned key, unsigned val) { - // Amortize double - if (cfg->len == cfg->size) { - size_t nsize = cfg->size * 2; - struct lfs_cfg_attr *nattrs = malloc(nsize*sizeof(struct lfs_cfg_attr)); - if (!nattrs) { - return -ENOMEM; - } - - memcpy(nattrs, cfg->attrs, cfg->size*sizeof(struct lfs_cfg_attr)); - free(cfg->attrs); - cfg->attrs = nattrs; - cfg->size = nsize; - } - - // Keep attrs sorted for binary search - unsigned i = 0; - while (i < cfg->len && - strcmp(&cfg->buf[key], - &cfg->buf[cfg->attrs[i].key]) > 0) { - i += 1; - } - - memmove(&cfg->attrs[i+1], &cfg->attrs[i], - (cfg->size - i)*sizeof(struct lfs_cfg_attr)); - cfg->attrs[i].key = key; - cfg->attrs[i].val = val; - cfg->len += 1; - return 0; -} - -static bool lfs_cfg_match(FILE *f, const char *matches) { - char c = getc(f); - ungetc(c, f); - - for (int i = 0; matches[i]; i++) { - if (c == matches[i]) { - return true; - } - } - - return false; -} - -int lfs_cfg_create(lfs_cfg_t *cfg, const char *filename) { - // start with some initial space - cfg->len = 0; - cfg->size = 4; - cfg->attrs = malloc(cfg->size*sizeof(struct lfs_cfg_attr)); - - cfg->blen = 0; - cfg->bsize = 16; - cfg->buf = malloc(cfg->size); - - FILE *f = fopen(filename, "r"); - if (!f) { - return -errno; - } - - while (!feof(f)) { - int err; - - while (lfs_cfg_match(f, " \t\v\f")) { - fgetc(f); - } - - if (!lfs_cfg_match(f, "#\r\n")) { - unsigned key = cfg->blen; - while (!lfs_cfg_match(f, " \t\v\f:#") && !feof(f)) { - if ((err = lfs_cfg_buffer(cfg, fgetc(f)))) { - return err; - } - } - if ((err = lfs_cfg_buffer(cfg, 0))) { - return err; - } - - while (lfs_cfg_match(f, " \t\v\f")) { - fgetc(f); - } - - if (lfs_cfg_match(f, ":")) { - fgetc(f); - while (lfs_cfg_match(f, " \t\v\f")) { - fgetc(f); - } - - unsigned val = cfg->blen; - while (!lfs_cfg_match(f, " \t\v\f#\r\n") && !feof(f)) { - if ((err = lfs_cfg_buffer(cfg, fgetc(f)))) { - return err; - } - } - if ((err = lfs_cfg_buffer(cfg, 0))) { - return err; - } - - if ((err = lfs_cfg_attr(cfg, key, val))) { - return err; - } - } else { - cfg->blen = key; - } - } - - while (!lfs_cfg_match(f, "\r\n") && !feof(f)) { - fgetc(f); - } - fgetc(f); - } - - return 0; -} - -void lfs_cfg_destroy(lfs_cfg_t *cfg) { - free(cfg->attrs); -} - -bool lfs_cfg_has(lfs_cfg_t *cfg, const char *key) { - return lfs_cfg_get(cfg, key, 0); -} - -const char *lfs_cfg_get(lfs_cfg_t *cfg, const char *key, const char *def) { - // binary search for attribute - int lo = 0; - int hi = cfg->len-1; - - while (lo <= hi) { - int i = (hi + lo) / 2; - int cmp = strcmp(key, &cfg->buf[cfg->attrs[i].key]); - if (cmp == 0) { - return &cfg->buf[cfg->attrs[i].val]; - } else if (cmp < 0) { - hi = i-1; - } else { - lo = i+1; - } - } - - return def; -} - -ssize_t lfs_cfg_geti(lfs_cfg_t *cfg, const char *key, ssize_t def) { - const char *val = lfs_cfg_get(cfg, key, 0); - if (!val) { - return def; - } - - char *end; - ssize_t res = strtoll(val, &end, 0); - return (end == val) ? def : res; -} - -size_t lfs_cfg_getu(lfs_cfg_t *cfg, const char *key, size_t def) { - const char *val = lfs_cfg_get(cfg, key, 0); - if (!val) { - return def; - } - - char *end; - size_t res = strtoull(val, &end, 0); - return (end == val) ? def : res; -} diff --git a/emubd/lfs_cfg.h b/emubd/lfs_cfg.h deleted file mode 100644 index ef73f35..0000000 --- a/emubd/lfs_cfg.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Simple config parser - * - * Copyright (c) 2017 Christopher Haster - * Distributed under the MIT license - */ -#ifndef LFS_CFG_H -#define LFS_CFG_H - -#include -#include -#include - -// This is a simple parser for config files -// -// The cfg file format is dumb simple. Attributes are -// key value pairs separated by a single colon. Delimited -// by comments (#) and newlines (\r\n) and trims -// whitespace ( \t\v\f) -// -// Here's an example file -// # Here is a dump example -// looky: it's_an_attribute -// hey_look: another_attribute -// -// huh: yeah_that's_basically_it # basically it - -// Internal config structure -typedef struct lfs_cfg { - size_t len; - size_t size; - - size_t blen; - size_t bsize; - char *buf; - - struct lfs_cfg_attr { - unsigned key; - unsigned val; - } *attrs; -} lfs_cfg_t; - - - -// Creates a cfg object and reads in the cfg file from the filename -// -// If the lfs_cfg_read fails, returns a negative value from the underlying -// stdio functions -int lfs_cfg_create(lfs_cfg_t *cfg, const char *filename); - -// Destroys the cfg object and frees any used memory -void lfs_cfg_destroy(lfs_cfg_t *cfg); - -// Checks if a cfg attribute exists -bool lfs_cfg_has(lfs_cfg_t *cfg, const char *key); - -// Retrieves a cfg attribute as a null-terminated string -// -// If the attribute does not exist, returns the string passed as def -const char *lfs_cfg_get(lfs_cfg_t *cfg, const char *key, const char *def); - -// Retrieves a cfg attribute parsed as an int -// -// If the attribute does not exist or can't be parsed, returns the -// integer passed as def -ssize_t lfs_cfg_geti(lfs_cfg_t *cfg, const char *name, ssize_t def); - -// Retrieves a cfg attribute parsed as an unsigned int -// -// If the attribute does not exist or can't be parsed, returns the -// integer passed as def -size_t lfs_cfg_getu(lfs_cfg_t *cfg, const char *name, size_t def); - -#endif diff --git a/emubd/lfs_emubd.c b/emubd/lfs_emubd.c index 05babfd..794f25f 100644 --- a/emubd/lfs_emubd.c +++ b/emubd/lfs_emubd.c @@ -5,7 +5,6 @@ * Distributed under the MIT license */ #include "emubd/lfs_emubd.h" -#include "emubd/lfs_cfg.h" #include #include @@ -14,6 +13,7 @@ #include #include #include +#include // Block device emulated on existing filesystem @@ -30,28 +30,44 @@ int lfs_emubd_create(lfs_emubd_t *emu, const char *path) { strcpy(emu->path, path); emu->path[pathlen] = '/'; - emu->path[pathlen + 1 + LFS_NAME_MAX] = '\0'; emu->child = &emu->path[pathlen+1]; - strncpy(emu->child, "config", LFS_NAME_MAX); + memset(emu->child, '\0', LFS_NAME_MAX+1); - // Load config, erroring if it doesn't exist - lfs_cfg_t cfg; - int err = lfs_cfg_create(&cfg, emu->path); - if (err) { - return err; + // Create directory if it doesn't exist + int err = mkdir(path, 0777); + if (err && errno != EEXIST) { + return -errno; } - emu->info.read_size = lfs_cfg_getu(&cfg, "read_size", 0); - emu->info.prog_size = lfs_cfg_getu(&cfg, "prog_size", 0); - emu->info.erase_size = lfs_cfg_getu(&cfg, "erase_size", 0); - emu->info.total_size = lfs_cfg_getu(&cfg, "total_size", 0); + // Setup info based on configuration + emu->info.read_size = LFS_EMUBD_READ_SIZE; + emu->info.prog_size = LFS_EMUBD_PROG_SIZE; + emu->info.erase_size = LFS_EMUBD_ERASE_SIZE; + emu->info.total_size = LFS_EMUBD_TOTAL_SIZE; - lfs_cfg_destroy(&cfg); + // Load stats to continue incrementing + snprintf(emu->child, LFS_NAME_MAX, "stats"); + FILE *f = fopen(emu->path, "r"); + if (!f) { + return -errno; + } + + size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f); + if (res < 1) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } return 0; } void lfs_emubd_destroy(lfs_emubd_t *emu) { + lfs_emubd_sync(emu); + free(emu->path); } @@ -194,7 +210,39 @@ int lfs_emubd_erase(lfs_emubd_t *emu, lfs_block_t block, } int lfs_emubd_sync(lfs_emubd_t *emu) { - // Always in sync + // Just write out info/stats for later lookup + snprintf(emu->child, LFS_NAME_MAX, "info"); + FILE *f = fopen(emu->path, "w"); + if (!f) { + return -errno; + } + + size_t res = fwrite(&emu->info, sizeof(emu->info), 1, f); + if (res < 1) { + return -errno; + } + + int err = fclose(f); + if (err) { + return -errno; + } + + snprintf(emu->child, LFS_NAME_MAX, "stats"); + f = fopen(emu->path, "w"); + if (!f) { + return -errno; + } + + res = fwrite(&emu->stats, sizeof(emu->stats), 1, f); + if (res < 1) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + return 0; } diff --git a/emubd/lfs_emubd.h b/emubd/lfs_emubd.h index d297039..89ee4ce 100644 --- a/emubd/lfs_emubd.h +++ b/emubd/lfs_emubd.h @@ -12,6 +12,24 @@ #include "lfs_bd.h" +// Config options +#ifndef LFS_EMUBD_READ_SIZE +#define LFS_EMUBD_READ_SIZE 1 +#endif + +#ifndef LFS_EMUBD_PROG_SIZE +#define LFS_EMUBD_PROG_SIZE 1 +#endif + +#ifndef LFS_EMUBD_ERASE_SIZE +#define LFS_EMUBD_ERASE_SIZE 512 +#endif + +#ifndef LFS_EMUBD_TOTAL_SIZE +#define LFS_EMUBD_TOTAL_SIZE 524288 +#endif + + // Stats for debugging and optimization struct lfs_bd_stats { uint64_t read_count; diff --git a/lfs.c b/lfs.c index 37d65b4..2dfb9cc 100644 --- a/lfs.c +++ b/lfs.c @@ -762,7 +762,7 @@ int lfs_mount(lfs_t *lfs, lfs_bd_t *bd, const struct lfs_bd_ops *bd_ops) { if ((err == LFS_ERROR_CORRUPT || memcmp(superblock.d.magic, "littlefs", 8) != 0)) { - LFS_ERROR("Invalid superblock at %d %d\n", + LFS_ERROR("Invalid superblock at %d %d", superblock.pair[0], superblock.pair[1]); return LFS_ERROR_CORRUPT; } diff --git a/lfs.h b/lfs.h index 2f9b296..88984cf 100644 --- a/lfs.h +++ b/lfs.h @@ -31,11 +31,11 @@ enum lfs_open_flags { LFS_O_RDONLY = 0, LFS_O_WRONLY = 1, LFS_O_RDWR = 2, - LFS_O_CREAT = 0x0040, - LFS_O_EXCL = 0x0080, - LFS_O_TRUNC = 0x0200, - LFS_O_APPEND = 0x0400, - LFS_O_SYNC = 0x1000, + LFS_O_CREAT = 0x020, + LFS_O_EXCL = 0x040, + LFS_O_TRUNC = 0x080, + LFS_O_APPEND = 0x100, + LFS_O_SYNC = 0x200, }; diff --git a/lfs_config.h b/lfs_config.h index b73e812..fd1e6ad 100644 --- a/lfs_config.h +++ b/lfs_config.h @@ -23,9 +23,9 @@ typedef uint32_t lfs_block_t; // Logging operations #include -#define LFS_ERROR(fmt, ...) printf("Error: " fmt "\n", __VA_ARGS__) -#define LFS_WARN(fmt, ...) printf("Warn: " fmt "\n", __VA_ARGS__) -#define LFS_INFO(fmt, ...) printf("Info: " fmt "\n", __VA_ARGS__) +#define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__) +#define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__) +#define LFS_INFO(fmt, ...) printf("lfs info: " fmt "\n", __VA_ARGS__) #endif diff --git a/tests/stats.py b/tests/stats.py new file mode 100755 index 0000000..9508e79 --- /dev/null +++ b/tests/stats.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import struct +import sys +import time +import os +import re + +def main(): + with open('blocks/info') as file: + s = struct.unpack(' +#include +#include + +// test stuff +void test_log(const char *s, uintmax_t v) {{ + printf("%s: %jd\n", s, v); +}} + +void test_assert(const char *s, uintmax_t v, uintmax_t e) {{ + test_log(s, v); + if (v != e) {{ + printf("\033[31massert %s failed, expected %jd\033[0m\n", s, e); + exit(-2); + }} +}} + +// lfs declarations +lfs_t lfs; +lfs_emubd_t bd; +lfs_file_t file[4]; +lfs_dir_t dir[4]; +struct lfs_bd_info info; +struct lfs_bd_stats stats; + +uint8_t buffer[1024]; +uint8_t wbuffer[1024]; +uint8_t rbuffer[1024]; +lfs_size_t size; +lfs_size_t wsize; +lfs_size_t rsize; + +uintmax_t res; + + +int main() {{ + lfs_emubd_create(&bd, "blocks"); + +{tests} + + lfs_emubd_destroy(&bd); +}} diff --git a/tests/test.py b/tests/test.py new file mode 100755 index 0000000..380211a --- /dev/null +++ b/tests/test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +import re +import sys +import subprocess +import os + +def generate(test): + with open("tests/template.fmt") as file: + template = file.read() + + lines = [] + + for line in test: + if '=>' in line: + test, expect = line.strip().strip(';').split('=>') + lines.append('res = {test};'.format(test=test.strip())) + lines.append('test_assert("{name}", res, {expect});'.format( + name = re.match('\w*', test.strip()).group(), + expect = expect.strip())) + else: + lines.append(line.strip()) + + with open('test.c', 'w') as file: + file.write(template.format(tests='\n'.join(4*' ' + l for l in lines))) + +def compile(): + os.environ['DEBUG'] = '1' + os.environ['CFLAGS'] = '-Werror' + subprocess.check_call(['make', '--no-print-directory', '-s'], env=os.environ) + +def execute(): + subprocess.check_call(["./lfs"]) + +def main(test=None): + if test: + with open(test) as file: + generate(file) + else: + generate(sys.stdin) + + compile() + execute() + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/tests/test_format.sh b/tests/test_format.sh new file mode 100755 index 0000000..cca00ee --- /dev/null +++ b/tests/test_format.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -eu + +echo "=== Formatting tests ===" +rm -rf blocks + +echo "--- Basic formatting ---" +./tests/test.py << TEST + lfs_format(&lfs, &bd, &lfs_emubd_ops) => 0; +TEST + +echo "--- Invalid superblocks ---" +ln -f -s /dev/null blocks/0 +./tests/test.py << TEST + lfs_format(&lfs, &bd, &lfs_emubd_ops) => LFS_ERROR_CORRUPT; +TEST +rm blocks/0 + +echo "--- Basic mounting ---" +./tests/test.py << TEST + lfs_mount(&lfs, &bd, &lfs_emubd_ops) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Invalid mount ---" +./tests/test.py << TEST + lfs_format(&lfs, &bd, &lfs_emubd_ops) => 0; +TEST +rm blocks/0 blocks/1 +./tests/test.py << TEST + lfs_mount(&lfs, &bd, &lfs_emubd_ops) => LFS_ERROR_CORRUPT; +TEST + +echo "--- Valid corrupt mount ---" +./tests/test.py << TEST + lfs_format(&lfs, &bd, &lfs_emubd_ops) => 0; +TEST +rm blocks/0 +./tests/test.py << TEST + lfs_mount(&lfs, &bd, &lfs_emubd_ops) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +./tests/stats.py