#include "qjs_fd.h" #include "qjs_blob.h" #include "jsffi.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_get_blob_data(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_new_blob_stoned_copy(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_new_blob_stoned_copy(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; }