570 lines
13 KiB
C++
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;
|
|
}
|