diff --git a/Makefile b/Makefile index 4303537..4f8b79a 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ size: $(OBJ) $(SIZE) -t $^ .SUFFIXES: -test: test_format test_dirs test_files test_alloc test_paths test_orphan +test: test_format test_dirs test_files test_seek test_alloc test_paths test_orphan test_%: tests/test_%.sh ./$< diff --git a/lfs.c b/lfs.c index eca399e..5507362 100644 --- a/lfs.c +++ b/lfs.c @@ -545,8 +545,8 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { while (true) { - if ((0x7fffffff & dir->d.size) - dir->off < sizeof(entry->d)) { - if (!(dir->d.size >> 31)) { + if (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)) { + if (!(0x80000000 & dir->d.size)) { entry->pair[0] = dir->pair[0]; entry->pair[1] = dir->pair[1]; entry->off = dir->off; @@ -559,6 +559,7 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { } dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d); continue; } @@ -569,6 +570,7 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { } dir->off += entry->d.len; + dir->pos += entry->d.len; if ((0xff & entry->d.type) == LFS_TYPE_REG || (0xff & entry->d.type) == LFS_TYPE_DIR) { entry->pair[0] = dir->pair[0]; @@ -714,9 +716,14 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { int err = lfs_dir_fetch(lfs, dir, dir->pair); if (err) { return err; - } else if (strcmp(path, "/") == 0) { - // special offset for '.' and '..' - dir->off = sizeof(dir->d) - 2; + } + + if (strspn(path, "/.") == strlen(path)) { + // can only be something like '/././../.' + dir->head[0] = dir->pair[0]; + dir->head[1] = dir->pair[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); return 0; } @@ -733,8 +740,12 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { return err; } + // setup head dir // special offset for '.' and '..' - dir->off = sizeof(dir->d) - 2; + dir->head[0] = dir->pair[0]; + dir->head[1] = dir->pair[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); return 0; } @@ -746,15 +757,16 @@ int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { memset(info, 0, sizeof(*info)); - if (dir->off == sizeof(dir->d) - 2) { + // special offset for '.' and '..' + if (dir->pos == sizeof(dir->d) - 2) { info->type = LFS_TYPE_DIR; strcpy(info->name, "."); - dir->off += 1; + dir->pos += 1; return 1; - } else if (dir->off == sizeof(dir->d) - 1) { + } else if (dir->pos == sizeof(dir->d) - 1) { info->type = LFS_TYPE_DIR; strcpy(info->name, ".."); - dir->off += 1; + dir->pos += 1; return 1; } @@ -778,6 +790,48 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { return 1; } +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + // simply walk from head dir + int err = lfs_dir_rewind(lfs, dir); + if (err) { + return err; + } + dir->pos = off; + + while (off > (0x7fffffff & dir->d.size)) { + off -= 0x7fffffff & dir->d.size; + if (!(0x80000000 & dir->d.size)) { + return LFS_ERROR_INVALID; + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } + + dir->off = off; + return 0; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + return dir->pos; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + // reload the head dir + int err = lfs_dir_fetch(lfs, dir, dir->head); + if (err) { + return err; + } + + dir->pair[0] = dir->head[0]; + dir->pair[1] = dir->head[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + return 0; +} + /// Index list operations /// static int lfs_index(lfs_t *lfs, lfs_off_t *off) { @@ -975,7 +1029,7 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { return lfs_file_sync(lfs, file); } -int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { if (file->wblock == 0) { // already in sync, may be rdonly return 0; @@ -1008,10 +1062,23 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { file->rblock = 0; file->wpos = oldwpos; file->wblock = 0; + return 0; +} + +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + if (file->wblock == 0) { + // already in sync, may be rdonly + return 0; + } + + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } // update dir entry lfs_dir_t cwd; - int err = lfs_dir_fetch(lfs, &cwd, file->entry.pair); + err = lfs_dir_fetch(lfs, &cwd, file->entry.pair); if (err) { return err; } @@ -1024,9 +1091,24 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const uint8_t *data = buffer; lfs_size_t nsize = size; + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERROR_INVALID; + } + while (nsize > 0) { // check if we need a new block if (!file->wblock || file->woff == lfs->cfg->block_size) { + if (!file->wblock) { + // find out which block we're extending from + int err = lfs_index_find(lfs, + file->entry.d.u.file.head, file->entry.d.u.file.size, + file->wpos, &file->wblock, &file->woff); + if (err) { + return err; + } + } + + // extend file with new blocks int err = lfs_index_extend(lfs, file->wblock, file->wpos, &file->wblock, &file->woff); if (err) { @@ -1068,6 +1150,10 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, size = lfs_min(size, file->entry.d.u.file.size - file->rpos); lfs_size_t nsize = size; + if ((file->flags & 3) == LFS_O_WRONLY) { + return LFS_ERROR_INVALID; + } + while (nsize > 0) { // check if we need a new block if (!file->rblock || file->roff == lfs->cfg->block_size) { @@ -1095,6 +1181,57 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, return size; } +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // rpos is always correct pos, even in append mode + // TODO keep rpos and wpos together? + lfs_off_t prev = file->rpos; + file->rblock = 0; + switch (whence) { + case LFS_SEEK_SET: + file->rpos = off; + break; + + case LFS_SEEK_CUR: + file->rpos = file->rpos + off; + break; + + case LFS_SEEK_END: + file->rpos = file->entry.d.u.file.size + off; + break; + } + + if (!(file->flags & LFS_O_APPEND)) { + file->wpos = file->rpos; + file->wblock = 0; + } + + return prev; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + return lfs_max(file->wpos, file->entry.d.u.file.size); +} + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + return file->rpos; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return res; + } + + return 0; +} + /// Generic filesystem operations /// static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { diff --git a/lfs.h b/lfs.h index db81e31..f091a66 100644 --- a/lfs.h +++ b/lfs.h @@ -40,6 +40,12 @@ enum lfs_open_flags { LFS_O_SYNC = 0x200, }; +enum lfs_whence_flags { + LFS_SEEK_SET = 0, + LFS_SEEK_CUR = 1, + LFS_SEEK_END = 2, +}; + // Configuration provided during initialization of the littlefs struct lfs_config { @@ -138,6 +144,9 @@ typedef struct lfs_dir { lfs_block_t pair[2]; lfs_off_t off; + lfs_block_t head[2]; + lfs_off_t pos; + struct lfs_disk_dir { uint32_t rev; lfs_size_t size; @@ -146,7 +155,7 @@ typedef struct lfs_dir { } lfs_dir_t; typedef struct lfs_superblock { - lfs_block_t dir[2]; //TODO rm me? + lfs_block_t pair[2]; lfs_off_t off; struct lfs_disk_superblock { @@ -195,6 +204,9 @@ int lfs_mkdir(lfs_t *lfs, const char *path); int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags); @@ -204,6 +216,11 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size); lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size); +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); int lfs_deorphan(lfs_t *lfs); int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); diff --git a/tests/test_seek.sh b/tests/test_seek.sh new file mode 100755 index 0000000..b385e8b --- /dev/null +++ b/tests/test_seek.sh @@ -0,0 +1,281 @@ +#!/bin/bash +set -eu + +SMALLSIZE=4 +MEDIUMSIZE=128 +LARGESIZE=132 + +echo "=== Seek tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hello") => 0; + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "hello/kitty%d", i); + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < $LARGESIZE; j++) { + lfs_file_write(&lfs, &file[0], buffer, size); + } + + lfs_file_close(&lfs, &file[0]) => 0; + } + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Simple dir seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + + lfs_soff_t pos; + int i; + for (i = 0; i < $SMALLSIZE; i++) { + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + pos = lfs_dir_tell(&lfs, &dir[0]); + } + pos >= 0 => 1; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_rewind(&lfs, &dir[0]) => 0; + sprintf((char*)buffer, "kitty%d", 0); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Large dir seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + + lfs_soff_t pos; + int i; + for (i = 0; i < $MEDIUMSIZE; i++) { + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + pos = lfs_dir_tell(&lfs, &dir[0]); + } + pos >= 0 => 1; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_rewind(&lfs, &dir[0]) => 0; + sprintf((char*)buffer, "kitty%d", 0); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Simple file seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $SMALLSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Large file seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $MEDIUMSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Simple file seek and write ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $SMALLSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + memcpy(buffer, "doggodogdog", size); + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_write(&lfs, &file[0], buffer, size) => size; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Large file seek and write ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $MEDIUMSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + if (i != $SMALLSIZE) { + memcmp(buffer, "kittycatcat", size) => 0; + } + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + memcpy(buffer, "doggodogdog", size); + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_write(&lfs, &file[0], buffer, size) => size; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py