Page MenuHomePhorge

fs.c
No OneTemporary

Size
61 KB
Referenced Files
None
Subscribers
None
#include <compat.h> // IWYU pragma: keep
#include <uv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <uthash.h>
#include <utarray.h>
#include <errno.h>
#include "ant.h"
#include "utf8.h"
#include "utils.h"
#include "base64.h"
#include "errors.h"
#include "internal.h"
#include "runtime.h"
#include "descriptors.h"
#include "modules/fs.h"
#include "modules/buffer.h"
#include "modules/symbol.h"
#define fs_err_code(js, code, op, path) ({ \
jsval_t _props = js_mkobj(js); \
js_set(js, _props, "code", js_mkstr(js, code, strlen(code))); \
js_mkerr_props(js, JS_ERR_TYPE, _props, "%s: %s, %s '%s'", code, strerror(errno), op, path); \
})
typedef enum {
FS_ENC_NONE = 0,
FS_ENC_UTF8,
FS_ENC_UTF16LE,
FS_ENC_LATIN1,
FS_ENC_BASE64,
FS_ENC_BASE64URL,
FS_ENC_HEX,
FS_ENC_ASCII,
} fs_encoding_t;
static jsval_t fs_coerce_path(ant_t *js, jsval_t arg) {
if (vtype(arg) == T_STR) return arg;
if (is_object_type(arg)) {
jsval_t pathname = js_get(js, arg, "pathname");
if (vtype(pathname) == T_STR) return pathname;
}
return js_mkundef();
}
static fs_encoding_t parse_encoding(ant_t *js, jsval_t arg) {
size_t len;
const char *str = NULL;
if (vtype(arg) == T_STR) str = js_getstr(js, arg, &len);
else if (vtype(arg) == T_OBJ) {
jsval_t enc = js_get(js, arg, "encoding");
if (vtype(enc) == T_STR) str = js_getstr(js, enc, &len);
}
if (!str) return FS_ENC_NONE;
if (len == 4 && memcmp(str, "utf8", 4) == 0) return FS_ENC_UTF8;
if (len == 5 && memcmp(str, "utf-8", 5) == 0) return FS_ENC_UTF8;
if (len == 7 && memcmp(str, "utf16le", 7) == 0) return FS_ENC_UTF16LE;
if (len == 4 && memcmp(str, "ucs2", 4) == 0) return FS_ENC_UTF16LE;
if (len == 5 && memcmp(str, "ucs-2", 5) == 0) return FS_ENC_UTF16LE;
if (len == 6 && memcmp(str, "latin1", 6) == 0) return FS_ENC_LATIN1;
if (len == 6 && memcmp(str, "binary", 6) == 0) return FS_ENC_LATIN1;
if (len == 6 && memcmp(str, "base64", 6) == 0) return FS_ENC_BASE64;
if (len == 9 && memcmp(str, "base64url", 9) == 0) return FS_ENC_BASE64URL;
if (len == 3 && memcmp(str, "hex", 3) == 0) return FS_ENC_HEX;
if (len == 5 && memcmp(str, "ascii", 5) == 0) return FS_ENC_ASCII;
return FS_ENC_NONE;
}
static jsval_t encode_data(ant_t *js, const char *data, size_t len, fs_encoding_t enc) {
switch (enc) {
case FS_ENC_UTF8:
case FS_ENC_LATIN1:
case FS_ENC_ASCII: return js_mkstr(js, data, len);
case FS_ENC_HEX: {
char *out = malloc(len * 2);
if (!out) return js_mkerr(js, "Out of memory");
for (size_t i = 0; i < len; i++) {
out[i * 2] = hex_char((unsigned char)data[i] >> 4);
out[i * 2 + 1] = hex_char(data[i]);
}
jsval_t result = js_mkstr(js, out, len * 2);
free(out); return result;
}
case FS_ENC_BASE64: {
size_t out_len;
char *out = ant_base64_encode((const uint8_t *)data, len, &out_len);
if (!out) return js_mkerr(js, "Out of memory");
jsval_t result = js_mkstr(js, out, out_len);
free(out); return result;
}
case FS_ENC_BASE64URL: {
size_t out_len;
char *out = ant_base64_encode((const uint8_t *)data, len, &out_len);
if (!out) return js_mkerr(js, "Out of memory");
for (size_t i = 0; i < out_len; i++) {
if (out[i] == '+') out[i] = '-';
else if (out[i] == '/') out[i] = '_';
}
while (out_len > 0 && out[out_len - 1] == '=') out_len--;
jsval_t result = js_mkstr(js, out, out_len);
free(out); return result;
}
case FS_ENC_UTF16LE: {
const unsigned char *s = (const unsigned char *)data;
char *out = malloc((len / 2) * 4);
if (!out) return js_mkerr(js, "Out of memory");
size_t j = 0;
for (size_t i = 0; i + 1 < len; i += 2) {
uint32_t cp = s[i] | (s[i + 1] << 8);
bool is_hi = cp >= 0xD800 && cp <= 0xDBFF && i + 3 < len;
uint32_t lo = is_hi ? s[i + 2] | (s[i + 3] << 8) : 0;
bool is_pair = is_hi && lo >= 0xDC00 && lo <= 0xDFFF;
if (is_pair) { cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); i += 2; }
j += utf8_encode(cp, out + j);
}
jsval_t result = js_mkstr(js, out, j);
free(out); return result;
}
default: return js_mkstr(js, data, len);
}
}
typedef enum {
FS_OP_READ,
FS_OP_WRITE,
FS_OP_UNLINK,
FS_OP_MKDIR,
FS_OP_RMDIR,
FS_OP_STAT,
FS_OP_READ_BYTES,
FS_OP_EXISTS,
FS_OP_READDIR,
FS_OP_ACCESS,
FS_OP_WRITE_FD,
FS_OP_OPEN,
FS_OP_CLOSE
} fs_op_type_t;
typedef struct fs_request_s {
uv_fs_t uv_req;
ant_t *js;
jsval_t promise;
char *path;
char *data;
char *error_msg;
size_t data_len;
fs_op_type_t op_type;
fs_encoding_t encoding;
uv_file fd;
int completed;
int failed;
int recursive;
} fs_request_t;
static UT_array *pending_requests = NULL;
static void free_fs_request(fs_request_t *req) {
if (!req) return;
if (req->path) free(req->path);
if (req->data) free(req->data);
if (req->error_msg) free(req->error_msg);
uv_fs_req_cleanup(&req->uv_req);
free(req);
}
static void remove_pending_request(fs_request_t *req) {
if (!req || !pending_requests) return;
unsigned int len = utarray_len(pending_requests);
for (unsigned int i = 0; i < len; i++) {
if (*(fs_request_t**)utarray_eltptr(pending_requests, i) != req) continue;
utarray_erase(pending_requests, i, 1); break;
}
}
static jsval_t fs_read_to_uint8array(ant_t *js, const char *data, size_t len) {
ArrayBufferData *ab = create_array_buffer_data(len);
if (!ab) return js_mkundef();
memcpy(ab->data, data, len);
jsval_t result = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer");
if (vtype(result) == T_ERR) free_array_buffer_data(ab);
return result;
}
static void complete_request(fs_request_t *req) {
if (req->failed) {
const char *err_msg = req->error_msg ? req->error_msg : "Unknown error";
jsval_t reject_value = js_mkerr(req->js, "%s", err_msg);
if (is_err(reject_value)) {
reject_value = req->js->thrown_value;
if (vtype(reject_value) == T_UNDEF) {
reject_value = js_mkstr(req->js, err_msg, strlen(err_msg));
}
req->js->thrown_exists = false;
req->js->thrown_value = js_mkundef();
}
js_reject_promise(req->js, req->promise, reject_value);
} else {
jsval_t result = js_mkundef();
if (req->op_type == FS_OP_READ && req->data) {
if (req->encoding != FS_ENC_NONE)
result = encode_data(req->js, req->data, req->data_len, req->encoding);
else result = fs_read_to_uint8array(req->js, req->data, req->data_len);
} else if (req->op_type == FS_OP_READ_BYTES && req->data)
result = js_mkstr(req->js, req->data, req->data_len);
js_resolve_promise(req->js, req->promise, result);
}
remove_pending_request(req);
free_fs_request(req);
}
static void on_read_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
req->data_len = uv_req->result;
uv_fs_t close_req;
uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
uv_fs_req_cleanup(&close_req);
req->completed = 1;
complete_request(req);
}
static void on_open_for_read(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
req->fd = (int)uv_req->result;
uv_fs_req_cleanup(uv_req);
uv_fs_t stat_req;
int stat_result = uv_fs_fstat(uv_default_loop(), &stat_req, req->fd, NULL);
if (stat_result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(stat_result));
req->completed = 1;
uv_fs_t close_req;
uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
uv_fs_req_cleanup(&close_req);
complete_request(req);
return;
}
size_t file_size = stat_req.statbuf.st_size;
uv_fs_req_cleanup(&stat_req);
size_t alloc_size = (req->op_type == FS_OP_READ) ? file_size + 1 : file_size;
req->data = malloc(alloc_size);
if (!req->data) {
req->failed = 1;
req->error_msg = strdup("Out of memory");
req->completed = 1;
uv_fs_t close_req;
uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
uv_fs_req_cleanup(&close_req);
complete_request(req);
return;
}
uv_buf_t buf = uv_buf_init(req->data, (unsigned int)file_size);
int read_result = uv_fs_read(uv_default_loop(), uv_req, req->fd, &buf, 1, 0, on_read_complete);
if (read_result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(read_result));
req->completed = 1;
uv_fs_t close_req;
uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
uv_fs_req_cleanup(&close_req);
complete_request(req);
return;
}
}
static void on_write_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
}
uv_fs_t close_req;
uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
uv_fs_req_cleanup(&close_req);
req->completed = 1;
complete_request(req);
}
static void on_open_for_write(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
req->fd = (int)uv_req->result;
uv_fs_req_cleanup(uv_req);
uv_buf_t buf = uv_buf_init(req->data, (unsigned int)req->data_len);
int write_result = uv_fs_write(uv_default_loop(), uv_req, req->fd, &buf, 1, 0, on_write_complete);
if (write_result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(write_result));
req->completed = 1;
uv_fs_t close_req;
uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
uv_fs_req_cleanup(&close_req);
complete_request(req);
return;
}
}
static void on_unlink_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
}
uv_fs_req_cleanup(uv_req);
req->completed = 1;
complete_request(req);
}
static void on_mkdir_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
if (!req->recursive || uv_req->result != UV_EEXIST) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
}}
uv_fs_req_cleanup(uv_req);
req->completed = 1;
complete_request(req);
}
static void on_rmdir_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
}
uv_fs_req_cleanup(uv_req);
req->completed = 1;
complete_request(req);
}
static void on_stat_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
jsval_t stat_obj = js_mkobj(req->js);
jsval_t proto = js_get_ctor_proto(req->js, "Stats", 5);
if (is_object_type(proto)) js_set_proto(req->js, stat_obj, proto);
uv_stat_t *st = &uv_req->statbuf;
js_set_slot(req->js, stat_obj, SLOT_DATA, js_mknum((double)st->st_mode));
js_set(req->js, stat_obj, "size", js_mknum((double)st->st_size));
js_set(req->js, stat_obj, "mode", js_mknum((double)st->st_mode));
js_set(req->js, stat_obj, "uid", js_mknum((double)st->st_uid));
js_set(req->js, stat_obj, "gid", js_mknum((double)st->st_gid));
req->completed = 1;
js_resolve_promise(req->js, req->promise, stat_obj);
remove_pending_request(req);
free_fs_request(req);
}
static void on_exists_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
jsval_t result = js_bool(uv_req->result >= 0);
req->completed = 1;
js_resolve_promise(req->js, req->promise, result);
remove_pending_request(req);
free_fs_request(req);
}
static void on_access_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
req->completed = 1;
js_resolve_promise(req->js, req->promise, js_mkundef());
remove_pending_request(req);
free_fs_request(req);
}
static void on_readdir_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
jsval_t arr = js_mkarr(req->js);
uv_dirent_t dirent;
while (uv_fs_scandir_next(uv_req, &dirent) != UV_EOF) {
jsval_t name = js_mkstr(req->js, dirent.name, strlen(dirent.name));
js_arr_push(req->js, arr, name);
}
req->completed = 1;
js_resolve_promise(req->js, req->promise, arr);
remove_pending_request(req);
free_fs_request(req);
}
static jsval_t builtin_fs_readFileSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "readFileSync() requires a path argument");
jsval_t path_val = fs_coerce_path(js, args[0]);
if (vtype(path_val) != T_STR) return js_mkerr(js, "readFileSync() path must be a string or URL");
size_t path_len;
char *path = js_getstr(js, path_val, &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
FILE *file = fopen(path_cstr, "rb");
if (!file) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
free(path_cstr); return js_mkerr(js, "%s", err_msg);
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
if (file_size < 0) {
fclose(file);
free(path_cstr);
return js_mkerr(js, "Failed to get file size");
}
char *data = malloc(file_size + 1);
if (!data) {
fclose(file);
free(path_cstr);
return js_mkerr(js, "Out of memory");
}
size_t bytes_read = fread(data, 1, file_size, file);
fclose(file);
free(path_cstr);
if (bytes_read != (size_t)file_size) {
free(data);
return js_mkerr(js, "Failed to read entire file");
}
fs_encoding_t enc = (nargs > 1) ? parse_encoding(js, args[1]) : FS_ENC_NONE;
jsval_t result = (enc != FS_ENC_NONE)
? encode_data(js, data, file_size, enc)
: fs_read_to_uint8array(js, data, file_size);
free(data);
return result;
}
static jsval_t builtin_fs_readBytesSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "readBytesSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "readBytesSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
FILE *file = fopen(path_cstr, "rb");
if (!file) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
free(path_cstr); return js_mkerr(js, "%s", err_msg);
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
if (file_size < 0) {
fclose(file);
free(path_cstr);
return js_mkerr(js, "Failed to get file size");
}
char *data = malloc(file_size);
if (!data) {
fclose(file);
free(path_cstr);
return js_mkerr(js, "Out of memory");
}
size_t bytes_read = fread(data, 1, file_size, file);
fclose(file);
free(path_cstr);
if (bytes_read != (size_t)file_size) {
free(data);
return js_mkerr(js, "Failed to read entire file");
}
jsval_t result = js_mkstr(js, data, file_size);
free(data);
return result;
}
static jsval_t builtin_fs_readFile(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "readFile() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "readFile() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_READ;
req->encoding = (nargs > 1) ? parse_encoding(js, args[1]) : FS_ENC_NONE;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_RDONLY, 0, on_open_for_read);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_readBytes(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "readBytes() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "readBytes() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_READ_BYTES;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_RDONLY, 0, on_open_for_read);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_writeFileSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "writeFileSync() requires path and data arguments");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "writeFileSync() path must be a string");
if (vtype(args[1]) != T_STR) return js_mkerr(js, "writeFileSync() data must be a string");
size_t path_len, data_len;
char *path = js_getstr(js, args[0], &path_len);
char *data = js_getstr(js, args[1], &data_len);
if (!path || !data) return js_mkerr(js, "Failed to get arguments");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
FILE *file = fopen(path_cstr, "wb");
if (!file) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
free(path_cstr); return js_mkerr(js, "%s", err_msg);
}
size_t bytes_written = fwrite(data, 1, data_len, file);
fclose(file);
free(path_cstr);
if (bytes_written != data_len) {
return js_mkerr(js, "Failed to write entire file");
}
return js_mkundef();
}
static jsval_t builtin_fs_copyFileSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "copyFileSync() requires src and dest arguments");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "copyFileSync() src must be a string");
if (vtype(args[1]) != T_STR) return js_mkerr(js, "copyFileSync() dest must be a string");
size_t src_len, dest_len;
char *src = js_getstr(js, args[0], &src_len);
char *dest = js_getstr(js, args[1], &dest_len);
if (!src || !dest) return js_mkerr(js, "Failed to get arguments");
char *src_cstr = strndup(src, src_len);
char *dest_cstr = strndup(dest, dest_len);
if (!src_cstr || !dest_cstr) {
free(src_cstr);
free(dest_cstr);
return js_mkerr(js, "Out of memory");
}
FILE *in = fopen(src_cstr, "rb");
if (!in) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to open source file: %s", strerror(errno));
free(src_cstr); free(dest_cstr);
return js_mkerr(js, "%s", err_msg);
}
FILE *out = fopen(dest_cstr, "wb");
if (!out) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to open dest file: %s", strerror(errno));
fclose(in);
free(src_cstr); free(dest_cstr);
return js_mkerr(js, "%s", err_msg);
}
char buf[8192];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
if (fwrite(buf, 1, n, out) != n) {
fclose(in);
fclose(out);
free(src_cstr);
free(dest_cstr);
return js_mkerr(js, "Failed to write to dest file");
}
}
fclose(in);
fclose(out);
free(src_cstr);
free(dest_cstr);
return js_mkundef();
}
static jsval_t builtin_fs_renameSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "renameSync() requires oldPath and newPath arguments");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "renameSync() oldPath must be a string");
if (vtype(args[1]) != T_STR) return js_mkerr(js, "renameSync() newPath must be a string");
size_t old_len, new_len;
char *old_path = js_getstr(js, args[0], &old_len);
char *new_path = js_getstr(js, args[1], &new_len);
if (!old_path || !new_path) return js_mkerr(js, "Failed to get arguments");
char *old_cstr = strndup(old_path, old_len);
char *new_cstr = strndup(new_path, new_len);
if (!old_cstr || !new_cstr) {
free(old_cstr);
free(new_cstr);
return js_mkerr(js, "Out of memory");
}
int result = rename(old_cstr, new_cstr);
free(old_cstr);
free(new_cstr);
if (result != 0) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to rename: %s", strerror(errno));
return js_mkerr(js, "%s", err_msg);
}
return js_mkundef();
}
static jsval_t builtin_fs_appendFileSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "appendFileSync() requires path and data arguments");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "appendFileSync() path must be a string");
if (vtype(args[1]) != T_STR) return js_mkerr(js, "appendFileSync() data must be a string");
size_t path_len, data_len;
char *path = js_getstr(js, args[0], &path_len);
char *data = js_getstr(js, args[1], &data_len);
if (!path || !data) return js_mkerr(js, "Failed to get arguments");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
FILE *file = fopen(path_cstr, "ab");
if (!file) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
free(path_cstr); return js_mkerr(js, "%s", err_msg);
}
size_t bytes_written = fwrite(data, 1, data_len, file);
fclose(file);
free(path_cstr);
if (bytes_written != data_len) {
return js_mkerr(js, "Failed to write entire file");
}
return js_mkundef();
}
static jsval_t builtin_fs_writeFile(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "writeFile() requires path and data arguments");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "writeFile() path must be a string");
if (vtype(args[1]) != T_STR) return js_mkerr(js, "writeFile() data must be a string");
size_t path_len, data_len;
char *path = js_getstr(js, args[0], &path_len);
char *data = js_getstr(js, args[1], &data_len);
if (!path || !data) return js_mkerr(js, "Failed to get arguments");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_WRITE;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->data = malloc(data_len);
if (!req->data) {
free(req->path);
free(req);
return js_mkerr(js, "Out of memory");
}
memcpy(req->data, data, data_len);
req->data_len = data_len;
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_WRONLY | O_CREAT | O_TRUNC, 0644, on_open_for_write);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_unlinkSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "unlinkSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "unlinkSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
int result = unlink(path_cstr);
free(path_cstr);
if (result != 0) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to unlink file: %s", strerror(errno));
return js_mkerr(js, "%s", err_msg);
}
return js_mkundef();
}
static jsval_t builtin_fs_unlink(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "unlink() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "unlink() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_UNLINK;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_unlink(uv_default_loop(), &req->uv_req, req->path, on_unlink_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_mkdirSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "mkdirSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "mkdirSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
int mode = 0755;
int recursive = 0;
if (nargs < 2) goto do_mkdir;
switch (vtype(args[1])) {
case T_NUM:
mode = (int)js_getnum(args[1]);
break;
case T_OBJ: {
jsval_t opt = args[1];
recursive = js_get(js, opt, "recursive") == js_true;
jsval_t mode_val = js_get(js, opt, "mode");
if (vtype(mode_val) == T_NUM) mode = (int)js_getnum(mode_val);
break;
}
}
do_mkdir:
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
#ifdef _WIN32
(void)mode;
int result = _mkdir(path_cstr);
#else
int result = mkdir(path_cstr, (mode_t)mode);
#endif
free(path_cstr);
if (result != 0) {
if (recursive && errno == EEXIST) {
return js_mkundef();
}
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to create directory: %s", strerror(errno));
return js_mkerr(js, "%s", err_msg);
}
return js_mkundef();
}
static jsval_t builtin_fs_mkdir(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "mkdir() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "mkdir() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
int mode = 0755;
int recursive = 0;
if (nargs >= 2) {
switch (vtype(args[1])) {
case T_NUM:
mode = (int)js_getnum(args[1]);
break;
case T_OBJ: {
jsval_t opt = args[1];
recursive = js_get(js, opt, "recursive") == js_true;
jsval_t mode_val = js_get(js, opt, "mode");
if (vtype(mode_val) == T_NUM) mode = (int)js_getnum(mode_val);
break;
}
}
}
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_MKDIR;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->recursive = recursive;
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_mkdir(uv_default_loop(), &req->uv_req, req->path, mode, on_mkdir_complete);
if (result < 0) {
if (recursive && result == UV_EEXIST) {
req->completed = 1;
complete_request(req);
} else {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
}
return req->promise;
}
static jsval_t builtin_fs_rmdirSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "rmdirSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "rmdirSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
#ifdef _WIN32
int result = _rmdir(path_cstr);
#else
int result = rmdir(path_cstr);
#endif
free(path_cstr);
if (result != 0) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to remove directory: %s", strerror(errno));
return js_mkerr(js, "%s", err_msg);
}
return js_mkundef();
}
static jsval_t builtin_fs_rmdir(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "rmdir() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "rmdir() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_RMDIR;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_rmdir(uv_default_loop(), &req->uv_req, req->path, on_rmdir_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t stat_isFile(ant_t *js, jsval_t *args, int nargs) {
jsval_t this = js_getthis(js);
jsval_t mode_val = js_get_slot(js, this, SLOT_DATA);
if (vtype(mode_val) != T_NUM) return js_false;
mode_t mode = (mode_t)js_getnum(mode_val);
return js_bool(S_ISREG(mode));
}
static jsval_t stat_isDirectory(ant_t *js, jsval_t *args, int nargs) {
jsval_t this = js_getthis(js);
jsval_t mode_val = js_get_slot(js, this, SLOT_DATA);
if (vtype(mode_val) != T_NUM) return js_false;
mode_t mode = (mode_t)js_getnum(mode_val);
return js_bool(S_ISDIR(mode));
}
static jsval_t stat_isSymbolicLink(ant_t *js, jsval_t *args, int nargs) {
jsval_t this = js_getthis(js);
jsval_t mode_val = js_get_slot(js, this, SLOT_DATA);
if (vtype(mode_val) != T_NUM) return js_false;
mode_t mode = (mode_t)js_getnum(mode_val);
return js_bool(S_ISLNK(mode));
}
static jsval_t create_stats_object(ant_t *js, struct stat *st) {
jsval_t stat_obj = js_mkobj(js);
jsval_t proto = js_get_ctor_proto(js, "Stats", 5);
if (is_special_object(proto)) js_set_proto(js, stat_obj, proto);
js_set_slot(js, stat_obj, SLOT_DATA, js_mknum((double)st->st_mode));
js_set(js, stat_obj, "size", js_mknum((double)st->st_size));
js_set(js, stat_obj, "mode", js_mknum((double)st->st_mode));
js_set(js, stat_obj, "uid", js_mknum((double)st->st_uid));
js_set(js, stat_obj, "gid", js_mknum((double)st->st_gid));
return stat_obj;
}
static const char *errno_to_code(int err_num) {
switch (err_num) {
case ENOENT: return "ENOENT";
case EACCES: return "EACCES";
case ENOTDIR: return "ENOTDIR";
case ELOOP: return "ELOOP";
case ENAMETOOLONG: return "ENAMETOOLONG";
case EOVERFLOW: return "EOVERFLOW";
case EROFS: return "EROFS";
case ETXTBSY: return "ETXTBSY";
case EEXIST: return "EEXIST";
case ENOTEMPTY: return "ENOTEMPTY";
case EISDIR: return "EISDIR";
case EBUSY: return "EBUSY";
case EINVAL: return "EINVAL";
case EPERM: return "EPERM";
case EIO: return "EIO";
default: return "UNKNOWN";
}
}
static jsval_t builtin_fs_statSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "statSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "statSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
struct stat st;
int result = stat(path_cstr, &st);
if (result != 0) {
const char *code = errno_to_code(errno);
jsval_t err = fs_err_code(js, code, "stat", path_cstr);
free(path_cstr); return err;
}
free(path_cstr);
return create_stats_object(js, &st);
}
static jsval_t builtin_fs_stat(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "stat() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "stat() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_STAT;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_stat(uv_default_loop(), &req->uv_req, req->path, on_stat_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_existsSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "existsSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "existsSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
struct stat st;
int result = stat(path_cstr, &st);
free(path_cstr);
return js_bool(result == 0);
}
static jsval_t builtin_fs_exists(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "exists() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "exists() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_EXISTS;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_stat(uv_default_loop(), &req->uv_req, req->path, on_exists_complete);
if (result < 0) {
req->completed = 1;
js_resolve_promise(req->js, req->promise, js_false);
remove_pending_request(req);
free_fs_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_accessSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "accessSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "accessSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
int mode = F_OK;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
mode = (int)js_getnum(args[1]);
}
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
int result = access(path_cstr, mode);
if (result != 0) {
const char *code = errno_to_code(errno);
jsval_t err = fs_err_code(js, code, "access", path_cstr);
free(path_cstr); return err;
}
free(path_cstr);
return js_mkundef();
}
static jsval_t builtin_fs_access(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "access() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "access() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
int mode = F_OK;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
mode = (int)js_getnum(args[1]);
}
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_ACCESS;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_access(uv_default_loop(), &req->uv_req, req->path, mode, on_access_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_readdirSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "readdirSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdirSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
uv_fs_t req;
int result = uv_fs_scandir(NULL, &req, path_cstr, 0, NULL);
free(path_cstr);
if (result < 0) {
char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to read directory: %s", uv_strerror(result));
uv_fs_req_cleanup(&req); return js_mkerr(js, "%s", err_msg);
}
jsval_t arr = js_mkarr(js);
uv_dirent_t dirent;
while (uv_fs_scandir_next(&req, &dirent) != UV_EOF) {
jsval_t name = js_mkstr(js, dirent.name, strlen(dirent.name));
js_arr_push(js, arr, name);
}
uv_fs_req_cleanup(&req);
return arr;
}
static jsval_t builtin_fs_readdir(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "readdir() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdir() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_READDIR;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_scandir(uv_default_loop(), &req->uv_req, req->path, 0, on_readdir_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static void on_write_fd_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
req->completed = 1;
js_resolve_promise(req->js, req->promise, js_mknum((double)uv_req->result));
remove_pending_request(req);
free_fs_request(req);
}
static jsval_t builtin_fs_readSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "readSync() requires fd and buffer arguments");
if (vtype(args[0]) != T_NUM) return js_mkerr(js, "readSync() fd must be a number");
int fd = (int)js_getnum(args[0]);
jsval_t ta_data_val = js_get_slot(js, args[1], SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
return js_mkerr(js, "readSync() second argument must be a Buffer, TypedArray, or DataView");
uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
size_t buf_len = ta_data->byte_length;
size_t offset = 0;
size_t length = buf_len;
int64_t position = -1;
if (nargs >= 3) {
if (vtype(args[2]) == T_OBJ) {
jsval_t off_val = js_get(js, args[2], "offset");
jsval_t len_val = js_get(js, args[2], "length");
jsval_t pos_val = js_get(js, args[2], "position");
if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
else length = buf_len - offset;
if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
} else if (vtype(args[2]) == T_NUM) {
offset = (size_t)js_getnum(args[2]);
length = buf_len - offset;
if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
}
}
if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
uv_fs_t req;
uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length);
int result = uv_fs_read(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
uv_fs_req_cleanup(&req);
if (result < 0) return js_mkerr(js, "readSync failed: %s", uv_strerror(result));
return js_mknum((double)result);
}
static jsval_t builtin_fs_writeSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "writeSync() requires fd and data arguments");
if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writeSync() fd must be a number");
int fd = (int)js_getnum(args[0]);
if (vtype(args[1]) == T_STR) {
size_t str_len;
const char *str = js_getstr(js, args[1], &str_len);
if (!str) return js_mkerr(js, "Failed to get string");
int64_t position = -1;
if (nargs >= 3 && vtype(args[2]) == T_NUM)
position = (int64_t)js_getnum(args[2]);
uv_fs_t req;
uv_buf_t buf = uv_buf_init((char *)str, (unsigned int)str_len);
int result = uv_fs_write(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
uv_fs_req_cleanup(&req);
if (result < 0) return js_mkerr(js, "writeSync failed: %s", uv_strerror(result));
return js_mknum((double)result);
}
jsval_t ta_data_val = js_get_slot(js, args[1], SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
return js_mkerr(js, "writeSync() second argument must be a Buffer, TypedArray, DataView, or string");
uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
size_t buf_len = ta_data->byte_length;
size_t offset = 0;
size_t length = buf_len;
int64_t position = -1;
if (nargs >= 3) {
if (vtype(args[2]) == T_OBJ) {
jsval_t off_val = js_get(js, args[2], "offset");
jsval_t len_val = js_get(js, args[2], "length");
jsval_t pos_val = js_get(js, args[2], "position");
if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
else length = buf_len - offset;
if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
} else if (vtype(args[2]) == T_NUM) {
offset = (size_t)js_getnum(args[2]);
length = buf_len - offset;
if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
}
}
if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
uv_fs_t req;
uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length);
int result = uv_fs_write(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
uv_fs_req_cleanup(&req);
if (result < 0) return js_mkerr(js, "writeSync failed: %s", uv_strerror(result));
return js_mknum((double)result);
}
static jsval_t builtin_fs_write_fd(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "write() requires fd and data arguments");
if (vtype(args[0]) != T_NUM) return js_mkerr(js, "write() fd must be a number");
int fd = (int)js_getnum(args[0]);
const char *write_data;
size_t write_len;
int64_t position = -1;
if (vtype(args[1]) == T_STR) {
size_t str_len;
const char *str = js_getstr(js, args[1], &str_len);
if (!str) return js_mkerr(js, "Failed to get string");
if (nargs >= 3 && vtype(args[2]) == T_NUM)
position = (int64_t)js_getnum(args[2]);
write_data = str;
write_len = str_len;
} else {
jsval_t ta_data_val = js_get_slot(js, args[1], SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
return js_mkerr(js, "write() second argument must be a Buffer, TypedArray, DataView, or string");
uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
size_t buf_len = ta_data->byte_length;
size_t offset = 0;
size_t length = buf_len;
if (nargs >= 3) {
if (vtype(args[2]) == T_OBJ) {
jsval_t off_val = js_get(js, args[2], "offset");
jsval_t len_val = js_get(js, args[2], "length");
jsval_t pos_val = js_get(js, args[2], "position");
if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
else length = buf_len - offset;
if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
} else if (vtype(args[2]) == T_NUM) {
offset = (size_t)js_getnum(args[2]);
length = buf_len - offset;
if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
}
}
if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
write_data = (const char *)(buf_data + offset);
write_len = length;
}
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_WRITE_FD;
req->promise = js_mkpromise(js);
req->fd = fd;
req->data = malloc(write_len);
if (!req->data) {
free(req);
return js_mkerr(js, "Out of memory");
}
memcpy(req->data, write_data, write_len);
req->data_len = write_len;
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
uv_buf_t buf = uv_buf_init(req->data, (unsigned int)req->data_len);
int result = uv_fs_write(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_write_fd_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static jsval_t builtin_fs_writevSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "writevSync() requires fd and buffers arguments");
if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writevSync() fd must be a number");
int fd = (int)js_getnum(args[0]);
jsoff_t arr_len = js_arr_len(js, args[1]);
if (arr_len == 0) return js_mknum(0);
int64_t position = -1;
if (nargs >= 3 && vtype(args[2]) == T_NUM)
position = (int64_t)js_getnum(args[2]);
uv_buf_t *bufs = calloc((size_t)arr_len, sizeof(uv_buf_t));
if (!bufs) return js_mkerr(js, "Out of memory");
for (jsoff_t i = 0; i < arr_len; i++) {
jsval_t item = js_arr_get(js, args[1], i);
jsval_t ta_val = js_get_slot(js, item, SLOT_BUFFER);
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val);
if (!ta || !ta->buffer || !ta->buffer->data) {
free(bufs);
return js_mkerr(js, "writevSync() buffers must contain ArrayBufferViews");
}
bufs[i] = uv_buf_init((char *)(ta->buffer->data + ta->byte_offset), (unsigned int)ta->byte_length);
}
uv_fs_t req;
int result = uv_fs_write(uv_default_loop(), &req, fd, bufs, (unsigned int)arr_len, position, NULL);
uv_fs_req_cleanup(&req);
free(bufs);
if (result < 0) return js_mkerr(js, "writevSync failed: %s", uv_strerror(result));
return js_mknum((double)result);
}
static jsval_t builtin_fs_writev_fd(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "writev() requires fd and buffers arguments");
if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writev() fd must be a number");
int fd = (int)js_getnum(args[0]);
jsoff_t arr_len = js_arr_len(js, args[1]);
if (arr_len == 0) {
jsval_t promise = js_mkpromise(js);
js_resolve_promise(js, promise, js_mknum(0));
return promise;
}
int64_t position = -1;
if (nargs >= 3 && vtype(args[2]) == T_NUM)
position = (int64_t)js_getnum(args[2]);
size_t total_len = 0;
for (jsoff_t i = 0; i < arr_len; i++) {
jsval_t item = js_arr_get(js, args[1], i);
jsval_t ta_val = js_get_slot(js, item, SLOT_BUFFER);
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val);
if (!ta || !ta->buffer || !ta->buffer->data)
return js_mkerr(js, "writev() buffers must contain ArrayBufferViews");
total_len += ta->byte_length;
}
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->data = malloc(total_len);
if (!req->data) {
free(req);
return js_mkerr(js, "Out of memory");
}
size_t off = 0;
for (jsoff_t i = 0; i < arr_len; i++) {
jsval_t item = js_arr_get(js, args[1], i);
jsval_t ta_val = js_get_slot(js, item, SLOT_BUFFER);
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val);
memcpy(req->data + off, ta->buffer->data + ta->byte_offset, ta->byte_length);
off += ta->byte_length;
}
req->js = js;
req->op_type = FS_OP_WRITE_FD;
req->promise = js_mkpromise(js);
req->fd = fd;
req->data_len = total_len;
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
uv_buf_t buf = uv_buf_init(req->data, (unsigned int)total_len);
int result = uv_fs_write(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_write_fd_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static int parse_open_flags(ant_t *js, jsval_t arg) {
if (vtype(arg) == T_NUM) return (int)js_getnum(arg);
if (vtype(arg) != T_STR) return O_RDONLY;
size_t len;
const char *str = js_getstr(js, arg, &len);
if (!str) return O_RDONLY;
if (len == 1 && str[0] == 'r') return O_RDONLY;
if (len == 2 && memcmp(str, "r+", 2) == 0) return O_RDWR;
if (len == 1 && str[0] == 'w') return O_WRONLY | O_CREAT | O_TRUNC;
if (len == 2 && memcmp(str, "wx", 2) == 0) return O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
if (len == 2 && memcmp(str, "w+", 2) == 0) return O_RDWR | O_CREAT | O_TRUNC;
if (len == 3 && memcmp(str, "wx+", 3) == 0) return O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
if (len == 1 && str[0] == 'a') return O_WRONLY | O_CREAT | O_APPEND;
if (len == 2 && memcmp(str, "ax", 2) == 0) return O_WRONLY | O_CREAT | O_APPEND | O_EXCL;
if (len == 2 && memcmp(str, "a+", 2) == 0) return O_RDWR | O_CREAT | O_APPEND;
if (len == 3 && memcmp(str, "ax+", 3) == 0) return O_RDWR | O_CREAT | O_APPEND | O_EXCL;
return O_RDONLY;
}
static jsval_t builtin_fs_openSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "openSync() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "openSync() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY;
int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666;
char *path_cstr = strndup(path, path_len);
if (!path_cstr) return js_mkerr(js, "Out of memory");
uv_fs_t req;
int result = uv_fs_open(uv_default_loop(), &req, path_cstr, flags, mode, NULL);
uv_fs_req_cleanup(&req);
free(path_cstr);
if (result < 0) return js_mkerr(js, "openSync failed: %s", uv_strerror(result));
return js_mknum((double)result);
}
static jsval_t builtin_fs_closeSync(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "closeSync() requires a fd argument");
if (vtype(args[0]) != T_NUM) return js_mkerr(js, "closeSync() fd must be a number");
int fd = (int)js_getnum(args[0]);
uv_fs_t req;
int result = uv_fs_close(uv_default_loop(), &req, fd, NULL);
uv_fs_req_cleanup(&req);
if (result < 0) return js_mkerr(js, "closeSync failed: %s", uv_strerror(result));
return js_mkundef();
}
static void on_open_fd_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
req->completed = 1;
js_resolve_promise(req->js, req->promise, js_mknum((double)uv_req->result));
remove_pending_request(req);
free_fs_request(req);
}
static jsval_t builtin_fs_open_fd(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "open() requires a path argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "open() path must be a string");
size_t path_len;
char *path = js_getstr(js, args[0], &path_len);
if (!path) return js_mkerr(js, "Failed to get path string");
int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY;
int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666;
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_OPEN;
req->promise = js_mkpromise(js);
req->path = strndup(path, path_len);
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, flags, mode, on_open_fd_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
static void on_close_fd_complete(uv_fs_t *uv_req) {
fs_request_t *req = (fs_request_t *)uv_req->data;
if (uv_req->result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)uv_req->result));
req->completed = 1;
complete_request(req);
return;
}
req->completed = 1;
js_resolve_promise(req->js, req->promise, js_mkundef());
remove_pending_request(req);
free_fs_request(req);
}
static jsval_t builtin_fs_close_fd(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "close() requires a fd argument");
if (vtype(args[0]) != T_NUM) return js_mkerr(js, "close() fd must be a number");
int fd = (int)js_getnum(args[0]);
fs_request_t *req = calloc(1, sizeof(fs_request_t));
if (!req) return js_mkerr(js, "Out of memory");
req->js = js;
req->op_type = FS_OP_CLOSE;
req->promise = js_mkpromise(js);
req->fd = fd;
req->uv_req.data = req;
utarray_push_back(pending_requests, &req);
int result = uv_fs_close(uv_default_loop(), &req->uv_req, req->fd, on_close_fd_complete);
if (result < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(result));
req->completed = 1;
complete_request(req);
}
return req->promise;
}
void init_fs_module(void) {
utarray_new(pending_requests, &ut_ptr_icd);
ant_t *js = rt->js;
jsval_t glob = js->global;
jsval_t stats_ctor = js_mkobj(js);
jsval_t stats_proto = js_mkobj(js);
js_set(js, stats_proto, "isFile", js_mkfun(stat_isFile));
js_set(js, stats_proto, "isDirectory", js_mkfun(stat_isDirectory));
js_set(js, stats_proto, "isSymbolicLink", js_mkfun(stat_isSymbolicLink));
js_set_sym(js, stats_proto, get_toStringTag_sym(), js_mkstr(js, "Stats", 5));
js_mkprop_fast(js, stats_ctor, "prototype", 9, stats_proto);
js_mkprop_fast(js, stats_ctor, "name", 4, js_mkstr(js, "Stats", 5));
js_set_descriptor(js, stats_ctor, "name", 4, 0);
js_set(js, glob, "Stats", js_obj_to_func(stats_ctor));
}
static void fs_set_promise_methods(ant_t *js, jsval_t lib) {
js_set(js, lib, "readFile", js_mkfun(builtin_fs_readFile));
js_set(js, lib, "open", js_mkfun(builtin_fs_open_fd));
js_set(js, lib, "close", js_mkfun(builtin_fs_close_fd));
js_set(js, lib, "writeFile", js_mkfun(builtin_fs_writeFile));
js_set(js, lib, "write", js_mkfun(builtin_fs_write_fd));
js_set(js, lib, "writev", js_mkfun(builtin_fs_writev_fd));
js_set(js, lib, "unlink", js_mkfun(builtin_fs_unlink));
js_set(js, lib, "mkdir", js_mkfun(builtin_fs_mkdir));
js_set(js, lib, "rmdir", js_mkfun(builtin_fs_rmdir));
js_set(js, lib, "stat", js_mkfun(builtin_fs_stat));
js_set(js, lib, "exists", js_mkfun(builtin_fs_exists));
js_set(js, lib, "access", js_mkfun(builtin_fs_access));
js_set(js, lib, "readdir", js_mkfun(builtin_fs_readdir));
}
static jsval_t fs_make_constants(ant_t *js) {
jsval_t constants = js_mkobj(js);
js_set(js, constants, "F_OK", js_mknum(F_OK));
js_set(js, constants, "R_OK", js_mknum(R_OK));
js_set(js, constants, "W_OK", js_mknum(W_OK));
js_set(js, constants, "X_OK", js_mknum(X_OK));
js_set(js, constants, "O_RDONLY", js_mknum(O_RDONLY));
js_set(js, constants, "O_WRONLY", js_mknum(O_WRONLY));
js_set(js, constants, "O_RDWR", js_mknum(O_RDWR));
js_set(js, constants, "O_CREAT", js_mknum(O_CREAT));
js_set(js, constants, "O_EXCL", js_mknum(O_EXCL));
js_set(js, constants, "O_TRUNC", js_mknum(O_TRUNC));
js_set(js, constants, "O_APPEND", js_mknum(O_APPEND));
return constants;
}
static jsval_t builtin_fs_promises_getter(ant_t *js, jsval_t *args, int nargs) {
jsval_t getter_fn = js_getcurrentfunc(js);
jsval_t cached = js_get_slot(js, getter_fn, SLOT_DATA);
if (is_object_type(cached)) return cached;
jsval_t promises = fs_promises_library(js);
js_set_slot(js, getter_fn, SLOT_DATA, promises);
return promises;
}
jsval_t fs_library(ant_t *js) {
jsval_t lib = js_mkobj(js);
fs_set_promise_methods(js, lib);
js_set(js, lib, "readFileSync", js_mkfun(builtin_fs_readFileSync));
js_set(js, lib, "readSync", js_mkfun(builtin_fs_readSync));
js_set(js, lib, "stream", js_mkfun(builtin_fs_readBytes));
js_set(js, lib, "openSync", js_mkfun(builtin_fs_openSync));
js_set(js, lib, "closeSync", js_mkfun(builtin_fs_closeSync));
js_set(js, lib, "writeFileSync", js_mkfun(builtin_fs_writeFileSync));
js_set(js, lib, "writeSync", js_mkfun(builtin_fs_writeSync));
js_set(js, lib, "writevSync", js_mkfun(builtin_fs_writevSync));
js_set(js, lib, "appendFileSync", js_mkfun(builtin_fs_appendFileSync));
js_set(js, lib, "copyFileSync", js_mkfun(builtin_fs_copyFileSync));
js_set(js, lib, "renameSync", js_mkfun(builtin_fs_renameSync));
js_set(js, lib, "unlinkSync", js_mkfun(builtin_fs_unlinkSync));
js_set(js, lib, "mkdirSync", js_mkfun(builtin_fs_mkdirSync));
js_set(js, lib, "rmdirSync", js_mkfun(builtin_fs_rmdirSync));
js_set(js, lib, "statSync", js_mkfun(builtin_fs_statSync));
js_set(js, lib, "existsSync", js_mkfun(builtin_fs_existsSync));
js_set(js, lib, "accessSync", js_mkfun(builtin_fs_accessSync));
js_set(js, lib, "readdirSync", js_mkfun(builtin_fs_readdirSync));
js_set_getter_desc(
js, lib,
"promises", 8,
js_heavy_mkfun(js, builtin_fs_promises_getter, js_mkundef()),
JS_DESC_E | JS_DESC_C
);
js_set(js, lib, "constants", fs_make_constants(js));
js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "fs", 2));
return lib;
}
jsval_t fs_promises_library(ant_t *js) {
jsval_t lib = js_mkobj(js);
fs_set_promise_methods(js, lib);
js_set(js, lib, "constants", fs_make_constants(js));
js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "fs/promises", 11));
return lib;
}
int has_pending_fs_ops(void) {
return pending_requests && utarray_len(pending_requests) > 0;
}
void fs_gc_update_roots(GC_OP_VAL_ARGS) {
if (!pending_requests) return;
unsigned int len = utarray_len(pending_requests);
for (unsigned int i = 0; i < len; i++) {
fs_request_t **reqp = (fs_request_t **)utarray_eltptr(pending_requests, i);
if (reqp && *reqp) { op_val(ctx, &(*reqp)->promise); }
}
}

File Metadata

Mime Type
text/x-c
Expires
Thu, Mar 26, 4:46 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
511736
Default Alt Text
fs.c (61 KB)

Event Timeline