From f73f7384597ff7a58c1767a399782ae5dd02440d Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 27 May 2025 01:52:27 -0500 Subject: [PATCH] add fd module --- meson.build | 2 +- source/jsffi.c | 2 + source/qjs_fd.c | 497 ++++++++++++++++++++++++++++++++++++++++++++++++ source/qjs_fd.h | 8 + 4 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 source/qjs_fd.c create mode 100644 source/qjs_fd.h diff --git a/meson.build b/meson.build index bdf92d56..0a677d5f 100644 --- a/meson.build +++ b/meson.build @@ -195,7 +195,7 @@ sources = [] src += [ 'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c', 'render.c','simplex.c','spline.c', 'transform.c','prosperon.c', 'wildmatch.c', - 'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_os.c', 'qjs_actor.c', + 'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c', 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c' ] # quirc src diff --git a/source/jsffi.c b/source/jsffi.c index 45ac200c..2f0b75e1 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -3,6 +3,7 @@ #include "datastream.h" #include "qjs_sdl.h" #include "qjs_io.h" +#include "qjs_fd.h" #include "transform.h" #include "stb_ds.h" #include "stb_image.h" @@ -1586,6 +1587,7 @@ void ffi_load(JSContext *js) // extra arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use})); + arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use})); arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use})); arrput(rt->module_registry, MISTLINE(qr)); arrput(rt->module_registry, MISTLINE(http)); diff --git a/source/qjs_fd.c b/source/qjs_fd.c new file mode 100644 index 00000000..37eed475 --- /dev/null +++ b/source/qjs_fd.c @@ -0,0 +1,497 @@ +#include "qjs_fd.h" +#include "jsffi.h" +#include "qjs_macros.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wildmatch.h" + +// File descriptor wrapper structure +typedef struct { + int fd; +} FDWrapper; + +// Free function for file descriptor +static void FD_free(JSRuntime *rt, FDWrapper *fdw) +{ + if (fdw->fd >= 0) { + close(fdw->fd); + } + js_free_rt(rt, fdw); +} + +// Class definition for file descriptor +static JSClassID js_fd_class_id; + +static JSClassDef js_fd_class = { + "FileDescriptor", + .finalizer = (JSClassFinalizer *)FD_free, +}; + +// Helper to convert JS value to FDWrapper +static FDWrapper *js2fd(JSContext *ctx, JSValueConst obj) +{ + return JS_GetOpaque2(ctx, obj, js_fd_class_id); +} + +// Helper to create JS FDWrapper object +static JSValue fd2js(JSContext *ctx, int fd) +{ + FDWrapper *fdw = js_mallocz(ctx, sizeof(FDWrapper)); + if (!fdw) return JS_EXCEPTION; + + fdw->fd = fd; + + JSValue obj = JS_NewObjectClass(ctx, js_fd_class_id); + if (JS_IsException(obj)) { + js_free(ctx, fdw); + return obj; + } + + JS_SetOpaque(obj, fdw); + return obj; +} + +// Helper function for writing +static ssize_t js_fd_write_helper(JSContext *js, int fd, JSValue val) +{ + size_t len; + ssize_t wrote; + if (JS_IsString(val)) { + const char *data = JS_ToCStringLen(js, &len, val); + wrote = write(fd, data, len); + JS_FreeCString(js, data); + } else { + unsigned char *data = JS_GetArrayBuffer(js, &len, val); + wrote = write(fd, data, len); + } + return wrote; +} + +// Glob data structure +struct fd_globdata { + JSContext *js; + JSValue arr; + char **globs; + int idx; + int recurse; +}; + +// Helper for recursive directory enumeration +static void enumerate_dir(struct fd_globdata *data, const char *path); + +// Callback for glob matching +static void globfs_cb(struct fd_globdata *data, const char *dir, const char *file) +{ + char *path; + int needfree = 0; + + if (dir[0] == 0) { + path = (char*)file; + } else { + size_t len = strlen(dir) + strlen(file) + 2; + path = malloc(len); + snprintf(path, len, "%s/%s", dir, file); + needfree = 1; + } + + char **glob = data->globs; + while (*glob != NULL) { + if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH) + goto END; + glob++; + } + + struct stat st; + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + enumerate_dir(data, path); + goto END; + } + + JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js, path)); + +END: + if (needfree) free(path); +} + +// Helper for directory enumeration +static void enumerate_dir(struct fd_globdata *data, const char *path) +{ + DIR *dir = opendir(path); + if (!dir) return; + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + globfs_cb(data, path, entry->d_name); + } + + closedir(dir); +} + +// FD I/O FUNCTIONS + +JSC_SCALL(fd_rm, + if (remove(str) != 0) + ret = JS_ThrowReferenceError(js, "could not remove %s: %s", str, strerror(errno)); +) + +JSC_SCALL(fd_mkdir, + if (mkdir(str, 0755) != 0) + ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno)); +) + +JSC_SCALL(fd_exists, + struct stat st; + ret = JS_NewBool(js, stat(str, &st) == 0); +) + +JSC_SCALL(fd_stat, + struct stat st; + if (stat(str, &st) != 0) + return JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno)); + + ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "filesize", number2js(js, st.st_size)); + JS_SetPropertyStr(js, ret, "modtime", number2js(js, st.st_mtime)); + JS_SetPropertyStr(js, ret, "createtime", number2js(js, st.st_ctime)); + JS_SetPropertyStr(js, ret, "accesstime", number2js(js, st.st_atime)); + JS_SetPropertyStr(js, ret, "mode", JS_NewInt32(js, st.st_mode)); + JS_SetPropertyStr(js, ret, "is_directory", JS_NewBool(js, S_ISDIR(st.st_mode))); + JS_SetPropertyStr(js, ret, "is_file", JS_NewBool(js, S_ISREG(st.st_mode))); +) + +JSC_SCALL(fd_slurpbytes, + int fd = open(str, O_RDONLY); + if (fd < 0) { + ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno)); + goto END; + } + + struct stat st; + if (fstat(fd, &st) != 0) { + close(fd); + ret = JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno)); + goto END; + } + + void *data = malloc(st.st_size); + if (!data) { + close(fd); + ret = JS_ThrowReferenceError(js, "malloc failed"); + goto END; + } + + ssize_t bytes_read = read(fd, data, st.st_size); + close(fd); + + if (bytes_read != st.st_size) { + free(data); + ret = JS_ThrowReferenceError(js, "read failed: %s", strerror(errno)); + goto END; + } + + ret = JS_NewArrayBufferCopy(js, data, st.st_size); + free(data); + +END: +) + +JSC_SCALL(fd_slurp, + int fd = open(str, O_RDONLY); + if (fd < 0) { + ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno)); + goto END; + } + + struct stat st; + if (fstat(fd, &st) != 0) { + close(fd); + ret = JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno)); + goto END; + } + + char *data = malloc(st.st_size + 1); + if (!data) { + close(fd); + ret = JS_ThrowReferenceError(js, "malloc failed"); + goto END; + } + + ssize_t bytes_read = read(fd, data, st.st_size); + close(fd); + + if (bytes_read != st.st_size) { + free(data); + ret = JS_ThrowReferenceError(js, "read failed: %s", strerror(errno)); + goto END; + } + + data[st.st_size] = '\0'; + ret = JS_NewStringLen(js, data, st.st_size); + free(data); + +END: +) + +JSC_SCALL(fd_slurpwrite, + int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno)); + goto END; + } + + ssize_t wrote = js_fd_write_helper(js, fd, argv[1]); + close(fd); + + if (wrote < 0) + ret = JS_ThrowReferenceError(js, "write failed: %s", strerror(errno)); + +END: +) + +JSC_SSCALL(fd_match, + if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH) + ret = JS_NewBool(js, 1); + else + ret = JS_NewBool(js, 0); +) + +JSC_CCALL(fd_globfs, + ret = JS_NewArray(js); + struct fd_globdata data; + data.js = js; + data.arr = ret; + data.idx = 0; + data.recurse = 1; + + int globs_len = JS_ArrayLength(js, argv[0]); + const char *globs[globs_len + 1]; + for (int i = 0; i < globs_len; i++) { + JSValue g = JS_GetPropertyUint32(js, argv[0], i); + globs[i] = JS_ToCString(js, g); + JS_FreeValue(js, g); + } + globs[globs_len] = NULL; + data.globs = (char**)globs; + + const char *path = "."; + if (!JS_IsUndefined(argv[1])) + path = JS_ToCString(js, argv[1]); + + enumerate_dir(&data, path); + + for (int i = 0; i < globs_len; i++) + JS_FreeCString(js, globs[i]); + + if (!JS_IsUndefined(argv[1])) + JS_FreeCString(js, path); +) + +JSC_SCALL(fd_enumerate, + ret = JS_NewArray(js); + + DIR *dir = opendir(str); + if (!dir) { + ret = JS_ThrowReferenceError(js, "could not open directory %s: %s", str, strerror(errno)); + goto END; + } + + int idx = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + JS_SetPropertyUint32(js, ret, idx++, JS_NewString(js, entry->d_name)); + } + + closedir(dir); + +END: +) + +JSC_CCALL(fd_getcwd, + char buf[PATH_MAX]; + if (getcwd(buf, sizeof(buf)) == NULL) + return JS_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno)); + return JS_NewString(js, buf); +) + +JSC_SCALL(fd_chdir, + if (chdir(str) != 0) + ret = JS_ThrowReferenceError(js, "chdir failed: %s", strerror(errno)); +) + +JSC_SCALL(fd_open, + int flags = O_RDWR | O_CREAT; + mode_t mode = 0644; + + // Parse optional flags argument + if (argc > 1 && JS_IsString(argv[1])) { + const char *flag_str = JS_ToCString(js, argv[1]); + flags = 0; + + if (strchr(flag_str, 'r')) flags |= O_RDONLY; + if (strchr(flag_str, 'w')) flags |= O_WRONLY | O_CREAT | O_TRUNC; + if (strchr(flag_str, 'a')) flags |= O_WRONLY | O_CREAT | O_APPEND; + if (strchr(flag_str, '+')) { + flags &= ~(O_RDONLY | O_WRONLY); + flags |= O_RDWR; + } + + JS_FreeCString(js, flag_str); + } + + int fd = open(str, flags, mode); + if (fd < 0) + ret = JS_ThrowReferenceError(js, "open failed: %s", strerror(errno)); + else + ret = fd2js(js, fd); +) + +JSC_SCALL(fd_is_directory, + struct stat st; + if (stat(str, &st) != 0) + ret = JS_NewBool(js, 0); + else + ret = JS_NewBool(js, S_ISDIR(st.st_mode)); +) + +// FILE DESCRIPTOR FUNCTIONS + +JSC_CCALL(file_close, + FDWrapper *fdw = js2fd(js, self); + if (!fdw) return JS_EXCEPTION; + + if (fdw->fd >= 0) { + close(fdw->fd); + fdw->fd = -1; + } +) + +JSC_CCALL(file_write, + FDWrapper *fdw = js2fd(js, self); + if (!fdw) return JS_EXCEPTION; + + ssize_t wrote = js_fd_write_helper(js, fdw->fd, argv[0]); + if (wrote < 0) + return JS_ThrowReferenceError(js, "write failed: %s", strerror(errno)); + + return JS_NewInt64(js, wrote); +) + +JSC_CCALL(file_read, + FDWrapper *fdw = js2fd(js, self); + if (!fdw) return JS_EXCEPTION; + + size_t size = 4096; + if (argc > 0) + size = js2number(js, argv[0]); + + void *buf = malloc(size); + if (!buf) + return JS_ThrowReferenceError(js, "malloc failed"); + + ssize_t bytes_read = read(fdw->fd, buf, size); + if (bytes_read < 0) { + free(buf); + return JS_ThrowReferenceError(js, "read failed: %s", strerror(errno)); + } + + ret = JS_NewArrayBufferCopy(js, buf, bytes_read); + free(buf); + return ret; +) + +JSC_CCALL(file_seek, + FDWrapper *fdw = js2fd(js, self); + if (!fdw) return JS_EXCEPTION; + + off_t offset = js2number(js, argv[0]); + int whence = SEEK_SET; + + if (argc > 1) { + const char *whence_str = JS_ToCString(js, argv[1]); + if (strcmp(whence_str, "cur") == 0) whence = SEEK_CUR; + else if (strcmp(whence_str, "end") == 0) whence = SEEK_END; + JS_FreeCString(js, whence_str); + } + + off_t new_pos = lseek(fdw->fd, offset, whence); + if (new_pos < 0) + return JS_ThrowReferenceError(js, "lseek failed: %s", strerror(errno)); + + return JS_NewInt64(js, new_pos); +) + +JSC_CCALL(file_tell, + FDWrapper *fdw = js2fd(js, self); + if (!fdw) return JS_EXCEPTION; + + off_t pos = lseek(fdw->fd, 0, SEEK_CUR); + if (pos < 0) + return JS_ThrowReferenceError(js, "lseek failed: %s", strerror(errno)); + + return JS_NewInt64(js, pos); +) + +JSC_CCALL(file_eof, + FDWrapper *fdw = js2fd(js, self); + if (!fdw) return JS_EXCEPTION; + + off_t cur = lseek(fdw->fd, 0, SEEK_CUR); + off_t end = lseek(fdw->fd, 0, SEEK_END); + lseek(fdw->fd, cur, SEEK_SET); + + return JS_NewBool(js, cur >= end); +) + +static const JSCFunctionListEntry js_fd_funcs[] = { + MIST_FUNC_DEF(fd, rm, 1), + MIST_FUNC_DEF(fd, mkdir, 1), + MIST_FUNC_DEF(fd, stat, 1), + MIST_FUNC_DEF(fd, globfs, 2), + MIST_FUNC_DEF(fd, match, 2), + MIST_FUNC_DEF(fd, exists, 1), + MIST_FUNC_DEF(fd, slurp, 1), + MIST_FUNC_DEF(fd, slurpbytes, 1), + MIST_FUNC_DEF(fd, slurpwrite, 2), + MIST_FUNC_DEF(fd, getcwd, 0), + MIST_FUNC_DEF(fd, chdir, 1), + MIST_FUNC_DEF(fd, open, 2), + MIST_FUNC_DEF(fd, enumerate, 1), + MIST_FUNC_DEF(fd, is_directory, 1), +}; + +static const JSCFunctionListEntry js_fd_file_funcs[] = { + MIST_FUNC_DEF(file, close, 0), + MIST_FUNC_DEF(file, write, 1), + MIST_FUNC_DEF(file, read, 1), + MIST_FUNC_DEF(file, seek, 2), + MIST_FUNC_DEF(file, tell, 0), + MIST_FUNC_DEF(file, eof, 0), +}; + +JSValue js_fd_use(JSContext *js) { + // Initialize the file descriptor class + JS_NewClassID(&js_fd_class_id); + JS_NewClass(JS_GetRuntime(js), js_fd_class_id, &js_fd_class); + + JSValue proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, proto, js_fd_file_funcs, countof(js_fd_file_funcs)); + JS_SetClassProto(js, js_fd_class_id, proto); + + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs)); + return mod; +} \ No newline at end of file diff --git a/source/qjs_fd.h b/source/qjs_fd.h new file mode 100644 index 00000000..6e352762 --- /dev/null +++ b/source/qjs_fd.h @@ -0,0 +1,8 @@ +#ifndef QJS_FD_H +#define QJS_FD_H + +#include + +JSValue js_fd_use(JSContext *js); + +#endif // QJS_FD_H \ No newline at end of file