lovr/src/api/l_filesystem.c

526 lines
14 KiB
C
Raw Normal View History

2017-12-10 20:40:37 +00:00
#include "api.h"
2016-11-19 09:28:01 +00:00
#include "filesystem/filesystem.h"
#include "data/blob.h"
2022-03-22 07:13:21 +00:00
#include "util.h"
2021-03-16 00:54:27 +00:00
#include <lua.h>
#include <lauxlib.h>
2019-01-18 16:04:20 +00:00
#include <stdlib.h>
#include <string.h>
2016-11-02 03:27:15 +00:00
2019-12-08 09:46:54 +00:00
void* luax_readfile(const char* filename, size_t* bytesRead) {
return lovrFilesystemRead(filename, -1, bytesRead);
}
2017-04-02 12:55:21 +00:00
// Returns a Blob, leaving stack unchanged. The Blob must be released when finished.
Blob* luax_readblob(lua_State* L, int index, const char* debug) {
if (lua_type(L, index) == LUA_TUSERDATA) {
2017-07-14 14:58:12 +00:00
Blob* blob = luax_checktype(L, index, Blob);
2018-02-26 08:59:03 +00:00
lovrRetain(blob);
2017-04-02 12:55:21 +00:00
return blob;
} else {
const char* path = luaL_checkstring(L, index);
size_t size;
2019-12-08 09:46:54 +00:00
void* data = luax_readfile(path, &size);
2017-04-02 12:55:21 +00:00
if (!data) {
luaL_error(L, "Could not read %s from '%s'", debug, path);
}
return lovrBlobCreate(data, size, path);
}
}
2019-11-25 01:27:17 +00:00
static void pushDirectoryItem(void* context, const char* path) {
lua_State* L = context;
if (path[0] == '.' && (path[1] == '\0' || (path[1] == '.' && path[2] == '\0'))) {
return;
}
lua_pushstring(L, path);
lua_rawget(L, 2);
if (lua_isnil(L, -1)) {
2017-03-11 09:37:00 +00:00
2019-11-25 01:27:17 +00:00
// t[path] = true
lua_pushstring(L, path);
lua_pushboolean(L, true);
lua_rawset(L, 2);
2019-11-25 01:27:17 +00:00
// t[#t + 1] = path
int n = luax_len(L, 2);
lua_pushstring(L, path);
lua_rawseti(L, 2, n + 1);
}
lua_pop(L, 1);
2016-11-02 03:27:15 +00:00
}
2016-11-01 00:14:31 +00:00
static int luax_loadfile(lua_State* L, const char* path, const char* debug) {
2019-11-25 01:27:17 +00:00
size_t size;
2019-12-08 09:46:54 +00:00
void* buffer = luax_readfile(path, &size);
if (!buffer) {
lua_pushnil(L);
lua_pushfstring(L, "Could not load file '%s'", path);
return 2;
}
2019-11-25 01:27:17 +00:00
int status = luaL_loadbuffer(L, buffer, size, debug);
free(buffer);
switch (status) {
case LUA_ERRMEM: return luaL_error(L, "Memory allocation error: %s", lua_tostring(L, -1));
case LUA_ERRSYNTAX: return luaL_error(L, "Syntax error: %s", lua_tostring(L, -1));
default: return 1;
}
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemAppend(lua_State* L) {
2016-11-07 22:31:11 +00:00
const char* path = luaL_checkstring(L, 1);
size_t size;
const char* data;
Blob* blob = luax_totype(L, 2, Blob);
if (blob) {
data = blob->data;
size = blob->size;
} else if (lua_type(L, 2) == LUA_TSTRING) {
data = lua_tolstring(L, 2, &size);
} else {
return luax_typeerror(L, 2, "string or Blob");
}
bool success = lovrFilesystemWrite(path, data, size, true);
lua_pushboolean(L, success);
2016-11-07 22:31:11 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemCreateDirectory(lua_State* L) {
const char* path = luaL_checkstring(L, 1);
2019-05-20 10:51:22 +00:00
lua_pushboolean(L, lovrFilesystemCreateDirectory(path));
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetAppdataDirectory(lua_State* L) {
char buffer[LOVR_PATH_MAX];
2017-03-11 09:37:00 +00:00
if (lovrFilesystemGetAppdataDirectory(buffer, sizeof(buffer))) {
2017-03-11 09:37:00 +00:00
lua_pushstring(L, buffer);
2019-05-20 10:51:22 +00:00
} else {
lua_pushnil(L);
2017-03-11 09:37:00 +00:00
}
return 1;
}
2019-08-21 23:30:20 +00:00
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetDirectoryItems(lua_State* L) {
2017-03-11 09:37:00 +00:00
const char* path = luaL_checkstring(L, 1);
2019-11-25 01:27:17 +00:00
lua_settop(L, 1);
2017-03-11 09:37:00 +00:00
lua_newtable(L);
lovrFilesystemGetDirectoryItems(path, pushDirectoryItem, L);
2019-11-25 01:27:17 +00:00
// Remove all string keys (used for deduplication)
lua_pushnil(L);
while (lua_next(L, 2)) {
lua_pop(L, 1);
if (lua_type(L, -1) == LUA_TSTRING) {
lua_pushvalue(L, -1);
lua_pushnil(L);
lua_rawset(L, 2);
}
}
lua_getglobal(L, "table");
if (lua_type(L, -1) == LUA_TTABLE) {
lua_getfield(L, -1, "sort");
if (lua_type(L, -1) == LUA_TFUNCTION) {
lua_pushvalue(L, 2);
lua_call(L, 1, 0);
} else {
lua_pop(L, 1);
}
}
2019-11-25 01:27:17 +00:00
lua_pop(L, 1);
2017-03-11 09:37:00 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetExecutablePath(lua_State* L) {
char buffer[LOVR_PATH_MAX];
2016-11-01 00:14:31 +00:00
2016-11-07 22:30:32 +00:00
if (lovrFilesystemGetExecutablePath(buffer, sizeof(buffer))) {
lua_pushstring(L, buffer);
2019-11-25 01:27:17 +00:00
} else {
lua_pushnil(L);
2016-11-07 22:30:32 +00:00
}
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetIdentity(lua_State* L) {
const char* identity = lovrFilesystemGetIdentity();
if (identity) {
lua_pushstring(L, identity);
} else {
lua_pushnil(L);
}
2016-11-07 22:30:32 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetLastModified(lua_State* L) {
2017-03-11 09:37:00 +00:00
const char* path = luaL_checkstring(L, 1);
2019-11-25 01:27:17 +00:00
uint64_t lastModified = lovrFilesystemGetLastModified(path);
2017-03-11 09:37:00 +00:00
2019-11-25 01:27:17 +00:00
if (lastModified == ~0ull) {
2017-03-11 09:37:00 +00:00
lua_pushnil(L);
} else {
lua_pushinteger(L, lastModified);
}
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetRealDirectory(lua_State* L) {
2016-11-07 22:30:32 +00:00
const char* path = luaL_checkstring(L, 1);
2016-11-08 21:45:12 +00:00
lua_pushstring(L, lovrFilesystemGetRealDirectory(path));
2016-11-07 22:30:32 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetRequirePath(lua_State* L) {
lua_pushstring(L, lovrFilesystemGetRequirePath());
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetSaveDirectory(lua_State* L) {
2017-03-11 09:37:00 +00:00
lua_pushstring(L, lovrFilesystemGetSaveDirectory());
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetSize(lua_State* L) {
2017-03-11 09:37:00 +00:00
const char* path = luaL_checkstring(L, 1);
2019-11-25 01:27:17 +00:00
uint64_t size = lovrFilesystemGetSize(path);
if (size == ~0ull) {
return luaL_error(L, "File does not exist");
}
lua_pushinteger(L, size);
2017-03-11 09:37:00 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetSource(lua_State* L) {
2017-04-02 12:55:21 +00:00
const char* source = lovrFilesystemGetSource();
if (source) {
lua_pushstring(L, source);
} else {
lua_pushnil(L);
}
2016-11-01 00:14:31 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetUserDirectory(lua_State* L) {
2019-11-25 01:27:17 +00:00
char buffer[LOVR_PATH_MAX];
if (lovrFilesystemGetUserDirectory(buffer, sizeof(buffer))) {
lua_pushstring(L, buffer);
} else {
lua_pushnil(L);
}
2016-11-07 22:30:32 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemGetWorkingDirectory(lua_State* L) {
char buffer[LOVR_PATH_MAX];
if (lovrFilesystemGetWorkingDirectory(buffer, sizeof(buffer))) {
lua_pushstring(L, buffer);
2019-05-20 10:51:22 +00:00
} else {
lua_pushnil(L);
}
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemIsDirectory(lua_State* L) {
2016-11-01 01:35:00 +00:00
const char* path = luaL_checkstring(L, 1);
lua_pushboolean(L, lovrFilesystemIsDirectory(path));
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemIsFile(lua_State* L) {
2016-11-01 01:35:00 +00:00
const char* path = luaL_checkstring(L, 1);
lua_pushboolean(L, lovrFilesystemIsFile(path));
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemIsFused(lua_State* L) {
2017-03-11 09:37:00 +00:00
lua_pushboolean(L, lovrFilesystemIsFused());
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemLoad(lua_State* L) {
2017-03-02 04:08:13 +00:00
const char* path = luaL_checkstring(L, 1);
lua_pushfstring(L, "@%s", path);
const char* debug = lua_tostring(L, -1);
return luax_loadfile(L, path, debug);
2017-03-02 04:08:13 +00:00
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemMount(lua_State* L) {
2017-03-11 09:37:00 +00:00
const char* path = luaL_checkstring(L, 1);
const char* mountpoint = luaL_optstring(L, 2, NULL);
2019-12-08 09:46:54 +00:00
bool append = lua_toboolean(L, 3);
const char* root = luaL_optstring(L, 4, NULL);
2019-05-20 10:51:22 +00:00
lua_pushboolean(L, lovrFilesystemMount(path, mountpoint, append, root));
2017-03-11 09:37:00 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemNewBlob(lua_State* L) {
2017-04-01 23:50:10 +00:00
size_t size;
const char* path = luaL_checkstring(L, 1);
2019-12-08 09:46:54 +00:00
uint8_t* data = luax_readfile(path, &size);
lovrAssert(data, "Could not load file '%s'", path);
Blob* blob = lovrBlobCreate(data, size, path);
luax_pushtype(L, Blob, blob);
2021-02-09 00:52:26 +00:00
lovrRelease(blob, lovrBlobDestroy);
2017-04-01 23:50:10 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemRead(lua_State* L) {
2016-11-05 22:55:01 +00:00
const char* path = luaL_checkstring(L, 1);
lua_Integer luaSize = luaL_optinteger(L, 2, -1);
size_t size = MAX(luaSize, -1);
size_t bytesRead;
void* content = lovrFilesystemRead(path, size, &bytesRead);
if (!content) {
lua_pushnil(L);
return 1;
}
lua_pushlstring(L, content, bytesRead);
lua_pushinteger(L, bytesRead);
2016-11-08 06:23:13 +00:00
free(content);
return 2;
2016-11-05 22:55:01 +00:00
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemRemove(lua_State* L) {
2017-03-11 09:37:00 +00:00
const char* path = luaL_checkstring(L, 1);
2019-05-20 10:51:22 +00:00
lua_pushboolean(L, lovrFilesystemRemove(path));
2017-03-11 09:37:00 +00:00
return 1;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemSetIdentity(lua_State* L) {
2019-11-25 01:27:17 +00:00
const char* identity = luaL_checkstring(L, 1);
bool precedence = lua_toboolean(L, 2);
lovrFilesystemSetIdentity(identity, precedence);
2016-11-07 22:30:32 +00:00
return 0;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemSetRequirePath(lua_State* L) {
lovrFilesystemSetRequirePath(luaL_checkstring(L, 1));
return 0;
}
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemUnmount(lua_State* L) {
2017-03-11 09:37:00 +00:00
const char* path = luaL_checkstring(L, 1);
2019-05-20 10:51:22 +00:00
lua_pushboolean(L, lovrFilesystemUnmount(path));
2016-11-01 00:14:31 +00:00
return 1;
}
2016-11-07 22:31:02 +00:00
2018-09-27 01:27:38 +00:00
static int l_lovrFilesystemWrite(lua_State* L) {
2016-11-07 22:31:02 +00:00
const char* path = luaL_checkstring(L, 1);
size_t size;
const char* data;
Blob* blob = luax_totype(L, 2, Blob);
if (blob) {
data = blob->data;
size = blob->size;
} else if (lua_type(L, 2) == LUA_TSTRING) {
data = lua_tolstring(L, 2, &size);
} else {
return luax_typeerror(L, 2, "string or Blob");
}
bool success = lovrFilesystemWrite(path, data, size, false);
lua_pushboolean(L, success);
2016-11-07 22:31:02 +00:00
return 1;
}
2017-03-11 11:08:07 +00:00
2018-09-27 01:27:38 +00:00
static const luaL_Reg lovrFilesystem[] = {
2017-03-11 11:08:07 +00:00
{ "append", l_lovrFilesystemAppend },
{ "createDirectory", l_lovrFilesystemCreateDirectory },
2017-03-11 11:08:07 +00:00
{ "getAppdataDirectory", l_lovrFilesystemGetAppdataDirectory },
{ "getDirectoryItems", l_lovrFilesystemGetDirectoryItems },
{ "getExecutablePath", l_lovrFilesystemGetExecutablePath },
{ "getIdentity", l_lovrFilesystemGetIdentity },
{ "getLastModified", l_lovrFilesystemGetLastModified },
{ "getRealDirectory", l_lovrFilesystemGetRealDirectory },
{ "getRequirePath", l_lovrFilesystemGetRequirePath },
2017-03-11 11:08:07 +00:00
{ "getSaveDirectory", l_lovrFilesystemGetSaveDirectory },
{ "getSize", l_lovrFilesystemGetSize },
{ "getSource", l_lovrFilesystemGetSource },
{ "getUserDirectory", l_lovrFilesystemGetUserDirectory },
{ "getWorkingDirectory", l_lovrFilesystemGetWorkingDirectory },
2017-03-11 11:08:07 +00:00
{ "isDirectory", l_lovrFilesystemIsDirectory },
{ "isFile", l_lovrFilesystemIsFile },
{ "isFused", l_lovrFilesystemIsFused },
{ "load", l_lovrFilesystemLoad },
{ "mount", l_lovrFilesystemMount },
2017-04-01 23:50:10 +00:00
{ "newBlob", l_lovrFilesystemNewBlob },
2017-03-11 11:08:07 +00:00
{ "read", l_lovrFilesystemRead },
{ "remove", l_lovrFilesystemRemove },
{ "setRequirePath", l_lovrFilesystemSetRequirePath },
2017-03-11 11:08:07 +00:00
{ "setIdentity", l_lovrFilesystemSetIdentity },
{ "unmount", l_lovrFilesystemUnmount },
2017-03-11 11:08:07 +00:00
{ "write", l_lovrFilesystemWrite },
{ NULL, NULL }
};
2018-09-27 01:27:38 +00:00
static int luaLoader(lua_State* L) {
const char* module = lua_tostring(L, 1);
const char* p = lovrFilesystemGetRequirePath();
char buffer[1024] = { '@' };
char* debug = &buffer[0];
char* filename = &buffer[1];
char* f = filename;
size_t n = sizeof(buffer) - 1;
// Loop over the require path, character by character, and:
// - Replace question marks with the module that's being required, converting '.' to '/'.
// - If there's a semicolon/eof, treat it as the end of a potential filename and try to load it.
// The filename buffer has an '@' before it so it can also be used as the debug label for the Lua
// chunk without additional memory allocation.
while (1) {
if (*p == ';' || *p == '\0') {
*f = '\0';
if (lovrFilesystemIsFile(filename)) {
return luax_loadfile(L, filename, debug);
}
if (*p == '\0') {
break;
} else {
p++;
f = filename;
n = sizeof(buffer) - 1;
}
} else if (*p == '?') {
for (const char* m = module; n && *m; n--, m++) {
*f++ = *m == '.' ? '/' : *m;
}
p++;
} else {
*f++ = *p++;
n--;
}
lovrAssert(n > 0, "Tried to require a filename that was too long (%s)", module);
}
return 0;
}
2021-05-01 00:11:28 +00:00
static int libLoaderCommon(lua_State* L, bool allInOneFlag) {
#ifdef _WIN32
const char* extension = ".dll";
#else
const char* extension = ".so";
#endif
const char* module = lua_tostring(L, 1);
const char* hyphen = strchr(module, '-');
const char* symbol = hyphen ? hyphen + 1 : module;
char path[1024];
// On Android, load libraries directly from the apk by passing a path like this to the linker:
// /path/to/app.apk!/lib/arm64-v8a/lib.so
// On desktop systems, look for libraries next to the executable
#ifdef __ANDROID__
const char* source = lovrFilesystemGetSource();
size_t length = strlen(source);
memcpy(path, source, length);
const char* subpath = "!/lib/arm64-v8a/";
size_t subpathLength = strlen(subpath);
char* p = path + length;
if (length + subpathLength >= sizeof(path)) {
return 0;
}
memcpy(p, subpath, subpathLength);
length += subpathLength;
p += subpathLength;
#else
size_t length = lovrFilesystemGetExecutablePath(path, sizeof(path));
if (length == 0) {
return 0;
}
char* slash = strrchr(path, LOVR_PATH_SEP);
char* p = slash ? slash + 1 : path;
length = p - path;
#endif
for (const char* m = module; *m && length < sizeof(path); m++, length++) {
2021-05-01 00:11:28 +00:00
if (allInOneFlag && *m == '.') break;
*p++ = *m == '.' ? LOVR_PATH_SEP : *m;
}
for (const char* e = extension; *e && length < sizeof(path); e++, length++) {
*p++ = *e;
}
if (length >= sizeof(path)) {
return 0;
}
*p = '\0';
lua_getglobal(L, "package");
lua_getfield(L, -1, "loadlib");
lua_pushlstring(L, path, length);
// Synthesize luaopen_<module> symbol
luaL_Buffer buffer;
luaL_buffinit(L, &buffer);
luaL_addstring(&buffer, "luaopen_");
for (const char* s = symbol; *s; s++) {
luaL_addchar(&buffer, *s == '.' ? '_' : *s);
}
luaL_pushresult(&buffer);
lua_call(L, 2, 1);
return 1;
}
2021-05-01 00:11:28 +00:00
static int libLoader(lua_State* L) {
bool allInOneFlag = false;
return libLoaderCommon(L, allInOneFlag);
}
static int libLoaderAllInOne(lua_State* L) {
bool allInOneFlag = true;
return libLoaderCommon(L, allInOneFlag);
}
int luaopen_lovr_filesystem(lua_State* L) {
2021-02-25 17:45:45 +00:00
const char* archive = NULL;
2018-09-27 01:27:38 +00:00
lua_getglobal(L, "arg");
if (lua_istable(L, -1)) {
lua_rawgeti(L, -1, 0);
2021-02-25 17:45:45 +00:00
archive = lua_tostring(L, -1);
lua_pop(L, 1);
2021-02-25 17:45:45 +00:00
}
lua_pop(L, 1);
if (lovrFilesystemInit(archive)) {
luax_atexit(L, lovrFilesystemDestroy);
}
2018-09-27 01:27:38 +00:00
lua_newtable(L);
2020-08-19 19:12:06 +00:00
luax_register(L, lovrFilesystem);
luax_registerloader(L, luaLoader, 2);
luax_registerloader(L, libLoader, 3);
2021-05-01 00:11:28 +00:00
luax_registerloader(L, libLoaderAllInOne, 4);
2018-09-27 01:27:38 +00:00
return 1;
}