project64/Source/Project64/UserInterface/Debugger/ScriptAPI/ScriptAPI_fs.cpp

570 lines
13 KiB
C++

#include <stdafx.h>
#include "ScriptAPI.h"
#include <sys/stat.h>
#pragma warning(disable: 4702) // disable unreachable code warning
enum fsop { FS_READ, FS_WRITE };
static duk_ret_t ReadWriteImpl(duk_context *ctx, fsop op); // (fd, buffer, offset, length, position)
static duk_ret_t js__FileFinalizer(duk_context *ctx);
void ScriptAPI::Define_fs(duk_context *ctx)
{
// todo DukPutProps DukClass
const duk_function_list_entry funcs[] = {
{ "open", js_fs_open, DUK_VARARGS },
{ "close", js_fs_close, DUK_VARARGS },
{ "write", js_fs_write, DUK_VARARGS },
{ "writefile", js_fs_writefile, DUK_VARARGS },
{ "read", js_fs_read, DUK_VARARGS },
{ "readfile", js_fs_readfile, DUK_VARARGS },
{ "exists", js_fs_exists, DUK_VARARGS },
{ "fstat", js_fs_fstat, DUK_VARARGS },
{ "stat", js_fs_stat, DUK_VARARGS },
{ "unlink", js_fs_unlink, DUK_VARARGS },
{ "mkdir", js_fs_mkdir, DUK_VARARGS },
{ "rmdir", js_fs_rmdir, DUK_VARARGS },
{ "readdir", js_fs_readdir, DUK_VARARGS },
{ "Stats", js_fs_Stats__constructor, DUK_VARARGS },
{ nullptr, nullptr, 0 }
};
const duk_function_list_entry Stats_funcs[] = {
{ "isFile", js_fs_Stats_isFile, DUK_VARARGS },
{ "isDirectory", js_fs_Stats_isDirectory, DUK_VARARGS },
{ nullptr, nullptr, 0 }
};
duk_push_global_object(ctx);
duk_push_string(ctx, "fs");
duk_push_object(ctx);
duk_put_function_list(ctx, -1, funcs);
duk_get_prop_string(ctx, -1, "Stats");
duk_push_object(ctx);
duk_put_function_list(ctx, -1, Stats_funcs);
duk_put_prop_string(ctx, -2, "prototype");
duk_pop(ctx);
duk_freeze(ctx, -1);
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_SET_ENUMERABLE);
duk_pop(ctx);
}
duk_ret_t ScriptAPI::js_fs_open(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String, Arg_String });
const char* path = duk_get_string(ctx, 0);
const char* mode = duk_get_string(ctx, 1);
bool bModeValid = false;
const char* validModes[] = {
"r", "rb",
"w", "wb",
"a", "ab",
"r+", "rb+", "r+b",
"w+", "wb+", "w+b",
"a+", "ab+", "a+b",
nullptr
};
for (int i = 0; validModes[i] != nullptr; i++)
{
if (strcmp(mode, validModes[i]) == 0)
{
bModeValid = true;
break;
}
}
if (!bModeValid)
{
duk_push_error_object(ctx, DUK_ERR_TYPE_ERROR, "mode '%s' is not valid", mode);
return duk_throw(ctx);
}
FILE *fp = fopen(path, mode);
if(fp == nullptr)
{
duk_push_error_object(ctx, DUK_ERR_ERROR, "could not open '%s' (mode: '%s')", path, mode);
return duk_throw(ctx);
}
int fd = _fileno(fp);
// FILES[fd] = {fp: fp}
duk_get_global_string(ctx, HS_gOpenFileDescriptors);
duk_push_object(ctx);
duk_push_pointer(ctx, fp);
duk_put_prop_string(ctx, -2, "fp");
duk_push_c_function(ctx, js__FileFinalizer, 1);
duk_set_finalizer(ctx, -2);
duk_put_prop_index(ctx, -2, fd);
duk_pop_n(ctx, 2);
duk_push_number(ctx, fd);
return 1;
}
duk_ret_t ScriptAPI::js_fs_close(duk_context *ctx)
{
CheckArgs(ctx, { Arg_Number });
int fd = duk_get_int(ctx, 0);
int rc = -1;
duk_get_global_string(ctx, HS_gOpenFileDescriptors);
if(duk_has_prop_index(ctx, -1, fd))
{
duk_get_prop_index(ctx, -1, fd);
duk_get_prop_string(ctx, -1, "fp");
FILE* fp = (FILE*)duk_get_pointer(ctx, -1);
rc = fclose(fp);
// unset finalizer before deleting
duk_push_undefined(ctx);
duk_set_finalizer(ctx, -3);
duk_del_prop_index(ctx, -3, fd);
duk_pop_n(ctx, 2);
}
else
{
duk_push_error_object(ctx, DUK_ERR_ERROR, "invalid file descriptor");
return duk_throw(ctx);
}
duk_pop(ctx);
duk_push_number(ctx, rc);
return 1;
}
duk_ret_t ScriptAPI::js_fs_write(duk_context *ctx)
{
CheckArgs(ctx, { Arg_Number, Arg_Any, Arg_OptNumber, Arg_OptNumber, Arg_OptNumber });
return ReadWriteImpl(ctx, FS_WRITE);
}
duk_ret_t ScriptAPI::js_fs_writefile(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String, Arg_Any });
const char* path = duk_get_string(ctx, 0);
void* buffer;
duk_size_t bufferSize;
if(duk_is_string(ctx, 1))
{
buffer = (void*)duk_get_lstring(ctx, 1, &bufferSize);
}
else if(duk_is_buffer_data(ctx, 1))
{
buffer = duk_get_buffer_data(ctx, 1, &bufferSize);
}
else
{
return ThrowInvalidArgsError(ctx);
}
FILE* fp = fopen(path, "wb");
if(fp == nullptr)
{
duk_push_error_object(ctx, DUK_ERR_ERROR, "could not open '%s' (mode: 'wb')", path);
return duk_throw(ctx);
}
if(fwrite(buffer, 1, bufferSize, fp) != bufferSize)
{
fclose(fp);
return DUK_RET_ERROR;
}
fclose(fp);
return 0;
}
duk_ret_t ScriptAPI::js_fs_read(duk_context *ctx)
{
CheckArgs(ctx, { Arg_Number, Arg_Any, Arg_Number, Arg_Number, Arg_Number });
return ReadWriteImpl(ctx, FS_READ);
}
duk_ret_t ScriptAPI::js_fs_readfile(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String });
const char* path = duk_get_string(ctx, 0);
FILE* fp = fopen(path, "rb");
if(fp == nullptr)
{
duk_push_error_object(ctx, DUK_ERR_ERROR, "could not open '%s' (mode: 'rb')", path);
return duk_throw(ctx);
}
struct stat stats;
if(fstat(_fileno(fp), &stats) != 0)
{
fclose(fp);
return DUK_RET_ERROR;
}
void *data = duk_push_fixed_buffer(ctx, stats.st_size);
duk_push_buffer_object(ctx, -1, 0, stats.st_size, DUK_BUFOBJ_NODEJS_BUFFER);
if(fread(data, 1, stats.st_size, fp) != (size_t)stats.st_size)
{
duk_pop(ctx);
fclose(fp);
return DUK_RET_ERROR;
}
fclose(fp);
return 1;
}
duk_ret_t ScriptAPI::js_fs_exists(duk_context* ctx)
{
CheckArgs(ctx, { Arg_String });
const char* path = duk_get_string(ctx, 0);
duk_push_boolean(ctx, PathFileExistsA(path) ? 1 : 0);
return 1;
}
duk_ret_t ScriptAPI::js_fs_fstat(duk_context *ctx)
{
CheckArgs(ctx, { Arg_Number });
int fd = duk_get_int(ctx, 0);
duk_push_global_object(ctx);
duk_get_prop_string(ctx, -1, "fs");
duk_get_prop_string(ctx, -1, "Stats");
duk_push_number(ctx, fd);
duk_new(ctx, 1);
return 1;
}
duk_ret_t ScriptAPI::js_fs_stat(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String });
const char *path = duk_get_string(ctx, 0);
duk_push_global_object(ctx);
duk_get_prop_string(ctx, -1, "fs");
duk_get_prop_string(ctx, -1, "Stats");
duk_push_string(ctx, path);
duk_new(ctx, 1);
return 1;
}
duk_ret_t ScriptAPI::js_fs_unlink(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String });
const char *path = duk_get_string(ctx, 0);
duk_push_boolean(ctx, DeleteFileA(path) != 0);
return 1;
}
duk_ret_t ScriptAPI::js_fs_mkdir(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String });
const char *path = duk_get_string(ctx, 0);
duk_push_boolean(ctx, CreateDirectoryA(path, nullptr) != 0);
return 1;
}
duk_ret_t ScriptAPI::js_fs_rmdir(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String });
const char *path = duk_get_string(ctx, 0);
duk_push_boolean(ctx, RemoveDirectoryA(path) != 0);
return 1;
}
// todo make sure failure behavior is similar to nodejs's fs.readdirSync
duk_ret_t ScriptAPI::js_fs_readdir(duk_context *ctx)
{
CheckArgs(ctx, { Arg_String });
const char* path = duk_get_string(ctx, 0);
char findFileName[MAX_PATH];
snprintf(findFileName, sizeof(findFileName), "%s%s", path, "\\*");
WIN32_FIND_DATAA ffd;
HANDLE hFind = FindFirstFileA(findFileName, &ffd);
if(hFind == INVALID_HANDLE_VALUE)
{
return DUK_RET_ERROR;
}
duk_uarridx_t idx = 0;
duk_push_array(ctx);
do
{
if(strcmp(ffd.cFileName, ".") == 0 ||
strcmp(ffd.cFileName, "..") == 0)
{
continue;
}
duk_push_string(ctx, ffd.cFileName);
duk_put_prop_index(ctx, -2, idx++);
} while(FindNextFileA(hFind, &ffd));
FindClose(hFind);
return 1;
}
duk_ret_t ScriptAPI::js_fs_Stats__constructor(duk_context *ctx)
{
if(!duk_is_constructor_call(ctx))
{
return DUK_RET_TYPE_ERROR;
}
if(duk_get_top(ctx) != 1)
{
return ThrowInvalidArgsError(ctx);
}
struct stat stats;
if(duk_is_number(ctx, 0))
{
int fd = duk_get_int(ctx, 0);
if(fstat(fd, &stats) != 0)
{
return DUK_RET_ERROR;
}
}
else if(duk_is_string(ctx, 0))
{
const char *path = duk_get_string(ctx, 0);
if(stat(path, &stats) != 0)
{
return DUK_RET_ERROR;
}
}
else
{
return ThrowInvalidArgsError(ctx);
}
const duk_number_list_entry numbers[] = {
{ "dev", (double)stats.st_dev },
{ "ino", (double)stats.st_ino },
{ "mode", (double)stats.st_mode },
{ "nlink", (double)stats.st_nlink },
{ "uid", (double)stats.st_uid },
{ "gid", (double)stats.st_gid },
{ "rdev", (double)stats.st_rdev },
{ "size", (double)stats.st_size },
{ "atimeMs", (double)stats.st_atime * 1000 },
{ "mtimeMs", (double)stats.st_mtime * 1000 },
{ "ctimeMs", (double)stats.st_ctime * 1000 },
{ nullptr, 0 }
};
struct { const char *key; time_t time; } dates[3] = {
{ "atime", stats.st_atime * 1000 },
{ "mtime", stats.st_mtime * 1000 },
{ "ctime", stats.st_ctime * 1000 },
};
duk_push_global_object(ctx);
duk_get_prop_string(ctx, -1, "Date");
duk_remove(ctx, -2);
duk_push_this(ctx);
duk_put_number_list(ctx, -1, numbers);
for(int i = 0; i < 3; i++)
{
duk_dup(ctx, -2);
duk_push_number(ctx, (duk_double_t)dates[i].time);
duk_new(ctx, 1);
duk_put_prop_string(ctx, -2, dates[i].key);
}
duk_remove(ctx, -2);
duk_freeze(ctx, -1);
return 0;
}
duk_ret_t ScriptAPI::js_fs_Stats_isDirectory(duk_context *ctx)
{
CheckArgs(ctx, {});
duk_push_this(ctx);
duk_get_prop_string(ctx, -1, "mode");
duk_uint_t mode = duk_get_uint(ctx, -1);
duk_push_boolean(ctx, (mode & S_IFDIR) != 0);
return 1;
}
duk_ret_t ScriptAPI::js_fs_Stats_isFile(duk_context *ctx)
{
CheckArgs(ctx, {});
duk_push_this(ctx);
duk_get_prop_string(ctx, -1, "mode");
duk_uint_t mode = duk_get_uint(ctx, -1);
duk_push_boolean(ctx, (mode & S_IFREG) != 0);
return 1;
}
static duk_ret_t ReadWriteImpl(duk_context *ctx, fsop op)
{
int fd;
const char* buffer;
size_t offset = 0;
size_t length = 0;
size_t position = 0;
bool bHavePos = false;
duk_size_t bufferSize;
FILE* fp;
size_t rc;
duk_idx_t nargs = duk_get_top(ctx);
if(nargs < 2 || !duk_is_number(ctx, 0))
{
goto err_invalid_args;
}
fd = duk_get_int(ctx, 0);
if(duk_is_buffer_data(ctx, 1))
{
buffer = (const char*)duk_get_buffer_data(ctx, 1, &bufferSize);
}
else if(duk_is_string(ctx, 1) && op == FS_WRITE)
{
buffer = duk_get_lstring(ctx, 1, &bufferSize);
}
else
{
goto err_invalid_args;
}
if(nargs >= 3)
{
if(!duk_is_number(ctx, 2))
{
goto err_invalid_args;
}
offset = duk_get_uint(ctx, 2);
if(offset >= bufferSize)
{
goto err_invalid_range;
}
}
length = bufferSize - offset;
if(nargs >= 4)
{
if(!duk_is_number(ctx, 3))
{
goto err_invalid_args;
}
length = duk_get_uint(ctx, 3);
}
if(nargs >= 5)
{
if(!duk_is_number(ctx, 4))
{
goto err_invalid_args;
}
position = duk_get_uint(ctx, 4);
bHavePos = true;
}
if(offset + length > bufferSize)
{
goto err_invalid_range;
}
duk_get_global_string(ctx, HS_gOpenFileDescriptors);
if(!duk_has_prop_index(ctx, -1, fd))
{
goto err_invalid_fd;
}
duk_get_prop_index(ctx, -1, fd);
duk_get_prop_string(ctx, -1, "fp");
fp = (FILE*)duk_get_pointer(ctx, -1);
duk_pop_n(ctx, 3);
if(bHavePos)
{
fseek(fp, position, SEEK_SET);
}
switch(op)
{
case FS_READ:
rc = fread((void*)&buffer[offset], 1, length, fp);
break;
case FS_WRITE:
rc = fwrite((void*)&buffer[offset], 1, length, fp);
break;
default:
return DUK_RET_ERROR;
}
duk_push_number(ctx, rc);
return 1;
err_invalid_args:
return ScriptAPI::ThrowInvalidArgsError(ctx);
err_invalid_range:
duk_push_error_object(ctx, DUK_ERR_RANGE_ERROR, "invalid range");
return duk_throw(ctx);
err_invalid_fd:
duk_push_error_object(ctx, DUK_ERR_ERROR, "invalid file descriptor");
return duk_throw(ctx);
}
static duk_ret_t js__FileFinalizer(duk_context *ctx)
{
CScriptInstance* inst = ScriptAPI::GetInstance(ctx);
duk_get_prop_string(ctx, 0, "fp");
FILE *fp = (FILE *)duk_get_pointer(ctx, -1);
duk_pop(ctx);
fclose(fp);
inst->System()->ConsoleLog("[SCRIPTSYS]: warning ('%s'): gc closed leftover file descriptor",
inst->Name().c_str());
return 0;
}