#include "cell.h" #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #define mkdir(path, mode) _mkdir(path) #define rmdir _rmdir #define unlink _unlink #define getcwd _getcwd #define PATH_MAX _MAX_PATH #define fsync(fd) _commit(fd) #define S_ISLNK(m) 0 #define S_ISSOCK(m) 0 #else #include #include #include #endif // Helper to convert JS value to file descriptor static int js2fd(JSContext *ctx, JSValueConst val) { int fd; if (JS_ToInt32(ctx, &fd, val) < 0) { JS_ThrowTypeError(ctx, "Expected file descriptor number"); return -1; } return fd; } // 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; } // POSIX FILE DESCRIPTOR FUNCTIONS 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_ThrowInternalError(js, "open failed: %s", strerror(errno)); else ret = JS_NewInt32(js, fd); ) JSC_CCALL(fd_write, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; ssize_t wrote = js_fd_write_helper(js, fd, argv[1]); if (wrote < 0) return JS_ThrowInternalError(js, "write failed: %s", strerror(errno)); return JS_NewInt64(js, wrote); ) JSC_CCALL(fd_read, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; size_t size = 4096; if (argc > 1) size = js2number(js, argv[1]); void *buf = malloc(size); if (!buf) return JS_ThrowInternalError(js, "malloc failed"); ssize_t bytes_read = read(fd, buf, size); if (bytes_read < 0) { free(buf); return JS_ThrowInternalError(js, "read failed: %s", strerror(errno)); } ret = js_new_blob_stoned_copy(js, buf, bytes_read); free(buf); return ret; ) JSC_SCALL(fd_slurp, struct stat st; if (stat(str, &st) != 0) return JS_ThrowInternalError(js, "stat failed: %s", strerror(errno)); if (!S_ISREG(st.st_mode)) return JS_ThrowTypeError(js, "path is not a regular file"); size_t size = st.st_size; if (size == 0) return js_new_blob_stoned_copy(js, NULL, 0); #ifndef _WIN32 int fd = open(str, O_RDONLY); if (fd < 0) return JS_ThrowInternalError(js, "open failed: %s", strerror(errno)); void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (data == MAP_FAILED) { close(fd); return JS_ThrowInternalError(js, "mmap failed: %s", strerror(errno)); } ret = js_new_blob_stoned_copy(js, data, size); munmap(data, size); close(fd); #else // Windows: use memory mapping for optimal performance HANDLE hFile = CreateFileA(str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return JS_ThrowInternalError(js, "CreateFile failed: %lu", GetLastError()); HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMapping == NULL) { CloseHandle(hFile); return JS_ThrowInternalError(js, "CreateFileMapping failed: %lu", GetLastError()); } void *data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); if (data == NULL) { CloseHandle(hMapping); CloseHandle(hFile); return JS_ThrowInternalError(js, "MapViewOfFile failed: %lu", GetLastError()); } ret = js_new_blob_stoned_copy(js, data, size); UnmapViewOfFile(data); CloseHandle(hMapping); CloseHandle(hFile); #endif ) JSC_CCALL(fd_lseek, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; off_t offset = js2number(js, argv[1]); int whence = SEEK_SET; if (argc > 2) { const char *whence_str = JS_ToCString(js, argv[2]); 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(fd, offset, whence); if (new_pos < 0) return JS_ThrowInternalError(js, "lseek failed: %s", strerror(errno)); return JS_NewInt64(js, new_pos); ) JSC_CCALL(fd_getcwd, char buf[PATH_MAX]; if (getcwd(buf, sizeof(buf)) == NULL) return JS_ThrowInternalError(js, "getcwd failed: %s", strerror(errno)); return JS_NewString(js, buf); ) JSC_SCALL(fd_rmdir, if (rmdir(str) != 0) ret = JS_ThrowInternalError(js, "could not remove directory %s: %s", str, strerror(errno)); ) JSC_SCALL(fd_mkdir, if (mkdir(str, 0755) != 0) ret = JS_ThrowInternalError(js, "could not make directory %s: %s", str, strerror(errno)); ) JSC_SCALL(fd_unlink, if (unlink(str) != 0) ret = JS_ThrowInternalError(js, "could not remove file %s: %s", str, strerror(errno)); ) JSC_SCALL(fd_mv, if (argc < 2) ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path"); else if (!JS_IsString(argv[1])) ret = JS_ThrowTypeError(js, "second argument must be a string (new path)"); else { const char *new_path = JS_ToCString(js, argv[1]); if (rename(str, new_path) != 0) ret = JS_ThrowInternalError(js, "could not rename %s to %s: %s", str, new_path, strerror(errno)); JS_FreeCString(js, new_path); } ) // Helper function for recursive removal static int remove_recursive(const char *path) { struct stat st; if (stat(path, &st) != 0) return -1; if (S_ISDIR(st.st_mode)) { // Directory: remove contents first #ifdef _WIN32 WIN32_FIND_DATA ffd; char search_path[PATH_MAX]; snprintf(search_path, sizeof(search_path), "%s\\*", path); HANDLE hFind = FindFirstFile(search_path, &ffd); if (hFind != INVALID_HANDLE_VALUE) { do { if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; char child_path[PATH_MAX]; snprintf(child_path, sizeof(child_path), "%s\\%s", path, ffd.cFileName); if (remove_recursive(child_path) != 0) { FindClose(hFind); return -1; } } while (FindNextFile(hFind, &ffd) != 0); FindClose(hFind); } #else DIR *d = opendir(path); if (d) { struct dirent *dir; while ((dir = readdir(d)) != NULL) { if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; char child_path[PATH_MAX]; snprintf(child_path, sizeof(child_path), "%s/%s", path, dir->d_name); if (remove_recursive(child_path) != 0) { closedir(d); return -1; } } closedir(d); } #endif // Remove the now-empty directory return rmdir(path); } else { // File: just unlink return unlink(path); } } JSC_SCALL(fd_rm, if (remove_recursive(str) != 0) ret = JS_ThrowInternalError(js, "could not remove %s: %s", str, strerror(errno)); ) JSC_CCALL(fd_fsync, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; if (fsync(fd) != 0) return JS_ThrowInternalError(js, "fsync failed: %s", strerror(errno)); return JS_NULL; ) JSC_CCALL(fd_close, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; if (close(fd) != 0) return JS_ThrowInternalError(js, "close failed: %s", strerror(errno)); return JS_NULL; ) JSC_CCALL(fd_fstat, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; struct stat st; if (fstat(fd, &st) != 0) return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno)); printf("fstat on %s\n", argv[0]); JSValue obj = JS_NewObject(js); JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); #ifndef _WIN32 JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); #else JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); #endif // Add boolean properties for file type JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode))); return obj; ) JSC_SCALL(fd_stat, struct stat st; if (stat(str, &st) != 0) return JS_NewObject(js); JSValue obj = JS_NewObject(js); JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); #ifndef _WIN32 JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); #else JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); #endif // Add boolean properties for file type JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode))); return obj; ) JSC_SCALL(fd_readdir, #ifdef _WIN32 WIN32_FIND_DATA ffd; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s\\*", str); HANDLE hFind = FindFirstFile(path, &ffd); if (hFind == INVALID_HANDLE_VALUE) { ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path); } else { ret = JS_NewArray(js); int i = 0; do { if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, ffd.cFileName)); } while (FindNextFile(hFind, &ffd) != 0); FindClose(hFind); } #else DIR *d; struct dirent *dir; d = opendir(str); if (d) { ret = JS_NewArray(js); int i = 0; while ((dir = readdir(d)) != NULL) { if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, dir->d_name)); } closedir(d); } else { ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno)); } #endif ) static const JSCFunctionListEntry js_fd_funcs[] = { MIST_FUNC_DEF(fd, open, 2), MIST_FUNC_DEF(fd, write, 2), MIST_FUNC_DEF(fd, read, 2), MIST_FUNC_DEF(fd, slurp, 1), MIST_FUNC_DEF(fd, lseek, 3), MIST_FUNC_DEF(fd, getcwd, 0), MIST_FUNC_DEF(fd, rmdir, 1), MIST_FUNC_DEF(fd, unlink, 1), MIST_FUNC_DEF(fd, mkdir, 1), MIST_FUNC_DEF(fd, mv, 2), MIST_FUNC_DEF(fd, rm, 1), MIST_FUNC_DEF(fd, fsync, 1), MIST_FUNC_DEF(fd, close, 1), MIST_FUNC_DEF(fd, stat, 1), MIST_FUNC_DEF(fd, fstat, 1), MIST_FUNC_DEF(fd, readdir, 1), }; JSValue js_fd_use(JSContext *js) { JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs)); return mod; }