diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..c2bcc048002 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +TARGET = lfs + +CC = gcc +AR = ar +SIZE = size + +SRC += $(wildcard *.c emubd/*.c) +OBJ := $(SRC:.c=.o) +DEP := $(SRC:.c=.d) +ASM := $(SRC:.c=.s) + +ifdef DEBUG +CFLAGS += -O0 -g3 +else +CFLAGS += -O2 +endif +ifdef WORD +CFLAGS += -m$(WORD) +endif +CFLAGS += -I. +CFLAGS += -std=c99 -Wall -pedantic + + +all: $(TARGET) + +asm: $(ASM) + +size: $(OBJ) + $(SIZE) -t $^ + +-include $(DEP) + +$(TARGET): $(OBJ) + $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ + +%.a: $(OBJ) + $(AR) rcs $@ $^ + +%.o: %.c + $(CC) -c -MMD $(CFLAGS) $< -o $@ + +%.s: %.c + $(CC) -S $(CFLAGS) $< -o $@ + +clean: + rm -f $(TARGET) + rm -f $(OBJ) + rm -f $(DEP) + rm -f $(ASM) diff --git a/emu/cfg.c b/emubd/lfs_cfg.c similarity index 61% rename from emu/cfg.c rename to emubd/lfs_cfg.c index 26f5bfcf4df..82dd62456d8 100644 --- a/emu/cfg.c +++ b/emubd/lfs_cfg.c @@ -1,4 +1,10 @@ -#include "cfg.h" +/* + * Simple config parser + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the MIT license + */ +#include "emubd/lfs_cfg.h" #include #include @@ -6,7 +12,7 @@ #include -static int cfg_buffer(cfg_t *cfg, char c) { +static int lfs_cfg_buffer(lfs_cfg_t *cfg, char c) { // Amortize double if (cfg->blen == cfg->bsize) { size_t nsize = cfg->bsize * 2; @@ -26,16 +32,16 @@ static int cfg_buffer(cfg_t *cfg, char c) { return 0; } -static int cfg_attr(cfg_t *cfg, unsigned key, unsigned val) { +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 cfg_attr *nattrs = malloc(nsize*sizeof(struct cfg_attr)); + struct lfs_cfg_attr *nattrs = malloc(nsize*sizeof(struct lfs_cfg_attr)); if (!nattrs) { return -ENOMEM; } - memcpy(nattrs, cfg->attrs, cfg->size*sizeof(struct cfg_attr)); + memcpy(nattrs, cfg->attrs, cfg->size*sizeof(struct lfs_cfg_attr)); free(cfg->attrs); cfg->attrs = nattrs; cfg->size = nsize; @@ -50,14 +56,14 @@ static int cfg_attr(cfg_t *cfg, unsigned key, unsigned val) { } memmove(&cfg->attrs[i+1], &cfg->attrs[i], - (cfg->size - i)*sizeof(struct cfg_attr)); + (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 cfg_match(FILE *f, const char *matches) { +static bool lfs_cfg_match(FILE *f, const char *matches) { char c = getc(f); ungetc(c, f); @@ -70,11 +76,11 @@ static bool cfg_match(FILE *f, const char *matches) { return false; } -int cfg_create(cfg_t *cfg, const char *filename) { +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 cfg_attr)); + cfg->attrs = malloc(cfg->size*sizeof(struct lfs_cfg_attr)); cfg->blen = 0; cfg->bsize = 16; @@ -88,42 +94,42 @@ int cfg_create(cfg_t *cfg, const char *filename) { while (!feof(f)) { int err; - while (cfg_match(f, " \t\v\f")) { + while (lfs_cfg_match(f, " \t\v\f")) { fgetc(f); } - if (!cfg_match(f, "#\r\n")) { + if (!lfs_cfg_match(f, "#\r\n")) { unsigned key = cfg->blen; - while (!cfg_match(f, " \t\v\f:#") && !feof(f)) { - if ((err = cfg_buffer(cfg, fgetc(f)))) { + while (!lfs_cfg_match(f, " \t\v\f:#") && !feof(f)) { + if ((err = lfs_cfg_buffer(cfg, fgetc(f)))) { return err; } } - if ((err = cfg_buffer(cfg, 0))) { + if ((err = lfs_cfg_buffer(cfg, 0))) { return err; } - while (cfg_match(f, " \t\v\f")) { + while (lfs_cfg_match(f, " \t\v\f")) { fgetc(f); } - if (cfg_match(f, ":")) { + if (lfs_cfg_match(f, ":")) { fgetc(f); - while (cfg_match(f, " \t\v\f")) { + while (lfs_cfg_match(f, " \t\v\f")) { fgetc(f); } unsigned val = cfg->blen; - while (!cfg_match(f, " \t\v\f#\r\n") && !feof(f)) { - if ((err = cfg_buffer(cfg, fgetc(f)))) { + while (!lfs_cfg_match(f, " \t\v\f#\r\n") && !feof(f)) { + if ((err = lfs_cfg_buffer(cfg, fgetc(f)))) { return err; } } - if ((err = cfg_buffer(cfg, 0))) { + if ((err = lfs_cfg_buffer(cfg, 0))) { return err; } - if ((err = cfg_attr(cfg, key, val))) { + if ((err = lfs_cfg_attr(cfg, key, val))) { return err; } } else { @@ -131,7 +137,7 @@ int cfg_create(cfg_t *cfg, const char *filename) { } } - while (!cfg_match(f, "\r\n") && !feof(f)) { + while (!lfs_cfg_match(f, "\r\n") && !feof(f)) { fgetc(f); } fgetc(f); @@ -140,15 +146,15 @@ int cfg_create(cfg_t *cfg, const char *filename) { return 0; } -void cfg_destroy(cfg_t *cfg) { +void lfs_cfg_destroy(lfs_cfg_t *cfg) { free(cfg->attrs); } -bool cfg_has(cfg_t *cfg, const char *key) { - return cfg_get(cfg, key, 0); +bool lfs_cfg_has(lfs_cfg_t *cfg, const char *key) { + return lfs_cfg_get(cfg, key, 0); } -const char *cfg_get(cfg_t *cfg, const char *key, const char *def) { +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; @@ -168,8 +174,8 @@ const char *cfg_get(cfg_t *cfg, const char *key, const char *def) { return def; } -ssize_t cfg_geti(cfg_t *cfg, const char *key, ssize_t def) { - const char *val = cfg_get(cfg, key, 0); +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; } @@ -179,8 +185,8 @@ ssize_t cfg_geti(cfg_t *cfg, const char *key, ssize_t def) { return (end == val) ? def : res; } -size_t cfg_getu(cfg_t *cfg, const char *key, size_t def) { - const char *val = cfg_get(cfg, key, 0); +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; } diff --git a/emu/cfg.h b/emubd/lfs_cfg.h similarity index 69% rename from emu/cfg.h rename to emubd/lfs_cfg.h index 8b4780d5b23..ef73f35de75 100644 --- a/emu/cfg.h +++ b/emubd/lfs_cfg.h @@ -1,11 +1,11 @@ /* * Simple config parser * - * Copyright (c) 2016 Christopher Haster + * Copyright (c) 2017 Christopher Haster * Distributed under the MIT license */ -#ifndef CFG_H -#define CFG_H +#ifndef LFS_CFG_H +#define LFS_CFG_H #include #include @@ -26,7 +26,7 @@ // huh: yeah_that's_basically_it # basically it // Internal config structure -typedef struct cfg { +typedef struct lfs_cfg { size_t len; size_t size; @@ -34,41 +34,41 @@ typedef struct cfg { size_t bsize; char *buf; - struct cfg_attr { + struct lfs_cfg_attr { unsigned key; unsigned val; } *attrs; -} cfg_t; +} lfs_cfg_t; // Creates a cfg object and reads in the cfg file from the filename // -// If the cfg_read fails, returns a negative value from the underlying +// If the lfs_cfg_read fails, returns a negative value from the underlying // stdio functions -int cfg_create(cfg_t *cfg, const char *filename); +int lfs_cfg_create(lfs_cfg_t *cfg, const char *filename); // Destroys the cfg object and frees any used memory -void cfg_destroy(cfg_t *cfg); +void lfs_cfg_destroy(lfs_cfg_t *cfg); // Checks if a cfg attribute exists -bool cfg_has(cfg_t *cfg, const char *key); +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 *cfg_get(cfg_t *cfg, const char *key, const char *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 cfg_geti(cfg_t *cfg, const char *name, ssize_t 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 cfg_getu(cfg_t *cfg, const char *name, size_t 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 new file mode 100644 index 00000000000..73730f1aae1 --- /dev/null +++ b/emubd/lfs_emubd.c @@ -0,0 +1,223 @@ +/* + * Block device emulated on standard files + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the MIT license + */ +#include "emubd/lfs_emubd.h" +#include "emubd/lfs_cfg.h" + +#include +#include +#include +#include +#include +#include + + +// Block device emulated on existing filesystem +lfs_error_t lfs_emubd_create(lfs_emubd_t *emu, const char *path) { + memset(&emu->info, 0, sizeof(emu->info)); + + // Allocate buffer for creating children files + size_t pathlen = strlen(path); + emu->path = malloc(pathlen + 1 + LFS_NAME_MAX + 1); + if (!emu->path) { + return -ENOMEM; + } + + 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); + + // Load config, erroring if it doesn't exist + lfs_cfg_t cfg; + int err = lfs_cfg_create(&cfg, emu->path); + if (err) { + return err; + } + + emu->info.read_size = lfs_cfg_getu(&cfg, "read_size", 0); + emu->info.write_size = lfs_cfg_getu(&cfg, "write_size", 0); + emu->info.erase_size = lfs_cfg_getu(&cfg, "erase_size", 0); + emu->info.total_size = lfs_cfg_getu(&cfg, "total_size", 0); + + lfs_cfg_destroy(&cfg); + + return 0; +} + +void lfs_emubd_destroy(lfs_emubd_t *emu) { + free(emu->path); +} + +lfs_error_t lfs_emubd_read(lfs_emubd_t *emu, uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size) { + + // Check if read is valid + if (!(off % emu->info.read_size == 0 && + size % emu->info.read_size == 0 && + ((lfs_lsize_t)ino*emu->info.erase_size + off + size + < emu->info.total_size))) { + return -EINVAL; + } + + // Default to some arbitrary value for debugging + memset(buffer, 0xcc, size); + + // Iterate over blocks until enough data is read + while (size > 0) { + snprintf(emu->child, LFS_NAME_MAX, "%d", ino); + size_t count = lfs_min(emu->info.erase_size - off, size); + + FILE *f = fopen(emu->path, "rb"); + if (!f && errno != ENOENT) { + return -errno; + } + + if (f) { + int err = fseek(f, off, SEEK_SET); + if (err) { + return -errno; + } + + size_t res = fread(buffer, 1, count, f); + if (res < count && !feof(f)) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + } + + size -= count; + buffer += count; + ino += 1; + off = 0; + } + + return 0; +} + +lfs_error_t lfs_emubd_write(lfs_emubd_t *emu, const uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size) { + + // Check if write is valid + if (!(off % emu->info.write_size == 0 && + size % emu->info.write_size == 0 && + ((lfs_lsize_t)ino*emu->info.erase_size + off + size + < emu->info.total_size))) { + return -EINVAL; + } + + // Iterate over blocks until enough data is read + while (size > 0) { + snprintf(emu->child, LFS_NAME_MAX, "%d", ino); + size_t count = lfs_min(emu->info.erase_size - off, size); + + FILE *f = fopen(emu->path, "r+b"); + if (!f && errno == ENOENT) { + f = fopen(emu->path, "w+b"); + if (!f) { + return -errno; + } + } + + int err = fseek(f, off, SEEK_SET); + if (err) { + return -errno; + } + + size_t res = fwrite(buffer, 1, count, f); + if (res < count) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + + size -= count; + buffer += count; + ino += 1; + off = 0; + } + + return 0; +} + +lfs_error_t lfs_emubd_erase(lfs_emubd_t *emu, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size) { + + // Check if erase is valid + if (!(off % emu->info.erase_size == 0 && + size % emu->info.erase_size == 0 && + ((lfs_lsize_t)ino*emu->info.erase_size + off + size + < emu->info.total_size))) { + return -EINVAL; + } + + // Iterate and erase blocks + while (size > 0) { + snprintf(emu->child, LFS_NAME_MAX, "%d", ino); + int err = unlink(emu->path); + if (err && errno != ENOENT) { + return -errno; + } + + size -= emu->info.erase_size; + ino += 1; + off = 0; + } + + return 0; +} + +lfs_error_t lfs_emubd_sync(lfs_emubd_t *emu) { + // Always in sync + return 0; +} + +lfs_error_t lfs_emubd_info(lfs_emubd_t *emu, struct lfs_bd_info *info) { + *info = emu->info; + return 0; +} + + +// Wrappers for void*s +static lfs_error_t lfs_emubd_bd_read(void *bd, uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size) { + return lfs_emubd_read((lfs_emubd_t*)bd, buffer, ino, off, size); +} + +static lfs_error_t lfs_emubd_bd_write(void *bd, const uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size) { + return lfs_emubd_write((lfs_emubd_t*)bd, buffer, ino, off, size); +} + +static lfs_error_t lfs_emubd_bd_erase(void *bd, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size) { + return lfs_emubd_erase((lfs_emubd_t*)bd, ino, off, size); +} + +static lfs_error_t lfs_emubd_bd_sync(void *bd) { + return lfs_emubd_sync((lfs_emubd_t*)bd); +} + +static lfs_error_t lfs_emubd_bd_info(void *bd, struct lfs_bd_info *info) { + return lfs_emubd_info((lfs_emubd_t*)bd, info); +} + +const struct lfs_bd_ops lfs_emubd_ops = { + .read = lfs_emubd_bd_read, + .write = lfs_emubd_bd_write, + .erase = lfs_emubd_bd_erase, + .sync = lfs_emubd_bd_sync, + .info = lfs_emubd_bd_info, +}; + diff --git a/emubd/lfs_emubd.h b/emubd/lfs_emubd.h new file mode 100644 index 00000000000..1304628e0ec --- /dev/null +++ b/emubd/lfs_emubd.h @@ -0,0 +1,57 @@ +/* + * Block device emulated on standard files + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the MIT license + */ +#ifndef LFS_EMUBD_H +#define LFS_EMUBD_H + +#include "lfs_config.h" +#include "lfs_bd.h" + + +// The emu bd state +typedef struct lfs_emubd { + char *path; + char *child; + struct lfs_bd_info info; +} lfs_emubd_t; + + +// Create a block device using path for the directory to store blocks +lfs_error_t lfs_emubd_create(lfs_emubd_t *emu, const char *path); + +// Clean up memory associated with emu block device +void lfs_emubd_destroy(lfs_emubd_t *emu); + +// Read a block +lfs_error_t lfs_emubd_read(lfs_emubd_t *bd, uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size); + +// Program a block +// +// The block must have previously been erased. +lfs_error_t lfs_emubd_write(lfs_emubd_t *bd, const uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size); + +// Erase a block +// +// A block must be erased before being programmed. The +// state of an erased block is undefined. +lfs_error_t lfs_emubd_erase(lfs_emubd_t *bd, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size); + +// Sync the block device +lfs_error_t lfs_emubd_sync(lfs_emubd_t *bd); + +// Get a description of the block device +// +// Any unknown information may be left unmodified +lfs_error_t lfs_emubd_info(lfs_emubd_t *bd, struct lfs_bd_info *info); + +// Block device operations +extern const struct lfs_bd_ops lfs_emubd_ops; + + +#endif diff --git a/lfs_bd.h b/lfs_bd.h new file mode 100644 index 00000000000..3ca7aca50b8 --- /dev/null +++ b/lfs_bd.h @@ -0,0 +1,56 @@ +/* + * Block device interface + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the MIT license + */ +#ifndef LFS_BD_H +#define LFS_BD_H + +#include "lfs_config.h" + + +// Description of block devices +struct lfs_bd_info { + lfs_size_t read_size; // Size of readable block + lfs_size_t write_size; // Size of programmable block + lfs_size_t erase_size; // Size of erase block + + lfs_lsize_t total_size; // Total size of the device +}; + +// Block device operations +// +// The little file system takes in a pointer to an opaque type +// and this struct, all operations are passed the opaque pointer +// which can be used to reference any state associated with the +// block device +struct lfs_bd_ops { + // Read a block + lfs_error_t (*read)(void *bd, uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size); + + // Program a block + // + // The block must have previously been erased. + lfs_error_t (*write)(void *bd, const uint8_t *buffer, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size); + + // Erase a block + // + // A block must be erased before being programmed. The + // state of an erased block is undefined. + lfs_error_t (*erase)(void *bd, + lfs_ino_t ino, lfs_off_t off, lfs_size_t size); + + // Sync the block device + lfs_error_t (*sync)(void *bd); + + // Get a description of the block device + // + // Any unknown information may be left as zero + lfs_error_t (*info)(void *bd, struct lfs_bd_info *info); +}; + + +#endif diff --git a/lfs_config.h b/lfs_config.h new file mode 100644 index 00000000000..ad1d84d9987 --- /dev/null +++ b/lfs_config.h @@ -0,0 +1,37 @@ +/* + * Configuration and type definitions + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the MIT license + */ +#ifndef LFS_CONFIG_H +#define LFS_CONFIG_H + +#include + +// Type definitions +typedef uint64_t lfs_lword_t; +typedef uint32_t lfs_word_t; +typedef uint16_t lfs_hword_t; + +typedef lfs_word_t lfs_size_t; +typedef lfs_word_t lfs_off_t; +typedef int lfs_error_t; + +typedef lfs_lword_t lfs_lsize_t; +typedef lfs_word_t lfs_ino_t; +typedef lfs_hword_t lfs_ioff_t; + +// Maximum length of file name +#define LFS_NAME_MAX 255 + +// Builtin functions +static inline lfs_word_t lfs_max(lfs_word_t a, lfs_word_t b) { + return (a > b) ? a : b; +} + +static inline lfs_word_t lfs_min(lfs_word_t a, lfs_word_t b) { + return (a < b) ? a : b; +} + +#endif