diff options
author | Christopher Haster <chaster@utexas.edu> | 2017-02-25 23:31:14 +0300 |
---|---|---|
committer | Christopher Haster <chaster@utexas.edu> | 2017-02-25 23:31:14 +0300 |
commit | 02156cb47d64b2a4b3281271305b8a2a63c660ea (patch) | |
tree | 438f5724f3f405d327e2d14ebc474417ecceaa77 /emubd | |
parent | b113bba3ae2eb8e09481eeeab44f47bfa076db8a (diff) |
Initial commit of block device interface and emulated block device
Diffstat (limited to 'emubd')
-rw-r--r-- | emubd/lfs_cfg.c | 197 | ||||
-rw-r--r-- | emubd/lfs_cfg.h | 74 | ||||
-rw-r--r-- | emubd/lfs_emubd.c | 223 | ||||
-rw-r--r-- | emubd/lfs_emubd.h | 57 |
4 files changed, 551 insertions, 0 deletions
diff --git a/emubd/lfs_cfg.c b/emubd/lfs_cfg.c new file mode 100644 index 0000000..82dd624 --- /dev/null +++ b/emubd/lfs_cfg.c @@ -0,0 +1,197 @@ +/* + * Simple config parser + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the MIT license + */ +#include "emubd/lfs_cfg.h" + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> + + +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 new file mode 100644 index 0000000..ef73f35 --- /dev/null +++ b/emubd/lfs_cfg.h @@ -0,0 +1,74 @@ +/* + * Simple config parser + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the MIT license + */ +#ifndef LFS_CFG_H +#define LFS_CFG_H + +#include <stddef.h> +#include <stdbool.h> +#include <unistd.h> + +// 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 new file mode 100644 index 0000000..73730f1 --- /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 <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> +#include <dirent.h> + + +// 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 0000000..1304628 --- /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 |