#include #include "ScriptAPI.h" #include #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; }