MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <uv.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <stdbool.h>
8#include <sys/stat.h>
9#include <fcntl.h>
10#include <uthash.h>
11#include <utarray.h>
12#include <errno.h>
13
14#include "ant.h"
15#include "ptr.h"
16#include "utf8.h"
17#include "utils.h"
18#include "base64.h"
19#include "errors.h"
20#include "watch.h"
21#include "internal.h"
22#include "runtime.h"
23#include "descriptors.h"
24
25#include "gc/roots.h"
26#include "gc/modules.h"
27#include "silver/engine.h"
28
29#include "modules/fs.h"
30#include "modules/date.h"
31#include "modules/buffer.h"
32#include "modules/events.h"
33#include "modules/stream.h"
34#include "modules/symbol.h"
35#include "modules/url.h"
36
37typedef enum {
38 FS_ENC_NONE = 0,
39 FS_ENC_UTF8,
40 FS_ENC_UTF16LE,
41 FS_ENC_LATIN1,
42 FS_ENC_BASE64,
43 FS_ENC_BASE64URL,
44 FS_ENC_HEX,
45 FS_ENC_ASCII,
46} fs_encoding_t;
47
48typedef enum {
49 FS_OP_READ,
50 FS_OP_WRITE,
51 FS_OP_UNLINK,
52 FS_OP_MKDIR,
53 FS_OP_RMDIR,
54 FS_OP_STAT,
55 FS_OP_READ_BYTES,
56 FS_OP_EXISTS,
57 FS_OP_READDIR,
58 FS_OP_ACCESS,
59 FS_OP_REALPATH,
60 FS_OP_WRITE_FD,
61 FS_OP_READ_FD,
62 FS_OP_OPEN,
63 FS_OP_CLOSE,
64 FS_OP_MKDTEMP,
65 FS_OP_CHMOD,
66 FS_OP_RENAME
67} fs_op_type_t;
68
69typedef struct fs_request_s {
70 uv_fs_t uv_req;
71 ant_t *js;
72 ant_value_t promise;
73 ant_value_t target_buffer;
74 ant_value_t callback_fn;
75
76 char *path;
77 char *path2;
78 char *data;
79 char *error_msg;
80 size_t data_len;
81 size_t buf_offset;
82
83 fs_op_type_t op_type;
84 fs_encoding_t encoding;
85 uv_file fd;
86
87 int completed;
88 int failed;
89 int recursive;
90 int error_code;
91 int with_file_types;
92} fs_request_t;
93
94typedef enum {
95 FS_WATCH_MODE_EVENT = 0,
96 FS_WATCH_MODE_STAT
97} fs_watch_mode_t;
98
99typedef union {
100 uv_fs_event_t event;
101 uv_fs_poll_t poll;
102} fs_watcher_handle_t;
103
104typedef struct fs_watcher_s {
105 ant_t *js;
106 ant_value_t obj;
107 ant_value_t callback;
108 fs_watcher_handle_t handle;
109 uv_stat_t last_stat;
110
111 struct fs_watcher_s *next_active;
112 char *path;
113
114 bool last_stat_valid;
115 bool in_active_list;
116 bool closing;
117 bool handle_closed;
118 bool finalized;
119 bool persistent;
120
121 fs_watch_mode_t mode;
122} fs_watcher_t;
123
124typedef struct {
125 bool persistent;
126 bool recursive;
127 ant_value_t listener;
128} fs_watch_options_t;
129
130typedef struct {
131 bool persistent;
132 unsigned int interval_ms;
133 ant_value_t listener;
134} fs_watchfile_options_t;
135
136typedef struct {
137 mode_t mode;
138 double size, uid, gid;
139 double atime_ms, mtime_ms, ctime_ms, birthtime_ms;
140} fs_stat_fields_t;
141
142static ant_value_t g_dirent_proto = 0;
143static ant_value_t g_fswatcher_proto = 0;
144static ant_value_t g_fswatcher_ctor = 0;
145static ant_value_t g_filehandle_proto = 0;
146static ant_value_t g_readstream_proto = 0;
147static ant_value_t g_readstream_ctor = 0;
148static ant_value_t g_writestream_proto = 0;
149static ant_value_t g_writestream_ctor = 0;
150
151static fs_watcher_t *active_watchers = NULL;
152static UT_array *pending_requests = NULL;
153
154enum {
155 FS_WATCHER_NATIVE_TAG = 0x46535754u, // FSWT
156 FS_FILEHANDLE_NATIVE_TAG = 0x46534648u // FSFH
157};
158
159static fs_watcher_t *fs_watcher_data(ant_value_t value) {
160 if (!js_check_native_tag(value, FS_WATCHER_NATIVE_TAG)) return NULL;
161 return (fs_watcher_t *)js_get_native_ptr(value);
162}
163
164static void fs_add_active_watcher(fs_watcher_t *watcher) {
165 if (!watcher || watcher->in_active_list) return;
166 watcher->next_active = active_watchers;
167 active_watchers = watcher;
168 watcher->in_active_list = true;
169}
170
171static void fs_remove_active_watcher(fs_watcher_t *watcher) {
172 fs_watcher_t **it = NULL;
173 if (!watcher || !watcher->in_active_list) return;
174
175 for (it = &active_watchers; *it; it = &(*it)->next_active) {
176 if (*it != watcher) continue;
177 *it = watcher->next_active;
178 watcher->next_active = NULL;
179 watcher->in_active_list = false;
180 return;
181 }
182}
183
184static ant_value_t fs_call_value(
185 ant_t *js,
186 ant_value_t fn,
187 ant_value_t this_val,
188 ant_value_t *args,
189 int nargs
190) {
191 ant_value_t saved_this = js->this_val;
192 ant_value_t result = js_mkundef();
193
194 js->this_val = this_val;
195 if (vtype(fn) == T_CFUNC) result = js_as_cfunc(fn)(js, args, nargs);
196 else result = sv_vm_call(js->vm, js, fn, this_val, args, nargs, NULL, false);
197 js->this_val = saved_this;
198
199 return result;
200}
201
202static int parse_open_flags(ant_t *js, ant_value_t arg);
203static ant_value_t fs_coerce_path(ant_t *js, ant_value_t arg);
204
205static ant_value_t fs_mk_errno_error(
206 ant_t *js, int err_num,
207 const char *syscall, const char *path, const char *dest
208);
209
210static ant_value_t fs_mk_uv_error(
211 ant_t *js, int uv_code,
212 const char *syscall, const char *path, const char *dest
213);
214
215static bool fs_parse_mode(ant_t *js, ant_value_t arg, mode_t *out) {
216 if (vtype(arg) == T_NUM) {
217 double mode = js_getnum(arg);
218 if (mode < 0) return false;
219 *out = (mode_t)mode;
220 return true;
221 }
222
223 if (vtype(arg) != T_STR) return false;
224
225 size_t len = 0;
226 const char *str = js_getstr(js, arg, &len);
227 if (!str) return false;
228
229 size_t start = 0;
230 if (len > 2 && str[0] == '0' && (str[1] == 'o' || str[1] == 'O')) start = 2;
231 if (start == len) return false;
232
233 mode_t mode = 0;
234 for (size_t i = start; i < len; i++) {
235 if (str[i] < '0' || str[i] > '7') return false;
236 mode = (mode_t)((mode << 3) + (str[i] - '0'));
237 }
238
239 *out = mode;
240 return true;
241}
242
243static ant_value_t fs_stream_error(ant_t *js, ant_value_t stream_obj, const char *op, int uv_code) {
244 ant_value_t props = js_mkobj(js);
245 ant_value_t path_val = js_get(js, stream_obj, "path");
246 const char *code = uv_err_name(uv_code);
247
248 if (code) js_set(js, props, "code", js_mkstr(js, code, strlen(code)));
249 js_set(js, props, "errno", js_mknum((double)uv_code));
250 if (vtype(path_val) == T_STR) js_set(js, props, "path", path_val);
251 return js_mkerr_props(js, JS_ERR_TYPE, props, "%s failed: %s", op, uv_strerror(uv_code));
252}
253
254static ant_value_t fs_stream_push_chunk(ant_t *js, ant_value_t stream_obj, ant_value_t chunk) {
255 return stream_readable_push(js, stream_obj, chunk, js_mkundef());
256}
257
258static ant_value_t fs_stream_callback(ant_t *js, ant_value_t callback, ant_value_t value) {
259 if (!is_callable(callback)) return js_mkundef();
260 return fs_call_value(js, callback, js_mkundef(), &value, 1);
261}
262
263static int fs_stream_close_fd_sync(ant_t *js, ant_value_t stream_obj) {
264 ant_value_t fd_val = js_get(js, stream_obj, "fd");
265 uv_fs_t req;
266 int result = 0;
267
268 if (vtype(fd_val) != T_NUM) {
269 js_set(js, stream_obj, "pending", js_false);
270 js_set(js, stream_obj, "closed", js_true);
271 return 0;
272 }
273
274 result = uv_fs_close(uv_default_loop(), &req, (uv_file)js_getnum(fd_val), NULL);
275 uv_fs_req_cleanup(&req);
276
277 js_set(js, stream_obj, "fd", js_mknull());
278 js_set(js, stream_obj, "pending", js_false);
279 js_set(js, stream_obj, "closed", js_true);
280
281 return result;
282}
283
284static int fs_stream_open_fd_sync(ant_t *js, ant_value_t stream_obj) {
285 ant_value_t fd_val = js_get(js, stream_obj, "fd");
286 ant_value_t path_val = js_get(js, stream_obj, "path");
287 ant_value_t mode_val = js_get(js, stream_obj, "mode");
288 ant_value_t flags_val = js_get_slot(stream_obj, SLOT_FS_FLAGS);
289
290 size_t path_len = 0;
291 const char *path = NULL;
292 char *path_copy = NULL;
293 uv_fs_t req;
294
295 int flags = 0;
296 int mode = 0666;
297 int result = 0;
298
299 if (vtype(fd_val) == T_NUM) return (int)js_getnum(fd_val);
300 if (vtype(path_val) != T_STR) return UV_EINVAL;
301
302 path = js_getstr(js, path_val, &path_len);
303 if (!path) return UV_EINVAL;
304
305 path_copy = strndup(path, path_len);
306 if (!path_copy) return UV_ENOMEM;
307
308 flags = (vtype(flags_val) == T_NUM) ? (int)js_getnum(flags_val) : O_RDONLY;
309 if (vtype(mode_val) == T_NUM) mode = (int)js_getnum(mode_val);
310
311 result = uv_fs_open(uv_default_loop(), &req, path_copy, flags, mode, NULL);
312 uv_fs_req_cleanup(&req);
313 free(path_copy);
314
315 if (result < 0) return result;
316
317 js_set(js, stream_obj, "fd", js_mknum((double)result));
318 js_set(js, stream_obj, "pending", js_false);
319 js_set(js, stream_obj, "closed", js_false);
320
321 ant_value_t open_arg = js_mknum((double)result);
322 eventemitter_emit_args(js, stream_obj, "open", &open_arg, 1);
323 eventemitter_emit_args(js, stream_obj, "ready", NULL, 0);
324
325 return result;
326}
327
328static ant_value_t fs_stream_destroy(ant_t *js, ant_value_t *args, int nargs) {
329 ant_value_t stream_obj = js_getthis(js);
330 ant_value_t err = nargs > 0 ? args[0] : js_mknull();
331 ant_value_t callback = nargs > 1 ? args[1] : js_mkundef();
332 int result = fs_stream_close_fd_sync(js, stream_obj);
333
334 if (result < 0 && (is_null(err) || is_undefined(err)))
335 err = fs_stream_error(js, stream_obj, "close", result);
336 if (is_undefined(err)) err = js_mknull();
337
338 return fs_stream_callback(js, callback, err);
339}
340
341static ant_value_t fs_stream_close(ant_t *js, ant_value_t *args, int nargs) {
342 ant_value_t stream_obj = js_getthis(js);
343
344 if (nargs > 0 && is_callable(args[0])) {
345 eventemitter_add_listener(js, stream_obj, "close", args[0], true);
346 if (js_truthy(js, js_get(js, stream_obj, "closed")))
347 fs_call_value(js, args[0], js_mkundef(), NULL, 0);
348 }
349
350 if (!js_truthy(js, js_get(js, stream_obj, "destroyed"))) {
351 ant_value_t destroy_fn = js_getprop_fallback(js, stream_obj, "destroy");
352 if (is_callable(destroy_fn)) fs_call_value(js, destroy_fn, stream_obj, NULL, 0);
353 }
354
355 return stream_obj;
356}
357
358static ant_value_t fs_readstream__read(ant_t *js, ant_value_t *args, int nargs) {
359 ant_value_t stream_obj = js_getthis(js);
360 ant_value_t pos_val = js_get(js, stream_obj, "pos");
361 ant_value_t end_val = js_get(js, stream_obj, "end");
362 ant_value_t bytes_read_val = js_get(js, stream_obj, "bytesRead");
363
364 int fd = fs_stream_open_fd_sync(js, stream_obj);
365 int64_t pos = (vtype(pos_val) == T_NUM) ? (int64_t)js_getnum(pos_val) : 0;
366 int64_t end = (vtype(end_val) == T_NUM) ? (int64_t)js_getnum(end_val) : -1;
367
368 size_t want = 16384;
369 bool reached_eof = false;
370
371 if (fd < 0) {
372 ant_value_t err = fs_stream_error(js, stream_obj, "open", fd);
373 ant_value_t destroy_fn = js_getprop_fallback(js, stream_obj, "destroy");
374 if (is_callable(destroy_fn)) fs_call_value(js, destroy_fn, stream_obj, &err, 1);
375 return js_mkundef();
376 }
377
378 if (nargs > 0 && vtype(args[0]) == T_NUM && js_getnum(args[0]) > 0)
379 want = (size_t)js_getnum(args[0]);
380
381 if (end >= 0) {
382 if (pos > end) {
383 if (js_truthy(js, js_get(js, stream_obj, "autoClose"))) fs_stream_close_fd_sync(js, stream_obj);
384 return fs_stream_push_chunk(js, stream_obj, js_mknull());
385 }
386 if ((int64_t)want > (end - pos + 1)) want = (size_t)(end - pos + 1);
387 }
388
389 ArrayBufferData *ab = create_array_buffer_data(want);
390 if (!ab) {
391 ant_value_t err = js_mkerr(js, "Failed to allocate ReadStream buffer");
392 ant_value_t destroy_fn = js_getprop_fallback(js, stream_obj, "destroy");
393 if (is_callable(destroy_fn)) fs_call_value(js, destroy_fn, stream_obj, &err, 1);
394 return js_mkundef();
395 }
396
397 uv_fs_t req;
398 uv_buf_t buf = uv_buf_init((char *)ab->data, (unsigned int)want);
399 int result = uv_fs_read(uv_default_loop(), &req, fd, &buf, 1, pos, NULL);
400 uv_fs_req_cleanup(&req);
401
402 if (result < 0) {
403 ant_value_t err = fs_stream_error(js, stream_obj, "read", result);
404 ant_value_t destroy_fn = js_getprop_fallback(js, stream_obj, "destroy");
405 free_array_buffer_data(ab);
406 if (is_callable(destroy_fn)) fs_call_value(js, destroy_fn, stream_obj, &err, 1);
407 return js_mkundef();
408 }
409
410 if (result == 0) {
411 free_array_buffer_data(ab);
412 if (js_truthy(js, js_get(js, stream_obj, "autoClose")))
413 (void)fs_stream_close_fd_sync(js, stream_obj);
414 return fs_stream_push_chunk(js, stream_obj, js_mknull());
415 }
416
417 ant_value_t chunk = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, (size_t)result, "Buffer");
418 if (vtype(chunk) == T_ERR) {
419 free_array_buffer_data(ab);
420 return chunk;
421 }
422
423 if (result < (int)want) reached_eof = true;
424 if (end >= 0 && (pos + result - 1) >= end) reached_eof = true;
425
426 js_set(js, stream_obj, "pos", js_mknum((double)(pos + result)));
427 js_set(js, stream_obj, "bytesRead", js_mknum(
428 (vtype(bytes_read_val) == T_NUM
429 ? js_getnum(bytes_read_val) : 0.0) + (double)result
430 ));
431
432 fs_stream_push_chunk(js, stream_obj, chunk);
433
434 if (reached_eof) {
435 if (js_truthy(js, js_get(js, stream_obj, "autoClose"))) fs_stream_close_fd_sync(js, stream_obj);
436 return fs_stream_push_chunk(js, stream_obj, js_mknull());
437 }
438
439 return js_mkundef();
440}
441
442static ant_value_t fs_writestream__write(ant_t *js, ant_value_t *args, int nargs) {
443 ant_value_t stream_obj = js_getthis(js);
444 ant_value_t callback = nargs > 2 ? args[2] : js_mkundef();
445 ant_value_t pos_val = js_get(js, stream_obj, "pos");
446 ant_value_t bytes_written_val = js_get(js, stream_obj, "bytesWritten");
447
448 const uint8_t *bytes = NULL;
449 size_t len = 0;
450
451 int fd = fs_stream_open_fd_sync(js, stream_obj);
452 int64_t pos = (vtype(pos_val) == T_NUM) ? (int64_t)js_getnum(pos_val) : -1;
453 size_t offset = 0;
454
455 if (fd < 0) return fs_stream_callback(js, callback, fs_stream_error(js, stream_obj, "open", fd));
456
457 if (vtype(args[0]) == T_STR) {
458 bytes = (const uint8_t *)js_getstr(js, args[0], &len);
459 } else if (!buffer_source_get_bytes(js, args[0], &bytes, &len)) {
460 return fs_stream_callback(js, callback, js_mkerr(js, "WriteStream chunk must be a string or ArrayBufferView"));
461 }
462
463 while (offset < len) {
464 uv_fs_t req;
465 uv_buf_t buf = uv_buf_init((char *)(bytes + offset), (unsigned int)(len - offset));
466 int result = uv_fs_write(uv_default_loop(), &req, fd, &buf, 1, pos, NULL);
467 uv_fs_req_cleanup(&req);
468
469 if (result < 0) {
470 if (js_truthy(js, js_get(js, stream_obj, "autoClose"))) fs_stream_close_fd_sync(js, stream_obj);
471 return fs_stream_callback(js, callback, fs_stream_error(js, stream_obj, "write", result));
472 }
473 if (result == 0) {
474 if (js_truthy(js, js_get(js, stream_obj, "autoClose"))) fs_stream_close_fd_sync(js, stream_obj);
475 return fs_stream_callback(js, callback, js_mkerr(js, "write failed: short write"));
476 }
477
478 offset += (size_t)result;
479 if (pos >= 0) pos += result;
480 }
481
482 if (pos >= 0) js_set(js, stream_obj, "pos", js_mknum((double)pos));
483 js_set(js, stream_obj, "bytesWritten", js_mknum(
484 (vtype(bytes_written_val) == T_NUM
485 ? js_getnum(bytes_written_val) : 0.0) + (double)offset
486 ));
487
488 return fs_stream_callback(js, callback, js_mknull());
489}
490
491static ant_value_t fs_writestream__final(ant_t *js, ant_value_t *args, int nargs) {
492 ant_value_t stream_obj = js_getthis(js);
493 ant_value_t callback = nargs > 0 ? args[0] : js_mkundef();
494 ant_value_t value = js_mknull();
495
496 if (js_truthy(js, js_get(js, stream_obj, "autoClose"))) {
497 int result = fs_stream_close_fd_sync(js, stream_obj);
498 if (result < 0) value = fs_stream_error(js, stream_obj, "close", result);
499 }
500
501 return fs_stream_callback(js, callback, value);
502}
503
504static ant_value_t fs_create_readstream_impl(ant_t *js, ant_value_t path_arg, ant_value_t options_arg, ant_value_t proto) {
505 ant_value_t path_val = fs_coerce_path(js, path_arg);
506 ant_value_t options = is_object_type(options_arg) ? options_arg : js_mkobj(js);
507 ant_value_t stream_options = js_mkobj(js);
508
509 ant_value_t hwm = js_get(js, options, "highWaterMark");
510 ant_value_t flags_raw = js_get(js, options, "flags");
511 ant_value_t fd_val = js_get(js, options, "fd");
512 ant_value_t start_val = js_get(js, options, "start");
513 ant_value_t end_val = js_get(js, options, "end");
514 ant_value_t mode_val = js_get(js, options, "mode");
515
516 ant_value_t auto_close_val = js_get(js, options, "autoClose");
517 ant_value_t emit_close_val = js_get(js, options, "emitClose");
518 ant_value_t stream_obj = 0;
519
520 int flags = parse_open_flags(js, is_undefined(flags_raw) ? js_mkstr(js, "r", 1) : flags_raw);
521 if (vtype(path_val) != T_STR) return js_mkerr(js, "ReadStream path must be a string");
522 if (vtype(hwm) == T_NUM && js_getnum(hwm) > 0) js_set(js, stream_options, "highWaterMark", hwm);
523
524 stream_obj = stream_construct_readable(js, proto, stream_options);
525 if (is_err(stream_obj)) return stream_obj;
526
527 js_set(js, stream_obj, "_read", js_mkfun(fs_readstream__read));
528 js_set(js, stream_obj, "_destroy", js_mkfun(fs_stream_destroy));
529 js_set(js, stream_obj, "path", path_val);
530 js_set(js, stream_obj, "flags", is_undefined(flags_raw) ? js_mkstr(js, "r", 1) : flags_raw);
531 js_set(js, stream_obj, "mode", vtype(mode_val) == T_NUM ? mode_val : js_mknum(0666));
532 js_set(js, stream_obj, "fd", vtype(fd_val) == T_NUM ? fd_val : js_mkundef());
533 js_set(js, stream_obj, "pending", js_bool(vtype(fd_val) != T_NUM));
534 js_set(js, stream_obj, "closed", js_false);
535 js_set(js, stream_obj, "autoClose", is_undefined(auto_close_val) ? js_true : js_bool(js_truthy(js, auto_close_val)));
536 js_set(js, stream_obj, "emitClose", is_undefined(emit_close_val) ? js_true : js_bool(js_truthy(js, emit_close_val)));
537 js_set(js, stream_obj, "bytesRead", js_mknum(0));
538 js_set(js, stream_obj, "start", vtype(start_val) == T_NUM ? start_val : js_mkundef());
539 js_set(js, stream_obj, "end", vtype(end_val) == T_NUM ? end_val : js_mkundef());
540 js_set(js, stream_obj, "pos", js_mknum(vtype(start_val) == T_NUM ? js_getnum(start_val) : 0.0));
541 js_set_slot(stream_obj, SLOT_FS_FLAGS, js_mknum((double)flags));
542
543 return stream_obj;
544}
545
546static ant_value_t fs_create_writestream_impl(ant_t *js, ant_value_t path_arg, ant_value_t options_arg, ant_value_t proto) {
547 ant_value_t path_val = fs_coerce_path(js, path_arg);
548 ant_value_t options = is_object_type(options_arg) ? options_arg : js_mkobj(js);
549 ant_value_t stream_options = js_mkobj(js);
550
551 ant_value_t flags_raw = js_get(js, options, "flags");
552 ant_value_t fd_val = js_get(js, options, "fd");
553 ant_value_t start_val = js_get(js, options, "start");
554 ant_value_t mode_val = js_get(js, options, "mode");
555 ant_value_t auto_close_val = js_get(js, options, "autoClose");
556 ant_value_t emit_close_val = js_get(js, options, "emitClose");
557 ant_value_t hwm = js_get(js, options, "highWaterMark");
558 ant_value_t stream_obj = 0;
559
560 int flags = parse_open_flags(js, is_undefined(flags_raw) ? js_mkstr(js, "w", 1) : flags_raw);
561 double start_pos = (vtype(start_val) == T_NUM) ? js_getnum(start_val) : ((flags & O_APPEND) ? -1.0 : 0.0);
562
563 if (vtype(path_val) != T_STR) return js_mkerr(js, "WriteStream path must be a string");
564 if (vtype(hwm) == T_NUM && js_getnum(hwm) > 0) js_set(js, stream_options, "highWaterMark", hwm);
565
566 stream_obj = stream_construct_writable(js, proto, stream_options);
567 if (is_err(stream_obj)) return stream_obj;
568
569 js_set(js, stream_obj, "_write", js_mkfun(fs_writestream__write));
570 js_set(js, stream_obj, "_final", js_mkfun(fs_writestream__final));
571 js_set(js, stream_obj, "_destroy", js_mkfun(fs_stream_destroy));
572 js_set(js, stream_obj, "path", path_val);
573 js_set(js, stream_obj, "flags", is_undefined(flags_raw) ? js_mkstr(js, "w", 1) : flags_raw);
574 js_set(js, stream_obj, "mode", vtype(mode_val) == T_NUM ? mode_val : js_mknum(0666));
575 js_set(js, stream_obj, "fd", vtype(fd_val) == T_NUM ? fd_val : js_mkundef());
576 js_set(js, stream_obj, "pending", js_bool(vtype(fd_val) != T_NUM));
577 js_set(js, stream_obj, "closed", js_false);
578 js_set(js, stream_obj, "autoClose", is_undefined(auto_close_val) ? js_true : js_bool(js_truthy(js, auto_close_val)));
579 js_set(js, stream_obj, "emitClose", is_undefined(emit_close_val) ? js_true : js_bool(js_truthy(js, emit_close_val)));
580 js_set(js, stream_obj, "bytesWritten", js_mknum(0));
581 js_set(js, stream_obj, "start", vtype(start_val) == T_NUM ? start_val : js_mkundef());
582 js_set(js, stream_obj, "pos", js_mknum(start_pos));
583 js_set_slot(stream_obj, SLOT_FS_FLAGS, js_mknum((double)flags));
584
585 return stream_obj;
586}
587
588static ant_value_t js_readstream_ctor(ant_t *js, ant_value_t *args, int nargs) {
589 if (nargs < 1) return js_mkerr(js, "ReadStream() requires a path argument");
590 return fs_create_readstream_impl(js, args[0], nargs > 1 ? args[1] : js_mkundef(), g_readstream_proto);
591}
592
593static ant_value_t js_writestream_ctor(ant_t *js, ant_value_t *args, int nargs) {
594 if (nargs < 1) return js_mkerr(js, "WriteStream() requires a path argument");
595 return fs_create_writestream_impl(js, args[0], nargs > 1 ? args[1] : js_mkundef(), g_writestream_proto);
596}
597
598static ant_value_t builtin_fs_createReadStream(ant_t *js, ant_value_t *args, int nargs) {
599 if (nargs < 1) return js_mkerr(js, "createReadStream() requires a path argument");
600 return fs_create_readstream_impl(js, args[0], nargs > 1 ? args[1] : js_mkundef(), g_readstream_proto);
601}
602
603static ant_value_t builtin_fs_createWriteStream(ant_t *js, ant_value_t *args, int nargs) {
604 if (nargs < 1) return js_mkerr(js, "createWriteStream() requires a path argument");
605 return fs_create_writestream_impl(js, args[0], nargs > 1 ? args[1] : js_mkundef(), g_writestream_proto);
606}
607
608static void fs_init_stream_constructors(ant_t *js) {
609 if (g_readstream_ctor && g_writestream_ctor) return;
610
611 stream_init_constructors(js);
612
613 g_readstream_proto = js_mkobj(js);
614 js_set_proto_init(g_readstream_proto, stream_readable_prototype(js));
615 js_set(js, g_readstream_proto, "close", js_mkfun(fs_stream_close));
616 js_set_sym(js, g_readstream_proto, get_toStringTag_sym(), js_mkstr(js, "ReadStream", 10));
617 g_readstream_ctor = js_make_ctor(js, js_readstream_ctor, g_readstream_proto, "ReadStream", 10);
618 js_set_proto_init(g_readstream_ctor, stream_readable_constructor(js));
619
620 g_writestream_proto = js_mkobj(js);
621 js_set_proto_init(g_writestream_proto, stream_writable_prototype(js));
622 js_set(js, g_writestream_proto, "close", js_mkfun(fs_stream_close));
623 js_set_sym(js, g_writestream_proto, get_toStringTag_sym(), js_mkstr(js, "WriteStream", 11));
624 g_writestream_ctor = js_make_ctor(js, js_writestream_ctor, g_writestream_proto, "WriteStream", 11);
625 js_set_proto_init(g_writestream_ctor, stream_writable_constructor(js));
626}
627
628static ant_value_t fs_make_date(ant_t *js, double ms) {
629 ant_value_t obj = js_mkobj(js);
630 ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4);
631 if (is_object_type(date_proto)) js_set_proto_init(obj, date_proto);
632 js_set_slot(obj, SLOT_DATA, tov(ms));
633 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_DATE));
634 return obj;
635}
636
637static ant_value_t fs_stats_object_new(ant_t *js, const fs_stat_fields_t *f) {
638 ant_value_t stat_obj = js_mkobj(js);
639 ant_value_t proto = js_get_ctor_proto(js, "Stats", 5);
640
641 if (is_object_type(proto) || is_special_object(proto))
642 js_set_proto_init(stat_obj, proto);
643
644 js_set_slot(stat_obj, SLOT_DATA, js_mknum((double)f->mode));
645 js_set(js, stat_obj, "size", js_mknum(f->size));
646 js_set(js, stat_obj, "mode", js_mknum((double)f->mode));
647 js_set(js, stat_obj, "uid", js_mknum(f->uid));
648 js_set(js, stat_obj, "gid", js_mknum(f->gid));
649
650 js_set(js, stat_obj, "atimeMs", js_mknum(f->atime_ms));
651 js_set(js, stat_obj, "mtimeMs", js_mknum(f->mtime_ms));
652 js_set(js, stat_obj, "ctimeMs", js_mknum(f->ctime_ms));
653 js_set(js, stat_obj, "birthtimeMs", js_mknum(f->birthtime_ms));
654
655 js_set(js, stat_obj, "atime", fs_make_date(js, f->atime_ms));
656 js_set(js, stat_obj, "mtime", fs_make_date(js, f->mtime_ms));
657 js_set(js, stat_obj, "ctime", fs_make_date(js, f->ctime_ms));
658 js_set(js, stat_obj, "birthtime", fs_make_date(js, f->birthtime_ms));
659
660 return stat_obj;
661}
662
663static double uv_ts_to_ms(uv_timespec_t ts) {
664 return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6;
665}
666
667static double posix_ts_to_ms(struct timespec ts) {
668 return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6;
669}
670
671#ifdef _WIN32
672 #define POSIX_ATIME_MS(st) ((double)(st)->st_atime * 1000.0)
673 #define POSIX_MTIME_MS(st) ((double)(st)->st_mtime * 1000.0)
674 #define POSIX_CTIME_MS(st) ((double)(st)->st_ctime * 1000.0)
675 #define POSIX_BIRTH_MS(st) 0.0
676#elif defined(__APPLE__)
677 #define POSIX_ATIME_MS(st) posix_ts_to_ms((st)->st_atimespec)
678 #define POSIX_MTIME_MS(st) posix_ts_to_ms((st)->st_mtimespec)
679 #define POSIX_CTIME_MS(st) posix_ts_to_ms((st)->st_ctimespec)
680 #define POSIX_BIRTH_MS(st) posix_ts_to_ms((st)->st_birthtimespec)
681#elif defined(__linux__)
682 #define POSIX_ATIME_MS(st) posix_ts_to_ms((st)->st_atim)
683 #define POSIX_MTIME_MS(st) posix_ts_to_ms((st)->st_mtim)
684 #define POSIX_CTIME_MS(st) posix_ts_to_ms((st)->st_ctim)
685 #define POSIX_BIRTH_MS(st) 0.0
686#else
687 #define POSIX_ATIME_MS(st) posix_ts_to_ms((st)->st_atim)
688 #define POSIX_MTIME_MS(st) posix_ts_to_ms((st)->st_mtim)
689 #define POSIX_CTIME_MS(st) posix_ts_to_ms((st)->st_ctim)
690 #define POSIX_BIRTH_MS(st) 0.0
691#endif
692
693static const fs_stat_fields_t fs_stat_fields_zero = {0};
694
695static ant_value_t fs_stats_object_from_uv(ant_t *js, const uv_stat_t *st) {
696 if (!st) return fs_stats_object_new(js, &fs_stat_fields_zero);
697 return fs_stats_object_new(js, &(fs_stat_fields_t){
698 .mode = (mode_t)st->st_mode,
699 .size = (double)st->st_size,
700 .uid = (double)st->st_uid,
701 .gid = (double)st->st_gid,
702 .atime_ms = uv_ts_to_ms(st->st_atim),
703 .mtime_ms = uv_ts_to_ms(st->st_mtim),
704 .ctime_ms = uv_ts_to_ms(st->st_ctim),
705 .birthtime_ms = uv_ts_to_ms(st->st_birthtim),
706 });
707}
708
709static ant_value_t fs_stats_object_from_posix(ant_t *js, const struct stat *st) {
710 if (!st) return fs_stats_object_new(js, &fs_stat_fields_zero);
711 return fs_stats_object_new(js, &(fs_stat_fields_t){
712 .mode = st->st_mode,
713 .size = (double)st->st_size,
714 .uid = (double)st->st_uid,
715 .gid = (double)st->st_gid,
716 .atime_ms = POSIX_ATIME_MS(st),
717 .mtime_ms = POSIX_MTIME_MS(st),
718 .ctime_ms = POSIX_CTIME_MS(st),
719 .birthtime_ms = POSIX_BIRTH_MS(st),
720 });
721}
722
723static bool fs_stat_path_sync(const char *path, uv_stat_t *out) {
724 uv_fs_t req;
725 int rc = 0;
726
727 if (!path || !out) return false;
728 memset(out, 0, sizeof(*out));
729
730 rc = uv_fs_stat(NULL, &req, path, NULL);
731 if (rc < 0) {
732 uv_fs_req_cleanup(&req);
733 return false;
734 }
735
736 *out = req.statbuf;
737 uv_fs_req_cleanup(&req);
738 return true;
739}
740
741static const char *fs_watch_basename(const char *path) {
742 const char *name = NULL;
743
744 if (!path || !*path) return NULL;
745 name = strrchr(path, '/');
746#ifdef _WIN32
747 {
748 const char *alt = strrchr(path, '\\');
749 if (!name || (alt && alt > name)) name = alt;
750 }
751#endif
752 return name ? name + 1 : path;
753}
754
755static ant_value_t fs_watch_error(ant_t *js, int status, const char *path) {
756 ant_value_t props = js_mkobj(js);
757 const char *code = uv_err_name(status);
758 js_set(js, props, "code", js_mkstr(js, code, strlen(code)));
759
760 return js_mkerr_props(
761 js, JS_ERR_TYPE,
762 props, "%s: %s",
763 path ? path : "watch",
764 uv_strerror(status)
765 );
766}
767
768static void fs_watcher_free(fs_watcher_t *watcher) {
769 if (!watcher) return;
770 free(watcher->path);
771 free(watcher);
772}
773
774static uv_handle_t *fs_watcher_uv_handle(fs_watcher_t *watcher) {
775 if (!watcher) return NULL;
776 if (watcher->mode == FS_WATCH_MODE_STAT) return (uv_handle_t *)&watcher->handle.poll;
777 return (uv_handle_t *)&watcher->handle.event;
778}
779
780static void fs_watcher_on_handle_closed(uv_handle_t *handle) {
781 fs_watcher_t *watcher = (fs_watcher_t *)handle->data;
782
783 if (!watcher) return;
784 watcher->handle_closed = true;
785 watcher->closing = false;
786 if (watcher->finalized) fs_watcher_free(watcher);
787}
788
789static void fs_watcher_close_native(fs_watcher_t *watcher) {
790 uv_handle_t *handle = NULL;
791
792 if (!watcher) return;
793 if (watcher->closing || watcher->handle_closed) return;
794
795 handle = fs_watcher_uv_handle(watcher);
796 if (!handle) return;
797
798 fs_remove_active_watcher(watcher);
799 if (watcher->mode == FS_WATCH_MODE_STAT) uv_fs_poll_stop(&watcher->handle.poll);
800 else ant_watch_stop(&watcher->handle.event);
801 watcher->closing = true;
802 uv_close(handle, fs_watcher_on_handle_closed);
803}
804
805static void fs_watcher_emit_error(fs_watcher_t *watcher, int status) {
806 ant_value_t args[1];
807
808 if (!watcher || vtype(watcher->obj) != T_OBJ) return;
809 args[0] = fs_watch_error(watcher->js, status, watcher->path);
810 eventemitter_emit_args(watcher->js, watcher->obj, "error", args, 1);
811}
812
813static void fs_watcher_emit_change(fs_watcher_t *watcher, const char *filename, int events) {
814 ant_value_t args[2];
815 const char *event_name = "change";
816 const char *name = filename;
817
818 if (!watcher || vtype(watcher->obj) != T_OBJ) return;
819 if ((events & UV_RENAME) != 0) event_name = "rename";
820 if (!name || !*name) name = fs_watch_basename(watcher->path);
821
822 args[0] = js_mkstr(watcher->js, event_name, strlen(event_name));
823 args[1] = name ? js_mkstr(watcher->js, name, strlen(name)) : js_mkundef();
824 eventemitter_emit_args(watcher->js, watcher->obj, "change", args, 2);
825}
826
827static void fs_watcher_invoke_watchfile_stats(
828 fs_watcher_t *watcher,
829 const uv_stat_t *curr,
830 const uv_stat_t *prev
831) {
832 uv_stat_t curr_stat;
833 uv_stat_t prev_stat;
834 ant_value_t args[2];
835
836 if (!watcher || !is_callable(watcher->callback)) return;
837
838 memset(&curr_stat, 0, sizeof(curr_stat));
839 memset(&prev_stat, 0, sizeof(prev_stat));
840 if (curr) curr_stat = *curr;
841 if (prev) prev_stat = *prev;
842
843 watcher->last_stat = curr_stat;
844 watcher->last_stat_valid = curr != NULL;
845
846 args[0] = fs_stats_object_from_uv(watcher->js, &curr_stat);
847 args[1] = fs_stats_object_from_uv(watcher->js, &prev_stat);
848 fs_call_value(watcher->js, watcher->callback, js_mkundef(), args, 2);
849}
850
851static void fs_watcher_on_event(
852 uv_fs_event_t *handle,
853 const char *filename,
854 int events,
855 int status
856) {
857 fs_watcher_t *watcher = (fs_watcher_t *)handle->data;
858
859 if (!watcher) return;
860 if (status < 0) {
861 fs_watcher_emit_error(watcher, status);
862 return;
863 }
864
865 if ((events & (UV_CHANGE | UV_RENAME)) == 0) return;
866 fs_watcher_emit_change(watcher, filename, events);
867}
868
869static void fs_watcher_on_poll(
870 uv_fs_poll_t *handle,
871 int status,
872 const uv_stat_t *prev,
873 const uv_stat_t *curr
874) {
875 fs_watcher_t *watcher = (fs_watcher_t *)handle->data;
876 uv_stat_t missing = {0};
877
878 if (!watcher) return;
879 if (status < 0 && status != UV_ENOENT) return;
880 if (status == UV_ENOENT) {
881 if (watcher->last_stat_valid) fs_watcher_invoke_watchfile_stats(watcher, &missing, &watcher->last_stat);
882 else fs_watcher_invoke_watchfile_stats(watcher, &missing, &missing);
883 return;
884 }
885
886 fs_watcher_invoke_watchfile_stats(watcher, curr, prev);
887}
888
889static ant_value_t js_fswatcher_close(ant_t *js, ant_value_t *args, int nargs) {
890 fs_watcher_t *watcher = fs_watcher_data(js->this_val);
891
892 if (!watcher) return js->this_val;
893 fs_watcher_close_native(watcher);
894 return js->this_val;
895}
896
897static ant_value_t js_fswatcher_ref(ant_t *js, ant_value_t *args, int nargs) {
898 fs_watcher_t *watcher = fs_watcher_data(js->this_val);
899 uv_handle_t *handle = NULL;
900
901 if (!watcher || watcher->handle_closed) return js->this_val;
902 handle = fs_watcher_uv_handle(watcher);
903 if (handle) uv_ref(handle);
904 watcher->persistent = true;
905 return js->this_val;
906}
907
908static ant_value_t js_fswatcher_unref(ant_t *js, ant_value_t *args, int nargs) {
909 fs_watcher_t *watcher = fs_watcher_data(js->this_val);
910 uv_handle_t *handle = NULL;
911
912 if (!watcher || watcher->handle_closed) return js->this_val;
913 handle = fs_watcher_uv_handle(watcher);
914 if (handle) uv_unref(handle);
915 watcher->persistent = false;
916 return js->this_val;
917}
918
919static ant_value_t js_fswatcher_ctor(ant_t *js, ant_value_t *args, int nargs) {
920 return js_mkerr_typed(js, JS_ERR_TYPE, "FSWatcher cannot be constructed directly");
921}
922
923static void fs_watcher_finalize(ant_t *js, ant_object_t *obj) {
924 fs_watcher_t *watcher = NULL;
925 if (!obj) return;
926
927 watcher = (fs_watcher_t *)obj->native.ptr;
928 obj->native.ptr = NULL;
929 if (!watcher) return;
930
931 watcher->finalized = true;
932 fs_watcher_close_native(watcher);
933 if (watcher->handle_closed) fs_watcher_free(watcher);
934}
935
936static void fs_init_watch_constructors(ant_t *js) {
937 ant_value_t events = 0;
938 ant_value_t ee_ctor = 0;
939 ant_value_t ee_proto = 0;
940
941 if (g_fswatcher_proto && g_fswatcher_ctor) return;
942
943 events = events_library(js);
944 ee_ctor = js_get(js, events, "EventEmitter");
945 ee_proto = js_get(js, ee_ctor, "prototype");
946
947 g_fswatcher_proto = js_mkobj(js);
948 js_set_proto_init(g_fswatcher_proto, ee_proto);
949
950 js_set(js, g_fswatcher_proto, "close", js_mkfun(js_fswatcher_close));
951 js_set(js, g_fswatcher_proto, "ref", js_mkfun(js_fswatcher_ref));
952 js_set(js, g_fswatcher_proto, "unref", js_mkfun(js_fswatcher_unref));
953
954 js_set_sym(js, g_fswatcher_proto, get_toStringTag_sym(), js_mkstr(js, "FSWatcher", 9));
955 g_fswatcher_ctor = js_make_ctor(js, js_fswatcher_ctor, g_fswatcher_proto, "FSWatcher", 9);
956}
957
958static bool fs_parse_watch_options(ant_t *js, ant_value_t *args, int nargs, fs_watch_options_t *out) {
959 ant_value_t options = js_mkundef();
960 ant_value_t persistent_val = js_mkundef();
961
962 if (!out) return false;
963
964 memset(out, 0, sizeof(*out));
965 out->persistent = true;
966 out->listener = js_mkundef();
967
968 if (nargs > 1) {
969 if (is_callable(args[1])) out->listener = args[1];
970 else options = args[1];
971 }
972
973 if (nargs > 2 && is_callable(args[2])) out->listener = args[2];
974 if (vtype(options) == T_UNDEF || vtype(options) == T_NULL || vtype(options) == T_STR) return true;
975 if (vtype(options) != T_OBJ) return false;
976
977 persistent_val = js_get(js, options, "persistent");
978 if (vtype(persistent_val) != T_UNDEF) out->persistent = js_truthy(js, persistent_val);
979 out->recursive = js_truthy(js, js_get(js, options, "recursive"));
980 return true;
981}
982
983static bool fs_parse_watchfile_options(
984 ant_t *js,
985 ant_value_t *args,
986 int nargs,
987 fs_watchfile_options_t *out
988) {
989 ant_value_t options = js_mkundef();
990 ant_value_t persistent_val = js_mkundef();
991
992 if (!out) return false;
993
994 memset(out, 0, sizeof(*out));
995 out->persistent = true;
996 out->interval_ms = 5007;
997 out->listener = js_mkundef();
998
999 if (nargs > 1) {
1000 if (is_callable(args[1])) {
1001 out->listener = args[1];
1002 return true;
1003 }
1004 options = args[1];
1005 }
1006
1007 if (nargs > 2 && is_callable(args[2])) out->listener = args[2];
1008 if (!is_callable(out->listener)) return false;
1009 if (vtype(options) == T_UNDEF || vtype(options) == T_NULL) return true;
1010 if (vtype(options) != T_OBJ) return false;
1011
1012 persistent_val = js_get(js, options, "persistent");
1013 if (vtype(persistent_val) != T_UNDEF) out->persistent = js_truthy(js, persistent_val);
1014 {
1015 ant_value_t interval_val = js_get(js, options, "interval");
1016 if (vtype(interval_val) == T_NUM && js_getnum(interval_val) > 0)
1017 out->interval_ms = (unsigned int)js_getnum(interval_val);
1018 }
1019 return true;
1020}
1021
1022static fs_watcher_t *fs_watcher_new(ant_t *js, fs_watch_mode_t mode) {
1023 fs_watcher_t *watcher = calloc(1, sizeof(*watcher));
1024
1025 if (!watcher) return NULL;
1026 watcher->js = js;
1027 watcher->obj = js_mkundef();
1028 watcher->callback = js_mkundef();
1029 watcher->persistent = true;
1030 watcher->mode = mode;
1031
1032 return watcher;
1033}
1034
1035static int fs_watcher_start_event(fs_watcher_t *watcher, const char *path, bool persistent, bool recursive) {
1036 unsigned int flags = 0;
1037
1038 if (!watcher || !path) return UV_EINVAL;
1039#ifdef UV_FS_EVENT_RECURSIVE
1040 if (recursive) flags |= UV_FS_EVENT_RECURSIVE;
1041#else
1042 (void)recursive;
1043#endif
1044
1045 watcher->persistent = persistent;
1046 return ant_watch_start(
1047 uv_default_loop(),
1048 &watcher->handle.event,
1049 path, fs_watcher_on_event,
1050 watcher, flags, &watcher->path
1051 );
1052}
1053
1054static int fs_watcher_start_poll(fs_watcher_t *watcher, const char *path, bool persistent, unsigned int interval_ms) {
1055 int rc = 0;
1056
1057 if (!watcher || !path) return UV_EINVAL;
1058
1059 watcher->path = ant_watch_resolve_path(path);
1060 if (!watcher->path) return UV_ENOMEM;
1061
1062 rc = uv_fs_poll_init(uv_default_loop(), &watcher->handle.poll);
1063 if (rc != 0) goto fail;
1064
1065 watcher->handle.poll.data = watcher;
1066 rc = uv_fs_poll_start(&watcher->handle.poll, fs_watcher_on_poll, watcher->path, interval_ms);
1067 if (rc != 0) goto fail;
1068
1069 watcher->persistent = persistent;
1070 return 0;
1071
1072fail:
1073 free(watcher->path);
1074 watcher->path = NULL;
1075 return rc;
1076}
1077
1078static ant_value_t fs_watcher_make_object(ant_t *js, fs_watcher_t *watcher) {
1079 ant_value_t obj = 0;
1080
1081 if (!watcher) return js_mkerr(js, "Out of memory");
1082 fs_init_watch_constructors(js);
1083
1084 obj = js_mkobj(js);
1085 js_set_proto_init(obj, g_fswatcher_proto);
1086 js_set_native_ptr(obj, watcher);
1087 js_set_native_tag(obj, FS_WATCHER_NATIVE_TAG);
1088 js_set_finalizer(obj, fs_watcher_finalize);
1089 watcher->obj = obj;
1090
1091 return obj;
1092}
1093
1094static void fs_request_fail(fs_request_t *req, int uv_code) {
1095 req->failed = 1;
1096 req->error_code = uv_code;
1097 if (req->error_msg) free(req->error_msg);
1098 req->error_msg = strdup(uv_strerror(uv_code));
1099}
1100
1101static ant_value_t fs_coerce_file_url_path(ant_t *js, ant_value_t arg) {
1102 ant_value_t href = js_getprop_fallback(js, arg, "href");
1103 if (vtype(href) != T_STR) return js_mkundef();
1104
1105 const char *href_str = js_getstr(js, href, NULL);
1106 if (!href_str) return js_mkundef();
1107
1108 url_state_t parsed = {0};
1109 if (parse_url_to_state(href_str, NULL, &parsed) != 0) return js_mkundef();
1110
1111 ant_value_t path = js_mkundef();
1112 if (parsed.protocol && strcmp(parsed.protocol, "file:") == 0) {
1113 char *decoded = url_decode_component(parsed.pathname);
1114 if (decoded) {
1115 path = js_mkstr(js, decoded, strlen(decoded));
1116 free(decoded);
1117 }}
1118
1119 url_state_clear(&parsed);
1120 return path;
1121}
1122
1123static ant_value_t fs_coerce_path(ant_t *js, ant_value_t arg) {
1124 if (vtype(arg) == T_STR) return arg;
1125 if (!is_object_type(arg)) return js_mkundef();
1126
1127 ant_value_t path = fs_coerce_file_url_path(js, arg);
1128 if (!is_undefined(path)) return path;
1129
1130 path = js_get(js, arg, "pathname");
1131 if (vtype(path) == T_STR) return path;
1132
1133 return js_mkundef();
1134}
1135
1136static int fs_remove_path_recursive(const char *path, bool recursive, bool force) {
1137 uv_fs_t req;
1138
1139 int result = uv_fs_lstat(NULL, &req, path, NULL);
1140 if (result < 0) {
1141 uv_fs_req_cleanup(&req);
1142 return (force && result == UV_ENOENT) ? 0 : result;
1143 }
1144
1145 uv_stat_t statbuf = req.statbuf;
1146 uv_fs_req_cleanup(&req);
1147
1148 if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
1149 result = uv_fs_unlink(NULL, &req, path, NULL);
1150 uv_fs_req_cleanup(&req);
1151 return (force && result == UV_ENOENT) ? 0 : result;
1152 }
1153
1154 if (!recursive) return UV_EISDIR;
1155 result = uv_fs_scandir(NULL, &req, path, 0, NULL);
1156
1157 if (result < 0) {
1158 uv_fs_req_cleanup(&req);
1159 return (force && result == UV_ENOENT) ? 0 : result;
1160 }
1161
1162 for (;;) {
1163 uv_dirent_t entry;
1164 result = uv_fs_scandir_next(&req, &entry);
1165 if (result == UV_EOF) break;
1166 if (result < 0) {
1167 uv_fs_req_cleanup(&req);
1168 return result;
1169 }
1170
1171 size_t path_len = strlen(path);
1172 size_t name_len = strlen(entry.name);
1173
1174 bool needs_sep = path_len > 0 && path[path_len - 1] != '/';
1175 char *child = malloc(path_len + (needs_sep ? 1u : 0u) + name_len + 1u);
1176 if (!child) {
1177 uv_fs_req_cleanup(&req);
1178 return UV_ENOMEM;
1179 }
1180
1181 memcpy(child, path, path_len);
1182 if (needs_sep) child[path_len++] = '/';
1183 memcpy(child + path_len, entry.name, name_len + 1u);
1184
1185 result = fs_remove_path_recursive(child, true, force);
1186 free(child);
1187 if (result < 0) {
1188 uv_fs_req_cleanup(&req);
1189 return result;
1190 }
1191 }
1192
1193 uv_fs_req_cleanup(&req);
1194 result = uv_fs_rmdir(NULL, &req, path, NULL);
1195 uv_fs_req_cleanup(&req);
1196 return (force && result == UV_ENOENT) ? 0 : result;
1197}
1198
1199static bool fs_parse_rm_options(ant_t *js, ant_value_t options, bool *recursive_out, bool *force_out) {
1200 if (recursive_out) *recursive_out = false;
1201 if (force_out) *force_out = false;
1202
1203 if (vtype(options) == T_UNDEF || vtype(options) == T_NULL) return true;
1204 if (vtype(options) != T_OBJ) return false;
1205
1206 if (recursive_out) *recursive_out = js_truthy(js, js_get(js, options, "recursive"));
1207 if (force_out) *force_out = js_truthy(js, js_get(js, options, "force"));
1208 return true;
1209}
1210
1211static ant_value_t fs_rm_impl(ant_t *js, ant_value_t *args, int nargs, bool return_promise) {
1212 ant_value_t promise = 0;
1213 ant_value_t path_val;
1214 size_t path_len = 0;
1215 const char *path = NULL;
1216 bool recursive = false;
1217 bool force = false;
1218 int result;
1219
1220 if (return_promise) promise = js_mkpromise(js);
1221 if (nargs < 1) {
1222 ant_value_t err = js_mkerr(js, "rm() requires a path argument");
1223 if (!return_promise) return err;
1224 js_reject_promise(js, promise, err);
1225 return promise;
1226 }
1227
1228 path_val = fs_coerce_path(js, args[0]);
1229 if (vtype(path_val) != T_STR) {
1230 ant_value_t err = js_mkerr(js, "rm() path must be a string");
1231 if (!return_promise) return err;
1232 js_reject_promise(js, promise, err);
1233 return promise;
1234 }
1235
1236 path = js_getstr(js, path_val, &path_len);
1237 if (!path) {
1238 ant_value_t err = js_mkerr(js, "Failed to get path string");
1239 if (!return_promise) return err;
1240 js_reject_promise(js, promise, err);
1241 return promise;
1242 }
1243
1244 if (!fs_parse_rm_options(js, nargs >= 2 ? args[1] : js_mkundef(), &recursive, &force)) {
1245 ant_value_t err = js_mkerr_typed(js, JS_ERR_TYPE, "rm() options must be an object");
1246 if (!return_promise) return err;
1247 js_reject_promise(js, promise, err);
1248 return promise;
1249 }
1250
1251 result = fs_remove_path_recursive(path, recursive, force);
1252 if (result < 0) {
1253 ant_value_t err = js_mkerr(js, "%s", uv_strerror(result));
1254 if (!return_promise) return err;
1255 js_reject_promise(js, promise, err);
1256 return promise;
1257 }
1258
1259 if (!return_promise) return js_mkundef();
1260 js_resolve_promise(js, promise, js_mkundef());
1261 return promise;
1262}
1263
1264static fs_encoding_t parse_encoding(ant_t *js, ant_value_t arg) {
1265 size_t len;
1266 const char *str = NULL;
1267
1268 if (vtype(arg) == T_STR) str = js_getstr(js, arg, &len);
1269 else if (vtype(arg) == T_OBJ) {
1270 ant_value_t enc = js_get(js, arg, "encoding");
1271 if (vtype(enc) == T_STR) str = js_getstr(js, enc, &len);
1272 }
1273
1274 if (!str) return FS_ENC_NONE;
1275
1276 if (len == 4 && memcmp(str, "utf8", 4) == 0) return FS_ENC_UTF8;
1277 if (len == 5 && memcmp(str, "utf-8", 5) == 0) return FS_ENC_UTF8;
1278 if (len == 7 && memcmp(str, "utf16le", 7) == 0) return FS_ENC_UTF16LE;
1279 if (len == 4 && memcmp(str, "ucs2", 4) == 0) return FS_ENC_UTF16LE;
1280 if (len == 5 && memcmp(str, "ucs-2", 5) == 0) return FS_ENC_UTF16LE;
1281 if (len == 6 && memcmp(str, "latin1", 6) == 0) return FS_ENC_LATIN1;
1282 if (len == 6 && memcmp(str, "binary", 6) == 0) return FS_ENC_LATIN1;
1283 if (len == 6 && memcmp(str, "base64", 6) == 0) return FS_ENC_BASE64;
1284 if (len == 9 && memcmp(str, "base64url", 9) == 0) return FS_ENC_BASE64URL;
1285 if (len == 3 && memcmp(str, "hex", 3) == 0) return FS_ENC_HEX;
1286 if (len == 5 && memcmp(str, "ascii", 5) == 0) return FS_ENC_ASCII;
1287
1288 return FS_ENC_NONE;
1289}
1290
1291static ant_value_t encode_data(ant_t *js, const char *data, size_t len, fs_encoding_t enc) {
1292 switch (enc) {
1293 case FS_ENC_UTF8:
1294 case FS_ENC_LATIN1:
1295 case FS_ENC_ASCII: return js_mkstr(js, data, len);
1296
1297 case FS_ENC_HEX: {
1298 char *out = malloc(len * 2);
1299 if (!out) return js_mkerr(js, "Out of memory");
1300 for (size_t i = 0; i < len; i++) {
1301 out[i * 2] = hex_char((unsigned char)data[i] >> 4);
1302 out[i * 2 + 1] = hex_char(data[i]);
1303 }
1304 ant_value_t result = js_mkstr(js, out, len * 2);
1305 free(out); return result;
1306 }
1307
1308 case FS_ENC_BASE64: {
1309 size_t out_len;
1310 char *out = ant_base64_encode((const uint8_t *)data, len, &out_len);
1311 if (!out) return js_mkerr(js, "Out of memory");
1312 ant_value_t result = js_mkstr(js, out, out_len);
1313 free(out); return result;
1314 }
1315
1316 case FS_ENC_BASE64URL: {
1317 size_t out_len;
1318 char *out = ant_base64_encode((const uint8_t *)data, len, &out_len);
1319 if (!out) return js_mkerr(js, "Out of memory");
1320 for (size_t i = 0; i < out_len; i++) {
1321 if (out[i] == '+') out[i] = '-';
1322 else if (out[i] == '/') out[i] = '_';
1323 }
1324 while (out_len > 0 && out[out_len - 1] == '=') out_len--;
1325 ant_value_t result = js_mkstr(js, out, out_len);
1326 free(out); return result;
1327 }
1328
1329 case FS_ENC_UTF16LE: {
1330 const unsigned char *s = (const unsigned char *)data;
1331 char *out = malloc((len / 2) * 4);
1332 if (!out) return js_mkerr(js, "Out of memory");
1333 size_t j = 0;
1334 for (size_t i = 0; i + 1 < len; i += 2) {
1335 uint32_t cp = s[i] | (s[i + 1] << 8);
1336 bool is_hi = cp >= 0xD800 && cp <= 0xDBFF && i + 3 < len;
1337 uint32_t lo = is_hi ? s[i + 2] | (s[i + 3] << 8) : 0;
1338 bool is_pair = is_hi && lo >= 0xDC00 && lo <= 0xDFFF;
1339 if (is_pair) { cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); i += 2; }
1340 j += utf8_encode(cp, out + j);
1341 }
1342 ant_value_t result = js_mkstr(js, out, j);
1343 free(out); return result;
1344 }
1345
1346 default: return js_mkstr(js, data, len);
1347 }
1348}
1349
1350static void free_fs_request(fs_request_t *req) {
1351 if (!req) return;
1352
1353 if (req->path) free(req->path);
1354 if (req->path2) free(req->path2);
1355 if (req->data) free(req->data);
1356 if (req->error_msg) free(req->error_msg);
1357
1358 uv_fs_req_cleanup(&req->uv_req);
1359 free(req);
1360}
1361
1362static ant_value_t fs_rejected_promise(ant_t *js, ant_value_t err) {
1363 ant_value_t promise = js_mkpromise(js);
1364 js_reject_promise(js, promise, err);
1365 return promise;
1366}
1367
1368static ant_value_t fs_resolved_promise(ant_t *js, ant_value_t value) {
1369 ant_value_t promise = js_mkpromise(js);
1370 js_resolve_promise(js, promise, value);
1371 return promise;
1372}
1373
1374static ant_value_t fs_filehandle_require_this(ant_t *js) {
1375 ant_value_t this_obj = js_getthis(js);
1376 if (!is_object_type(this_obj) || !js_check_native_tag(this_obj, FS_FILEHANDLE_NATIVE_TAG))
1377 return js_mkerr_typed(js, JS_ERR_TYPE, "FileHandle method called on incompatible receiver");
1378 return this_obj;
1379}
1380
1381static ant_value_t fs_filehandle_fd_value(ant_t *js, ant_value_t handle_obj) {
1382 ant_value_t fd_val = js_get_slot(handle_obj, SLOT_DATA);
1383 if (vtype(fd_val) != T_NUM) fd_val = js_get(js, handle_obj, "fd");
1384 return fd_val;
1385}
1386
1387static ant_value_t fs_filehandle_get_fd(ant_t *js, ant_value_t handle_obj) {
1388 ant_value_t fd_val = fs_filehandle_fd_value(js, handle_obj);
1389 if (vtype(fd_val) != T_NUM || js_getnum(fd_val) < 0)
1390 return js_mkerr_typed(js, JS_ERR_TYPE, "FileHandle is closed");
1391 return fd_val;
1392}
1393
1394static void fs_filehandle_mark_closed(ant_t *js, ant_value_t handle_obj) {
1395 js_set_slot(handle_obj, SLOT_DATA, js_mkundef());
1396 js_set(js, handle_obj, "fd", js_mknum(-1));
1397}
1398
1399static ant_value_t builtin_fs_filehandle_close(ant_t *js, ant_value_t *args, int nargs) {
1400 ant_value_t handle_obj = fs_filehandle_require_this(js);
1401 if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj);
1402
1403 ant_value_t fd_val = fs_filehandle_fd_value(js, handle_obj);
1404 if (vtype(fd_val) != T_NUM || js_getnum(fd_val) < 0)
1405 return fs_resolved_promise(js, js_mkundef());
1406
1407 uv_fs_t req;
1408 int result = uv_fs_close(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), NULL);
1409 uv_fs_req_cleanup(&req);
1410 if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "close", NULL, NULL));
1411
1412 fs_filehandle_mark_closed(js, handle_obj);
1413 return fs_resolved_promise(js, js_mkundef());
1414}
1415
1416static ant_value_t builtin_fs_filehandle_stat(ant_t *js, ant_value_t *args, int nargs) {
1417 ant_value_t handle_obj = fs_filehandle_require_this(js);
1418 if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj);
1419
1420 ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj);
1421 if (is_err(fd_val)) return fs_rejected_promise(js, fd_val);
1422
1423 uv_fs_t req;
1424 int result = uv_fs_fstat(NULL, &req, (uv_file)(int)js_getnum(fd_val), NULL);
1425 if (result < 0) {
1426 ant_value_t err = fs_mk_uv_error(js, result, "fstat", NULL, NULL);
1427 uv_fs_req_cleanup(&req);
1428 return fs_rejected_promise(js, err);
1429 }
1430
1431 ant_value_t stat_obj = fs_stats_object_from_uv(js, &req.statbuf);
1432 uv_fs_req_cleanup(&req);
1433 return fs_resolved_promise(js, stat_obj);
1434}
1435
1436static ant_value_t builtin_fs_filehandle_sync(ant_t *js, ant_value_t *args, int nargs) {
1437 ant_value_t handle_obj = fs_filehandle_require_this(js);
1438 if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj);
1439
1440 ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj);
1441 if (is_err(fd_val)) return fs_rejected_promise(js, fd_val);
1442
1443 uv_fs_t req;
1444 int result = uv_fs_fsync(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), NULL);
1445 uv_fs_req_cleanup(&req);
1446 if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "fsync", NULL, NULL));
1447
1448 return fs_resolved_promise(js, js_mkundef());
1449}
1450
1451static ant_value_t builtin_fs_filehandle_read(ant_t *js, ant_value_t *args, int nargs) {
1452 ant_value_t handle_obj = fs_filehandle_require_this(js);
1453 if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj);
1454
1455 ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj);
1456 if (is_err(fd_val)) return fs_rejected_promise(js, fd_val);
1457 if (nargs < 1) return fs_rejected_promise(js, js_mkerr(js, "FileHandle.read() requires a buffer argument"));
1458
1459 TypedArrayData *ta_data = buffer_get_typedarray_data(args[0]);
1460 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
1461 return fs_rejected_promise(js, js_mkerr(js, "FileHandle.read() buffer must be a Buffer or TypedArray"));
1462
1463 size_t buf_len = ta_data->byte_length;
1464 size_t offset = 0;
1465 size_t length = buf_len;
1466 int64_t position = -1;
1467
1468 if (nargs >= 2 && vtype(args[1]) == T_NUM) offset = (size_t)js_getnum(args[1]);
1469 if (nargs >= 3 && vtype(args[2]) == T_NUM) length = (size_t)js_getnum(args[2]);
1470 if (nargs >= 4 && vtype(args[3]) == T_NUM) position = (int64_t)js_getnum(args[3]);
1471
1472 if (offset > buf_len) return fs_rejected_promise(js, js_mkerr(js, "offset is out of bounds"));
1473 if (offset + length > buf_len) return fs_rejected_promise(js, js_mkerr(js, "length extends beyond buffer"));
1474
1475 uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
1476 uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length);
1477
1478 uv_fs_t req;
1479 int result = uv_fs_read(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), &buf, 1, position, NULL);
1480 uv_fs_req_cleanup(&req);
1481 if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "read", NULL, NULL));
1482
1483 ant_value_t out = js_mkobj(js);
1484 js_set(js, out, "bytesRead", js_mknum((double)result));
1485 js_set(js, out, "buffer", args[0]);
1486
1487 return fs_resolved_promise(js, out);
1488}
1489
1490static ant_value_t builtin_fs_filehandle_write(ant_t *js, ant_value_t *args, int nargs) {
1491 ant_value_t handle_obj = fs_filehandle_require_this(js);
1492 if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj);
1493
1494 ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj);
1495 if (is_err(fd_val)) return fs_rejected_promise(js, fd_val);
1496 if (nargs < 1) return fs_rejected_promise(js, js_mkerr(js, "FileHandle.write() requires a data argument"));
1497
1498 const char *write_data = NULL;
1499 size_t write_len = 0;
1500 int64_t position = -1;
1501
1502 if (vtype(args[0]) == T_STR) {
1503 write_data = js_getstr(js, args[0], &write_len);
1504 if (!write_data) return fs_rejected_promise(js, js_mkerr(js, "Failed to get string"));
1505 if (nargs >= 2 && vtype(args[1]) == T_NUM) position = (int64_t)js_getnum(args[1]);
1506 } else {
1507 TypedArrayData *ta_data = buffer_get_typedarray_data(args[0]);
1508 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
1509 return fs_rejected_promise(js, js_mkerr(js, "FileHandle.write() data must be a Buffer, TypedArray, DataView, or string"));
1510
1511 uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
1512 size_t buf_len = ta_data->byte_length;
1513 size_t offset = 0;
1514 size_t length = buf_len;
1515
1516 if (nargs >= 2) {
1517 if (vtype(args[1]) == T_OBJ) {
1518 ant_value_t off_val = js_get(js, args[1], "offset");
1519 ant_value_t len_val = js_get(js, args[1], "length");
1520 ant_value_t pos_val = js_get(js, args[1], "position");
1521 if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
1522 if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
1523 else length = buf_len - offset;
1524 if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
1525 } else if (vtype(args[1]) == T_NUM) {
1526 offset = (size_t)js_getnum(args[1]);
1527 length = buf_len - offset;
1528 if (nargs >= 3 && vtype(args[2]) == T_NUM) length = (size_t)js_getnum(args[2]);
1529 if (nargs >= 4 && vtype(args[3]) == T_NUM) position = (int64_t)js_getnum(args[3]);
1530 }}
1531
1532 if (offset > buf_len) return fs_rejected_promise(js, js_mkerr(js, "offset is out of bounds"));
1533 if (offset + length > buf_len) return fs_rejected_promise(js, js_mkerr(js, "length extends beyond buffer"));
1534 write_data = (const char *)(buf_data + offset);
1535 write_len = length;
1536 }
1537
1538 uv_buf_t buf = uv_buf_init((char *)write_data, (unsigned int)write_len);
1539 uv_fs_t req;
1540 int result = uv_fs_write(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), &buf, 1, position, NULL);
1541 uv_fs_req_cleanup(&req);
1542 if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "write", NULL, NULL));
1543
1544 ant_value_t out = js_mkobj(js);
1545 js_set(js, out, "bytesWritten", js_mknum((double)result));
1546 js_set(js, out, "buffer", args[0]);
1547
1548 return fs_resolved_promise(js, out);
1549}
1550
1551static ant_value_t builtin_fs_filehandle_writeFile(ant_t *js, ant_value_t *args, int nargs) {
1552 ant_value_t handle_obj = fs_filehandle_require_this(js);
1553 if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj);
1554
1555 ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj);
1556 if (is_err(fd_val)) return fs_rejected_promise(js, fd_val);
1557 if (nargs < 1) return fs_rejected_promise(js, js_mkerr(js, "FileHandle.writeFile() requires data"));
1558
1559 const char *data = NULL;
1560 size_t len = 0;
1561
1562 if (vtype(args[0]) == T_STR) {
1563 data = js_getstr(js, args[0], &len);
1564 if (!data) return fs_rejected_promise(js, js_mkerr(js, "Failed to get string"));
1565 } else {
1566 TypedArrayData *ta_data = buffer_get_typedarray_data(args[0]);
1567 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
1568 return fs_rejected_promise(js, js_mkerr(js, "FileHandle.writeFile() data must be a Buffer, TypedArray, DataView, or string"));
1569 data = (const char *)(ta_data->buffer->data + ta_data->byte_offset);
1570 len = ta_data->byte_length;
1571 }
1572
1573 size_t written = 0;
1574 while (written < len) {
1575 uv_buf_t buf = uv_buf_init((char *)(data + written), (unsigned int)(len - written));
1576 uv_fs_t req;
1577 int result = uv_fs_write(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), &buf, 1, -1, NULL);
1578 uv_fs_req_cleanup(&req);
1579 if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "write", NULL, NULL));
1580 if (result == 0) return fs_rejected_promise(js, js_mkerr(js, "short write"));
1581 written += (size_t)result;
1582 }
1583
1584 return fs_resolved_promise(js, js_mkundef());
1585}
1586
1587static void fs_init_filehandle_proto(ant_t *js) {
1588 if (is_object_type(g_filehandle_proto)) return;
1589 g_filehandle_proto = js_mkobj(js);
1590 js_set_native_tag(g_filehandle_proto, FS_FILEHANDLE_NATIVE_TAG);
1591 js_set(js, g_filehandle_proto, "close", js_mkfun(builtin_fs_filehandle_close));
1592 js_set(js, g_filehandle_proto, "stat", js_mkfun(builtin_fs_filehandle_stat));
1593 js_set(js, g_filehandle_proto, "sync", js_mkfun(builtin_fs_filehandle_sync));
1594 js_set(js, g_filehandle_proto, "read", js_mkfun(builtin_fs_filehandle_read));
1595 js_set(js, g_filehandle_proto, "write", js_mkfun(builtin_fs_filehandle_write));
1596 js_set(js, g_filehandle_proto, "writeFile", js_mkfun(builtin_fs_filehandle_writeFile));
1597 js_set_sym(js, g_filehandle_proto, get_toStringTag_sym(), js_mkstr(js, "FileHandle", 10));
1598}
1599
1600static ant_value_t fs_make_filehandle(ant_t *js, int fd) {
1601 fs_init_filehandle_proto(js);
1602 ant_value_t handle = js_mkobj(js);
1603
1604 js_set_native_tag(handle, FS_FILEHANDLE_NATIVE_TAG);
1605 js_set_proto_init(handle, g_filehandle_proto);
1606 js_set_slot(handle, SLOT_DATA, js_mknum((double)fd));
1607 js_set(js, handle, "fd", js_mknum((double)fd));
1608
1609 return handle;
1610}
1611
1612static void remove_pending_request(fs_request_t *req) {
1613 if (!req || !pending_requests) return;
1614 unsigned int len = utarray_len(pending_requests);
1615
1616 for (unsigned int i = 0; i < len; i++) {
1617 if (*(fs_request_t**)utarray_eltptr(pending_requests, i) != req) continue;
1618 utarray_erase(pending_requests, i, 1); break;
1619 }
1620}
1621
1622static ant_value_t fs_read_to_uint8array(ant_t *js, const char *data, size_t len) {
1623 ArrayBufferData *ab = create_array_buffer_data(len);
1624 if (!ab) return js_mkundef();
1625 memcpy(ab->data, data, len);
1626 ant_value_t result = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer");
1627 if (vtype(result) == T_ERR) free_array_buffer_data(ab);
1628 return result;
1629}
1630
1631static void complete_request(fs_request_t *req) {
1632 if (req->failed) {
1633 const char *err_msg = req->error_msg
1634 ? req->error_msg
1635 : "Unknown error";
1636
1637 ant_value_t props = js_mkobj(req->js);
1638 ant_value_t reject_value;
1639
1640 if (req->error_code) {
1641 const char *code = uv_err_name(req->error_code);
1642 js_set(req->js, props, "code", js_mkstr(req->js, code, strlen(code)));
1643 js_set(req->js, props, "errno", js_mknum((double)req->error_code));
1644 if (req->path) js_set(req->js, props, "path", js_mkstr(req->js, req->path, strlen(req->path)));
1645 if (req->path2) js_set(req->js, props, "dest", js_mkstr(req->js, req->path2, strlen(req->path2)));
1646 reject_value = js_mkerr_props(req->js, JS_ERR_TYPE, props, "%s", err_msg);
1647 } else reject_value = js_mkerr(req->js, "%s", err_msg);
1648
1649 if (is_err(reject_value)) {
1650 reject_value = req->js->thrown_exists ? req->js->thrown_value : js_mkundef();
1651 if (!req->js->thrown_exists)
1652 reject_value = js_mkstr(req->js, err_msg, strlen(err_msg));
1653 req->js->thrown_exists = false;
1654 req->js->thrown_value = js_mkundef();
1655 }
1656 js_reject_promise(req->js, req->promise, reject_value);
1657 } else {
1658 ant_value_t result = js_mkundef();
1659 if (req->op_type == FS_OP_READ && req->data) {
1660 if (req->encoding != FS_ENC_NONE)
1661 result = encode_data(req->js, req->data, req->data_len, req->encoding);
1662 else result = fs_read_to_uint8array(req->js, req->data, req->data_len);
1663 } else if (req->op_type == FS_OP_READ_BYTES && req->data)
1664 result = js_mkstr(req->js, req->data, req->data_len);
1665 else if (req->op_type == FS_OP_REALPATH && req->data)
1666 result = js_mkstr(req->js, req->data, req->data_len);
1667 else if (req->op_type == FS_OP_MKDTEMP && req->data)
1668 result = js_mkstr(req->js, req->data, req->data_len);
1669 js_resolve_promise(req->js, req->promise, result);
1670 }
1671
1672 remove_pending_request(req);
1673 free_fs_request(req);
1674}
1675
1676static void on_read_complete(uv_fs_t *uv_req) {
1677 fs_request_t *req = (fs_request_t *)uv_req->data;
1678
1679 if (uv_req->result < 0) {
1680 fs_request_fail(req, (int)uv_req->result);
1681 req->completed = 1;
1682 complete_request(req);
1683 return;
1684 }
1685
1686 req->data_len = uv_req->result;
1687 uv_fs_t close_req;
1688
1689 uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
1690 uv_fs_req_cleanup(&close_req);
1691
1692 req->completed = 1;
1693 complete_request(req);
1694}
1695
1696static void on_open_for_read(uv_fs_t *uv_req) {
1697 fs_request_t *req = (fs_request_t *)uv_req->data;
1698
1699 if (uv_req->result < 0) {
1700 fs_request_fail(req, (int)uv_req->result);
1701 req->completed = 1;
1702 complete_request(req);
1703 return;
1704 }
1705
1706 req->fd = (int)uv_req->result;
1707 uv_fs_req_cleanup(uv_req);
1708
1709 uv_fs_t stat_req;
1710 int stat_result = uv_fs_fstat(uv_default_loop(), &stat_req, req->fd, NULL);
1711
1712 if (stat_result < 0) {
1713 fs_request_fail(req, stat_result);
1714 req->completed = 1;
1715 uv_fs_t close_req;
1716 uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
1717 uv_fs_req_cleanup(&close_req);
1718 complete_request(req);
1719 return;
1720 }
1721
1722 size_t file_size = stat_req.statbuf.st_size;
1723 uv_fs_req_cleanup(&stat_req);
1724
1725 size_t alloc_size = (req->op_type == FS_OP_READ) ? file_size + 1 : file_size;
1726 req->data = malloc(alloc_size);
1727 if (!req->data) {
1728 req->failed = 1;
1729 req->error_msg = strdup("Out of memory");
1730 req->completed = 1;
1731 uv_fs_t close_req;
1732 uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
1733 uv_fs_req_cleanup(&close_req);
1734 complete_request(req);
1735 return;
1736 }
1737
1738 uv_buf_t buf = uv_buf_init(req->data, (unsigned int)file_size);
1739 int read_result = uv_fs_read(uv_default_loop(), uv_req, req->fd, &buf, 1, 0, on_read_complete);
1740
1741 if (read_result < 0) {
1742 fs_request_fail(req, read_result);
1743 req->completed = 1;
1744 uv_fs_t close_req;
1745 uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
1746 uv_fs_req_cleanup(&close_req);
1747 complete_request(req);
1748 return;
1749 }
1750}
1751
1752static void on_write_complete(uv_fs_t *uv_req) {
1753 fs_request_t *req = (fs_request_t *)uv_req->data;
1754
1755 if (uv_req->result < 0) {
1756 fs_request_fail(req, (int)uv_req->result);
1757 }
1758
1759 uv_fs_t close_req;
1760 uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
1761 uv_fs_req_cleanup(&close_req);
1762
1763 req->completed = 1;
1764 complete_request(req);
1765}
1766
1767static void on_open_for_write(uv_fs_t *uv_req) {
1768 fs_request_t *req = (fs_request_t *)uv_req->data;
1769
1770 if (uv_req->result < 0) {
1771 fs_request_fail(req, (int)uv_req->result);
1772 req->completed = 1;
1773 complete_request(req);
1774 return;
1775 }
1776
1777 req->fd = (int)uv_req->result;
1778 uv_fs_req_cleanup(uv_req);
1779
1780 uv_buf_t buf = uv_buf_init(req->data, (unsigned int)req->data_len);
1781 int write_result = uv_fs_write(uv_default_loop(), uv_req, req->fd, &buf, 1, 0, on_write_complete);
1782
1783 if (write_result < 0) {
1784 fs_request_fail(req, write_result);
1785 req->completed = 1;
1786 uv_fs_t close_req;
1787 uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
1788 uv_fs_req_cleanup(&close_req);
1789 complete_request(req);
1790 return;
1791 }
1792}
1793
1794static void on_unlink_complete(uv_fs_t *uv_req) {
1795 fs_request_t *req = (fs_request_t *)uv_req->data;
1796
1797 if (uv_req->result < 0) {
1798 fs_request_fail(req, (int)uv_req->result);
1799 }
1800
1801 uv_fs_req_cleanup(uv_req);
1802 req->completed = 1;
1803 complete_request(req);
1804}
1805
1806static void on_rename_complete(uv_fs_t *uv_req) {
1807 fs_request_t *req = (fs_request_t *)uv_req->data;
1808
1809 if (uv_req->result < 0) {
1810 fs_request_fail(req, (int)uv_req->result);
1811 }
1812
1813 uv_fs_req_cleanup(uv_req);
1814 req->completed = 1;
1815 complete_request(req);
1816}
1817
1818static int mkdirp(const char *path, mode_t mode) {
1819 int result = -1;
1820 char *tmp = strdup(path);
1821 if (!tmp) goto done;
1822
1823 size_t len = strlen(tmp);
1824 if (len > 0 && tmp[len - 1] == '/') tmp[len - 1] = '\0';
1825
1826 for (char *p = tmp + 1; *p; p++) {
1827 if (*p != '/') continue;
1828 *p = '\0';
1829#ifdef _WIN32
1830 _mkdir(tmp);
1831#else
1832 mkdir(tmp, mode);
1833#endif
1834 *p = '/';
1835 }
1836
1837#ifdef _WIN32
1838 result = _mkdir(tmp);
1839#else
1840 result = mkdir(tmp, mode);
1841#endif
1842 if (result != 0 && errno == EEXIST) result = 0;
1843
1844 free(tmp);
1845done:
1846 return result;
1847}
1848
1849static void on_mkdir_complete(uv_fs_t *uv_req) {
1850 fs_request_t *req = (fs_request_t *)uv_req->data;
1851
1852 if (uv_req->result < 0) {
1853 if (!req->recursive || uv_req->result != UV_EEXIST) {
1854 fs_request_fail(req, (int)uv_req->result);
1855 }}
1856
1857 uv_fs_req_cleanup(uv_req);
1858 req->completed = 1;
1859 complete_request(req);
1860}
1861
1862static void on_rmdir_complete(uv_fs_t *uv_req) {
1863 fs_request_t *req = (fs_request_t *)uv_req->data;
1864
1865 if (uv_req->result < 0) {
1866 fs_request_fail(req, (int)uv_req->result);
1867 }
1868
1869 uv_fs_req_cleanup(uv_req);
1870 req->completed = 1;
1871 complete_request(req);
1872}
1873
1874static void on_stat_complete(uv_fs_t *uv_req) {
1875 fs_request_t *req = (fs_request_t *)uv_req->data;
1876
1877 if (uv_req->result < 0) {
1878 fs_request_fail(req, (int)uv_req->result);
1879 req->completed = 1;
1880 complete_request(req);
1881 return;
1882 }
1883
1884 ant_value_t stat_obj = fs_stats_object_from_uv(req->js, &uv_req->statbuf);
1885
1886 req->completed = 1;
1887 js_resolve_promise(req->js, req->promise, stat_obj);
1888 remove_pending_request(req);
1889 free_fs_request(req);
1890}
1891
1892static void on_realpath_complete(uv_fs_t *uv_req) {
1893 fs_request_t *req = (fs_request_t *)uv_req->data;
1894
1895 if (uv_req->result < 0 || !uv_req->ptr) {
1896 fs_request_fail(req, (int)uv_req->result);
1897 req->completed = 1;
1898 complete_request(req);
1899 return;
1900 }
1901
1902 req->data = strdup((const char *)uv_req->ptr);
1903 if (!req->data) {
1904 req->failed = 1;
1905 req->error_msg = strdup("Out of memory");
1906 req->completed = 1;
1907 complete_request(req);
1908 return;
1909 }
1910
1911 req->data_len = strlen(req->data);
1912 req->completed = 1;
1913 complete_request(req);
1914}
1915
1916static ant_value_t create_dirent_object(ant_t *js, const char *name, size_t name_len, uv_dirent_type_t type) {
1917 ant_value_t obj = js_newobj(js);
1918 js_set_proto(obj, g_dirent_proto);
1919 js_set(js, obj, "name", js_mkstr(js, name, name_len));
1920 js_set_slot(obj, SLOT_DATA, tov((double)type));
1921 return obj;
1922}
1923
1924static void on_mkdtemp_complete(uv_fs_t *uv_req) {
1925 fs_request_t *req = (fs_request_t *)uv_req->data;
1926
1927 if (uv_req->result < 0) {
1928 fs_request_fail(req, (int)uv_req->result);
1929 req->completed = 1;
1930 complete_request(req);
1931 uv_fs_req_cleanup(uv_req);
1932 return;
1933 }
1934
1935 req->data = strdup(uv_req->path);
1936 if (!req->data) {
1937 req->failed = 1;
1938 req->error_msg = strdup("Out of memory");
1939 req->completed = 1;
1940 complete_request(req);
1941 uv_fs_req_cleanup(uv_req);
1942 return;
1943 }
1944
1945 req->data_len = strlen(req->data);
1946 req->completed = 1;
1947 uv_fs_req_cleanup(uv_req);
1948 complete_request(req);
1949}
1950
1951static void on_exists_complete(uv_fs_t *uv_req) {
1952 fs_request_t *req = (fs_request_t *)uv_req->data;
1953 ant_value_t result = js_bool(uv_req->result >= 0);
1954
1955 req->completed = 1;
1956 js_resolve_promise(req->js, req->promise, result);
1957 remove_pending_request(req);
1958 free_fs_request(req);
1959}
1960
1961static void on_access_complete(uv_fs_t *uv_req) {
1962 fs_request_t *req = (fs_request_t *)uv_req->data;
1963
1964 if (uv_req->result < 0) {
1965 fs_request_fail(req, (int)uv_req->result);
1966 req->completed = 1;
1967 complete_request(req);
1968 return;
1969 }
1970
1971 req->completed = 1;
1972 js_resolve_promise(req->js, req->promise, js_mkundef());
1973 remove_pending_request(req);
1974 free_fs_request(req);
1975}
1976
1977static void on_chmod_complete(uv_fs_t *uv_req) {
1978 fs_request_t *req = (fs_request_t *)uv_req->data;
1979
1980 if (uv_req->result < 0) {
1981 fs_request_fail(req, (int)uv_req->result);
1982 req->completed = 1;
1983 complete_request(req);
1984 return;
1985 }
1986
1987 req->completed = 1;
1988 js_resolve_promise(req->js, req->promise, js_mkundef());
1989 remove_pending_request(req);
1990 free_fs_request(req);
1991}
1992
1993static void on_readdir_complete(uv_fs_t *uv_req) {
1994 fs_request_t *req = (fs_request_t *)uv_req->data;
1995
1996 if (uv_req->result < 0) {
1997 fs_request_fail(req, (int)uv_req->result);
1998 req->completed = 1;
1999 complete_request(req);
2000 return;
2001 }
2002
2003 ant_value_t arr = js_mkarr(req->js);
2004 uv_dirent_t dirent;
2005
2006 while (uv_fs_scandir_next(uv_req, &dirent) != UV_EOF) {
2007 if (req->with_file_types) {
2008 ant_value_t entry = create_dirent_object(req->js, dirent.name, strlen(dirent.name), dirent.type);
2009 js_arr_push(req->js, arr, entry);
2010 } else {
2011 ant_value_t name = js_mkstr(req->js, dirent.name, strlen(dirent.name));
2012 js_arr_push(req->js, arr, name);
2013 }}
2014
2015 req->completed = 1;
2016 js_resolve_promise(req->js, req->promise, arr);
2017 remove_pending_request(req);
2018 free_fs_request(req);
2019}
2020
2021static ant_value_t builtin_fs_readFileSync(ant_t *js, ant_value_t *args, int nargs) {
2022 if (nargs < 1) return js_mkerr(js, "readFileSync() requires a path argument");
2023
2024 ant_value_t path_val = fs_coerce_path(js, args[0]);
2025 if (vtype(path_val) != T_STR) return js_mkerr(js, "readFileSync() path must be a string or URL");
2026
2027 size_t path_len;
2028 char *path = js_getstr(js, path_val, &path_len);
2029 if (!path) return js_mkerr(js, "Failed to get path string");
2030
2031 char *path_cstr = strndup(path, path_len);
2032 if (!path_cstr) return js_mkerr(js, "Out of memory");
2033
2034 FILE *file = fopen(path_cstr, "rb");
2035 if (!file) {
2036 ant_value_t err = fs_mk_errno_error(js, errno, "open", path_cstr, NULL);
2037 free(path_cstr);
2038 return err;
2039 }
2040
2041 fseek(file, 0, SEEK_END);
2042 long file_size = ftell(file);
2043 fseek(file, 0, SEEK_SET);
2044
2045 if (file_size < 0) {
2046 fclose(file);
2047 free(path_cstr);
2048 return js_mkerr(js, "Failed to get file size");
2049 }
2050
2051 char *data = malloc(file_size + 1);
2052 if (!data) {
2053 fclose(file);
2054 free(path_cstr);
2055 return js_mkerr(js, "Out of memory");
2056 }
2057
2058 size_t bytes_read = fread(data, 1, file_size, file);
2059 fclose(file);
2060 free(path_cstr);
2061
2062 if (bytes_read != (size_t)file_size) {
2063 free(data);
2064 return js_mkerr(js, "Failed to read entire file");
2065 }
2066
2067 fs_encoding_t enc = (nargs > 1) ? parse_encoding(js, args[1]) : FS_ENC_NONE;
2068 ant_value_t result = (enc != FS_ENC_NONE)
2069 ? encode_data(js, data, file_size, enc)
2070 : fs_read_to_uint8array(js, data, file_size);
2071
2072 free(data);
2073 return result;
2074}
2075
2076static ant_value_t builtin_fs_readBytesSync(ant_t *js, ant_value_t *args, int nargs) {
2077 if (nargs < 1) return js_mkerr(js, "readBytesSync() requires a path argument");
2078 if (vtype(args[0]) != T_STR) return js_mkerr(js, "readBytesSync() path must be a string");
2079
2080 size_t path_len;
2081 char *path = js_getstr(js, args[0], &path_len);
2082 if (!path) return js_mkerr(js, "Failed to get path string");
2083
2084 char *path_cstr = strndup(path, path_len);
2085 if (!path_cstr) return js_mkerr(js, "Out of memory");
2086
2087 FILE *file = fopen(path_cstr, "rb");
2088 if (!file) {
2089 ant_value_t err = fs_mk_errno_error(js, errno, "open", path_cstr, NULL);
2090 free(path_cstr);
2091 return err;
2092 }
2093
2094 fseek(file, 0, SEEK_END);
2095 long file_size = ftell(file);
2096 fseek(file, 0, SEEK_SET);
2097
2098 if (file_size < 0) {
2099 fclose(file);
2100 free(path_cstr);
2101 return js_mkerr(js, "Failed to get file size");
2102 }
2103
2104 char *data = malloc(file_size);
2105 if (!data) {
2106 fclose(file);
2107 free(path_cstr);
2108 return js_mkerr(js, "Out of memory");
2109 }
2110
2111 size_t bytes_read = fread(data, 1, file_size, file);
2112 fclose(file);
2113 free(path_cstr);
2114
2115 if (bytes_read != (size_t)file_size) {
2116 free(data);
2117 return js_mkerr(js, "Failed to read entire file");
2118 }
2119
2120 ant_value_t result = js_mkstr(js, data, file_size);
2121 free(data);
2122
2123 return result;
2124}
2125
2126static ant_value_t builtin_fs_readFile(ant_t *js, ant_value_t *args, int nargs) {
2127 if (nargs < 1) return js_mkerr(js, "readFile() requires a path argument");
2128
2129 ant_value_t path_val = fs_coerce_path(js, args[0]);
2130 if (vtype(path_val) != T_STR) return js_mkerr(js, "readFile() path must be a string or URL");
2131
2132 size_t path_len;
2133 char *path = js_getstr(js, path_val, &path_len);
2134 if (!path) return js_mkerr(js, "Failed to get path string");
2135
2136
2137 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2138 if (!req) return js_mkerr(js, "Out of memory");
2139
2140 req->js = js;
2141 req->op_type = FS_OP_READ;
2142 req->encoding = (nargs > 1) ? parse_encoding(js, args[1]) : FS_ENC_NONE;
2143 req->promise = js_mkpromise(js);
2144 req->path = strndup(path, path_len);
2145 req->uv_req.data = req;
2146
2147 utarray_push_back(pending_requests, &req);
2148 int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_RDONLY, 0, on_open_for_read);
2149
2150 if (result < 0) {
2151 fs_request_fail(req, result);
2152 req->completed = 1;
2153 complete_request(req);
2154 }
2155
2156 return req->promise;
2157}
2158
2159static ant_value_t builtin_fs_readBytes(ant_t *js, ant_value_t *args, int nargs) {
2160 if (nargs < 1) return js_mkerr(js, "readBytes() requires a path argument");
2161
2162 if (vtype(args[0]) != T_STR) return js_mkerr(js, "readBytes() path must be a string");
2163
2164 size_t path_len;
2165 char *path = js_getstr(js, args[0], &path_len);
2166 if (!path) return js_mkerr(js, "Failed to get path string");
2167
2168
2169 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2170 if (!req) return js_mkerr(js, "Out of memory");
2171
2172 req->js = js;
2173 req->op_type = FS_OP_READ_BYTES;
2174 req->promise = js_mkpromise(js);
2175 req->path = strndup(path, path_len);
2176 req->uv_req.data = req;
2177
2178 utarray_push_back(pending_requests, &req);
2179 int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_RDONLY, 0, on_open_for_read);
2180
2181 if (result < 0) {
2182 fs_request_fail(req, result);
2183 req->completed = 1;
2184 complete_request(req);
2185 }
2186
2187 return req->promise;
2188}
2189
2190static ant_value_t fs_write_file_sync_impl(
2191 ant_t *js,
2192 ant_value_t *args,
2193 int nargs,
2194 const char *fn_name,
2195 const char *mode
2196) {
2197 if (nargs < 2) return js_mkerr(js, "%s() requires path and data arguments", fn_name);
2198 if (vtype(args[0]) != T_STR) return js_mkerr(js, "%s() path must be a string", fn_name);
2199 if (vtype(args[1]) != T_STR) return js_mkerr(js, "%s() data must be a string", fn_name);
2200
2201 size_t path_len, data_len;
2202 char *path = js_getstr(js, args[0], &path_len);
2203 char *data = js_getstr(js, args[1], &data_len);
2204 if (!path || !data) return js_mkerr(js, "Failed to get arguments");
2205
2206 char *path_cstr = strndup(path, path_len);
2207 if (!path_cstr) return js_mkerr(js, "Out of memory");
2208
2209 FILE *file = fopen(path_cstr, mode);
2210 if (!file) {
2211 ant_value_t err = fs_mk_errno_error(js, errno, "open", path_cstr, NULL);
2212 free(path_cstr);
2213 return err;
2214 }
2215
2216 size_t bytes_written = fwrite(data, 1, data_len, file);
2217 fclose(file);
2218 free(path_cstr);
2219
2220 if (bytes_written != data_len) {
2221 return js_mkerr(js, "Failed to write entire file");
2222 }
2223
2224 return js_mkundef();
2225}
2226
2227static ant_value_t builtin_fs_writeFileSync(ant_t *js, ant_value_t *args, int nargs) {
2228 return fs_write_file_sync_impl(js, args, nargs, "writeFileSync", "wb");
2229}
2230
2231static ant_value_t builtin_fs_copyFileSync(ant_t *js, ant_value_t *args, int nargs) {
2232 if (nargs < 2) return js_mkerr(js, "copyFileSync() requires src and dest arguments");
2233
2234 if (vtype(args[0]) != T_STR) return js_mkerr(js, "copyFileSync() src must be a string");
2235 if (vtype(args[1]) != T_STR) return js_mkerr(js, "copyFileSync() dest must be a string");
2236
2237 size_t src_len, dest_len;
2238 char *src = js_getstr(js, args[0], &src_len);
2239 char *dest = js_getstr(js, args[1], &dest_len);
2240
2241 if (!src || !dest) return js_mkerr(js, "Failed to get arguments");
2242
2243 char *src_cstr = strndup(src, src_len);
2244 char *dest_cstr = strndup(dest, dest_len);
2245 if (!src_cstr || !dest_cstr) {
2246 free(src_cstr);
2247 free(dest_cstr);
2248 return js_mkerr(js, "Out of memory");
2249 }
2250
2251 FILE *in = fopen(src_cstr, "rb");
2252 if (!in) {
2253 ant_value_t err = fs_mk_errno_error(js, errno, "copyfile", src_cstr, dest_cstr);
2254 free(src_cstr); free(dest_cstr);
2255 return err;
2256 }
2257
2258 FILE *out = fopen(dest_cstr, "wb");
2259 if (!out) {
2260 ant_value_t err = fs_mk_errno_error(js, errno, "copyfile", src_cstr, dest_cstr);
2261 fclose(in);
2262 free(src_cstr); free(dest_cstr);
2263 return err;
2264 }
2265
2266 char buf[8192];
2267 size_t n;
2268
2269 while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
2270 if (fwrite(buf, 1, n, out) != n) {
2271 ant_value_t err = fs_mk_errno_error(js, errno ? errno : EIO, "copyfile", src_cstr, dest_cstr);
2272 fclose(in); fclose(out);
2273 free(src_cstr); free(dest_cstr);
2274 return err;
2275 }}
2276
2277 fclose(in); fclose(out);
2278 free(src_cstr); free(dest_cstr);
2279
2280 return js_mkundef();
2281}
2282
2283typedef struct {
2284 bool recursive;
2285 bool force;
2286 bool error_on_exist;
2287} fs_cp_options_t;
2288
2289static void fs_parse_cp_options(ant_t *js, ant_value_t value, fs_cp_options_t *opts) {
2290 opts->recursive = false;
2291 opts->force = true;
2292 opts->error_on_exist = false;
2293
2294 if (vtype(value) != T_OBJ) return;
2295
2296 ant_value_t recursive = js_get(js, value, "recursive");
2297 ant_value_t force = js_get(js, value, "force");
2298 ant_value_t error_on_exist = js_get(js, value, "errorOnExist");
2299
2300 if (!is_undefined(recursive)) opts->recursive = js_truthy(js, recursive);
2301 if (!is_undefined(force)) opts->force = js_truthy(js, force);
2302 if (!is_undefined(error_on_exist)) opts->error_on_exist = js_truthy(js, error_on_exist);
2303}
2304
2305static char *fs_join_path(const char *base, const char *name) {
2306 size_t base_len = strlen(base);
2307 size_t name_len = strlen(name);
2308 bool need_sep = base_len > 0 && base[base_len - 1] != '/';
2309 size_t total_len = base_len + (need_sep ? 1 : 0) + name_len;
2310
2311 char *joined = calloc(total_len + 1, 1);
2312 if (!joined) return NULL;
2313
2314 memcpy(joined, base, base_len);
2315 if (need_sep) joined[base_len++] = '/';
2316 memcpy(joined + base_len, name, name_len);
2317 joined[total_len] = '\0';
2318 return joined;
2319}
2320
2321static ant_value_t fs_copy_file_sync_impl(
2322 ant_t *js,
2323 const char *src_cstr,
2324 const char *dest_cstr,
2325 const fs_cp_options_t *opts,
2326 const char *op_name
2327) {
2328 struct stat dest_st;
2329 if (!opts->force && stat(dest_cstr, &dest_st) == 0) {
2330 if (opts->error_on_exist) return fs_mk_errno_error(js, EEXIST, op_name, src_cstr, dest_cstr);
2331 return js_mkundef();
2332 }
2333
2334 FILE *in = fopen(src_cstr, "rb");
2335 if (!in) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr);
2336
2337 FILE *out = fopen(dest_cstr, "wb");
2338 if (!out) {
2339 ant_value_t err = fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr);
2340 fclose(in);
2341 return err;
2342 }
2343
2344 char buf[8192];
2345 size_t n = 0;
2346 while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
2347 if (fwrite(buf, 1, n, out) != n) {
2348 ant_value_t err = fs_mk_errno_error(js, errno ? errno : EIO, op_name, src_cstr, dest_cstr);
2349 fclose(in);
2350 fclose(out);
2351 return err;
2352 }}
2353
2354 fclose(in);
2355 fclose(out);
2356
2357 return js_mkundef();
2358}
2359
2360static ant_value_t builtin_fs_copyFile(ant_t *js, ant_value_t *args, int nargs) {
2361 if (nargs < 2) return js_mkerr(js, "copyFile() requires src and dest arguments");
2362 if (vtype(args[0]) != T_STR) return js_mkerr(js, "copyFile() src must be a string");
2363 if (vtype(args[1]) != T_STR) return js_mkerr(js, "copyFile() dest must be a string");
2364
2365 size_t src_len = 0, dest_len = 0;
2366 const char *src = js_getstr(js, args[0], &src_len);
2367 const char *dest = js_getstr(js, args[1], &dest_len);
2368 if (!src || !dest) return js_mkerr(js, "Failed to get arguments");
2369
2370 char *src_cstr = strndup(src, src_len);
2371 char *dest_cstr = strndup(dest, dest_len);
2372 if (!src_cstr || !dest_cstr) {
2373 free(src_cstr);
2374 free(dest_cstr);
2375 return js_mkerr(js, "Out of memory");
2376 }
2377
2378 fs_cp_options_t opts = {
2379 .recursive = false,
2380 .force = true,
2381 .error_on_exist = false,
2382 };
2383
2384 ant_value_t promise = js_mkpromise(js);
2385 ant_value_t result = fs_copy_file_sync_impl(js, src_cstr, dest_cstr, &opts, "copyFile");
2386 free(src_cstr);
2387 free(dest_cstr);
2388
2389 if (is_err(result)) js_reject_promise(js, promise, result);
2390 else js_resolve_promise(js, promise, js_mkundef());
2391
2392 return promise;
2393}
2394
2395static ant_value_t fs_copy_path_sync_impl(
2396 ant_t *js,
2397 const char *src_cstr,
2398 const char *dest_cstr,
2399 const fs_cp_options_t *opts,
2400 const char *op_name
2401) {
2402 struct stat src_st;
2403 if (stat(src_cstr, &src_st) != 0) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr);
2404
2405 if ((src_st.st_mode & S_IFMT) == S_IFDIR) {
2406 if (!opts->recursive) {
2407 return js_mkerr(js, "%s() recursive option is required to copy directories", op_name);
2408 }
2409
2410 struct stat dest_st;
2411 if (stat(dest_cstr, &dest_st) != 0) {
2412 if (errno != ENOENT) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr);
2413 #ifdef _WIN32
2414 if (_mkdir(dest_cstr) != 0) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr);
2415 #else
2416 if (mkdir(dest_cstr, (mode_t)(src_st.st_mode & 0777)) != 0) {
2417 return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr);
2418 }
2419 #endif
2420 } else if ((dest_st.st_mode & S_IFMT) != S_IFDIR) {
2421 return fs_mk_errno_error(js, EEXIST, op_name, src_cstr, dest_cstr);
2422 }
2423
2424 uv_fs_t req;
2425 int rc = uv_fs_scandir(NULL, &req, src_cstr, 0, NULL);
2426 if (rc < 0) {
2427 ant_value_t err = fs_mk_uv_error(js, rc, "scandir", src_cstr, dest_cstr);
2428 uv_fs_req_cleanup(&req);
2429 return err;
2430 }
2431
2432 uv_dirent_t dirent;
2433 ant_value_t result = js_mkundef();
2434 while (uv_fs_scandir_next(&req, &dirent) != UV_EOF) {
2435 if (
2436 strcmp(dirent.name, ".") == 0 ||
2437 strcmp(dirent.name, "..") == 0
2438 ) continue;
2439
2440 char *child_src = fs_join_path(src_cstr, dirent.name);
2441 char *child_dest = fs_join_path(dest_cstr, dirent.name);
2442 if (!child_src || !child_dest) {
2443 free(child_src);
2444 free(child_dest);
2445 result = js_mkerr(js, "Out of memory");
2446 break;
2447 }
2448
2449 result = fs_copy_path_sync_impl(js, child_src, child_dest, opts, op_name);
2450 free(child_src);
2451 free(child_dest);
2452 if (is_err(result)) break;
2453 }
2454
2455 uv_fs_req_cleanup(&req);
2456 return result;
2457 }
2458
2459 return fs_copy_file_sync_impl(js, src_cstr, dest_cstr, opts, op_name);
2460}
2461
2462static ant_value_t fs_cp_sync_common(
2463 ant_t *js,
2464 ant_value_t *args,
2465 int nargs,
2466 const char *fn_name
2467) {
2468 if (nargs < 2) return js_mkerr(js, "%s() requires src and dest arguments", fn_name);
2469 if (vtype(args[0]) != T_STR) return js_mkerr(js, "%s() src must be a string", fn_name);
2470 if (vtype(args[1]) != T_STR) return js_mkerr(js, "%s() dest must be a string", fn_name);
2471
2472 size_t src_len = 0, dest_len = 0;
2473 const char *src = js_getstr(js, args[0], &src_len);
2474 const char *dest = js_getstr(js, args[1], &dest_len);
2475 if (!src || !dest) return js_mkerr(js, "Failed to get arguments");
2476
2477 fs_cp_options_t opts;
2478 fs_parse_cp_options(js, nargs >= 3 ? args[2] : js_mkundef(), &opts);
2479
2480 char *src_cstr = strndup(src, src_len);
2481 char *dest_cstr = strndup(dest, dest_len);
2482 if (!src_cstr || !dest_cstr) {
2483 free(src_cstr);
2484 free(dest_cstr);
2485 return js_mkerr(js, "Out of memory");
2486 }
2487
2488 ant_value_t result = fs_copy_path_sync_impl(js, src_cstr, dest_cstr, &opts, fn_name);
2489 free(src_cstr);
2490 free(dest_cstr);
2491 return result;
2492}
2493
2494static ant_value_t builtin_fs_cpSync(ant_t *js, ant_value_t *args, int nargs) {
2495 return fs_cp_sync_common(js, args, nargs, "cpSync");
2496}
2497
2498static ant_value_t builtin_fs_cp(ant_t *js, ant_value_t *args, int nargs) {
2499 ant_value_t promise = js_mkpromise(js);
2500 ant_value_t result = fs_cp_sync_common(js, args, nargs, "cp");
2501 if (is_err(result)) js_reject_promise(js, promise, result);
2502 else js_resolve_promise(js, promise, js_mkundef());
2503 return promise;
2504}
2505
2506static ant_value_t builtin_fs_renameSync(ant_t *js, ant_value_t *args, int nargs) {
2507 if (nargs < 2) return js_mkerr(js, "renameSync() requires oldPath and newPath arguments");
2508
2509 if (vtype(args[0]) != T_STR) return js_mkerr(js, "renameSync() oldPath must be a string");
2510 if (vtype(args[1]) != T_STR) return js_mkerr(js, "renameSync() newPath must be a string");
2511
2512 size_t old_len, new_len;
2513 char *old_path = js_getstr(js, args[0], &old_len);
2514 char *new_path = js_getstr(js, args[1], &new_len);
2515
2516 if (!old_path || !new_path) return js_mkerr(js, "Failed to get arguments");
2517
2518 char *old_cstr = strndup(old_path, old_len);
2519 char *new_cstr = strndup(new_path, new_len);
2520 if (!old_cstr || !new_cstr) {
2521 free(old_cstr);
2522 free(new_cstr);
2523 return js_mkerr(js, "Out of memory");
2524 }
2525
2526 int result = rename(old_cstr, new_cstr);
2527
2528 if (result != 0) {
2529 ant_value_t err = fs_mk_errno_error(js, errno, "rename", old_cstr, new_cstr);
2530 free(old_cstr);
2531 free(new_cstr);
2532 return err;
2533 }
2534
2535 free(old_cstr);
2536 free(new_cstr);
2537
2538 return js_mkundef();
2539}
2540
2541static ant_value_t builtin_fs_rename(ant_t *js, ant_value_t *args, int nargs) {
2542 if (nargs < 2) return js_mkerr(js, "rename() requires oldPath and newPath arguments");
2543 if (vtype(args[0]) != T_STR) return js_mkerr(js, "rename() oldPath must be a string");
2544 if (vtype(args[1]) != T_STR) return js_mkerr(js, "rename() newPath must be a string");
2545
2546 size_t old_len = 0, new_len = 0;
2547 const char *old_path = js_getstr(js, args[0], &old_len);
2548 const char *new_path = js_getstr(js, args[1], &new_len);
2549 if (!old_path || !new_path) return js_mkerr(js, "Failed to get arguments");
2550
2551 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2552 if (!req) return js_mkerr(js, "Out of memory");
2553
2554 req->js = js;
2555 req->op_type = FS_OP_RENAME;
2556 req->promise = js_mkpromise(js);
2557 req->path = strndup(old_path, old_len);
2558 req->path2 = strndup(new_path, new_len);
2559 req->uv_req.data = req;
2560
2561 if (!req->path || !req->path2) {
2562 free_fs_request(req);
2563 return js_mkerr(js, "Out of memory");
2564 }
2565
2566 utarray_push_back(pending_requests, &req);
2567 int result = uv_fs_rename(uv_default_loop(), &req->uv_req, req->path, req->path2, on_rename_complete);
2568
2569 if (result < 0) {
2570 fs_request_fail(req, result);
2571 req->completed = 1;
2572 complete_request(req);
2573 }
2574
2575 return req->promise;
2576}
2577
2578static double fs_time_arg_to_seconds(ant_t *js, ant_value_t v) {
2579 if (is_date_instance(v)) {
2580 ant_value_t t = js_get_slot(js_as_obj(v), SLOT_DATA);
2581 return (vtype(t) == T_NUM) ? js_getnum(t) / 1000.0 : 0.0;
2582 }
2583 return js_to_number(js, v);
2584}
2585
2586static ant_value_t builtin_fs_utimesSync(ant_t *js, ant_value_t *args, int nargs) {
2587 if (nargs < 3) return js_mkerr(js, "utimesSync() requires path, atime, and mtime");
2588 if (vtype(args[0]) != T_STR) return js_mkerr(js, "utimesSync() path must be a string");
2589
2590 const char *path = js_str(js, args[0]);
2591 double atime = fs_time_arg_to_seconds(js, args[1]);
2592 double mtime = fs_time_arg_to_seconds(js, args[2]);
2593
2594 uv_fs_t req;
2595 int rc = uv_fs_utime(NULL, &req, path, atime, mtime, NULL);
2596 uv_fs_req_cleanup(&req);
2597
2598 if (rc < 0) return js_mkerr(js, "utimesSync: %s", uv_strerror(rc));
2599 return js_mkundef();
2600}
2601
2602static ant_value_t builtin_fs_utimes(ant_t *js, ant_value_t *args, int nargs) {
2603 return builtin_fs_utimesSync(js, args, nargs);
2604}
2605
2606static ant_value_t builtin_fs_futimesSync(ant_t *js, ant_value_t *args, int nargs) {
2607 if (nargs < 3) return js_mkerr(js, "futimesSync() requires fd, atime, and mtime");
2608 int fd = (int)js_to_number(js, args[0]);
2609 double atime = fs_time_arg_to_seconds(js, args[1]);
2610 double mtime = fs_time_arg_to_seconds(js, args[2]);
2611
2612 uv_fs_t req;
2613 int rc = uv_fs_futime(NULL, &req, fd, atime, mtime, NULL);
2614 uv_fs_req_cleanup(&req);
2615
2616 if (rc < 0) return js_mkerr(js, "futimesSync: %s", uv_strerror(rc));
2617 return js_mkundef();
2618}
2619
2620static ant_value_t builtin_fs_futimes(ant_t *js, ant_value_t *args, int nargs) {
2621 return builtin_fs_futimesSync(js, args, nargs);
2622}
2623
2624static ant_value_t builtin_fs_appendFileSync(ant_t *js, ant_value_t *args, int nargs) {
2625 return fs_write_file_sync_impl(js, args, nargs, "appendFileSync", "ab");
2626}
2627
2628static ant_value_t builtin_fs_appendFile(ant_t *js, ant_value_t *args, int nargs) {
2629 ant_value_t promise = js_mkpromise(js);
2630 ant_value_t result = fs_write_file_sync_impl(js, args, nargs, "appendFile", "ab");
2631 if (is_err(result)) js_reject_promise(js, promise, result);
2632 else js_resolve_promise(js, promise, js_mkundef());
2633 return promise;
2634}
2635
2636static ant_value_t builtin_fs_writeFile(ant_t *js, ant_value_t *args, int nargs) {
2637 if (nargs < 2) return js_mkerr(js, "writeFile() requires path and data arguments");
2638
2639 if (vtype(args[0]) != T_STR) return js_mkerr(js, "writeFile() path must be a string");
2640 if (vtype(args[1]) != T_STR) return js_mkerr(js, "writeFile() data must be a string");
2641
2642 size_t path_len, data_len;
2643 char *path = js_getstr(js, args[0], &path_len);
2644 char *data = js_getstr(js, args[1], &data_len);
2645
2646 if (!path || !data) return js_mkerr(js, "Failed to get arguments");
2647
2648
2649 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2650 if (!req) return js_mkerr(js, "Out of memory");
2651
2652 req->js = js;
2653 req->op_type = FS_OP_WRITE;
2654 req->promise = js_mkpromise(js);
2655 req->path = strndup(path, path_len);
2656 req->data = malloc(data_len);
2657 if (!req->data) {
2658 free(req->path);
2659 free(req);
2660 return js_mkerr(js, "Out of memory");
2661 }
2662
2663 memcpy(req->data, data, data_len);
2664 req->data_len = data_len;
2665 req->uv_req.data = req;
2666
2667 utarray_push_back(pending_requests, &req);
2668 int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_WRONLY | O_CREAT | O_TRUNC, 0644, on_open_for_write);
2669
2670 if (result < 0) {
2671 fs_request_fail(req, result);
2672 req->completed = 1;
2673 complete_request(req);
2674 }
2675
2676 return req->promise;
2677}
2678
2679static ant_value_t builtin_fs_unlinkSync(ant_t *js, ant_value_t *args, int nargs) {
2680 if (nargs < 1) return js_mkerr(js, "unlinkSync() requires a path argument");
2681
2682 if (vtype(args[0]) != T_STR) return js_mkerr(js, "unlinkSync() path must be a string");
2683
2684 size_t path_len;
2685 char *path = js_getstr(js, args[0], &path_len);
2686 if (!path) return js_mkerr(js, "Failed to get path string");
2687
2688 char *path_cstr = strndup(path, path_len);
2689 if (!path_cstr) return js_mkerr(js, "Out of memory");
2690 int result = unlink(path_cstr);
2691
2692 if (result != 0) {
2693 ant_value_t err = fs_mk_errno_error(js, errno, "unlink", path_cstr, NULL);
2694 free(path_cstr);
2695 return err;
2696 }
2697
2698 free(path_cstr);
2699 return js_mkundef();
2700}
2701
2702static ant_value_t builtin_fs_unlink(ant_t *js, ant_value_t *args, int nargs) {
2703 if (nargs < 1) return js_mkerr(js, "unlink() requires a path argument");
2704
2705 if (vtype(args[0]) != T_STR) return js_mkerr(js, "unlink() path must be a string");
2706
2707 size_t path_len;
2708 char *path = js_getstr(js, args[0], &path_len);
2709 if (!path) return js_mkerr(js, "Failed to get path string");
2710
2711
2712 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2713 if (!req) return js_mkerr(js, "Out of memory");
2714
2715 req->js = js;
2716 req->op_type = FS_OP_UNLINK;
2717 req->promise = js_mkpromise(js);
2718 req->path = strndup(path, path_len);
2719 req->uv_req.data = req;
2720
2721 utarray_push_back(pending_requests, &req);
2722 int result = uv_fs_unlink(uv_default_loop(), &req->uv_req, req->path, on_unlink_complete);
2723
2724 if (result < 0) {
2725 fs_request_fail(req, result);
2726 req->completed = 1;
2727 complete_request(req);
2728 }
2729
2730 return req->promise;
2731}
2732
2733static ant_value_t builtin_fs_mkdirSync(ant_t *js, ant_value_t *args, int nargs) {
2734 if (nargs < 1) return js_mkerr(js, "mkdirSync() requires a path argument");
2735
2736 if (vtype(args[0]) != T_STR) return js_mkerr(js, "mkdirSync() path must be a string");
2737
2738 size_t path_len;
2739 char *path = js_getstr(js, args[0], &path_len);
2740 if (!path) return js_mkerr(js, "Failed to get path string");
2741
2742 int mode = 0755;
2743 int recursive = 0;
2744
2745 if (nargs < 2) goto do_mkdir;
2746
2747 switch (vtype(args[1])) {
2748 case T_NUM:
2749 mode = (int)js_getnum(args[1]);
2750 break;
2751 case T_OBJ: {
2752 ant_value_t opt = args[1];
2753 recursive = js_get(js, opt, "recursive") == js_true;
2754 ant_value_t mode_val = js_get(js, opt, "mode");
2755 if (vtype(mode_val) == T_NUM) mode = (int)js_getnum(mode_val);
2756 break;
2757 }
2758 }
2759
2760do_mkdir:
2761
2762 char *path_cstr = strndup(path, path_len);
2763 if (!path_cstr) return js_mkerr(js, "Out of memory");
2764
2765 int result;
2766 if (recursive) {
2767 result = mkdirp(path_cstr, (mode_t)mode);
2768 } else {
2769#ifdef _WIN32
2770 (void)mode;
2771 result = _mkdir(path_cstr);
2772#else
2773 result = mkdir(path_cstr, (mode_t)mode);
2774#endif
2775 }
2776 if (result != 0) {
2777 ant_value_t err = fs_mk_errno_error(js, errno, "mkdir", path_cstr, NULL);
2778 free(path_cstr);
2779 return err;
2780 }
2781
2782 free(path_cstr);
2783
2784 return js_mkundef();
2785}
2786
2787static ant_value_t builtin_fs_mkdir(ant_t *js, ant_value_t *args, int nargs) {
2788 if (nargs < 1) return js_mkerr(js, "mkdir() requires a path argument");
2789
2790 if (vtype(args[0]) != T_STR) return js_mkerr(js, "mkdir() path must be a string");
2791
2792 size_t path_len;
2793 char *path = js_getstr(js, args[0], &path_len);
2794 if (!path) return js_mkerr(js, "Failed to get path string");
2795
2796 int mode = 0755;
2797 int recursive = 0;
2798 if (nargs >= 2) {
2799 switch (vtype(args[1])) {
2800 case T_NUM:
2801 mode = (int)js_getnum(args[1]);
2802 break;
2803 case T_OBJ: {
2804 ant_value_t opt = args[1];
2805 recursive = js_get(js, opt, "recursive") == js_true;
2806 ant_value_t mode_val = js_get(js, opt, "mode");
2807 if (vtype(mode_val) == T_NUM) mode = (int)js_getnum(mode_val);
2808 break;
2809 }}
2810 }
2811
2812 if (recursive) {
2813 ant_value_t promise = js_mkpromise(js);
2814 char *path_cstr = strndup(path, path_len);
2815
2816 if (!path_cstr) return js_mkerr(js, "Out of memory");
2817 int result = mkdirp(path_cstr, (mode_t)mode);
2818
2819 free(path_cstr);
2820 if (result != 0) {
2821 js_reject_promise(js, promise, js_mkerr(js, "Failed to create directory: %s", strerror(errno)));
2822 } else js_resolve_promise(js, promise, js_mkundef());
2823
2824 return promise;
2825 }
2826
2827 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2828 if (!req) return js_mkerr(js, "Out of memory");
2829
2830 req->js = js;
2831 req->op_type = FS_OP_MKDIR;
2832 req->promise = js_mkpromise(js);
2833 req->path = strndup(path, path_len);
2834 req->recursive = recursive;
2835 req->uv_req.data = req;
2836
2837 utarray_push_back(pending_requests, &req);
2838 int result = uv_fs_mkdir(uv_default_loop(), &req->uv_req, req->path, mode, on_mkdir_complete);
2839
2840 if (result < 0) {
2841 fs_request_fail(req, result);
2842 req->completed = 1;
2843 complete_request(req);
2844 }
2845
2846 return req->promise;
2847}
2848
2849static ant_value_t builtin_fs_mkdtempSync(ant_t *js, ant_value_t *args, int nargs) {
2850 if (nargs < 1 || vtype(args[0]) != T_STR)
2851 return js_mkerr(js, "mkdtempSync() requires a prefix string");
2852
2853 size_t prefix_len;
2854 const char *prefix = js_getstr(js, args[0], &prefix_len);
2855 if (!prefix) return js_mkerr(js, "Failed to get prefix string");
2856
2857 size_t tpl_len = prefix_len + 6;
2858 char *tpl = malloc(tpl_len + 1);
2859 if (!tpl) return js_mkerr(js, "Out of memory");
2860
2861 memcpy(tpl, prefix, prefix_len);
2862 memcpy(tpl + prefix_len, "XXXXXX", 6);
2863 tpl[tpl_len] = '\0';
2864
2865 char *result = mkdtemp(tpl);
2866 if (!result) {
2867 free(tpl);
2868 return js_mkerr(js, "mkdtempSync failed: %s", strerror(errno));
2869 }
2870
2871 ant_value_t ret = js_mkstr(js, result, strlen(result));
2872 free(tpl);
2873 return ret;
2874}
2875
2876static ant_value_t builtin_fs_mkdtemp(ant_t *js, ant_value_t *args, int nargs) {
2877 if (nargs < 1 || vtype(args[0]) != T_STR)
2878 return js_mkerr(js, "mkdtemp() requires a prefix string");
2879
2880 size_t prefix_len;
2881 const char *prefix = js_getstr(js, args[0], &prefix_len);
2882 if (!prefix) return js_mkerr(js, "Failed to get prefix string");
2883
2884 size_t tpl_len = prefix_len + 6;
2885 char *tpl = malloc(tpl_len + 1);
2886 if (!tpl) return js_mkerr(js, "Out of memory");
2887
2888 memcpy(tpl, prefix, prefix_len);
2889 memcpy(tpl + prefix_len, "XXXXXX", 6);
2890 tpl[tpl_len] = '\0';
2891
2892 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2893 if (!req) { free(tpl); return js_mkerr(js, "Out of memory"); }
2894
2895 req->js = js;
2896 req->op_type = FS_OP_MKDTEMP;
2897 req->promise = js_mkpromise(js);
2898 req->path = tpl;
2899 req->uv_req.data = req;
2900
2901 utarray_push_back(pending_requests, &req);
2902 int result = uv_fs_mkdtemp(uv_default_loop(), &req->uv_req, req->path, on_mkdtemp_complete);
2903
2904 if (result < 0) {
2905 fs_request_fail(req, result);
2906 req->completed = 1;
2907 complete_request(req);
2908 }
2909
2910 return req->promise;
2911}
2912
2913static ant_value_t builtin_fs_rmdirSync(ant_t *js, ant_value_t *args, int nargs) {
2914 if (nargs < 1) return js_mkerr(js, "rmdirSync() requires a path argument");
2915
2916 if (vtype(args[0]) != T_STR) return js_mkerr(js, "rmdirSync() path must be a string");
2917
2918 size_t path_len;
2919 char *path = js_getstr(js, args[0], &path_len);
2920 if (!path) return js_mkerr(js, "Failed to get path string");
2921
2922 char *path_cstr = strndup(path, path_len);
2923 if (!path_cstr) return js_mkerr(js, "Out of memory");
2924
2925#ifdef _WIN32
2926 int result = _rmdir(path_cstr);
2927#else
2928 int result = rmdir(path_cstr);
2929#endif
2930 if (result != 0) {
2931 ant_value_t err = fs_mk_errno_error(js, errno, "rmdir", path_cstr, NULL);
2932 free(path_cstr);
2933 return err;
2934 }
2935
2936 free(path_cstr);
2937 return js_mkundef();
2938}
2939
2940static ant_value_t builtin_fs_rmdir(ant_t *js, ant_value_t *args, int nargs) {
2941 if (nargs < 1) return js_mkerr(js, "rmdir() requires a path argument");
2942
2943 if (vtype(args[0]) != T_STR) return js_mkerr(js, "rmdir() path must be a string");
2944
2945 size_t path_len;
2946 char *path = js_getstr(js, args[0], &path_len);
2947 if (!path) return js_mkerr(js, "Failed to get path string");
2948
2949
2950 fs_request_t *req = calloc(1, sizeof(fs_request_t));
2951 if (!req) return js_mkerr(js, "Out of memory");
2952
2953 req->js = js;
2954 req->op_type = FS_OP_RMDIR;
2955 req->promise = js_mkpromise(js);
2956 req->path = strndup(path, path_len);
2957 req->uv_req.data = req;
2958
2959 utarray_push_back(pending_requests, &req);
2960 int result = uv_fs_rmdir(uv_default_loop(), &req->uv_req, req->path, on_rmdir_complete);
2961
2962 if (result < 0) {
2963 fs_request_fail(req, result);
2964 req->completed = 1;
2965 complete_request(req);
2966 }
2967
2968 return req->promise;
2969}
2970
2971static ant_value_t builtin_fs_rmSync(ant_t *js, ant_value_t *args, int nargs) {
2972 return fs_rm_impl(js, args, nargs, false);
2973}
2974
2975static ant_value_t builtin_fs_rm(ant_t *js, ant_value_t *args, int nargs) {
2976 return fs_rm_impl(js, args, nargs, true);
2977}
2978
2979static ant_value_t dirent_isFile(ant_t *js, ant_value_t *args, int nargs) {
2980 ant_value_t this = js_getthis(js);
2981 ant_value_t type_val = js_get_slot(this, SLOT_DATA);
2982 if (vtype(type_val) != T_NUM) return js_false;
2983 return js_bool((int)js_getnum(type_val) == UV_DIRENT_FILE);
2984}
2985
2986static ant_value_t dirent_isDirectory(ant_t *js, ant_value_t *args, int nargs) {
2987 ant_value_t this = js_getthis(js);
2988 ant_value_t type_val = js_get_slot(this, SLOT_DATA);
2989 if (vtype(type_val) != T_NUM) return js_false;
2990 return js_bool((int)js_getnum(type_val) == UV_DIRENT_DIR);
2991}
2992
2993static ant_value_t dirent_isSymbolicLink(ant_t *js, ant_value_t *args, int nargs) {
2994 ant_value_t this = js_getthis(js);
2995 ant_value_t type_val = js_get_slot(this, SLOT_DATA);
2996 if (vtype(type_val) != T_NUM) return js_false;
2997 return js_bool((int)js_getnum(type_val) == UV_DIRENT_LINK);
2998}
2999
3000static ant_value_t dirent_isBlockDevice(ant_t *js, ant_value_t *args, int nargs) {
3001 ant_value_t this = js_getthis(js);
3002 ant_value_t type_val = js_get_slot(this, SLOT_DATA);
3003 if (vtype(type_val) != T_NUM) return js_false;
3004 return js_bool((int)js_getnum(type_val) == UV_DIRENT_BLOCK);
3005}
3006
3007static ant_value_t dirent_isCharacterDevice(ant_t *js, ant_value_t *args, int nargs) {
3008 ant_value_t this = js_getthis(js);
3009 ant_value_t type_val = js_get_slot(this, SLOT_DATA);
3010 if (vtype(type_val) != T_NUM) return js_false;
3011 return js_bool((int)js_getnum(type_val) == UV_DIRENT_CHAR);
3012}
3013
3014static ant_value_t dirent_isFIFO(ant_t *js, ant_value_t *args, int nargs) {
3015 ant_value_t this = js_getthis(js);
3016 ant_value_t type_val = js_get_slot(this, SLOT_DATA);
3017 if (vtype(type_val) != T_NUM) return js_false;
3018 return js_bool((int)js_getnum(type_val) == UV_DIRENT_FIFO);
3019}
3020
3021static ant_value_t dirent_isSocket(ant_t *js, ant_value_t *args, int nargs) {
3022 ant_value_t this = js_getthis(js);
3023 ant_value_t type_val = js_get_slot(this, SLOT_DATA);
3024 if (vtype(type_val) != T_NUM) return js_false;
3025 return js_bool((int)js_getnum(type_val) == UV_DIRENT_SOCKET);
3026}
3027
3028static ant_value_t stat_isFile(ant_t *js, ant_value_t *args, int nargs) {
3029 ant_value_t this = js_getthis(js);
3030 ant_value_t mode_val = js_get_slot(this, SLOT_DATA);
3031
3032 if (vtype(mode_val) != T_NUM) return js_false;
3033 mode_t mode = (mode_t)js_getnum(mode_val);
3034
3035 return js_bool(S_ISREG(mode));
3036}
3037
3038static ant_value_t stat_isDirectory(ant_t *js, ant_value_t *args, int nargs) {
3039 ant_value_t this = js_getthis(js);
3040 ant_value_t mode_val = js_get_slot(this, SLOT_DATA);
3041
3042 if (vtype(mode_val) != T_NUM) return js_false;
3043 mode_t mode = (mode_t)js_getnum(mode_val);
3044
3045 return js_bool(S_ISDIR(mode));
3046}
3047
3048static ant_value_t stat_isSymbolicLink(ant_t *js, ant_value_t *args, int nargs) {
3049 ant_value_t this = js_getthis(js);
3050 ant_value_t mode_val = js_get_slot(this, SLOT_DATA);
3051
3052 if (vtype(mode_val) != T_NUM) return js_false;
3053 mode_t mode = (mode_t)js_getnum(mode_val);
3054
3055 return js_bool(S_ISLNK(mode));
3056}
3057
3058static ant_value_t create_stats_object(ant_t *js, struct stat *st) {
3059 return fs_stats_object_from_posix(js, st);
3060}
3061
3062static const char *errno_to_code(int err_num) {
3063switch (err_num) {
3064 case ENOENT: return "ENOENT";
3065 case EACCES: return "EACCES";
3066 case ENOTDIR: return "ENOTDIR";
3067 case ELOOP: return "ELOOP";
3068 case ENAMETOOLONG: return "ENAMETOOLONG";
3069 case EOVERFLOW: return "EOVERFLOW";
3070 case EROFS: return "EROFS";
3071 case ETXTBSY: return "ETXTBSY";
3072 case EEXIST: return "EEXIST";
3073 case ENOTEMPTY: return "ENOTEMPTY";
3074 case EISDIR: return "EISDIR";
3075 case EBUSY: return "EBUSY";
3076 case EINVAL: return "EINVAL";
3077 case EPERM: return "EPERM";
3078 case EIO: return "EIO";
3079 default: return "UNKNOWN";
3080}}
3081
3082static ant_value_t fs_mk_syscall_error(
3083 ant_t *js, const char *code, double err_num,
3084 const char *message, const char *syscall,
3085 const char *path, const char *dest
3086) {
3087 ant_value_t props = js_mkobj(js);
3088
3089 if (!code) code = "UNKNOWN";
3090 if (!message) message = "Unknown error";
3091
3092 js_set(js, props, "code", js_mkstr(js, code, strlen(code)));
3093 js_set(js, props, "errno", js_mknum(err_num));
3094
3095 if (syscall) js_set(js, props, "syscall", js_mkstr(js, syscall, strlen(syscall)));
3096 if (path) js_set(js, props, "path", js_mkstr(js, path, strlen(path)));
3097 if (dest) js_set(js, props, "dest", js_mkstr(js, dest, strlen(dest)));
3098
3099 if (path && dest) return js_mkerr_props(
3100 js, JS_ERR_GENERIC,
3101 props, "%s: %s, %s '%s' -> '%s'",
3102 code, message, syscall, path, dest
3103 );
3104
3105 if (path) return js_mkerr_props(
3106 js, JS_ERR_GENERIC,
3107 props, "%s: %s, %s '%s'",
3108 code, message, syscall, path
3109 );
3110
3111 return js_mkerr_props(
3112 js, JS_ERR_GENERIC,
3113 props, "%s: %s, %s",
3114 code, message, syscall
3115 );
3116}
3117
3118static ant_value_t fs_mk_errno_error(
3119 ant_t *js, int err_num,
3120 const char *syscall, const char *path, const char *dest
3121) {
3122 int uv_code = uv_translate_sys_error(err_num);
3123 if (uv_code == 0) uv_code = -err_num;
3124
3125 return fs_mk_syscall_error(
3126 js, errno_to_code(err_num),
3127 (double)uv_code,
3128 uv_strerror(uv_code),
3129 syscall, path, dest
3130 );
3131}
3132
3133static ant_value_t fs_mk_uv_error(
3134 ant_t *js, int uv_code,
3135 const char *syscall, const char *path, const char *dest
3136) {
3137 return fs_mk_syscall_error(
3138 js, uv_err_name(uv_code),
3139 (double)uv_code,
3140 uv_strerror(uv_code),
3141 syscall, path, dest
3142 );
3143}
3144
3145static ant_value_t builtin_fs_statSync(ant_t *js, ant_value_t *args, int nargs) {
3146 if (nargs < 1) return js_mkerr(js, "statSync() requires a path argument");
3147 if (vtype(args[0]) != T_STR) return js_mkerr(js, "statSync() path must be a string");
3148
3149 size_t path_len;
3150 char *path = js_getstr(js, args[0], &path_len);
3151 if (!path) return js_mkerr(js, "Failed to get path string");
3152
3153 char *path_cstr = strndup(path, path_len);
3154 if (!path_cstr) return js_mkerr(js, "Out of memory");
3155
3156 struct stat st;
3157 int result = stat(path_cstr, &st);
3158
3159 if (result != 0) {
3160 ant_value_t err = fs_mk_errno_error(js, errno, "stat", path_cstr, NULL);
3161 free(path_cstr); return err;
3162 }
3163
3164 free(path_cstr);
3165 return create_stats_object(js, &st);
3166}
3167
3168static ant_value_t builtin_fs_stat(ant_t *js, ant_value_t *args, int nargs) {
3169 if (nargs < 1) return js_mkerr(js, "stat() requires a path argument");
3170 if (vtype(args[0]) != T_STR) return js_mkerr(js, "stat() path must be a string");
3171
3172 size_t path_len;
3173 char *path = js_getstr(js, args[0], &path_len);
3174 if (!path) return js_mkerr(js, "Failed to get path string");
3175
3176
3177 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3178 if (!req) return js_mkerr(js, "Out of memory");
3179
3180 req->js = js;
3181 req->op_type = FS_OP_STAT;
3182 req->promise = js_mkpromise(js);
3183 req->path = strndup(path, path_len);
3184 req->uv_req.data = req;
3185
3186 utarray_push_back(pending_requests, &req);
3187 int result = uv_fs_stat(uv_default_loop(), &req->uv_req, req->path, on_stat_complete);
3188
3189 if (result < 0) {
3190 fs_request_fail(req, result);
3191 req->completed = 1;
3192 complete_request(req);
3193 }
3194
3195 return req->promise;
3196}
3197
3198static ant_value_t builtin_fs_lstatSync(ant_t *js, ant_value_t *args, int nargs) {
3199 if (nargs < 1) return js_mkerr(js, "lstatSync() requires a path argument");
3200 if (vtype(args[0]) != T_STR) return js_mkerr(js, "lstatSync() path must be a string");
3201
3202 size_t path_len;
3203 char *path = js_getstr(js, args[0], &path_len);
3204 if (!path) return js_mkerr(js, "Failed to get path string");
3205
3206 char *path_cstr = strndup(path, path_len);
3207 if (!path_cstr) return js_mkerr(js, "Out of memory");
3208
3209 uv_fs_t req;
3210 int result = uv_fs_lstat(NULL, &req, path_cstr, NULL);
3211
3212 if (result < 0) {
3213 ant_value_t err = fs_mk_uv_error(js, result, "lstat", path_cstr, NULL);
3214 uv_fs_req_cleanup(&req);
3215 free(path_cstr);
3216 return err;
3217 }
3218
3219 ant_value_t stat_obj = fs_stats_object_from_uv(js, &req.statbuf);
3220 uv_fs_req_cleanup(&req);
3221 free(path_cstr);
3222
3223 return stat_obj;
3224}
3225
3226static ant_value_t builtin_fs_lstat(ant_t *js, ant_value_t *args, int nargs) {
3227 if (nargs < 1) return js_mkerr(js, "lstat() requires a path argument");
3228 if (vtype(args[0]) != T_STR) return js_mkerr(js, "lstat() path must be a string");
3229
3230 size_t path_len;
3231 char *path = js_getstr(js, args[0], &path_len);
3232 if (!path) return js_mkerr(js, "Failed to get path string");
3233
3234
3235 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3236 if (!req) return js_mkerr(js, "Out of memory");
3237
3238 req->js = js;
3239 req->op_type = FS_OP_STAT;
3240 req->promise = js_mkpromise(js);
3241 req->path = strndup(path, path_len);
3242 req->uv_req.data = req;
3243
3244 utarray_push_back(pending_requests, &req);
3245 int result = uv_fs_lstat(uv_default_loop(), &req->uv_req, req->path, on_stat_complete);
3246
3247 if (result < 0) {
3248 fs_request_fail(req, result);
3249 req->completed = 1;
3250 complete_request(req);
3251 }
3252
3253 return req->promise;
3254}
3255
3256static ant_value_t builtin_fs_fstatSync(ant_t *js, ant_value_t *args, int nargs) {
3257 if (nargs < 1) return js_mkerr(js, "fstatSync() requires an fd argument");
3258 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "fstatSync() fd must be a number");
3259
3260 uv_file fd = (uv_file)(int)js_getnum(args[0]);
3261 uv_fs_t req;
3262 int result = uv_fs_fstat(NULL, &req, fd, NULL);
3263
3264 if (result < 0) {
3265 ant_value_t err = fs_mk_uv_error(js, result, "fstat", NULL, NULL);
3266 uv_fs_req_cleanup(&req);
3267 return err;
3268 }
3269
3270 ant_value_t stat_obj = fs_stats_object_from_uv(js, &req.statbuf);
3271 uv_fs_req_cleanup(&req);
3272 return stat_obj;
3273}
3274
3275static ant_value_t builtin_fs_fstat(ant_t *js, ant_value_t *args, int nargs) {
3276 if (nargs < 1) return js_mkerr(js, "fstat() requires an fd argument");
3277 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "fstat() fd must be a number");
3278
3279 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3280 if (!req) return js_mkerr(js, "Out of memory");
3281
3282 req->js = js;
3283 req->op_type = FS_OP_STAT;
3284 req->promise = js_mkpromise(js);
3285 req->fd = (uv_file)(int)js_getnum(args[0]);
3286 req->uv_req.data = req;
3287
3288 utarray_push_back(pending_requests, &req);
3289 int result = uv_fs_fstat(uv_default_loop(), &req->uv_req, req->fd, on_stat_complete);
3290
3291 if (result < 0) {
3292 fs_request_fail(req, result);
3293 req->completed = 1;
3294 complete_request(req);
3295 }
3296
3297 return req->promise;
3298}
3299
3300static ant_value_t builtin_fs_existsSync(ant_t *js, ant_value_t *args, int nargs) {
3301 if (nargs < 1) return js_mkerr(js, "existsSync() requires a path argument");
3302 if (vtype(args[0]) != T_STR) return js_mkerr(js, "existsSync() path must be a string");
3303
3304 size_t path_len;
3305 char *path = js_getstr(js, args[0], &path_len);
3306 if (!path) return js_mkerr(js, "Failed to get path string");
3307
3308 char *path_cstr = strndup(path, path_len);
3309 if (!path_cstr) return js_mkerr(js, "Out of memory");
3310
3311 struct stat st;
3312 int result = stat(path_cstr, &st);
3313 free(path_cstr);
3314
3315 return js_bool(result == 0);
3316}
3317
3318static ant_value_t builtin_fs_exists(ant_t *js, ant_value_t *args, int nargs) {
3319 if (nargs < 1) return js_mkerr(js, "exists() requires a path argument");
3320 if (vtype(args[0]) != T_STR) return js_mkerr(js, "exists() path must be a string");
3321
3322 size_t path_len;
3323 char *path = js_getstr(js, args[0], &path_len);
3324 if (!path) return js_mkerr(js, "Failed to get path string");
3325
3326
3327 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3328 if (!req) return js_mkerr(js, "Out of memory");
3329
3330 req->js = js;
3331 req->op_type = FS_OP_EXISTS;
3332 req->promise = js_mkpromise(js);
3333 req->path = strndup(path, path_len);
3334 req->uv_req.data = req;
3335
3336 utarray_push_back(pending_requests, &req);
3337 int result = uv_fs_stat(uv_default_loop(), &req->uv_req, req->path, on_exists_complete);
3338
3339 if (result < 0) {
3340 req->completed = 1;
3341 js_resolve_promise(req->js, req->promise, js_false);
3342 remove_pending_request(req);
3343 free_fs_request(req);
3344 }
3345
3346 return req->promise;
3347}
3348
3349static ant_value_t builtin_fs_accessSync(ant_t *js, ant_value_t *args, int nargs) {
3350 if (nargs < 1) return js_mkerr(js, "accessSync() requires a path argument");
3351 if (vtype(args[0]) != T_STR) return js_mkerr(js, "accessSync() path must be a string");
3352
3353 size_t path_len;
3354 char *path = js_getstr(js, args[0], &path_len);
3355 if (!path) return js_mkerr(js, "Failed to get path string");
3356
3357 int mode = F_OK;
3358 if (nargs >= 2 && vtype(args[1]) == T_NUM) {
3359 mode = (int)js_getnum(args[1]);
3360 }
3361
3362 char *path_cstr = strndup(path, path_len);
3363 if (!path_cstr) return js_mkerr(js, "Out of memory");
3364 int result = access(path_cstr, mode);
3365
3366 if (result != 0) {
3367 ant_value_t err = fs_mk_errno_error(js, errno, "access", path_cstr, NULL);
3368 free(path_cstr); return err;
3369 }
3370
3371 free(path_cstr);
3372 return js_mkundef();
3373}
3374
3375static ant_value_t builtin_fs_chmodSync(ant_t *js, ant_value_t *args, int nargs) {
3376 if (nargs < 1) return js_mkerr(js, "chmodSync() requires a path argument");
3377 if (nargs < 2) return js_mkerr(js, "chmodSync() requires a mode argument");
3378
3379 ant_value_t path_val = fs_coerce_path(js, args[0]);
3380 if (vtype(path_val) != T_STR) return js_mkerr(js, "chmodSync() path must be a string or URL");
3381
3382 mode_t mode = 0;
3383 if (!fs_parse_mode(js, args[1], &mode)) {
3384 return js_mkerr(js, "chmodSync() mode must be a number or octal string");
3385 }
3386
3387 size_t path_len = 0;
3388 char *path = js_getstr(js, path_val, &path_len);
3389 if (!path) return js_mkerr(js, "Failed to get path string");
3390
3391 char *path_cstr = strndup(path, path_len);
3392 if (!path_cstr) return js_mkerr(js, "Out of memory");
3393
3394 uv_fs_t req;
3395 int result = uv_fs_chmod(uv_default_loop(), &req, path_cstr, mode, NULL);
3396
3397 if (result < 0) {
3398 ant_value_t err = fs_mk_uv_error(js, result, "chmod", path_cstr, NULL);
3399 uv_fs_req_cleanup(&req);
3400 free(path_cstr);
3401 return err;
3402 }
3403
3404 uv_fs_req_cleanup(&req);
3405 free(path_cstr);
3406 return js_mkundef();
3407}
3408
3409static ant_value_t builtin_fs_chmod(ant_t *js, ant_value_t *args, int nargs) {
3410 if (nargs < 1) return js_mkerr(js, "chmod() requires a path argument");
3411 if (nargs < 2) return js_mkerr(js, "chmod() requires a mode argument");
3412
3413 ant_value_t path_val = fs_coerce_path(js, args[0]);
3414 if (vtype(path_val) != T_STR) return js_mkerr(js, "chmod() path must be a string or URL");
3415
3416 mode_t mode = 0;
3417 if (!fs_parse_mode(js, args[1], &mode)) {
3418 return js_mkerr(js, "chmod() mode must be a number or octal string");
3419 }
3420
3421 size_t path_len = 0;
3422 char *path = js_getstr(js, path_val, &path_len);
3423 if (!path) return js_mkerr(js, "Failed to get path string");
3424
3425 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3426 if (!req) return js_mkerr(js, "Out of memory");
3427
3428 req->js = js;
3429 req->op_type = FS_OP_CHMOD;
3430 req->promise = js_mkpromise(js);
3431 req->path = strndup(path, path_len);
3432 req->uv_req.data = req;
3433
3434 if (!req->path) {
3435 free_fs_request(req);
3436 return js_mkerr(js, "Out of memory");
3437 }
3438
3439 utarray_push_back(pending_requests, &req);
3440 int result = uv_fs_chmod(uv_default_loop(), &req->uv_req, req->path, mode, on_chmod_complete);
3441
3442 if (result < 0) {
3443 fs_request_fail(req, result);
3444 req->completed = 1;
3445 complete_request(req);
3446 }
3447
3448 return req->promise;
3449}
3450
3451static ant_value_t builtin_fs_access(ant_t *js, ant_value_t *args, int nargs) {
3452 if (nargs < 1) return js_mkerr(js, "access() requires a path argument");
3453 if (vtype(args[0]) != T_STR) return js_mkerr(js, "access() path must be a string");
3454
3455 size_t path_len;
3456 char *path = js_getstr(js, args[0], &path_len);
3457 if (!path) return js_mkerr(js, "Failed to get path string");
3458
3459 int mode = F_OK;
3460 if (nargs >= 2 && vtype(args[1]) == T_NUM) {
3461 mode = (int)js_getnum(args[1]);
3462 }
3463
3464
3465 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3466 if (!req) return js_mkerr(js, "Out of memory");
3467
3468 req->js = js;
3469 req->op_type = FS_OP_ACCESS;
3470 req->promise = js_mkpromise(js);
3471 req->path = strndup(path, path_len);
3472 req->uv_req.data = req;
3473
3474 utarray_push_back(pending_requests, &req);
3475 int result = uv_fs_access(uv_default_loop(), &req->uv_req, req->path, mode, on_access_complete);
3476
3477 if (result < 0) {
3478 fs_request_fail(req, result);
3479 req->completed = 1;
3480 complete_request(req);
3481 }
3482
3483 return req->promise;
3484}
3485
3486static ant_value_t builtin_fs_realpathSync(ant_t *js, ant_value_t *args, int nargs) {
3487 if (nargs < 1) return js_mkerr(js, "realpathSync() requires a path argument");
3488
3489 ant_value_t path_val = fs_coerce_path(js, args[0]);
3490 if (vtype(path_val) != T_STR) return js_mkerr(js, "realpathSync() path must be a string");
3491
3492 size_t path_len = 0;
3493 const char *path = js_getstr(js, path_val, &path_len);
3494 if (!path) return js_mkerr(js, "Failed to get path string");
3495
3496 char *resolved = realpath(path, NULL);
3497 if (!resolved) return js_mkerr(js, "realpathSync failed for '%s'", path);
3498
3499 ant_value_t result = js_mkstr(js, resolved, strlen(resolved));
3500 free(resolved);
3501 return result;
3502}
3503
3504static ant_value_t builtin_fs_realpath(ant_t *js, ant_value_t *args, int nargs) {
3505 if (nargs < 1) return js_mkerr(js, "realpath() requires a path argument");
3506
3507 ant_value_t path_val = fs_coerce_path(js, args[0]);
3508 if (vtype(path_val) != T_STR) return js_mkerr(js, "realpath() path must be a string");
3509
3510 size_t path_len = 0;
3511 const char *path = js_getstr(js, path_val, &path_len);
3512 if (!path) return js_mkerr(js, "Failed to get path string");
3513
3514 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3515 if (!req) return js_mkerr(js, "Out of memory");
3516
3517 req->js = js;
3518 req->op_type = FS_OP_REALPATH;
3519 req->promise = js_mkpromise(js);
3520 req->path = strndup(path, path_len);
3521 req->uv_req.data = req;
3522
3523 if (!req->path) {
3524 free_fs_request(req);
3525 return js_mkerr(js, "Out of memory");
3526 }
3527
3528 utarray_push_back(pending_requests, &req);
3529 int result = uv_fs_realpath(uv_default_loop(), &req->uv_req, req->path, on_realpath_complete);
3530
3531 if (result < 0) {
3532 fs_request_fail(req, result);
3533 req->completed = 1;
3534 complete_request(req);
3535 }
3536
3537 return req->promise;
3538}
3539
3540static ant_value_t builtin_fs_readlinkSync(ant_t *js, ant_value_t *args, int nargs) {
3541 if (nargs < 1) return js_mkerr(js, "readlinkSync() requires a path argument");
3542
3543 ant_value_t path_val = fs_coerce_path(js, args[0]);
3544 if (vtype(path_val) != T_STR) return js_mkerr(js, "readlinkSync() path must be a string");
3545
3546 size_t path_len = 0;
3547 const char *path = js_getstr(js, path_val, &path_len);
3548 if (!path) return js_mkerr(js, "Failed to get path string");
3549
3550 char *path_cstr = strndup(path, path_len);
3551 if (!path_cstr) return js_mkerr(js, "Out of memory");
3552
3553 uv_fs_t req;
3554 int result = uv_fs_readlink(NULL, &req, path_cstr, NULL);
3555
3556 if (result < 0 || !req.ptr) {
3557 ant_value_t err = fs_mk_uv_error(js, result, "readlink", path_cstr, NULL);
3558 uv_fs_req_cleanup(&req);
3559 free(path_cstr);
3560 return err;
3561 }
3562
3563 ant_value_t link = js_mkstr(js, (const char *)req.ptr, strlen((const char *)req.ptr));
3564 uv_fs_req_cleanup(&req);
3565 free(path_cstr);
3566 return link;
3567}
3568
3569static ant_value_t builtin_fs_readlink(ant_t *js, ant_value_t *args, int nargs) {
3570 if (nargs < 1) return js_mkerr(js, "readlink() requires a path argument");
3571
3572 ant_value_t path_val = fs_coerce_path(js, args[0]);
3573 if (vtype(path_val) != T_STR) return js_mkerr(js, "readlink() path must be a string");
3574
3575 size_t path_len = 0;
3576 const char *path = js_getstr(js, path_val, &path_len);
3577 if (!path) return js_mkerr(js, "Failed to get path string");
3578
3579 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3580 if (!req) return js_mkerr(js, "Out of memory");
3581
3582 req->js = js;
3583 req->op_type = FS_OP_REALPATH;
3584 req->promise = js_mkpromise(js);
3585 req->path = strndup(path, path_len);
3586 req->uv_req.data = req;
3587
3588 if (!req->path) {
3589 free_fs_request(req);
3590 return js_mkerr(js, "Out of memory");
3591 }
3592
3593 utarray_push_back(pending_requests, &req);
3594 int result = uv_fs_readlink(uv_default_loop(), &req->uv_req, req->path, on_realpath_complete);
3595
3596 if (result < 0) {
3597 fs_request_fail(req, result);
3598 req->completed = 1;
3599 complete_request(req);
3600 }
3601
3602 return req->promise;
3603}
3604
3605static ant_value_t builtin_fs_readdirSync(ant_t *js, ant_value_t *args, int nargs) {
3606 if (nargs < 1) return js_mkerr(js, "readdirSync() requires a path argument");
3607 if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdirSync() path must be a string");
3608
3609 bool with_file_types = false;
3610 if (nargs >= 2 && vtype(args[1]) == T_OBJ) {
3611 ant_value_t wft = js_get(js, args[1], "withFileTypes");
3612 with_file_types = js_truthy(js, wft);
3613 }
3614
3615 size_t path_len;
3616 char *path = js_getstr(js, args[0], &path_len);
3617 if (!path) return js_mkerr(js, "Failed to get path string");
3618
3619 char *path_cstr = strndup(path, path_len);
3620 if (!path_cstr) return js_mkerr(js, "Out of memory");
3621
3622 uv_fs_t req;
3623 int result = uv_fs_scandir(NULL, &req, path_cstr, 0, NULL);
3624
3625 if (result < 0) {
3626 ant_value_t err = fs_mk_uv_error(js, result, "scandir", path_cstr, NULL);
3627 uv_fs_req_cleanup(&req);
3628 free(path_cstr);
3629 return err;
3630 }
3631
3632 free(path_cstr);
3633
3634 ant_value_t arr = js_mkarr(js);
3635 uv_dirent_t dirent;
3636
3637 while (uv_fs_scandir_next(&req, &dirent) != UV_EOF) {
3638 if (with_file_types) {
3639 ant_value_t entry = create_dirent_object(js, dirent.name, strlen(dirent.name), dirent.type);
3640 js_arr_push(js, arr, entry);
3641 } else {
3642 ant_value_t name = js_mkstr(js, dirent.name, strlen(dirent.name));
3643 js_arr_push(js, arr, name);
3644 }}
3645
3646 uv_fs_req_cleanup(&req);
3647 return arr;
3648}
3649
3650static ant_value_t builtin_fs_readdir(ant_t *js, ant_value_t *args, int nargs) {
3651 if (nargs < 1) return js_mkerr(js, "readdir() requires a path argument");
3652 if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdir() path must be a string");
3653
3654 bool with_file_types = false;
3655 if (nargs >= 2 && vtype(args[1]) == T_OBJ) {
3656 ant_value_t wft = js_get(js, args[1], "withFileTypes");
3657 with_file_types = js_truthy(js, wft);
3658 }
3659
3660 size_t path_len;
3661 char *path = js_getstr(js, args[0], &path_len);
3662 if (!path) return js_mkerr(js, "Failed to get path string");
3663
3664
3665 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3666 if (!req) return js_mkerr(js, "Out of memory");
3667
3668 req->js = js;
3669 req->op_type = FS_OP_READDIR;
3670 req->with_file_types = with_file_types;
3671 req->promise = js_mkpromise(js);
3672 req->path = strndup(path, path_len);
3673 req->uv_req.data = req;
3674
3675 utarray_push_back(pending_requests, &req);
3676 int result = uv_fs_scandir(uv_default_loop(), &req->uv_req, req->path, 0, on_readdir_complete);
3677
3678 if (result < 0) {
3679 fs_request_fail(req, result);
3680 req->completed = 1;
3681 complete_request(req);
3682 }
3683
3684 return req->promise;
3685}
3686
3687static void on_write_fd_complete(uv_fs_t *uv_req) {
3688 fs_request_t *req = (fs_request_t *)uv_req->data;
3689
3690 if (uv_req->result < 0) {
3691 fs_request_fail(req, (int)uv_req->result);
3692 req->completed = 1;
3693 complete_request(req);
3694 return;
3695 }
3696
3697 req->completed = 1;
3698 js_resolve_promise(req->js, req->promise, js_mknum((double)uv_req->result));
3699 remove_pending_request(req);
3700 free_fs_request(req);
3701}
3702
3703static void on_read_fd_complete(uv_fs_t *uv_req) {
3704 fs_request_t *req = (fs_request_t *)uv_req->data;
3705
3706 if (uv_req->result < 0) {
3707 if (is_callable(req->callback_fn)) {
3708 ant_value_t err = js_mkerr(req->js, "read failed: %s", uv_strerror((int)uv_req->result));
3709 ant_value_t cb_args[1] = { err };
3710 fs_call_value(req->js, req->callback_fn, js_mkundef(), cb_args, 1);
3711 remove_pending_request(req);
3712 free_fs_request(req);
3713 return;
3714 }
3715 fs_request_fail(req, (int)uv_req->result);
3716 req->completed = 1;
3717 complete_request(req);
3718 return;
3719 }
3720
3721 ssize_t bytes_read = uv_req->result;
3722
3723 if (req->data && bytes_read > 0) {
3724 TypedArrayData *ta = buffer_get_typedarray_data(req->target_buffer);
3725 if (ta && ta->buffer && ta->buffer->data) {
3726 uint8_t *dest = ta->buffer->data + ta->byte_offset + req->buf_offset;
3727 memcpy(dest, req->data, (size_t)bytes_read);
3728 }
3729 }
3730
3731 if (is_callable(req->callback_fn)) {
3732 ant_value_t cb_args[3] = { js_mknull(), js_mknum((double)bytes_read), req->target_buffer };
3733 fs_call_value(req->js, req->callback_fn, js_mkundef(), cb_args, 3);
3734 remove_pending_request(req);
3735 free_fs_request(req);
3736 return;
3737 }
3738
3739 req->completed = 1;
3740 js_resolve_promise(req->js, req->promise, js_mknum((double)bytes_read));
3741 remove_pending_request(req);
3742 free_fs_request(req);
3743}
3744
3745static ant_value_t builtin_fs_read_fd(ant_t *js, ant_value_t *args, int nargs) {
3746 if (nargs < 2) return js_mkerr(js, "read() requires fd and buffer arguments");
3747 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "read() fd must be a number");
3748
3749 int fd = (int)js_getnum(args[0]);
3750
3751 ant_value_t buf_arg = args[1];
3752 TypedArrayData *ta_data = buffer_get_typedarray_data(buf_arg);
3753 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
3754 return js_mkerr(js, "read() buffer argument must be a Buffer or TypedArray");
3755
3756 size_t buf_len = ta_data->byte_length;
3757 size_t offset = 0;
3758 size_t length = buf_len;
3759 int64_t position = -1;
3760
3761 ant_value_t callback = js_mkundef();
3762 int data_nargs = nargs;
3763 if (nargs >= 3 && is_callable(args[nargs - 1])) {
3764 callback = args[nargs - 1];
3765 data_nargs = nargs - 1;
3766 }
3767
3768 if (data_nargs >= 3 && vtype(args[2]) == T_NUM) offset = (size_t)js_getnum(args[2]);
3769 if (data_nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
3770 if (data_nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
3771
3772 if (offset > buf_len) return js_mkerr(js, "read() offset out of bounds");
3773 if (offset + length > buf_len) return js_mkerr(js, "read() length extends beyond buffer");
3774
3775 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3776 if (!req) return js_mkerr(js, "Out of memory");
3777
3778 req->js = js;
3779 req->op_type = FS_OP_READ_FD;
3780 req->promise = js_mkpromise(js);
3781 req->fd = fd;
3782 req->target_buffer = buf_arg;
3783 req->buf_offset = offset;
3784 req->data_len = length;
3785 req->callback_fn = callback;
3786 req->data = malloc(length > 0 ? length : 1);
3787 if (!req->data) {
3788 free(req);
3789 return js_mkerr(js, "Out of memory");
3790 }
3791 req->uv_req.data = req;
3792
3793 utarray_push_back(pending_requests, &req);
3794
3795 uv_buf_t buf = uv_buf_init(req->data, (unsigned int)length);
3796 int result = uv_fs_read(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_read_fd_complete);
3797
3798 if (result < 0) {
3799 fs_request_fail(req, result);
3800 req->completed = 1;
3801 complete_request(req);
3802 }
3803
3804 return req->promise;
3805}
3806
3807static ant_value_t builtin_fs_readSync(ant_t *js, ant_value_t *args, int nargs) {
3808 if (nargs < 2) return js_mkerr(js, "readSync() requires fd and buffer arguments");
3809 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "readSync() fd must be a number");
3810
3811 int fd = (int)js_getnum(args[0]);
3812
3813 TypedArrayData *ta_data = buffer_get_typedarray_data(args[1]);
3814 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
3815 return js_mkerr(js, "readSync() second argument must be a Buffer, TypedArray, or DataView");
3816
3817 uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
3818 size_t buf_len = ta_data->byte_length;
3819 size_t offset = 0;
3820 size_t length = buf_len;
3821 int64_t position = -1;
3822
3823 if (nargs >= 3) {
3824 if (vtype(args[2]) == T_OBJ) {
3825 ant_value_t off_val = js_get(js, args[2], "offset");
3826 ant_value_t len_val = js_get(js, args[2], "length");
3827 ant_value_t pos_val = js_get(js, args[2], "position");
3828 if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
3829 if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
3830 else length = buf_len - offset;
3831 if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
3832 } else if (vtype(args[2]) == T_NUM) {
3833 offset = (size_t)js_getnum(args[2]);
3834 length = buf_len - offset;
3835 if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
3836 if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
3837 }}
3838
3839 if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
3840 if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
3841
3842 uv_fs_t req;
3843 uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length);
3844 int result = uv_fs_read(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
3845 uv_fs_req_cleanup(&req);
3846
3847 if (result < 0) return js_mkerr(js, "readSync failed: %s", uv_strerror(result));
3848 return js_mknum((double)result);
3849}
3850
3851static ant_value_t builtin_fs_writeSync(ant_t *js, ant_value_t *args, int nargs) {
3852 if (nargs < 2) return js_mkerr(js, "writeSync() requires fd and data arguments");
3853 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writeSync() fd must be a number");
3854
3855 int fd = (int)js_getnum(args[0]);
3856
3857 if (vtype(args[1]) == T_STR) {
3858 size_t str_len;
3859 const char *str = js_getstr(js, args[1], &str_len);
3860 if (!str) return js_mkerr(js, "Failed to get string");
3861
3862 int64_t position = -1;
3863 if (nargs >= 3 && vtype(args[2]) == T_NUM)
3864 position = (int64_t)js_getnum(args[2]);
3865
3866 uv_fs_t req;
3867 uv_buf_t buf = uv_buf_init((char *)str, (unsigned int)str_len);
3868 int result = uv_fs_write(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
3869 uv_fs_req_cleanup(&req);
3870
3871 if (result < 0) return js_mkerr(js, "writeSync failed: %s", uv_strerror(result));
3872 return js_mknum((double)result);
3873 }
3874
3875 TypedArrayData *ta_data = buffer_get_typedarray_data(args[1]);
3876 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
3877 return js_mkerr(js, "writeSync() second argument must be a Buffer, TypedArray, DataView, or string");
3878
3879 uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
3880 size_t buf_len = ta_data->byte_length;
3881 size_t offset = 0;
3882 size_t length = buf_len;
3883 int64_t position = -1;
3884
3885 if (nargs >= 3) {
3886 if (vtype(args[2]) == T_OBJ) {
3887 ant_value_t off_val = js_get(js, args[2], "offset");
3888 ant_value_t len_val = js_get(js, args[2], "length");
3889 ant_value_t pos_val = js_get(js, args[2], "position");
3890 if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
3891 if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
3892 else length = buf_len - offset;
3893 if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
3894 } else if (vtype(args[2]) == T_NUM) {
3895 offset = (size_t)js_getnum(args[2]);
3896 length = buf_len - offset;
3897 if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
3898 if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
3899 }
3900 }
3901
3902 if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
3903 if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
3904
3905 uv_fs_t req;
3906 uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length);
3907 int result = uv_fs_write(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
3908 uv_fs_req_cleanup(&req);
3909
3910 if (result < 0) return js_mkerr(js, "writeSync failed: %s", uv_strerror(result));
3911 return js_mknum((double)result);
3912}
3913
3914static ant_value_t builtin_fs_write_fd(ant_t *js, ant_value_t *args, int nargs) {
3915 if (nargs < 2) return js_mkerr(js, "write() requires fd and data arguments");
3916 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "write() fd must be a number");
3917
3918 int fd = (int)js_getnum(args[0]);
3919 const char *write_data;
3920 size_t write_len;
3921 int64_t position = -1;
3922
3923 if (vtype(args[1]) == T_STR) {
3924 size_t str_len;
3925 const char *str = js_getstr(js, args[1], &str_len);
3926 if (!str) return js_mkerr(js, "Failed to get string");
3927
3928 if (nargs >= 3 && vtype(args[2]) == T_NUM)
3929 position = (int64_t)js_getnum(args[2]);
3930
3931 write_data = str;
3932 write_len = str_len;
3933 } else {
3934 TypedArrayData *ta_data = buffer_get_typedarray_data(args[1]);
3935 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
3936 return js_mkerr(js, "write() second argument must be a Buffer, TypedArray, DataView, or string");
3937
3938 uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
3939 size_t buf_len = ta_data->byte_length;
3940 size_t offset = 0;
3941 size_t length = buf_len;
3942
3943 if (nargs >= 3) {
3944 if (vtype(args[2]) == T_OBJ) {
3945 ant_value_t off_val = js_get(js, args[2], "offset");
3946 ant_value_t len_val = js_get(js, args[2], "length");
3947 ant_value_t pos_val = js_get(js, args[2], "position");
3948 if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
3949 if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
3950 else length = buf_len - offset;
3951 if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
3952 } else if (vtype(args[2]) == T_NUM) {
3953 offset = (size_t)js_getnum(args[2]);
3954 length = buf_len - offset;
3955 if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
3956 if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
3957 }}
3958
3959 if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
3960 if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
3961
3962 write_data = (const char *)(buf_data + offset);
3963 write_len = length;
3964 }
3965
3966 fs_request_t *req = calloc(1, sizeof(fs_request_t));
3967 if (!req) return js_mkerr(js, "Out of memory");
3968
3969 req->js = js;
3970 req->op_type = FS_OP_WRITE_FD;
3971 req->promise = js_mkpromise(js);
3972 req->fd = fd;
3973 req->data = malloc(write_len);
3974 if (!req->data) {
3975 free(req);
3976 return js_mkerr(js, "Out of memory");
3977 }
3978 memcpy(req->data, write_data, write_len);
3979 req->data_len = write_len;
3980 req->uv_req.data = req;
3981
3982 utarray_push_back(pending_requests, &req);
3983
3984 uv_buf_t buf = uv_buf_init(req->data, (unsigned int)req->data_len);
3985 int result = uv_fs_write(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_write_fd_complete);
3986
3987 if (result < 0) {
3988 fs_request_fail(req, result);
3989 req->completed = 1;
3990 complete_request(req);
3991 }
3992
3993 return req->promise;
3994}
3995
3996static ant_value_t builtin_fs_writevSync(ant_t *js, ant_value_t *args, int nargs) {
3997 if (nargs < 2) return js_mkerr(js, "writevSync() requires fd and buffers arguments");
3998 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writevSync() fd must be a number");
3999
4000 int fd = (int)js_getnum(args[0]);
4001 ant_offset_t arr_len = js_arr_len(js, args[1]);
4002 if (arr_len == 0) return js_mknum(0);
4003
4004 int64_t position = -1;
4005 if (nargs >= 3 && vtype(args[2]) == T_NUM)
4006 position = (int64_t)js_getnum(args[2]);
4007
4008 uv_buf_t *bufs = calloc((size_t)arr_len, sizeof(uv_buf_t));
4009 if (!bufs) return js_mkerr(js, "Out of memory");
4010
4011 for (ant_offset_t i = 0; i < arr_len; i++) {
4012 ant_value_t item = js_arr_get(js, args[1], i);
4013 TypedArrayData *ta = buffer_get_typedarray_data(item);
4014 if (!ta || !ta->buffer || !ta->buffer->data) {
4015 free(bufs);
4016 return js_mkerr(js, "writevSync() buffers must contain ArrayBufferViews");
4017 }
4018 bufs[i] = uv_buf_init((char *)(ta->buffer->data + ta->byte_offset), (unsigned int)ta->byte_length);
4019 }
4020
4021 uv_fs_t req;
4022 int result = uv_fs_write(uv_default_loop(), &req, fd, bufs, (unsigned int)arr_len, position, NULL);
4023 uv_fs_req_cleanup(&req);
4024 free(bufs);
4025
4026 if (result < 0) return js_mkerr(js, "writevSync failed: %s", uv_strerror(result));
4027 return js_mknum((double)result);
4028}
4029
4030static ant_value_t builtin_fs_writev_fd(ant_t *js, ant_value_t *args, int nargs) {
4031 if (nargs < 2) return js_mkerr(js, "writev() requires fd and buffers arguments");
4032 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writev() fd must be a number");
4033
4034 int fd = (int)js_getnum(args[0]);
4035 ant_offset_t arr_len = js_arr_len(js, args[1]);
4036
4037 if (arr_len == 0) {
4038 ant_value_t promise = js_mkpromise(js);
4039 js_resolve_promise(js, promise, js_mknum(0));
4040 return promise;
4041 }
4042
4043 int64_t position = -1;
4044 if (nargs >= 3 && vtype(args[2]) == T_NUM)
4045 position = (int64_t)js_getnum(args[2]);
4046
4047 size_t total_len = 0;
4048 for (ant_offset_t i = 0; i < arr_len; i++) {
4049 ant_value_t item = js_arr_get(js, args[1], i);
4050 TypedArrayData *ta = buffer_get_typedarray_data(item);
4051 if (!ta || !ta->buffer || !ta->buffer->data)
4052 return js_mkerr(js, "writev() buffers must contain ArrayBufferViews");
4053 total_len += ta->byte_length;
4054 }
4055
4056 fs_request_t *req = calloc(1, sizeof(fs_request_t));
4057 if (!req) return js_mkerr(js, "Out of memory");
4058
4059 req->data = malloc(total_len);
4060 if (!req->data) {
4061 free(req);
4062 return js_mkerr(js, "Out of memory");
4063 }
4064
4065 size_t off = 0;
4066 for (ant_offset_t i = 0; i < arr_len; i++) {
4067 ant_value_t item = js_arr_get(js, args[1], i);
4068 TypedArrayData *ta = buffer_get_typedarray_data(item);
4069 memcpy(req->data + off, ta->buffer->data + ta->byte_offset, ta->byte_length);
4070 off += ta->byte_length;
4071 }
4072
4073 req->js = js;
4074 req->op_type = FS_OP_WRITE_FD;
4075 req->promise = js_mkpromise(js);
4076 req->fd = fd;
4077 req->data_len = total_len;
4078 req->uv_req.data = req;
4079
4080 utarray_push_back(pending_requests, &req);
4081
4082 uv_buf_t buf = uv_buf_init(req->data, (unsigned int)total_len);
4083 int result = uv_fs_write(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_write_fd_complete);
4084
4085 if (result < 0) {
4086 fs_request_fail(req, result);
4087 req->completed = 1;
4088 complete_request(req);
4089 }
4090
4091 return req->promise;
4092}
4093
4094static int parse_open_flags(ant_t *js, ant_value_t arg) {
4095 if (vtype(arg) == T_NUM) return (int)js_getnum(arg);
4096 if (vtype(arg) != T_STR) return O_RDONLY;
4097
4098 size_t len;
4099 const char *str = js_getstr(js, arg, &len);
4100 if (!str) return O_RDONLY;
4101
4102 if (len == 1 && str[0] == 'r') return O_RDONLY;
4103 if (len == 2 && memcmp(str, "r+", 2) == 0) return O_RDWR;
4104 if (len == 1 && str[0] == 'w') return O_WRONLY | O_CREAT | O_TRUNC;
4105 if (len == 2 && memcmp(str, "wx", 2) == 0) return O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
4106 if (len == 2 && memcmp(str, "w+", 2) == 0) return O_RDWR | O_CREAT | O_TRUNC;
4107 if (len == 3 && memcmp(str, "wx+", 3) == 0) return O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
4108 if (len == 1 && str[0] == 'a') return O_WRONLY | O_CREAT | O_APPEND;
4109 if (len == 2 && memcmp(str, "ax", 2) == 0) return O_WRONLY | O_CREAT | O_APPEND | O_EXCL;
4110 if (len == 2 && memcmp(str, "a+", 2) == 0) return O_RDWR | O_CREAT | O_APPEND;
4111 if (len == 3 && memcmp(str, "ax+", 3) == 0) return O_RDWR | O_CREAT | O_APPEND | O_EXCL;
4112
4113 return O_RDONLY;
4114}
4115
4116static ant_value_t builtin_fs_openSync(ant_t *js, ant_value_t *args, int nargs) {
4117 if (nargs < 1) return js_mkerr(js, "openSync() requires a path argument");
4118 if (vtype(args[0]) != T_STR) return js_mkerr(js, "openSync() path must be a string");
4119
4120 size_t path_len;
4121 char *path = js_getstr(js, args[0], &path_len);
4122 if (!path) return js_mkerr(js, "Failed to get path string");
4123
4124 int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY;
4125 int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666;
4126
4127 char *path_cstr = strndup(path, path_len);
4128 if (!path_cstr) return js_mkerr(js, "Out of memory");
4129
4130 uv_fs_t req;
4131 int result = uv_fs_open(uv_default_loop(), &req, path_cstr, flags, mode, NULL);
4132
4133 if (result < 0) {
4134 ant_value_t err = fs_mk_uv_error(js, result, "open", path_cstr, NULL);
4135 uv_fs_req_cleanup(&req);
4136 free(path_cstr);
4137 return err;
4138 }
4139
4140 uv_fs_req_cleanup(&req);
4141 free(path_cstr);
4142
4143 return js_mknum((double)result);
4144}
4145
4146static ant_value_t builtin_fs_closeSync(ant_t *js, ant_value_t *args, int nargs) {
4147 if (nargs < 1) return js_mkerr(js, "closeSync() requires a fd argument");
4148 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "closeSync() fd must be a number");
4149
4150 int fd = (int)js_getnum(args[0]);
4151
4152 uv_fs_t req;
4153 int result = uv_fs_close(uv_default_loop(), &req, fd, NULL);
4154 uv_fs_req_cleanup(&req);
4155
4156 if (result < 0) return js_mkerr(js, "closeSync failed: %s", uv_strerror(result));
4157 return js_mkundef();
4158}
4159
4160static void on_open_fd_complete(uv_fs_t *uv_req) {
4161 fs_request_t *req = (fs_request_t *)uv_req->data;
4162
4163 if (uv_req->result < 0) {
4164 fs_request_fail(req, (int)uv_req->result);
4165 req->completed = 1;
4166 complete_request(req);
4167 return;
4168 }
4169
4170 req->completed = 1;
4171 js_resolve_promise(req->js, req->promise, js_mknum((double)uv_req->result));
4172 remove_pending_request(req);
4173 free_fs_request(req);
4174}
4175
4176static void on_open_filehandle_complete(uv_fs_t *uv_req) {
4177 fs_request_t *req = (fs_request_t *)uv_req->data;
4178
4179 if (uv_req->result < 0) {
4180 fs_request_fail(req, (int)uv_req->result);
4181 req->completed = 1;
4182 complete_request(req);
4183 return;
4184 }
4185
4186 req->completed = 1;
4187 js_resolve_promise(
4188 req->js, req->promise,
4189 fs_make_filehandle(req->js, (int)uv_req->result)
4190 );
4191
4192 remove_pending_request(req);
4193 free_fs_request(req);
4194}
4195
4196static ant_value_t builtin_fs_open_fd(ant_t *js, ant_value_t *args, int nargs) {
4197 if (nargs < 1) return js_mkerr(js, "open() requires a path argument");
4198 if (vtype(args[0]) != T_STR) return js_mkerr(js, "open() path must be a string");
4199
4200 size_t path_len;
4201 char *path = js_getstr(js, args[0], &path_len);
4202 if (!path) return js_mkerr(js, "Failed to get path string");
4203
4204 int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY;
4205 int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666;
4206
4207 fs_request_t *req = calloc(1, sizeof(fs_request_t));
4208 if (!req) return js_mkerr(js, "Out of memory");
4209
4210 req->js = js;
4211 req->op_type = FS_OP_OPEN;
4212 req->promise = js_mkpromise(js);
4213 req->path = strndup(path, path_len);
4214 req->uv_req.data = req;
4215
4216 utarray_push_back(pending_requests, &req);
4217 int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, flags, mode, on_open_fd_complete);
4218
4219 if (result < 0) {
4220 fs_request_fail(req, result);
4221 req->completed = 1;
4222 complete_request(req);
4223 }
4224
4225 return req->promise;
4226}
4227
4228static ant_value_t builtin_fs_open_filehandle(ant_t *js, ant_value_t *args, int nargs) {
4229 if (nargs < 1) return js_mkerr(js, "open() requires a path argument");
4230 if (vtype(args[0]) != T_STR) return js_mkerr(js, "open() path must be a string");
4231
4232 size_t path_len;
4233 char *path = js_getstr(js, args[0], &path_len);
4234 if (!path) return js_mkerr(js, "Failed to get path string");
4235
4236 int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY;
4237 int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666;
4238
4239 fs_request_t *req = calloc(1, sizeof(fs_request_t));
4240 if (!req) return js_mkerr(js, "Out of memory");
4241
4242 req->js = js;
4243 req->op_type = FS_OP_OPEN;
4244 req->promise = js_mkpromise(js);
4245 req->path = strndup(path, path_len);
4246 req->uv_req.data = req;
4247
4248 utarray_push_back(pending_requests, &req);
4249 int result = uv_fs_open(
4250 uv_default_loop(), &req->uv_req, req->path,
4251 flags, mode, on_open_filehandle_complete
4252 );
4253
4254 if (result < 0) {
4255 fs_request_fail(req, result);
4256 req->completed = 1;
4257 complete_request(req);
4258 }
4259
4260 return req->promise;
4261}
4262
4263static void on_close_fd_complete(uv_fs_t *uv_req) {
4264 fs_request_t *req = (fs_request_t *)uv_req->data;
4265
4266 if (uv_req->result < 0) {
4267 fs_request_fail(req, (int)uv_req->result);
4268 req->completed = 1;
4269 complete_request(req);
4270 return;
4271 }
4272
4273 req->completed = 1;
4274 js_resolve_promise(req->js, req->promise, js_mkundef());
4275 remove_pending_request(req);
4276 free_fs_request(req);
4277}
4278
4279static ant_value_t builtin_fs_close_fd(ant_t *js, ant_value_t *args, int nargs) {
4280 if (nargs < 1) return js_mkerr(js, "close() requires a fd argument");
4281 if (vtype(args[0]) != T_NUM) return js_mkerr(js, "close() fd must be a number");
4282
4283 int fd = (int)js_getnum(args[0]);
4284
4285 fs_request_t *req = calloc(1, sizeof(fs_request_t));
4286 if (!req) return js_mkerr(js, "Out of memory");
4287
4288 req->js = js;
4289 req->op_type = FS_OP_CLOSE;
4290 req->promise = js_mkpromise(js);
4291 req->fd = fd;
4292 req->uv_req.data = req;
4293
4294 utarray_push_back(pending_requests, &req);
4295 int result = uv_fs_close(uv_default_loop(), &req->uv_req, req->fd, on_close_fd_complete);
4296
4297 if (result < 0) {
4298 fs_request_fail(req, result);
4299 req->completed = 1;
4300 complete_request(req);
4301 }
4302
4303 return req->promise;
4304}
4305
4306static ant_value_t builtin_fs_watch(ant_t *js, ant_value_t *args, int nargs) {
4307 ant_value_t path_val = 0;
4308 ant_value_t watcher_obj = 0;
4309 fs_watch_options_t opts;
4310 fs_watcher_t *watcher = NULL;
4311 uv_handle_t *handle = NULL;
4312
4313 const char *path = NULL;
4314 size_t path_len = 0;
4315 int rc = 0;
4316
4317 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "watch() requires a path argument");
4318 if (!fs_parse_watch_options(js, args, nargs, &opts))
4319 return js_mkerr_typed(js, JS_ERR_TYPE, "watch() options must be a string, object, or callback");
4320
4321 path_val = fs_coerce_path(js, args[0]);
4322 if (vtype(path_val) != T_STR) return js_mkerr_typed(js, JS_ERR_TYPE, "watch() path must be a string or URL");
4323
4324 path = js_getstr(js, path_val, &path_len);
4325 if (!path || path_len == 0) return js_mkerr(js, "watch() path must not be empty");
4326
4327 watcher = fs_watcher_new(js, FS_WATCH_MODE_EVENT);
4328 if (!watcher) return js_mkerr(js, "Out of memory");
4329
4330 watcher_obj = fs_watcher_make_object(js, watcher);
4331 if (is_err(watcher_obj)) {
4332 fs_watcher_free(watcher);
4333 return watcher_obj;
4334 }
4335
4336 rc = fs_watcher_start_event(watcher, path, opts.persistent, opts.recursive);
4337 if (rc != 0) {
4338 js_set_native_ptr(watcher_obj, NULL);
4339 fs_watcher_free(watcher);
4340 return fs_watch_error(js, rc, path);
4341 }
4342
4343 fs_add_active_watcher(watcher);
4344 if (!opts.persistent) {
4345 handle = fs_watcher_uv_handle(watcher);
4346 if (handle) uv_unref(handle);
4347 }
4348 if (is_callable(opts.listener))
4349 eventemitter_add_listener(js, watcher_obj, "change", opts.listener, false);
4350 return watcher_obj;
4351}
4352
4353static ant_value_t builtin_fs_watchFile(ant_t *js, ant_value_t *args, int nargs) {
4354 ant_value_t path_val = 0;
4355 fs_watchfile_options_t opts;
4356
4357 fs_watcher_t *watcher = NULL;
4358 uv_handle_t *handle = NULL;
4359
4360 const char *path = NULL;
4361 size_t path_len = 0;
4362 int rc = 0;
4363
4364 if (nargs < 2)
4365 return js_mkerr_typed(js, JS_ERR_TYPE, "watchFile() requires a path and listener");
4366 if (!fs_parse_watchfile_options(js, args, nargs, &opts))
4367 return js_mkerr_typed(js, JS_ERR_TYPE, "watchFile() requires a listener callback");
4368
4369 path_val = fs_coerce_path(js, args[0]);
4370 if (vtype(path_val) != T_STR) return js_mkerr_typed(js, JS_ERR_TYPE, "watchFile() path must be a string or URL");
4371
4372 path = js_getstr(js, path_val, &path_len);
4373 if (!path || path_len == 0) return js_mkerr(js, "watchFile() path must not be empty");
4374
4375 watcher = fs_watcher_new(js, FS_WATCH_MODE_STAT);
4376 if (!watcher) return js_mkerr(js, "Out of memory");
4377
4378 watcher->callback = opts.listener;
4379 watcher->last_stat_valid = fs_stat_path_sync(path, &watcher->last_stat);
4380
4381 rc = fs_watcher_start_poll(watcher, path, opts.persistent, opts.interval_ms);
4382 if (rc != 0) {
4383 fs_watcher_free(watcher);
4384 return fs_watch_error(js, rc, path);
4385 }
4386
4387 fs_add_active_watcher(watcher);
4388 if (!opts.persistent) {
4389 handle = fs_watcher_uv_handle(watcher);
4390 if (handle) uv_unref(handle);
4391 }
4392 return js_mkundef();
4393}
4394
4395static ant_value_t builtin_fs_unwatchFile(ant_t *js, ant_value_t *args, int nargs) {
4396 ant_value_t path_val = 0;
4397 ant_value_t listener = js_mkundef();
4398
4399 char *resolved = NULL;
4400 fs_watcher_t *watcher = NULL;
4401 fs_watcher_t *next = NULL;
4402
4403 const char *path = NULL;
4404 size_t path_len = 0;
4405
4406 if (nargs < 1) return js_mkundef();
4407
4408 path_val = fs_coerce_path(js, args[0]);
4409 if (vtype(path_val) != T_STR) return js_mkundef();
4410
4411 path = js_getstr(js, path_val, &path_len);
4412 if (!path || path_len == 0) return js_mkundef();
4413 if (nargs > 1 && is_callable(args[1])) listener = args[1];
4414
4415 resolved = ant_watch_resolve_path(path);
4416 if (!resolved) return js_mkundef();
4417
4418 for (watcher = active_watchers; watcher; watcher = next) {
4419 next = watcher->next_active;
4420 if (watcher->mode != FS_WATCH_MODE_STAT) continue;
4421 if (!watcher->path || strcmp(watcher->path, resolved) != 0) continue;
4422 if (is_callable(listener) && watcher->callback != listener) continue;
4423 fs_watcher_close_native(watcher);
4424 }
4425
4426 free(resolved);
4427 return js_mkundef();
4428}
4429
4430void init_fs_module(void) {
4431 utarray_new(pending_requests, &ut_ptr_icd);
4432
4433 ant_t *js = rt->js;
4434 ant_value_t glob = js->global;
4435
4436 ant_value_t stats_ctor = js_mkobj(js);
4437 ant_value_t stats_proto = js_mkobj(js);
4438
4439 js_set(js, stats_proto, "isFile", js_mkfun(stat_isFile));
4440 js_set(js, stats_proto, "isDirectory", js_mkfun(stat_isDirectory));
4441 js_set(js, stats_proto, "isSymbolicLink", js_mkfun(stat_isSymbolicLink));
4442 js_set_sym(js, stats_proto, get_toStringTag_sym(), js_mkstr(js, "Stats", 5));
4443
4444 js_mkprop_fast(js, stats_ctor, "prototype", 9, stats_proto);
4445 js_mkprop_fast(js, stats_ctor, "name", 4, js_mkstr(js, "Stats", 5));
4446 js_set_descriptor(js, stats_ctor, "name", 4, 0);
4447
4448 js_set(js, glob, "Stats", js_obj_to_func(stats_ctor));
4449
4450 g_dirent_proto = js_mkobj(js);
4451 js_set(js, g_dirent_proto, "isFile", js_mkfun(dirent_isFile));
4452 js_set(js, g_dirent_proto, "isDirectory", js_mkfun(dirent_isDirectory));
4453 js_set(js, g_dirent_proto, "isSymbolicLink", js_mkfun(dirent_isSymbolicLink));
4454 js_set(js, g_dirent_proto, "isBlockDevice", js_mkfun(dirent_isBlockDevice));
4455 js_set(js, g_dirent_proto, "isCharacterDevice", js_mkfun(dirent_isCharacterDevice));
4456 js_set(js, g_dirent_proto, "isFIFO", js_mkfun(dirent_isFIFO));
4457 js_set(js, g_dirent_proto, "isSocket", js_mkfun(dirent_isSocket));
4458 js_set_sym(js, g_dirent_proto, get_toStringTag_sym(), js_mkstr(js, "Dirent", 6));
4459 gc_register_root(&g_dirent_proto);
4460}
4461
4462static ant_value_t fs_callback_success_handler(ant_t *js, ant_value_t *args, int nargs) {
4463 ant_value_t ctx = js_get_slot(js_getcurrentfunc(js), SLOT_DATA);
4464 ant_value_t callback = js_get_slot(ctx, SLOT_DATA);
4465
4466 if (!is_callable(callback)) return js_mkundef();
4467
4468 if (js_truthy(js, js_get(js, ctx, "existsStyle"))) {
4469 ant_value_t cb_args[1] = { nargs > 0 ? args[0] : js_false };
4470 fs_call_value(js, callback, js_mkundef(), cb_args, 1);
4471 return js_mkundef();
4472 }
4473
4474 ant_value_t cb_args[2] = { js_mknull(), nargs > 0 ? args[0] : js_mkundef() };
4475 fs_call_value(js, callback, js_mkundef(), cb_args, 2);
4476 return js_mkundef();
4477}
4478
4479static ant_value_t fs_callback_error_handler(ant_t *js, ant_value_t *args, int nargs) {
4480 ant_value_t ctx = js_get_slot(js_getcurrentfunc(js), SLOT_DATA);
4481 ant_value_t callback = js_get_slot(ctx, SLOT_DATA);
4482 ant_value_t cb_args[1];
4483
4484 if (!is_callable(callback)) return js_mkundef();
4485
4486 if (js_truthy(js, js_get(js, ctx, "existsStyle"))) {
4487 cb_args[0] = js_false;
4488 fs_call_value(js, callback, js_mkundef(), cb_args, 1);
4489 return js_mkundef();
4490 }
4491
4492 cb_args[0] = nargs > 0 ? args[0] : js_mkundef();
4493 fs_call_value(js, callback, js_mkundef(), cb_args, 1);
4494 return js_mkundef();
4495}
4496
4497static void fs_callback_emit_success(
4498 ant_t *js,
4499 ant_value_t callback,
4500 bool exists_style,
4501 ant_value_t value
4502) {
4503 if (exists_style) {
4504 ant_value_t cb_args[1] = { value };
4505 fs_call_value(js, callback, js_mkundef(), cb_args, 1);
4506 return;
4507 }
4508
4509 ant_value_t cb_args[2] = { js_mknull(), value };
4510 fs_call_value(js, callback, js_mkundef(), cb_args, 2);
4511}
4512
4513static void fs_callback_emit_error(
4514 ant_t *js,
4515 ant_value_t callback,
4516 bool exists_style,
4517 ant_value_t error
4518) {
4519 ant_value_t cb_args[1];
4520
4521 cb_args[0] = exists_style ? js_false : error;
4522 fs_call_value(js, callback, js_mkundef(), cb_args, 1);
4523}
4524
4525static ant_value_t fs_callback_attach_promise(
4526 ant_t *js,
4527 ant_value_t promise,
4528 ant_value_t callback,
4529 bool exists_style
4530) {
4531 GC_ROOT_SAVE(root_mark, js);
4532 ant_value_t success_fn = js_mkundef();
4533 ant_value_t error_fn = js_mkundef();
4534
4535 GC_ROOT_PIN(js, promise);
4536 GC_ROOT_PIN(js, callback);
4537
4538 ant_value_t success_ctx = js_mkobj(js);
4539 GC_ROOT_PIN(js, success_ctx);
4540 ant_value_t error_ctx = js_mkobj(js);
4541 GC_ROOT_PIN(js, error_ctx);
4542
4543 js_set_slot(success_ctx, SLOT_DATA, callback);
4544 js_set_slot(error_ctx, SLOT_DATA, callback);
4545 if (exists_style) {
4546 js_set(js, success_ctx, "existsStyle", js_true);
4547 js_set(js, error_ctx, "existsStyle", js_true);
4548 }
4549
4550 success_fn = js_heavy_mkfun(js, fs_callback_success_handler, success_ctx);
4551 GC_ROOT_PIN(js, success_fn);
4552 error_fn = js_heavy_mkfun(js, fs_callback_error_handler, error_ctx);
4553 GC_ROOT_PIN(js, error_fn);
4554
4555 js_promise_then(js, promise, success_fn, error_fn);
4556 GC_ROOT_RESTORE(js, root_mark);
4557 return js_mkundef();
4558}
4559
4560static ant_value_t fs_callback_wrapper_call(ant_t *js, ant_value_t *args, int nargs) {
4561 GC_ROOT_SAVE(root_mark, js);
4562 ant_value_t wrapper = js_getcurrentfunc(js);
4563 ant_value_t config = js_get_slot(wrapper, SLOT_DATA);
4564 ant_value_t original = js_get_slot(config, SLOT_DATA);
4565
4566 ant_value_t callback = js_mkundef();
4567 ant_value_t result = js_mkundef();
4568 ant_value_t this_arg = js_getthis(js);
4569 ant_value_t ex = js_mkundef();
4570
4571 bool exists_style = false;
4572 int call_nargs = nargs;
4573
4574 GC_ROOT_PIN(js, original);
4575 GC_ROOT_PIN(js, this_arg);
4576
4577 exists_style = js_truthy(js, js_get(js, config, "existsStyle"));
4578 if (nargs > 0 && is_callable(args[nargs - 1])) {
4579 callback = args[nargs - 1];
4580 GC_ROOT_PIN(js, callback);
4581 call_nargs--;
4582 }
4583
4584 result = fs_call_value(js, original, this_arg, args, call_nargs);
4585 GC_ROOT_PIN(js, result);
4586
4587 if (!is_callable(callback)) {
4588 GC_ROOT_RESTORE(js, root_mark);
4589 return result;
4590 }
4591
4592 if (is_err(result) || js->thrown_exists) {
4593 ex = js->thrown_exists ? js->thrown_value : result;
4594 GC_ROOT_PIN(js, ex);
4595 js->thrown_exists = false;
4596 js->thrown_value = js_mkundef();
4597 js->thrown_stack = js_mkundef();
4598 fs_callback_emit_error(js, callback, exists_style, ex);
4599 GC_ROOT_RESTORE(js, root_mark);
4600 return js_mkundef();
4601 }
4602
4603 if (vtype(result) != T_PROMISE) {
4604 fs_callback_emit_success(js, callback, exists_style, result);
4605 GC_ROOT_RESTORE(js, root_mark);
4606 return js_mkundef();
4607 }
4608
4609 ant_value_t attach_result = fs_callback_attach_promise(js, result, callback, exists_style);
4610 GC_ROOT_RESTORE(js, root_mark);
4611 return attach_result;
4612}
4613
4614static ant_value_t fs_make_callback_wrapper(ant_t *js, ant_value_t original, bool exists_style) {
4615 ant_value_t config = js_mkobj(js);
4616
4617 js_set_slot(config, SLOT_DATA, original);
4618 if (exists_style) js_set(js, config, "existsStyle", js_true);
4619 return js_heavy_mkfun(js, fs_callback_wrapper_call, config);
4620}
4621
4622static void fs_set_promise_methods(ant_t *js, ant_value_t lib) {
4623 js_set(js, lib, "appendFile", js_mkfun(builtin_fs_appendFile));
4624 js_set(js, lib, "cp", js_mkfun(builtin_fs_cp));
4625 js_set(js, lib, "copyFile", js_mkfun(builtin_fs_copyFile));
4626 js_set(js, lib, "readFile", js_mkfun(builtin_fs_readFile));
4627 js_set(js, lib, "open", js_mkfun(builtin_fs_open_filehandle));
4628 js_set(js, lib, "close", js_mkfun(builtin_fs_close_fd));
4629 js_set(js, lib, "writeFile", js_mkfun(builtin_fs_writeFile));
4630 js_set(js, lib, "write", js_mkfun(builtin_fs_write_fd));
4631 js_set(js, lib, "writev", js_mkfun(builtin_fs_writev_fd));
4632 js_set(js, lib, "rename", js_mkfun(builtin_fs_rename));
4633 js_set(js, lib, "rm", js_mkfun(builtin_fs_rm));
4634 js_set(js, lib, "unlink", js_mkfun(builtin_fs_unlink));
4635 js_set(js, lib, "mkdir", js_mkfun(builtin_fs_mkdir));
4636 js_set(js, lib, "mkdtemp", js_mkfun(builtin_fs_mkdtemp));
4637 js_set(js, lib, "rmdir", js_mkfun(builtin_fs_rmdir));
4638 js_set(js, lib, "stat", js_mkfun(builtin_fs_stat));
4639 js_set(js, lib, "lstat", js_mkfun(builtin_fs_lstat));
4640 js_set(js, lib, "fstat", js_mkfun(builtin_fs_fstat));
4641 js_set(js, lib, "utimes", js_mkfun(builtin_fs_utimes));
4642 js_set(js, lib, "futimes", js_mkfun(builtin_fs_futimes));
4643 js_set(js, lib, "exists", js_mkfun(builtin_fs_exists));
4644 js_set(js, lib, "access", js_mkfun(builtin_fs_access));
4645 js_set(js, lib, "chmod", js_mkfun(builtin_fs_chmod));
4646 js_set(js, lib, "readdir", js_mkfun(builtin_fs_readdir));
4647 js_set(js, lib, "realpath", js_mkfun(builtin_fs_realpath));
4648 js_set(js, lib, "readlink", js_mkfun(builtin_fs_readlink));
4649}
4650
4651static void fs_set_callback_compatible_methods(ant_t *js, ant_value_t lib) {
4652 ant_value_t realpath = fs_make_callback_wrapper(js, js_mkfun(builtin_fs_realpath), false);
4653
4654 js_set(js, lib, "appendFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_appendFile), false));
4655 js_set(js, lib, "cp", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_cp), false));
4656 js_set(js, lib, "copyFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_copyFile), false));
4657 js_set(js, lib, "readFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readFile), false));
4658 js_set(js, lib, "open", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_open_fd), false));
4659 js_set(js, lib, "close", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_close_fd), false));
4660 js_set(js, lib, "writeFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_writeFile), false));
4661 js_set(js, lib, "write", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_write_fd), false));
4662 js_set(js, lib, "writev", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_writev_fd), false));
4663 js_set(js, lib, "rename", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rename), false));
4664 js_set(js, lib, "rm", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rm), false));
4665 js_set(js, lib, "unlink", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_unlink), false));
4666 js_set(js, lib, "mkdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_mkdir), false));
4667 js_set(js, lib, "mkdtemp", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_mkdtemp), false));
4668 js_set(js, lib, "rmdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rmdir), false));
4669 js_set(js, lib, "stat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_stat), false));
4670 js_set(js, lib, "lstat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_lstat), false));
4671 js_set(js, lib, "fstat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_fstat), false));
4672 js_set(js, lib, "utimes", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_utimes), false));
4673 js_set(js, lib, "futimes", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_futimes), false));
4674 js_set(js, lib, "exists", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_exists), true));
4675 js_set(js, lib, "access", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_access), false));
4676 js_set(js, lib, "chmod", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_chmod), false));
4677 js_set(js, lib, "readdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readdir), false));
4678 js_set(js, lib, "readlink", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readlink), false));
4679
4680 js_set(js, realpath, "native", realpath);
4681 js_set(js, lib, "realpath", realpath);
4682}
4683
4684static ant_value_t fs_make_constants(ant_t *js) {
4685 ant_value_t constants = js_newobj(js);
4686 js_set(js, constants, "F_OK", js_mknum(F_OK));
4687 js_set(js, constants, "R_OK", js_mknum(R_OK));
4688 js_set(js, constants, "W_OK", js_mknum(W_OK));
4689 js_set(js, constants, "X_OK", js_mknum(X_OK));
4690 js_set(js, constants, "O_RDONLY", js_mknum(O_RDONLY));
4691 js_set(js, constants, "O_WRONLY", js_mknum(O_WRONLY));
4692 js_set(js, constants, "O_RDWR", js_mknum(O_RDWR));
4693 js_set(js, constants, "O_CREAT", js_mknum(O_CREAT));
4694 js_set(js, constants, "O_EXCL", js_mknum(O_EXCL));
4695 js_set(js, constants, "O_TRUNC", js_mknum(O_TRUNC));
4696 js_set(js, constants, "O_APPEND", js_mknum(O_APPEND));
4697#ifdef O_SYMLINK
4698 js_set(js, constants, "O_SYMLINK", js_mknum(O_SYMLINK));
4699#endif
4700#ifdef O_NOFOLLOW
4701 js_set(js, constants, "O_NOFOLLOW", js_mknum(O_NOFOLLOW));
4702#endif
4703#ifdef S_IFMT
4704 js_set(js, constants, "S_IFMT", js_mknum(S_IFMT));
4705#endif
4706#ifdef S_IFREG
4707 js_set(js, constants, "S_IFREG", js_mknum(S_IFREG));
4708#endif
4709#ifdef S_IFDIR
4710 js_set(js, constants, "S_IFDIR", js_mknum(S_IFDIR));
4711#endif
4712#ifdef S_IFLNK
4713 js_set(js, constants, "S_IFLNK", js_mknum(S_IFLNK));
4714#endif
4715 return constants;
4716}
4717
4718static ant_value_t builtin_fs_promises_getter(ant_t *js, ant_value_t *args, int nargs) {
4719 ant_value_t getter_fn = js_getcurrentfunc(js);
4720 ant_value_t cached = js_get_slot(getter_fn, SLOT_DATA);
4721 if (is_object_type(cached)) return cached;
4722
4723 ant_value_t promises = fs_promises_library(js);
4724 js_set_slot(getter_fn, SLOT_DATA, promises);
4725 return promises;
4726}
4727
4728ant_value_t fs_library(ant_t *js) {
4729 ant_value_t lib = js_mkobj(js);
4730 ant_value_t realpath_sync = js_heavy_mkfun(js, builtin_fs_realpathSync, js_mkundef());
4731
4732 fs_set_callback_compatible_methods(js, lib);
4733 fs_init_watch_constructors(js);
4734 fs_init_stream_constructors(js);
4735
4736 js_set(js, lib, "read", js_mkfun(builtin_fs_read_fd));
4737 js_set(js, lib, "readFileSync", js_mkfun(builtin_fs_readFileSync));
4738 js_set(js, lib, "readSync", js_mkfun(builtin_fs_readSync));
4739 js_set(js, lib, "stream", js_mkfun(builtin_fs_readBytes));
4740 js_set(js, lib, "createReadStream", js_mkfun(builtin_fs_createReadStream));
4741 js_set(js, lib, "createWriteStream", js_mkfun(builtin_fs_createWriteStream));
4742 js_set(js, lib, "openSync", js_mkfun(builtin_fs_openSync));
4743 js_set(js, lib, "closeSync", js_mkfun(builtin_fs_closeSync));
4744 js_set(js, lib, "writeFileSync", js_mkfun(builtin_fs_writeFileSync));
4745 js_set(js, lib, "writeSync", js_mkfun(builtin_fs_writeSync));
4746 js_set(js, lib, "writevSync", js_mkfun(builtin_fs_writevSync));
4747 js_set(js, lib, "appendFileSync", js_mkfun(builtin_fs_appendFileSync));
4748 js_set(js, lib, "cpSync", js_mkfun(builtin_fs_cpSync));
4749 js_set(js, lib, "copyFileSync", js_mkfun(builtin_fs_copyFileSync));
4750 js_set(js, lib, "renameSync", js_mkfun(builtin_fs_renameSync));
4751 js_set(js, lib, "rmSync", js_mkfun(builtin_fs_rmSync));
4752 js_set(js, lib, "unlinkSync", js_mkfun(builtin_fs_unlinkSync));
4753 js_set(js, lib, "mkdirSync", js_mkfun(builtin_fs_mkdirSync));
4754 js_set(js, lib, "mkdtempSync", js_mkfun(builtin_fs_mkdtempSync));
4755 js_set(js, lib, "rmdirSync", js_mkfun(builtin_fs_rmdirSync));
4756 js_set(js, lib, "statSync", js_mkfun(builtin_fs_statSync));
4757 js_set(js, lib, "lstatSync", js_mkfun(builtin_fs_lstatSync));
4758 js_set(js, lib, "fstatSync", js_mkfun(builtin_fs_fstatSync));
4759 js_set(js, lib, "utimesSync", js_mkfun(builtin_fs_utimesSync));
4760 js_set(js, lib, "futimesSync", js_mkfun(builtin_fs_futimesSync));
4761 js_set(js, lib, "existsSync", js_mkfun(builtin_fs_existsSync));
4762 js_set(js, lib, "accessSync", js_mkfun(builtin_fs_accessSync));
4763 js_set(js, lib, "chmodSync", js_mkfun(builtin_fs_chmodSync));
4764 js_set(js, lib, "readdirSync", js_mkfun(builtin_fs_readdirSync));
4765 js_set(js, lib, "realpathSync", realpath_sync);
4766 js_set(js, lib, "readlinkSync", js_mkfun(builtin_fs_readlinkSync));
4767 js_set(js, lib, "watch", js_mkfun(builtin_fs_watch));
4768 js_set(js, lib, "watchFile", js_mkfun(builtin_fs_watchFile));
4769 js_set(js, lib, "unwatchFile", js_mkfun(builtin_fs_unwatchFile));
4770 js_set(js, lib, "FSWatcher", g_fswatcher_ctor);
4771 js_set(js, lib, "ReadStream", g_readstream_ctor);
4772 js_set(js, lib, "WriteStream", g_writestream_ctor);
4773 js_set(js, realpath_sync, "native", realpath_sync);
4774
4775 js_set_getter_desc(
4776 js, lib,
4777 "promises", 8,
4778 js_heavy_mkfun(js, builtin_fs_promises_getter, js_mkundef()),
4779 JS_DESC_E | JS_DESC_C
4780 );
4781
4782 js_set(js, lib, "constants", fs_make_constants(js));
4783 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "fs", 2));
4784
4785 return lib;
4786}
4787
4788ant_value_t fs_promises_library(ant_t *js) {
4789 ant_value_t lib = js_mkobj(js);
4790
4791 fs_set_promise_methods(js, lib);
4792 js_set(js, lib, "constants", fs_make_constants(js));
4793
4794 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "fs/promises", 11));
4795 return lib;
4796}
4797
4798ant_value_t fs_constants_library(ant_t *js) {
4799 ant_value_t constants = fs_make_constants(js);
4800 js_set(js, constants, "default", constants);
4801 js_set_slot_wb(js, constants, SLOT_DEFAULT, constants);
4802 js_set_sym(js, constants, get_toStringTag_sym(), js_mkstr(js, "constants", 9));
4803 return constants;
4804}
4805
4806int has_pending_fs_ops(void) {
4807 return pending_requests && utarray_len(pending_requests) > 0;
4808}
4809
4810void gc_mark_fs(ant_t *js, gc_mark_fn mark) {
4811 fs_watcher_t *watcher = NULL;
4812
4813 if (g_fswatcher_proto) mark(js, g_fswatcher_proto);
4814 if (g_fswatcher_ctor) mark(js, g_fswatcher_ctor);
4815 if (g_filehandle_proto) mark(js, g_filehandle_proto);
4816 if (g_readstream_proto) mark(js, g_readstream_proto);
4817 if (g_readstream_ctor) mark(js, g_readstream_ctor);
4818 if (g_writestream_proto) mark(js, g_writestream_proto);
4819 if (g_writestream_ctor) mark(js, g_writestream_ctor);
4820 if (!pending_requests) return;
4821
4822 unsigned int len = utarray_len(pending_requests);
4823 for (unsigned int i = 0; i < len; i++) {
4824 fs_request_t **reqp = (fs_request_t **)utarray_eltptr(pending_requests, i);
4825 if (reqp && *reqp) {
4826 mark(js, (*reqp)->promise);
4827 if (is_object_type((*reqp)->target_buffer)) mark(js, (*reqp)->target_buffer);
4828 if (is_callable((*reqp)->callback_fn)) mark(js, (*reqp)->callback_fn);
4829 }}
4830
4831 for (watcher = active_watchers; watcher; watcher = watcher->next_active) {
4832 if (vtype(watcher->obj) == T_OBJ) mark(js, watcher->obj);
4833 if (vtype(watcher->callback) != T_UNDEF) mark(js, watcher->callback);
4834 }
4835}