diff --git a/src/filesystem.c b/src/filesystem.c new file mode 100644 index 0000000..5e345dd --- /dev/null +++ b/src/filesystem.c @@ -0,0 +1,460 @@ +/** + * Copyright (c) 2016 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include + +#include "luaobj.h" +#include "lib/microtar/microtar.h" +#include "lib/dmt/dmt.h" + +#include "filesystem.h" + +#define MAX_MOUNTS 8 +#define MAX_PATH 256 + +enum { + FILESYSTEM_TNONE, + FILESYSTEM_TREG, + FILESYSTEM_TDIR, +}; + +typedef struct Mount Mount; + +struct Mount { + void (*unmount)(Mount *mnt); + int (*exists)(Mount *mnt, const char *filename); + int (*isFile)(Mount *mnt, const char *filename); + int (*isDirectory)(Mount *mnt, const char *filename); + void *(*read)(Mount *mnt, const char *filename, int *size); + void *udata; + char path[MAX_PATH]; +}; + +int filesystem_mountIdx; +Mount filesystem_mounts[MAX_MOUNTS]; + +#define FOREACH_MOUNT(var)\ + for (Mount *var = &filesystem_mounts[filesystem_mountIdx - 1];\ + var >= filesystem_mounts;\ + var--) + + +static int get_file_type(const char *filename) { + /* The use of `stat` is intentionally avoided here, a stat call seems to + * block for a long time on DOS -- over 500ms in Dosbox at 26800 cycles */ + DIR *dir = opendir(filename); + if (dir) { + closedir(dir); + return FILESYSTEM_TDIR; + } + FILE *fp = fopen(filename, "rb"); + if (fp) { + fclose(fp); + return FILESYSTEM_TREG; + } + return FILESYSTEM_TNONE; +} + + +static int concat_path(char *dst, const char *dir, const char *filename) { + int dirlen = strlen(dir); + int filenamelen = strlen(filename); + + /* Fail if the resultant path would overflow buffer */ + if (dirlen + filenamelen + 2 > MAX_PATH) { + return FILESYSTEM_EFAILURE; + } + + /* Write full name to buffer and return ok */ + if ( dir[dirlen - 1] == '/' ) { + sprintf(dst, "%s%s", dir, filename); + } else { + sprintf(dst, "%s/%s", dir, filename); + } + return FILESYSTEM_ESUCCESS; +} + + +static int concat_and_get_file_type(const char *dir, const char *filename) { + char buf[MAX_PATH]; + /* Make fullpath */ + int err = concat_path(buf, dir, filename); + if (err) { + return err; + } + /* Stat */ + return get_file_type(buf); +} + + +/*==================*/ +/* Directory Mount */ +/*==================*/ + +static void dir_unmount(Mount *mnt) { + /* Intentionally empty */ +} + + +static int dir_exists(Mount *mnt, const char *filename) { + return concat_and_get_file_type(mnt->path, filename) != FILESYSTEM_TNONE; +} + + +static int dir_isFile(Mount *mnt, const char *filename) { + return concat_and_get_file_type(mnt->path, filename) == FILESYSTEM_TREG; +} + + +static int dir_isDirectory(Mount *mnt, const char *filename) { + return concat_and_get_file_type(mnt->path, filename) == FILESYSTEM_TDIR; +} + + +static void* dir_read(Mount *mnt, const char *filename, int *size) { + char buf[MAX_PATH]; + /* Make fullpath */ + int err = concat_path(buf, mnt->path, filename); + if (err) { + return NULL; + } + /* Open file */ + FILE *fp = fopen(buf, "rb"); + if (!fp) { + return NULL; + } + /* Get size */ + fseek(fp, 0, SEEK_END); + *size = ftell(fp); + fseek(fp, 0, SEEK_SET); + /* Load data */ + void *p = dmt_malloc(*size); + if (!p) { + return NULL; + } + fread(p, 1, *size, fp); + return p; +} + + +static int dir_mount(Mount *mnt, const char *path) { + /* Check the path is actually a directory */ + if ( get_file_type(path) != FILESYSTEM_TDIR ) { + return FILESYSTEM_EFAILURE; + } + + /* Init mount */ + mnt->udata = NULL; + mnt->unmount = dir_unmount; + mnt->exists = dir_exists; + mnt->isFile = dir_isFile; + mnt->isDirectory = dir_isDirectory; + mnt->read = dir_read; + + /* Return ok */ + return FILESYSTEM_ESUCCESS; +} + + +/*==================*/ +/* Tar Mount */ +/*==================*/ + +static int tar_find(mtar_t *tar, const char *filename, mtar_header_t *h) { + int err; + char buf[MAX_PATH + 1]; + /* Try to match */ + err = mtar_find(tar, filename, h); + if (err != MTAR_ENOTFOUND) { + return err; + } + /* Try to match with "/" at the end */ + strcpy(buf, filename); + strcat(buf, "/"); + return mtar_find(tar, buf, h); +} + + +static void tar_unmount(Mount *mnt) { + mtar_t *tar = mnt->udata; + mtar_close(tar); + dmt_free(tar); +} + + +static int tar_exists(Mount *mnt, const char *filename) { + mtar_t *tar = mnt->udata; + return tar_find(tar, filename, NULL) == MTAR_ESUCCESS; +} + + +static int tar_isFile(Mount *mnt, const char *filename) { + mtar_t *tar = mnt->udata; + int err; + mtar_header_t h; + err = mtar_find(tar, filename, &h); + if (err) { + return 0; + } + return h.type == MTAR_TREG; +} + + +static int tar_isDirectory(Mount *mnt, const char *filename) { + mtar_t *tar = mnt->udata; + int err; + mtar_header_t h; + err = tar_find(tar, filename, &h); + if (err) { + return 0; + } + return h.type == MTAR_TDIR; +} + + +static void* tar_read(Mount *mnt, const char *filename, int *size) { + mtar_t *tar = mnt->udata; + int err; + mtar_header_t h; + + /* Find and load header for file */ + err = mtar_find(tar, filename, &h); + if (err) { + return 0; + } + + /* Allocate and read data, set size and return */ + char *p = dmt_malloc(h.size); + err = mtar_read_data(tar, p, h.size); + if (err) { + dmt_free(p); + return NULL; + } + *size = h.size; + return p; +} + + +static int tar_mount(Mount *mnt, const char *path) { + mtar_t *tar = dmt_malloc( sizeof(*tar) ); + + /* Try to init tar */ + int err = mtar_open(tar, path, "r"); + if (err) { + dmt_free(tar); + return FILESYSTEM_EFAILURE; + } + + /* Init mount */ + mnt->udata = tar; + mnt->unmount = tar_unmount; + mnt->exists = tar_exists; + mnt->isFile = tar_isFile; + mnt->isDirectory = tar_isDirectory; + mnt->read = tar_read; + + /* Return ok */ + return FILESYSTEM_ESUCCESS; +} + + +/*==================*/ +/* Filesystem */ +/*==================*/ + +const char* filesystem_strerror(int err) { + switch (err) { + case FILESYSTEM_ESUCCESS: return "success"; + case FILESYSTEM_EFAILURE: return "failure"; + } + return "unknown error"; +} + + +void filesystem_deinit(void) { + FOREACH_MOUNT(mnt) { + mnt->unmount(mnt); + } + filesystem_mountIdx = 0; +} + + +int filesystem_mount(const char *path) { + /* Check path length is ok */ + if ( strlen(path) >= MAX_PATH ) { + return FILESYSTEM_EFAILURE; + } + /* Check path isn't already mounted */ + FOREACH_MOUNT(m) { + if ( !strcmp(m->path, path) ) { + return FILESYSTEM_EFAILURE; + } + } + + /* Get mount slot */ + if (filesystem_mountIdx >= MAX_MOUNTS) { + return FILESYSTEM_EFAILURE; + } + Mount *mnt = &filesystem_mounts[filesystem_mountIdx++]; + + /* Copy path name */ + strcpy(mnt->path, path); + + /* Try to mount path */ + if ( tar_mount(mnt, path) == FILESYSTEM_ESUCCESS ) goto success; + if ( dir_mount(mnt, path) == FILESYSTEM_ESUCCESS ) goto success; + + /* Fail */ + filesystem_mountIdx--; + return FILESYSTEM_EFAILURE; + +success: + return FILESYSTEM_ESUCCESS; +} + + +int filesystem_unmount(const char *path) { + FOREACH_MOUNT(mnt) { + if ( !strcmp(mnt->path, path) ) { + /* Unmount */ + mnt->unmount(mnt); + /* Shift remaining mounts to fill gap and decrement idx */ + int idx = mnt - filesystem_mounts; + memmove(mnt, mnt + 1, (filesystem_mountIdx - idx - 1) * sizeof(Mount)); + filesystem_mountIdx--; + return FILESYSTEM_ESUCCESS; + } + } + return FILESYSTEM_EFAILURE; +} + + +int filesystem_exists(const char *filename) { + FOREACH_MOUNT(mnt) { + if ( mnt->exists(mnt, filename) ) { + return 1; + } + } + return 0; +} + + +int filesystem_isFile(const char *filename) { + FOREACH_MOUNT(mnt) { + if ( mnt->exists(mnt, filename) ) { + return mnt->isFile(mnt, filename); + } + } + return 0; +} + + +int filesystem_isDirectory(const char *filename) { + FOREACH_MOUNT(mnt) { + if ( mnt->exists(mnt, filename) ) { + return mnt->isDirectory(mnt, filename); + } + } + return 0; +} + + +void* filesystem_read(const char *filename, int *size) { + FOREACH_MOUNT(mnt) { + if ( mnt->exists(mnt, filename) && mnt->isFile(mnt, filename) ) { + return mnt->read(mnt, filename, size); + } + } + return NULL; +} + + +void filesystem_free(void *ptr) { + dmt_free(ptr); +} + + +/*==================*/ +/* Lua Binds */ +/*==================*/ + +int l_filesystem_mount(lua_State *L) { + const char *path = luaL_checkstring(L, 1); + int err = filesystem_mount(path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, filesystem_strerror(err)); + return 2; + } + lua_pushboolean(L, 1); + return 1; +} + + +int l_filesystem_unmount(lua_State *L) { + const char *path = luaL_checkstring(L, 1); + int err = filesystem_unmount(path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, filesystem_strerror(err)); + return 2; + } + lua_pushboolean(L, 1); + return 1; +} + + +int l_filesystem_exists(lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + lua_pushboolean( L, filesystem_exists(filename) ); + return 1; +} + + +int l_filesystem_isFile(lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + lua_pushboolean( L, filesystem_isFile(filename) ); + return 1; +} + + +int l_filesystem_isDirectory(lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + lua_pushboolean( L, filesystem_isDirectory(filename) ); + return 1; +} + + +int l_filesystem_read(lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + int size; + void *data = filesystem_read(filename, &size); + if (!data) { + luaL_error(L, "could not read file"); + } + lua_pushlstring(L, data, size); + filesystem_free(data); + return 1; +} + + +int luaopen_filesystem(lua_State *L) { + luaL_Reg reg[] = { + { "mount", l_filesystem_mount }, + { "unmount", l_filesystem_unmount }, + { "exists", l_filesystem_exists }, + { "isFile", l_filesystem_isFile }, + { "isDirectory", l_filesystem_isDirectory }, + { "read", l_filesystem_read }, + { 0, 0 }, + }; + luaL_newlib(L, reg); + return 1; +} diff --git a/src/filesystem.h b/src/filesystem.h new file mode 100644 index 0000000..cb68a4c --- /dev/null +++ b/src/filesystem.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2016 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include +#include + +enum { + FILESYSTEM_ESUCCESS = 0, + FILESYSTEM_EFAILURE = -1, +}; + +const char* filesystem_strerror(int err); +void filesystem_deinit(void); +int filesystem_mount(const char *path); +int filesystem_unmount(const char *path); +int filesystem_exists(const char *filename); +int filesystem_isFile(const char *filename); +int filesystem_isDirectory(const char *filename); +void* filesystem_read(const char *filename, int *size); +void filesystem_free(void *ptr); + +#endif diff --git a/src/love.c b/src/love.c index 286056a..af31aa0 100644 --- a/src/love.c +++ b/src/love.c @@ -20,6 +20,7 @@ int luaopen_image(lua_State *L); int luaopen_quad(lua_State *L); int luaopen_font(lua_State *L); int luaopen_system(lua_State *L); +int luaopen_filesystem(lua_State *L); int luaopen_graphics(lua_State *L); int luaopen_timer(lua_State *L); int luaopen_keyboard(lua_State *L); @@ -50,11 +51,12 @@ int luaopen_love(lua_State *L) { /* Init submodules */ struct { char *name; int (*fn)(lua_State *L); } mods[] = { - { "system", luaopen_system }, - { "graphics", luaopen_graphics }, - { "timer", luaopen_timer }, - { "keyboard", luaopen_keyboard }, - { "mouse", luaopen_mouse }, + { "system", luaopen_system }, + { "filesystem", luaopen_filesystem }, + { "graphics", luaopen_graphics }, + { "timer", luaopen_timer }, + { "keyboard", luaopen_keyboard }, + { "mouse", luaopen_mouse }, { 0 }, }; for (i = 0; mods[i].name; i++) { diff --git a/src/main.c b/src/main.c index 94ab9e9..d8dc0c8 100644 --- a/src/main.c +++ b/src/main.c @@ -15,6 +15,7 @@ #include "vga.h" #include "luaobj.h" #include "keyboard.h" +#include "filesystem.h" #include "mouse.h" #include "image.h" #include "palette.h" @@ -27,9 +28,11 @@ static void deinit(void) { vga_deinit(); keyboard_deinit(); lua_close(L); + filesystem_deinit(); dmt_dump(stdout); } + static int onLuaPanic(lua_State *L) { vga_deinit(); const char *err = lua_tostring(L, -1); @@ -44,6 +47,7 @@ int main(void) { /* Init everything */ atexit(deinit); + filesystem_mount("."); /* Mount cwd: temporary */ vga_init(); palette_init(); keyboard_init();