Skip to main content

bun_runtime/
node_fs.rs

1// @trace REQ-ENG-007
2use ::std::cell::{Cell, RefCell};
3use ::std::collections::{HashMap, VecDeque};
4use ::std::fs;
5use ::std::path::{Path, PathBuf};
6use ::std::sync::{Arc, Mutex};
7use bao_engine::context::RawValueRootGuard;
8use bun_core::ZBox;
9use bun_sys::fs as bun_fs;
10// @trace REQ-ENG-005 [algorithm:base64] base64 via workspace bun_base64 (SIMD-accelerated)
11
12use mozjs::glue::NewCompileOptions;
13use mozjs::jsapi::*;
14use mozjs::jsval::{DoubleValue, Int32Value, JSVal, StringValue, UndefinedValue};
15use mozjs::rooted;
16use mozjs::rust::wrappers2 as w2;
17
18use crate::require::cache_builtin;
19
20// --- Async I/O infrastructure ---
21// Background I/O uses std::thread::spawn + Arc<Mutex<Option<Result>>> shared slot.
22// Completion is scheduled on the JS thread via bao_uloop::uws_loop_defer (next_tick).
23
24/// Result of statfs() — simplified subset of fields exposed by Node.js fs.statfs().
25#[allow(dead_code)]
26struct StatfsResult {
27    type_: u64,
28    bsize: u64,
29    #[allow(dead_code)]
30    frsize: u64,
31    blocks: u64,
32    bfree: u64,
33    bavail: u64,
34    files: u64,
35    ffree: u64,
36}
37
38#[allow(dead_code)]
39enum FsAsyncResult {
40    Ok(Vec<u8>),
41    OkStat(bun_sys::PosixStat),
42    OkString(String),
43    OkVoid,
44    OkBool(bool),
45    OkI32(i32),
46    OkOpen(i32),
47    OkRead { bytes_read: i32, buffer: Vec<u8> },
48    OkWrite(i32),
49    OkDirnames(Vec<String>),
50    OkStatfs(StatfsResult),
51    OkDirents(Vec<(String, bool)>),
52}
53
54struct FsAsyncCtx {
55    cx: *mut JSContext,
56    /// Raw callback pointer captured at spawn. Prefer `cb_root.get(0)` —
57    /// the guard's slot is updated in place by a moving GC; this pointer is
58    /// only the fallback for the rooting-failed path.
59    callback: *mut JSObject,
60    /// RAII heap root for the callback value, spanning the worker-thread
61    /// window. Released when this Box drops (defer callback or the
62    /// degenerate no-loop path), liveness-guarded.
63    cb_root: Option<RawValueRootGuard>,
64    result: Arc<Mutex<Option<::std::result::Result<FsAsyncResult, (String, String)>>>>,
65    encoding: Option<String>,
66    op_name: String,
67    path: String,
68}
69
70unsafe fn schedule_defer(ctx: *mut FsAsyncCtx) {
71    bao_uloop::force_link();
72    let loop_ = bao_uloop::uws_get_loop();
73    if loop_.is_null() {
74        let _ = Box::from_raw(ctx);
75        return;
76    }
77    bao_uloop::uws_loop_defer(
78        loop_,
79        ctx as *mut ::std::ffi::c_void,
80        fs_async_defer_callback,
81    );
82}
83
84unsafe extern "C" fn fs_async_defer_callback(raw_ctx: *mut ::std::ffi::c_void) {
85    let ctx = Box::from_raw(raw_ctx as *mut FsAsyncCtx);
86    let cx = ctx.cx;
87    // Live callback value: prefer the RAII root's slot (updated in place by
88    // a moving GC) over the raw pointer captured at spawn time.
89    let cb_value = ctx.cb_root.as_ref().map_or_else(
90        || mozjs::jsval::ObjectValue(ctx.callback),
91        |g| g.get(0),
92    );
93    let encoding = ctx.encoding.as_deref();
94    let _op_name = &ctx.op_name;
95
96    let mut result_guard = ctx.result.lock().unwrap();
97    let result_opt = result_guard.take();
98    ::std::mem::drop(result_guard);
99
100    let mut wrapped_cx =
101        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
102    let cx_ref = &mut wrapped_cx;
103
104    rooted!(&in(cx_ref) let cb_val = cb_value);
105    let global = CurrentGlobalOrNull(cx);
106    if global.is_null() {
107        return;
108    }
109    rooted!(&in(cx_ref) let global_rooted = global);
110
111    match result_opt {
112        Some(Ok(FsAsyncResult::Ok(data))) => {
113            let val = string_or_buffer(cx, &data, encoding);
114            rooted!(&in(cx_ref) let val_rooted = val);
115            let args_arr = [UndefinedValue(), val_rooted.get()];
116            let cb_args = HandleValueArray {
117                length_: 2,
118                elements_: args_arr.as_ptr(),
119            };
120            let mut rval = UndefinedValue();
121            JS_CallFunctionValue(
122                cx,
123                global_rooted.handle().into(),
124                cb_val.handle().into(),
125                &cb_args,
126                MutableHandle::<Value> {
127                    _phantom_0: ::std::marker::PhantomData,
128                    ptr: &mut rval,
129                },
130            );
131            JS_ClearPendingException(cx);
132        }
133        Some(Ok(FsAsyncResult::OkStat(stat))) => {
134            let stats_obj = create_stats_object(cx, &stat);
135            rooted!(&in(cx_ref) let stats_val = mozjs::jsval::ObjectValue(stats_obj));
136            let args_arr = [UndefinedValue(), stats_val.get()];
137            let cb_args = HandleValueArray {
138                length_: 2,
139                elements_: args_arr.as_ptr(),
140            };
141            let mut rval = UndefinedValue();
142            JS_CallFunctionValue(
143                cx,
144                global_rooted.handle().into(),
145                cb_val.handle().into(),
146                &cb_args,
147                MutableHandle::<Value> {
148                    _phantom_0: ::std::marker::PhantomData,
149                    ptr: &mut rval,
150                },
151            );
152            JS_ClearPendingException(cx);
153        }
154        Some(Ok(FsAsyncResult::OkString(s))) => {
155            let c_str = ZBox::from_bytes(s.as_bytes());
156            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
157            let val = if js_str.is_null() {
158                UndefinedValue()
159            } else {
160                mozjs::jsval::StringValue(&*js_str)
161            };
162            rooted!(&in(cx_ref) let val_rooted = val);
163            let args_arr = [UndefinedValue(), val_rooted.get()];
164            let cb_args = HandleValueArray {
165                length_: 2,
166                elements_: args_arr.as_ptr(),
167            };
168            let mut rval = UndefinedValue();
169            JS_CallFunctionValue(
170                cx,
171                global_rooted.handle().into(),
172                cb_val.handle().into(),
173                &cb_args,
174                MutableHandle::<Value> {
175                    _phantom_0: ::std::marker::PhantomData,
176                    ptr: &mut rval,
177                },
178            );
179            JS_ClearPendingException(cx);
180        }
181        Some(Ok(FsAsyncResult::OkVoid)) => {
182            let args_arr = [UndefinedValue()];
183            let cb_args = HandleValueArray {
184                length_: 1,
185                elements_: args_arr.as_ptr(),
186            };
187            let mut rval = UndefinedValue();
188            JS_CallFunctionValue(
189                cx,
190                global_rooted.handle().into(),
191                cb_val.handle().into(),
192                &cb_args,
193                MutableHandle::<Value> {
194                    _phantom_0: ::std::marker::PhantomData,
195                    ptr: &mut rval,
196                },
197            );
198            JS_ClearPendingException(cx);
199        }
200        Some(Ok(FsAsyncResult::OkBool(b))) => {
201            rooted!(&in(cx_ref) let val = mozjs::jsval::BooleanValue(b));
202            let args_arr = [UndefinedValue(), val.get()];
203            let cb_args = HandleValueArray {
204                length_: 2,
205                elements_: args_arr.as_ptr(),
206            };
207            let mut rval = UndefinedValue();
208            JS_CallFunctionValue(
209                cx,
210                global_rooted.handle().into(),
211                cb_val.handle().into(),
212                &cb_args,
213                MutableHandle::<Value> {
214                    _phantom_0: ::std::marker::PhantomData,
215                    ptr: &mut rval,
216                },
217            );
218            JS_ClearPendingException(cx);
219        }
220        Some(Ok(FsAsyncResult::OkI32(v))) => {
221            rooted!(&in(cx_ref) let val = mozjs::jsval::Int32Value(v));
222            let args_arr = [UndefinedValue(), val.get()];
223            let cb_args = HandleValueArray {
224                length_: 2,
225                elements_: args_arr.as_ptr(),
226            };
227            let mut rval = UndefinedValue();
228            JS_CallFunctionValue(
229                cx,
230                global_rooted.handle().into(),
231                cb_val.handle().into(),
232                &cb_args,
233                MutableHandle::<Value> {
234                    _phantom_0: ::std::marker::PhantomData,
235                    ptr: &mut rval,
236                },
237            );
238            JS_ClearPendingException(cx);
239        }
240        Some(Ok(FsAsyncResult::OkOpen(fd))) => {
241            rooted!(&in(cx_ref) let val = mozjs::jsval::Int32Value(fd));
242            let args_arr = [UndefinedValue(), val.get()];
243            let cb_args = HandleValueArray {
244                length_: 2,
245                elements_: args_arr.as_ptr(),
246            };
247            let mut rval = UndefinedValue();
248            JS_CallFunctionValue(
249                cx,
250                global_rooted.handle().into(),
251                cb_val.handle().into(),
252                &cb_args,
253                MutableHandle::<Value> {
254                    _phantom_0: ::std::marker::PhantomData,
255                    ptr: &mut rval,
256                },
257            );
258            JS_ClearPendingException(cx);
259        }
260        Some(Ok(FsAsyncResult::OkRead { bytes_read, buffer })) => {
261            let buf_obj = crate::globals::create_buffer_object(cx, &buffer);
262            let buf_val = if buf_obj.is_null() {
263                UndefinedValue()
264            } else {
265                mozjs::jsval::ObjectValue(buf_obj)
266            };
267            rooted!(&in(cx_ref) let buf_rooted = buf_val);
268            rooted!(&in(cx_ref) let br_val = mozjs::jsval::Int32Value(bytes_read));
269            let args_arr = [UndefinedValue(), br_val.get(), buf_rooted.get()];
270            let cb_args = HandleValueArray {
271                length_: 3,
272                elements_: args_arr.as_ptr(),
273            };
274            let mut rval = UndefinedValue();
275            JS_CallFunctionValue(
276                cx,
277                global_rooted.handle().into(),
278                cb_val.handle().into(),
279                &cb_args,
280                MutableHandle::<Value> {
281                    _phantom_0: ::std::marker::PhantomData,
282                    ptr: &mut rval,
283                },
284            );
285            JS_ClearPendingException(cx);
286        }
287        Some(Ok(FsAsyncResult::OkWrite(written))) => {
288            rooted!(&in(cx_ref) let val = mozjs::jsval::Int32Value(written));
289            let args_arr = [UndefinedValue(), val.get()];
290            let cb_args = HandleValueArray {
291                length_: 2,
292                elements_: args_arr.as_ptr(),
293            };
294            let mut rval = UndefinedValue();
295            JS_CallFunctionValue(
296                cx,
297                global_rooted.handle().into(),
298                cb_val.handle().into(),
299                &cb_args,
300                MutableHandle::<Value> {
301                    _phantom_0: ::std::marker::PhantomData,
302                    ptr: &mut rval,
303                },
304            );
305            JS_ClearPendingException(cx);
306        }
307        Some(Ok(FsAsyncResult::OkDirnames(names))) => {
308            rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, names.len()));
309            if !arr.get().is_null() {
310                for (idx, name) in names.iter().enumerate() {
311                    let c_name = ZBox::from_bytes(name.as_bytes());
312                    let js_str = JS_NewStringCopyZ(cx, c_name.as_ptr());
313                    if !js_str.is_null() {
314                        rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
315                        JS_DefineElement(
316                            cx,
317                            arr.handle().into(),
318                            idx as u32,
319                            val.handle().into(),
320                            JSPROP_ENUMERATE as u32,
321                        );
322                    }
323                }
324            }
325            rooted!(&in(cx_ref) let arr_val = mozjs::jsval::ObjectValue(arr.get()));
326            let args_arr = [UndefinedValue(), arr_val.get()];
327            let cb_args = HandleValueArray {
328                length_: 2,
329                elements_: args_arr.as_ptr(),
330            };
331            let mut rval = UndefinedValue();
332            JS_CallFunctionValue(
333                cx,
334                global_rooted.handle().into(),
335                cb_val.handle().into(),
336                &cb_args,
337                MutableHandle::<Value> {
338                    _phantom_0: ::std::marker::PhantomData,
339                    ptr: &mut rval,
340                },
341            );
342            JS_ClearPendingException(cx);
343        }
344        Some(Ok(FsAsyncResult::OkDirents(entries))) => {
345            rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, entries.len()));
346            if !arr.get().is_null() {
347                for (idx, (name, is_dir)) in entries.iter().enumerate() {
348                    let dirent = create_dirent(cx, name, *is_dir);
349                    rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(dirent));
350                    JS_DefineElement(
351                        cx,
352                        arr.handle().into(),
353                        idx as u32,
354                        val.handle().into(),
355                        JSPROP_ENUMERATE as u32,
356                    );
357                }
358            }
359            rooted!(&in(cx_ref) let arr_val = mozjs::jsval::ObjectValue(arr.get()));
360            let args_arr = [UndefinedValue(), arr_val.get()];
361            let cb_args = HandleValueArray {
362                length_: 2,
363                elements_: args_arr.as_ptr(),
364            };
365            let mut rval = UndefinedValue();
366            JS_CallFunctionValue(
367                cx,
368                global_rooted.handle().into(),
369                cb_val.handle().into(),
370                &cb_args,
371                MutableHandle::<Value> {
372                    _phantom_0: ::std::marker::PhantomData,
373                    ptr: &mut rval,
374                },
375            );
376            JS_ClearPendingException(cx);
377        }
378        Some(Ok(FsAsyncResult::OkStatfs(sf))) => {
379            let stats_obj = create_statfs_object(cx, &sf);
380            rooted!(&in(cx_ref) let stats_val = mozjs::jsval::ObjectValue(stats_obj));
381            let args_arr = [UndefinedValue(), stats_val.get()];
382            let cb_args = HandleValueArray {
383                length_: 2,
384                elements_: args_arr.as_ptr(),
385            };
386            let mut rval = UndefinedValue();
387            JS_CallFunctionValue(
388                cx,
389                global_rooted.handle().into(),
390                cb_val.handle().into(),
391                &cb_args,
392                MutableHandle::<Value> {
393                    _phantom_0: ::std::marker::PhantomData,
394                    ptr: &mut rval,
395                },
396            );
397            JS_ClearPendingException(cx);
398        }
399        Some(Err((code, msg))) => {
400            rooted!(&in(cx_ref) let err_obj = JS_NewPlainObject(cx));
401            if !err_obj.get().is_null() {
402                let c_msg = ZBox::from_bytes(msg.as_bytes());
403                let js_str = JS_NewStringCopyZ(cx, c_msg.as_ptr());
404                if !js_str.is_null() {
405                    rooted!(&in(cx_ref) let msg_val = mozjs::jsval::StringValue(&*js_str));
406                    JS_DefineProperty(
407                        cx,
408                        err_obj.handle().into(),
409                        c"message".as_ptr(),
410                        msg_val.handle().into(),
411                        JSPROP_ENUMERATE as u32,
412                    );
413                }
414                let c_code = ZBox::from_bytes(code.as_bytes());
415                let code_str = JS_NewStringCopyZ(cx, c_code.as_ptr());
416                if !code_str.is_null() {
417                    rooted!(&in(cx_ref) let code_val = mozjs::jsval::StringValue(&*code_str));
418                    JS_DefineProperty(
419                        cx,
420                        err_obj.handle().into(),
421                        c"code".as_ptr(),
422                        code_val.handle().into(),
423                        JSPROP_ENUMERATE as u32,
424                    );
425                }
426                let c_path = ZBox::from_bytes(ctx.path.as_bytes());
427                let path_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
428                if !path_str.is_null() {
429                    rooted!(&in(cx_ref) let path_val = mozjs::jsval::StringValue(&*path_str));
430                    JS_DefineProperty(
431                        cx,
432                        err_obj.handle().into(),
433                        c"path".as_ptr(),
434                        path_val.handle().into(),
435                        JSPROP_ENUMERATE as u32,
436                    );
437                }
438            }
439            rooted!(&in(cx_ref) let err_val = mozjs::jsval::ObjectValue(err_obj.get()));
440            let args_arr = [err_val.get()];
441            let cb_args = HandleValueArray {
442                length_: 1,
443                elements_: args_arr.as_ptr(),
444            };
445            let mut rval = UndefinedValue();
446            JS_CallFunctionValue(
447                cx,
448                global_rooted.handle().into(),
449                cb_val.handle().into(),
450                &cb_args,
451                MutableHandle::<Value> {
452                    _phantom_0: ::std::marker::PhantomData,
453                    ptr: &mut rval,
454                },
455            );
456            JS_ClearPendingException(cx);
457        }
458        None => {
459            let null_args = HandleValueArray::empty();
460            let mut rval = UndefinedValue();
461            JS_CallFunctionValue(
462                cx,
463                global_rooted.handle().into(),
464                cb_val.handle().into(),
465                &null_args,
466                MutableHandle::<Value> {
467                    _phantom_0: ::std::marker::PhantomData,
468                    ptr: &mut rval,
469                },
470            );
471            JS_ClearPendingException(cx);
472        }
473    }
474    // Terminal unroot is RAII: `ctx` (Box<FsAsyncCtx>) drops at the end of
475    // this callback, releasing the `cb_root` heap root with the correct
476    // registered address on every exit path (including the null-global
477    // early return above).
478}
479
480#[allow(unsafe_op_in_unsafe_fn)]
481unsafe fn extract_callback_and_encoding(
482    cx: *mut JSContext,
483    args: &CallArgs,
484    start_idx: u32,
485) -> Option<(*mut JSObject, Option<String>)> {
486    let mut cb_idx = None;
487    let mut encoding = None;
488    for i in start_idx..args.argc_ {
489        let val = *args.get(i).ptr;
490        if val.is_object() {
491            if cb_idx.is_none() {
492                cb_idx = Some(i);
493            } else if encoding.is_none() {
494                encoding = get_encoding_opt(cx, args, i);
495            }
496        } else if val.is_string() && encoding.is_none() {
497            encoding = Some(crate::jsstr_to_rust_string(cx, val.to_string()));
498        }
499    }
500    cb_idx.map(|idx| ((*args.get(idx).ptr).to_object(), encoding))
501}
502
503fn io_error_code(err: &::std::io::Error) -> &'static str {
504    match err.kind() {
505        ::std::io::ErrorKind::NotFound => "ENOENT",
506        ::std::io::ErrorKind::PermissionDenied => "EACCES",
507        ::std::io::ErrorKind::AlreadyExists => "EEXIST",
508        _ => "ERR",
509    }
510}
511
512#[allow(unsafe_op_in_unsafe_fn)]
513unsafe fn spawn_fs_async<F>(
514    cx: *mut JSContext,
515    op_name: &str,
516    path: String,
517    callback: *mut JSObject,
518    encoding: Option<String>,
519    work: F,
520) where
521    F: FnOnce() -> ::std::result::Result<FsAsyncResult, ::std::io::Error> + Send + 'static,
522{
523    // Heap-root the callback value for the async window via the RAII guard
524    // (stable heap slot the GC updates in place; unrooted when the
525    // FsAsyncCtx Box drops, with the correct registered address).
526    let cb_val = mozjs::jsval::ObjectValue(callback);
527    let cb_root = unsafe {
528        RawValueRootGuard::new(cx, ::std::slice::from_ref(&cb_val), c"fs_async_cb")
529    };
530
531    let result_slot: Arc<Mutex<Option<::std::result::Result<FsAsyncResult, (String, String)>>>> =
532        Arc::new(Mutex::new(None));
533    let result_slot_clone = result_slot.clone();
534
535    let op_name_owned = op_name.to_string();
536    let path_for_err = path.clone();
537
538    let ctx = Box::new(FsAsyncCtx {
539        cx,
540        callback,
541        cb_root,
542        result: result_slot,
543        encoding,
544        op_name: op_name.to_string(),
545        path,
546    });
547    let ctx_ptr = Box::into_raw(ctx) as usize;
548
549    ::std::thread::spawn(move || {
550        let result = work();
551        let stored = match result {
552            Ok(v) => Ok(v),
553            Err(e) => {
554                let code = io_error_code(&e).to_string();
555                let msg = format!("{} '{}': {}", op_name_owned, path_for_err, e);
556                Err((code, msg))
557            }
558        };
559        {
560            let mut slot = result_slot_clone.lock().unwrap();
561            *slot = Some(stored);
562        }
563        schedule_defer(ctx_ptr as *mut FsAsyncCtx);
564    });
565}
566
567const FS_STREAM_JS: &str = r#"
568(function() {
569  var fs = globalThis.__fs_stream_ref;
570  var flush = globalThis.__bao_fs_stream_flush;
571
572  function EE() { this._events = {}; }
573  EE.prototype.on = function(e, fn) {
574    (this._events[e] || (this._events[e] = [])).push(fn);
575    return this;
576  };
577  EE.prototype.emit = function(e) {
578    var a = Array.prototype.slice.call(arguments, 1);
579    var ls = this._events[e];
580    if (ls) for (var i = 0; i < ls.length; i++) ls[i].apply(this, a);
581    return !!ls;
582  };
583
584  function createReadStream(path, opts) {
585    var s = new EE();
586    s.path = path;
587    s.readable = true;
588    s.writable = false;
589    s.bytesRead = 0;
590    var encoding = (opts && opts.encoding) || null;
591    try {
592      var data = fs.readFileSync(path, encoding);
593      s.bytesRead = (typeof data === 'string') ? data.length : 0;
594      setTimeout(function() {
595        s.emit('open', 0);
596        if (data) s.emit('data', data);
597        s.emit('end');
598        s.emit('close');
599      }, 0);
600    } catch(e) {
601      setTimeout(function() { s.emit('error', e); }, 0);
602    }
603    s.pipe = function(dest) {
604      this.on('data', function(c) { dest.write(c); });
605      this.on('end', function() { dest.end(); });
606      return dest;
607    };
608    s.destroy = function() { this.readable = false; this.emit('close'); return this; };
609    return s;
610  }
611
612  function createWriteStream(path, opts) {
613    var s = new EE();
614    s.path = path;
615    s.readable = false;
616    s.writable = true;
617    s.bytesWritten = 0;
618    s._buffer = [];
619    s._ended = false;
620    setTimeout(function() { s.emit('open', 0); }, 0);
621    s.write = function(chunk) {
622      if (this._ended) return false;
623      // Keep chunks RAW: strings stay strings, Buffer/TypedArray chunks stay
624      // binary views (flush extracts their bytes natively). Non-string
625      // non-object primitives keep the legacy String() coercion.
626      if (typeof chunk !== 'string' && typeof chunk !== 'object') chunk = String(chunk);
627      this._buffer.push(chunk);
628      this.bytesWritten += (chunk && chunk.length) || 0;
629      return true;
630    };
631    s.end = function(chunk) {
632      if (chunk) {
633        if (typeof chunk !== 'string' && typeof chunk !== 'object') chunk = String(chunk);
634        this._buffer.push(chunk);
635        this.bytesWritten += (chunk && chunk.length) || 0;
636      }
637      this._ended = true;
638      this.writable = false;
639      try {
640        // Binary-safe flush: string chunks → UTF-8 bytes (byte-identical to
641        // the old join('') + writeFileSync(string)); view chunks → raw bytes.
642        flush(this.path, this._buffer);
643        this.emit('finish');
644      } catch(e) {
645        this.emit('error', e);
646      }
647      this.emit('close');
648      return this;
649    };
650    s.destroy = function() { this.writable = false; this.emit('close'); return this; };
651    return s;
652  }
653
654  return { createReadStream: createReadStream, createWriteStream: createWriteStream };
655})();
656"#;
657
658pub fn install(cx: &mut mozjs::context::JSContext) {
659    rooted!(&in(cx) let fs_obj = unsafe { w2::JS_NewPlainObject(cx) });
660    if fs_obj.get().is_null() {
661        return;
662    }
663
664    unsafe {
665        // Sync methods
666        w2::JS_DefineFunction(
667            cx,
668            fs_obj.handle(),
669            c"readFileSync".as_ptr(),
670            Some(fs_read_file_sync),
671            1,
672            JSPROP_ENUMERATE as u32,
673        );
674        w2::JS_DefineFunction(
675            cx,
676            fs_obj.handle(),
677            c"writeFileSync".as_ptr(),
678            Some(fs_write_file_sync),
679            2,
680            JSPROP_ENUMERATE as u32,
681        );
682        w2::JS_DefineFunction(
683            cx,
684            fs_obj.handle(),
685            c"appendFileSync".as_ptr(),
686            Some(fs_append_file_sync),
687            2,
688            JSPROP_ENUMERATE as u32,
689        );
690        w2::JS_DefineFunction(
691            cx,
692            fs_obj.handle(),
693            c"existsSync".as_ptr(),
694            Some(fs_exists_sync),
695            1,
696            JSPROP_ENUMERATE as u32,
697        );
698        w2::JS_DefineFunction(
699            cx,
700            fs_obj.handle(),
701            c"mkdirSync".as_ptr(),
702            Some(fs_mkdir_sync),
703            1,
704            JSPROP_ENUMERATE as u32,
705        );
706        w2::JS_DefineFunction(
707            cx,
708            fs_obj.handle(),
709            c"readdirSync".as_ptr(),
710            Some(fs_readdir_sync),
711            1,
712            JSPROP_ENUMERATE as u32,
713        );
714        w2::JS_DefineFunction(
715            cx,
716            fs_obj.handle(),
717            c"statSync".as_ptr(),
718            Some(fs_stat_sync),
719            1,
720            JSPROP_ENUMERATE as u32,
721        );
722        w2::JS_DefineFunction(
723            cx,
724            fs_obj.handle(),
725            c"lstatSync".as_ptr(),
726            Some(fs_lstat_sync),
727            1,
728            JSPROP_ENUMERATE as u32,
729        );
730        w2::JS_DefineFunction(
731            cx,
732            fs_obj.handle(),
733            c"unlinkSync".as_ptr(),
734            Some(fs_unlink_sync),
735            1,
736            JSPROP_ENUMERATE as u32,
737        );
738        w2::JS_DefineFunction(
739            cx,
740            fs_obj.handle(),
741            c"rmdirSync".as_ptr(),
742            Some(fs_rmdir_sync),
743            1,
744            JSPROP_ENUMERATE as u32,
745        );
746        w2::JS_DefineFunction(
747            cx,
748            fs_obj.handle(),
749            c"rmSync".as_ptr(),
750            Some(fs_rm_sync),
751            1,
752            JSPROP_ENUMERATE as u32,
753        );
754        w2::JS_DefineFunction(
755            cx,
756            fs_obj.handle(),
757            c"renameSync".as_ptr(),
758            Some(fs_rename_sync),
759            2,
760            JSPROP_ENUMERATE as u32,
761        );
762        w2::JS_DefineFunction(
763            cx,
764            fs_obj.handle(),
765            c"copyFileSync".as_ptr(),
766            Some(fs_copy_file_sync),
767            2,
768            JSPROP_ENUMERATE as u32,
769        );
770        w2::JS_DefineFunction(
771            cx,
772            fs_obj.handle(),
773            c"chmodSync".as_ptr(),
774            Some(fs_chmod_sync),
775            2,
776            JSPROP_ENUMERATE as u32,
777        );
778        w2::JS_DefineFunction(
779            cx,
780            fs_obj.handle(),
781            c"realpathSync".as_ptr(),
782            Some(fs_realpath_sync),
783            1,
784            JSPROP_ENUMERATE as u32,
785        );
786        w2::JS_DefineFunction(
787            cx,
788            fs_obj.handle(),
789            c"readlinkSync".as_ptr(),
790            Some(fs_readlink_sync),
791            1,
792            JSPROP_ENUMERATE as u32,
793        );
794        w2::JS_DefineFunction(
795            cx,
796            fs_obj.handle(),
797            c"symlinkSync".as_ptr(),
798            Some(fs_symlink_sync),
799            2,
800            JSPROP_ENUMERATE as u32,
801        );
802        w2::JS_DefineFunction(
803            cx,
804            fs_obj.handle(),
805            c"linkSync".as_ptr(),
806            Some(fs_link_sync),
807            2,
808            JSPROP_ENUMERATE as u32,
809        );
810        w2::JS_DefineFunction(
811            cx,
812            fs_obj.handle(),
813            c"cpSync".as_ptr(),
814            Some(fs_cp_sync),
815            2,
816            JSPROP_ENUMERATE as u32,
817        );
818        w2::JS_DefineFunction(
819            cx,
820            fs_obj.handle(),
821            c"cp".as_ptr(),
822            Some(fs_cp),
823            3,
824            JSPROP_ENUMERATE as u32,
825        );
826        w2::JS_DefineFunction(
827            cx,
828            fs_obj.handle(),
829            c"watch".as_ptr(),
830            Some(fs_watch),
831            2,
832            JSPROP_ENUMERATE as u32,
833        );
834        w2::JS_DefineFunction(
835            cx,
836            fs_obj.handle(),
837            c"watchFile".as_ptr(),
838            Some(fs_watch_file),
839            2,
840            JSPROP_ENUMERATE as u32,
841        );
842        w2::JS_DefineFunction(
843            cx,
844            fs_obj.handle(),
845            c"unwatchFile".as_ptr(),
846            Some(fs_unwatch_file),
847            2,
848            JSPROP_ENUMERATE as u32,
849        );
850        w2::JS_DefineFunction(
851            cx,
852            fs_obj.handle(),
853            c"statfsSync".as_ptr(),
854            Some(fs_statfs_sync),
855            1,
856            JSPROP_ENUMERATE as u32,
857        );
858        w2::JS_DefineFunction(
859            cx,
860            fs_obj.handle(),
861            c"openSync".as_ptr(),
862            Some(fs_open_sync),
863            2,
864            JSPROP_ENUMERATE as u32,
865        );
866        w2::JS_DefineFunction(
867            cx,
868            fs_obj.handle(),
869            c"closeSync".as_ptr(),
870            Some(fs_close_sync),
871            1,
872            JSPROP_ENUMERATE as u32,
873        );
874        w2::JS_DefineFunction(
875            cx,
876            fs_obj.handle(),
877            c"readSync".as_ptr(),
878            Some(fs_read_sync),
879            4,
880            JSPROP_ENUMERATE as u32,
881        );
882        w2::JS_DefineFunction(
883            cx,
884            fs_obj.handle(),
885            c"writeSync".as_ptr(),
886            Some(fs_write_sync),
887            4,
888            JSPROP_ENUMERATE as u32,
889        );
890        w2::JS_DefineFunction(
891            cx,
892            fs_obj.handle(),
893            c"mkdtempSync".as_ptr(),
894            Some(fs_mkdtemp_sync),
895            1,
896            JSPROP_ENUMERATE as u32,
897        );
898        w2::JS_DefineFunction(
899            cx,
900            fs_obj.handle(),
901            c"fchmodSync".as_ptr(),
902            Some(fs_fchmod_sync),
903            2,
904            JSPROP_ENUMERATE as u32,
905        );
906        w2::JS_DefineFunction(
907            cx,
908            fs_obj.handle(),
909            c"fchownSync".as_ptr(),
910            Some(fs_fchown_sync),
911            3,
912            JSPROP_ENUMERATE as u32,
913        );
914        w2::JS_DefineFunction(
915            cx,
916            fs_obj.handle(),
917            c"fdatasyncSync".as_ptr(),
918            Some(fs_fdatasync_sync),
919            1,
920            JSPROP_ENUMERATE as u32,
921        );
922        w2::JS_DefineFunction(
923            cx,
924            fs_obj.handle(),
925            c"fsyncSync".as_ptr(),
926            Some(fs_fsync_sync),
927            1,
928            JSPROP_ENUMERATE as u32,
929        );
930        w2::JS_DefineFunction(
931            cx,
932            fs_obj.handle(),
933            c"ftruncateSync".as_ptr(),
934            Some(fs_ftruncate_sync),
935            2,
936            JSPROP_ENUMERATE as u32,
937        );
938        w2::JS_DefineFunction(
939            cx,
940            fs_obj.handle(),
941            c"truncateSync".as_ptr(),
942            Some(fs_truncate_sync),
943            2,
944            JSPROP_ENUMERATE as u32,
945        );
946        w2::JS_DefineFunction(
947            cx,
948            fs_obj.handle(),
949            c"opendirSync".as_ptr(),
950            Some(fs_opendir_sync),
951            1,
952            JSPROP_ENUMERATE as u32,
953        );
954        w2::JS_DefineFunction(
955            cx,
956            fs_obj.handle(),
957            c"futimesSync".as_ptr(),
958            Some(fs_futimes_sync),
959            3,
960            JSPROP_ENUMERATE as u32,
961        );
962        w2::JS_DefineFunction(
963            cx,
964            fs_obj.handle(),
965            c"lchmodSync".as_ptr(),
966            Some(fs_lchmod_sync),
967            2,
968            JSPROP_ENUMERATE as u32,
969        );
970        w2::JS_DefineFunction(
971            cx,
972            fs_obj.handle(),
973            c"lchownSync".as_ptr(),
974            Some(fs_lchown_sync),
975            3,
976            JSPROP_ENUMERATE as u32,
977        );
978        w2::JS_DefineFunction(
979            cx,
980            fs_obj.handle(),
981            c"readvSync".as_ptr(),
982            Some(fs_readv_sync),
983            3,
984            JSPROP_ENUMERATE as u32,
985        );
986        w2::JS_DefineFunction(
987            cx,
988            fs_obj.handle(),
989            c"writevSync".as_ptr(),
990            Some(fs_writev_sync),
991            3,
992            JSPROP_ENUMERATE as u32,
993        );
994        w2::JS_DefineFunction(
995            cx,
996            fs_obj.handle(),
997            c"globSync".as_ptr(),
998            Some(fs_glob_sync),
999            2,
1000            JSPROP_ENUMERATE as u32,
1001        );
1002        w2::JS_DefineFunction(
1003            cx,
1004            fs_obj.handle(),
1005            c"openAsBlob".as_ptr(),
1006            Some(fs_open_as_blob),
1007            2,
1008            JSPROP_ENUMERATE as u32,
1009        );
1010
1011        // Async methods
1012        w2::JS_DefineFunction(
1013            cx,
1014            fs_obj.handle(),
1015            c"readFile".as_ptr(),
1016            Some(fs_read_file),
1017            2,
1018            JSPROP_ENUMERATE as u32,
1019        );
1020        w2::JS_DefineFunction(
1021            cx,
1022            fs_obj.handle(),
1023            c"writeFile".as_ptr(),
1024            Some(fs_write_file),
1025            3,
1026            JSPROP_ENUMERATE as u32,
1027        );
1028        w2::JS_DefineFunction(
1029            cx,
1030            fs_obj.handle(),
1031            c"mkdir".as_ptr(),
1032            Some(fs_mkdir),
1033            2,
1034            JSPROP_ENUMERATE as u32,
1035        );
1036        w2::JS_DefineFunction(
1037            cx,
1038            fs_obj.handle(),
1039            c"appendFile".as_ptr(),
1040            Some(fs_append_file),
1041            3,
1042            JSPROP_ENUMERATE as u32,
1043        );
1044        w2::JS_DefineFunction(
1045            cx,
1046            fs_obj.handle(),
1047            c"access".as_ptr(),
1048            Some(fs_access),
1049            2,
1050            JSPROP_ENUMERATE as u32,
1051        );
1052        w2::JS_DefineFunction(
1053            cx,
1054            fs_obj.handle(),
1055            c"chmod".as_ptr(),
1056            Some(fs_chmod),
1057            2,
1058            JSPROP_ENUMERATE as u32,
1059        );
1060        w2::JS_DefineFunction(
1061            cx,
1062            fs_obj.handle(),
1063            c"chown".as_ptr(),
1064            Some(fs_chown),
1065            3,
1066            JSPROP_ENUMERATE as u32,
1067        );
1068        w2::JS_DefineFunction(
1069            cx,
1070            fs_obj.handle(),
1071            c"close".as_ptr(),
1072            Some(fs_close),
1073            1,
1074            JSPROP_ENUMERATE as u32,
1075        );
1076        w2::JS_DefineFunction(
1077            cx,
1078            fs_obj.handle(),
1079            c"copyFile".as_ptr(),
1080            Some(fs_copy_file),
1081            2,
1082            JSPROP_ENUMERATE as u32,
1083        );
1084        w2::JS_DefineFunction(
1085            cx,
1086            fs_obj.handle(),
1087            c"exists".as_ptr(),
1088            Some(fs_exists),
1089            1,
1090            JSPROP_ENUMERATE as u32,
1091        );
1092        w2::JS_DefineFunction(
1093            cx,
1094            fs_obj.handle(),
1095            c"fchmod".as_ptr(),
1096            Some(fs_fchmod),
1097            2,
1098            JSPROP_ENUMERATE as u32,
1099        );
1100        w2::JS_DefineFunction(
1101            cx,
1102            fs_obj.handle(),
1103            c"fchown".as_ptr(),
1104            Some(fs_fchown),
1105            3,
1106            JSPROP_ENUMERATE as u32,
1107        );
1108        w2::JS_DefineFunction(
1109            cx,
1110            fs_obj.handle(),
1111            c"fdatasync".as_ptr(),
1112            Some(fs_fdatasync),
1113            1,
1114            JSPROP_ENUMERATE as u32,
1115        );
1116        w2::JS_DefineFunction(
1117            cx,
1118            fs_obj.handle(),
1119            c"fstat".as_ptr(),
1120            Some(fs_fstat),
1121            1,
1122            JSPROP_ENUMERATE as u32,
1123        );
1124        w2::JS_DefineFunction(
1125            cx,
1126            fs_obj.handle(),
1127            c"fsync".as_ptr(),
1128            Some(fs_fsync),
1129            1,
1130            JSPROP_ENUMERATE as u32,
1131        );
1132        w2::JS_DefineFunction(
1133            cx,
1134            fs_obj.handle(),
1135            c"ftruncate".as_ptr(),
1136            Some(fs_ftruncate),
1137            2,
1138            JSPROP_ENUMERATE as u32,
1139        );
1140        w2::JS_DefineFunction(
1141            cx,
1142            fs_obj.handle(),
1143            c"futimes".as_ptr(),
1144            Some(fs_futimes),
1145            3,
1146            JSPROP_ENUMERATE as u32,
1147        );
1148        w2::JS_DefineFunction(
1149            cx,
1150            fs_obj.handle(),
1151            c"lchown".as_ptr(),
1152            Some(fs_lchown),
1153            3,
1154            JSPROP_ENUMERATE as u32,
1155        );
1156        w2::JS_DefineFunction(
1157            cx,
1158            fs_obj.handle(),
1159            c"link".as_ptr(),
1160            Some(fs_link),
1161            2,
1162            JSPROP_ENUMERATE as u32,
1163        );
1164        w2::JS_DefineFunction(
1165            cx,
1166            fs_obj.handle(),
1167            c"lstat".as_ptr(),
1168            Some(fs_lstat),
1169            1,
1170            JSPROP_ENUMERATE as u32,
1171        );
1172        w2::JS_DefineFunction(
1173            cx,
1174            fs_obj.handle(),
1175            c"lutimes".as_ptr(),
1176            Some(fs_lutimes),
1177            3,
1178            JSPROP_ENUMERATE as u32,
1179        );
1180        w2::JS_DefineFunction(
1181            cx,
1182            fs_obj.handle(),
1183            c"mkdtemp".as_ptr(),
1184            Some(fs_mkdtemp),
1185            1,
1186            JSPROP_ENUMERATE as u32,
1187        );
1188        w2::JS_DefineFunction(
1189            cx,
1190            fs_obj.handle(),
1191            c"open".as_ptr(),
1192            Some(fs_open),
1193            3,
1194            JSPROP_ENUMERATE as u32,
1195        );
1196        w2::JS_DefineFunction(
1197            cx,
1198            fs_obj.handle(),
1199            c"opendir".as_ptr(),
1200            Some(fs_opendir),
1201            1,
1202            JSPROP_ENUMERATE as u32,
1203        );
1204        w2::JS_DefineFunction(
1205            cx,
1206            fs_obj.handle(),
1207            c"read".as_ptr(),
1208            Some(fs_read),
1209            4,
1210            JSPROP_ENUMERATE as u32,
1211        );
1212        w2::JS_DefineFunction(
1213            cx,
1214            fs_obj.handle(),
1215            c"readdir".as_ptr(),
1216            Some(fs_readdir),
1217            2,
1218            JSPROP_ENUMERATE as u32,
1219        );
1220        w2::JS_DefineFunction(
1221            cx,
1222            fs_obj.handle(),
1223            c"readlink".as_ptr(),
1224            Some(fs_readlink),
1225            1,
1226            JSPROP_ENUMERATE as u32,
1227        );
1228        w2::JS_DefineFunction(
1229            cx,
1230            fs_obj.handle(),
1231            c"readv".as_ptr(),
1232            Some(fs_readv),
1233            4,
1234            JSPROP_ENUMERATE as u32,
1235        );
1236        w2::JS_DefineFunction(
1237            cx,
1238            fs_obj.handle(),
1239            c"realpath".as_ptr(),
1240            Some(fs_realpath),
1241            1,
1242            JSPROP_ENUMERATE as u32,
1243        );
1244        w2::JS_DefineFunction(
1245            cx,
1246            fs_obj.handle(),
1247            c"rename".as_ptr(),
1248            Some(fs_rename),
1249            2,
1250            JSPROP_ENUMERATE as u32,
1251        );
1252        w2::JS_DefineFunction(
1253            cx,
1254            fs_obj.handle(),
1255            c"rm".as_ptr(),
1256            Some(fs_rm),
1257            2,
1258            JSPROP_ENUMERATE as u32,
1259        );
1260        w2::JS_DefineFunction(
1261            cx,
1262            fs_obj.handle(),
1263            c"rmdir".as_ptr(),
1264            Some(fs_rmdir),
1265            1,
1266            JSPROP_ENUMERATE as u32,
1267        );
1268        w2::JS_DefineFunction(
1269            cx,
1270            fs_obj.handle(),
1271            c"stat".as_ptr(),
1272            Some(fs_stat),
1273            1,
1274            JSPROP_ENUMERATE as u32,
1275        );
1276        w2::JS_DefineFunction(
1277            cx,
1278            fs_obj.handle(),
1279            c"symlink".as_ptr(),
1280            Some(fs_symlink),
1281            2,
1282            JSPROP_ENUMERATE as u32,
1283        );
1284        w2::JS_DefineFunction(
1285            cx,
1286            fs_obj.handle(),
1287            c"truncate".as_ptr(),
1288            Some(fs_truncate),
1289            2,
1290            JSPROP_ENUMERATE as u32,
1291        );
1292        w2::JS_DefineFunction(
1293            cx,
1294            fs_obj.handle(),
1295            c"unlink".as_ptr(),
1296            Some(fs_unlink),
1297            1,
1298            JSPROP_ENUMERATE as u32,
1299        );
1300        w2::JS_DefineFunction(
1301            cx,
1302            fs_obj.handle(),
1303            c"utimes".as_ptr(),
1304            Some(fs_utimes),
1305            3,
1306            JSPROP_ENUMERATE as u32,
1307        );
1308        w2::JS_DefineFunction(
1309            cx,
1310            fs_obj.handle(),
1311            c"write".as_ptr(),
1312            Some(fs_write),
1313            4,
1314            JSPROP_ENUMERATE as u32,
1315        );
1316        w2::JS_DefineFunction(
1317            cx,
1318            fs_obj.handle(),
1319            c"writev".as_ptr(),
1320            Some(fs_writev),
1321            4,
1322            JSPROP_ENUMERATE as u32,
1323        );
1324        w2::JS_DefineFunction(
1325            cx,
1326            fs_obj.handle(),
1327            c"statfs".as_ptr(),
1328            Some(fs_statfs),
1329            1,
1330            JSPROP_ENUMERATE as u32,
1331        );
1332        w2::JS_DefineFunction(
1333            cx,
1334            fs_obj.handle(),
1335            c"glob".as_ptr(),
1336            Some(fs_glob),
1337            3,
1338            JSPROP_ENUMERATE as u32,
1339        );
1340
1341        // Constants
1342        let constants: &[(&str, i32)] = &[("F_OK", 0), ("R_OK", 4), ("W_OK", 2), ("X_OK", 1)];
1343        for (name, value) in constants {
1344            let c_name = ZBox::from_bytes(name.as_bytes());
1345            rooted!(&in(cx) let val = mozjs::jsval::Int32Value(*value));
1346            JS_DefineProperty(
1347                cx.raw_cx(),
1348                fs_obj.handle().into(),
1349                c_name.as_ptr(),
1350                val.handle().into(),
1351                JSPROP_ENUMERATE as u32,
1352            );
1353        }
1354
1355        // fs.promises namespace
1356        rooted!(&in(cx) let promises_obj = w2::JS_NewPlainObject(cx));
1357        if !promises_obj.get().is_null() {
1358            w2::JS_DefineFunction(
1359                cx,
1360                promises_obj.handle(),
1361                c"readFile".as_ptr(),
1362                Some(fs_promises_read_file),
1363                1,
1364                JSPROP_ENUMERATE as u32,
1365            );
1366            w2::JS_DefineFunction(
1367                cx,
1368                promises_obj.handle(),
1369                c"writeFile".as_ptr(),
1370                Some(fs_promises_write_file),
1371                2,
1372                JSPROP_ENUMERATE as u32,
1373            );
1374            w2::JS_DefineFunction(
1375                cx,
1376                promises_obj.handle(),
1377                c"stat".as_ptr(),
1378                Some(fs_promises_stat),
1379                1,
1380                JSPROP_ENUMERATE as u32,
1381            );
1382            w2::JS_DefineFunction(
1383                cx,
1384                promises_obj.handle(),
1385                c"readdir".as_ptr(),
1386                Some(fs_promises_readdir),
1387                1,
1388                JSPROP_ENUMERATE as u32,
1389            );
1390            w2::JS_DefineFunction(
1391                cx,
1392                promises_obj.handle(),
1393                c"mkdir".as_ptr(),
1394                Some(fs_promises_mkdir),
1395                1,
1396                JSPROP_ENUMERATE as u32,
1397            );
1398            w2::JS_DefineFunction(
1399                cx,
1400                promises_obj.handle(),
1401                c"unlink".as_ptr(),
1402                Some(fs_promises_unlink),
1403                1,
1404                JSPROP_ENUMERATE as u32,
1405            );
1406            w2::JS_DefineFunction(
1407                cx,
1408                promises_obj.handle(),
1409                c"rename".as_ptr(),
1410                Some(fs_promises_rename),
1411                2,
1412                JSPROP_ENUMERATE as u32,
1413            );
1414            w2::JS_DefineFunction(
1415                cx,
1416                promises_obj.handle(),
1417                c"copyFile".as_ptr(),
1418                Some(fs_promises_copy_file),
1419                2,
1420                JSPROP_ENUMERATE as u32,
1421            );
1422            w2::JS_DefineFunction(
1423                cx,
1424                promises_obj.handle(),
1425                c"lstat".as_ptr(),
1426                Some(fs_promises_lstat),
1427                1,
1428                JSPROP_ENUMERATE as u32,
1429            );
1430            w2::JS_DefineFunction(
1431                cx,
1432                promises_obj.handle(),
1433                c"appendFile".as_ptr(),
1434                Some(fs_promises_append_file),
1435                2,
1436                JSPROP_ENUMERATE as u32,
1437            );
1438            w2::JS_DefineFunction(
1439                cx,
1440                promises_obj.handle(),
1441                c"chmod".as_ptr(),
1442                Some(fs_promises_chmod),
1443                2,
1444                JSPROP_ENUMERATE as u32,
1445            );
1446            w2::JS_DefineFunction(
1447                cx,
1448                promises_obj.handle(),
1449                c"chown".as_ptr(),
1450                Some(fs_promises_chown),
1451                3,
1452                JSPROP_ENUMERATE as u32,
1453            );
1454            w2::JS_DefineFunction(
1455                cx,
1456                promises_obj.handle(),
1457                c"access".as_ptr(),
1458                Some(fs_promises_access),
1459                1,
1460                JSPROP_ENUMERATE as u32,
1461            );
1462            w2::JS_DefineFunction(
1463                cx,
1464                promises_obj.handle(),
1465                c"rm".as_ptr(),
1466                Some(fs_promises_rm),
1467                1,
1468                JSPROP_ENUMERATE as u32,
1469            );
1470            w2::JS_DefineFunction(
1471                cx,
1472                promises_obj.handle(),
1473                c"rmdir".as_ptr(),
1474                Some(fs_promises_rmdir),
1475                1,
1476                JSPROP_ENUMERATE as u32,
1477            );
1478            w2::JS_DefineFunction(
1479                cx,
1480                promises_obj.handle(),
1481                c"realpath".as_ptr(),
1482                Some(fs_promises_realpath),
1483                1,
1484                JSPROP_ENUMERATE as u32,
1485            );
1486            w2::JS_DefineFunction(
1487                cx,
1488                promises_obj.handle(),
1489                c"readlink".as_ptr(),
1490                Some(fs_promises_readlink),
1491                1,
1492                JSPROP_ENUMERATE as u32,
1493            );
1494            w2::JS_DefineFunction(
1495                cx,
1496                promises_obj.handle(),
1497                c"symlink".as_ptr(),
1498                Some(fs_promises_symlink),
1499                2,
1500                JSPROP_ENUMERATE as u32,
1501            );
1502            w2::JS_DefineFunction(
1503                cx,
1504                promises_obj.handle(),
1505                c"link".as_ptr(),
1506                Some(fs_promises_link),
1507                2,
1508                JSPROP_ENUMERATE as u32,
1509            );
1510            w2::JS_DefineFunction(
1511                cx,
1512                promises_obj.handle(),
1513                c"truncate".as_ptr(),
1514                Some(fs_promises_truncate),
1515                2,
1516                JSPROP_ENUMERATE as u32,
1517            );
1518            w2::JS_DefineFunction(
1519                cx,
1520                promises_obj.handle(),
1521                c"utimes".as_ptr(),
1522                Some(fs_promises_utimes),
1523                3,
1524                JSPROP_ENUMERATE as u32,
1525            );
1526            w2::JS_DefineFunction(
1527                cx,
1528                promises_obj.handle(),
1529                c"mkdtemp".as_ptr(),
1530                Some(fs_promises_mkdtemp),
1531                1,
1532                JSPROP_ENUMERATE as u32,
1533            );
1534            w2::JS_DefineFunction(
1535                cx,
1536                promises_obj.handle(),
1537                c"open".as_ptr(),
1538                Some(fs_promises_open),
1539                2,
1540                JSPROP_ENUMERATE as u32,
1541            );
1542            w2::JS_DefineFunction(
1543                cx,
1544                promises_obj.handle(),
1545                c"read".as_ptr(),
1546                Some(fs_promises_read),
1547                4,
1548                JSPROP_ENUMERATE as u32,
1549            );
1550            w2::JS_DefineFunction(
1551                cx,
1552                promises_obj.handle(),
1553                c"write".as_ptr(),
1554                Some(fs_promises_write),
1555                4,
1556                JSPROP_ENUMERATE as u32,
1557            );
1558            w2::JS_DefineFunction(
1559                cx,
1560                promises_obj.handle(),
1561                c"statfs".as_ptr(),
1562                Some(fs_promises_statfs),
1563                1,
1564                JSPROP_ENUMERATE as u32,
1565            );
1566
1567            rooted!(&in(cx) let prom_val = mozjs::jsval::ObjectValue(promises_obj.get()));
1568            JS_DefineProperty(
1569                cx.raw_cx(),
1570                fs_obj.handle().into(),
1571                c"promises".as_ptr(),
1572                prom_val.handle().into(),
1573                JSPROP_ENUMERATE as u32,
1574            );
1575        }
1576    }
1577
1578    // Evaluate createReadStream/createWriteStream polyfill
1579    unsafe {
1580        let global = JS::CurrentGlobalOrNull(cx.raw_cx());
1581        if !global.is_null() {
1582            rooted!(&in(cx) let global_rooted = global);
1583            rooted!(&in(cx) let fs_val = mozjs::jsval::ObjectValue(fs_obj.get()));
1584            JS_DefineProperty(
1585                cx.raw_cx(),
1586                global_rooted.handle().into(),
1587                c"__fs_stream_ref".as_ptr(),
1588                fs_val.handle().into(),
1589                JSPROP_ENUMERATE as u32,
1590            );
1591            // Hidden native the polyfill captures in its closure for the
1592            // binary-safe end() flush; deleted right after evaluation (the
1593            // captured reference stays alive) so no API surface is added.
1594            w2::JS_DefineFunction(
1595                cx,
1596                global_rooted.handle(),
1597                c"__bao_fs_stream_flush".as_ptr(),
1598                Some(fs_write_stream_flush),
1599                2,
1600                0,
1601            );
1602
1603            let c_filename = ZBox::from_bytes("node:fs:streams".as_bytes());
1604            let opts = NewCompileOptions(cx.raw_cx(), c_filename.as_ptr(), 1);
1605            if !opts.is_null() {
1606                let mut src = mozjs::rust::transform_str_to_source_text(FS_STREAM_JS);
1607                let mut rval = UndefinedValue();
1608                let rval_handle = MutableHandle::<Value> {
1609                    _phantom_0: ::std::marker::PhantomData,
1610                    ptr: &mut rval,
1611                };
1612                let ok = mozjs_sys::jsapi::JS::Evaluate2(cx.raw_cx(), opts, &mut src, rval_handle);
1613                libc::free(opts as *mut _);
1614
1615                if ok && rval.is_object() {
1616                    let exports = rval.to_object();
1617                    rooted!(&in(cx) let exports_rooted = exports);
1618
1619                    for name in &["createReadStream", "createWriteStream"] {
1620                        let cname = ZBox::from_bytes(name.as_bytes());
1621                        let mut val = UndefinedValue();
1622                        JS_GetProperty(
1623                            cx.raw_cx(),
1624                            exports_rooted.handle().into(),
1625                            cname.as_ptr(),
1626                            MutableHandle::<Value> {
1627                                _phantom_0: ::std::marker::PhantomData,
1628                                ptr: &mut val,
1629                            },
1630                        );
1631                        if !val.is_undefined() {
1632                            rooted!(&in(cx) let val_root = val);
1633                            JS_DefineProperty(
1634                                cx.raw_cx(),
1635                                fs_obj.handle().into(),
1636                                cname.as_ptr(),
1637                                val_root.handle().into(),
1638                                JSPROP_ENUMERATE as u32,
1639                            );
1640                        }
1641                    }
1642                }
1643            }
1644
1645            JS_DeleteProperty1(
1646                cx.raw_cx(),
1647                global_rooted.handle().into(),
1648                c"__fs_stream_ref".as_ptr(),
1649            );
1650            JS_DeleteProperty1(
1651                cx.raw_cx(),
1652                global_rooted.handle().into(),
1653                c"__bao_fs_stream_flush".as_ptr(),
1654            );
1655        }
1656    }
1657
1658    cache_builtin(cx, "fs", fs_obj.get());
1659
1660    // Register fs/promises sub-path — reuses the same promise-based methods
1661    // already defined on fs.promises. The sub-path module `require("fs/promises")`
1662    // gets its own top-level builtin with the promise methods directly on it.
1663    unsafe {
1664        rooted!(&in(cx) let fsp_obj = w2::JS_NewPlainObject(cx));
1665        if !fsp_obj.get().is_null() {
1666            w2::JS_DefineFunction(
1667                cx,
1668                fsp_obj.handle(),
1669                c"readFile".as_ptr(),
1670                Some(fs_promises_read_file),
1671                1,
1672                JSPROP_ENUMERATE as u32,
1673            );
1674            w2::JS_DefineFunction(
1675                cx,
1676                fsp_obj.handle(),
1677                c"writeFile".as_ptr(),
1678                Some(fs_promises_write_file),
1679                2,
1680                JSPROP_ENUMERATE as u32,
1681            );
1682            w2::JS_DefineFunction(
1683                cx,
1684                fsp_obj.handle(),
1685                c"stat".as_ptr(),
1686                Some(fs_promises_stat),
1687                1,
1688                JSPROP_ENUMERATE as u32,
1689            );
1690            w2::JS_DefineFunction(
1691                cx,
1692                fsp_obj.handle(),
1693                c"readdir".as_ptr(),
1694                Some(fs_promises_readdir),
1695                1,
1696                JSPROP_ENUMERATE as u32,
1697            );
1698            w2::JS_DefineFunction(
1699                cx,
1700                fsp_obj.handle(),
1701                c"mkdir".as_ptr(),
1702                Some(fs_promises_mkdir),
1703                1,
1704                JSPROP_ENUMERATE as u32,
1705            );
1706            w2::JS_DefineFunction(
1707                cx,
1708                fsp_obj.handle(),
1709                c"unlink".as_ptr(),
1710                Some(fs_promises_unlink),
1711                1,
1712                JSPROP_ENUMERATE as u32,
1713            );
1714            w2::JS_DefineFunction(
1715                cx,
1716                fsp_obj.handle(),
1717                c"rename".as_ptr(),
1718                Some(fs_promises_rename),
1719                2,
1720                JSPROP_ENUMERATE as u32,
1721            );
1722            w2::JS_DefineFunction(
1723                cx,
1724                fsp_obj.handle(),
1725                c"copyFile".as_ptr(),
1726                Some(fs_promises_copy_file),
1727                2,
1728                JSPROP_ENUMERATE as u32,
1729            );
1730            w2::JS_DefineFunction(
1731                cx,
1732                fsp_obj.handle(),
1733                c"lstat".as_ptr(),
1734                Some(fs_promises_lstat),
1735                1,
1736                JSPROP_ENUMERATE as u32,
1737            );
1738            w2::JS_DefineFunction(
1739                cx,
1740                fsp_obj.handle(),
1741                c"appendFile".as_ptr(),
1742                Some(fs_promises_append_file),
1743                2,
1744                JSPROP_ENUMERATE as u32,
1745            );
1746            w2::JS_DefineFunction(
1747                cx,
1748                fsp_obj.handle(),
1749                c"chmod".as_ptr(),
1750                Some(fs_promises_chmod),
1751                2,
1752                JSPROP_ENUMERATE as u32,
1753            );
1754            w2::JS_DefineFunction(
1755                cx,
1756                fsp_obj.handle(),
1757                c"chown".as_ptr(),
1758                Some(fs_promises_chown),
1759                3,
1760                JSPROP_ENUMERATE as u32,
1761            );
1762            w2::JS_DefineFunction(
1763                cx,
1764                fsp_obj.handle(),
1765                c"access".as_ptr(),
1766                Some(fs_promises_access),
1767                1,
1768                JSPROP_ENUMERATE as u32,
1769            );
1770            w2::JS_DefineFunction(
1771                cx,
1772                fsp_obj.handle(),
1773                c"rm".as_ptr(),
1774                Some(fs_promises_rm),
1775                1,
1776                JSPROP_ENUMERATE as u32,
1777            );
1778            w2::JS_DefineFunction(
1779                cx,
1780                fsp_obj.handle(),
1781                c"rmdir".as_ptr(),
1782                Some(fs_promises_rmdir),
1783                1,
1784                JSPROP_ENUMERATE as u32,
1785            );
1786            w2::JS_DefineFunction(
1787                cx,
1788                fsp_obj.handle(),
1789                c"realpath".as_ptr(),
1790                Some(fs_promises_realpath),
1791                1,
1792                JSPROP_ENUMERATE as u32,
1793            );
1794            w2::JS_DefineFunction(
1795                cx,
1796                fsp_obj.handle(),
1797                c"readlink".as_ptr(),
1798                Some(fs_promises_readlink),
1799                1,
1800                JSPROP_ENUMERATE as u32,
1801            );
1802            w2::JS_DefineFunction(
1803                cx,
1804                fsp_obj.handle(),
1805                c"symlink".as_ptr(),
1806                Some(fs_promises_symlink),
1807                2,
1808                JSPROP_ENUMERATE as u32,
1809            );
1810            w2::JS_DefineFunction(
1811                cx,
1812                fsp_obj.handle(),
1813                c"link".as_ptr(),
1814                Some(fs_promises_link),
1815                2,
1816                JSPROP_ENUMERATE as u32,
1817            );
1818            w2::JS_DefineFunction(
1819                cx,
1820                fsp_obj.handle(),
1821                c"truncate".as_ptr(),
1822                Some(fs_promises_truncate),
1823                2,
1824                JSPROP_ENUMERATE as u32,
1825            );
1826            w2::JS_DefineFunction(
1827                cx,
1828                fsp_obj.handle(),
1829                c"utimes".as_ptr(),
1830                Some(fs_promises_utimes),
1831                3,
1832                JSPROP_ENUMERATE as u32,
1833            );
1834            w2::JS_DefineFunction(
1835                cx,
1836                fsp_obj.handle(),
1837                c"mkdtemp".as_ptr(),
1838                Some(fs_promises_mkdtemp),
1839                1,
1840                JSPROP_ENUMERATE as u32,
1841            );
1842            w2::JS_DefineFunction(
1843                cx,
1844                fsp_obj.handle(),
1845                c"open".as_ptr(),
1846                Some(fs_promises_open),
1847                2,
1848                JSPROP_ENUMERATE as u32,
1849            );
1850            w2::JS_DefineFunction(
1851                cx,
1852                fsp_obj.handle(),
1853                c"read".as_ptr(),
1854                Some(fs_promises_read),
1855                4,
1856                JSPROP_ENUMERATE as u32,
1857            );
1858            w2::JS_DefineFunction(
1859                cx,
1860                fsp_obj.handle(),
1861                c"write".as_ptr(),
1862                Some(fs_promises_write),
1863                4,
1864                JSPROP_ENUMERATE as u32,
1865            );
1866            w2::JS_DefineFunction(
1867                cx,
1868                fsp_obj.handle(),
1869                c"statfs".as_ptr(),
1870                Some(fs_promises_statfs),
1871                1,
1872                JSPROP_ENUMERATE as u32,
1873            );
1874
1875            // FileHandle class — wraps a file descriptor and provides
1876            // promise-based read/write/close/stat etc.
1877            w2::JS_DefineFunction(
1878                cx,
1879                fsp_obj.handle(),
1880                c"FileHandle".as_ptr(),
1881                Some(fs_promises_filehandle_ctor),
1882                1,
1883                JSPROP_ENUMERATE as u32,
1884            );
1885            cache_builtin(cx, "fs/promises", fsp_obj.get());
1886        }
1887    }
1888}
1889
1890// --- Argument helpers ---
1891
1892#[allow(unsafe_op_in_unsafe_fn)]
1893unsafe fn get_path_arg(
1894    cx: *mut JSContext,
1895    args: &CallArgs,
1896    index: u32,
1897) -> ::std::result::Result<::std::string::String, bool> {
1898    if args.argc_ <= index {
1899        JS_ReportErrorUTF8(cx, c"Missing path argument".as_ptr());
1900        return ::std::result::Result::Err(false);
1901    }
1902    let val = *args.get(index).ptr;
1903    if val.is_string() {
1904        let s = val.to_string();
1905        if !s.is_null() {
1906            return ::std::result::Result::Ok(crate::jsstr_to_rust_string(cx, s));
1907        }
1908    }
1909    JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
1910    ::std::result::Result::Err(false)
1911}
1912
1913#[allow(unsafe_op_in_unsafe_fn)]
1914unsafe fn get_encoding_opt(
1915    cx: *mut JSContext,
1916    args: &CallArgs,
1917    index: u32,
1918) -> ::std::option::Option<::std::string::String> {
1919    if args.argc_ <= index {
1920        return ::std::option::Option::None;
1921    }
1922    let val = *args.get(index).ptr;
1923    if val.is_string() {
1924        let s = val.to_string();
1925        if !s.is_null() {
1926            return ::std::option::Option::Some(crate::jsstr_to_rust_string(cx, s));
1927        }
1928    }
1929    if val.is_object() {
1930        let mut wrapped_cx_enc =
1931            mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
1932        let cx_ref_enc = &mut wrapped_cx_enc;
1933        rooted!(&in(cx_ref_enc) let obj = val.to_object());
1934        let mut enc_val = UndefinedValue();
1935        let enc_h = MutableHandle::<Value> {
1936            _phantom_0: ::std::marker::PhantomData,
1937            ptr: &mut enc_val,
1938        };
1939        JS_GetProperty(cx, obj.handle().into(), c"encoding".as_ptr(), enc_h);
1940        if enc_val.is_string() {
1941            let s = enc_val.to_string();
1942            if !s.is_null() {
1943                return ::std::option::Option::Some(crate::jsstr_to_rust_string(cx, s));
1944            }
1945        }
1946    }
1947    ::std::option::Option::None
1948}
1949
1950/// Valid-UTF-8 text → JSString through the UTF-8 decoder. JS_NewStringCopyZ
1951/// reads the buffer as Latin-1 (one byte = one char code), which mangles
1952/// multibyte UTF-8 (E4 BD A0 → "ä½ " instead of 你); the JSString must be
1953/// built via JS_NewStringCopyUTF8N — same discipline as the Buffer.toString
1954/// mojibake fix in globals.rs (@trace REQ-ENG-005).
1955#[allow(unsafe_op_in_unsafe_fn)]
1956unsafe fn js_string_from_utf8(cx: *mut JSContext, text: &str) -> *mut JSString {
1957    let chars = mozjs::conversions::Utf8Chars::from(text);
1958    mozjs_sys::jsapi::JS_NewStringCopyUTF8N(
1959        cx,
1960        &*chars as *const _ as *const mozjs_sys::jsapi::JS::UTF8Chars,
1961    )
1962}
1963
1964#[allow(unsafe_op_in_unsafe_fn)]
1965unsafe fn return_string_content(
1966    cx: *mut JSContext,
1967    args: &CallArgs,
1968    data: &[u8],
1969    encoding: ::std::option::Option<&str>,
1970) -> bool {
1971    match encoding {
1972        // @trace REQ-ENG-005 [entity:Buffer]
1973        // Node.js: readFileSync(path) with NO encoding returns a Buffer
1974        // (binary-safe). Only when an encoding is supplied does it return a
1975        // decoded String. Previously bao returned a utf8-lossy String for the
1976        // no-encoding case, breaking Buffer.isBuffer() checks downstream.
1977        None => {
1978            let buf_obj = crate::globals::create_buffer_object(cx, data);
1979            if buf_obj.is_null() {
1980                args.rval().set(UndefinedValue());
1981            } else {
1982                args.rval().set(mozjs::jsval::ObjectValue(buf_obj));
1983            }
1984        }
1985        Some("utf-8" | "utf8" | "text") => {
1986            // @trace REQ-ENG-005 — JS_NewStringCopyUTF8N (not CopyZ): multibyte
1987            // UTF-8 (中文/emoji) must decode through the UTF-8 decoder, not be
1988            // re-read as Latin-1 (mojibake). Same fix as buffer_to_string.
1989            let s = ::std::string::String::from_utf8_lossy(data);
1990            let js_str = js_string_from_utf8(cx, &s);
1991            if js_str.is_null() {
1992                args.rval().set(UndefinedValue());
1993            } else {
1994                args.rval().set(mozjs::jsval::StringValue(&*js_str));
1995            }
1996        }
1997        Some("hex") => {
1998            let hex: ::std::string::String = bun_core::fmt::bytes_to_hex_lower_string(data);
1999            let c_str = ZBox::from_bytes(hex.as_bytes());
2000            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
2001            if js_str.is_null() {
2002                args.rval().set(UndefinedValue());
2003            } else {
2004                args.rval().set(mozjs::jsval::StringValue(&*js_str));
2005            }
2006        }
2007        Some("base64") => {
2008            // @trace REQ-ENG-005 [algorithm:base64]
2009            // SIMD-accelerated base64 encode via workspace bun_base64 (replaces crates.io base64).
2010            let encoded_bytes = bun_base64::encode_alloc(data);
2011            let encoded = ::std::str::from_utf8(&encoded_bytes).unwrap_or("");
2012            let c_str = ZBox::from_bytes(encoded.as_bytes());
2013            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
2014            if js_str.is_null() {
2015                args.rval().set(UndefinedValue());
2016            } else {
2017                args.rval().set(mozjs::jsval::StringValue(&*js_str));
2018            }
2019        }
2020        Some("latin1" | "binary") => {
2021            // Node latin1: each byte maps to code point U+0000..U+00FF. The
2022            // `b as char` String holds those chars (UTF-8: 2 bytes for ≥0x80);
2023            // js_string_from_utf8 decodes them back to the single correct
2024            // char per byte. CopyZ would re-read those 2 bytes as Latin-1
2025            // (0xE5 → "Ã¥" mojibake).
2026            let s: ::std::string::String = data.iter().map(|&b| b as char).collect();
2027            let js_str = js_string_from_utf8(cx, &s);
2028            if js_str.is_null() {
2029                args.rval().set(UndefinedValue());
2030            } else {
2031                args.rval().set(mozjs::jsval::StringValue(&*js_str));
2032            }
2033        }
2034        Some(_) => {
2035            let s = ::std::string::String::from_utf8_lossy(data);
2036            let js_str = js_string_from_utf8(cx, &s);
2037            if js_str.is_null() {
2038                args.rval().set(UndefinedValue());
2039            } else {
2040                args.rval().set(mozjs::jsval::StringValue(&*js_str));
2041            }
2042        }
2043    }
2044    true
2045}
2046
2047#[allow(unsafe_op_in_unsafe_fn)]
2048unsafe fn throw_fs_error(cx: *mut JSContext, op: &str, path: &str, err: &::std::io::Error) -> bool {
2049    let code = if err.raw_os_error() == Some(libc::EINVAL) {
2050        // Raw errno has no ErrorKind mapping; surface it verbatim (Node parity,
2051        // e.g. mkdtemp('') must throw code EINVAL).
2052        "EINVAL"
2053    } else {
2054        match err.kind() {
2055            ::std::io::ErrorKind::NotFound => "ENOENT",
2056            ::std::io::ErrorKind::PermissionDenied => "EACCES",
2057            ::std::io::ErrorKind::AlreadyExists => "EEXIST",
2058            ::std::io::ErrorKind::IsADirectory => "EISDIR",
2059            ::std::io::ErrorKind::NotADirectory => "ENOTDIR",
2060            _ => "ERR",
2061        }
2062    };
2063    let msg = format!("{} '{}': {}", op, path, err);
2064    let c_msg = ZBox::from_bytes(msg.as_bytes());
2065    let code_str = JS_NewStringCopyZ(cx, ZBox::from_bytes(code.as_bytes()).as_ptr());
2066    if !code_str.is_null() {
2067        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2068        if JS_IsExceptionPending(cx) {
2069            rooted!(in(cx) let mut exn = UndefinedValue());
2070            JS_GetPendingException(cx, exn.handle_mut().into());
2071            let exn_val = exn.get();
2072            if !exn_val.is_undefined() && exn_val.is_object() {
2073                rooted!(in(cx) let exn_obj = exn_val.to_object());
2074                rooted!(in(cx) let code_val = StringValue(&*code_str));
2075                JS_DefineProperty(
2076                    cx,
2077                    exn_obj.handle().into(),
2078                    c"code".as_ptr(),
2079                    code_val.handle().into(),
2080                    JSPROP_ENUMERATE as u32,
2081                );
2082                let path_val = ZBox::from_bytes(path.as_bytes());
2083                let path_str = JS_NewStringCopyZ(cx, path_val.as_ptr());
2084                if !path_str.is_null() {
2085                    rooted!(in(cx) let path_v = StringValue(&*path_str));
2086                    JS_DefineProperty(
2087                        cx,
2088                        exn_obj.handle().into(),
2089                        c"path".as_ptr(),
2090                        path_v.handle().into(),
2091                        JSPROP_ENUMERATE as u32,
2092                    );
2093                }
2094                JS_SetPendingException(
2095                    cx,
2096                    exn.handle().into(),
2097                    ExceptionStackBehavior::DoNotCapture,
2098                );
2099            }
2100        }
2101    } else {
2102        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2103    }
2104    false
2105}
2106
2107// --- Sync file operations ---
2108
2109#[allow(unsafe_op_in_unsafe_fn)]
2110unsafe extern "C" fn fs_read_file_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2111    let args = CallArgs::from_vp(vp, argc);
2112    let path = match get_path_arg(cx, &args, 0) {
2113        ::std::result::Result::Ok(p) => p,
2114        ::std::result::Result::Err(b) => return b,
2115    };
2116    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_read(&path) {
2117        let c_msg = ZBox::from_bytes(e.as_bytes());
2118        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2119        return false;
2120    }
2121    let encoding = get_encoding_opt(cx, &args, 1);
2122    match bun_fs::read(&path) {
2123        ::std::result::Result::Ok(data) => {
2124            return_string_content(cx, &args, &data, encoding.as_deref())
2125        }
2126        ::std::result::Result::Err(e) => throw_fs_error(cx, "readFileSync", &path, &e),
2127    }
2128}
2129
2130#[allow(unsafe_op_in_unsafe_fn)]
2131unsafe extern "C" fn fs_write_file_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2132    let args = CallArgs::from_vp(vp, argc);
2133    let path = match get_path_arg(cx, &args, 0) {
2134        ::std::result::Result::Ok(p) => p,
2135        ::std::result::Result::Err(b) => return b,
2136    };
2137    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
2138        let c_msg = ZBox::from_bytes(e.as_bytes());
2139        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2140        return false;
2141    }
2142    let data_val = if argc > 1 {
2143        *args.get(1).ptr
2144    } else {
2145        UndefinedValue()
2146    };
2147
2148    let result = if data_val.is_string() {
2149        let s = data_val.to_string();
2150        if !s.is_null() {
2151            let rust_str = crate::jsstr_to_rust_string(cx, s);
2152            bun_fs::write(&path, rust_str.as_bytes())
2153        } else {
2154            bun_fs::write(&path, &[] as &[u8])
2155        }
2156    } else if data_val.is_object() {
2157        let bytes = crate::node_crypto::extract_buffer_bytes(cx, data_val);
2158        bun_fs::write(&path, &bytes)
2159    } else {
2160        bun_fs::write(&path, &[] as &[u8])
2161    };
2162
2163    match result {
2164        ::std::result::Result::Ok(()) => {
2165            args.rval().set(UndefinedValue());
2166            true
2167        }
2168        ::std::result::Result::Err(e) => throw_fs_error(cx, "writeFileSync", &path, &e),
2169    }
2170}
2171
2172// ── createWriteStream binary-safe flush ─────────────────────────────────────
2173// The FS_STREAM_JS polyfill buffers string and Buffer chunks RAW; at end()
2174// it hands the chunk array here instead of String()-coercing every chunk.
2175// Byte semantics: string chunks → their UTF-8 bytes (byte-identical to the
2176// old join('') + writeFileSync(string) path — UTF-8 concatenation is
2177// stable); Buffer/TypedArray/DataView/ArrayBuffer chunks → raw bytes via
2178// collect_byte_view. The old polyfill ran Buffer chunks through
2179// String(chunk), which is binary-unsafe (bytes ≥ 0x80 re-encoded through
2180// lossy UTF-8 → corrupted file). Unrecognized chunk types throw instead of
2181// silently corrupting the stream (Node throws ERR_INVALID_ARG_TYPE).
2182// @trace REQ-ENG-005 [entity:Buffer] [api:createWriteStream]
2183#[allow(unsafe_op_in_unsafe_fn)]
2184unsafe extern "C" fn fs_write_stream_flush(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2185    let args = CallArgs::from_vp(vp, argc);
2186    let path = match get_path_arg(cx, &args, 0) {
2187        ::std::result::Result::Ok(p) => p,
2188        ::std::result::Result::Err(b) => return b,
2189    };
2190    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
2191        let c_msg = ZBox::from_bytes(e.as_bytes());
2192        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2193        return false;
2194    }
2195
2196    let mut total: Vec<u8> = Vec::new();
2197    let chunks_val = if argc > 1 {
2198        *args.get(1).ptr
2199    } else {
2200        UndefinedValue()
2201    };
2202    if chunks_val.is_object() {
2203        let mut wrapped_cx =
2204            mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
2205        let cx_ref = &mut wrapped_cx;
2206        rooted!(&in(cx_ref) let arr_root = chunks_val.to_object());
2207        let mut len_val = UndefinedValue();
2208        JS_GetProperty(
2209            cx,
2210            arr_root.handle().into(),
2211            c"length".as_ptr(),
2212            MutableHandle::<Value> {
2213                _phantom_0: ::std::marker::PhantomData,
2214                ptr: &mut len_val,
2215            },
2216        );
2217        let len = if len_val.is_int32() && len_val.to_int32() > 0 {
2218            len_val.to_int32() as u32
2219        } else {
2220            0
2221        };
2222        for i in 0..len {
2223            let mut elem = UndefinedValue();
2224            JS_GetElement(
2225                cx,
2226                arr_root.handle().into(),
2227                i,
2228                MutableHandle::<Value> {
2229                    _phantom_0: ::std::marker::PhantomData,
2230                    ptr: &mut elem,
2231                },
2232            );
2233            rooted!(&in(cx_ref) let el_root = elem);
2234            let v = el_root.get();
2235            if v.is_string() {
2236                let s = v.to_string();
2237                if !s.is_null() {
2238                    total.extend_from_slice(crate::jsstr_to_rust_string(cx, s).as_bytes());
2239                }
2240            } else if v.is_object() {
2241                match crate::node_buffer::collect_byte_view(cx, v) {
2242                    ::std::option::Option::Some(bytes) => total.extend_from_slice(&bytes),
2243                    // fail-closed: an unrecognized object would previously be
2244                    // written as "[object Object]" garbage — throw instead.
2245                    ::std::option::Option::None => {
2246                        JS_ReportErrorUTF8(
2247                            cx,
2248                            c"The \"chunk\" argument must be a string or a binary view".as_ptr(),
2249                        );
2250                        return false;
2251                    }
2252                }
2253            } else {
2254                JS_ReportErrorUTF8(
2255                    cx,
2256                    c"The \"chunk\" argument must be a string or a binary view".as_ptr(),
2257                );
2258                return false;
2259            }
2260        }
2261    }
2262
2263    match bun_fs::write(&path, &total) {
2264        ::std::result::Result::Ok(()) => {
2265            args.rval().set(UndefinedValue());
2266            true
2267        }
2268        ::std::result::Result::Err(e) => throw_fs_error(cx, "createWriteStream", &path, &e),
2269    }
2270}
2271
2272#[allow(unsafe_op_in_unsafe_fn)]
2273unsafe extern "C" fn fs_append_file_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2274    let args = CallArgs::from_vp(vp, argc);
2275    let path = match get_path_arg(cx, &args, 0) {
2276        ::std::result::Result::Ok(p) => p,
2277        ::std::result::Result::Err(b) => return b,
2278    };
2279    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
2280        let c_msg = ZBox::from_bytes(e.as_bytes());
2281        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2282        return false;
2283    }
2284    let data_val = if argc > 1 {
2285        *args.get(1).ptr
2286    } else {
2287        UndefinedValue()
2288    };
2289    let data = if data_val.is_string() {
2290        let s = data_val.to_string();
2291        if !s.is_null() {
2292            crate::jsstr_to_rust_string(cx, s).into_bytes()
2293        } else {
2294            Vec::new()
2295        }
2296    } else if data_val.is_object() {
2297        crate::node_crypto::extract_buffer_bytes(cx, data_val)
2298    } else {
2299        Vec::new()
2300    };
2301
2302    match bun_fs::OpenOptions::new()
2303        .create(true)
2304        .append(true)
2305        .open(&path)
2306    {
2307        ::std::result::Result::Ok(file) => match file.write_all(&data) {
2308            ::std::result::Result::Ok(()) => {
2309                args.rval().set(UndefinedValue());
2310                true
2311            }
2312            ::std::result::Result::Err(e) => throw_fs_error(
2313                cx,
2314                "appendFileSync",
2315                &path,
2316                &::std::io::Error::from_raw_os_error(e.errno as i32),
2317            ),
2318        },
2319        ::std::result::Result::Err(e) => throw_fs_error(cx, "appendFileSync", &path, &e),
2320    }
2321}
2322
2323#[allow(unsafe_op_in_unsafe_fn)]
2324unsafe extern "C" fn fs_exists_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2325    let args = CallArgs::from_vp(vp, argc);
2326    let path = match get_path_arg(cx, &args, 0) {
2327        ::std::result::Result::Ok(p) => p,
2328        ::std::result::Result::Err(b) => return b,
2329    };
2330    args.rval()
2331        .set(mozjs::jsval::BooleanValue(Path::new(&path).exists()));
2332    true
2333}
2334
2335#[allow(unsafe_op_in_unsafe_fn)]
2336unsafe extern "C" fn fs_mkdir_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2337    let args = CallArgs::from_vp(vp, argc);
2338    let path = match get_path_arg(cx, &args, 0) {
2339        ::std::result::Result::Ok(p) => p,
2340        ::std::result::Result::Err(b) => return b,
2341    };
2342    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
2343        let c_msg = ZBox::from_bytes(e.as_bytes());
2344        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2345        return false;
2346    }
2347    let recursive = get_bool_option(cx, &args, 1, "recursive");
2348    let result = if recursive {
2349        fs::create_dir_all(&path)
2350    } else {
2351        fs::create_dir(&path)
2352    };
2353    match result {
2354        ::std::result::Result::Ok(()) => {
2355            args.rval().set(UndefinedValue());
2356            true
2357        }
2358        ::std::result::Result::Err(e) => throw_fs_error(cx, "mkdirSync", &path, &e),
2359    }
2360}
2361
2362#[allow(unsafe_op_in_unsafe_fn)]
2363unsafe extern "C" fn fs_readdir_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2364    let args = CallArgs::from_vp(vp, argc);
2365    let path = match get_path_arg(cx, &args, 0) {
2366        ::std::result::Result::Ok(p) => p,
2367        ::std::result::Result::Err(b) => return b,
2368    };
2369    let with_file_types = get_bool_option(cx, &args, 1, "withFileTypes");
2370
2371    match fs::read_dir(&path) {
2372        ::std::result::Result::Ok(entries) => {
2373            let mut names: Vec<::std::string::String> = Vec::new();
2374            let mut is_dirs: Vec<bool> = Vec::new();
2375            for entry in entries.flatten() {
2376                names.push(entry.file_name().to_string_lossy().into_owned());
2377                is_dirs.push(entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false));
2378            }
2379            // SAFETY: construct wrapped cx to use rooted! and w2:: functions
2380            let mut wrapped_cx = unsafe {
2381                mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx))
2382            };
2383            let cx_ref = &mut wrapped_cx;
2384            rooted!(&in(cx_ref) let arr = unsafe { w2::NewArrayObject1(cx_ref, names.len()) });
2385            if arr.get().is_null() {
2386                args.rval().set(UndefinedValue());
2387                return true;
2388            }
2389            for (i, name) in names.iter().enumerate() {
2390                if with_file_types {
2391                    let dirent = create_dirent(cx, name, is_dirs[i]);
2392                    if !dirent.is_null() {
2393                        rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(dirent));
2394                        unsafe {
2395                            JS_DefineElement(
2396                                cx,
2397                                arr.handle().into(),
2398                                i as u32,
2399                                val.handle().into(),
2400                                JSPROP_ENUMERATE as u32,
2401                            );
2402                        }
2403                    }
2404                } else {
2405                    let c_name = ZBox::from_bytes(name.as_bytes());
2406                    let js_str = unsafe { JS_NewStringCopyZ(cx, c_name.as_ptr()) };
2407                    if !js_str.is_null() {
2408                        rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
2409                        unsafe {
2410                            JS_DefineElement(
2411                                cx,
2412                                arr.handle().into(),
2413                                i as u32,
2414                                val.handle().into(),
2415                                JSPROP_ENUMERATE as u32,
2416                            );
2417                        }
2418                    }
2419                }
2420            }
2421            args.rval().set(mozjs::jsval::ObjectValue(arr.get()));
2422            true
2423        }
2424        ::std::result::Result::Err(e) => throw_fs_error(cx, "readdirSync", &path, &e),
2425    }
2426}
2427
2428#[allow(unsafe_op_in_unsafe_fn)]
2429unsafe extern "C" fn fs_stat_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2430    let args = CallArgs::from_vp(vp, argc);
2431    let path = match get_path_arg(cx, &args, 0) {
2432        ::std::result::Result::Ok(p) => p,
2433        ::std::result::Result::Err(b) => return b,
2434    };
2435    match bun_fs::metadata(&path) {
2436        ::std::result::Result::Ok(meta) => {
2437            let stats = create_stats_object(cx, &meta);
2438            args.rval().set(mozjs::jsval::ObjectValue(stats));
2439            true
2440        }
2441        ::std::result::Result::Err(e) => throw_fs_error(cx, "statSync", &path, &e),
2442    }
2443}
2444
2445#[allow(unsafe_op_in_unsafe_fn)]
2446unsafe extern "C" fn fs_lstat_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2447    let args = CallArgs::from_vp(vp, argc);
2448    let path = match get_path_arg(cx, &args, 0) {
2449        ::std::result::Result::Ok(p) => p,
2450        ::std::result::Result::Err(b) => return b,
2451    };
2452    match fs::symlink_metadata(&path) {
2453        ::std::result::Result::Ok(meta) => {
2454            let posix = metadata_to_posix_stat(&meta);
2455            let stats = create_stats_object(cx, &posix);
2456            args.rval().set(mozjs::jsval::ObjectValue(stats));
2457            true
2458        }
2459        ::std::result::Result::Err(e) => throw_fs_error(cx, "lstatSync", &path, &e),
2460    }
2461}
2462
2463#[allow(unsafe_op_in_unsafe_fn)]
2464unsafe extern "C" fn fs_unlink_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2465    let args = CallArgs::from_vp(vp, argc);
2466    let path = match get_path_arg(cx, &args, 0) {
2467        ::std::result::Result::Ok(p) => p,
2468        ::std::result::Result::Err(b) => return b,
2469    };
2470    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
2471        let c_msg = ZBox::from_bytes(e.as_bytes());
2472        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2473        return false;
2474    }
2475    match fs::remove_file(&path) {
2476        ::std::result::Result::Ok(()) => {
2477            args.rval().set(UndefinedValue());
2478            true
2479        }
2480        ::std::result::Result::Err(e) => throw_fs_error(cx, "unlinkSync", &path, &e),
2481    }
2482}
2483
2484#[allow(unsafe_op_in_unsafe_fn)]
2485unsafe extern "C" fn fs_rmdir_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2486    let args = CallArgs::from_vp(vp, argc);
2487    let path = match get_path_arg(cx, &args, 0) {
2488        ::std::result::Result::Ok(p) => p,
2489        ::std::result::Result::Err(b) => return b,
2490    };
2491    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
2492        let c_msg = ZBox::from_bytes(e.as_bytes());
2493        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2494        return false;
2495    }
2496    match fs::remove_dir(&path) {
2497        ::std::result::Result::Ok(()) => {
2498            args.rval().set(UndefinedValue());
2499            true
2500        }
2501        ::std::result::Result::Err(e) => throw_fs_error(cx, "rmdirSync", &path, &e),
2502    }
2503}
2504
2505#[allow(unsafe_op_in_unsafe_fn)]
2506unsafe extern "C" fn fs_rm_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2507    let args = CallArgs::from_vp(vp, argc);
2508    let path = match get_path_arg(cx, &args, 0) {
2509        ::std::result::Result::Ok(p) => p,
2510        ::std::result::Result::Err(b) => return b,
2511    };
2512    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
2513        let c_msg = ZBox::from_bytes(e.as_bytes());
2514        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2515        return false;
2516    }
2517    let recursive = get_bool_option(cx, &args, 1, "recursive");
2518    let result = if recursive {
2519        fs::remove_dir_all(&path)
2520    } else {
2521        fs::remove_file(&path)
2522    };
2523    match result {
2524        ::std::result::Result::Ok(()) => {
2525            args.rval().set(UndefinedValue());
2526            true
2527        }
2528        ::std::result::Result::Err(e) => throw_fs_error(cx, "rmSync", &path, &e),
2529    }
2530}
2531
2532#[allow(unsafe_op_in_unsafe_fn)]
2533unsafe extern "C" fn fs_rename_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2534    let args = CallArgs::from_vp(vp, argc);
2535    let from = match get_path_arg(cx, &args, 0) {
2536        ::std::result::Result::Ok(p) => p,
2537        ::std::result::Result::Err(b) => return b,
2538    };
2539    let to = match get_path_arg(cx, &args, 1) {
2540        ::std::result::Result::Ok(p) => p,
2541        ::std::result::Result::Err(b) => return b,
2542    };
2543    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_read(&from) {
2544        let c_msg = ZBox::from_bytes(e.as_bytes());
2545        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2546        return false;
2547    }
2548    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&to) {
2549        let c_msg = ZBox::from_bytes(e.as_bytes());
2550        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2551        return false;
2552    }
2553    match fs::rename(&from, &to) {
2554        ::std::result::Result::Ok(()) => {
2555            args.rval().set(UndefinedValue());
2556            true
2557        }
2558        ::std::result::Result::Err(e) => throw_fs_error(cx, "renameSync", &from, &e),
2559    }
2560}
2561
2562#[allow(unsafe_op_in_unsafe_fn)]
2563unsafe extern "C" fn fs_copy_file_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2564    let args = CallArgs::from_vp(vp, argc);
2565    let from = match get_path_arg(cx, &args, 0) {
2566        ::std::result::Result::Ok(p) => p,
2567        ::std::result::Result::Err(b) => return b,
2568    };
2569    let to = match get_path_arg(cx, &args, 1) {
2570        ::std::result::Result::Ok(p) => p,
2571        ::std::result::Result::Err(b) => return b,
2572    };
2573    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_read(&from) {
2574        let c_msg = ZBox::from_bytes(e.as_bytes());
2575        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2576        return false;
2577    }
2578    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&to) {
2579        let c_msg = ZBox::from_bytes(e.as_bytes());
2580        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2581        return false;
2582    }
2583    match fs::copy(&from, &to) {
2584        ::std::result::Result::Ok(_) => {
2585            args.rval().set(UndefinedValue());
2586            true
2587        }
2588        ::std::result::Result::Err(e) => throw_fs_error(cx, "copyFileSync", &from, &e),
2589    }
2590}
2591
2592#[allow(unsafe_op_in_unsafe_fn)]
2593unsafe extern "C" fn fs_chmod_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2594    let args = CallArgs::from_vp(vp, argc);
2595    let path = match get_path_arg(cx, &args, 0) {
2596        ::std::result::Result::Ok(p) => p,
2597        ::std::result::Result::Err(b) => return b,
2598    };
2599    let mode_val = if argc > 1 {
2600        *args.get(1).ptr
2601    } else {
2602        UndefinedValue()
2603    };
2604    let mode = if mode_val.is_int32() {
2605        mode_val.to_int32() as u32
2606    } else if mode_val.is_double() {
2607        mode_val.to_double() as u32
2608    } else {
2609        0o644
2610    };
2611    #[cfg(unix)]
2612    let result = {
2613        use ::std::os::unix::fs::PermissionsExt;
2614        fs::set_permissions(&path, fs::Permissions::from_mode(mode))
2615    };
2616    #[cfg(not(unix))]
2617    let result = fs::set_permissions(&path, fs::Permissions::new());
2618    match result {
2619        ::std::result::Result::Ok(()) => {
2620            args.rval().set(UndefinedValue());
2621            true
2622        }
2623        ::std::result::Result::Err(e) => throw_fs_error(cx, "chmodSync", &path, &e),
2624    }
2625}
2626
2627#[allow(unsafe_op_in_unsafe_fn)]
2628unsafe extern "C" fn fs_realpath_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2629    let args = CallArgs::from_vp(vp, argc);
2630    let path = match get_path_arg(cx, &args, 0) {
2631        ::std::result::Result::Ok(p) => p,
2632        ::std::result::Result::Err(b) => return b,
2633    };
2634    match fs::canonicalize(&path) {
2635        ::std::result::Result::Ok(resolved) => {
2636            let s = resolved.to_string_lossy();
2637            let c_str = ZBox::from_bytes(s.as_bytes());
2638            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
2639            if js_str.is_null() {
2640                args.rval().set(UndefinedValue());
2641            } else {
2642                args.rval().set(mozjs::jsval::StringValue(&*js_str));
2643            }
2644            true
2645        }
2646        ::std::result::Result::Err(e) => throw_fs_error(cx, "realpathSync", &path, &e),
2647    }
2648}
2649
2650#[allow(unsafe_op_in_unsafe_fn)]
2651unsafe extern "C" fn fs_readlink_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2652    let args = CallArgs::from_vp(vp, argc);
2653    let path = match get_path_arg(cx, &args, 0) {
2654        ::std::result::Result::Ok(p) => p,
2655        ::std::result::Result::Err(b) => return b,
2656    };
2657    match fs::read_link(&path) {
2658        ::std::result::Result::Ok(target) => {
2659            let s = target.to_string_lossy();
2660            let c_str = ZBox::from_bytes(s.as_bytes());
2661            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
2662            if js_str.is_null() {
2663                args.rval().set(UndefinedValue());
2664            } else {
2665                args.rval().set(mozjs::jsval::StringValue(&*js_str));
2666            }
2667            true
2668        }
2669        ::std::result::Result::Err(e) => throw_fs_error(cx, "readlinkSync", &path, &e),
2670    }
2671}
2672
2673#[allow(unsafe_op_in_unsafe_fn)]
2674unsafe extern "C" fn fs_symlink_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2675    let args = CallArgs::from_vp(vp, argc);
2676    let target = match get_path_arg(cx, &args, 0) {
2677        ::std::result::Result::Ok(p) => p,
2678        ::std::result::Result::Err(b) => return b,
2679    };
2680    let path = match get_path_arg(cx, &args, 1) {
2681        ::std::result::Result::Ok(p) => p,
2682        ::std::result::Result::Err(b) => return b,
2683    };
2684    #[cfg(unix)]
2685    let result = ::std::os::unix::fs::symlink(&target, &path);
2686    #[cfg(not(unix))]
2687    let result = fs::hard_link(&target, &path);
2688    match result {
2689        ::std::result::Result::Ok(()) => {
2690            args.rval().set(UndefinedValue());
2691            true
2692        }
2693        ::std::result::Result::Err(e) => throw_fs_error(cx, "symlinkSync", &target, &e),
2694    }
2695}
2696
2697#[allow(unsafe_op_in_unsafe_fn)]
2698unsafe extern "C" fn fs_link_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2699    let args = CallArgs::from_vp(vp, argc);
2700    let from = match get_path_arg(cx, &args, 0) {
2701        ::std::result::Result::Ok(p) => p,
2702        ::std::result::Result::Err(b) => return b,
2703    };
2704    let to = match get_path_arg(cx, &args, 1) {
2705        ::std::result::Result::Ok(p) => p,
2706        ::std::result::Result::Err(b) => return b,
2707    };
2708    match fs::hard_link(&from, &to) {
2709        ::std::result::Result::Ok(()) => {
2710            args.rval().set(UndefinedValue());
2711            true
2712        }
2713        ::std::result::Result::Err(e) => throw_fs_error(cx, "linkSync", &from, &e),
2714    }
2715}
2716
2717// Recursive directory copy. Mirrors Node.js fs.cpSync(src, dst[, opts])
2718// behaviour for the common recursive case.
2719// Supports errorOnExist option: throw if destination exists and is not a directory.
2720#[allow(unsafe_op_in_unsafe_fn)]
2721fn cp_recursive(src: &Path, dst: &Path, error_on_exist: bool) -> ::std::io::Result<()> {
2722    if fs::metadata(src)?.is_dir() {
2723        if error_on_exist && dst.exists() && !dst.is_dir() {
2724            return Err(::std::io::Error::new(
2725                ::std::io::ErrorKind::AlreadyExists,
2726                "destination already exists",
2727            ));
2728        }
2729        fs::create_dir_all(dst)?;
2730        for entry in fs::read_dir(src)? {
2731            let entry = entry?;
2732            let from = entry.path();
2733            let to = dst.join(entry.file_name());
2734            cp_recursive(&from, &to, error_on_exist)?;
2735        }
2736    } else {
2737        fs::copy(src, dst)?;
2738    }
2739    ::std::result::Result::Ok(())
2740}
2741
2742#[allow(unsafe_op_in_unsafe_fn)]
2743unsafe extern "C" fn fs_cp_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2744    let args = CallArgs::from_vp(vp, argc);
2745    let from = match get_path_arg(cx, &args, 0) {
2746        ::std::result::Result::Ok(p) => p,
2747        ::std::result::Result::Err(b) => return b,
2748    };
2749    let to = match get_path_arg(cx, &args, 1) {
2750        ::std::result::Result::Ok(p) => p,
2751        ::std::result::Result::Err(b) => return b,
2752    };
2753    let error_on_exist = get_bool_option(cx, &args, 2, "errorOnExist");
2754    match cp_recursive(Path::new(&from), Path::new(&to), error_on_exist) {
2755        ::std::result::Result::Ok(()) => {
2756            args.rval().set(UndefinedValue());
2757            true
2758        }
2759        ::std::result::Result::Err(e) => {
2760            let msg = format!("cpSync: {}", e);
2761            let c_msg = ZBox::from_bytes(msg.as_bytes());
2762            JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2763            false
2764        }
2765    }
2766}
2767
2768#[allow(unsafe_op_in_unsafe_fn)]
2769unsafe extern "C" fn fs_cp(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
2770    // Async variant: invoke callback (if provided) after the recursive copy.
2771    let args = CallArgs::from_vp(vp, argc);
2772    let from = match get_path_arg(cx, &args, 0) {
2773        ::std::result::Result::Ok(p) => p,
2774        ::std::result::Result::Err(b) => return b,
2775    };
2776    let to = match get_path_arg(cx, &args, 1) {
2777        ::std::result::Result::Ok(p) => p,
2778        ::std::result::Result::Err(b) => return b,
2779    };
2780    let error_on_exist = get_bool_option(cx, &args, 2, "errorOnExist");
2781    let res = cp_recursive(Path::new(&from), Path::new(&to), error_on_exist);
2782    if let ::std::result::Result::Err(e) = res {
2783        let msg = format!("cp: {}", e);
2784        let c_msg = ZBox::from_bytes(msg.as_bytes());
2785        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
2786        return false;
2787    }
2788    args.rval().set(UndefinedValue());
2789    true
2790}
2791
2792#[allow(unsafe_op_in_unsafe_fn)]
2793unsafe extern "C" fn fs_watch(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
2794    let args = CallArgs::from_vp(vp, _argc);
2795    let path = match get_path_arg(cx, &args, 0) {
2796        ::std::result::Result::Ok(p) => p,
2797        ::std::result::Result::Err(b) => return b,
2798    };
2799
2800    // options may be an object (position 1) or an encoding string; the
2801    // listener then sits at position 1 or 2 (Node: watch(filename[, options][, listener])).
2802    let mut persistent = true;
2803    let mut listener_val = UndefinedValue();
2804    if _argc > 1 {
2805        let opt_val = *args.get(1).ptr;
2806        // Node overload: watch(filename[, options][, listener]) — a FUNCTION
2807        // at position 1 is the listener, not the options object.
2808        let opt_is_fn = opt_val.is_object()
2809            && unsafe { mozjs_sys::jsapi::js::IsFunctionObject(opt_val.to_object()) };
2810        if opt_val.is_object() && !opt_is_fn {
2811            let mut wrapped_opt =
2812                mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
2813            let cx_opt = &mut wrapped_opt;
2814            rooted!(&in(cx_opt) let opt_obj = opt_val.to_object());
2815            // recursive: Node on Linux without recursive inotify support throws
2816            // ERR_FEATURE_UNAVAILABLE_ON_PLATFORM — explicit, never a silent fake.
2817            let mut recursive_v = UndefinedValue();
2818            JS_GetProperty(
2819                cx,
2820                opt_obj.handle().into(),
2821                c"recursive".as_ptr(),
2822                MutableHandle::<Value> {
2823                    _phantom_0: ::std::marker::PhantomData,
2824                    ptr: &mut recursive_v,
2825                },
2826            );
2827            if recursive_v.is_boolean() && recursive_v.to_boolean() {
2828                JS_ReportErrorUTF8(
2829                    cx,
2830                    c"The value of \"options.recursive\" is not supported on this platform: watch recursive is unavailable (inotify backend covers the watched path only)".as_ptr(),
2831                );
2832                return false;
2833            }
2834            let mut persistent_v = UndefinedValue();
2835            JS_GetProperty(
2836                cx,
2837                opt_obj.handle().into(),
2838                c"persistent".as_ptr(),
2839                MutableHandle::<Value> {
2840                    _phantom_0: ::std::marker::PhantomData,
2841                    ptr: &mut persistent_v,
2842                },
2843            );
2844            if persistent_v.is_boolean() {
2845                persistent = persistent_v.to_boolean();
2846            }
2847            if _argc > 2 {
2848                listener_val = *args.get(2).ptr;
2849            }
2850        } else {
2851            listener_val = opt_val;
2852        }
2853    }
2854
2855    let is_dir = Path::new(&path).metadata().map(|m| m.is_dir()).unwrap_or(false);
2856
2857    // Register a real inotify watch (kernel events — not polling).
2858    let id = fsw_next_id();
2859    let wd = fsw_add_inotify_watch(&path);
2860    match wd {
2861        ::std::result::Result::Ok(wd) => {
2862            fsw_register_watch(id, wd, path.clone(), is_dir, persistent);
2863            let watcher = fsw_make_watcher_object(cx, id, is_dir);
2864            if watcher.is_null() {
2865                args.rval().set(UndefinedValue());
2866                return true;
2867            }
2868            let mut wrapped_cx =
2869                mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
2870            let cx_ref = &mut wrapped_cx;
2871            rooted!(&in(cx_ref) let watcher_r = watcher);
2872            if listener_val.is_object() {
2873                // Attach the listener via the object's own `on('change', fn)`.
2874                let c_ev = ZBox::from_bytes("change".as_bytes());
2875                let c_name = ZBox::from_bytes("on".as_bytes());
2876                rooted!(&in(cx_ref) let lv = listener_val);
2877                let ev_str = JS_NewStringCopyZ(cx, c_ev.as_ptr());
2878                if !ev_str.is_null() {
2879                    let argv = [mozjs::jsval::StringValue(&*ev_str), lv.get()];
2880                    let call_args = HandleValueArray {
2881                        length_: argv.len(),
2882                        elements_: argv.as_ptr(),
2883                    };
2884                    let mut rval = UndefinedValue();
2885                    JS_CallFunctionName(
2886                        cx,
2887                        watcher_r.handle().into(),
2888                        c_name.as_ptr(),
2889                        &call_args,
2890                        MutableHandle::<Value> {
2891                            _phantom_0: ::std::marker::PhantomData,
2892                            ptr: &mut rval,
2893                        },
2894                    );
2895                }
2896            }
2897            args.rval().set(mozjs::jsval::ObjectValue(watcher_r.get()));
2898            true
2899        }
2900        ::std::result::Result::Err(e) => {
2901            let _ = fsw_maybe_shutdown_if_idle();
2902            throw_fs_error(cx, "watch", &path, &e)
2903        }
2904    }
2905}
2906
2907#[allow(unsafe_op_in_unsafe_fn)]
2908unsafe extern "C" fn fs_watch_file(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
2909    let args = CallArgs::from_vp(vp, _argc);
2910    let path = match get_path_arg(cx, &args, 0) {
2911        ::std::result::Result::Ok(p) => p,
2912        ::std::result::Result::Err(b) => return b,
2913    };
2914
2915    // watchFile(filename[, options], listener): options at 1, listener at 1/2.
2916    let mut interval_ms: u64 = 5007; // Node default
2917    let mut persistent = true;
2918    let mut listener_val = UndefinedValue();
2919    if _argc > 1 {
2920        let opt_val = *args.get(1).ptr;
2921        // Node overload: watch(filename[, options][, listener]) — a FUNCTION
2922        // at position 1 is the listener, not the options object.
2923        let opt_is_fn = opt_val.is_object()
2924            && unsafe { mozjs_sys::jsapi::js::IsFunctionObject(opt_val.to_object()) };
2925        if opt_val.is_object() && !opt_is_fn {
2926            let mut wrapped_opt =
2927                mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
2928            let cx_opt = &mut wrapped_opt;
2929            rooted!(&in(cx_opt) let opt_obj = opt_val.to_object());
2930            let mut interval_v = UndefinedValue();
2931            JS_GetProperty(
2932                cx,
2933                opt_obj.handle().into(),
2934                c"interval".as_ptr(),
2935                MutableHandle::<Value> {
2936                    _phantom_0: ::std::marker::PhantomData,
2937                    ptr: &mut interval_v,
2938                },
2939            );
2940            if interval_v.is_number() {
2941                // JSVals carry ints as int32-or-double tags; to_double asserts
2942                // the double tag — convert through the numeric union instead.
2943                interval_ms = if interval_v.is_int32() {
2944                    interval_v.to_int32().max(1) as u64
2945                } else {
2946                    interval_v.to_double().max(1.0) as u64
2947                };
2948            }
2949            let mut persistent_v = UndefinedValue();
2950            JS_GetProperty(
2951                cx,
2952                opt_obj.handle().into(),
2953                c"persistent".as_ptr(),
2954                MutableHandle::<Value> {
2955                    _phantom_0: ::std::marker::PhantomData,
2956                    ptr: &mut persistent_v,
2957                },
2958            );
2959            if persistent_v.is_boolean() {
2960                persistent = persistent_v.to_boolean();
2961            }
2962            if _argc > 2 {
2963                listener_val = *args.get(2).ptr;
2964            }
2965        } else {
2966            listener_val = opt_val;
2967        }
2968    }
2969
2970    let id = fsw_next_id();
2971    // Baseline stat on the JS thread; missing file = zeroed stat (fires once
2972    // when the file appears — Node parity).
2973    let baseline = fsw_stat_path(&path).unwrap_or_else(fsw_zero_stat);
2974    fsw_register_poll(id, path.clone(), interval_ms, baseline, persistent);
2975
2976    let watcher = fsw_make_watcher_object(cx, id, false);
2977    if watcher.is_null() {
2978        args.rval().set(UndefinedValue());
2979        return true;
2980    }
2981    let mut wrapped_cx =
2982        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
2983    let cx_ref = &mut wrapped_cx;
2984    rooted!(&in(cx_ref) let watcher_r = watcher);
2985    if listener_val.is_object() {
2986        let c_ev = ZBox::from_bytes("change".as_bytes());
2987        let c_name = ZBox::from_bytes("on".as_bytes());
2988        rooted!(&in(cx_ref) let lv = listener_val);
2989        let ev_str = JS_NewStringCopyZ(cx, c_ev.as_ptr());
2990        if !ev_str.is_null() {
2991            let argv = [mozjs::jsval::StringValue(&*ev_str), lv.get()];
2992            let call_args = HandleValueArray {
2993                length_: argv.len(),
2994                elements_: argv.as_ptr(),
2995            };
2996            let mut rval = UndefinedValue();
2997            JS_CallFunctionName(
2998                cx,
2999                watcher_r.handle().into(),
3000                c_name.as_ptr(),
3001                &call_args,
3002                MutableHandle::<Value> {
3003                    _phantom_0: ::std::marker::PhantomData,
3004                    ptr: &mut rval,
3005                },
3006            );
3007        }
3008    }
3009    args.rval().set(mozjs::jsval::ObjectValue(watcher_r.get()));
3010    true
3011}
3012
3013/// fs.unwatchFile(filename[, listener]) — stop watchFile polling for `filename`.
3014/// Node semantics: with a listener, remove that listener from each matching
3015/// StatWatcher and close watchers left with no 'change' listeners; without,
3016/// close every StatWatcher on the path.
3017#[allow(unsafe_op_in_unsafe_fn)]
3018unsafe extern "C" fn fs_unwatch_file(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
3019    let args = CallArgs::from_vp(vp, _argc);
3020    let path = match get_path_arg(cx, &args, 0) {
3021        ::std::result::Result::Ok(p) => p,
3022        ::std::result::Result::Err(b) => return b,
3023    };
3024    let listener_val = if _argc > 1 { *args.get(1).ptr } else { UndefinedValue() };
3025    let canonical = fsw_canonicalize(&path);
3026    let ids: Vec<u64> = FSW_POLLERS.with(|p| {
3027        p.borrow()
3028            .iter()
3029            .filter(|e| e.path == canonical)
3030            .map(|e| e.id)
3031            .collect()
3032    });
3033    for id in ids {
3034        if listener_val.is_object() {
3035            // Remove just this listener; close only if 'change' went quiet.
3036            if let Some(obj) = crate::gc_store::gc_store_get_ns(cx, "fswatch", &format!("w{}", id))
3037            {
3038                let mut wrapped_cx =
3039                    mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
3040                let cx_ref = &mut wrapped_cx;
3041                rooted!(&in(cx_ref) let obj_r = obj);
3042                let c_rm = ZBox::from_bytes("removeListener".as_bytes());
3043                let c_ev = ZBox::from_bytes("change".as_bytes());
3044                rooted!(&in(cx_ref) let lv = listener_val);
3045                let ev_str = JS_NewStringCopyZ(cx, c_ev.as_ptr());
3046                if !ev_str.is_null() {
3047                    let argv = [mozjs::jsval::StringValue(&*ev_str), lv.get()];
3048                    let call_args = HandleValueArray {
3049                        length_: argv.len(),
3050                        elements_: argv.as_ptr(),
3051                    };
3052                    let mut rval = UndefinedValue();
3053                    JS_CallFunctionName(
3054                        cx,
3055                        obj_r.handle().into(),
3056                        c_rm.as_ptr(),
3057                        &call_args,
3058                        MutableHandle::<Value> {
3059                            _phantom_0: ::std::marker::PhantomData,
3060                            ptr: &mut rval,
3061                        },
3062                    );
3063                }
3064                // Still has 'change' listeners? keep polling — ask the events
3065                // module's static listenerCount(emitter, event) (public JS
3066                // surface; no EE internals touched from here).
3067                if let Some(events_mod) = crate::require::get_builtin(cx, "events") {
3068                    if !events_mod.is_null() {
3069                        rooted!(&in(cx_ref) let em_r = events_mod);
3070                        let c_cnt = ZBox::from_bytes("listenerCount".as_bytes());
3071                        let ev2_str = JS_NewStringCopyZ(cx, c_ev.as_ptr());
3072                        if !ev2_str.is_null() {
3073                            let argv = [mozjs::jsval::ObjectValue(obj_r.get()), mozjs::jsval::StringValue(&*ev2_str)];
3074                            let call_args = HandleValueArray {
3075                                length_: argv.len(),
3076                                elements_: argv.as_ptr(),
3077                            };
3078                            let mut cnt = UndefinedValue();
3079                            let ok = JS_CallFunctionName(
3080                                cx,
3081                                em_r.handle().into(),
3082                                c_cnt.as_ptr(),
3083                                &call_args,
3084                                MutableHandle::<Value> {
3085                                    _phantom_0: ::std::marker::PhantomData,
3086                                    ptr: &mut cnt,
3087                                },
3088                            );
3089                            if ok && cnt.is_int32() && cnt.to_int32() > 0 {
3090                                continue;
3091                            }
3092                        }
3093                    }
3094                }
3095            }
3096        }
3097        fsw_close_entry(cx, id);
3098    }
3099    args.rval().set(UndefinedValue());
3100    true
3101}
3102
3103#[allow(unsafe_op_in_unsafe_fn)]
3104unsafe extern "C" fn fs_noop_native(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
3105    let args = CallArgs::from_vp(vp, _argc);
3106    args.rval().set(UndefinedValue());
3107    true
3108}
3109
3110// ══════════════════════════════════════════════════════════════════════════
3111// fs.watch / fs.watchFile backend (BCE: silent-fake eradication)
3112//
3113// Architecture (single-threaded JS model + one OS worker thread per JS thread):
3114//   * fs.watch     → inotify (kernel events via bun_sys::linux — workspace
3115//                    syscall surface, same primitives bun_watcher builds on).
3116//   * fs.watchFile → stat polling at the caller's interval on the same worker.
3117//   * Worker thread owns the poll loop: poll(2) on [inotify_fd, wake_pipe_r]
3118//     with timeout = soonest watchFile deadline. It NEVER touches JS types —
3119//     events cross to the JS thread as plain data (PendingFsEvent) through a
3120//     Mutex<VecDeque> (cross-thread share: Mutex is the sanctioned tool per
3121//     去锁化). JS callbacks fire on the JS thread from
3122//     `fs_watch_pump_all` (driven by timers::drain_and_check / drain_one_pass,
3123//     same integration point as web_api::ws_pump_all).
3124//   * Liveness: a persistent watcher keeps the eval loop alive via
3125//     `fs_watch_loop_alive` (Node semantics: persistent=true keeps the
3126//     process alive; persistent:false delivers events only while the loop is
3127//     alive for other reasons).
3128// ══════════════════════════════════════════════════════════════════════════
3129
3130/// Plain-data event marshalled worker → JS thread. No JS types cross threads.
3131enum PendingFsEvent {
3132    /// inotify event for an fs.watch entry.
3133    Inotify {
3134        id: u64,
3135        /// "change" (IN_MODIFY/IN_ATTRIB) or "rename" (create/delete/move).
3136        event_type: &'static str,
3137        /// Event name for directory watches; basename for file watches; None
3138        /// when the kernel supplies no name (e.g. IN_DELETE_SELF).
3139        filename: Option<String>,
3140    },
3141    /// watchFile poll detected a stat change (or ENOENT → zeroed curr).
3142    StatChange {
3143        id: u64,
3144        prev: libc::stat,
3145        curr: libc::stat,
3146    },
3147    /// Watch-level failure surfaced to the JS 'error' event.
3148    WatchError { id: u64, message: String },
3149}
3150
3151enum FswCommand {
3152    AddPoll {
3153        id: u64,
3154        path: PathBuf,
3155        interval_ms: u64,
3156        baseline: libc::stat,
3157    },
3158    RemovePoll { id: u64 },
3159    Shutdown,
3160}
3161
3162struct FswShared {
3163    queue: VecDeque<PendingFsEvent>,
3164    commands: VecDeque<FswCommand>,
3165    /// wd → watcher ids sharing that kernel watch (inotify returns the SAME
3166    /// wd for repeated add_watch on one path within one fd; Node semantics =
3167    /// every fs.watch watcher is independent, so one wd fans out to ALL
3168    /// watchers registered on it — a wd→single-id map made the first
3169    /// registrant silently lose events to the overwrite). The JS thread
3170    /// writes on add/remove, the worker reads when decoding events.
3171    wd_map: HashMap<i32, Vec<u64>>,
3172}
3173
3174impl FswShared {
3175    fn new() -> Self {
3176        Self {
3177            queue: VecDeque::new(),
3178            commands: VecDeque::new(),
3179            wd_map: HashMap::new(),
3180        }
3181    }
3182}
3183
3184/// JS-thread watcher registration (fs.watch entries).
3185struct FswWatchEntry {
3186    id: u64,
3187    wd: i32,
3188    path: PathBuf,
3189    is_dir: bool,
3190    persistent: bool,
3191}
3192
3193/// JS-thread poller registration (fs.watchFile entries).
3194struct FswPollEntry {
3195    id: u64,
3196    path: PathBuf,
3197    persistent: bool,
3198}
3199
3200thread_local! {
3201    static FSW_NEXT_ID: Cell<u64> = const { Cell::new(1) };
3202    static FSW_WATCHERS: RefCell<Vec<FswWatchEntry>> = const { RefCell::new(Vec::new()) };
3203    static FSW_POLLERS: RefCell<Vec<FswPollEntry>> = const { RefCell::new(Vec::new()) };
3204    /// Hub: worker thread + shared queues + inotify/wake fds. Materialized on
3205    /// the first watcher, torn down (joined) when the last watcher closes.
3206    static FSW_HUB: RefCell<Option<FswHub>> = const { RefCell::new(None) };
3207}
3208
3209struct FswHub {
3210    shared: Arc<Mutex<FswShared>>,
3211    inotify_fd: i32,
3212    wake_w: i32,
3213    handle: Option<::std::thread::JoinHandle<()>>,
3214}
3215
3216impl Drop for FswHub {
3217    fn drop(&mut self) {
3218        // Best-effort synchronous teardown: signal shutdown, join the worker
3219        // (the wake pipe makes the poll return immediately), close fds.
3220        if let Ok(mut shared) = self.shared.lock() {
3221            shared.commands.push_back(FswCommand::Shutdown);
3222        }
3223        fsw_write_wake(self.wake_w);
3224        if let Some(handle) = self.handle.take() {
3225            let _ = handle.join();
3226        }
3227        if self.inotify_fd >= 0 {
3228            unsafe { libc::close(self.inotify_fd) };
3229        }
3230        if self.wake_w >= 0 {
3231            unsafe { libc::close(self.wake_w) };
3232        }
3233    }
3234}
3235
3236fn fsw_write_wake(fd: i32) {
3237    if fd < 0 {
3238        return;
3239    }
3240    let byte = [b'p'];
3241    // SAFETY: write one byte to a pipe we own; ignore EAGAIN/EINTR — the
3242    // worker's poll timeout bounds the worst-case missed wake.
3243    unsafe {
3244        let _ = libc::write(fd, byte.as_ptr() as *const ::std::ffi::c_void, 1);
3245    }
3246}
3247
3248fn fsw_next_id() -> u64 {
3249    FSW_NEXT_ID.with(|c| {
3250        let v = c.get();
3251        c.set(v + 1);
3252        v
3253    })
3254}
3255
3256fn fsw_zero_stat() -> libc::stat {
3257    // SAFETY: libc::stat is plain POD; zeroed() is the "file absent" sentinel
3258    // (Node passes zeroed Stats when the watched file is gone).
3259    unsafe { ::std::mem::zeroed() }
3260}
3261
3262fn fsw_stat_path(path: &str) -> ::std::option::Option<libc::stat> {
3263    let c_path = bun_core::ZBox::from_bytes(path.as_bytes());
3264    let mut st: libc::stat = fsw_zero_stat();
3265    // SAFETY: c_path is NUL-terminated; st is writable POD of the size stat expects.
3266    let rc = unsafe { libc::stat(c_path.as_ptr() as *const ::std::ffi::c_char, &mut st) };
3267    if rc == 0 {
3268        Some(st)
3269    } else {
3270        None
3271    }
3272}
3273
3274fn fsw_stat_changed(a: &libc::stat, b: &libc::stat) -> bool {
3275    // Node/libuv uv_fs_poll change predicate: size, mtime (ns), ino, mode.
3276    a.st_size != b.st_size
3277        || a.st_mtime != b.st_mtime
3278        || a.st_mtime_nsec != b.st_mtime_nsec
3279        || a.st_ino != b.st_ino
3280        || a.st_mode != b.st_mode
3281}
3282
3283fn fsw_canonicalize(path: &str) -> PathBuf {
3284    PathBuf::from(path).canonicalize().unwrap_or_else(|_| PathBuf::from(path))
3285}
3286
3287/// Materialize the hub (worker thread + inotify fd + wake pipe) if absent.
3288fn fsw_ensure_hub() -> bool {
3289    FSW_HUB.with(|h| {
3290        if h.borrow().is_some() {
3291            return true;
3292        }
3293        // SAFETY: raw inotify/pipe setup via libc; fds checked below.
3294        unsafe {
3295            let inotify_fd = libc::inotify_init1(libc::IN_CLOEXEC | libc::IN_NONBLOCK);
3296            if inotify_fd < 0 {
3297                return false;
3298            }
3299            let mut pipe_fds = [-1i32, -1];
3300            if libc::pipe2(pipe_fds.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) != 0 {
3301                libc::close(inotify_fd);
3302                return false;
3303            }
3304            let (wake_r, wake_w) = (pipe_fds[0], pipe_fds[1]);
3305            let shared = Arc::new(Mutex::new(FswShared::new()));
3306            let worker_shared = Arc::clone(&shared);
3307            let spawned = ::std::thread::Builder::new()
3308                .name("bao-fswatch".to_string())
3309                .stack_size(128 * 1024)
3310                .spawn(move || fsw_worker_main(worker_shared, inotify_fd, wake_r));
3311            match spawned {
3312                Ok(handle) => {
3313                    // wake_r is owned by the worker loop exclusively now.
3314                    *h.borrow_mut() = Some(FswHub {
3315                        shared,
3316                        inotify_fd,
3317                        wake_w,
3318                        handle: Some(handle),
3319                    });
3320                    true
3321                }
3322                Err(_) => {
3323                    libc::close(inotify_fd);
3324                    libc::close(wake_r);
3325                    libc::close(wake_w);
3326                    false
3327                }
3328            }
3329        }
3330    })
3331}
3332
3333/// Worker thread: poll [inotify, wake] + stat-poll loop. Never touches JS.
3334fn fsw_worker_main(shared: Arc<Mutex<FswShared>>, inotify_fd: i32, wake_r: i32) {
3335    struct PollSpec {
3336        id: u64,
3337        path: PathBuf,
3338        interval_ms: u64,
3339        last: libc::stat,
3340        next_due_ms: u128,
3341    }
3342    let mut polls: Vec<PollSpec> = Vec::new();
3343    let mut inotify_buf = [0u8; 64 * 1024];
3344
3345    loop {
3346        // Apply pending commands under a short lock.
3347        {
3348            let mut commands: VecDeque<FswCommand> = VecDeque::new();
3349            let mut shutdown = false;
3350            if let Ok(mut guard) = shared.lock() {
3351                ::std::mem::swap(&mut commands, &mut guard.commands);
3352                shutdown = commands.iter().any(|c| matches!(c, FswCommand::Shutdown));
3353                if shutdown {
3354                    guard.commands.clear();
3355                }
3356            }
3357            if shutdown {
3358                break;
3359            }
3360            for cmd in commands {
3361                match cmd {
3362                    FswCommand::AddPoll { id, path, interval_ms, baseline } => {
3363                        polls.retain(|p| p.id != id);
3364                        polls.push(PollSpec {
3365                            id,
3366                            path,
3367                            interval_ms,
3368                            last: baseline,
3369                            next_due_ms: monotonic_ms().saturating_add(interval_ms as u128),
3370                        });
3371                    }
3372                    FswCommand::RemovePoll { id } => polls.retain(|p| p.id != id),
3373                    FswCommand::Shutdown => unreachable!("handled above"),
3374                }
3375            }
3376        }
3377
3378        // Timeout: soonest poll deadline, else block until inotify/wake.
3379        let now = monotonic_ms();
3380        let timeout_ms: i64 = polls
3381            .iter()
3382            .map(|p| p.next_due_ms.saturating_sub(now) as i64)
3383            .min()
3384            .unwrap_or(-1);
3385        let timeout_i32: i32 = if timeout_ms < 0 {
3386            -1
3387        } else {
3388            timeout_ms.min(i32::MAX as i64) as i32
3389        };
3390
3391        let mut poll_fds = [
3392            libc::pollfd { fd: inotify_fd, events: libc::POLLIN as i16, revents: 0 },
3393            libc::pollfd { fd: wake_r, events: libc::POLLIN as i16, revents: 0 },
3394        ];
3395        // SAFETY: poll on fds we own with a bounded (or -1) timeout — woken
3396        // by inotify events or the wake pipe, so -1 cannot pin shutdown.
3397        let rc = unsafe { libc::poll(poll_fds.as_mut_ptr(), 2, timeout_i32) };
3398        if rc < 0 {
3399            let errno = unsafe { *libc::__errno_location() };
3400            if errno == libc::EINTR {
3401                continue;
3402            }
3403            break;
3404        }
3405
3406        // Drain the wake pipe so it never fills.
3407        if poll_fds[1].revents & (libc::POLLIN as i16) != 0 {
3408            let mut scratch = [0u8; 64];
3409            loop {
3410                // SAFETY: read ≤64 bytes from the non-blocking wake pipe.
3411                let n = unsafe {
3412                    libc::read(wake_r, scratch.as_mut_ptr() as *mut ::std::ffi::c_void, 64)
3413                };
3414                if n <= 0 {
3415                    break;
3416                }
3417            }
3418        }
3419
3420        // Decode inotify events → queue.
3421        if poll_fds[0].revents & (libc::POLLIN as i16 | libc::POLLHUP as i16) != 0 {
3422            loop {
3423                // SAFETY: read into our buffer from the non-blocking inotify fd.
3424                let n = unsafe {
3425                    libc::read(inotify_fd, inotify_buf.as_mut_ptr() as *mut ::std::ffi::c_void, inotify_buf.len())
3426                };
3427                if n <= 0 {
3428                    break;
3429                }
3430                let buf = &inotify_buf[..n as usize];
3431                let mut off = 0usize;
3432                while off + ::std::mem::size_of::<libc::inotify_event>() <= buf.len() {
3433                    // SAFETY: the kernel guarantees struct-aligned inotify_event
3434                    // headers at these offsets (that is the inotify ABI).
3435                    let ev = unsafe {
3436                        &*(buf[off..].as_ptr() as *const libc::inotify_event)
3437                    };
3438                    let name: Option<String> = if ev.len > 0 {
3439                        let name_start = off + ::std::mem::size_of::<libc::inotify_event>();
3440                        let name_end = (name_start + ev.len as usize).min(buf.len());
3441                        let name_bytes: Vec<u8> = buf[name_start..name_end]
3442                            .iter()
3443                            .take_while(|&&b| b != 0)
3444                            .cloned()
3445                            .collect::<Vec<u8>>();
3446                        String::from_utf8(name_bytes).ok() // non-utf8 names are surfaced as absent (registered limit)
3447                    } else {
3448                        None
3449                    };
3450                    // Fan out to EVERY watcher sharing this wd (same-path
3451                    // multi-watch: all watchers get their own event copy).
3452                    let ids: Vec<u64> = shared
3453                        .lock()
3454                        .ok()
3455                        .and_then(|g| g.wd_map.get(&ev.wd).cloned())
3456                        .unwrap_or_default();
3457                    for id in ids {
3458                        let event_type = if ev.mask & (libc::IN_MODIFY | libc::IN_ATTRIB) != 0 {
3459                            "change"
3460                        } else {
3461                            // CREATE / DELETE / MOVED_FROM / MOVED_TO /
3462                            // MOVE_SELF / DELETE_SELF / IGNORED → rename (Node).
3463                            "rename"
3464                        };
3465                        if let Ok(mut guard) = shared.lock() {
3466                            guard.queue.push_back(PendingFsEvent::Inotify {
3467                                id,
3468                                event_type,
3469                                filename: name.clone(),
3470                            });
3471                        }
3472                    }
3473                    off += ::std::mem::size_of::<libc::inotify_event>() + ev.len as usize;
3474                }
3475            }
3476        }
3477
3478        // Due stat polls.
3479        let now = monotonic_ms();
3480        let mut due: Vec<usize> = Vec::new();
3481        for (i, p) in polls.iter().enumerate() {
3482            if p.next_due_ms <= now {
3483                due.push(i);
3484            }
3485        }
3486        for i in due {
3487            let Some(spec) = polls.get_mut(i) else { continue };
3488            spec.next_due_ms = now + spec.interval_ms as u128;
3489            let curr = match fsw_stat_path(&spec.path.to_string_lossy()) {
3490                Some(st) => st,
3491                None => fsw_zero_stat(),
3492            };
3493            if fsw_stat_changed(&spec.last, &curr) {
3494                let prev = spec.last;
3495                spec.last = curr;
3496                if let Ok(mut guard) = shared.lock() {
3497                    guard.queue.push_back(PendingFsEvent::StatChange {
3498                        id: spec.id,
3499                        prev,
3500                        curr,
3501                    });
3502                }
3503            } else {
3504                spec.last = curr;
3505            }
3506        }
3507    }
3508    // Worker exit: drop our copy of the wake read-end.
3509    unsafe { libc::close(wake_r) };
3510}
3511
3512fn monotonic_ms() -> u128 {
3513    ::std::time::SystemTime::now()
3514        .duration_since(::std::time::UNIX_EPOCH)
3515        .map(|d| d.as_millis())
3516        .unwrap_or(0)
3517}
3518
3519fn fsw_add_inotify_watch(path: &str) -> ::std::result::Result<i32, ::std::io::Error> {
3520    if !fsw_ensure_hub() {
3521        return ::std::result::Result::Err(::std::io::Error::other(
3522            "fs.watch: failed to start the watcher thread",
3523        ));
3524    }
3525    let c_path = bun_core::ZBox::from_bytes(path.as_bytes());
3526    // Node/libuv inotify mask for fs.watch.
3527    let mask = libc::IN_ATTRIB
3528        | libc::IN_CREATE
3529        | libc::IN_MODIFY
3530        | libc::IN_MOVED_FROM
3531        | libc::IN_MOVED_TO
3532        | libc::IN_DELETE
3533        | libc::IN_DELETE_SELF
3534        | libc::IN_MOVE_SELF;
3535    // SAFETY: c_path is NUL-terminated; fd is the hub's live inotify fd.
3536    let wd = unsafe {
3537        libc::inotify_add_watch(
3538            FSW_HUB.with(|h| h.borrow().as_ref().map(|hub| hub.inotify_fd).unwrap_or(-1)),
3539            c_path.as_ptr() as *const ::std::ffi::c_char,
3540            mask,
3541        )
3542    };
3543    if wd < 0 {
3544        return ::std::result::Result::Err(::std::io::Error::last_os_error());
3545    }
3546    ::std::result::Result::Ok(wd)
3547}
3548
3549fn fsw_register_watch(id: u64, wd: i32, path: String, is_dir: bool, persistent: bool) {
3550    FSW_HUB.with(|h| {
3551        if let Some(hub) = h.borrow().as_ref() {
3552            if let Ok(mut guard) = hub.shared.lock() {
3553                // Append, never overwrite: same-path add_watch returns the
3554                // same wd, and every watcher on that wd keeps its events.
3555                guard.wd_map.entry(wd).or_default().push(id);
3556            }
3557        }
3558    });
3559    FSW_WATCHERS.with(|w| {
3560        w.borrow_mut().push(FswWatchEntry {
3561            id,
3562            wd,
3563            path: PathBuf::from(path),
3564            is_dir,
3565            persistent,
3566        });
3567    });
3568}
3569
3570fn fsw_register_poll(id: u64, path: String, interval_ms: u64, baseline: libc::stat, persistent: bool) {
3571    if !fsw_ensure_hub() {
3572        return;
3573    }
3574    FSW_POLLERS.with(|p| {
3575        p.borrow_mut().push(FswPollEntry {
3576            id,
3577            path: fsw_canonicalize(&path),
3578            persistent,
3579        });
3580    });
3581    FSW_HUB.with(|h| {
3582        if let Some(hub) = h.borrow().as_ref() {
3583            if let Ok(mut guard) = hub.shared.lock() {
3584                guard.commands.push_back(FswCommand::AddPoll {
3585                    id,
3586                    path: PathBuf::from(path),
3587                    interval_ms,
3588                    baseline,
3589                });
3590            }
3591        }
3592    });
3593    fsw_wake_worker();
3594}
3595
3596fn fsw_wake_worker() {
3597    FSW_HUB.with(|h| {
3598        if let Some(hub) = h.borrow().as_ref() {
3599            fsw_write_wake(hub.wake_w);
3600        }
3601    });
3602}
3603
3604/// Close one watcher/poller entry (fs_watch close native / unwatchFile):
3605/// deregister, emit 'close' on the JS object, tear the hub down when idle.
3606unsafe fn fsw_close_entry(cx: *mut JSContext, id: u64) {
3607    let watch = FSW_WATCHERS.with(|w| w.borrow_mut().iter().position(|e| e.id == id));
3608    let poll = FSW_POLLERS.with(|p| p.borrow_mut().iter().position(|e| e.id == id));
3609    let key = format!("w{}", id);
3610
3611    if let Some(idx) = watch {
3612        let entry = FSW_WATCHERS.with(|w| w.borrow_mut().remove(idx));
3613        // Refcounted teardown: other watchers may share this wd (same-path
3614        // multi-watch). The kernel watch + wd mapping go away only when the
3615        // LAST watcher on the wd closes — dropping them earlier would silence
3616        // the surviving watchers.
3617        FSW_HUB.with(|h| {
3618            if let Some(hub) = h.borrow().as_ref() {
3619                let mut last_on_wd = false;
3620                if let Ok(mut guard) = hub.shared.lock() {
3621                    if let Some(ids) = guard.wd_map.get_mut(&entry.wd) {
3622                        ids.retain(|&i| i != id);
3623                        last_on_wd = ids.is_empty();
3624                    }
3625                    if last_on_wd {
3626                        guard.wd_map.remove(&entry.wd);
3627                    }
3628                }
3629                if last_on_wd {
3630                    unsafe { libc::inotify_rm_watch(hub.inotify_fd, entry.wd) };
3631                }
3632            }
3633        });
3634    }
3635    if let Some(idx) = poll {
3636        FSW_POLLERS.with(|p| p.borrow_mut().remove(idx));
3637        FSW_HUB.with(|h| {
3638            if let Some(hub) = h.borrow().as_ref() {
3639                if let Ok(mut guard) = hub.shared.lock() {
3640                    guard.commands.push_back(FswCommand::RemovePoll { id });
3641                }
3642            }
3643        });
3644        fsw_wake_worker();
3645    }
3646
3647    // Emit 'close' (Node: FSWatcher/StatWatcher emit 'close' when closed),
3648    // then release the GcStore root.
3649    if let Some(obj) = crate::gc_store::gc_store_get_ns(cx, "fswatch", &key) {
3650        let cx_ref = &mut mozjs::context::JSContext::from_ptr(
3651            ::std::ptr::NonNull::new_unchecked(cx),
3652        );
3653        rooted!(&in(cx_ref) let obj_r = obj);
3654        let c_emit = ZBox::from_bytes("emit".as_bytes());
3655        let c_ev = ZBox::from_bytes("close".as_bytes());
3656        let ev_str = JS_NewStringCopyZ(cx, c_ev.as_ptr());
3657        if !ev_str.is_null() {
3658            let argv = [mozjs::jsval::StringValue(&*ev_str)];
3659            let call_args = HandleValueArray {
3660                length_: argv.len(),
3661                elements_: argv.as_ptr(),
3662            };
3663            let mut rval = UndefinedValue();
3664            JS_CallFunctionName(
3665                cx,
3666                obj_r.handle().into(),
3667                c_emit.as_ptr(),
3668                &call_args,
3669                MutableHandle::<Value> {
3670                    _phantom_0: ::std::marker::PhantomData,
3671                    ptr: &mut rval,
3672                },
3673            );
3674            JS_ClearPendingException(cx);
3675        }
3676    }
3677    crate::gc_store::gc_store_remove_ns(cx, "fswatch", &key);
3678    let _ = fsw_maybe_shutdown_if_idle();
3679}
3680
3681/// Tear the worker hub down when the last entry closed. Returns true if the
3682/// hub was torn down (or was already absent).
3683fn fsw_maybe_shutdown_if_idle() -> bool {
3684    let watchers_empty = FSW_WATCHERS.with(|w| w.borrow().is_empty());
3685    let pollers_empty = FSW_POLLERS.with(|p| p.borrow().is_empty());
3686    if watchers_empty && pollers_empty {
3687        FSW_HUB.with(|h| {
3688            *h.borrow_mut() = None; // Drop signals shutdown + joins the worker.
3689        });
3690        true
3691    } else {
3692        false
3693    }
3694}
3695
3696/// Build the FSWatcher / StatWatcher JS object: EventEmitter-shaped (on/once/
3697/// off/emit/listenerCount wired to node_events' EE natives) + a real close().
3698unsafe fn fsw_make_watcher_object(cx: *mut JSContext, id: u64, _is_dir: bool) -> *mut JSObject {
3699    let cx_ref = &mut mozjs::context::JSContext::from_ptr(
3700        ::std::ptr::NonNull::new_unchecked(cx),
3701    );
3702    rooted!(&in(cx_ref) let watcher = w2::JS_NewPlainObject(cx_ref));
3703    if watcher.get().is_null() {
3704        return ::std::ptr::null_mut();
3705    }
3706    let on_op: JSNative = Some(crate::node_events::ee_on);
3707    let off_op: JSNative = Some(crate::node_events::ee_off);
3708    let once_op: JSNative = Some(crate::node_events::ee_once);
3709    let emit_op: JSNative = Some(crate::node_events::ee_emit);
3710    let close_op: JSNative = Some(fsw_close_native);
3711    for (name, op, nargs) in [
3712        ("on", on_op, 2u32),
3713        ("addListener", on_op, 2),
3714        ("off", off_op, 2),
3715        ("removeListener", off_op, 2),
3716        ("once", once_op, 2),
3717        ("emit", emit_op, 2),
3718        ("close", close_op, 0),
3719    ] {
3720        let c_name = ZBox::from_bytes(name.as_bytes());
3721        mozjs_sys::jsapi::JS_DefineFunction(
3722            cx,
3723            watcher.handle().into(),
3724            c_name.as_ptr(),
3725            op,
3726            nargs,
3727            JSPROP_ENUMERATE as u32,
3728        );
3729    }
3730    // Hidden numeric id used by fsw_close_native; rooted in GcStore for GC
3731    // safety across event-loop iterations.
3732    rooted!(&in(cx_ref) let id_v = Int32Value(id as i32));
3733    JS_DefineProperty(
3734        cx,
3735        watcher.handle().into(),
3736        c"_fswId".as_ptr(),
3737        id_v.handle().into(),
3738        0,
3739    );
3740    crate::gc_store::gc_store_insert_ns(cx, "fswatch", &format!("w{}", id), watcher.get());
3741    watcher.get()
3742}
3743
3744#[allow(unsafe_op_in_unsafe_fn)]
3745unsafe extern "C" fn fsw_close_native(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
3746    let args = CallArgs::from_vp(vp, _argc);
3747    let this_v = *args.thisv().ptr;
3748    if this_v.is_object() {
3749        let obj = this_v.to_object();
3750        let cx_ref = &mut mozjs::context::JSContext::from_ptr(
3751            ::std::ptr::NonNull::new_unchecked(cx),
3752        );
3753        rooted!(&in(cx_ref) let obj_r = obj);
3754        let mut id_v = UndefinedValue();
3755        JS_GetProperty(
3756            cx,
3757            obj_r.handle().into(),
3758            c"_fswId".as_ptr(),
3759            MutableHandle::<Value> {
3760                _phantom_0: ::std::marker::PhantomData,
3761                ptr: &mut id_v,
3762            },
3763        );
3764        if id_v.is_int32() {
3765            fsw_close_entry(cx, id_v.to_int32() as u64);
3766        }
3767    }
3768    args.rval().set(UndefinedValue());
3769    true
3770}
3771
3772/// Event-loop liveness: a persistent fs.watch / fs.watchFile entry keeps the
3773/// process alive (Node semantics); non-persistent entries never pin.
3774pub fn fs_watch_loop_alive() -> bool {
3775    let w = FSW_WATCHERS.with(|w| w.borrow().iter().any(|e| e.persistent));
3776    let p = FSW_POLLERS.with(|p| p.borrow().iter().any(|e| e.persistent));
3777    w || p
3778}
3779
3780/// Pump all queued watch events on the JS thread. Called from
3781/// `timers::drain_and_check` / `drain_one_pass` (same integration point as
3782/// `web_api::ws_pump_all`).
3783pub fn fs_watch_pump_all(raw_cx: *mut JSContext) {
3784    // Fast path: no hub → nothing queued.
3785    let events: Vec<PendingFsEvent> = match FSW_HUB.with(|h| h.borrow().as_ref().map(|hub| Arc::clone(&hub.shared))) {
3786        Some(shared) => match shared.lock() {
3787            Ok(mut guard) => ::std::mem::take(&mut guard.queue).into_iter().collect(),
3788            Err(_) => return,
3789        },
3790        None => return,
3791    };
3792    if events.is_empty() {
3793        return;
3794    }
3795    for ev in events {
3796        // SAFETY: raw_cx is the live JSContext on this thread (drain hook
3797        // contract); dispatch only touches JS on this thread.
3798        unsafe { fsw_dispatch_event(raw_cx, ev) };
3799    }
3800}
3801
3802/// Fire one event on its watcher object. Listener throws route through the
3803/// unified uncaught-exception router — never silently swallowed.
3804unsafe fn fsw_dispatch_event(raw_cx: *mut JSContext, ev: PendingFsEvent) {
3805    let (id, event_name, argv): (u64, &str, Vec<mozjs::jsval::JSVal>) = match ev {
3806        PendingFsEvent::Inotify { id, event_type, filename } => {
3807            let mut argv = Vec::with_capacity(2);
3808            let c_type = ZBox::from_bytes(event_type.as_bytes());
3809            let type_str = JS_NewStringCopyZ(raw_cx, c_type.as_ptr());
3810            if !type_str.is_null() {
3811                argv.push(mozjs::jsval::StringValue(&*type_str));
3812            }
3813            // Filename resolution (Node semantics): directory watches report
3814            // the event's name; FILE watches carry no kernel name — Node
3815            // passes the watched file's basename instead of null.
3816            let resolved_name: Option<String> = match filename {
3817                Some(name) => Some(name),
3818                None => FSW_WATCHERS.with(|w| {
3819                    w.borrow().iter().find(|e| e.id == id).and_then(|e| {
3820                        if e.is_dir {
3821                            None
3822                        } else {
3823                            e.path
3824                                .file_name()
3825                                .map(|n| n.to_string_lossy().into_owned())
3826                        }
3827                    })
3828                }),
3829            };
3830            match resolved_name {
3831                Some(name) => {
3832                    let c_name = ZBox::from_bytes(name.as_bytes());
3833                    let name_str = JS_NewStringCopyZ(raw_cx, c_name.as_ptr());
3834                    if !name_str.is_null() {
3835                        argv.push(mozjs::jsval::StringValue(&*name_str));
3836                    }
3837                }
3838                None => argv.push(mozjs::jsval::NullValue()),
3839            }
3840            (id, "change", argv)
3841        }
3842        PendingFsEvent::StatChange { id, prev, curr } => {
3843            let curr_obj = build_stats_object(raw_cx, &curr);
3844            let prev_obj = build_stats_object(raw_cx, &prev);
3845            let mut argv = Vec::with_capacity(2);
3846            if !curr_obj.is_null() {
3847                argv.push(mozjs::jsval::ObjectValue(curr_obj));
3848            }
3849            if !prev_obj.is_null() {
3850                argv.push(mozjs::jsval::ObjectValue(prev_obj));
3851            }
3852            (id, "change", argv)
3853        }
3854        PendingFsEvent::WatchError { id, message } => {
3855            // Error events without an 'error' listener must throw (Node) —
3856            // ee_emit's BCE-20260816-EE-ERRORTHROW semantics handle that when
3857            // the Error value is a real object; build one here.
3858            let c_msg = ZBox::from_bytes(message.as_bytes());
3859            let err_obj = JS_NewPlainObject(raw_cx);
3860            if !err_obj.is_null() {
3861                let cx_ref = &mut mozjs::context::JSContext::from_ptr(
3862                    ::std::ptr::NonNull::new_unchecked(raw_cx),
3863                );
3864                rooted!(&in(cx_ref) let err_r = err_obj);
3865                let msg_str = JS_NewStringCopyZ(raw_cx, c_msg.as_ptr());
3866                if !msg_str.is_null() {
3867                    rooted!(&in(cx_ref) let mv = mozjs::jsval::StringValue(&*msg_str));
3868                    JS_DefineProperty(
3869                        raw_cx,
3870                        err_r.handle().into(),
3871                        c"message".as_ptr(),
3872                        mv.handle().into(),
3873                        JSPROP_ENUMERATE as u32,
3874                    );
3875                    let argv = vec![mozjs::jsval::ObjectValue(err_r.get())];
3876                    return fsw_emit_on_entry(raw_cx, id, "error", argv);
3877                }
3878            }
3879            return;
3880        }
3881    };
3882    fsw_emit_on_entry(raw_cx, id, event_name, argv);
3883}
3884
3885unsafe fn fsw_emit_on_entry(raw_cx: *mut JSContext, id: u64, event: &str, argv: Vec<mozjs::jsval::JSVal>) {
3886    let key = format!("w{}", id);
3887    let Some(obj) = crate::gc_store::gc_store_get_ns(raw_cx, "fswatch", &key) else {
3888        return; // entry closed while the event was in flight — drop it.
3889    };
3890    let cx_ref = &mut mozjs::context::JSContext::from_ptr(
3891        ::std::ptr::NonNull::new_unchecked(raw_cx),
3892    );
3893    rooted!(&in(cx_ref) let obj_r = obj);
3894    let c_emit = ZBox::from_bytes("emit".as_bytes());
3895    let c_ev = ZBox::from_bytes(event.as_bytes());
3896    let ev_str = JS_NewStringCopyZ(raw_cx, c_ev.as_ptr());
3897    if ev_str.is_null() {
3898        return;
3899    }
3900    rooted!(&in(cx_ref) let ev_root = mozjs::jsval::StringValue(&*ev_str));
3901    let mut all_argv: Vec<mozjs::jsval::JSVal> = Vec::with_capacity(argv.len() + 1);
3902    all_argv.push(ev_root.get());
3903    all_argv.extend(argv);
3904    let call_args = HandleValueArray {
3905        length_: all_argv.len(),
3906        elements_: all_argv.as_ptr(),
3907    };
3908    let mut rval = UndefinedValue();
3909    let ok = JS_CallFunctionName(
3910        raw_cx,
3911        obj_r.handle().into(),
3912        c_emit.as_ptr(),
3913        &call_args,
3914        MutableHandle::<Value> {
3915            _phantom_0: ::std::marker::PhantomData,
3916            ptr: &mut rval,
3917        },
3918    );
3919    if !ok {
3920        // Route listener throws exactly like timer callbacks (uncaught router),
3921        // then clear so subsequent events still dispatch.
3922        let mut exn = UndefinedValue();
3923        JS_GetPendingException(
3924            raw_cx,
3925            MutableHandle::<Value> {
3926                _phantom_0: ::std::marker::PhantomData,
3927                ptr: &mut exn,
3928            },
3929        );
3930        JS_ClearPendingException(raw_cx);
3931        if !exn.is_undefined() {
3932            rooted!(&in(cx_ref) let reason_root = exn);
3933            crate::uncaught::route_uncaught_exception(raw_cx, exn);
3934        }
3935    }
3936}
3937
3938// --- Async (callback-based) ---
3939
3940#[allow(unsafe_op_in_unsafe_fn)]
3941unsafe extern "C" fn fs_read_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
3942    let args = CallArgs::from_vp(vp, argc);
3943    let path = match get_path_arg(cx, &args, 0) {
3944        ::std::result::Result::Ok(p) => p,
3945        ::std::result::Result::Err(b) => return b,
3946    };
3947    let encoding = get_encoding_opt(cx, &args, 1);
3948
3949    match bun_fs::read(&path) {
3950        ::std::result::Result::Ok(data) => {
3951            return_string_content(cx, &args, &data, encoding.as_deref())
3952        }
3953        ::std::result::Result::Err(e) => throw_fs_error(cx, "readFile", &path, &e),
3954    }
3955}
3956
3957#[allow(unsafe_op_in_unsafe_fn)]
3958unsafe extern "C" fn fs_write_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
3959    let args = CallArgs::from_vp(vp, argc);
3960    let path = match get_path_arg(cx, &args, 0) {
3961        ::std::result::Result::Ok(p) => p,
3962        ::std::result::Result::Err(b) => return b,
3963    };
3964    let data_val = if argc > 1 {
3965        *args.get(1).ptr
3966    } else {
3967        UndefinedValue()
3968    };
3969    let bytes = if data_val.is_string() {
3970        let s = data_val.to_string();
3971        if !s.is_null() {
3972            crate::jsstr_to_rust_string(cx, s).into_bytes()
3973        } else {
3974            Vec::new()
3975        }
3976    } else {
3977        Vec::new()
3978    };
3979
3980    match bun_fs::write(&path, &bytes) {
3981        ::std::result::Result::Ok(()) => {
3982            args.rval().set(UndefinedValue());
3983            true
3984        }
3985        ::std::result::Result::Err(e) => throw_fs_error(cx, "writeFile", &path, &e),
3986    }
3987}
3988
3989#[allow(unsafe_op_in_unsafe_fn)]
3990unsafe extern "C" fn fs_mkdir(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
3991    let args = CallArgs::from_vp(vp, argc);
3992    let path = match get_path_arg(cx, &args, 0) {
3993        ::std::result::Result::Ok(p) => p,
3994        ::std::result::Result::Err(b) => return b,
3995    };
3996    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
3997        let c_msg = ZBox::from_bytes(e.as_bytes());
3998        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
3999        return false;
4000    }
4001    let recursive = get_bool_option(cx, &args, 1, "recursive");
4002    let result = if recursive {
4003        fs::create_dir_all(&path)
4004    } else {
4005        fs::create_dir(&path)
4006    };
4007    match result {
4008        ::std::result::Result::Ok(()) => {
4009            if argc > 1 && (*args.get(argc - 1).ptr).is_object() {
4010                let mut wrapped_cx_cb =
4011                    mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
4012                let cx_ref_cb = &mut wrapped_cx_cb;
4013                rooted!(&in(cx_ref_cb) let cb = (*args.get(argc - 1).ptr).to_object());
4014                rooted!(&in(cx_ref_cb) let cb_val = mozjs::jsval::ObjectValue(cb.get()));
4015                let null_args = HandleValueArray::empty();
4016                let global = CurrentGlobalOrNull(cx);
4017                if !global.is_null() {
4018                    rooted!(&in(cx_ref_cb) let global_rooted = global);
4019                    let mut rval = UndefinedValue();
4020                    JS_CallFunctionValue(
4021                        cx,
4022                        global_rooted.handle().into(),
4023                        cb_val.handle().into(),
4024                        &null_args,
4025                        MutableHandle::<Value> {
4026                            _phantom_0: ::std::marker::PhantomData,
4027                            ptr: &mut rval,
4028                        },
4029                    );
4030                    JS_ClearPendingException(cx);
4031                }
4032            }
4033            args.rval().set(UndefinedValue());
4034            true
4035        }
4036        ::std::result::Result::Err(e) => {
4037            if argc > 1 && (*args.get(argc - 1).ptr).is_object() {
4038                let mut wrapped_cx_err =
4039                    mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
4040                let cx_ref_err = &mut wrapped_cx_err;
4041                rooted!(&in(cx_ref_err) let cb = (*args.get(argc - 1).ptr).to_object());
4042                rooted!(&in(cx_ref_err) let cb_val = mozjs::jsval::ObjectValue(cb.get()));
4043                let err_msg = format!("EACCES: mkdir '{}': {}", path, e);
4044                let c_err = ZBox::from_bytes(err_msg.as_bytes());
4045                rooted!(&in(cx_ref_err) let err_obj = JS_NewPlainObject(cx));
4046                if !err_obj.get().is_null() {
4047                    let msg_str = JS_NewStringCopyZ(cx, c_err.as_ptr());
4048                    if !msg_str.is_null() {
4049                        rooted!(&in(cx_ref_err) let msg_val = mozjs::jsval::StringValue(&*msg_str));
4050                        JS_DefineProperty(
4051                            cx,
4052                            err_obj.handle().into(),
4053                            c"message".as_ptr(),
4054                            msg_val.handle().into(),
4055                            JSPROP_ENUMERATE as u32,
4056                        );
4057                    }
4058                    let code_str = JS_NewStringCopyZ(cx, c"EACCES".as_ptr());
4059                    if !code_str.is_null() {
4060                        rooted!(&in(cx_ref_err) let code_val = mozjs::jsval::StringValue(&*code_str));
4061                        JS_DefineProperty(
4062                            cx,
4063                            err_obj.handle().into(),
4064                            c"code".as_ptr(),
4065                            code_val.handle().into(),
4066                            JSPROP_ENUMERATE as u32,
4067                        );
4068                    }
4069                    rooted!(&in(cx_ref_err) let err_val = mozjs::jsval::ObjectValue(err_obj.get()));
4070                    let err_args = HandleValueArray {
4071                        length_: 1,
4072                        elements_: &err_val.get() as *const JSVal,
4073                    };
4074                    let global = CurrentGlobalOrNull(cx);
4075                    if !global.is_null() {
4076                        rooted!(&in(cx_ref_err) let global_rooted = global);
4077                        let mut rval = UndefinedValue();
4078                        JS_CallFunctionValue(
4079                            cx,
4080                            global_rooted.handle().into(),
4081                            cb_val.handle().into(),
4082                            &err_args,
4083                            MutableHandle::<Value> {
4084                                _phantom_0: ::std::marker::PhantomData,
4085                                ptr: &mut rval,
4086                            },
4087                        );
4088                        JS_ClearPendingException(cx);
4089                    }
4090                }
4091                args.rval().set(UndefinedValue());
4092                true
4093            } else {
4094                throw_fs_error(cx, "mkdir", &path, &e)
4095            }
4096        }
4097    }
4098}
4099
4100#[allow(unsafe_op_in_unsafe_fn)]
4101unsafe extern "C" fn fs_append_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4102    let args = CallArgs::from_vp(vp, argc);
4103    let path = match get_path_arg(cx, &args, 0) {
4104        ::std::result::Result::Ok(p) => p,
4105        ::std::result::Result::Err(b) => return b,
4106    };
4107    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
4108        let c_msg = ZBox::from_bytes(e.as_bytes());
4109        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
4110        return false;
4111    }
4112    let data_val = if argc > 1 {
4113        *args.get(1).ptr
4114    } else {
4115        UndefinedValue()
4116    };
4117    let data = if data_val.is_string() {
4118        let s = data_val.to_string();
4119        if !s.is_null() {
4120            crate::jsstr_to_rust_string(cx, s).into_bytes()
4121        } else {
4122            Vec::new()
4123        }
4124    } else if data_val.is_object() {
4125        crate::node_crypto::extract_buffer_bytes(cx, data_val)
4126    } else {
4127        Vec::new()
4128    };
4129
4130    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
4131        spawn_fs_async(cx, "appendFile", path.clone(), callback, None, move || {
4132            ::std::fs::OpenOptions::new()
4133                .create(true)
4134                .append(true)
4135                .open(&path)
4136                .and_then(|mut f| ::std::io::Write::write_all(&mut f, &data))
4137                .map(|_| FsAsyncResult::OkVoid)
4138        });
4139        args.rval().set(UndefinedValue());
4140        return true;
4141    }
4142
4143    match ::std::fs::OpenOptions::new()
4144        .create(true)
4145        .append(true)
4146        .open(&path)
4147    {
4148        ::std::result::Result::Ok(mut file) => {
4149            match ::std::io::Write::write_all(&mut file, &data) {
4150                ::std::result::Result::Ok(()) => {
4151                    args.rval().set(UndefinedValue());
4152                    true
4153                }
4154                ::std::result::Result::Err(e) => throw_fs_error(cx, "appendFile", &path, &e),
4155            }
4156        }
4157        ::std::result::Result::Err(e) => throw_fs_error(cx, "appendFile", &path, &e),
4158    }
4159}
4160
4161#[allow(unsafe_op_in_unsafe_fn)]
4162unsafe extern "C" fn fs_access(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4163    let args = CallArgs::from_vp(vp, argc);
4164    let path = match get_path_arg(cx, &args, 0) {
4165        ::std::result::Result::Ok(p) => p,
4166        ::std::result::Result::Err(b) => return b,
4167    };
4168
4169    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
4170        spawn_fs_async(cx, "access", path.clone(), callback, None, move || {
4171            fs::metadata(&path).map(|_| FsAsyncResult::OkVoid)
4172        });
4173        args.rval().set(UndefinedValue());
4174        return true;
4175    }
4176
4177    match fs::metadata(&path) {
4178        ::std::result::Result::Ok(_) => {
4179            args.rval().set(UndefinedValue());
4180            true
4181        }
4182        ::std::result::Result::Err(e) => throw_fs_error(cx, "access", &path, &e),
4183    }
4184}
4185
4186#[allow(unsafe_op_in_unsafe_fn)]
4187unsafe extern "C" fn fs_chmod(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4188    let args = CallArgs::from_vp(vp, argc);
4189    let path = match get_path_arg(cx, &args, 0) {
4190        ::std::result::Result::Ok(p) => p,
4191        ::std::result::Result::Err(b) => return b,
4192    };
4193    let mode_val = if argc > 1 {
4194        *args.get(1).ptr
4195    } else {
4196        UndefinedValue()
4197    };
4198    let mode = if mode_val.is_int32() {
4199        mode_val.to_int32() as u32
4200    } else if mode_val.is_double() {
4201        mode_val.to_double() as u32
4202    } else {
4203        0o644
4204    };
4205
4206    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
4207        spawn_fs_async(cx, "chmod", path.clone(), callback, None, move || {
4208            #[cfg(unix)]
4209            {
4210                use ::std::os::unix::fs::PermissionsExt;
4211                fs::set_permissions(&path, fs::Permissions::from_mode(mode))
4212                    .map(|_| FsAsyncResult::OkVoid)
4213            }
4214            #[cfg(not(unix))]
4215            {
4216                fs::set_permissions(&path, fs::Permissions::new()).map(|_| FsAsyncResult::OkVoid)
4217            }
4218        });
4219        args.rval().set(UndefinedValue());
4220        return true;
4221    }
4222
4223    #[cfg(unix)]
4224    let result = {
4225        use ::std::os::unix::fs::PermissionsExt;
4226        fs::set_permissions(&path, fs::Permissions::from_mode(mode))
4227    };
4228    #[cfg(not(unix))]
4229    let result = { fs::set_permissions(&path, fs::Permissions::new()) };
4230    match result {
4231        ::std::result::Result::Ok(()) => {
4232            args.rval().set(UndefinedValue());
4233            true
4234        }
4235        ::std::result::Result::Err(e) => throw_fs_error(cx, "chmod", &path, &e),
4236    }
4237}
4238
4239#[allow(unsafe_op_in_unsafe_fn)]
4240unsafe extern "C" fn fs_chown(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4241    let args = CallArgs::from_vp(vp, argc);
4242    let path = match get_path_arg(cx, &args, 0) {
4243        ::std::result::Result::Ok(p) => p,
4244        ::std::result::Result::Err(b) => return b,
4245    };
4246    let uid = if argc > 1 {
4247        let v = *args.get(1).ptr;
4248        if v.is_int32() {
4249            v.to_int32() as u32
4250        } else if v.is_double() {
4251            v.to_double() as u32
4252        } else {
4253            0
4254        }
4255    } else {
4256        0
4257    };
4258    let gid = if argc > 2 {
4259        let v = *args.get(2).ptr;
4260        if v.is_int32() {
4261            v.to_int32() as u32
4262        } else if v.is_double() {
4263            v.to_double() as u32
4264        } else {
4265            0
4266        }
4267    } else {
4268        0
4269    };
4270
4271    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 3) {
4272        spawn_fs_async(cx, "chown", path.clone(), callback, None, move || {
4273            #[cfg(unix)]
4274            {
4275                ::std::os::unix::fs::chown(&path, Some(uid), Some(gid))
4276                    .map(|_| FsAsyncResult::OkVoid)
4277            }
4278            #[cfg(not(unix))]
4279            {
4280                Ok(FsAsyncResult::OkVoid)
4281            }
4282        });
4283        args.rval().set(UndefinedValue());
4284        return true;
4285    }
4286
4287    #[cfg(unix)]
4288    match ::std::os::unix::fs::chown(&path, Some(uid), Some(gid)) {
4289        ::std::result::Result::Ok(()) => {
4290            args.rval().set(UndefinedValue());
4291            true
4292        }
4293        ::std::result::Result::Err(e) => throw_fs_error(cx, "chown", &path, &e),
4294    }
4295    #[cfg(not(unix))]
4296    {
4297        args.rval().set(UndefinedValue());
4298        true
4299    }
4300}
4301
4302#[allow(unsafe_op_in_unsafe_fn)]
4303unsafe extern "C" fn fs_close(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4304    let args = CallArgs::from_vp(vp, argc);
4305    let fd_val = if argc > 0 {
4306        *args.get(0).ptr
4307    } else {
4308        UndefinedValue()
4309    };
4310    let fd = if fd_val.is_int32() {
4311        fd_val.to_int32()
4312    } else {
4313        -1
4314    };
4315
4316    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
4317        spawn_fs_async(
4318            cx,
4319            "close",
4320            format!("fd:{}", fd),
4321            callback,
4322            None,
4323            move || {
4324                #[cfg(unix)]
4325                {
4326                    let rv = unsafe { libc::close(fd) };
4327                    if rv == 0 {
4328                        Ok(FsAsyncResult::OkVoid)
4329                    } else {
4330                        Err(::std::io::Error::last_os_error())
4331                    }
4332                }
4333                #[cfg(not(unix))]
4334                {
4335                    Ok(FsAsyncResult::OkVoid)
4336                }
4337            },
4338        );
4339        args.rval().set(UndefinedValue());
4340        return true;
4341    }
4342
4343    #[cfg(unix)]
4344    {
4345        let rv = unsafe { libc::close(fd) };
4346        if rv == 0 {
4347            args.rval().set(UndefinedValue());
4348            true
4349        } else {
4350            throw_fs_error(
4351                cx,
4352                "close",
4353                &format!("fd:{}", fd),
4354                &::std::io::Error::last_os_error(),
4355            )
4356        }
4357    }
4358    #[cfg(not(unix))]
4359    {
4360        args.rval().set(UndefinedValue());
4361        true
4362    }
4363}
4364
4365#[allow(unsafe_op_in_unsafe_fn)]
4366unsafe extern "C" fn fs_copy_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4367    let args = CallArgs::from_vp(vp, argc);
4368    let from = match get_path_arg(cx, &args, 0) {
4369        ::std::result::Result::Ok(p) => p,
4370        ::std::result::Result::Err(b) => return b,
4371    };
4372    let to = match get_path_arg(cx, &args, 1) {
4373        ::std::result::Result::Ok(p) => p,
4374        ::std::result::Result::Err(b) => return b,
4375    };
4376
4377    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
4378        spawn_fs_async(cx, "copyFile", from.clone(), callback, None, move || {
4379            fs::copy(&from, &to).map(|_| FsAsyncResult::OkVoid)
4380        });
4381        args.rval().set(UndefinedValue());
4382        return true;
4383    }
4384
4385    match fs::copy(&from, &to) {
4386        ::std::result::Result::Ok(_) => {
4387            args.rval().set(UndefinedValue());
4388            true
4389        }
4390        ::std::result::Result::Err(e) => throw_fs_error(cx, "copyFile", &from, &e),
4391    }
4392}
4393
4394#[allow(unsafe_op_in_unsafe_fn)]
4395unsafe extern "C" fn fs_exists(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4396    let args = CallArgs::from_vp(vp, argc);
4397    let path = match get_path_arg(cx, &args, 0) {
4398        ::std::result::Result::Ok(p) => p,
4399        ::std::result::Result::Err(b) => return b,
4400    };
4401
4402    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
4403        spawn_fs_async(cx, "exists", path.clone(), callback, None, move || {
4404            Ok(FsAsyncResult::OkBool(Path::new(&path).exists()))
4405        });
4406        args.rval().set(UndefinedValue());
4407        return true;
4408    }
4409
4410    args.rval()
4411        .set(mozjs::jsval::BooleanValue(Path::new(&path).exists()));
4412    true
4413}
4414
4415#[allow(unsafe_op_in_unsafe_fn)]
4416unsafe extern "C" fn fs_fchmod(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4417    let args = CallArgs::from_vp(vp, argc);
4418    let fd_val = if argc > 0 {
4419        *args.get(0).ptr
4420    } else {
4421        UndefinedValue()
4422    };
4423    let fd = if fd_val.is_int32() {
4424        fd_val.to_int32()
4425    } else {
4426        -1
4427    };
4428    let mode_val = if argc > 1 {
4429        *args.get(1).ptr
4430    } else {
4431        UndefinedValue()
4432    };
4433    let mode = if mode_val.is_int32() {
4434        mode_val.to_int32() as u32
4435    } else {
4436        0o644
4437    };
4438
4439    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
4440        spawn_fs_async(
4441            cx,
4442            "fchmod",
4443            format!("fd:{}", fd),
4444            callback,
4445            None,
4446            move || {
4447                #[cfg(unix)]
4448                {
4449                    let rv = unsafe { libc::fchmod(fd, mode) };
4450                    if rv == 0 {
4451                        Ok(FsAsyncResult::OkVoid)
4452                    } else {
4453                        Err(::std::io::Error::last_os_error())
4454                    }
4455                }
4456                #[cfg(not(unix))]
4457                {
4458                    Ok(FsAsyncResult::OkVoid)
4459                }
4460            },
4461        );
4462        args.rval().set(UndefinedValue());
4463        return true;
4464    }
4465
4466    #[cfg(unix)]
4467    {
4468        let rv = unsafe { libc::fchmod(fd, mode) };
4469        if rv == 0 {
4470            args.rval().set(UndefinedValue());
4471            true
4472        } else {
4473            throw_fs_error(
4474                cx,
4475                "fchmod",
4476                &format!("fd:{}", fd),
4477                &::std::io::Error::last_os_error(),
4478            )
4479        }
4480    }
4481    #[cfg(not(unix))]
4482    {
4483        args.rval().set(UndefinedValue());
4484        true
4485    }
4486}
4487
4488#[allow(unsafe_op_in_unsafe_fn)]
4489unsafe extern "C" fn fs_fchown(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4490    let args = CallArgs::from_vp(vp, argc);
4491    let fd_val = if argc > 0 {
4492        *args.get(0).ptr
4493    } else {
4494        UndefinedValue()
4495    };
4496    let fd = if fd_val.is_int32() {
4497        fd_val.to_int32()
4498    } else {
4499        -1
4500    };
4501    let uid = if argc > 1 {
4502        let v = *args.get(1).ptr;
4503        if v.is_int32() { v.to_int32() as u32 } else { 0 }
4504    } else {
4505        0
4506    };
4507    let gid = if argc > 2 {
4508        let v = *args.get(2).ptr;
4509        if v.is_int32() { v.to_int32() as u32 } else { 0 }
4510    } else {
4511        0
4512    };
4513
4514    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 3) {
4515        spawn_fs_async(
4516            cx,
4517            "fchown",
4518            format!("fd:{}", fd),
4519            callback,
4520            None,
4521            move || {
4522                #[cfg(unix)]
4523                {
4524                    let rv = unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) };
4525                    if rv == 0 {
4526                        Ok(FsAsyncResult::OkVoid)
4527                    } else {
4528                        Err(::std::io::Error::last_os_error())
4529                    }
4530                }
4531                #[cfg(not(unix))]
4532                {
4533                    Ok(FsAsyncResult::OkVoid)
4534                }
4535            },
4536        );
4537        args.rval().set(UndefinedValue());
4538        return true;
4539    }
4540
4541    #[cfg(unix)]
4542    {
4543        let rv = unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) };
4544        if rv == 0 {
4545            args.rval().set(UndefinedValue());
4546            true
4547        } else {
4548            throw_fs_error(
4549                cx,
4550                "fchown",
4551                &format!("fd:{}", fd),
4552                &::std::io::Error::last_os_error(),
4553            )
4554        }
4555    }
4556    #[cfg(not(unix))]
4557    {
4558        args.rval().set(UndefinedValue());
4559        true
4560    }
4561}
4562
4563#[allow(unsafe_op_in_unsafe_fn)]
4564unsafe extern "C" fn fs_fdatasync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4565    let args = CallArgs::from_vp(vp, argc);
4566    let fd_val = if argc > 0 {
4567        *args.get(0).ptr
4568    } else {
4569        UndefinedValue()
4570    };
4571    let fd = if fd_val.is_int32() {
4572        fd_val.to_int32()
4573    } else {
4574        -1
4575    };
4576
4577    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
4578        spawn_fs_async(
4579            cx,
4580            "fdatasync",
4581            format!("fd:{}", fd),
4582            callback,
4583            None,
4584            move || {
4585                #[cfg(unix)]
4586                {
4587                    let rv = unsafe { libc::fdatasync(fd) };
4588                    if rv == 0 {
4589                        Ok(FsAsyncResult::OkVoid)
4590                    } else {
4591                        Err(::std::io::Error::last_os_error())
4592                    }
4593                }
4594                #[cfg(not(unix))]
4595                {
4596                    Ok(FsAsyncResult::OkVoid)
4597                }
4598            },
4599        );
4600        args.rval().set(UndefinedValue());
4601        return true;
4602    }
4603
4604    #[cfg(unix)]
4605    {
4606        let rv = unsafe { libc::fdatasync(fd) };
4607        if rv == 0 {
4608            args.rval().set(UndefinedValue());
4609            true
4610        } else {
4611            throw_fs_error(
4612                cx,
4613                "fdatasync",
4614                &format!("fd:{}", fd),
4615                &::std::io::Error::last_os_error(),
4616            )
4617        }
4618    }
4619    #[cfg(not(unix))]
4620    {
4621        args.rval().set(UndefinedValue());
4622        true
4623    }
4624}
4625
4626#[allow(unsafe_op_in_unsafe_fn)]
4627unsafe extern "C" fn fs_fstat(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4628    let args = CallArgs::from_vp(vp, argc);
4629    let fd_val = if argc > 0 {
4630        *args.get(0).ptr
4631    } else {
4632        UndefinedValue()
4633    };
4634    let fd = if fd_val.is_int32() {
4635        fd_val.to_int32()
4636    } else {
4637        -1
4638    };
4639
4640    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
4641        spawn_fs_async(
4642            cx,
4643            "fstat",
4644            format!("fd:{}", fd),
4645            callback,
4646            None,
4647            move || {
4648                #[cfg(unix)]
4649                {
4650                    let mut stat_buf: libc::stat = ::std::mem::zeroed();
4651                    let rv = unsafe { libc::fstat(fd, &mut stat_buf) };
4652                    if rv == 0 {
4653                        Ok(FsAsyncResult::OkStat(posix_stat_from_libc(&stat_buf)))
4654                    } else {
4655                        Err(::std::io::Error::last_os_error())
4656                    }
4657                }
4658                #[cfg(not(unix))]
4659                {
4660                    Ok(FsAsyncResult::OkVoid)
4661                }
4662            },
4663        );
4664        args.rval().set(UndefinedValue());
4665        return true;
4666    }
4667
4668    #[cfg(unix)]
4669    {
4670        let mut stat_buf: libc::stat = ::std::mem::zeroed();
4671        let rv = unsafe { libc::fstat(fd, &mut stat_buf) };
4672        if rv == 0 {
4673            let posix = posix_stat_from_libc(&stat_buf);
4674            let stats = create_stats_object(cx, &posix);
4675            args.rval().set(mozjs::jsval::ObjectValue(stats));
4676            true
4677        } else {
4678            throw_fs_error(
4679                cx,
4680                "fstat",
4681                &format!("fd:{}", fd),
4682                &::std::io::Error::last_os_error(),
4683            )
4684        }
4685    }
4686    #[cfg(not(unix))]
4687    {
4688        args.rval().set(UndefinedValue());
4689        true
4690    }
4691}
4692
4693#[allow(unsafe_op_in_unsafe_fn)]
4694unsafe extern "C" fn fs_fsync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4695    let args = CallArgs::from_vp(vp, argc);
4696    let fd_val = if argc > 0 {
4697        *args.get(0).ptr
4698    } else {
4699        UndefinedValue()
4700    };
4701    let fd = if fd_val.is_int32() {
4702        fd_val.to_int32()
4703    } else {
4704        -1
4705    };
4706
4707    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
4708        spawn_fs_async(
4709            cx,
4710            "fsync",
4711            format!("fd:{}", fd),
4712            callback,
4713            None,
4714            move || {
4715                #[cfg(unix)]
4716                {
4717                    let rv = unsafe { libc::fsync(fd) };
4718                    if rv == 0 {
4719                        Ok(FsAsyncResult::OkVoid)
4720                    } else {
4721                        Err(::std::io::Error::last_os_error())
4722                    }
4723                }
4724                #[cfg(not(unix))]
4725                {
4726                    Ok(FsAsyncResult::OkVoid)
4727                }
4728            },
4729        );
4730        args.rval().set(UndefinedValue());
4731        return true;
4732    }
4733
4734    #[cfg(unix)]
4735    {
4736        let rv = unsafe { libc::fsync(fd) };
4737        if rv == 0 {
4738            args.rval().set(UndefinedValue());
4739            true
4740        } else {
4741            throw_fs_error(
4742                cx,
4743                "fsync",
4744                &format!("fd:{}", fd),
4745                &::std::io::Error::last_os_error(),
4746            )
4747        }
4748    }
4749    #[cfg(not(unix))]
4750    {
4751        args.rval().set(UndefinedValue());
4752        true
4753    }
4754}
4755
4756#[allow(unsafe_op_in_unsafe_fn)]
4757unsafe extern "C" fn fs_ftruncate(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4758    let args = CallArgs::from_vp(vp, argc);
4759    let fd_val = if argc > 0 {
4760        *args.get(0).ptr
4761    } else {
4762        UndefinedValue()
4763    };
4764    let fd = if fd_val.is_int32() {
4765        fd_val.to_int32()
4766    } else {
4767        -1
4768    };
4769    let len_val = if argc > 1 {
4770        *args.get(1).ptr
4771    } else {
4772        UndefinedValue()
4773    };
4774    let len = if len_val.is_int32() {
4775        len_val.to_int32() as i64
4776    } else if len_val.is_double() {
4777        len_val.to_double() as i64
4778    } else {
4779        0
4780    };
4781
4782    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
4783        spawn_fs_async(
4784            cx,
4785            "ftruncate",
4786            format!("fd:{}", fd),
4787            callback,
4788            None,
4789            move || {
4790                #[cfg(unix)]
4791                {
4792                    let rv = unsafe { libc::ftruncate(fd, len) };
4793                    if rv == 0 {
4794                        Ok(FsAsyncResult::OkVoid)
4795                    } else {
4796                        Err(::std::io::Error::last_os_error())
4797                    }
4798                }
4799                #[cfg(not(unix))]
4800                {
4801                    Ok(FsAsyncResult::OkVoid)
4802                }
4803            },
4804        );
4805        args.rval().set(UndefinedValue());
4806        return true;
4807    }
4808
4809    #[cfg(unix)]
4810    {
4811        let rv = unsafe { libc::ftruncate(fd, len) };
4812        if rv == 0 {
4813            args.rval().set(UndefinedValue());
4814            true
4815        } else {
4816            throw_fs_error(
4817                cx,
4818                "ftruncate",
4819                &format!("fd:{}", fd),
4820                &::std::io::Error::last_os_error(),
4821            )
4822        }
4823    }
4824    #[cfg(not(unix))]
4825    {
4826        args.rval().set(UndefinedValue());
4827        true
4828    }
4829}
4830
4831#[allow(unsafe_op_in_unsafe_fn)]
4832unsafe extern "C" fn fs_futimes(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4833    let args = CallArgs::from_vp(vp, argc);
4834    let fd_val = if argc > 0 {
4835        *args.get(0).ptr
4836    } else {
4837        UndefinedValue()
4838    };
4839    let fd = if fd_val.is_int32() {
4840        fd_val.to_int32()
4841    } else {
4842        -1
4843    };
4844    let atime_val = if argc > 1 {
4845        *args.get(1).ptr
4846    } else {
4847        UndefinedValue()
4848    };
4849    let mtime_val = if argc > 2 {
4850        *args.get(2).ptr
4851    } else {
4852        UndefinedValue()
4853    };
4854    let atime = if atime_val.is_double() {
4855        atime_val.to_double()
4856    } else if atime_val.is_int32() {
4857        atime_val.to_int32() as f64
4858    } else {
4859        0.0
4860    };
4861    let mtime = if mtime_val.is_double() {
4862        mtime_val.to_double()
4863    } else if mtime_val.is_int32() {
4864        mtime_val.to_int32() as f64
4865    } else {
4866        0.0
4867    };
4868
4869    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 3) {
4870        spawn_fs_async(
4871            cx,
4872            "futimes",
4873            format!("fd:{}", fd),
4874            callback,
4875            None,
4876            move || {
4877                #[cfg(unix)]
4878                {
4879                    let tv = [
4880                        libc::timeval {
4881                            tv_sec: atime as i64,
4882                            tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
4883                        },
4884                        libc::timeval {
4885                            tv_sec: mtime as i64,
4886                            tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
4887                        },
4888                    ];
4889                    let rv = unsafe { libc::futimes(fd, tv.as_ptr()) };
4890                    if rv == 0 {
4891                        Ok(FsAsyncResult::OkVoid)
4892                    } else {
4893                        Err(::std::io::Error::last_os_error())
4894                    }
4895                }
4896                #[cfg(not(unix))]
4897                {
4898                    Ok(FsAsyncResult::OkVoid)
4899                }
4900            },
4901        );
4902        args.rval().set(UndefinedValue());
4903        return true;
4904    }
4905
4906    #[cfg(unix)]
4907    {
4908        let tv = [
4909            libc::timeval {
4910                tv_sec: atime as i64,
4911                tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
4912            },
4913            libc::timeval {
4914                tv_sec: mtime as i64,
4915                tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
4916            },
4917        ];
4918        let rv = unsafe { libc::futimes(fd, tv.as_ptr()) };
4919        if rv == 0 {
4920            args.rval().set(UndefinedValue());
4921            true
4922        } else {
4923            throw_fs_error(
4924                cx,
4925                "futimes",
4926                &format!("fd:{}", fd),
4927                &::std::io::Error::last_os_error(),
4928            )
4929        }
4930    }
4931    #[cfg(not(unix))]
4932    {
4933        args.rval().set(UndefinedValue());
4934        true
4935    }
4936}
4937
4938#[allow(unsafe_op_in_unsafe_fn)]
4939unsafe extern "C" fn fs_lchown(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4940    let args = CallArgs::from_vp(vp, argc);
4941    let path = match get_path_arg(cx, &args, 0) {
4942        ::std::result::Result::Ok(p) => p,
4943        ::std::result::Result::Err(b) => return b,
4944    };
4945    let uid = if argc > 1 {
4946        let v = *args.get(1).ptr;
4947        if v.is_int32() { v.to_int32() as u32 } else { 0 }
4948    } else {
4949        0
4950    };
4951    let gid = if argc > 2 {
4952        let v = *args.get(2).ptr;
4953        if v.is_int32() { v.to_int32() as u32 } else { 0 }
4954    } else {
4955        0
4956    };
4957
4958    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 3) {
4959        spawn_fs_async(cx, "lchown", path.clone(), callback, None, move || {
4960            #[cfg(unix)]
4961            {
4962                let c_p = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
4963                let rv = unsafe { libc::lchown(c_p.as_ptr(), uid, gid) };
4964                if rv == 0 {
4965                    Ok(FsAsyncResult::OkVoid)
4966                } else {
4967                    Err(::std::io::Error::last_os_error())
4968                }
4969            }
4970            #[cfg(not(unix))]
4971            {
4972                Ok(FsAsyncResult::OkVoid)
4973            }
4974        });
4975        args.rval().set(UndefinedValue());
4976        return true;
4977    }
4978
4979    #[cfg(unix)]
4980    {
4981        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
4982        let rv = unsafe { libc::lchown(c_path.as_ptr(), uid, gid) };
4983        if rv == 0 {
4984            args.rval().set(UndefinedValue());
4985            true
4986        } else {
4987            throw_fs_error(cx, "lchown", &path, &::std::io::Error::last_os_error())
4988        }
4989    }
4990    #[cfg(not(unix))]
4991    {
4992        args.rval().set(UndefinedValue());
4993        true
4994    }
4995}
4996
4997#[allow(unsafe_op_in_unsafe_fn)]
4998unsafe extern "C" fn fs_link(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
4999    let args = CallArgs::from_vp(vp, argc);
5000    let from = match get_path_arg(cx, &args, 0) {
5001        ::std::result::Result::Ok(p) => p,
5002        ::std::result::Result::Err(b) => return b,
5003    };
5004    let to = match get_path_arg(cx, &args, 1) {
5005        ::std::result::Result::Ok(p) => p,
5006        ::std::result::Result::Err(b) => return b,
5007    };
5008
5009    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
5010        spawn_fs_async(cx, "link", from.clone(), callback, None, move || {
5011            fs::hard_link(&from, &to).map(|_| FsAsyncResult::OkVoid)
5012        });
5013        args.rval().set(UndefinedValue());
5014        return true;
5015    }
5016
5017    match fs::hard_link(&from, &to) {
5018        ::std::result::Result::Ok(()) => {
5019            args.rval().set(UndefinedValue());
5020            true
5021        }
5022        ::std::result::Result::Err(e) => throw_fs_error(cx, "link", &from, &e),
5023    }
5024}
5025
5026#[allow(unsafe_op_in_unsafe_fn)]
5027unsafe extern "C" fn fs_lstat(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5028    let args = CallArgs::from_vp(vp, argc);
5029    let path = match get_path_arg(cx, &args, 0) {
5030        ::std::result::Result::Ok(p) => p,
5031        ::std::result::Result::Err(b) => return b,
5032    };
5033
5034    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
5035        spawn_fs_async(cx, "lstat", path.clone(), callback, None, move || {
5036            fs::symlink_metadata(&path).map(|m| FsAsyncResult::OkStat(metadata_to_posix_stat(&m)))
5037        });
5038        args.rval().set(UndefinedValue());
5039        return true;
5040    }
5041
5042    match fs::symlink_metadata(&path) {
5043        ::std::result::Result::Ok(meta) => {
5044            let posix = metadata_to_posix_stat(&meta);
5045            let stats = create_stats_object(cx, &posix);
5046            args.rval().set(mozjs::jsval::ObjectValue(stats));
5047            true
5048        }
5049        ::std::result::Result::Err(e) => throw_fs_error(cx, "lstat", &path, &e),
5050    }
5051}
5052
5053#[allow(unsafe_op_in_unsafe_fn)]
5054unsafe extern "C" fn fs_lutimes(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5055    let args = CallArgs::from_vp(vp, argc);
5056    let path = match get_path_arg(cx, &args, 0) {
5057        ::std::result::Result::Ok(p) => p,
5058        ::std::result::Result::Err(b) => return b,
5059    };
5060    let atime_val = if argc > 1 {
5061        *args.get(1).ptr
5062    } else {
5063        UndefinedValue()
5064    };
5065    let mtime_val = if argc > 2 {
5066        *args.get(2).ptr
5067    } else {
5068        UndefinedValue()
5069    };
5070    let atime = if atime_val.is_double() {
5071        atime_val.to_double()
5072    } else if atime_val.is_int32() {
5073        atime_val.to_int32() as f64
5074    } else {
5075        0.0
5076    };
5077    let mtime = if mtime_val.is_double() {
5078        mtime_val.to_double()
5079    } else if mtime_val.is_int32() {
5080        mtime_val.to_int32() as f64
5081    } else {
5082        0.0
5083    };
5084
5085    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 3) {
5086        spawn_fs_async(cx, "lutimes", path.clone(), callback, None, move || {
5087            #[cfg(unix)]
5088            {
5089                let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
5090                let tv = [
5091                    libc::timeval {
5092                        tv_sec: atime as i64,
5093                        tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
5094                    },
5095                    libc::timeval {
5096                        tv_sec: mtime as i64,
5097                        tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
5098                    },
5099                ];
5100                let rv = unsafe { libc::lutimes(c_path.as_ptr(), tv.as_ptr()) };
5101                if rv == 0 {
5102                    Ok(FsAsyncResult::OkVoid)
5103                } else {
5104                    Err(::std::io::Error::last_os_error())
5105                }
5106            }
5107            #[cfg(not(unix))]
5108            {
5109                Ok(FsAsyncResult::OkVoid)
5110            }
5111        });
5112        args.rval().set(UndefinedValue());
5113        return true;
5114    }
5115
5116    #[cfg(unix)]
5117    {
5118        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
5119        let tv = [
5120            libc::timeval {
5121                tv_sec: atime as i64,
5122                tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
5123            },
5124            libc::timeval {
5125                tv_sec: mtime as i64,
5126                tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
5127            },
5128        ];
5129        let rv = unsafe { libc::lutimes(c_path.as_ptr(), tv.as_ptr()) };
5130        if rv == 0 {
5131            args.rval().set(UndefinedValue());
5132            true
5133        } else {
5134            throw_fs_error(cx, "lutimes", &path, &::std::io::Error::last_os_error())
5135        }
5136    }
5137    #[cfg(not(unix))]
5138    {
5139        args.rval().set(UndefinedValue());
5140        true
5141    }
5142}
5143
5144#[allow(unsafe_op_in_unsafe_fn)]
5145unsafe extern "C" fn fs_mkdtemp(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5146    let args = CallArgs::from_vp(vp, argc);
5147    let prefix = match get_path_arg(cx, &args, 0) {
5148        ::std::result::Result::Ok(p) => p,
5149        ::std::result::Result::Err(b) => return b,
5150    };
5151    let _encoding = get_encoding_opt(cx, &args, 1);
5152
5153    if let Some((callback, cb_encoding)) = extract_callback_and_encoding(cx, &args, 1) {
5154        spawn_fs_async(
5155            cx,
5156            "mkdtemp",
5157            prefix.clone(),
5158            callback,
5159            cb_encoding,
5160            move || mkdtemp_inner(&prefix).map(FsAsyncResult::OkString),
5161        );
5162        args.rval().set(UndefinedValue());
5163        return true;
5164    }
5165
5166    match mkdtemp_inner(&prefix) {
5167        ::std::result::Result::Ok(dir) => {
5168            let c_str = ZBox::from_bytes(dir.as_bytes());
5169            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
5170            if js_str.is_null() {
5171                args.rval().set(UndefinedValue());
5172            } else {
5173                args.rval().set(mozjs::jsval::StringValue(&*js_str));
5174            }
5175            true
5176        }
5177        ::std::result::Result::Err(e) => throw_fs_error(cx, "mkdtemp", &prefix, &e),
5178    }
5179}
5180
5181fn mkdtemp_inner(prefix: &str) -> ::std::io::Result<String> {
5182    // Node rejects an empty prefix with EINVAL (its snprintf builds a five-X
5183    // template here); otherwise we'd create a bare six-random-character
5184    // directory in the process cwd.
5185    if prefix.is_empty() {
5186        return Err(::std::io::Error::from_raw_os_error(libc::EINVAL));
5187    }
5188    let mut template = prefix.to_string();
5189    template.push_str("XXXXXX");
5190    let c_template = ::std::ffi::CString::new(template).map_err(|_| {
5191        ::std::io::Error::new(
5192            ::std::io::ErrorKind::InvalidInput,
5193            "prefix contains null byte",
5194        )
5195    })?;
5196    let c_ptr = c_template.into_raw();
5197    let result = unsafe { libc::mkdtemp(c_ptr) };
5198    if result.is_null() {
5199        let e = ::std::io::Error::last_os_error();
5200        unsafe {
5201            let _ = ::std::ffi::CString::from_raw(c_ptr);
5202        }
5203        return Err(e);
5204    }
5205    let result_cstr = unsafe { ::std::ffi::CString::from_raw(result) };
5206    result_cstr
5207        .into_string()
5208        .map_err(|e| ::std::io::Error::new(::std::io::ErrorKind::InvalidData, e))
5209}
5210
5211#[allow(unsafe_op_in_unsafe_fn)]
5212unsafe extern "C" fn fs_open(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5213    let args = CallArgs::from_vp(vp, argc);
5214    let path = match get_path_arg(cx, &args, 0) {
5215        ::std::result::Result::Ok(p) => p,
5216        ::std::result::Result::Err(b) => return b,
5217    };
5218    let flags_val = if argc > 1 {
5219        *args.get(1).ptr
5220    } else {
5221        UndefinedValue()
5222    };
5223    let flags = if flags_val.is_int32() {
5224        flags_val.to_int32()
5225    } else if flags_val.is_string() {
5226        let s = flags_val.to_string();
5227        if !s.is_null() {
5228            let rust_str = crate::jsstr_to_rust_string(cx, s);
5229            parse_open_flags(&rust_str)
5230        } else {
5231            0
5232        }
5233    } else {
5234        0
5235    };
5236    let mode_val = if argc > 2 {
5237        *args.get(2).ptr
5238    } else {
5239        UndefinedValue()
5240    };
5241    let mode = if mode_val.is_int32() {
5242        mode_val.to_int32() as u32
5243    } else {
5244        0o644
5245    };
5246
5247    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 3) {
5248        spawn_fs_async(cx, "open", path.clone(), callback, None, move || {
5249            #[cfg(unix)]
5250            {
5251                let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
5252                let fd = unsafe { libc::open(c_path.as_ptr(), flags, mode) };
5253                if fd >= 0 {
5254                    Ok(FsAsyncResult::OkOpen(fd))
5255                } else {
5256                    Err(::std::io::Error::last_os_error())
5257                }
5258            }
5259            #[cfg(not(unix))]
5260            {
5261                Ok(FsAsyncResult::OkOpen(0))
5262            }
5263        });
5264        args.rval().set(UndefinedValue());
5265        return true;
5266    }
5267
5268    #[cfg(unix)]
5269    {
5270        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
5271        let fd = unsafe { libc::open(c_path.as_ptr(), flags, mode) };
5272        if fd >= 0 {
5273            args.rval().set(mozjs::jsval::Int32Value(fd));
5274            true
5275        } else {
5276            throw_fs_error(cx, "open", &path, &::std::io::Error::last_os_error())
5277        }
5278    }
5279    #[cfg(not(unix))]
5280    {
5281        args.rval().set(mozjs::jsval::Int32Value(0));
5282        true
5283    }
5284}
5285
5286fn parse_open_flags(s: &str) -> i32 {
5287    let mut flags = 0;
5288    if s.contains('w') {
5289        flags |= libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC;
5290    }
5291    if s.contains('a') {
5292        flags |= libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND;
5293    }
5294    if s.contains('+') {
5295        flags = (flags & !(libc::O_WRONLY | libc::O_RDONLY)) | libc::O_RDWR;
5296    }
5297    flags
5298}
5299
5300#[allow(unsafe_op_in_unsafe_fn)]
5301unsafe extern "C" fn fs_opendir(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5302    let args = CallArgs::from_vp(vp, argc);
5303    let path = match get_path_arg(cx, &args, 0) {
5304        ::std::result::Result::Ok(p) => p,
5305        ::std::result::Result::Err(b) => return b,
5306    };
5307
5308    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
5309        // With callback: create Dir object and pass to callback
5310        let dir_obj = create_dir_object(cx, &path);
5311        let mut wrapped_cx_od =
5312            mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
5313        let cx_ref_od = &mut wrapped_cx_od;
5314        rooted!(&in(cx_ref_od) let cb = callback);
5315        rooted!(&in(cx_ref_od) let cb_val = mozjs::jsval::ObjectValue(cb.get()));
5316        rooted!(&in(cx_ref_od) let dir_val = mozjs::jsval::ObjectValue(dir_obj));
5317        let args_arr = [UndefinedValue(), dir_val.get()];
5318        let cb_args = HandleValueArray {
5319            length_: 2,
5320            elements_: args_arr.as_ptr(),
5321        };
5322        let global = CurrentGlobalOrNull(cx);
5323        if !global.is_null() {
5324            rooted!(&in(cx_ref_od) let global_rooted = global);
5325            let mut rval = UndefinedValue();
5326            JS_CallFunctionValue(
5327                cx,
5328                global_rooted.handle().into(),
5329                cb_val.handle().into(),
5330                &cb_args,
5331                MutableHandle::<Value> {
5332                    _phantom_0: ::std::marker::PhantomData,
5333                    ptr: &mut rval,
5334                },
5335            );
5336            JS_ClearPendingException(cx);
5337        }
5338        args.rval().set(UndefinedValue());
5339        return true;
5340    }
5341
5342    // No callback: return Dir object directly
5343    match fs::metadata(&path) {
5344        ::std::result::Result::Ok(meta) if meta.is_dir() => {
5345            let dir_obj = create_dir_object(cx, &path);
5346            args.rval().set(mozjs::jsval::ObjectValue(dir_obj));
5347            true
5348        }
5349        ::std::result::Result::Ok(_) => {
5350            JS_ReportErrorUTF8(cx, c"opendir: path is not a directory".as_ptr());
5351            false
5352        }
5353        ::std::result::Result::Err(e) => throw_fs_error(cx, "opendir", &path, &e),
5354    }
5355}
5356
5357#[allow(unsafe_op_in_unsafe_fn)]
5358unsafe extern "C" fn fs_read(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5359    let args = CallArgs::from_vp(vp, argc);
5360    let fd_val = if argc > 0 {
5361        *args.get(0).ptr
5362    } else {
5363        UndefinedValue()
5364    };
5365    let fd = if fd_val.is_int32() {
5366        fd_val.to_int32()
5367    } else {
5368        -1
5369    };
5370    let length = if argc > 3 {
5371        let v = *args.get(3).ptr;
5372        if v.is_int32() {
5373            v.to_int32() as usize
5374        } else {
5375            65536
5376        }
5377    } else {
5378        65536
5379    };
5380
5381    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 5) {
5382        spawn_fs_async(
5383            cx,
5384            "read",
5385            format!("fd:{}", fd),
5386            callback,
5387            None,
5388            move || {
5389                let mut buf = vec![0u8; length];
5390                #[cfg(unix)]
5391                {
5392                    let bytes_read = unsafe {
5393                        libc::read(fd, buf.as_mut_ptr() as *mut ::std::ffi::c_void, length)
5394                    };
5395                    if bytes_read >= 0 {
5396                        buf.truncate(bytes_read as usize);
5397                        Ok(FsAsyncResult::OkRead {
5398                            bytes_read: bytes_read as i32,
5399                            buffer: buf,
5400                        })
5401                    } else {
5402                        Err(::std::io::Error::last_os_error())
5403                    }
5404                }
5405                #[cfg(not(unix))]
5406                {
5407                    Ok(FsAsyncResult::OkRead {
5408                        bytes_read: 0,
5409                        buffer: buf,
5410                    })
5411                }
5412            },
5413        );
5414        args.rval().set(UndefinedValue());
5415        return true;
5416    }
5417
5418    let mut buf = vec![0u8; length];
5419    #[cfg(unix)]
5420    {
5421        let bytes_read =
5422            unsafe { libc::read(fd, buf.as_mut_ptr() as *mut ::std::ffi::c_void, length) };
5423        if bytes_read >= 0 {
5424            buf.truncate(bytes_read as usize);
5425            let buf_obj = crate::globals::create_buffer_object(cx, &buf);
5426            if buf_obj.is_null() {
5427                args.rval().set(UndefinedValue());
5428            } else {
5429                args.rval()
5430                    .set(mozjs::jsval::DoubleValue(bytes_read as f64));
5431            }
5432            true
5433        } else {
5434            throw_fs_error(
5435                cx,
5436                "read",
5437                &format!("fd:{}", fd),
5438                &::std::io::Error::last_os_error(),
5439            )
5440        }
5441    }
5442    #[cfg(not(unix))]
5443    {
5444        args.rval().set(UndefinedValue());
5445        true
5446    }
5447}
5448
5449#[allow(unsafe_op_in_unsafe_fn)]
5450unsafe extern "C" fn fs_readdir(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5451    let args = CallArgs::from_vp(vp, argc);
5452    let path = match get_path_arg(cx, &args, 0) {
5453        ::std::result::Result::Ok(p) => p,
5454        ::std::result::Result::Err(b) => return b,
5455    };
5456    let with_file_types = get_bool_option(cx, &args, 1, "withFileTypes");
5457
5458    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
5459        spawn_fs_async(cx, "readdir", path.clone(), callback, None, move || {
5460            fs::read_dir(&path).map(|entries| {
5461                let items: Vec<_> = entries.flatten().collect();
5462                if with_file_types {
5463                    let dirents: Vec<(String, bool)> = items
5464                        .iter()
5465                        .map(|e| {
5466                            (
5467                                e.file_name().to_string_lossy().into_owned(),
5468                                e.file_type().map(|ft| ft.is_dir()).unwrap_or(false),
5469                            )
5470                        })
5471                        .collect();
5472                    FsAsyncResult::OkDirents(dirents)
5473                } else {
5474                    let names: Vec<String> = items
5475                        .iter()
5476                        .map(|e| e.file_name().to_string_lossy().into_owned())
5477                        .collect();
5478                    FsAsyncResult::OkDirnames(names)
5479                }
5480            })
5481        });
5482        args.rval().set(UndefinedValue());
5483        return true;
5484    }
5485
5486    match fs::read_dir(&path) {
5487        ::std::result::Result::Ok(entries) => {
5488            let mut names: Vec<String> = Vec::new();
5489            let mut is_dirs: Vec<bool> = Vec::new();
5490            for entry in entries.flatten() {
5491                names.push(entry.file_name().to_string_lossy().into_owned());
5492                is_dirs.push(entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false));
5493            }
5494            let mut wrapped_cx = unsafe {
5495                mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx))
5496            };
5497            let cx_ref = &mut wrapped_cx;
5498            rooted!(&in(cx_ref) let arr = unsafe { w2::NewArrayObject1(cx_ref, names.len()) });
5499            if arr.get().is_null() {
5500                args.rval().set(UndefinedValue());
5501                return true;
5502            }
5503            for (i, name) in names.iter().enumerate() {
5504                if with_file_types {
5505                    let dirent = create_dirent(cx, name, is_dirs[i]);
5506                    if !dirent.is_null() {
5507                        rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(dirent));
5508                        unsafe {
5509                            JS_DefineElement(
5510                                cx,
5511                                arr.handle().into(),
5512                                i as u32,
5513                                val.handle().into(),
5514                                JSPROP_ENUMERATE as u32,
5515                            );
5516                        }
5517                    }
5518                } else {
5519                    let c_name = ZBox::from_bytes(name.as_bytes());
5520                    let js_str = unsafe { JS_NewStringCopyZ(cx, c_name.as_ptr()) };
5521                    if !js_str.is_null() {
5522                        rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
5523                        unsafe {
5524                            JS_DefineElement(
5525                                cx,
5526                                arr.handle().into(),
5527                                i as u32,
5528                                val.handle().into(),
5529                                JSPROP_ENUMERATE as u32,
5530                            );
5531                        }
5532                    }
5533                }
5534            }
5535            args.rval().set(mozjs::jsval::ObjectValue(arr.get()));
5536            true
5537        }
5538        ::std::result::Result::Err(e) => throw_fs_error(cx, "readdir", &path, &e),
5539    }
5540}
5541
5542#[allow(unsafe_op_in_unsafe_fn)]
5543unsafe extern "C" fn fs_readlink(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5544    let args = CallArgs::from_vp(vp, argc);
5545    let path = match get_path_arg(cx, &args, 0) {
5546        ::std::result::Result::Ok(p) => p,
5547        ::std::result::Result::Err(b) => return b,
5548    };
5549    let _encoding = get_encoding_opt(cx, &args, 1);
5550
5551    if let Some((callback, cb_encoding)) = extract_callback_and_encoding(cx, &args, 1) {
5552        spawn_fs_async(
5553            cx,
5554            "readlink",
5555            path.clone(),
5556            callback,
5557            cb_encoding,
5558            move || {
5559                fs::read_link(&path)
5560                    .map(|t| FsAsyncResult::OkString(t.to_string_lossy().into_owned()))
5561            },
5562        );
5563        args.rval().set(UndefinedValue());
5564        return true;
5565    }
5566
5567    match fs::read_link(&path) {
5568        ::std::result::Result::Ok(target) => {
5569            let s = target.to_string_lossy();
5570            let c_str = ZBox::from_bytes(s.as_bytes());
5571            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
5572            if js_str.is_null() {
5573                args.rval().set(UndefinedValue());
5574            } else {
5575                args.rval().set(mozjs::jsval::StringValue(&*js_str));
5576            }
5577            true
5578        }
5579        ::std::result::Result::Err(e) => throw_fs_error(cx, "readlink", &path, &e),
5580    }
5581}
5582
5583#[allow(unsafe_op_in_unsafe_fn)]
5584unsafe extern "C" fn fs_readv(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5585    fs_read(cx, argc, vp)
5586}
5587
5588#[allow(unsafe_op_in_unsafe_fn)]
5589unsafe extern "C" fn fs_realpath(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5590    let args = CallArgs::from_vp(vp, argc);
5591    let path = match get_path_arg(cx, &args, 0) {
5592        ::std::result::Result::Ok(p) => p,
5593        ::std::result::Result::Err(b) => return b,
5594    };
5595    let _encoding = get_encoding_opt(cx, &args, 1);
5596
5597    if let Some((callback, cb_encoding)) = extract_callback_and_encoding(cx, &args, 1) {
5598        spawn_fs_async(
5599            cx,
5600            "realpath",
5601            path.clone(),
5602            callback,
5603            cb_encoding,
5604            move || {
5605                fs::canonicalize(&path)
5606                    .map(|p| FsAsyncResult::OkString(p.to_string_lossy().into_owned()))
5607            },
5608        );
5609        args.rval().set(UndefinedValue());
5610        return true;
5611    }
5612
5613    match fs::canonicalize(&path) {
5614        ::std::result::Result::Ok(resolved) => {
5615            let s = resolved.to_string_lossy();
5616            let c_str = ZBox::from_bytes(s.as_bytes());
5617            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
5618            if js_str.is_null() {
5619                args.rval().set(UndefinedValue());
5620            } else {
5621                args.rval().set(mozjs::jsval::StringValue(&*js_str));
5622            }
5623            true
5624        }
5625        ::std::result::Result::Err(e) => throw_fs_error(cx, "realpath", &path, &e),
5626    }
5627}
5628
5629#[allow(unsafe_op_in_unsafe_fn)]
5630unsafe extern "C" fn fs_rename(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5631    let args = CallArgs::from_vp(vp, argc);
5632    let from = match get_path_arg(cx, &args, 0) {
5633        ::std::result::Result::Ok(p) => p,
5634        ::std::result::Result::Err(b) => return b,
5635    };
5636    let to = match get_path_arg(cx, &args, 1) {
5637        ::std::result::Result::Ok(p) => p,
5638        ::std::result::Result::Err(b) => return b,
5639    };
5640
5641    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
5642        spawn_fs_async(cx, "rename", from.clone(), callback, None, move || {
5643            fs::rename(&from, &to).map(|_| FsAsyncResult::OkVoid)
5644        });
5645        args.rval().set(UndefinedValue());
5646        return true;
5647    }
5648
5649    match fs::rename(&from, &to) {
5650        ::std::result::Result::Ok(()) => {
5651            args.rval().set(UndefinedValue());
5652            true
5653        }
5654        ::std::result::Result::Err(e) => throw_fs_error(cx, "rename", &from, &e),
5655    }
5656}
5657
5658#[allow(unsafe_op_in_unsafe_fn)]
5659unsafe extern "C" fn fs_rm(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5660    let args = CallArgs::from_vp(vp, argc);
5661    let path = match get_path_arg(cx, &args, 0) {
5662        ::std::result::Result::Ok(p) => p,
5663        ::std::result::Result::Err(b) => return b,
5664    };
5665    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
5666        let c_msg = ZBox::from_bytes(e.as_bytes());
5667        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
5668        return false;
5669    }
5670    let recursive = get_bool_option(cx, &args, 1, "recursive");
5671
5672    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
5673        spawn_fs_async(cx, "rm", path.clone(), callback, None, move || {
5674            if recursive {
5675                fs::remove_dir_all(&path)
5676            } else {
5677                fs::remove_file(&path)
5678            }
5679            .map(|_| FsAsyncResult::OkVoid)
5680        });
5681        args.rval().set(UndefinedValue());
5682        return true;
5683    }
5684
5685    let result = if recursive {
5686        fs::remove_dir_all(&path)
5687    } else {
5688        fs::remove_file(&path)
5689    };
5690    match result {
5691        ::std::result::Result::Ok(()) => {
5692            args.rval().set(UndefinedValue());
5693            true
5694        }
5695        ::std::result::Result::Err(e) => throw_fs_error(cx, "rm", &path, &e),
5696    }
5697}
5698
5699#[allow(unsafe_op_in_unsafe_fn)]
5700unsafe extern "C" fn fs_rmdir(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5701    let args = CallArgs::from_vp(vp, argc);
5702    let path = match get_path_arg(cx, &args, 0) {
5703        ::std::result::Result::Ok(p) => p,
5704        ::std::result::Result::Err(b) => return b,
5705    };
5706    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
5707        let c_msg = ZBox::from_bytes(e.as_bytes());
5708        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
5709        return false;
5710    }
5711
5712    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
5713        spawn_fs_async(cx, "rmdir", path.clone(), callback, None, move || {
5714            fs::remove_dir(&path).map(|_| FsAsyncResult::OkVoid)
5715        });
5716        args.rval().set(UndefinedValue());
5717        return true;
5718    }
5719
5720    match fs::remove_dir(&path) {
5721        ::std::result::Result::Ok(()) => {
5722            args.rval().set(UndefinedValue());
5723            true
5724        }
5725        ::std::result::Result::Err(e) => throw_fs_error(cx, "rmdir", &path, &e),
5726    }
5727}
5728
5729#[allow(unsafe_op_in_unsafe_fn)]
5730unsafe extern "C" fn fs_stat(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5731    let args = CallArgs::from_vp(vp, argc);
5732    let path = match get_path_arg(cx, &args, 0) {
5733        ::std::result::Result::Ok(p) => p,
5734        ::std::result::Result::Err(b) => return b,
5735    };
5736
5737    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
5738        spawn_fs_async(cx, "stat", path.clone(), callback, None, move || {
5739            bun_fs::metadata(&path).map(FsAsyncResult::OkStat)
5740        });
5741        args.rval().set(UndefinedValue());
5742        return true;
5743    }
5744
5745    match bun_fs::metadata(&path) {
5746        ::std::result::Result::Ok(meta) => {
5747            let stats = create_stats_object(cx, &meta);
5748            args.rval().set(mozjs::jsval::ObjectValue(stats));
5749            true
5750        }
5751        ::std::result::Result::Err(e) => throw_fs_error(cx, "stat", &path, &e),
5752    }
5753}
5754
5755#[allow(unsafe_op_in_unsafe_fn)]
5756unsafe extern "C" fn fs_symlink(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5757    let args = CallArgs::from_vp(vp, argc);
5758    let target = match get_path_arg(cx, &args, 0) {
5759        ::std::result::Result::Ok(p) => p,
5760        ::std::result::Result::Err(b) => return b,
5761    };
5762    let path = match get_path_arg(cx, &args, 1) {
5763        ::std::result::Result::Ok(p) => p,
5764        ::std::result::Result::Err(b) => return b,
5765    };
5766
5767    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
5768        spawn_fs_async(cx, "symlink", target.clone(), callback, None, move || {
5769            #[cfg(unix)]
5770            {
5771                ::std::os::unix::fs::symlink(&target, &path).map(|_| FsAsyncResult::OkVoid)
5772            }
5773            #[cfg(not(unix))]
5774            {
5775                fs::hard_link(&target, &path).map(|_| FsAsyncResult::OkVoid)
5776            }
5777        });
5778        args.rval().set(UndefinedValue());
5779        return true;
5780    }
5781
5782    #[cfg(unix)]
5783    let result = ::std::os::unix::fs::symlink(&target, &path);
5784    #[cfg(not(unix))]
5785    let result = fs::hard_link(&target, &path);
5786    match result {
5787        ::std::result::Result::Ok(()) => {
5788            args.rval().set(UndefinedValue());
5789            true
5790        }
5791        ::std::result::Result::Err(e) => throw_fs_error(cx, "symlink", &target, &e),
5792    }
5793}
5794
5795#[allow(unsafe_op_in_unsafe_fn)]
5796unsafe extern "C" fn fs_truncate(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5797    let args = CallArgs::from_vp(vp, argc);
5798    let path = match get_path_arg(cx, &args, 0) {
5799        ::std::result::Result::Ok(p) => p,
5800        ::std::result::Result::Err(b) => return b,
5801    };
5802    let len_val = if argc > 1 {
5803        *args.get(1).ptr
5804    } else {
5805        UndefinedValue()
5806    };
5807    let len = if len_val.is_int32() {
5808        len_val.to_int32() as i64
5809    } else if len_val.is_double() {
5810        len_val.to_double() as i64
5811    } else {
5812        0
5813    };
5814
5815    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
5816        spawn_fs_async(cx, "truncate", path.clone(), callback, None, move || {
5817            // write-open: ftruncate(2) rejects read-only fds (EINVAL)
5818            fs::OpenOptions::new()
5819                .write(true)
5820                .open(&path)
5821                .and_then(|f| f.set_len(len.max(0) as u64))
5822                .map(|_| FsAsyncResult::OkVoid)
5823        });
5824        args.rval().set(UndefinedValue());
5825        return true;
5826    }
5827
5828    match fs::OpenOptions::new()
5829        .write(true)
5830        .open(&path)
5831        .and_then(|f| f.set_len(len.max(0) as u64))
5832    {
5833        ::std::result::Result::Ok(()) => {
5834            args.rval().set(UndefinedValue());
5835            true
5836        }
5837        ::std::result::Result::Err(e) => throw_fs_error(cx, "truncate", &path, &e),
5838    }
5839}
5840
5841#[allow(unsafe_op_in_unsafe_fn)]
5842unsafe extern "C" fn fs_unlink(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5843    let args = CallArgs::from_vp(vp, argc);
5844    let path = match get_path_arg(cx, &args, 0) {
5845        ::std::result::Result::Ok(p) => p,
5846        ::std::result::Result::Err(b) => return b,
5847    };
5848    if let ::std::result::Result::Err(e) = crate::permission_bridge::check_fs_write(&path) {
5849        let c_msg = ZBox::from_bytes(e.as_bytes());
5850        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
5851        return false;
5852    }
5853
5854    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
5855        spawn_fs_async(cx, "unlink", path.clone(), callback, None, move || {
5856            fs::remove_file(&path).map(|_| FsAsyncResult::OkVoid)
5857        });
5858        args.rval().set(UndefinedValue());
5859        return true;
5860    }
5861
5862    match fs::remove_file(&path) {
5863        ::std::result::Result::Ok(()) => {
5864            args.rval().set(UndefinedValue());
5865            true
5866        }
5867        ::std::result::Result::Err(e) => throw_fs_error(cx, "unlink", &path, &e),
5868    }
5869}
5870
5871#[allow(unsafe_op_in_unsafe_fn)]
5872unsafe extern "C" fn fs_utimes(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5873    let args = CallArgs::from_vp(vp, argc);
5874    let path = match get_path_arg(cx, &args, 0) {
5875        ::std::result::Result::Ok(p) => p,
5876        ::std::result::Result::Err(b) => return b,
5877    };
5878    let atime_val = if argc > 1 {
5879        *args.get(1).ptr
5880    } else {
5881        UndefinedValue()
5882    };
5883    let mtime_val = if argc > 2 {
5884        *args.get(2).ptr
5885    } else {
5886        UndefinedValue()
5887    };
5888    let atime = if atime_val.is_double() {
5889        atime_val.to_double()
5890    } else if atime_val.is_int32() {
5891        atime_val.to_int32() as f64
5892    } else {
5893        0.0
5894    };
5895    let mtime = if mtime_val.is_double() {
5896        mtime_val.to_double()
5897    } else if mtime_val.is_int32() {
5898        mtime_val.to_int32() as f64
5899    } else {
5900        0.0
5901    };
5902
5903    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 3) {
5904        spawn_fs_async(cx, "utimes", path.clone(), callback, None, move || {
5905            #[cfg(unix)]
5906            {
5907                let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
5908                let tv = [
5909                    libc::timeval {
5910                        tv_sec: atime as i64,
5911                        tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
5912                    },
5913                    libc::timeval {
5914                        tv_sec: mtime as i64,
5915                        tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
5916                    },
5917                ];
5918                let rv = unsafe { libc::utimes(c_path.as_ptr(), tv.as_ptr()) };
5919                if rv == 0 {
5920                    Ok(FsAsyncResult::OkVoid)
5921                } else {
5922                    Err(::std::io::Error::last_os_error())
5923                }
5924            }
5925            #[cfg(not(unix))]
5926            {
5927                Ok(FsAsyncResult::OkVoid)
5928            }
5929        });
5930        args.rval().set(UndefinedValue());
5931        return true;
5932    }
5933
5934    #[cfg(unix)]
5935    {
5936        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
5937        let tv = [
5938            libc::timeval {
5939                tv_sec: atime as i64,
5940                tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
5941            },
5942            libc::timeval {
5943                tv_sec: mtime as i64,
5944                tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
5945            },
5946        ];
5947        let rv = unsafe { libc::utimes(c_path.as_ptr(), tv.as_ptr()) };
5948        if rv == 0 {
5949            args.rval().set(UndefinedValue());
5950            true
5951        } else {
5952            throw_fs_error(cx, "utimes", &path, &::std::io::Error::last_os_error())
5953        }
5954    }
5955    #[cfg(not(unix))]
5956    {
5957        args.rval().set(UndefinedValue());
5958        true
5959    }
5960}
5961
5962#[allow(unsafe_op_in_unsafe_fn)]
5963unsafe extern "C" fn fs_write(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
5964    let args = CallArgs::from_vp(vp, argc);
5965    let fd_val = if argc > 0 {
5966        *args.get(0).ptr
5967    } else {
5968        UndefinedValue()
5969    };
5970    let fd = if fd_val.is_int32() {
5971        fd_val.to_int32()
5972    } else {
5973        -1
5974    };
5975    let data_val = if argc > 1 {
5976        *args.get(1).ptr
5977    } else {
5978        UndefinedValue()
5979    };
5980    let bytes = if data_val.is_string() {
5981        let s = data_val.to_string();
5982        if !s.is_null() {
5983            crate::jsstr_to_rust_string(cx, s).into_bytes()
5984        } else {
5985            Vec::new()
5986        }
5987    } else if data_val.is_object() {
5988        crate::node_crypto::extract_buffer_bytes(cx, data_val)
5989    } else {
5990        Vec::new()
5991    };
5992
5993    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 5) {
5994        spawn_fs_async(
5995            cx,
5996            "write",
5997            format!("fd:{}", fd),
5998            callback,
5999            None,
6000            move || {
6001                #[cfg(unix)]
6002                {
6003                    let written = unsafe {
6004                        libc::write(fd, bytes.as_ptr() as *const ::std::ffi::c_void, bytes.len())
6005                    };
6006                    if written >= 0 {
6007                        Ok(FsAsyncResult::OkWrite(written as i32))
6008                    } else {
6009                        Err(::std::io::Error::last_os_error())
6010                    }
6011                }
6012                #[cfg(not(unix))]
6013                {
6014                    Ok(FsAsyncResult::OkWrite(0))
6015                }
6016            },
6017        );
6018        args.rval().set(UndefinedValue());
6019        return true;
6020    }
6021
6022    #[cfg(unix)]
6023    {
6024        let written =
6025            unsafe { libc::write(fd, bytes.as_ptr() as *const ::std::ffi::c_void, bytes.len()) };
6026        if written >= 0 {
6027            args.rval().set(mozjs::jsval::DoubleValue(written as f64));
6028            true
6029        } else {
6030            throw_fs_error(
6031                cx,
6032                "write",
6033                &format!("fd:{}", fd),
6034                &::std::io::Error::last_os_error(),
6035            )
6036        }
6037    }
6038    #[cfg(not(unix))]
6039    {
6040        args.rval().set(UndefinedValue());
6041        true
6042    }
6043}
6044
6045#[allow(unsafe_op_in_unsafe_fn)]
6046unsafe extern "C" fn fs_writev(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6047    fs_write(cx, argc, vp)
6048}
6049
6050// --- fs.promises ---
6051
6052macro_rules! promise_simple_op {
6053    ($fn_name:ident, $op:expr, $op_name:expr) => {
6054        #[allow(unsafe_op_in_unsafe_fn)]
6055        unsafe extern "C" fn $fn_name(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6056            let args = CallArgs::from_vp(vp, argc);
6057            let path = match get_path_arg(cx, &args, 0) { ::std::result::Result::Ok(p) => p, ::std::result::Result::Err(b) => return b };
6058            let mut wrapped_cx = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6059            let cx_ref = &mut wrapped_cx;
6060            rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6061            if promise.get().is_null() { args.rval().set(UndefinedValue()); return false; }
6062            match $op(&path) {
6063                ::std::result::Result::Ok(()) => {
6064                    resolve_undefined(cx, promise.get());
6065                }
6066                ::std::result::Result::Err(e) => {
6067                    reject_with_error(cx, promise.get(), &format!("{} '{}': {}", $op_name, path, e));
6068                }
6069            }
6070            args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6071            true
6072        }
6073    };
6074}
6075
6076promise_simple_op!(fs_promises_mkdir, |p: &str| fs::create_dir_all(p), "mkdir");
6077promise_simple_op!(fs_promises_unlink, |p: &str| fs::remove_file(p), "unlink");
6078
6079#[allow(unsafe_op_in_unsafe_fn)]
6080unsafe extern "C" fn fs_promises_rename(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6081    let args = CallArgs::from_vp(vp, argc);
6082    let from = match get_path_arg(cx, &args, 0) {
6083        ::std::result::Result::Ok(p) => p,
6084        ::std::result::Result::Err(b) => return b,
6085    };
6086    let to = match get_path_arg(cx, &args, 1) {
6087        ::std::result::Result::Ok(p) => p,
6088        ::std::result::Result::Err(b) => return b,
6089    };
6090    let mut wrapped_cx =
6091        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6092    let cx_ref = &mut wrapped_cx;
6093    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6094    if promise.get().is_null() {
6095        args.rval().set(UndefinedValue());
6096        return false;
6097    }
6098    match fs::rename(&from, &to) {
6099        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6100        ::std::result::Result::Err(e) => {
6101            reject_with_error(cx, promise.get(), &format!("rename '{}': {}", from, e))
6102        }
6103    }
6104    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6105    true
6106}
6107
6108#[allow(unsafe_op_in_unsafe_fn)]
6109unsafe extern "C" fn fs_promises_copy_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6110    let args = CallArgs::from_vp(vp, argc);
6111    let from = match get_path_arg(cx, &args, 0) {
6112        ::std::result::Result::Ok(p) => p,
6113        ::std::result::Result::Err(b) => return b,
6114    };
6115    let to = match get_path_arg(cx, &args, 1) {
6116        ::std::result::Result::Ok(p) => p,
6117        ::std::result::Result::Err(b) => return b,
6118    };
6119    let mut wrapped_cx =
6120        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6121    let cx_ref = &mut wrapped_cx;
6122    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6123    if promise.get().is_null() {
6124        args.rval().set(UndefinedValue());
6125        return false;
6126    }
6127    match fs::copy(&from, &to) {
6128        ::std::result::Result::Ok(_) => resolve_undefined(cx, promise.get()),
6129        ::std::result::Result::Err(e) => {
6130            reject_with_error(cx, promise.get(), &format!("copyFile '{}': {}", from, e))
6131        }
6132    }
6133    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6134    true
6135}
6136
6137#[allow(unsafe_op_in_unsafe_fn)]
6138unsafe extern "C" fn fs_promises_read_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6139    let args = CallArgs::from_vp(vp, argc);
6140    let path = match get_path_arg(cx, &args, 0) {
6141        ::std::result::Result::Ok(p) => p,
6142        ::std::result::Result::Err(b) => return b,
6143    };
6144    let encoding = get_encoding_opt(cx, &args, 1);
6145    let mut wrapped_cx =
6146        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6147    let cx_ref = &mut wrapped_cx;
6148    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6149    if promise.get().is_null() {
6150        args.rval().set(UndefinedValue());
6151        return false;
6152    }
6153
6154    match bun_fs::read(&path) {
6155        ::std::result::Result::Ok(data) => {
6156            let val = string_or_buffer(cx, &data, encoding.as_deref());
6157            rooted!(&in(cx_ref) let val_rooted = val);
6158            unsafe {
6159                mozjs_sys::jsapi::JS::ResolvePromise(
6160                    cx,
6161                    promise.handle().into(),
6162                    val_rooted.handle().into(),
6163                );
6164            }
6165        }
6166        ::std::result::Result::Err(e) => {
6167            reject_with_error(cx, promise.get(), &format!("readFile '{}': {}", path, e))
6168        }
6169    }
6170    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6171    true
6172}
6173
6174#[allow(unsafe_op_in_unsafe_fn)]
6175unsafe extern "C" fn fs_promises_write_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6176    let args = CallArgs::from_vp(vp, argc);
6177    let path = match get_path_arg(cx, &args, 0) {
6178        ::std::result::Result::Ok(p) => p,
6179        ::std::result::Result::Err(b) => return b,
6180    };
6181    let data_val = if argc > 1 {
6182        *args.get(1).ptr
6183    } else {
6184        UndefinedValue()
6185    };
6186    let bytes = if data_val.is_string() {
6187        let s = data_val.to_string();
6188        if !s.is_null() {
6189            crate::jsstr_to_rust_string(cx, s).into_bytes()
6190        } else {
6191            Vec::new()
6192        }
6193    } else {
6194        Vec::new()
6195    };
6196    let mut wrapped_cx =
6197        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6198    let cx_ref = &mut wrapped_cx;
6199    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6200    if promise.get().is_null() {
6201        args.rval().set(UndefinedValue());
6202        return false;
6203    }
6204    match bun_fs::write(&path, &bytes) {
6205        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6206        ::std::result::Result::Err(e) => {
6207            reject_with_error(cx, promise.get(), &format!("writeFile '{}': {}", path, e))
6208        }
6209    }
6210    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6211    true
6212}
6213
6214#[allow(unsafe_op_in_unsafe_fn)]
6215unsafe extern "C" fn fs_promises_stat(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6216    let args = CallArgs::from_vp(vp, argc);
6217    let path = match get_path_arg(cx, &args, 0) {
6218        ::std::result::Result::Ok(p) => p,
6219        ::std::result::Result::Err(b) => return b,
6220    };
6221    let mut wrapped_cx =
6222        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6223    let cx_ref = &mut wrapped_cx;
6224    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6225    if promise.get().is_null() {
6226        args.rval().set(UndefinedValue());
6227        return false;
6228    }
6229    match bun_fs::metadata(&path) {
6230        ::std::result::Result::Ok(meta) => {
6231            let stats = create_stats_object(cx, &meta);
6232            rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(stats));
6233            unsafe {
6234                mozjs_sys::jsapi::JS::ResolvePromise(
6235                    cx,
6236                    promise.handle().into(),
6237                    val.handle().into(),
6238                );
6239            }
6240        }
6241        ::std::result::Result::Err(e) => {
6242            reject_with_error(cx, promise.get(), &format!("stat '{}': {}", path, e))
6243        }
6244    }
6245    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6246    true
6247}
6248
6249#[allow(unsafe_op_in_unsafe_fn)]
6250unsafe extern "C" fn fs_promises_readdir(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6251    let args = CallArgs::from_vp(vp, argc);
6252    let path = match get_path_arg(cx, &args, 0) {
6253        ::std::result::Result::Ok(p) => p,
6254        ::std::result::Result::Err(b) => return b,
6255    };
6256
6257    let mut wrapped_cx =
6258        unsafe { mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx)) };
6259    let cx_ref = &mut wrapped_cx;
6260
6261    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6262    if promise.get().is_null() {
6263        args.rval().set(UndefinedValue());
6264        return false;
6265    }
6266
6267    match fs::read_dir(&path) {
6268        ::std::result::Result::Ok(entries) => {
6269            let names: Vec<::std::string::String> = entries
6270                .flatten()
6271                .map(|e| e.file_name().to_string_lossy().into_owned())
6272                .collect();
6273            rooted!(&in(cx_ref) let arr = unsafe { w2::NewArrayObject1(cx_ref, names.len()) });
6274            if arr.get().is_null() {
6275                args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6276                return true;
6277            }
6278            for (idx, name) in names.iter().enumerate() {
6279                let c_name = ZBox::from_bytes(name.as_bytes());
6280                let js_str = unsafe { JS_NewStringCopyZ(cx, c_name.as_ptr()) };
6281                if !js_str.is_null() {
6282                    rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
6283                    unsafe {
6284                        JS_DefineElement(
6285                            cx,
6286                            arr.handle().into(),
6287                            idx as u32,
6288                            val.handle().into(),
6289                            JSPROP_ENUMERATE as u32,
6290                        );
6291                    }
6292                }
6293            }
6294            rooted!(&in(cx_ref) let arr_val = mozjs::jsval::ObjectValue(arr.get()));
6295            unsafe {
6296                mozjs_sys::jsapi::JS::ResolvePromise(
6297                    cx,
6298                    promise.handle().into(),
6299                    arr_val.handle().into(),
6300                );
6301            }
6302        }
6303        ::std::result::Result::Err(e) => {
6304            reject_with_error(cx, promise.get(), &format!("readdir '{}': {}", path, e))
6305        }
6306    }
6307    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6308    true
6309}
6310
6311#[allow(unsafe_op_in_unsafe_fn)]
6312unsafe extern "C" fn fs_promises_lstat(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6313    let args = CallArgs::from_vp(vp, argc);
6314    let path = match get_path_arg(cx, &args, 0) {
6315        ::std::result::Result::Ok(p) => p,
6316        ::std::result::Result::Err(b) => return b,
6317    };
6318    let mut wrapped_cx =
6319        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6320    let cx_ref = &mut wrapped_cx;
6321    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6322    if promise.get().is_null() {
6323        args.rval().set(UndefinedValue());
6324        return false;
6325    }
6326    match fs::symlink_metadata(&path) {
6327        ::std::result::Result::Ok(meta) => {
6328            let posix = metadata_to_posix_stat(&meta);
6329            let stats = create_stats_object(cx, &posix);
6330            rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(stats));
6331            unsafe {
6332                mozjs_sys::jsapi::JS::ResolvePromise(
6333                    cx,
6334                    promise.handle().into(),
6335                    val.handle().into(),
6336                );
6337            }
6338        }
6339        ::std::result::Result::Err(e) => {
6340            reject_with_error(cx, promise.get(), &format!("lstat '{}': {}", path, e))
6341        }
6342    }
6343    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6344    true
6345}
6346
6347#[allow(unsafe_op_in_unsafe_fn)]
6348unsafe extern "C" fn fs_promises_append_file(
6349    cx: *mut JSContext,
6350    argc: u32,
6351    vp: *mut JSVal,
6352) -> bool {
6353    let args = CallArgs::from_vp(vp, argc);
6354    let path = match get_path_arg(cx, &args, 0) {
6355        ::std::result::Result::Ok(p) => p,
6356        ::std::result::Result::Err(b) => return b,
6357    };
6358    let data_val = if argc > 1 {
6359        *args.get(1).ptr
6360    } else {
6361        UndefinedValue()
6362    };
6363    let data = if data_val.is_string() {
6364        let s = data_val.to_string();
6365        if !s.is_null() {
6366            crate::jsstr_to_rust_string(cx, s).into_bytes()
6367        } else {
6368            Vec::new()
6369        }
6370    } else if data_val.is_object() {
6371        crate::node_crypto::extract_buffer_bytes(cx, data_val)
6372    } else {
6373        Vec::new()
6374    };
6375    let mut wrapped_cx =
6376        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6377    let cx_ref = &mut wrapped_cx;
6378    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6379    if promise.get().is_null() {
6380        args.rval().set(UndefinedValue());
6381        return false;
6382    }
6383    match ::std::fs::OpenOptions::new()
6384        .create(true)
6385        .append(true)
6386        .open(&path)
6387        .and_then(|mut f| ::std::io::Write::write_all(&mut f, &data))
6388    {
6389        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6390        ::std::result::Result::Err(e) => {
6391            reject_with_error(cx, promise.get(), &format!("appendFile '{}': {}", path, e))
6392        }
6393    }
6394    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6395    true
6396}
6397
6398#[allow(unsafe_op_in_unsafe_fn)]
6399unsafe extern "C" fn fs_promises_chmod(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6400    let args = CallArgs::from_vp(vp, argc);
6401    let path = match get_path_arg(cx, &args, 0) {
6402        ::std::result::Result::Ok(p) => p,
6403        ::std::result::Result::Err(b) => return b,
6404    };
6405    let mode_val = if argc > 1 {
6406        *args.get(1).ptr
6407    } else {
6408        UndefinedValue()
6409    };
6410    let mode = if mode_val.is_int32() {
6411        mode_val.to_int32() as u32
6412    } else if mode_val.is_double() {
6413        mode_val.to_double() as u32
6414    } else {
6415        0o644
6416    };
6417    let mut wrapped_cx =
6418        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6419    let cx_ref = &mut wrapped_cx;
6420    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6421    if promise.get().is_null() {
6422        args.rval().set(UndefinedValue());
6423        return false;
6424    }
6425    #[cfg(unix)]
6426    let result = {
6427        use ::std::os::unix::fs::PermissionsExt;
6428        fs::set_permissions(&path, fs::Permissions::from_mode(mode))
6429    };
6430    #[cfg(not(unix))]
6431    let result = fs::set_permissions(&path, fs::Permissions::new());
6432    match result {
6433        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6434        ::std::result::Result::Err(e) => {
6435            reject_with_error(cx, promise.get(), &format!("chmod '{}': {}", path, e))
6436        }
6437    }
6438    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6439    true
6440}
6441
6442#[allow(unsafe_op_in_unsafe_fn)]
6443unsafe extern "C" fn fs_promises_chown(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6444    let args = CallArgs::from_vp(vp, argc);
6445    let path = match get_path_arg(cx, &args, 0) {
6446        ::std::result::Result::Ok(p) => p,
6447        ::std::result::Result::Err(b) => return b,
6448    };
6449    let uid = if argc > 1 {
6450        let v = *args.get(1).ptr;
6451        if v.is_int32() { v.to_int32() as u32 } else { 0 }
6452    } else {
6453        0
6454    };
6455    let gid = if argc > 2 {
6456        let v = *args.get(2).ptr;
6457        if v.is_int32() { v.to_int32() as u32 } else { 0 }
6458    } else {
6459        0
6460    };
6461    let mut wrapped_cx =
6462        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6463    let cx_ref = &mut wrapped_cx;
6464    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6465    if promise.get().is_null() {
6466        args.rval().set(UndefinedValue());
6467        return false;
6468    }
6469    #[cfg(unix)]
6470    let result = ::std::os::unix::fs::chown(&path, Some(uid), Some(gid));
6471    #[cfg(not(unix))]
6472    let result: ::std::io::Result<()> = Ok(());
6473    match result {
6474        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6475        ::std::result::Result::Err(e) => {
6476            reject_with_error(cx, promise.get(), &format!("chown '{}': {}", path, e))
6477        }
6478    }
6479    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6480    true
6481}
6482
6483#[allow(unsafe_op_in_unsafe_fn)]
6484unsafe extern "C" fn fs_promises_access(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6485    let args = CallArgs::from_vp(vp, argc);
6486    let path = match get_path_arg(cx, &args, 0) {
6487        ::std::result::Result::Ok(p) => p,
6488        ::std::result::Result::Err(b) => return b,
6489    };
6490    let mut wrapped_cx =
6491        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6492    let cx_ref = &mut wrapped_cx;
6493    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6494    if promise.get().is_null() {
6495        args.rval().set(UndefinedValue());
6496        return false;
6497    }
6498    match fs::metadata(&path) {
6499        ::std::result::Result::Ok(_) => resolve_undefined(cx, promise.get()),
6500        ::std::result::Result::Err(e) => {
6501            reject_with_error(cx, promise.get(), &format!("access '{}': {}", path, e))
6502        }
6503    }
6504    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6505    true
6506}
6507
6508#[allow(unsafe_op_in_unsafe_fn)]
6509unsafe extern "C" fn fs_promises_rm(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6510    let args = CallArgs::from_vp(vp, argc);
6511    let path = match get_path_arg(cx, &args, 0) {
6512        ::std::result::Result::Ok(p) => p,
6513        ::std::result::Result::Err(b) => return b,
6514    };
6515    let recursive = get_bool_option(cx, &args, 1, "recursive");
6516    let mut wrapped_cx =
6517        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6518    let cx_ref = &mut wrapped_cx;
6519    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6520    if promise.get().is_null() {
6521        args.rval().set(UndefinedValue());
6522        return false;
6523    }
6524    let result = if recursive {
6525        fs::remove_dir_all(&path)
6526    } else {
6527        fs::remove_file(&path)
6528    };
6529    match result {
6530        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6531        ::std::result::Result::Err(e) => {
6532            reject_with_error(cx, promise.get(), &format!("rm '{}': {}", path, e))
6533        }
6534    }
6535    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6536    true
6537}
6538
6539#[allow(unsafe_op_in_unsafe_fn)]
6540unsafe extern "C" fn fs_promises_rmdir(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6541    let args = CallArgs::from_vp(vp, argc);
6542    let path = match get_path_arg(cx, &args, 0) {
6543        ::std::result::Result::Ok(p) => p,
6544        ::std::result::Result::Err(b) => return b,
6545    };
6546    let mut wrapped_cx =
6547        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6548    let cx_ref = &mut wrapped_cx;
6549    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6550    if promise.get().is_null() {
6551        args.rval().set(UndefinedValue());
6552        return false;
6553    }
6554    match fs::remove_dir(&path) {
6555        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6556        ::std::result::Result::Err(e) => {
6557            reject_with_error(cx, promise.get(), &format!("rmdir '{}': {}", path, e))
6558        }
6559    }
6560    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6561    true
6562}
6563
6564#[allow(unsafe_op_in_unsafe_fn)]
6565unsafe extern "C" fn fs_promises_realpath(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6566    let args = CallArgs::from_vp(vp, argc);
6567    let path = match get_path_arg(cx, &args, 0) {
6568        ::std::result::Result::Ok(p) => p,
6569        ::std::result::Result::Err(b) => return b,
6570    };
6571    let mut wrapped_cx =
6572        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6573    let cx_ref = &mut wrapped_cx;
6574    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6575    if promise.get().is_null() {
6576        args.rval().set(UndefinedValue());
6577        return false;
6578    }
6579    match fs::canonicalize(&path) {
6580        ::std::result::Result::Ok(resolved) => {
6581            let s = resolved.to_string_lossy();
6582            let c_str = ZBox::from_bytes(s.as_bytes());
6583            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
6584            if !js_str.is_null() {
6585                rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
6586                unsafe {
6587                    mozjs_sys::jsapi::JS::ResolvePromise(
6588                        cx,
6589                        promise.handle().into(),
6590                        val.handle().into(),
6591                    );
6592                }
6593            } else {
6594                resolve_undefined(cx, promise.get());
6595            }
6596        }
6597        ::std::result::Result::Err(e) => {
6598            reject_with_error(cx, promise.get(), &format!("realpath '{}': {}", path, e))
6599        }
6600    }
6601    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6602    true
6603}
6604
6605#[allow(unsafe_op_in_unsafe_fn)]
6606unsafe extern "C" fn fs_promises_readlink(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6607    let args = CallArgs::from_vp(vp, argc);
6608    let path = match get_path_arg(cx, &args, 0) {
6609        ::std::result::Result::Ok(p) => p,
6610        ::std::result::Result::Err(b) => return b,
6611    };
6612    let mut wrapped_cx =
6613        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6614    let cx_ref = &mut wrapped_cx;
6615    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6616    if promise.get().is_null() {
6617        args.rval().set(UndefinedValue());
6618        return false;
6619    }
6620    match fs::read_link(&path) {
6621        ::std::result::Result::Ok(target) => {
6622            let s = target.to_string_lossy();
6623            let c_str = ZBox::from_bytes(s.as_bytes());
6624            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
6625            if !js_str.is_null() {
6626                rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
6627                unsafe {
6628                    mozjs_sys::jsapi::JS::ResolvePromise(
6629                        cx,
6630                        promise.handle().into(),
6631                        val.handle().into(),
6632                    );
6633                }
6634            } else {
6635                resolve_undefined(cx, promise.get());
6636            }
6637        }
6638        ::std::result::Result::Err(e) => {
6639            reject_with_error(cx, promise.get(), &format!("readlink '{}': {}", path, e))
6640        }
6641    }
6642    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6643    true
6644}
6645
6646#[allow(unsafe_op_in_unsafe_fn)]
6647unsafe extern "C" fn fs_promises_symlink(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6648    let args = CallArgs::from_vp(vp, argc);
6649    let target = match get_path_arg(cx, &args, 0) {
6650        ::std::result::Result::Ok(p) => p,
6651        ::std::result::Result::Err(b) => return b,
6652    };
6653    let path = match get_path_arg(cx, &args, 1) {
6654        ::std::result::Result::Ok(p) => p,
6655        ::std::result::Result::Err(b) => return b,
6656    };
6657    let mut wrapped_cx =
6658        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6659    let cx_ref = &mut wrapped_cx;
6660    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6661    if promise.get().is_null() {
6662        args.rval().set(UndefinedValue());
6663        return false;
6664    }
6665    #[cfg(unix)]
6666    let result = ::std::os::unix::fs::symlink(&target, &path);
6667    #[cfg(not(unix))]
6668    let result = fs::hard_link(&target, &path);
6669    match result {
6670        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6671        ::std::result::Result::Err(e) => {
6672            reject_with_error(cx, promise.get(), &format!("symlink '{}': {}", target, e))
6673        }
6674    }
6675    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6676    true
6677}
6678
6679#[allow(unsafe_op_in_unsafe_fn)]
6680unsafe extern "C" fn fs_promises_link(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6681    let args = CallArgs::from_vp(vp, argc);
6682    let from = match get_path_arg(cx, &args, 0) {
6683        ::std::result::Result::Ok(p) => p,
6684        ::std::result::Result::Err(b) => return b,
6685    };
6686    let to = match get_path_arg(cx, &args, 1) {
6687        ::std::result::Result::Ok(p) => p,
6688        ::std::result::Result::Err(b) => return b,
6689    };
6690    let mut wrapped_cx =
6691        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6692    let cx_ref = &mut wrapped_cx;
6693    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6694    if promise.get().is_null() {
6695        args.rval().set(UndefinedValue());
6696        return false;
6697    }
6698    match fs::hard_link(&from, &to) {
6699        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6700        ::std::result::Result::Err(e) => {
6701            reject_with_error(cx, promise.get(), &format!("link '{}': {}", from, e))
6702        }
6703    }
6704    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6705    true
6706}
6707
6708#[allow(unsafe_op_in_unsafe_fn)]
6709unsafe extern "C" fn fs_promises_truncate(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6710    let args = CallArgs::from_vp(vp, argc);
6711    let path = match get_path_arg(cx, &args, 0) {
6712        ::std::result::Result::Ok(p) => p,
6713        ::std::result::Result::Err(b) => return b,
6714    };
6715    let len_val = if argc > 1 {
6716        *args.get(1).ptr
6717    } else {
6718        UndefinedValue()
6719    };
6720    let len = if len_val.is_int32() {
6721        len_val.to_int32() as u64
6722    } else if len_val.is_double() {
6723        len_val.to_double() as u64
6724    } else {
6725        0
6726    };
6727    let mut wrapped_cx =
6728        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6729    let cx_ref = &mut wrapped_cx;
6730    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6731    if promise.get().is_null() {
6732        args.rval().set(UndefinedValue());
6733        return false;
6734    }
6735    // write-open: ftruncate(2) rejects read-only fds (EINVAL)
6736    match fs::OpenOptions::new().write(true).open(&path).and_then(|f| f.set_len(len)) {
6737        ::std::result::Result::Ok(()) => resolve_undefined(cx, promise.get()),
6738        ::std::result::Result::Err(e) => {
6739            reject_with_error(cx, promise.get(), &format!("truncate '{}': {}", path, e))
6740        }
6741    }
6742    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6743    true
6744}
6745
6746#[allow(unsafe_op_in_unsafe_fn)]
6747unsafe extern "C" fn fs_promises_utimes(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6748    let args = CallArgs::from_vp(vp, argc);
6749    let path = match get_path_arg(cx, &args, 0) {
6750        ::std::result::Result::Ok(p) => p,
6751        ::std::result::Result::Err(b) => return b,
6752    };
6753    let atime_val = if argc > 1 {
6754        *args.get(1).ptr
6755    } else {
6756        UndefinedValue()
6757    };
6758    let mtime_val = if argc > 2 {
6759        *args.get(2).ptr
6760    } else {
6761        UndefinedValue()
6762    };
6763    let atime = if atime_val.is_double() {
6764        atime_val.to_double()
6765    } else if atime_val.is_int32() {
6766        atime_val.to_int32() as f64
6767    } else {
6768        0.0
6769    };
6770    let mtime = if mtime_val.is_double() {
6771        mtime_val.to_double()
6772    } else if mtime_val.is_int32() {
6773        mtime_val.to_int32() as f64
6774    } else {
6775        0.0
6776    };
6777    let mut wrapped_cx =
6778        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6779    let cx_ref = &mut wrapped_cx;
6780    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6781    if promise.get().is_null() {
6782        args.rval().set(UndefinedValue());
6783        return false;
6784    }
6785    #[cfg(unix)]
6786    {
6787        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
6788        let tv = [
6789            libc::timeval {
6790                tv_sec: atime as i64,
6791                tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
6792            },
6793            libc::timeval {
6794                tv_sec: mtime as i64,
6795                tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
6796            },
6797        ];
6798        let rv = unsafe { libc::utimes(c_path.as_ptr(), tv.as_ptr()) };
6799        if rv == 0 {
6800            resolve_undefined(cx, promise.get());
6801        } else {
6802            reject_with_error(
6803                cx,
6804                promise.get(),
6805                &format!("utimes '{}': {}", path, ::std::io::Error::last_os_error()),
6806            );
6807        }
6808    }
6809    #[cfg(not(unix))]
6810    {
6811        resolve_undefined(cx, promise.get());
6812    }
6813    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6814    true
6815}
6816
6817#[allow(unsafe_op_in_unsafe_fn)]
6818unsafe extern "C" fn fs_promises_mkdtemp(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6819    let args = CallArgs::from_vp(vp, argc);
6820    let prefix = match get_path_arg(cx, &args, 0) {
6821        ::std::result::Result::Ok(p) => p,
6822        ::std::result::Result::Err(b) => return b,
6823    };
6824    let mut wrapped_cx =
6825        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6826    let cx_ref = &mut wrapped_cx;
6827    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6828    if promise.get().is_null() {
6829        args.rval().set(UndefinedValue());
6830        return false;
6831    }
6832    match mkdtemp_inner(&prefix) {
6833        ::std::result::Result::Ok(dir) => {
6834            let c_str = ZBox::from_bytes(dir.as_bytes());
6835            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
6836            if !js_str.is_null() {
6837                rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
6838                unsafe {
6839                    mozjs_sys::jsapi::JS::ResolvePromise(
6840                        cx,
6841                        promise.handle().into(),
6842                        val.handle().into(),
6843                    );
6844                }
6845            } else {
6846                resolve_undefined(cx, promise.get());
6847            }
6848        }
6849        ::std::result::Result::Err(e) => {
6850            reject_with_error(cx, promise.get(), &format!("mkdtemp '{}': {}", prefix, e))
6851        }
6852    }
6853    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6854    true
6855}
6856
6857#[allow(unsafe_op_in_unsafe_fn)]
6858unsafe extern "C" fn fs_promises_open(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6859    let args = CallArgs::from_vp(vp, argc);
6860    let path = match get_path_arg(cx, &args, 0) {
6861        ::std::result::Result::Ok(p) => p,
6862        ::std::result::Result::Err(b) => return b,
6863    };
6864    let flags_val = if argc > 1 {
6865        *args.get(1).ptr
6866    } else {
6867        UndefinedValue()
6868    };
6869    let flags = if flags_val.is_int32() {
6870        flags_val.to_int32()
6871    } else if flags_val.is_string() {
6872        let s = flags_val.to_string();
6873        if !s.is_null() {
6874            parse_open_flags(&crate::jsstr_to_rust_string(cx, s))
6875        } else {
6876            0
6877        }
6878    } else {
6879        0
6880    };
6881    let mode_val = if argc > 2 {
6882        *args.get(2).ptr
6883    } else {
6884        UndefinedValue()
6885    };
6886    let mode = if mode_val.is_int32() {
6887        mode_val.to_int32() as u32
6888    } else {
6889        0o644
6890    };
6891    let mut wrapped_cx =
6892        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6893    let cx_ref = &mut wrapped_cx;
6894    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6895    if promise.get().is_null() {
6896        args.rval().set(UndefinedValue());
6897        return false;
6898    }
6899    #[cfg(unix)]
6900    {
6901        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
6902        let fd = unsafe { libc::open(c_path.as_ptr(), flags, mode) };
6903        if fd >= 0 {
6904            // Wrap the fd in a FileHandle instance before resolving the promise.
6905            let fh = create_filehandle_object(cx, fd);
6906            if !fh.is_null() {
6907                rooted!(&in(cx_ref) let fh_val = mozjs::jsval::ObjectValue(fh));
6908                unsafe {
6909                    mozjs_sys::jsapi::JS::ResolvePromise(
6910                        cx,
6911                        promise.handle().into(),
6912                        fh_val.handle().into(),
6913                    );
6914                }
6915            } else {
6916                // Fallback: resolve with raw fd
6917                rooted!(&in(cx_ref) let val = mozjs::jsval::Int32Value(fd));
6918                unsafe {
6919                    mozjs_sys::jsapi::JS::ResolvePromise(
6920                        cx,
6921                        promise.handle().into(),
6922                        val.handle().into(),
6923                    );
6924                }
6925            }
6926        } else {
6927            reject_with_error(
6928                cx,
6929                promise.get(),
6930                &format!("open '{}': {}", path, ::std::io::Error::last_os_error()),
6931            );
6932        }
6933    }
6934    #[cfg(not(unix))]
6935    {
6936        resolve_undefined(cx, promise.get());
6937    }
6938    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
6939    true
6940}
6941
6942#[allow(unsafe_op_in_unsafe_fn)]
6943unsafe extern "C" fn fs_promises_read(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
6944    let args = CallArgs::from_vp(vp, argc);
6945    let fd_val = if argc > 0 {
6946        *args.get(0).ptr
6947    } else {
6948        UndefinedValue()
6949    };
6950    let fd = if fd_val.is_int32() {
6951        fd_val.to_int32()
6952    } else {
6953        -1
6954    };
6955    let length = if argc > 3 {
6956        let v = *args.get(3).ptr;
6957        if v.is_int32() {
6958            v.to_int32() as usize
6959        } else {
6960            65536
6961        }
6962    } else {
6963        65536
6964    };
6965    let mut wrapped_cx =
6966        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
6967    let cx_ref = &mut wrapped_cx;
6968    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
6969    if promise.get().is_null() {
6970        args.rval().set(UndefinedValue());
6971        return false;
6972    }
6973    let mut buf = vec![0u8; length];
6974    #[cfg(unix)]
6975    {
6976        let bytes_read =
6977            unsafe { libc::read(fd, buf.as_mut_ptr() as *mut ::std::ffi::c_void, length) };
6978        if bytes_read >= 0 {
6979            buf.truncate(bytes_read as usize);
6980            let buf_obj = crate::globals::create_buffer_object(cx, &buf);
6981            if !buf_obj.is_null() {
6982                rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(buf_obj));
6983                unsafe {
6984                    mozjs_sys::jsapi::JS::ResolvePromise(
6985                        cx,
6986                        promise.handle().into(),
6987                        val.handle().into(),
6988                    );
6989                }
6990            } else {
6991                resolve_undefined(cx, promise.get());
6992            }
6993        } else {
6994            reject_with_error(
6995                cx,
6996                promise.get(),
6997                &format!("read fd:{}: {}", fd, ::std::io::Error::last_os_error()),
6998            );
6999        }
7000    }
7001    #[cfg(not(unix))]
7002    {
7003        resolve_undefined(cx, promise.get());
7004    }
7005    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
7006    true
7007}
7008
7009#[allow(unsafe_op_in_unsafe_fn)]
7010unsafe extern "C" fn fs_promises_write(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7011    let args = CallArgs::from_vp(vp, argc);
7012    let fd_val = if argc > 0 {
7013        *args.get(0).ptr
7014    } else {
7015        UndefinedValue()
7016    };
7017    let fd = if fd_val.is_int32() {
7018        fd_val.to_int32()
7019    } else {
7020        -1
7021    };
7022    let data_val = if argc > 1 {
7023        *args.get(1).ptr
7024    } else {
7025        UndefinedValue()
7026    };
7027    let bytes = if data_val.is_string() {
7028        let s = data_val.to_string();
7029        if !s.is_null() {
7030            crate::jsstr_to_rust_string(cx, s).into_bytes()
7031        } else {
7032            Vec::new()
7033        }
7034    } else if data_val.is_object() {
7035        crate::node_crypto::extract_buffer_bytes(cx, data_val)
7036    } else {
7037        Vec::new()
7038    };
7039    let mut wrapped_cx =
7040        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
7041    let cx_ref = &mut wrapped_cx;
7042    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
7043    if promise.get().is_null() {
7044        args.rval().set(UndefinedValue());
7045        return false;
7046    }
7047    #[cfg(unix)]
7048    {
7049        let written =
7050            unsafe { libc::write(fd, bytes.as_ptr() as *const ::std::ffi::c_void, bytes.len()) };
7051        if written >= 0 {
7052            rooted!(&in(cx_ref) let val = mozjs::jsval::DoubleValue(written as f64));
7053            unsafe {
7054                mozjs_sys::jsapi::JS::ResolvePromise(
7055                    cx,
7056                    promise.handle().into(),
7057                    val.handle().into(),
7058                );
7059            }
7060        } else {
7061            reject_with_error(
7062                cx,
7063                promise.get(),
7064                &format!("write fd:{}: {}", fd, ::std::io::Error::last_os_error()),
7065            );
7066        }
7067    }
7068    #[cfg(not(unix))]
7069    {
7070        resolve_undefined(cx, promise.get());
7071    }
7072    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
7073    true
7074}
7075
7076// --- statfs ---
7077
7078#[allow(unsafe_op_in_unsafe_fn)]
7079unsafe extern "C" fn fs_statfs_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7080    let args = CallArgs::from_vp(vp, argc);
7081    let path = match get_path_arg(cx, &args, 0) {
7082        ::std::result::Result::Ok(p) => p,
7083        ::std::result::Result::Err(b) => return b,
7084    };
7085    #[cfg(unix)]
7086    {
7087        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
7088        let mut buf: libc::statfs = ::std::mem::zeroed();
7089        let rv = unsafe { libc::statfs(c_path.as_ptr(), &mut buf) };
7090        if rv == 0 {
7091            let sf = StatfsResult {
7092                type_: buf.f_type as u64,
7093                bsize: buf.f_bsize as u64,
7094                frsize: buf.f_frsize as u64,
7095                blocks: buf.f_blocks as u64,
7096                bfree: buf.f_bfree as u64,
7097                bavail: buf.f_bavail as u64,
7098                files: buf.f_files as u64,
7099                ffree: buf.f_ffree as u64,
7100            };
7101            let obj = create_statfs_object(cx, &sf);
7102            args.rval().set(mozjs::jsval::ObjectValue(obj));
7103            true
7104        } else {
7105            throw_fs_error(cx, "statfsSync", &path, &::std::io::Error::last_os_error())
7106        }
7107    }
7108    #[cfg(not(unix))]
7109    {
7110        let _ = path;
7111        args.rval().set(UndefinedValue());
7112        true
7113    }
7114}
7115
7116#[allow(unsafe_op_in_unsafe_fn)]
7117unsafe extern "C" fn fs_statfs(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7118    let args = CallArgs::from_vp(vp, argc);
7119    let path = match get_path_arg(cx, &args, 0) {
7120        ::std::result::Result::Ok(p) => p,
7121        ::std::result::Result::Err(b) => return b,
7122    };
7123
7124    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 1) {
7125        spawn_fs_async(cx, "statfs", path.clone(), callback, None, move || {
7126            #[cfg(unix)]
7127            {
7128                let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
7129                let mut buf: libc::statfs = ::std::mem::zeroed();
7130                let rv = unsafe { libc::statfs(c_path.as_ptr(), &mut buf) };
7131                if rv == 0 {
7132                    Ok(FsAsyncResult::OkStatfs(StatfsResult {
7133                        type_: buf.f_type as u64,
7134                        bsize: buf.f_bsize as u64,
7135                        frsize: buf.f_frsize as u64,
7136                        blocks: buf.f_blocks as u64,
7137                        bfree: buf.f_bfree as u64,
7138                        bavail: buf.f_bavail as u64,
7139                        files: buf.f_files as u64,
7140                        ffree: buf.f_ffree as u64,
7141                    }))
7142                } else {
7143                    Err(::std::io::Error::last_os_error())
7144                }
7145            }
7146            #[cfg(not(unix))]
7147            {
7148                Ok(FsAsyncResult::OkStatfs(StatfsResult {
7149                    type_: 0,
7150                    bsize: 0,
7151                    frsize: 0,
7152                    blocks: 0,
7153                    bfree: 0,
7154                    bavail: 0,
7155                    files: 0,
7156                    ffree: 0,
7157                }))
7158            }
7159        });
7160        args.rval().set(UndefinedValue());
7161        return true;
7162    }
7163
7164    #[cfg(unix)]
7165    {
7166        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
7167        let mut buf: libc::statfs = ::std::mem::zeroed();
7168        let rv = unsafe { libc::statfs(c_path.as_ptr(), &mut buf) };
7169        if rv == 0 {
7170            let sf = StatfsResult {
7171                type_: buf.f_type as u64,
7172                bsize: buf.f_bsize as u64,
7173                frsize: buf.f_frsize as u64,
7174                blocks: buf.f_blocks as u64,
7175                bfree: buf.f_bfree as u64,
7176                bavail: buf.f_bavail as u64,
7177                files: buf.f_files as u64,
7178                ffree: buf.f_ffree as u64,
7179            };
7180            let obj = create_statfs_object(cx, &sf);
7181            args.rval().set(mozjs::jsval::ObjectValue(obj));
7182            true
7183        } else {
7184            throw_fs_error(cx, "statfs", &path, &::std::io::Error::last_os_error())
7185        }
7186    }
7187    #[cfg(not(unix))]
7188    {
7189        args.rval().set(UndefinedValue());
7190        true
7191    }
7192}
7193
7194#[allow(unsafe_op_in_unsafe_fn)]
7195unsafe extern "C" fn fs_promises_statfs(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7196    let args = CallArgs::from_vp(vp, argc);
7197    let path = match get_path_arg(cx, &args, 0) {
7198        ::std::result::Result::Ok(p) => p,
7199        ::std::result::Result::Err(b) => return b,
7200    };
7201    let mut wrapped_cx =
7202        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
7203    let cx_ref = &mut wrapped_cx;
7204    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
7205    if promise.get().is_null() {
7206        args.rval().set(UndefinedValue());
7207        return false;
7208    }
7209
7210    #[cfg(unix)]
7211    {
7212        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
7213        let mut buf: libc::statfs = ::std::mem::zeroed();
7214        let rv = unsafe { libc::statfs(c_path.as_ptr(), &mut buf) };
7215        if rv == 0 {
7216            let sf = StatfsResult {
7217                type_: buf.f_type as u64,
7218                bsize: buf.f_bsize as u64,
7219                frsize: buf.f_bsize as u64,
7220                blocks: buf.f_blocks as u64,
7221                bfree: buf.f_bfree as u64,
7222                bavail: buf.f_bavail as u64,
7223                files: buf.f_files as u64,
7224                ffree: buf.f_ffree as u64,
7225            };
7226            let obj = create_statfs_object(cx, &sf);
7227            rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(obj));
7228            unsafe {
7229                mozjs_sys::jsapi::JS::ResolvePromise(
7230                    cx,
7231                    promise.handle().into(),
7232                    val.handle().into(),
7233                );
7234            }
7235        } else {
7236            reject_with_error(
7237                cx,
7238                promise.get(),
7239                &format!("statfs '{}': {}", path, ::std::io::Error::last_os_error()),
7240            );
7241        }
7242    }
7243    #[cfg(not(unix))]
7244    {
7245        resolve_undefined(cx, promise.get());
7246    }
7247    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
7248    true
7249}
7250
7251// --- fd sync operations ---
7252
7253#[allow(unsafe_op_in_unsafe_fn)]
7254unsafe extern "C" fn fs_open_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7255    let args = CallArgs::from_vp(vp, argc);
7256    let path = match get_path_arg(cx, &args, 0) {
7257        ::std::result::Result::Ok(p) => p,
7258        ::std::result::Result::Err(b) => return b,
7259    };
7260    let flags_val = if argc > 1 {
7261        *args.get(1).ptr
7262    } else {
7263        UndefinedValue()
7264    };
7265    let flags = if flags_val.is_int32() {
7266        flags_val.to_int32()
7267    } else if flags_val.is_string() {
7268        let s = flags_val.to_string();
7269        if !s.is_null() {
7270            parse_open_flags(&crate::jsstr_to_rust_string(cx, s))
7271        } else {
7272            0
7273        }
7274    } else {
7275        0
7276    };
7277    let mode_val = if argc > 2 {
7278        *args.get(2).ptr
7279    } else {
7280        UndefinedValue()
7281    };
7282    let mode = if mode_val.is_int32() {
7283        mode_val.to_int32() as u32
7284    } else {
7285        0o644
7286    };
7287    #[cfg(unix)]
7288    {
7289        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
7290        let fd = unsafe { libc::open(c_path.as_ptr(), flags, mode) };
7291        if fd >= 0 {
7292            args.rval().set(mozjs::jsval::Int32Value(fd));
7293            true
7294        } else {
7295            throw_fs_error(cx, "openSync", &path, &::std::io::Error::last_os_error())
7296        }
7297    }
7298    #[cfg(not(unix))]
7299    {
7300        args.rval().set(mozjs::jsval::Int32Value(0));
7301        true
7302    }
7303}
7304
7305#[allow(unsafe_op_in_unsafe_fn)]
7306unsafe extern "C" fn fs_close_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7307    let args = CallArgs::from_vp(vp, argc);
7308    let fd_val = if argc > 0 {
7309        *args.get(0).ptr
7310    } else {
7311        UndefinedValue()
7312    };
7313    let fd = if fd_val.is_int32() {
7314        fd_val.to_int32()
7315    } else {
7316        -1
7317    };
7318    #[cfg(unix)]
7319    {
7320        let rv = unsafe { libc::close(fd) };
7321        if rv == 0 {
7322            args.rval().set(UndefinedValue());
7323            true
7324        } else {
7325            throw_fs_error(
7326                cx,
7327                "closeSync",
7328                &format!("fd:{}", fd),
7329                &::std::io::Error::last_os_error(),
7330            )
7331        }
7332    }
7333    #[cfg(not(unix))]
7334    {
7335        args.rval().set(UndefinedValue());
7336        true
7337    }
7338}
7339
7340// BCE-20260816-FS-READSYNC — the old implementation ignored the caller's
7341// buffer argument entirely: it read into a throwaway Vec (the created Buffer
7342// object was dropped unused) and returned only the byte count, so the
7343// canonical `fd = openSync(p, 'r'); readSync(fd, buf, 0, n, 0)` pattern left
7344// `buf` zeroed — the "openSync+readSync combo dead" audit item. Node
7345// semantics (fs.readSync(fd, buffer, offset, length, position)): bytes are
7346// written into the CALLER'S typed array at `offset`, `length` caps the read,
7347// and a numeric `position` reads via pread without moving the fd cursor
7348// (null/undefined position = current cursor via read).
7349// fs.truncateSync(path, len) — path-based truncate (the fd-based
7350// ftruncateSync already existed; the path form was missing entirely, so
7351// `typeof fs.truncateSync === 'undefined'`). Opened WRITE: ftruncate(2)
7352// rejects read-only fds with EINVAL — a read-only File::open + set_len
7353// always failed (probe: truncateSync EINVAL).
7354#[allow(unsafe_op_in_unsafe_fn)]
7355unsafe extern "C" fn fs_truncate_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7356    let args = CallArgs::from_vp(vp, argc);
7357    let path = match get_path_arg(cx, &args, 0) {
7358        ::std::result::Result::Ok(p) => p,
7359        ::std::result::Result::Err(b) => return b,
7360    };
7361    let len_val = if argc > 1 {
7362        *args.get(1).ptr
7363    } else {
7364        UndefinedValue()
7365    };
7366    let len = if len_val.is_int32() {
7367        len_val.to_int32() as i64
7368    } else if len_val.is_double() {
7369        len_val.to_double() as i64
7370    } else {
7371        0
7372    };
7373    match fs::OpenOptions::new().write(true).open(&path).and_then(|f| f.set_len(len.max(0) as u64)) {
7374        ::std::result::Result::Ok(()) => {
7375            args.rval().set(UndefinedValue());
7376            true
7377        }
7378        ::std::result::Result::Err(e) => throw_fs_error(cx, "truncateSync", &path, &e),
7379    }
7380}
7381
7382// fs.opendirSync(path) — same Dir object as the async opendir() (readSync /
7383// closeSync / async iteration), returned synchronously.
7384#[allow(unsafe_op_in_unsafe_fn)]
7385unsafe extern "C" fn fs_opendir_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7386    let args = CallArgs::from_vp(vp, argc);
7387    let path = match get_path_arg(cx, &args, 0) {
7388        ::std::result::Result::Ok(p) => p,
7389        ::std::result::Result::Err(b) => return b,
7390    };
7391    match fs::metadata(&path) {
7392        ::std::result::Result::Ok(meta) if meta.is_dir() => {
7393            let dir_obj = create_dir_object(cx, &path);
7394            args.rval().set(mozjs::jsval::ObjectValue(dir_obj));
7395            true
7396        }
7397        ::std::result::Result::Ok(_) => {
7398            JS_ReportErrorUTF8(cx, c"opendirSync: path is not a directory".as_ptr());
7399            false
7400        }
7401        ::std::result::Result::Err(e) => throw_fs_error(cx, "opendirSync", &path, &e),
7402    }
7403}
7404
7405#[allow(unsafe_op_in_unsafe_fn)]
7406unsafe extern "C" fn fs_read_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7407    let args = CallArgs::from_vp(vp, argc);
7408    let fd_val = if argc > 0 {
7409        *args.get(0).ptr
7410    } else {
7411        UndefinedValue()
7412    };
7413    let fd = if fd_val.is_int32() {
7414        fd_val.to_int32()
7415    } else {
7416        -1
7417    };
7418
7419    // Caller-supplied buffer: must be a typed array (Buffer IS a Uint8Array).
7420    let buf_val = if argc > 1 { *args.get(1).ptr } else { UndefinedValue() };
7421    if !buf_val.is_object() {
7422        JS_ReportErrorUTF8(
7423            cx,
7424            c"readSync: buffer argument must be a Buffer or Uint8Array".as_ptr(),
7425        );
7426        return false;
7427    }
7428    let wrapped_cx = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
7429    rooted!(&in(wrapped_cx) let buf_obj = buf_val.to_object());
7430    if !mozjs_sys::jsapi::JS_IsArrayBufferViewObject(buf_obj.get()) {
7431        JS_ReportErrorUTF8(
7432            cx,
7433            c"readSync: buffer argument must be a Buffer or Uint8Array".as_ptr(),
7434        );
7435        return false;
7436    }
7437
7438    let byte_len = mozjs_sys::jsapi::JS_GetTypedArrayByteLength(buf_obj.get()) as usize;
7439
7440    let to_num = |v: JSVal| -> Option<f64> {
7441        if v.is_number() {
7442            Some(v.to_number())
7443        } else if v.is_int32() {
7444            Some(v.to_int32() as f64)
7445        } else {
7446            None
7447        }
7448    };
7449    let offset = if argc > 2 {
7450        to_num(*args.get(2).ptr).unwrap_or(0.0)
7451    } else {
7452        0.0
7453    };
7454    let length = if argc > 3 {
7455        to_num(*args.get(3).ptr)
7456            .filter(|n| *n >= 0.0)
7457            .map(|n| n as usize)
7458            .unwrap_or(byte_len)
7459    } else {
7460        byte_len
7461    };
7462    let position_arg = if argc > 4 { *args.get(4).ptr } else { UndefinedValue() };
7463    let use_pread = position_arg.is_number() || position_arg.is_int32();
7464    let position: i64 = if use_pread {
7465        to_num(position_arg).unwrap_or(0.0) as i64
7466    } else {
7467        -1
7468    };
7469
7470    if offset < 0.0 || offset as usize > byte_len {
7471        JS_ReportErrorUTF8(
7472            cx,
7473            c"readSync: offset is out of bounds".as_ptr(),
7474        );
7475        return false;
7476    }
7477    let offset = offset as usize;
7478    if length > byte_len - offset {
7479        JS_ReportErrorUTF8(
7480            cx,
7481            c"readSync: length extends beyond buffer".as_ptr(),
7482        );
7483        return false;
7484    }
7485    if length == 0 {
7486        args.rval().set(mozjs::jsval::Int32Value(0));
7487        return true;
7488    }
7489
7490    #[cfg(unix)]
7491    {
7492        // Rooted view — data pointer stays valid across the read (no JS runs).
7493        let mut is_shared = false;
7494        let data_ptr = mozjs_sys::jsapi::JS_GetUint8ArrayData(
7495            buf_obj.get(),
7496            &mut is_shared,
7497            ::std::ptr::null(),
7498        );
7499        if data_ptr.is_null() {
7500            JS_ReportErrorUTF8(
7501                cx,
7502                c"readSync: cannot access buffer storage".as_ptr(),
7503            );
7504            return false;
7505        }
7506        let dst = unsafe { data_ptr.add(offset) };
7507        let bytes_read = if use_pread {
7508            unsafe {
7509                libc::pread(
7510                    fd,
7511                    dst as *mut ::std::ffi::c_void,
7512                    length,
7513                    position,
7514                )
7515            }
7516        } else {
7517            unsafe { libc::read(fd, dst as *mut ::std::ffi::c_void, length) }
7518        };
7519        if bytes_read >= 0 {
7520            args.rval().set(mozjs::jsval::Int32Value(bytes_read as i32));
7521            true
7522        } else {
7523            throw_fs_error(
7524                cx,
7525                "readSync",
7526                &format!("fd:{}", fd),
7527                &::std::io::Error::last_os_error(),
7528            )
7529        }
7530    }
7531    #[cfg(not(unix))]
7532    {
7533        args.rval().set(mozjs::jsval::Int32Value(0));
7534        true
7535    }
7536}
7537
7538#[allow(unsafe_op_in_unsafe_fn)]
7539unsafe extern "C" fn fs_write_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7540    let args = CallArgs::from_vp(vp, argc);
7541    let fd_val = if argc > 0 {
7542        *args.get(0).ptr
7543    } else {
7544        UndefinedValue()
7545    };
7546    let fd = if fd_val.is_int32() {
7547        fd_val.to_int32()
7548    } else {
7549        -1
7550    };
7551    let data_val = if argc > 1 {
7552        *args.get(1).ptr
7553    } else {
7554        UndefinedValue()
7555    };
7556    let bytes = if data_val.is_string() {
7557        let s = data_val.to_string();
7558        if !s.is_null() {
7559            crate::jsstr_to_rust_string(cx, s).into_bytes()
7560        } else {
7561            Vec::new()
7562        }
7563    } else if data_val.is_object() {
7564        crate::node_crypto::extract_buffer_bytes(cx, data_val)
7565    } else {
7566        Vec::new()
7567    };
7568    #[cfg(unix)]
7569    {
7570        let written =
7571            unsafe { libc::write(fd, bytes.as_ptr() as *const ::std::ffi::c_void, bytes.len()) };
7572        if written >= 0 {
7573            args.rval().set(mozjs::jsval::DoubleValue(written as f64));
7574            true
7575        } else {
7576            throw_fs_error(
7577                cx,
7578                "writeSync",
7579                &format!("fd:{}", fd),
7580                &::std::io::Error::last_os_error(),
7581            )
7582        }
7583    }
7584    #[cfg(not(unix))]
7585    {
7586        args.rval().set(UndefinedValue());
7587        true
7588    }
7589}
7590
7591#[allow(unsafe_op_in_unsafe_fn)]
7592unsafe extern "C" fn fs_mkdtemp_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7593    let args = CallArgs::from_vp(vp, argc);
7594    let prefix = match get_path_arg(cx, &args, 0) {
7595        ::std::result::Result::Ok(p) => p,
7596        ::std::result::Result::Err(b) => return b,
7597    };
7598    match mkdtemp_inner(&prefix) {
7599        ::std::result::Result::Ok(dir) => {
7600            let c_str = ZBox::from_bytes(dir.as_bytes());
7601            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
7602            if js_str.is_null() {
7603                args.rval().set(UndefinedValue());
7604            } else {
7605                args.rval().set(mozjs::jsval::StringValue(&*js_str));
7606            }
7607            true
7608        }
7609        ::std::result::Result::Err(e) => throw_fs_error(cx, "mkdtempSync", &prefix, &e),
7610    }
7611}
7612
7613#[allow(unsafe_op_in_unsafe_fn)]
7614unsafe extern "C" fn fs_fchmod_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7615    let args = CallArgs::from_vp(vp, argc);
7616    let fd_val = if argc > 0 {
7617        *args.get(0).ptr
7618    } else {
7619        UndefinedValue()
7620    };
7621    let fd = if fd_val.is_int32() {
7622        fd_val.to_int32()
7623    } else {
7624        -1
7625    };
7626    let mode_val = if argc > 1 {
7627        *args.get(1).ptr
7628    } else {
7629        UndefinedValue()
7630    };
7631    let mode = if mode_val.is_int32() {
7632        mode_val.to_int32() as u32
7633    } else {
7634        0o644
7635    };
7636    #[cfg(unix)]
7637    {
7638        let rv = unsafe { libc::fchmod(fd, mode) };
7639        if rv == 0 {
7640            args.rval().set(UndefinedValue());
7641            true
7642        } else {
7643            throw_fs_error(
7644                cx,
7645                "fchmodSync",
7646                &format!("fd:{}", fd),
7647                &::std::io::Error::last_os_error(),
7648            )
7649        }
7650    }
7651    #[cfg(not(unix))]
7652    {
7653        args.rval().set(UndefinedValue());
7654        true
7655    }
7656}
7657
7658#[allow(unsafe_op_in_unsafe_fn)]
7659unsafe extern "C" fn fs_fchown_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7660    let args = CallArgs::from_vp(vp, argc);
7661    let fd_val = if argc > 0 {
7662        *args.get(0).ptr
7663    } else {
7664        UndefinedValue()
7665    };
7666    let fd = if fd_val.is_int32() {
7667        fd_val.to_int32()
7668    } else {
7669        -1
7670    };
7671    let uid = if argc > 1 {
7672        let v = *args.get(1).ptr;
7673        if v.is_int32() { v.to_int32() as u32 } else { 0 }
7674    } else {
7675        0
7676    };
7677    let gid = if argc > 2 {
7678        let v = *args.get(2).ptr;
7679        if v.is_int32() { v.to_int32() as u32 } else { 0 }
7680    } else {
7681        0
7682    };
7683    #[cfg(unix)]
7684    {
7685        let rv = unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) };
7686        if rv == 0 {
7687            args.rval().set(UndefinedValue());
7688            true
7689        } else {
7690            throw_fs_error(
7691                cx,
7692                "fchownSync",
7693                &format!("fd:{}", fd),
7694                &::std::io::Error::last_os_error(),
7695            )
7696        }
7697    }
7698    #[cfg(not(unix))]
7699    {
7700        args.rval().set(UndefinedValue());
7701        true
7702    }
7703}
7704
7705#[allow(unsafe_op_in_unsafe_fn)]
7706unsafe extern "C" fn fs_fdatasync_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7707    let args = CallArgs::from_vp(vp, argc);
7708    let fd_val = if argc > 0 {
7709        *args.get(0).ptr
7710    } else {
7711        UndefinedValue()
7712    };
7713    let fd = if fd_val.is_int32() {
7714        fd_val.to_int32()
7715    } else {
7716        -1
7717    };
7718    #[cfg(unix)]
7719    {
7720        let rv = unsafe { libc::fdatasync(fd) };
7721        if rv == 0 {
7722            args.rval().set(UndefinedValue());
7723            true
7724        } else {
7725            throw_fs_error(
7726                cx,
7727                "fdatasyncSync",
7728                &format!("fd:{}", fd),
7729                &::std::io::Error::last_os_error(),
7730            )
7731        }
7732    }
7733    #[cfg(not(unix))]
7734    {
7735        args.rval().set(UndefinedValue());
7736        true
7737    }
7738}
7739
7740#[allow(unsafe_op_in_unsafe_fn)]
7741unsafe extern "C" fn fs_fsync_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7742    let args = CallArgs::from_vp(vp, argc);
7743    let fd_val = if argc > 0 {
7744        *args.get(0).ptr
7745    } else {
7746        UndefinedValue()
7747    };
7748    let fd = if fd_val.is_int32() {
7749        fd_val.to_int32()
7750    } else {
7751        -1
7752    };
7753    #[cfg(unix)]
7754    {
7755        let rv = unsafe { libc::fsync(fd) };
7756        if rv == 0 {
7757            args.rval().set(UndefinedValue());
7758            true
7759        } else {
7760            throw_fs_error(
7761                cx,
7762                "fsyncSync",
7763                &format!("fd:{}", fd),
7764                &::std::io::Error::last_os_error(),
7765            )
7766        }
7767    }
7768    #[cfg(not(unix))]
7769    {
7770        args.rval().set(UndefinedValue());
7771        true
7772    }
7773}
7774
7775#[allow(unsafe_op_in_unsafe_fn)]
7776unsafe extern "C" fn fs_ftruncate_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7777    let args = CallArgs::from_vp(vp, argc);
7778    let fd_val = if argc > 0 {
7779        *args.get(0).ptr
7780    } else {
7781        UndefinedValue()
7782    };
7783    let fd = if fd_val.is_int32() {
7784        fd_val.to_int32()
7785    } else {
7786        -1
7787    };
7788    let len_val = if argc > 1 {
7789        *args.get(1).ptr
7790    } else {
7791        UndefinedValue()
7792    };
7793    let len = if len_val.is_int32() {
7794        len_val.to_int32() as i64
7795    } else if len_val.is_double() {
7796        len_val.to_double() as i64
7797    } else {
7798        0
7799    };
7800    #[cfg(unix)]
7801    {
7802        let rv = unsafe { libc::ftruncate(fd, len) };
7803        if rv == 0 {
7804            args.rval().set(UndefinedValue());
7805            true
7806        } else {
7807            throw_fs_error(
7808                cx,
7809                "ftruncateSync",
7810                &format!("fd:{}", fd),
7811                &::std::io::Error::last_os_error(),
7812            )
7813        }
7814    }
7815    #[cfg(not(unix))]
7816    {
7817        args.rval().set(UndefinedValue());
7818        true
7819    }
7820}
7821
7822#[allow(unsafe_op_in_unsafe_fn)]
7823unsafe extern "C" fn fs_futimes_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7824    let args = CallArgs::from_vp(vp, argc);
7825    let fd_val = if argc > 0 {
7826        *args.get(0).ptr
7827    } else {
7828        UndefinedValue()
7829    };
7830    let fd = if fd_val.is_int32() {
7831        fd_val.to_int32()
7832    } else {
7833        -1
7834    };
7835    let atime_val = if argc > 1 {
7836        *args.get(1).ptr
7837    } else {
7838        UndefinedValue()
7839    };
7840    let mtime_val = if argc > 2 {
7841        *args.get(2).ptr
7842    } else {
7843        UndefinedValue()
7844    };
7845    let atime = if atime_val.is_double() {
7846        atime_val.to_double()
7847    } else if atime_val.is_int32() {
7848        atime_val.to_int32() as f64
7849    } else {
7850        0.0
7851    };
7852    let mtime = if mtime_val.is_double() {
7853        mtime_val.to_double()
7854    } else if mtime_val.is_int32() {
7855        mtime_val.to_int32() as f64
7856    } else {
7857        0.0
7858    };
7859    #[cfg(unix)]
7860    {
7861        let tv = [
7862            libc::timeval {
7863                tv_sec: atime as i64,
7864                tv_usec: ((atime % 1.0) * 1_000_000.0) as i64,
7865            },
7866            libc::timeval {
7867                tv_sec: mtime as i64,
7868                tv_usec: ((mtime % 1.0) * 1_000_000.0) as i64,
7869            },
7870        ];
7871        let rv = unsafe { libc::futimes(fd, tv.as_ptr()) };
7872        if rv == 0 {
7873            args.rval().set(UndefinedValue());
7874            true
7875        } else {
7876            throw_fs_error(
7877                cx,
7878                "futimesSync",
7879                &format!("fd:{}", fd),
7880                &::std::io::Error::last_os_error(),
7881            )
7882        }
7883    }
7884    #[cfg(not(unix))]
7885    {
7886        args.rval().set(UndefinedValue());
7887        true
7888    }
7889}
7890
7891#[allow(unsafe_op_in_unsafe_fn)]
7892unsafe extern "C" fn fs_lchmod_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7893    let args = CallArgs::from_vp(vp, argc);
7894    let path = match get_path_arg(cx, &args, 0) {
7895        ::std::result::Result::Ok(p) => p,
7896        ::std::result::Result::Err(b) => return b,
7897    };
7898    let mode_val = if argc > 1 {
7899        *args.get(1).ptr
7900    } else {
7901        UndefinedValue()
7902    };
7903    let mode = if mode_val.is_int32() {
7904        mode_val.to_int32() as u32
7905    } else {
7906        0o644
7907    };
7908    // lchmod is not available on Linux; fall back to chmod
7909    #[cfg(unix)]
7910    {
7911        use ::std::os::unix::fs::PermissionsExt;
7912        match fs::set_permissions(&path, fs::Permissions::from_mode(mode)) {
7913            ::std::result::Result::Ok(()) => {
7914                args.rval().set(UndefinedValue());
7915                true
7916            }
7917            ::std::result::Result::Err(e) => throw_fs_error(cx, "lchmodSync", &path, &e),
7918        }
7919    }
7920    #[cfg(not(unix))]
7921    {
7922        let _ = mode;
7923        let _ = path;
7924        args.rval().set(UndefinedValue());
7925        true
7926    }
7927}
7928
7929#[allow(unsafe_op_in_unsafe_fn)]
7930unsafe extern "C" fn fs_lchown_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7931    let args = CallArgs::from_vp(vp, argc);
7932    let path = match get_path_arg(cx, &args, 0) {
7933        ::std::result::Result::Ok(p) => p,
7934        ::std::result::Result::Err(b) => return b,
7935    };
7936    let uid = if argc > 1 {
7937        let v = *args.get(1).ptr;
7938        if v.is_int32() { v.to_int32() as u32 } else { 0 }
7939    } else {
7940        0
7941    };
7942    let gid = if argc > 2 {
7943        let v = *args.get(2).ptr;
7944        if v.is_int32() { v.to_int32() as u32 } else { 0 }
7945    } else {
7946        0
7947    };
7948    #[cfg(unix)]
7949    {
7950        let c_path = ::std::ffi::CString::new(path.as_str()).unwrap_or_default();
7951        let rv = unsafe { libc::lchown(c_path.as_ptr(), uid, gid) };
7952        if rv == 0 {
7953            args.rval().set(UndefinedValue());
7954            true
7955        } else {
7956            throw_fs_error(cx, "lchownSync", &path, &::std::io::Error::last_os_error())
7957        }
7958    }
7959    #[cfg(not(unix))]
7960    {
7961        args.rval().set(UndefinedValue());
7962        true
7963    }
7964}
7965
7966#[allow(unsafe_op_in_unsafe_fn)]
7967unsafe extern "C" fn fs_readv_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
7968    let args = CallArgs::from_vp(vp, argc);
7969    let fd_val = if argc > 0 {
7970        *args.get(0).ptr
7971    } else {
7972        UndefinedValue()
7973    };
7974    let fd = if fd_val.is_int32() {
7975        fd_val.to_int32()
7976    } else {
7977        -1
7978    };
7979    let buffers_val = if argc > 1 {
7980        *args.get(1).ptr
7981    } else {
7982        UndefinedValue()
7983    };
7984    if !buffers_val.is_object() {
7985        JS_ReportErrorUTF8(cx, c"readvSync: buffers must be an array".as_ptr());
7986        return false;
7987    }
7988    let mut wrapped_cx =
7989        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
7990    let cx_ref = &mut wrapped_cx;
7991    rooted!(&in(cx_ref) let buffers_obj = buffers_val.to_object());
7992    let mut total_read: i32 = 0;
7993    let mut buf_idx: u32 = 0;
7994    loop {
7995        let mut elem = UndefinedValue();
7996        JS_GetElement(
7997            cx,
7998            buffers_obj.handle().into(),
7999            buf_idx,
8000            MutableHandle::<Value> {
8001                _phantom_0: ::std::marker::PhantomData,
8002                ptr: &mut elem,
8003            },
8004        );
8005        if elem.is_undefined() {
8006            break;
8007        }
8008        if elem.is_object() {
8009            let buf_bytes = crate::node_crypto::extract_buffer_bytes(cx, elem);
8010            if !buf_bytes.is_empty() {
8011                let mut write_buf = buf_bytes;
8012                #[cfg(unix)]
8013                {
8014                    let n = unsafe {
8015                        libc::read(
8016                            fd,
8017                            write_buf.as_mut_ptr() as *mut ::std::ffi::c_void,
8018                            write_buf.len(),
8019                        )
8020                    };
8021                    if n < 0 {
8022                        return throw_fs_error(
8023                            cx,
8024                            "readvSync",
8025                            &format!("fd:{}", fd),
8026                            &::std::io::Error::last_os_error(),
8027                        );
8028                    }
8029                    total_read += n as i32;
8030                }
8031            }
8032        }
8033        buf_idx += 1;
8034    }
8035    args.rval().set(mozjs::jsval::Int32Value(total_read));
8036    true
8037}
8038
8039#[allow(unsafe_op_in_unsafe_fn)]
8040unsafe extern "C" fn fs_writev_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
8041    let args = CallArgs::from_vp(vp, argc);
8042    let fd_val = if argc > 0 {
8043        *args.get(0).ptr
8044    } else {
8045        UndefinedValue()
8046    };
8047    let fd = if fd_val.is_int32() {
8048        fd_val.to_int32()
8049    } else {
8050        -1
8051    };
8052    let buffers_val = if argc > 1 {
8053        *args.get(1).ptr
8054    } else {
8055        UndefinedValue()
8056    };
8057    if !buffers_val.is_object() {
8058        JS_ReportErrorUTF8(cx, c"writevSync: buffers must be an array".as_ptr());
8059        return false;
8060    }
8061    let mut wrapped_cx =
8062        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8063    let cx_ref = &mut wrapped_cx;
8064    rooted!(&in(cx_ref) let buffers_obj = buffers_val.to_object());
8065    let mut total_written: i32 = 0;
8066    let mut buf_idx: u32 = 0;
8067    loop {
8068        let mut elem = UndefinedValue();
8069        JS_GetElement(
8070            cx,
8071            buffers_obj.handle().into(),
8072            buf_idx,
8073            MutableHandle::<Value> {
8074                _phantom_0: ::std::marker::PhantomData,
8075                ptr: &mut elem,
8076            },
8077        );
8078        if elem.is_undefined() {
8079            break;
8080        }
8081        let bytes = if elem.is_string() {
8082            let s = elem.to_string();
8083            if !s.is_null() {
8084                crate::jsstr_to_rust_string(cx, s).into_bytes()
8085            } else {
8086                Vec::new()
8087            }
8088        } else if elem.is_object() {
8089            crate::node_crypto::extract_buffer_bytes(cx, elem)
8090        } else {
8091            Vec::new()
8092        };
8093        if !bytes.is_empty() {
8094            #[cfg(unix)]
8095            {
8096                let n = unsafe {
8097                    libc::write(fd, bytes.as_ptr() as *const ::std::ffi::c_void, bytes.len())
8098                };
8099                if n < 0 {
8100                    return throw_fs_error(
8101                        cx,
8102                        "writevSync",
8103                        &format!("fd:{}", fd),
8104                        &::std::io::Error::last_os_error(),
8105                    );
8106                }
8107                total_written += n as i32;
8108            }
8109        }
8110        buf_idx += 1;
8111    }
8112    args.rval().set(mozjs::jsval::Int32Value(total_written));
8113    true
8114}
8115
8116// --- glob ---
8117//
8118// BCE-20260816-FS-GLOB — the hand-written glob_walk/glob_match walker had two
8119// fatal shape defects: (1) `options.cwd` was ignored entirely (patterns were
8120// always walked from process CWD — probe: globSync('**/*.ts', {cwd:'/tmp/x'})
8121// returned project-tree hits); (2) the matcher was a naive recursive glob
8122// with no brace/char-class/dotfile semantics and no `dot` switch. Replaced by
8123// the workspace's bun_glob engine (the Bun-faithful GlobWalker powering
8124// upstream fs.glob — see ~/code/rust/bun/src/js/internal/fs/glob.ts):
8125//   pattern: string | string[]
8126//   options: { cwd (default process cwd), root (fallback start dir),
8127//              dot (default false), exclude (fn | string[]) }
8128// Node/Bun fs.glob yield paths RELATIVE to the start dir; absolute patterns
8129// yield absolute paths. onlyFiles defaults to false (dirs match too), matching
8130// upstream mapOptions.
8131
8132/// Options parsed from the JS `options` argument of glob/globSync.
8133struct GlobOptions {
8134    start_dir: String,
8135    dot: bool,
8136    /// JS exclude callbacks are applied post-walk (IgnoreFilterFn is a plain
8137    /// fn pointer and cannot close over a JS callable).
8138    exclude_fn: Option<*mut JSObject>,
8139    exclude_globs: Vec<String>,
8140}
8141
8142/// Collect glob matches for one pattern via bun_glob::GlobWalker, applying
8143/// JS/glob excludes and the relative-path yield contract.
8144#[allow(unsafe_op_in_unsafe_fn)]
8145unsafe fn glob_collect(
8146    cx: *mut JSContext,
8147    pattern: &str,
8148    opts: &GlobOptions,
8149    results: &mut Vec<String>,
8150) {
8151    type Walker = bun_glob::GlobWalker<bun_glob::walk::SyscallAccessor, false>;
8152    let absolute = pattern.starts_with('/');
8153    let mut walker = match Walker::init_with_cwd(
8154        pattern.as_bytes(),
8155        opts.start_dir.as_bytes(),
8156        opts.dot,
8157        absolute,
8158        // followSymlinks: true — upstream fs.glob pins this (mapOptions).
8159        true,
8160        false,
8161        // onlyFiles: false — upstream pins dirs+files (mapOptions).
8162        false,
8163        None,
8164    ) {
8165        Ok(Ok(w)) => w,
8166        // Malformed pattern (unbalanced brace/class) → no matches for it.
8167        _ => return,
8168    };
8169    let mut iter = bun_glob::walk::Iterator::new(&mut walker);
8170    if iter.init().is_err() {
8171        return;
8172    }
8173    let prefix = format!("{}/", opts.start_dir.trim_end_matches('/'));
8174    loop {
8175        match iter.next() {
8176            Ok(Ok(Some(path))) => {
8177                let full = String::from_utf8_lossy(&path).into_owned();
8178                let shown = if !absolute && full.starts_with(&prefix) {
8179                    full[prefix.len()..].to_string()
8180                } else {
8181                    full
8182                };
8183                if glob_excluded(cx, &shown, opts) {
8184                    continue;
8185                }
8186                results.push(shown);
8187            }
8188            _ => break,
8189        }
8190    }
8191}
8192
8193/// Apply options.exclude: a JS predicate (path => boolean) or a list of glob
8194/// patterns (path is excluded when any pattern matches).
8195#[allow(unsafe_op_in_unsafe_fn)]
8196unsafe fn glob_excluded(cx: *mut JSContext, path: &str, opts: &GlobOptions) -> bool {
8197    if let Some(cb) = opts.exclude_fn {
8198        if !cb.is_null() {
8199            let wrapped_cx = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8200            rooted!(&in(wrapped_cx) let cb_root = cb);
8201            let path_c = ZBox::from_bytes(path.as_bytes());
8202            let path_js = JS_NewStringCopyZ(cx, path_c.as_ptr());
8203            if path_js.is_null() {
8204                return false;
8205            }
8206            let arg = mozjs::jsval::StringValue(&*path_js);
8207            let args_arr = [arg];
8208            let call_args = HandleValueArray {
8209                length_: 1,
8210                elements_: args_arr.as_ptr(),
8211            };
8212            let global = CurrentGlobalOrNull(cx);
8213            if global.is_null() {
8214                return false;
8215            }
8216            rooted!(&in(wrapped_cx) let global_root = global);
8217            rooted!(&in(wrapped_cx) let cb_val = mozjs::jsval::ObjectValue(cb_root.get()));
8218            let mut rval = UndefinedValue();
8219            let ok = JS_CallFunctionValue(
8220                cx,
8221                global_root.handle().into(),
8222                cb_val.handle().into(),
8223                &call_args,
8224                MutableHandle::<Value> {
8225                    _phantom_0: ::std::marker::PhantomData,
8226                    ptr: &mut rval,
8227                },
8228            );
8229            return ok && rval.is_boolean() && rval.to_boolean();
8230        }
8231    }
8232    for pat in &opts.exclude_globs {
8233        if let bun_glob::MatchResult::Match = bun_glob::r#match(pat.as_bytes(), path.as_bytes()) {
8234            return true;
8235        }
8236    }
8237    false
8238}
8239
8240/// Read a string-valued property off a JS options object ("" when absent).
8241#[allow(unsafe_op_in_unsafe_fn)]
8242unsafe fn glob_opt_string(cx: *mut JSContext, obj: *mut JSObject, name: &[u8]) -> Option<String> {
8243    let name_z = ZBox::from_bytes(name);
8244    let wrapped_cx = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8245    rooted!(&in(wrapped_cx) let obj_root = obj);
8246    let mut val = UndefinedValue();
8247    JS_GetProperty(
8248        cx,
8249        obj_root.handle().into(),
8250        name_z.as_ptr(),
8251        MutableHandle::<Value> {
8252            _phantom_0: ::std::marker::PhantomData,
8253            ptr: &mut val,
8254        },
8255    );
8256    if val.is_string() {
8257        let s = crate::jsstr_to_rust_string(cx, val.to_string());
8258        if !s.is_empty() {
8259            return Some(s);
8260        }
8261    }
8262    None
8263}
8264
8265/// Read a boolean-valued property off a JS options object.
8266#[allow(unsafe_op_in_unsafe_fn)]
8267unsafe fn glob_opt_bool(cx: *mut JSContext, obj: *mut JSObject, name: &[u8]) -> bool {
8268    let name_z = ZBox::from_bytes(name);
8269    let wrapped_cx = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8270    rooted!(&in(wrapped_cx) let obj_root = obj);
8271    let mut val = UndefinedValue();
8272    JS_GetProperty(
8273        cx,
8274        obj_root.handle().into(),
8275        name_z.as_ptr(),
8276        MutableHandle::<Value> {
8277            _phantom_0: ::std::marker::PhantomData,
8278            ptr: &mut val,
8279        },
8280    );
8281    val.is_boolean() && val.to_boolean()
8282}
8283
8284/// Parse the `options` argument shared by glob/globSync into GlobOptions.
8285/// `arg_index` is where the options object may sit (1 for globSync, 1 or 2
8286/// for glob depending on callback arity).
8287#[allow(unsafe_op_in_unsafe_fn)]
8288unsafe fn glob_parse_options(cx: *mut JSContext, args: &CallArgs, arg_index: u32) -> GlobOptions {
8289    let mut opts = GlobOptions {
8290        start_dir: ::std::env::current_dir()
8291            .map(|p| p.to_string_lossy().into_owned())
8292            .unwrap_or_default(),
8293        dot: false,
8294        exclude_fn: None,
8295        exclude_globs: Vec::new(),
8296    };
8297    let val = *args.get(arg_index).ptr;
8298    if !val.is_object() {
8299        return opts;
8300    }
8301    let obj = val.to_object();
8302    // Node fs.glob: cwd takes precedence, root is the legacy fallback.
8303    if let Some(cwd) = glob_opt_string(cx, obj, b"cwd") {
8304        opts.start_dir = cwd;
8305    } else if let Some(root) = glob_opt_string(cx, obj, b"root") {
8306        opts.start_dir = root;
8307    }
8308    opts.dot = glob_opt_bool(cx, obj, b"dot");
8309
8310    let excl_z = ZBox::from_bytes(b"exclude");
8311    let wrapped_cx = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8312    rooted!(&in(wrapped_cx) let obj_root = obj);
8313    let mut excl = UndefinedValue();
8314    JS_GetProperty(
8315        cx,
8316        obj_root.handle().into(),
8317        excl_z.as_ptr(),
8318        MutableHandle::<Value> {
8319            _phantom_0: ::std::marker::PhantomData,
8320            ptr: &mut excl,
8321        },
8322    );
8323    if excl.is_object() {
8324        let excl_obj = excl.to_object();
8325        if mozjs_sys::jsapi::JS::IsCallable(excl_obj) {
8326            opts.exclude_fn = Some(excl_obj);
8327        } else {
8328            // Array of glob patterns.
8329            let mut len_val = UndefinedValue();
8330            JS_GetProperty(
8331                cx,
8332                obj_root.handle().into(),
8333                c"length".as_ptr(),
8334                MutableHandle::<Value> {
8335                    _phantom_0: ::std::marker::PhantomData,
8336                    ptr: &mut len_val,
8337                },
8338            );
8339            if len_val.is_int32() {
8340                for i in 0..len_val.to_int32().max(0) as u32 {
8341                    let mut elem = UndefinedValue();
8342                    if JS_GetElement(
8343                        cx,
8344                        obj_root.handle().into(),
8345                        i,
8346                        MutableHandle::<Value> {
8347                            _phantom_0: ::std::marker::PhantomData,
8348                            ptr: &mut elem,
8349                        },
8350                    ) && elem.is_string()
8351                    {
8352                        opts.exclude_globs.push(crate::jsstr_to_rust_string(cx, elem.to_string()));
8353                    }
8354                }
8355            }
8356        }
8357    }
8358    opts
8359}
8360
8361/// Expand the pattern argument: a single string or an array of strings.
8362#[allow(unsafe_op_in_unsafe_fn)]
8363unsafe fn glob_patterns(cx: *mut JSContext, args: &CallArgs) -> Vec<String> {
8364    let val = *args.get(0).ptr;
8365    let mut patterns = Vec::new();
8366    if val.is_string() {
8367        patterns.push(crate::jsstr_to_rust_string(cx, val.to_string()));
8368    } else if val.is_object() {
8369        let wrapped_cx = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8370        rooted!(&in(wrapped_cx) let arr = val.to_object());
8371        let mut len_val = UndefinedValue();
8372        JS_GetProperty(
8373            cx,
8374            arr.handle().into(),
8375            c"length".as_ptr(),
8376            MutableHandle::<Value> {
8377                _phantom_0: ::std::marker::PhantomData,
8378                ptr: &mut len_val,
8379            },
8380        );
8381        if len_val.is_int32() {
8382            for i in 0..len_val.to_int32().max(0) as u32 {
8383                let mut elem = UndefinedValue();
8384                if JS_GetElement(
8385                    cx,
8386                    arr.handle().into(),
8387                    i,
8388                    MutableHandle::<Value> {
8389                        _phantom_0: ::std::marker::PhantomData,
8390                        ptr: &mut elem,
8391                    },
8392                ) && elem.is_string()
8393                {
8394                    patterns.push(crate::jsstr_to_rust_string(cx, elem.to_string()));
8395                }
8396            }
8397        }
8398    }
8399    patterns
8400}
8401
8402#[allow(unsafe_op_in_unsafe_fn)]
8403unsafe extern "C" fn fs_glob_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
8404    let args = CallArgs::from_vp(vp, argc);
8405    let patterns = glob_patterns(cx, &args);
8406    let opts = glob_parse_options(cx, &args, 1);
8407    let mut results: Vec<String> = Vec::new();
8408    for pat in &patterns {
8409        glob_collect(cx, pat, &opts, &mut results);
8410    }
8411    let mut wrapped_cx =
8412        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8413    let cx_ref = &mut wrapped_cx;
8414    rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, results.len()));
8415    if arr.get().is_null() {
8416        args.rval().set(UndefinedValue());
8417        return true;
8418    }
8419    for (idx, path) in results.iter().enumerate() {
8420        let c_path = ZBox::from_bytes(path.as_bytes());
8421        let js_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
8422        if !js_str.is_null() {
8423            rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
8424            JS_DefineElement(
8425                cx,
8426                arr.handle().into(),
8427                idx as u32,
8428                val.handle().into(),
8429                JSPROP_ENUMERATE as u32,
8430            );
8431        }
8432    }
8433    args.rval().set(mozjs::jsval::ObjectValue(arr.get()));
8434    true
8435}
8436
8437#[allow(unsafe_op_in_unsafe_fn)]
8438unsafe extern "C" fn fs_glob(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
8439    let args = CallArgs::from_vp(vp, argc);
8440    let patterns = glob_patterns(cx, &args);
8441
8442    // Node fs.glob(pattern, options, callback): options may sit at index 1
8443    // (callback at 2) or be skipped (callback at 1).
8444    let opts_idx = if argc >= 3 { 1 } else { 1 };
8445    let opts = glob_parse_options(cx, &args, opts_idx);
8446
8447    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 2) {
8448        // The JS exclude predicate cannot cross the thread boundary — apply
8449        // it synchronously on the collected results instead of in the worker.
8450        let mut prefiltered: Vec<String> = Vec::new();
8451        for pat in &patterns {
8452            glob_collect(cx, pat, &opts, &mut prefiltered);
8453        }
8454        spawn_fs_async(cx, "glob", patterns.join(","), callback, None, move || {
8455            Ok(FsAsyncResult::OkDirnames(prefiltered))
8456        });
8457        args.rval().set(UndefinedValue());
8458        return true;
8459    }
8460
8461    // No callback — behave like sync
8462    let mut results: Vec<String> = Vec::new();
8463    for pat in &patterns {
8464        glob_collect(cx, pat, &opts, &mut results);
8465    }
8466    let mut wrapped_cx =
8467        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8468    let cx_ref = &mut wrapped_cx;
8469    rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, results.len()));
8470    if arr.get().is_null() {
8471        args.rval().set(UndefinedValue());
8472        return true;
8473    }
8474    for (idx, path) in results.iter().enumerate() {
8475        let c_path = ZBox::from_bytes(path.as_bytes());
8476        let js_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
8477        if !js_str.is_null() {
8478            rooted!(&in(cx_ref) let val = mozjs::jsval::StringValue(&*js_str));
8479            JS_DefineElement(
8480                cx,
8481                arr.handle().into(),
8482                idx as u32,
8483                val.handle().into(),
8484                JSPROP_ENUMERATE as u32,
8485            );
8486        }
8487    }
8488    args.rval().set(mozjs::jsval::ObjectValue(arr.get()));
8489    true
8490}
8491
8492#[allow(unsafe_op_in_unsafe_fn)]
8493unsafe extern "C" fn fs_open_as_blob(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
8494    let args = CallArgs::from_vp(vp, argc);
8495    let path = match get_path_arg(cx, &args, 0) {
8496        ::std::result::Result::Ok(p) => p,
8497        ::std::result::Result::Err(b) => return b,
8498    };
8499    let mut wrapped_cx =
8500        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8501    let cx_ref = &mut wrapped_cx;
8502    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
8503    if promise.get().is_null() {
8504        args.rval().set(UndefinedValue());
8505        return false;
8506    }
8507
8508    match bun_fs::read(&path) {
8509        ::std::result::Result::Ok(data) => {
8510            let buf_obj = crate::globals::create_buffer_object(cx, &data);
8511            if !buf_obj.is_null() {
8512                // Create a Blob-like object with arrayBuffer() and size
8513                rooted!(&in(cx_ref) let blob_obj = JS_NewPlainObject(cx));
8514                if !blob_obj.get().is_null() {
8515                    define_num_prop(cx, blob_obj.get(), "size", data.len() as f64);
8516                    rooted!(&in(cx_ref) let buf_val = mozjs::jsval::ObjectValue(buf_obj));
8517                    JS_DefineProperty(
8518                        cx,
8519                        blob_obj.handle().into(),
8520                        c"_buffer".as_ptr(),
8521                        buf_val.handle().into(),
8522                        0,
8523                    );
8524                    rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(blob_obj.get()));
8525                    unsafe {
8526                        mozjs_sys::jsapi::JS::ResolvePromise(
8527                            cx,
8528                            promise.handle().into(),
8529                            val.handle().into(),
8530                        );
8531                    }
8532                } else {
8533                    resolve_undefined(cx, promise.get());
8534                }
8535            } else {
8536                resolve_undefined(cx, promise.get());
8537            }
8538        }
8539        ::std::result::Result::Err(e) => {
8540            reject_with_error(cx, promise.get(), &format!("openAsBlob '{}': {}", path, e))
8541        }
8542    }
8543    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
8544    true
8545}
8546
8547// --- Helper functions ---
8548
8549#[allow(unsafe_op_in_unsafe_fn)]
8550unsafe fn get_bool_option(cx: *mut JSContext, args: &CallArgs, opt_index: u32, key: &str) -> bool {
8551    if args.argc_ <= opt_index {
8552        return false;
8553    }
8554    let opt_val = *args.get(opt_index).ptr;
8555    if !opt_val.is_object() {
8556        return false;
8557    }
8558    let mut wrapped_cx_opt =
8559        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8560    let cx_ref_opt = &mut wrapped_cx_opt;
8561    rooted!(&in(cx_ref_opt) let obj = opt_val.to_object());
8562    let c_key = ZBox::from_bytes(key.as_bytes());
8563    let mut val = UndefinedValue();
8564    JS_GetProperty(
8565        cx,
8566        obj.handle().into(),
8567        c_key.as_ptr(),
8568        MutableHandle::<Value> {
8569            _phantom_0: ::std::marker::PhantomData,
8570            ptr: &mut val,
8571        },
8572    );
8573    val.is_boolean() && val.to_boolean()
8574}
8575
8576#[allow(unsafe_op_in_unsafe_fn)]
8577unsafe fn string_or_buffer(
8578    cx: *mut JSContext,
8579    data: &[u8],
8580    encoding: ::std::option::Option<&str>,
8581) -> JSVal {
8582    match encoding {
8583        Some("utf-8" | "utf8" | "text") | None => {
8584            // @trace REQ-ENG-005 — same mojibake class as return_string_content:
8585            // multibyte UTF-8 must go through JS_NewStringCopyUTF8N, not CopyZ.
8586            let s = ::std::string::String::from_utf8_lossy(data);
8587            let js_str = js_string_from_utf8(cx, &s);
8588            if js_str.is_null() {
8589                UndefinedValue()
8590            } else {
8591                mozjs::jsval::StringValue(&*js_str)
8592            }
8593        }
8594        Some("hex") => {
8595            let hex: ::std::string::String = bun_core::fmt::bytes_to_hex_lower_string(data);
8596            let c_str = ZBox::from_bytes(hex.as_bytes());
8597            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
8598            if js_str.is_null() {
8599                UndefinedValue()
8600            } else {
8601                mozjs::jsval::StringValue(&*js_str)
8602            }
8603        }
8604        Some("base64") => {
8605            // @trace REQ-ENG-005 [algorithm:base64]
8606            // SIMD-accelerated base64 encode via workspace bun_base64 (replaces crates.io base64).
8607            let encoded_bytes = bun_base64::encode_alloc(data);
8608            let encoded = ::std::str::from_utf8(&encoded_bytes).unwrap_or("");
8609            let c_str = ZBox::from_bytes(encoded.as_bytes());
8610            let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
8611            if js_str.is_null() {
8612                UndefinedValue()
8613            } else {
8614                mozjs::jsval::StringValue(&*js_str)
8615            }
8616        }
8617        _ => UndefinedValue(),
8618    }
8619}
8620
8621#[allow(unsafe_op_in_unsafe_fn)]
8622/// Convert `std::fs::Metadata` to `PosixStat` for use with `create_stats_object`.
8623/// Used as a bridge for `symlink_metadata` (lstat) which has no bun_sys equivalent yet.
8624#[cfg(unix)]
8625fn metadata_to_posix_stat(meta: &fs::Metadata) -> bun_sys::PosixStat {
8626    use ::std::os::unix::fs::MetadataExt;
8627    bun_sys::PosixStat {
8628        dev: meta.dev() as u64,
8629        ino: meta.ino() as u64,
8630        mode: meta.mode() as u64,
8631        nlink: meta.nlink() as u64,
8632        uid: meta.uid() as u64,
8633        gid: meta.gid() as u64,
8634        rdev: meta.rdev() as u64,
8635        size: meta.size(),
8636        blksize: meta.blksize() as u64,
8637        blocks: meta.blocks() as u64,
8638        atim: bun_sys::Timespec {
8639            sec: meta.atime(),
8640            nsec: meta.atime_nsec() as i64,
8641        },
8642        mtim: bun_sys::Timespec {
8643            sec: meta.mtime(),
8644            nsec: meta.mtime_nsec() as i64,
8645        },
8646        ctim: bun_sys::Timespec {
8647            sec: meta.ctime(),
8648            nsec: meta.ctime_nsec() as i64,
8649        },
8650        birthtim: bun_sys::Timespec { sec: 0, nsec: 0 },
8651    }
8652}
8653
8654#[cfg(not(unix))]
8655fn metadata_to_posix_stat(meta: &fs::Metadata) -> bun_sys::PosixStat {
8656    bun_sys::PosixStat {
8657        dev: 0,
8658        ino: 0,
8659        mode: 0,
8660        nlink: 0,
8661        uid: 0,
8662        gid: 0,
8663        rdev: 0,
8664        size: meta.len(),
8665        blksize: 0,
8666        blocks: 0,
8667        atim: bun_sys::Timespec { sec: 0, nsec: 0 },
8668        mtim: bun_sys::Timespec { sec: 0, nsec: 0 },
8669        ctim: bun_sys::Timespec { sec: 0, nsec: 0 },
8670        birthtim: bun_sys::Timespec { sec: 0, nsec: 0 },
8671    }
8672}
8673
8674/// Convert a raw `libc::stat` (from fstat/lstat syscalls) to `PosixStat`.
8675/// This is used by fd-based operations (fstat) that cannot go through
8676/// `std::fs::Metadata` since there is no `File` opened via Rust.
8677#[cfg(unix)]
8678fn posix_stat_from_libc(s: &libc::stat) -> bun_sys::PosixStat {
8679    bun_sys::PosixStat {
8680        dev: s.st_dev as u64,
8681        ino: s.st_ino as u64,
8682        mode: s.st_mode as u64,
8683        nlink: s.st_nlink as u64,
8684        uid: s.st_uid as u64,
8685        gid: s.st_gid as u64,
8686        rdev: s.st_rdev as u64,
8687        size: s.st_size as u64,
8688        blksize: s.st_blksize as u64,
8689        blocks: s.st_blocks as u64,
8690        atim: bun_sys::Timespec {
8691            sec: s.st_atime,
8692            nsec: s.st_atime_nsec as i64,
8693        },
8694        mtim: bun_sys::Timespec {
8695            sec: s.st_mtime,
8696            nsec: s.st_mtime_nsec as i64,
8697        },
8698        ctim: bun_sys::Timespec {
8699            sec: s.st_ctime,
8700            nsec: s.st_ctime_nsec as i64,
8701        },
8702        birthtim: bun_sys::Timespec { sec: 0, nsec: 0 },
8703    }
8704}
8705
8706#[cfg(not(unix))]
8707fn posix_stat_from_libc(_s: &libc::stat) -> bun_sys::PosixStat {
8708    bun_sys::PosixStat {
8709        dev: 0,
8710        ino: 0,
8711        mode: 0,
8712        nlink: 0,
8713        uid: 0,
8714        gid: 0,
8715        rdev: 0,
8716        size: 0,
8717        blksize: 0,
8718        blocks: 0,
8719        atim: bun_sys::Timespec { sec: 0, nsec: 0 },
8720        mtim: bun_sys::Timespec { sec: 0, nsec: 0 },
8721        ctim: bun_sys::Timespec { sec: 0, nsec: 0 },
8722        birthtim: bun_sys::Timespec { sec: 0, nsec: 0 },
8723    }
8724}
8725
8726#[allow(unsafe_op_in_unsafe_fn)]
8727unsafe fn create_stats_object(cx: *mut JSContext, meta: &bun_fs::PosixStat) -> *mut JSObject {
8728    let mut wrapped_cx =
8729        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8730    let cx_ref = &mut wrapped_cx;
8731    rooted!(&in(cx_ref) let stats = JS_NewPlainObject(cx));
8732    if stats.get().is_null() {
8733        return stats.get();
8734    }
8735
8736    // Determine file type from mode (S_IFMT bits)
8737    let mode_type = (meta.mode as u32) & libc::S_IFMT;
8738    let is_file = mode_type == libc::S_IFREG;
8739    let is_dir = mode_type == libc::S_IFDIR;
8740    let is_symlink = mode_type == libc::S_IFLNK;
8741    let is_block_device = mode_type == libc::S_IFBLK;
8742    let is_character_device = mode_type == libc::S_IFCHR;
8743    let is_fifo = mode_type == libc::S_IFIFO;
8744    let is_socket = mode_type == libc::S_IFSOCK;
8745
8746    let atime_ms = meta.atim.sec as f64 * 1000.0 + meta.atim.nsec as f64 / 1_000_000.0;
8747    let mtime_ms = meta.mtim.sec as f64 * 1000.0 + meta.mtim.nsec as f64 / 1_000_000.0;
8748    let ctime_ms = meta.ctim.sec as f64 * 1000.0 + meta.ctim.nsec as f64 / 1_000_000.0;
8749    // On Linux, birthtime is often not available; fall back to ctime
8750    let birthtime_ms = if meta.birthtim.sec == 0 && meta.birthtim.nsec == 0 {
8751        ctime_ms
8752    } else {
8753        meta.birthtim.sec as f64 * 1000.0 + meta.birthtim.nsec as f64 / 1_000_000.0
8754    };
8755
8756    define_num_prop(cx, stats.get(), "size", meta.size as f64);
8757    define_num_prop(cx, stats.get(), "dev", meta.dev as f64);
8758    define_num_prop(cx, stats.get(), "ino", meta.ino as f64);
8759    define_num_prop(cx, stats.get(), "mode", meta.mode as f64);
8760    define_num_prop(cx, stats.get(), "nlink", meta.nlink as f64);
8761    define_num_prop(cx, stats.get(), "uid", meta.uid as f64);
8762    define_num_prop(cx, stats.get(), "gid", meta.gid as f64);
8763    define_num_prop(cx, stats.get(), "rdev", meta.rdev as f64);
8764    define_num_prop(cx, stats.get(), "blksize", meta.blksize as f64);
8765    define_num_prop(cx, stats.get(), "blocks", meta.blocks as f64);
8766    define_num_prop(cx, stats.get(), "atimeMs", atime_ms);
8767    define_num_prop(cx, stats.get(), "mtimeMs", mtime_ms);
8768    define_num_prop(cx, stats.get(), "ctimeMs", ctime_ms);
8769    define_num_prop(cx, stats.get(), "birthtimeMs", birthtime_ms);
8770
8771    // Date objects for atime, mtime, ctime, birthtime
8772    let date_props: [(&str, f64); 4] = [
8773        ("atime", atime_ms),
8774        ("mtime", mtime_ms),
8775        ("ctime", ctime_ms),
8776        ("birthtime", birthtime_ms),
8777    ];
8778    for (prop, ms) in &date_props {
8779        let date_obj = w2::NewDateObject(cx_ref, mozjs::jsapi::ClippedTime { t: *ms });
8780        if !date_obj.is_null() {
8781            rooted!(&in(cx_ref) let date_val = mozjs::jsval::ObjectValue(date_obj));
8782            let c_prop = ZBox::from_bytes(prop.as_bytes());
8783            JS_DefineProperty(
8784                cx,
8785                stats.handle().into(),
8786                c_prop.as_ptr(),
8787                date_val.handle().into(),
8788                JSPROP_ENUMERATE as u32,
8789            );
8790        }
8791    }
8792
8793    // Store boolean values as hidden properties for method callbacks
8794    define_bool_prop(cx, stats.get(), "_isFile", is_file);
8795    define_bool_prop(cx, stats.get(), "_isDirectory", is_dir);
8796    define_bool_prop(cx, stats.get(), "_isSymbolicLink", is_symlink);
8797    define_bool_prop(cx, stats.get(), "_isBlockDevice", is_block_device);
8798    define_bool_prop(cx, stats.get(), "_isCharacterDevice", is_character_device);
8799    define_bool_prop(cx, stats.get(), "_isFIFO", is_fifo);
8800    define_bool_prop(cx, stats.get(), "_isSocket", is_socket);
8801
8802    // Node.js Stats methods
8803    w2::JS_DefineFunction(
8804        cx_ref,
8805        stats.handle().into(),
8806        c"isFile".as_ptr(),
8807        Some(stats_is_file),
8808        0,
8809        JSPROP_ENUMERATE as u32,
8810    );
8811    w2::JS_DefineFunction(
8812        cx_ref,
8813        stats.handle().into(),
8814        c"isDirectory".as_ptr(),
8815        Some(stats_is_directory),
8816        0,
8817        JSPROP_ENUMERATE as u32,
8818    );
8819    w2::JS_DefineFunction(
8820        cx_ref,
8821        stats.handle().into(),
8822        c"isSymbolicLink".as_ptr(),
8823        Some(stats_is_symlink),
8824        0,
8825        JSPROP_ENUMERATE as u32,
8826    );
8827    w2::JS_DefineFunction(
8828        cx_ref,
8829        stats.handle().into(),
8830        c"isBlockDevice".as_ptr(),
8831        Some(stats_is_block_device),
8832        0,
8833        JSPROP_ENUMERATE as u32,
8834    );
8835    w2::JS_DefineFunction(
8836        cx_ref,
8837        stats.handle().into(),
8838        c"isCharacterDevice".as_ptr(),
8839        Some(stats_is_character_device),
8840        0,
8841        JSPROP_ENUMERATE as u32,
8842    );
8843    w2::JS_DefineFunction(
8844        cx_ref,
8845        stats.handle().into(),
8846        c"isFIFO".as_ptr(),
8847        Some(stats_is_fifo),
8848        0,
8849        JSPROP_ENUMERATE as u32,
8850    );
8851    w2::JS_DefineFunction(
8852        cx_ref,
8853        stats.handle().into(),
8854        c"isSocket".as_ptr(),
8855        Some(stats_is_socket),
8856        0,
8857        JSPROP_ENUMERATE as u32,
8858    );
8859
8860    stats.get()
8861}
8862
8863#[allow(unsafe_op_in_unsafe_fn)]
8864unsafe fn create_dirent(cx: *mut JSContext, name: &str, is_dir: bool) -> *mut JSObject {
8865    let mut wrapped_cx =
8866        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8867    let cx_ref = &mut wrapped_cx;
8868    rooted!(&in(cx_ref) let dirent = JS_NewPlainObject(cx));
8869    if dirent.get().is_null() {
8870        return dirent.get();
8871    }
8872    let c_name = ZBox::from_bytes(name.as_bytes());
8873    let js_str = JS_NewStringCopyZ(cx, c_name.as_ptr());
8874    if !js_str.is_null() {
8875        rooted!(&in(cx_ref) let name_val = mozjs::jsval::StringValue(&*js_str));
8876        JS_DefineProperty(
8877            cx,
8878            dirent.handle().into(),
8879            c"name".as_ptr(),
8880            name_val.handle().into(),
8881            JSPROP_ENUMERATE as u32,
8882        );
8883    }
8884    // Type code: 0=file, 1=dir, 2=symlink, 3=block, 4=char, 5=fifo, 6=socket
8885    let type_code: i32 = if is_dir { 1 } else { 0 };
8886    set_hidden_int(cx, dirent.get(), "_typeCode", type_code);
8887    define_bool_prop(cx, dirent.get(), "isFile", !is_dir);
8888    define_bool_prop(cx, dirent.get(), "isDirectory", is_dir);
8889
8890    w2::JS_DefineFunction(
8891        cx_ref,
8892        dirent.handle().into(),
8893        c"isFile".as_ptr(),
8894        Some(dirent_is_file),
8895        0,
8896        JSPROP_ENUMERATE as u32,
8897    );
8898    w2::JS_DefineFunction(
8899        cx_ref,
8900        dirent.handle().into(),
8901        c"isDirectory".as_ptr(),
8902        Some(dirent_is_directory),
8903        0,
8904        JSPROP_ENUMERATE as u32,
8905    );
8906    w2::JS_DefineFunction(
8907        cx_ref,
8908        dirent.handle().into(),
8909        c"isSymbolicLink".as_ptr(),
8910        Some(dirent_is_symbolic_link),
8911        0,
8912        JSPROP_ENUMERATE as u32,
8913    );
8914    w2::JS_DefineFunction(
8915        cx_ref,
8916        dirent.handle().into(),
8917        c"isBlockDevice".as_ptr(),
8918        Some(dirent_is_block_device),
8919        0,
8920        JSPROP_ENUMERATE as u32,
8921    );
8922    w2::JS_DefineFunction(
8923        cx_ref,
8924        dirent.handle().into(),
8925        c"isCharacterDevice".as_ptr(),
8926        Some(dirent_is_character_device),
8927        0,
8928        JSPROP_ENUMERATE as u32,
8929    );
8930    w2::JS_DefineFunction(
8931        cx_ref,
8932        dirent.handle().into(),
8933        c"isFIFO".as_ptr(),
8934        Some(dirent_is_fifo),
8935        0,
8936        JSPROP_ENUMERATE as u32,
8937    );
8938    w2::JS_DefineFunction(
8939        cx_ref,
8940        dirent.handle().into(),
8941        c"isSocket".as_ptr(),
8942        Some(dirent_is_socket),
8943        0,
8944        JSPROP_ENUMERATE as u32,
8945    );
8946
8947    dirent.get()
8948}
8949
8950#[allow(unsafe_op_in_unsafe_fn)]
8951unsafe fn resolve_undefined(cx: *mut JSContext, promise: *mut JSObject) {
8952    let mut wrapped_cx =
8953        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8954    let cx_ref = &mut wrapped_cx;
8955    rooted!(&in(cx_ref) let val = UndefinedValue());
8956    rooted!(&in(cx_ref) let promise_rooted = promise);
8957    mozjs_sys::jsapi::JS::ResolvePromise(cx, promise_rooted.handle().into(), val.handle().into());
8958}
8959
8960#[allow(unsafe_op_in_unsafe_fn)]
8961unsafe fn reject_with_error(cx: *mut JSContext, promise: *mut JSObject, msg: &str) {
8962    let mut wrapped_cx =
8963        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8964    let cx_ref = &mut wrapped_cx;
8965    rooted!(&in(cx_ref) let err_obj = JS_NewPlainObject(cx));
8966    if !err_obj.get().is_null() {
8967        let c_msg = ZBox::from_bytes(msg.as_bytes());
8968        let js_str = JS_NewStringCopyZ(cx, c_msg.as_ptr());
8969        if !js_str.is_null() {
8970            rooted!(&in(cx_ref) let msg_val = mozjs::jsval::StringValue(&*js_str));
8971            JS_DefineProperty(
8972                cx,
8973                err_obj.handle().into(),
8974                c"message".as_ptr(),
8975                msg_val.handle().into(),
8976                JSPROP_ENUMERATE as u32,
8977            );
8978        }
8979    }
8980    rooted!(&in(cx_ref) let err_val = mozjs::jsval::ObjectValue(err_obj.get()));
8981    rooted!(&in(cx_ref) let promise_rooted = promise);
8982    mozjs_sys::jsapi::JS::RejectPromise(
8983        cx,
8984        promise_rooted.handle().into(),
8985        err_val.handle().into(),
8986    );
8987}
8988
8989#[allow(unsafe_op_in_unsafe_fn)]
8990unsafe fn define_num_prop(cx: *mut JSContext, obj_ptr: *mut JSObject, name: &str, val: f64) {
8991    let c_name = ZBox::from_bytes(name.as_bytes());
8992    let mut wrapped_cx =
8993        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
8994    let cx_ref = &mut wrapped_cx;
8995    rooted!(&in(cx_ref) let obj = obj_ptr);
8996    let js_val = if val == (val as i32) as f64 && val.abs() < i32::MAX as f64 {
8997        mozjs::jsval::Int32Value(val as i32)
8998    } else {
8999        mozjs::jsval::DoubleValue(val)
9000    };
9001    rooted!(&in(cx_ref) let v = js_val);
9002    JS_DefineProperty(
9003        cx,
9004        obj.handle().into(),
9005        c_name.as_ptr(),
9006        v.handle().into(),
9007        JSPROP_ENUMERATE as u32,
9008    );
9009}
9010
9011#[allow(unsafe_op_in_unsafe_fn)]
9012unsafe fn define_bool_prop(cx: *mut JSContext, obj_ptr: *mut JSObject, name: &str, val: bool) {
9013    let c_name = ZBox::from_bytes(name.as_bytes());
9014    let mut wrapped_cx =
9015        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9016    let cx_ref = &mut wrapped_cx;
9017    rooted!(&in(cx_ref) let obj = obj_ptr);
9018    rooted!(&in(cx_ref) let v = mozjs::jsval::BooleanValue(val));
9019    JS_DefineProperty(
9020        cx,
9021        obj.handle().into(),
9022        c_name.as_ptr(),
9023        v.handle().into(),
9024        JSPROP_ENUMERATE as u32,
9025    );
9026}
9027
9028#[allow(unsafe_op_in_unsafe_fn)]
9029unsafe fn set_hidden_int(cx: *mut JSContext, obj: *mut JSObject, prop: &str, val: i32) {
9030    let c_name = ZBox::from_bytes(prop.as_bytes());
9031    let mut wrapped_cx =
9032        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9033    let cx_ref = &mut wrapped_cx;
9034    rooted!(&in(cx_ref) let obj_rooted = obj);
9035    rooted!(&in(cx_ref) let v = mozjs::jsval::Int32Value(val));
9036    JS_DefineProperty(
9037        cx,
9038        obj_rooted.handle().into(),
9039        c_name.as_ptr(),
9040        v.handle().into(),
9041        (JSPROP_ENUMERATE | JSPROP_PERMANENT) as u32,
9042    );
9043}
9044
9045#[allow(unsafe_op_in_unsafe_fn)]
9046unsafe fn set_hidden_bool(cx: *mut JSContext, obj: *mut JSObject, prop: &str, val: bool) {
9047    let c_name = ZBox::from_bytes(prop.as_bytes());
9048    let mut wrapped_cx =
9049        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9050    let cx_ref = &mut wrapped_cx;
9051    rooted!(&in(cx_ref) let obj_rooted = obj);
9052    rooted!(&in(cx_ref) let v = mozjs::jsval::BooleanValue(val));
9053    JS_DefineProperty(
9054        cx,
9055        obj_rooted.handle().into(),
9056        c_name.as_ptr(),
9057        v.handle().into(),
9058        (JSPROP_ENUMERATE | JSPROP_PERMANENT) as u32,
9059    );
9060}
9061
9062#[allow(unsafe_op_in_unsafe_fn)]
9063unsafe fn get_hidden_int(cx: *mut JSContext, obj: *mut JSObject, prop: &str) -> i32 {
9064    let c_name = ZBox::from_bytes(prop.as_bytes());
9065    let mut wrapped_cx =
9066        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9067    let cx_ref = &mut wrapped_cx;
9068    rooted!(&in(cx_ref) let obj_rooted = obj);
9069    let mut val = UndefinedValue();
9070    JS_GetProperty(
9071        cx,
9072        obj_rooted.handle().into(),
9073        c_name.as_ptr(),
9074        MutableHandle::<Value> {
9075            _phantom_0: ::std::marker::PhantomData,
9076            ptr: &mut val,
9077        },
9078    );
9079    if val.is_int32() { val.to_int32() } else { -1 }
9080}
9081
9082/// Build a Stats object from a `libc::stat` (used by FileHandle.stat).
9083unsafe fn build_stats_object(cx: *mut JSContext, st: &libc::stat) -> *mut JSObject {
9084    let mut wrapped_cx =
9085        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9086    let cx_ref = &mut wrapped_cx;
9087    rooted!(&in(cx_ref) let stats = JS_NewPlainObject(cx));
9088    if stats.get().is_null() {
9089        return stats.get();
9090    }
9091
9092    let mode_type = st.st_mode & libc::S_IFMT;
9093    let is_file = mode_type == libc::S_IFREG;
9094    let is_dir = mode_type == libc::S_IFDIR;
9095    let is_symlink = mode_type == libc::S_IFLNK;
9096    let is_block_device = mode_type == libc::S_IFBLK;
9097    let is_character_device = mode_type == libc::S_IFCHR;
9098    let is_fifo = mode_type == libc::S_IFIFO;
9099    let is_socket = mode_type == libc::S_IFSOCK;
9100
9101    let atime_ms = st.st_atime as f64 * 1000.0 + st.st_atime_nsec as f64 / 1_000_000.0;
9102    let mtime_ms = st.st_mtime as f64 * 1000.0 + st.st_mtime_nsec as f64 / 1_000_000.0;
9103    let ctime_ms = st.st_ctime as f64 * 1000.0 + st.st_ctime_nsec as f64 / 1_000_000.0;
9104    let birthtime_ms = ctime_ms; // Linux fallback
9105
9106    define_num_prop(cx, stats.get(), "size", st.st_size as f64);
9107    define_num_prop(cx, stats.get(), "dev", st.st_dev as f64);
9108    define_num_prop(cx, stats.get(), "ino", st.st_ino as f64);
9109    define_num_prop(cx, stats.get(), "mode", st.st_mode as f64);
9110    define_num_prop(cx, stats.get(), "nlink", st.st_nlink as f64);
9111    define_num_prop(cx, stats.get(), "uid", st.st_uid as f64);
9112    define_num_prop(cx, stats.get(), "gid", st.st_gid as f64);
9113    define_num_prop(cx, stats.get(), "rdev", st.st_rdev as f64);
9114    define_num_prop(cx, stats.get(), "blksize", st.st_blksize as f64);
9115    define_num_prop(cx, stats.get(), "blocks", st.st_blocks as f64);
9116    define_num_prop(cx, stats.get(), "atimeMs", atime_ms);
9117    define_num_prop(cx, stats.get(), "mtimeMs", mtime_ms);
9118    define_num_prop(cx, stats.get(), "ctimeMs", ctime_ms);
9119    define_num_prop(cx, stats.get(), "birthtimeMs", birthtime_ms);
9120
9121    // Date objects for atime, mtime, ctime, birthtime
9122    let date_props: [(&str, f64); 4] = [
9123        ("atime", atime_ms),
9124        ("mtime", mtime_ms),
9125        ("ctime", ctime_ms),
9126        ("birthtime", birthtime_ms),
9127    ];
9128    for (prop, ms) in &date_props {
9129        let date_obj = w2::NewDateObject(cx_ref, mozjs::jsapi::ClippedTime { t: *ms });
9130        if !date_obj.is_null() {
9131            rooted!(&in(cx_ref) let date_val = mozjs::jsval::ObjectValue(date_obj));
9132            let c_prop = ZBox::from_bytes(prop.as_bytes());
9133            JS_DefineProperty(
9134                cx,
9135                stats.handle().into(),
9136                c_prop.as_ptr(),
9137                date_val.handle().into(),
9138                JSPROP_ENUMERATE as u32,
9139            );
9140        }
9141    }
9142
9143    define_bool_prop(cx, stats.get(), "_isFile", is_file);
9144    define_bool_prop(cx, stats.get(), "_isDirectory", is_dir);
9145    define_bool_prop(cx, stats.get(), "_isSymbolicLink", is_symlink);
9146    define_bool_prop(cx, stats.get(), "_isBlockDevice", is_block_device);
9147    define_bool_prop(cx, stats.get(), "_isCharacterDevice", is_character_device);
9148    define_bool_prop(cx, stats.get(), "_isFIFO", is_fifo);
9149    define_bool_prop(cx, stats.get(), "_isSocket", is_socket);
9150
9151    w2::JS_DefineFunction(
9152        cx_ref,
9153        stats.handle().into(),
9154        c"isFile".as_ptr(),
9155        Some(stats_is_file),
9156        0,
9157        JSPROP_ENUMERATE as u32,
9158    );
9159    w2::JS_DefineFunction(
9160        cx_ref,
9161        stats.handle().into(),
9162        c"isDirectory".as_ptr(),
9163        Some(stats_is_directory),
9164        0,
9165        JSPROP_ENUMERATE as u32,
9166    );
9167    w2::JS_DefineFunction(
9168        cx_ref,
9169        stats.handle().into(),
9170        c"isSymbolicLink".as_ptr(),
9171        Some(stats_is_symlink),
9172        0,
9173        JSPROP_ENUMERATE as u32,
9174    );
9175    w2::JS_DefineFunction(
9176        cx_ref,
9177        stats.handle().into(),
9178        c"isBlockDevice".as_ptr(),
9179        Some(stats_is_block_device),
9180        0,
9181        JSPROP_ENUMERATE as u32,
9182    );
9183    w2::JS_DefineFunction(
9184        cx_ref,
9185        stats.handle().into(),
9186        c"isCharacterDevice".as_ptr(),
9187        Some(stats_is_character_device),
9188        0,
9189        JSPROP_ENUMERATE as u32,
9190    );
9191    w2::JS_DefineFunction(
9192        cx_ref,
9193        stats.handle().into(),
9194        c"isFIFO".as_ptr(),
9195        Some(stats_is_fifo),
9196        0,
9197        JSPROP_ENUMERATE as u32,
9198    );
9199    w2::JS_DefineFunction(
9200        cx_ref,
9201        stats.handle().into(),
9202        c"isSocket".as_ptr(),
9203        Some(stats_is_socket),
9204        0,
9205        JSPROP_ENUMERATE as u32,
9206    );
9207
9208    stats.get()
9209}
9210
9211#[allow(unsafe_op_in_unsafe_fn)]
9212unsafe fn get_hidden_bool(cx: *mut JSContext, obj: *mut JSObject, prop: &str) -> bool {
9213    let c_name = ZBox::from_bytes(prop.as_bytes());
9214    let mut wrapped_cx =
9215        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9216    let cx_ref = &mut wrapped_cx;
9217    rooted!(&in(cx_ref) let obj_rooted = obj);
9218    let mut val = UndefinedValue();
9219    JS_GetProperty(
9220        cx,
9221        obj_rooted.handle().into(),
9222        c_name.as_ptr(),
9223        MutableHandle::<Value> {
9224            _phantom_0: ::std::marker::PhantomData,
9225            ptr: &mut val,
9226        },
9227    );
9228    val.to_boolean()
9229}
9230
9231#[allow(unsafe_op_in_unsafe_fn)]
9232unsafe extern "C" fn stats_is_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9233    let args = CallArgs::from_vp(vp, argc);
9234    let mut wrapped_cx =
9235        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9236    let cx_ref = &mut wrapped_cx;
9237    rooted!(&in(cx_ref) let this = args.thisv().to_object());
9238    args.rval().set(mozjs::jsval::BooleanValue(get_hidden_bool(
9239        cx,
9240        this.get(),
9241        "_isFile",
9242    )));
9243    true
9244}
9245
9246#[allow(unsafe_op_in_unsafe_fn)]
9247unsafe extern "C" fn stats_is_directory(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9248    let args = CallArgs::from_vp(vp, argc);
9249    let mut wrapped_cx =
9250        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9251    let cx_ref = &mut wrapped_cx;
9252    rooted!(&in(cx_ref) let this = args.thisv().to_object());
9253    args.rval().set(mozjs::jsval::BooleanValue(get_hidden_bool(
9254        cx,
9255        this.get(),
9256        "_isDirectory",
9257    )));
9258    true
9259}
9260// --- FileHandle class for fs/promises ---
9261//
9262// FileHandle wraps a raw fd and provides async read/write/close/stat etc.
9263// Internal state is stored as hidden properties on the JS object:
9264//   _fd       — the raw file descriptor (i32)
9265//   _refs     — reference count (starts at 1, close only when 0)
9266//   _closed   — whether the fd has been closed (bool)
9267
9268/// Create a FileHandle JS object wrapping the given fd.
9269unsafe fn create_filehandle_object(cx: *mut JSContext, fd: i32) -> *mut JSObject {
9270    let mut wrapped_cx =
9271        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9272    let cx_ref = &mut wrapped_cx;
9273    rooted!(&in(cx_ref) let fh = mozjs_sys::jsapi::JS_NewPlainObject(cx));
9274    if fh.get().is_null() {
9275        return ::std::ptr::null_mut();
9276    }
9277
9278    set_hidden_int(cx, fh.get(), "_fd", fd);
9279    set_hidden_int(cx, fh.get(), "_refs", 1);
9280    set_hidden_bool(cx, fh.get(), "_closed", false);
9281
9282    rooted!(&in(cx_ref) let fd_val = mozjs::jsval::Int32Value(fd));
9283    JS_DefineProperty(
9284        cx,
9285        fh.handle().into(),
9286        c"fd".as_ptr(),
9287        fd_val.handle().into(),
9288        (JSPROP_ENUMERATE | JSPROP_READONLY) as u32,
9289    );
9290    JS_DefineFunction(
9291        cx,
9292        fh.handle().into(),
9293        c"read".as_ptr(),
9294        Some(fh_read),
9295        4,
9296        JSPROP_ENUMERATE as u32,
9297    );
9298    JS_DefineFunction(
9299        cx,
9300        fh.handle().into(),
9301        c"write".as_ptr(),
9302        Some(fh_write),
9303        4,
9304        JSPROP_ENUMERATE as u32,
9305    );
9306    JS_DefineFunction(
9307        cx,
9308        fh.handle().into(),
9309        c"close".as_ptr(),
9310        Some(fh_close),
9311        0,
9312        JSPROP_ENUMERATE as u32,
9313    );
9314    JS_DefineFunction(
9315        cx,
9316        fh.handle().into(),
9317        c"stat".as_ptr(),
9318        Some(fh_stat),
9319        0,
9320        JSPROP_ENUMERATE as u32,
9321    );
9322    JS_DefineFunction(
9323        cx,
9324        fh.handle().into(),
9325        c"chmod".as_ptr(),
9326        Some(fh_chmod),
9327        1,
9328        JSPROP_ENUMERATE as u32,
9329    );
9330    JS_DefineFunction(
9331        cx,
9332        fh.handle().into(),
9333        c"chown".as_ptr(),
9334        Some(fh_chown),
9335        2,
9336        JSPROP_ENUMERATE as u32,
9337    );
9338    JS_DefineFunction(
9339        cx,
9340        fh.handle().into(),
9341        c"datasync".as_ptr(),
9342        Some(fh_datasync),
9343        0,
9344        JSPROP_ENUMERATE as u32,
9345    );
9346    JS_DefineFunction(
9347        cx,
9348        fh.handle().into(),
9349        c"sync".as_ptr(),
9350        Some(fh_sync),
9351        0,
9352        JSPROP_ENUMERATE as u32,
9353    );
9354    JS_DefineFunction(
9355        cx,
9356        fh.handle().into(),
9357        c"truncate".as_ptr(),
9358        Some(fh_truncate),
9359        1,
9360        JSPROP_ENUMERATE as u32,
9361    );
9362    JS_DefineFunction(
9363        cx,
9364        fh.handle().into(),
9365        c"utimes".as_ptr(),
9366        Some(fh_utimes),
9367        2,
9368        JSPROP_ENUMERATE as u32,
9369    );
9370    JS_DefineFunction(
9371        cx,
9372        fh.handle().into(),
9373        c"appendFile".as_ptr(),
9374        Some(fh_append_file),
9375        1,
9376        JSPROP_ENUMERATE as u32,
9377    );
9378    JS_DefineFunction(
9379        cx,
9380        fh.handle().into(),
9381        c"readFile".as_ptr(),
9382        Some(fh_read_file),
9383        0,
9384        JSPROP_ENUMERATE as u32,
9385    );
9386    JS_DefineFunction(
9387        cx,
9388        fh.handle().into(),
9389        c"writeFile".as_ptr(),
9390        Some(fh_write_file),
9391        1,
9392        JSPROP_ENUMERATE as u32,
9393    );
9394
9395    fh.get()
9396}
9397
9398/// FileHandle constructor — callable as `new FileHandle(fd)`.
9399#[allow(unsafe_op_in_unsafe_fn)]
9400unsafe extern "C" fn fs_promises_filehandle_ctor(
9401    cx: *mut JSContext,
9402    argc: u32,
9403    vp: *mut JSVal,
9404) -> bool {
9405    let args = CallArgs::from_vp(vp, argc);
9406    let fd_val = if argc > 0 {
9407        *args.get(0).ptr
9408    } else {
9409        UndefinedValue()
9410    };
9411    let fd = if fd_val.is_int32() {
9412        fd_val.to_int32()
9413    } else {
9414        -1
9415    };
9416    if fd < 0 {
9417        JS_ReportErrorUTF8(cx, c"FileHandle requires a valid file descriptor".as_ptr());
9418        args.rval().set(UndefinedValue());
9419        return false;
9420    }
9421    let fh = create_filehandle_object(cx, fd);
9422    if fh.is_null() {
9423        args.rval().set(UndefinedValue());
9424        return false;
9425    }
9426    args.rval().set(mozjs::jsval::ObjectValue(fh));
9427    true
9428}
9429
9430#[allow(unsafe_op_in_unsafe_fn)]
9431unsafe extern "C" fn fh_read(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9432    let args = CallArgs::from_vp(vp, argc);
9433    let this = args.thisv();
9434    if !this.is_object() {
9435        JS_ReportErrorUTF8(
9436            cx,
9437            c"FileHandle.read() must be called on a FileHandle instance".as_ptr(),
9438        );
9439        args.rval().set(UndefinedValue());
9440        return false;
9441    }
9442    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9443    let mut wrapped_cx =
9444        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9445    let cx_ref = &mut wrapped_cx;
9446    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9447    if promise.get().is_null() {
9448        args.rval().set(UndefinedValue());
9449        return false;
9450    }
9451
9452    if fd < 0 {
9453        reject_with_error(
9454            cx,
9455            promise.get(),
9456            "FileHandle: fd is invalid (already closed)",
9457        );
9458        args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9459        return true;
9460    }
9461
9462    let length = if argc > 2 {
9463        let v = *args.get(2).ptr;
9464        if v.is_int32() {
9465            v.to_int32() as usize
9466        } else {
9467            65536
9468        }
9469    } else {
9470        65536
9471    };
9472    let position = if argc > 3 {
9473        let v = *args.get(3).ptr;
9474        if v.is_int32() {
9475            v.to_int32() as i64
9476        } else if v.is_double() {
9477            v.to_double() as i64
9478        } else {
9479            -1
9480        }
9481    } else {
9482        -1
9483    };
9484
9485    let mut buf = vec![0u8; length];
9486    #[cfg(unix)]
9487    {
9488        let bytes_read = if position >= 0 {
9489            unsafe {
9490                libc::lseek(fd, position, libc::SEEK_SET);
9491            }
9492            unsafe { libc::read(fd, buf.as_mut_ptr() as *mut ::std::ffi::c_void, length) }
9493        } else {
9494            unsafe { libc::read(fd, buf.as_mut_ptr() as *mut ::std::ffi::c_void, length) }
9495        };
9496        if bytes_read >= 0 {
9497            buf.truncate(bytes_read as usize);
9498            let buf_obj = crate::globals::create_buffer_object(cx, &buf);
9499            if !buf_obj.is_null() {
9500                rooted!(&in(cx_ref) let result_obj = mozjs_sys::jsapi::JS_NewPlainObject(cx));
9501                if !result_obj.get().is_null() {
9502                    rooted!(&in(cx_ref) let br_val = mozjs::jsval::DoubleValue(bytes_read as f64));
9503                    JS_DefineProperty(
9504                        cx,
9505                        result_obj.handle().into(),
9506                        c"bytesRead".as_ptr(),
9507                        br_val.handle().into(),
9508                        JSPROP_ENUMERATE as u32,
9509                    );
9510                    rooted!(&in(cx_ref) let buf_val = mozjs::jsval::ObjectValue(buf_obj));
9511                    JS_DefineProperty(
9512                        cx,
9513                        result_obj.handle().into(),
9514                        c"buffer".as_ptr(),
9515                        buf_val.handle().into(),
9516                        JSPROP_ENUMERATE as u32,
9517                    );
9518                    rooted!(&in(cx_ref) let result_val = mozjs::jsval::ObjectValue(result_obj.get()));
9519                    unsafe {
9520                        mozjs_sys::jsapi::JS::ResolvePromise(
9521                            cx,
9522                            promise.handle().into(),
9523                            result_val.handle().into(),
9524                        );
9525                    }
9526                } else {
9527                    resolve_undefined(cx, promise.get());
9528                }
9529            } else {
9530                resolve_undefined(cx, promise.get());
9531            }
9532        } else {
9533            reject_with_error(
9534                cx,
9535                promise.get(),
9536                &format!("FileHandle.read: {}", ::std::io::Error::last_os_error()),
9537            );
9538        }
9539    }
9540    #[cfg(not(unix))]
9541    {
9542        resolve_undefined(cx, promise.get());
9543    }
9544    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9545    true
9546}
9547
9548#[allow(unsafe_op_in_unsafe_fn)]
9549unsafe extern "C" fn fh_write(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9550    let args = CallArgs::from_vp(vp, argc);
9551    let this = args.thisv();
9552    if !this.is_object() {
9553        JS_ReportErrorUTF8(
9554            cx,
9555            c"FileHandle.write() must be called on a FileHandle instance".as_ptr(),
9556        );
9557        args.rval().set(UndefinedValue());
9558        return false;
9559    }
9560    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9561    let mut wrapped_cx =
9562        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9563    let cx_ref = &mut wrapped_cx;
9564    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9565    if promise.get().is_null() {
9566        args.rval().set(UndefinedValue());
9567        return false;
9568    }
9569
9570    if fd < 0 {
9571        reject_with_error(
9572            cx,
9573            promise.get(),
9574            "FileHandle: fd is invalid (already closed)",
9575        );
9576        args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9577        return true;
9578    }
9579
9580    let data = if argc > 0 && (*args.get(0).ptr).is_object() {
9581        crate::node_crypto::extract_buffer_bytes(cx, *args.get(0).ptr)
9582    } else if argc > 0 && (*args.get(0).ptr).is_string() {
9583        let s = (*args.get(0).ptr).to_string();
9584        if !s.is_null() {
9585            crate::jsstr_to_rust_string(cx, s).into_bytes()
9586        } else {
9587            Vec::new()
9588        }
9589    } else {
9590        Vec::new()
9591    };
9592
9593    let position = if argc > 3 {
9594        let v = *args.get(3).ptr;
9595        if v.is_int32() {
9596            v.to_int32() as i64
9597        } else if v.is_double() {
9598            v.to_double() as i64
9599        } else {
9600            -1
9601        }
9602    } else {
9603        -1
9604    };
9605
9606    #[cfg(unix)]
9607    {
9608        let bytes_written = if position >= 0 {
9609            unsafe {
9610                libc::lseek(fd, position, libc::SEEK_SET);
9611            }
9612            unsafe { libc::write(fd, data.as_ptr() as *const ::std::ffi::c_void, data.len()) }
9613        } else {
9614            unsafe { libc::write(fd, data.as_ptr() as *const ::std::ffi::c_void, data.len()) }
9615        };
9616        if bytes_written >= 0 {
9617            rooted!(&in(cx_ref) let result_obj = mozjs_sys::jsapi::JS_NewPlainObject(cx));
9618            if !result_obj.get().is_null() {
9619                rooted!(&in(cx_ref) let bw_val = mozjs::jsval::DoubleValue(bytes_written as f64));
9620                JS_DefineProperty(
9621                    cx,
9622                    result_obj.handle().into(),
9623                    c"bytesWritten".as_ptr(),
9624                    bw_val.handle().into(),
9625                    JSPROP_ENUMERATE as u32,
9626                );
9627                let buf_obj = crate::globals::create_buffer_object(cx, &data);
9628                if !buf_obj.is_null() {
9629                    rooted!(&in(cx_ref) let buf_val = mozjs::jsval::ObjectValue(buf_obj));
9630                    JS_DefineProperty(
9631                        cx,
9632                        result_obj.handle().into(),
9633                        c"buffer".as_ptr(),
9634                        buf_val.handle().into(),
9635                        JSPROP_ENUMERATE as u32,
9636                    );
9637                }
9638                unsafe {
9639                    rooted!(&in(cx_ref) let result_val = mozjs::jsval::ObjectValue(result_obj.get()));
9640                    mozjs_sys::jsapi::JS::ResolvePromise(
9641                        cx,
9642                        promise.handle().into(),
9643                        result_val.handle().into(),
9644                    );
9645                }
9646            } else {
9647                resolve_undefined(cx, promise.get());
9648            }
9649        } else {
9650            reject_with_error(
9651                cx,
9652                promise.get(),
9653                &format!("FileHandle.write: {}", ::std::io::Error::last_os_error()),
9654            );
9655        }
9656    }
9657    #[cfg(not(unix))]
9658    {
9659        resolve_undefined(cx, promise.get());
9660    }
9661    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9662    true
9663}
9664
9665#[allow(unsafe_op_in_unsafe_fn)]
9666unsafe extern "C" fn fh_close(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
9667    let args = CallArgs::from_vp(vp, _argc);
9668    let this = args.thisv();
9669    if !this.is_object() {
9670        args.rval().set(UndefinedValue());
9671        return false;
9672    }
9673    let fh_obj = this.to_object();
9674    let fd = get_hidden_int(cx, fh_obj, "_fd");
9675    let closed = get_hidden_bool(cx, fh_obj, "_closed");
9676    let mut wrapped_cx =
9677        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9678    let cx_ref = &mut wrapped_cx;
9679    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9680    if promise.get().is_null() {
9681        args.rval().set(UndefinedValue());
9682        return false;
9683    }
9684    if closed || fd < 0 {
9685        resolve_undefined(cx, promise.get());
9686    } else {
9687        #[cfg(unix)]
9688        {
9689            let ret = unsafe { libc::close(fd) };
9690            if ret == 0 {
9691                set_hidden_bool(cx, fh_obj, "_closed", true);
9692                set_hidden_int(cx, fh_obj, "_fd", -1);
9693                resolve_undefined(cx, promise.get());
9694            } else {
9695                reject_with_error(
9696                    cx,
9697                    promise.get(),
9698                    &format!("FileHandle.close: {}", ::std::io::Error::last_os_error()),
9699                );
9700            }
9701        }
9702        #[cfg(not(unix))]
9703        {
9704            set_hidden_bool(cx, fh_obj, "_closed", true);
9705            set_hidden_int(cx, fh_obj, "_fd", -1);
9706            resolve_undefined(cx, promise.get());
9707        }
9708    }
9709    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9710    true
9711}
9712
9713#[allow(unsafe_op_in_unsafe_fn)]
9714unsafe extern "C" fn fh_stat(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
9715    let args = CallArgs::from_vp(vp, _argc);
9716    let this = args.thisv();
9717    if !this.is_object() {
9718        args.rval().set(UndefinedValue());
9719        return false;
9720    }
9721    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9722    let mut wrapped_cx =
9723        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9724    let cx_ref = &mut wrapped_cx;
9725    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9726    if promise.get().is_null() {
9727        args.rval().set(UndefinedValue());
9728        return false;
9729    }
9730    #[cfg(unix)]
9731    {
9732        let mut st: libc::stat = unsafe { ::std::mem::zeroed() };
9733        let ret = unsafe { libc::fstat(fd, &mut st) };
9734        if ret == 0 {
9735            let stats_obj = build_stats_object(cx, &st);
9736            if !stats_obj.is_null() {
9737                rooted!(&in(cx_ref) let stats_val = mozjs::jsval::ObjectValue(stats_obj));
9738                unsafe {
9739                    mozjs_sys::jsapi::JS::ResolvePromise(
9740                        cx,
9741                        promise.handle().into(),
9742                        stats_val.handle().into(),
9743                    );
9744                }
9745            } else {
9746                resolve_undefined(cx, promise.get());
9747            }
9748        } else {
9749            reject_with_error(
9750                cx,
9751                promise.get(),
9752                &format!("FileHandle.stat: {}", ::std::io::Error::last_os_error()),
9753            );
9754        }
9755    }
9756    #[cfg(not(unix))]
9757    {
9758        resolve_undefined(cx, promise.get());
9759    }
9760    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9761    true
9762}
9763
9764#[allow(unsafe_op_in_unsafe_fn)]
9765unsafe extern "C" fn fh_chmod(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9766    let args = CallArgs::from_vp(vp, argc);
9767    let this = args.thisv();
9768    if !this.is_object() {
9769        args.rval().set(UndefinedValue());
9770        return false;
9771    }
9772    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9773    let mode = if argc > 0 {
9774        let v = *args.get(0).ptr;
9775        if v.is_int32() {
9776            v.to_int32() as u32
9777        } else if v.is_double() {
9778            v.to_double() as u32
9779        } else {
9780            0o644
9781        }
9782    } else {
9783        0o644
9784    };
9785    let mut wrapped_cx =
9786        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9787    let cx_ref = &mut wrapped_cx;
9788    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9789    if promise.get().is_null() {
9790        args.rval().set(UndefinedValue());
9791        return false;
9792    }
9793    #[cfg(unix)]
9794    {
9795        let ret = unsafe { libc::fchmod(fd, mode) };
9796        if ret == 0 {
9797            resolve_undefined(cx, promise.get());
9798        } else {
9799            reject_with_error(
9800                cx,
9801                promise.get(),
9802                &format!("FileHandle.chmod: {}", ::std::io::Error::last_os_error()),
9803            );
9804        }
9805    }
9806    #[cfg(not(unix))]
9807    {
9808        resolve_undefined(cx, promise.get());
9809    }
9810    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9811    true
9812}
9813
9814#[allow(unsafe_op_in_unsafe_fn)]
9815unsafe extern "C" fn fh_chown(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9816    let args = CallArgs::from_vp(vp, argc);
9817    let this = args.thisv();
9818    if !this.is_object() {
9819        args.rval().set(UndefinedValue());
9820        return false;
9821    }
9822    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9823    let uid = if argc > 0 {
9824        let v = *args.get(0).ptr;
9825        if v.is_int32() { v.to_int32() } else { -1 }
9826    } else {
9827        -1
9828    };
9829    let gid = if argc > 1 {
9830        let v = *args.get(1).ptr;
9831        if v.is_int32() { v.to_int32() } else { -1 }
9832    } else {
9833        -1
9834    };
9835    let mut wrapped_cx =
9836        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9837    let cx_ref = &mut wrapped_cx;
9838    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9839    if promise.get().is_null() {
9840        args.rval().set(UndefinedValue());
9841        return false;
9842    }
9843    #[cfg(unix)]
9844    {
9845        let ret = unsafe { libc::fchown(fd, uid as u32, gid as u32) };
9846        if ret == 0 {
9847            resolve_undefined(cx, promise.get());
9848        } else {
9849            reject_with_error(
9850                cx,
9851                promise.get(),
9852                &format!("FileHandle.chown: {}", ::std::io::Error::last_os_error()),
9853            );
9854        }
9855    }
9856    #[cfg(not(unix))]
9857    {
9858        resolve_undefined(cx, promise.get());
9859    }
9860    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9861    true
9862}
9863
9864#[allow(unsafe_op_in_unsafe_fn)]
9865unsafe extern "C" fn fh_datasync(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
9866    let args = CallArgs::from_vp(vp, _argc);
9867    let this = args.thisv();
9868    if !this.is_object() {
9869        args.rval().set(UndefinedValue());
9870        return false;
9871    }
9872    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9873    let mut wrapped_cx =
9874        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9875    let cx_ref = &mut wrapped_cx;
9876    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9877    if promise.get().is_null() {
9878        args.rval().set(UndefinedValue());
9879        return false;
9880    }
9881    #[cfg(unix)]
9882    {
9883        let ret = unsafe { libc::fdatasync(fd) };
9884        if ret == 0 {
9885            resolve_undefined(cx, promise.get());
9886        } else {
9887            reject_with_error(
9888                cx,
9889                promise.get(),
9890                &format!("FileHandle.datasync: {}", ::std::io::Error::last_os_error()),
9891            );
9892        }
9893    }
9894    #[cfg(not(unix))]
9895    {
9896        resolve_undefined(cx, promise.get());
9897    }
9898    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9899    true
9900}
9901
9902#[allow(unsafe_op_in_unsafe_fn)]
9903unsafe extern "C" fn fh_sync(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
9904    let args = CallArgs::from_vp(vp, _argc);
9905    let this = args.thisv();
9906    if !this.is_object() {
9907        args.rval().set(UndefinedValue());
9908        return false;
9909    }
9910    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9911    let mut wrapped_cx =
9912        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9913    let cx_ref = &mut wrapped_cx;
9914    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9915    if promise.get().is_null() {
9916        args.rval().set(UndefinedValue());
9917        return false;
9918    }
9919    #[cfg(unix)]
9920    {
9921        let ret = unsafe { libc::fsync(fd) };
9922        if ret == 0 {
9923            resolve_undefined(cx, promise.get());
9924        } else {
9925            reject_with_error(
9926                cx,
9927                promise.get(),
9928                &format!("FileHandle.sync: {}", ::std::io::Error::last_os_error()),
9929            );
9930        }
9931    }
9932    #[cfg(not(unix))]
9933    {
9934        resolve_undefined(cx, promise.get());
9935    }
9936    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9937    true
9938}
9939
9940#[allow(unsafe_op_in_unsafe_fn)]
9941unsafe extern "C" fn fh_truncate(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9942    let args = CallArgs::from_vp(vp, argc);
9943    let this = args.thisv();
9944    if !this.is_object() {
9945        args.rval().set(UndefinedValue());
9946        return false;
9947    }
9948    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9949    let len = if argc > 0 {
9950        let v = *args.get(0).ptr;
9951        if v.is_int32() {
9952            v.to_int32() as i64
9953        } else if v.is_double() {
9954            v.to_double() as i64
9955        } else {
9956            0
9957        }
9958    } else {
9959        0
9960    };
9961    let mut wrapped_cx =
9962        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
9963    let cx_ref = &mut wrapped_cx;
9964    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
9965    if promise.get().is_null() {
9966        args.rval().set(UndefinedValue());
9967        return false;
9968    }
9969    #[cfg(unix)]
9970    {
9971        let ret = unsafe { libc::ftruncate(fd, len) };
9972        if ret == 0 {
9973            resolve_undefined(cx, promise.get());
9974        } else {
9975            reject_with_error(
9976                cx,
9977                promise.get(),
9978                &format!("FileHandle.truncate: {}", ::std::io::Error::last_os_error()),
9979            );
9980        }
9981    }
9982    #[cfg(not(unix))]
9983    {
9984        resolve_undefined(cx, promise.get());
9985    }
9986    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
9987    true
9988}
9989
9990#[allow(unsafe_op_in_unsafe_fn)]
9991unsafe extern "C" fn fh_utimes(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
9992    let args = CallArgs::from_vp(vp, argc);
9993    let this = args.thisv();
9994    if !this.is_object() {
9995        args.rval().set(UndefinedValue());
9996        return false;
9997    }
9998    let fd = get_hidden_int(cx, this.to_object(), "_fd");
9999    let atime = if argc > 0 {
10000        let v = *args.get(0).ptr;
10001        if v.is_double() {
10002            v.to_double()
10003        } else if v.is_int32() {
10004            v.to_int32() as f64
10005        } else {
10006            0.0
10007        }
10008    } else {
10009        0.0
10010    };
10011    let mtime = if argc > 1 {
10012        let v = *args.get(1).ptr;
10013        if v.is_double() {
10014            v.to_double()
10015        } else if v.is_int32() {
10016            v.to_int32() as f64
10017        } else {
10018            0.0
10019        }
10020    } else {
10021        0.0
10022    };
10023    let mut wrapped_cx =
10024        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10025    let cx_ref = &mut wrapped_cx;
10026    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
10027    if promise.get().is_null() {
10028        args.rval().set(UndefinedValue());
10029        return false;
10030    }
10031    #[cfg(unix)]
10032    {
10033        let tv = [
10034            libc::timeval {
10035                tv_sec: atime as i64,
10036                tv_usec: 0,
10037            },
10038            libc::timeval {
10039                tv_sec: mtime as i64,
10040                tv_usec: 0,
10041            },
10042        ];
10043        let ret = unsafe { libc::futimes(fd, tv.as_ptr()) };
10044        if ret == 0 {
10045            resolve_undefined(cx, promise.get());
10046        } else {
10047            reject_with_error(
10048                cx,
10049                promise.get(),
10050                &format!("FileHandle.utimes: {}", ::std::io::Error::last_os_error()),
10051            );
10052        }
10053    }
10054    #[cfg(not(unix))]
10055    {
10056        resolve_undefined(cx, promise.get());
10057    }
10058    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
10059    true
10060}
10061
10062#[allow(unsafe_op_in_unsafe_fn)]
10063unsafe extern "C" fn fh_append_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10064    let args = CallArgs::from_vp(vp, argc);
10065    let this = args.thisv();
10066    if !this.is_object() {
10067        args.rval().set(UndefinedValue());
10068        return false;
10069    }
10070    let fd = get_hidden_int(cx, this.to_object(), "_fd");
10071    let data = if argc > 0 && (*args.get(0).ptr).is_object() {
10072        crate::node_crypto::extract_buffer_bytes(cx, *args.get(0).ptr)
10073    } else if argc > 0 && (*args.get(0).ptr).is_string() {
10074        let s = (*args.get(0).ptr).to_string();
10075        if !s.is_null() {
10076            crate::jsstr_to_rust_string(cx, s).into_bytes()
10077        } else {
10078            Vec::new()
10079        }
10080    } else {
10081        Vec::new()
10082    };
10083    let mut wrapped_cx =
10084        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10085    let cx_ref = &mut wrapped_cx;
10086    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
10087    if promise.get().is_null() {
10088        args.rval().set(UndefinedValue());
10089        return false;
10090    }
10091    #[cfg(unix)]
10092    {
10093        let ret =
10094            unsafe { libc::write(fd, data.as_ptr() as *const ::std::ffi::c_void, data.len()) };
10095        if ret >= 0 {
10096            resolve_undefined(cx, promise.get());
10097        } else {
10098            reject_with_error(
10099                cx,
10100                promise.get(),
10101                &format!(
10102                    "FileHandle.appendFile: {}",
10103                    ::std::io::Error::last_os_error()
10104                ),
10105            );
10106        }
10107    }
10108    #[cfg(not(unix))]
10109    {
10110        resolve_undefined(cx, promise.get());
10111    }
10112    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
10113    true
10114}
10115
10116#[allow(unsafe_op_in_unsafe_fn)]
10117unsafe extern "C" fn fh_read_file(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
10118    let args = CallArgs::from_vp(vp, _argc);
10119    let this = args.thisv();
10120    if !this.is_object() {
10121        args.rval().set(UndefinedValue());
10122        return false;
10123    }
10124    let fd = get_hidden_int(cx, this.to_object(), "_fd");
10125    let mut wrapped_cx =
10126        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10127    let cx_ref = &mut wrapped_cx;
10128    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
10129    if promise.get().is_null() {
10130        args.rval().set(UndefinedValue());
10131        return false;
10132    }
10133    #[cfg(unix)]
10134    {
10135        let file_size = unsafe { libc::lseek(fd, 0, libc::SEEK_END) };
10136        if file_size < 0 {
10137            reject_with_error(
10138                cx,
10139                promise.get(),
10140                &format!("FileHandle.readFile: {}", ::std::io::Error::last_os_error()),
10141            );
10142        } else {
10143            unsafe {
10144                libc::lseek(fd, 0, libc::SEEK_SET);
10145            }
10146            let mut buf = vec![0u8; file_size as usize];
10147            let bytes_read = unsafe {
10148                libc::read(
10149                    fd,
10150                    buf.as_mut_ptr() as *mut ::std::ffi::c_void,
10151                    file_size as usize,
10152                )
10153            };
10154            if bytes_read >= 0 {
10155                buf.truncate(bytes_read as usize);
10156                let buf_obj = crate::globals::create_buffer_object(cx, &buf);
10157                if !buf_obj.is_null() {
10158                    rooted!(&in(cx_ref) let buf_val = mozjs::jsval::ObjectValue(buf_obj));
10159                    unsafe {
10160                        mozjs_sys::jsapi::JS::ResolvePromise(
10161                            cx,
10162                            promise.handle().into(),
10163                            buf_val.handle().into(),
10164                        );
10165                    }
10166                } else {
10167                    resolve_undefined(cx, promise.get());
10168                }
10169            } else {
10170                reject_with_error(
10171                    cx,
10172                    promise.get(),
10173                    &format!("FileHandle.readFile: {}", ::std::io::Error::last_os_error()),
10174                );
10175            }
10176        }
10177    }
10178    #[cfg(not(unix))]
10179    {
10180        resolve_undefined(cx, promise.get());
10181    }
10182    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
10183    true
10184}
10185
10186#[allow(unsafe_op_in_unsafe_fn)]
10187unsafe extern "C" fn fh_write_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10188    let args = CallArgs::from_vp(vp, argc);
10189    let this = args.thisv();
10190    if !this.is_object() {
10191        args.rval().set(UndefinedValue());
10192        return false;
10193    }
10194    let fd = get_hidden_int(cx, this.to_object(), "_fd");
10195    let data = if argc > 0 && (*args.get(0).ptr).is_object() {
10196        crate::node_crypto::extract_buffer_bytes(cx, *args.get(0).ptr)
10197    } else if argc > 0 && (*args.get(0).ptr).is_string() {
10198        let s = (*args.get(0).ptr).to_string();
10199        if !s.is_null() {
10200            crate::jsstr_to_rust_string(cx, s).into_bytes()
10201        } else {
10202            Vec::new()
10203        }
10204    } else {
10205        Vec::new()
10206    };
10207    let mut wrapped_cx =
10208        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10209    let cx_ref = &mut wrapped_cx;
10210    rooted!(&in(cx_ref) let promise = mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()));
10211    if promise.get().is_null() {
10212        args.rval().set(UndefinedValue());
10213        return false;
10214    }
10215    #[cfg(unix)]
10216    {
10217        let ret =
10218            unsafe { libc::write(fd, data.as_ptr() as *const ::std::ffi::c_void, data.len()) };
10219        if ret >= 0 {
10220            resolve_undefined(cx, promise.get());
10221        } else {
10222            reject_with_error(
10223                cx,
10224                promise.get(),
10225                &format!(
10226                    "FileHandle.writeFile: {}",
10227                    ::std::io::Error::last_os_error()
10228                ),
10229            );
10230        }
10231    }
10232    #[cfg(not(unix))]
10233    {
10234        resolve_undefined(cx, promise.get());
10235    }
10236    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
10237    true
10238}
10239
10240// --- Stats type methods ---
10241
10242#[allow(unsafe_op_in_unsafe_fn)]
10243unsafe extern "C" fn stats_is_block_device(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10244    let args = CallArgs::from_vp(vp, argc);
10245    let mut wrapped_cx =
10246        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10247    let cx_ref = &mut wrapped_cx;
10248    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10249    args.rval().set(mozjs::jsval::BooleanValue(get_hidden_bool(
10250        cx,
10251        this.get(),
10252        "_isBlockDevice",
10253    )));
10254    true
10255}
10256
10257#[allow(unsafe_op_in_unsafe_fn)]
10258unsafe extern "C" fn stats_is_character_device(
10259    cx: *mut JSContext,
10260    argc: u32,
10261    vp: *mut JSVal,
10262) -> bool {
10263    let args = CallArgs::from_vp(vp, argc);
10264    let mut wrapped_cx =
10265        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10266    let cx_ref = &mut wrapped_cx;
10267    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10268    args.rval().set(mozjs::jsval::BooleanValue(get_hidden_bool(
10269        cx,
10270        this.get(),
10271        "_isCharacterDevice",
10272    )));
10273    true
10274}
10275
10276#[allow(unsafe_op_in_unsafe_fn)]
10277unsafe extern "C" fn stats_is_fifo(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10278    let args = CallArgs::from_vp(vp, argc);
10279    let mut wrapped_cx =
10280        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10281    let cx_ref = &mut wrapped_cx;
10282    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10283    args.rval().set(mozjs::jsval::BooleanValue(get_hidden_bool(
10284        cx,
10285        this.get(),
10286        "_isFIFO",
10287    )));
10288    true
10289}
10290
10291#[allow(unsafe_op_in_unsafe_fn)]
10292unsafe extern "C" fn stats_is_socket(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10293    let args = CallArgs::from_vp(vp, argc);
10294    let mut wrapped_cx =
10295        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10296    let cx_ref = &mut wrapped_cx;
10297    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10298    args.rval().set(mozjs::jsval::BooleanValue(get_hidden_bool(
10299        cx,
10300        this.get(),
10301        "_isSocket",
10302    )));
10303    true
10304}
10305
10306#[allow(unsafe_op_in_unsafe_fn)]
10307unsafe extern "C" fn stats_is_symlink(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10308    let args = CallArgs::from_vp(vp, argc);
10309    let mut wrapped_cx =
10310        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10311    let cx_ref = &mut wrapped_cx;
10312    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10313    args.rval().set(mozjs::jsval::BooleanValue(get_hidden_bool(
10314        cx,
10315        this.get(),
10316        "_isSymbolicLink",
10317    )));
10318    true
10319}
10320
10321// --- Dirent type methods ---
10322
10323#[allow(unsafe_op_in_unsafe_fn)]
10324unsafe extern "C" fn dirent_is_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10325    let args = CallArgs::from_vp(vp, argc);
10326    let mut wrapped_cx =
10327        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10328    let cx_ref = &mut wrapped_cx;
10329    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10330    let type_code = get_hidden_int(cx, this.get(), "_typeCode");
10331    args.rval().set(mozjs::jsval::BooleanValue(type_code == 0));
10332    true
10333}
10334
10335#[allow(unsafe_op_in_unsafe_fn)]
10336unsafe extern "C" fn dirent_is_directory(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10337    let args = CallArgs::from_vp(vp, argc);
10338    let mut wrapped_cx =
10339        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10340    let cx_ref = &mut wrapped_cx;
10341    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10342    let type_code = get_hidden_int(cx, this.get(), "_typeCode");
10343    args.rval().set(mozjs::jsval::BooleanValue(type_code == 1));
10344    true
10345}
10346
10347#[allow(unsafe_op_in_unsafe_fn)]
10348unsafe extern "C" fn dirent_is_symbolic_link(
10349    cx: *mut JSContext,
10350    argc: u32,
10351    vp: *mut JSVal,
10352) -> bool {
10353    let args = CallArgs::from_vp(vp, argc);
10354    let mut wrapped_cx =
10355        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10356    let cx_ref = &mut wrapped_cx;
10357    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10358    let type_code = get_hidden_int(cx, this.get(), "_typeCode");
10359    args.rval().set(mozjs::jsval::BooleanValue(type_code == 2));
10360    true
10361}
10362
10363macro_rules! dirent_type_method {
10364    ($fn_name:ident) => {
10365        #[allow(unsafe_op_in_unsafe_fn)]
10366        unsafe extern "C" fn $fn_name(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
10367            let args = CallArgs::from_vp(vp, _argc);
10368            args.rval().set(mozjs::jsval::BooleanValue(false));
10369            true
10370        }
10371    };
10372}
10373dirent_type_method!(dirent_is_block_device);
10374dirent_type_method!(dirent_is_character_device);
10375dirent_type_method!(dirent_is_fifo);
10376dirent_type_method!(dirent_is_socket);
10377
10378// --- statfs helper ---
10379
10380#[allow(unsafe_op_in_unsafe_fn)]
10381unsafe fn create_statfs_object(cx: *mut JSContext, sf: &StatfsResult) -> *mut JSObject {
10382    let mut wrapped_cx =
10383        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10384    let cx_ref = &mut wrapped_cx;
10385    rooted!(&in(cx_ref) let obj = JS_NewPlainObject(cx));
10386    if obj.get().is_null() {
10387        return obj.get();
10388    }
10389    define_num_prop(cx, obj.get(), "type", sf.type_ as f64);
10390    define_num_prop(cx, obj.get(), "bsize", sf.bsize as f64);
10391    define_num_prop(cx, obj.get(), "blocks", sf.blocks as f64);
10392    define_num_prop(cx, obj.get(), "bfree", sf.bfree as f64);
10393    define_num_prop(cx, obj.get(), "bavail", sf.bavail as f64);
10394    define_num_prop(cx, obj.get(), "files", sf.files as f64);
10395    define_num_prop(cx, obj.get(), "ffree", sf.ffree as f64);
10396    obj.get()
10397}
10398
10399// --- glob helpers: deleted (BCE-20260816-FS-GLOB) — see fs.glob section above ---
10400
10401// --- Dir class ---
10402//
10403// fs.opendir() / fs.opendirSync() returns a Dir object with:
10404//   .path          — the directory path
10405//   .readSync()    — next Dirent or null
10406//   .read(cb)      — async next Dirent
10407//   .closeSync()   — close the dir
10408//   .close(cb)     — async close
10409//   [Symbol.asyncIterator]() — async iterable
10410
10411#[allow(unsafe_op_in_unsafe_fn)]
10412unsafe fn create_dir_object(cx: *mut JSContext, dir_path: &str) -> *mut JSObject {
10413    let mut wrapped_cx =
10414        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10415    let cx_ref = &mut wrapped_cx;
10416    rooted!(&in(cx_ref) let dir = JS_NewPlainObject(cx));
10417    if dir.get().is_null() {
10418        return dir.get();
10419    }
10420
10421    // .path property
10422    let c_path = ZBox::from_bytes(dir_path.as_bytes());
10423    let path_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
10424    if !path_str.is_null() {
10425        rooted!(&in(cx_ref) let path_val = mozjs::jsval::StringValue(&*path_str));
10426        JS_DefineProperty(
10427            cx,
10428            dir.handle().into(),
10429            c"path".as_ptr(),
10430            path_val.handle().into(),
10431            JSPROP_ENUMERATE as u32,
10432        );
10433    }
10434
10435    // Hidden _dirPath for method callbacks
10436    let c_dp = ZBox::from_bytes(dir_path.as_bytes());
10437    let dp_str = JS_NewStringCopyZ(cx, c_dp.as_ptr());
10438    if !dp_str.is_null() {
10439        rooted!(&in(cx_ref) let dp_val = mozjs::jsval::StringValue(&*dp_str));
10440        JS_DefineProperty(
10441            cx,
10442            dir.handle().into(),
10443            c"_dirPath".as_ptr(),
10444            dp_val.handle().into(),
10445            0,
10446        );
10447    }
10448
10449    // _entriesIndex hidden prop (current position in entries cache)
10450    set_hidden_int(cx, dir.get(), "_entriesIndex", 0);
10451    // _closed hidden prop
10452    set_hidden_bool(cx, dir.get(), "_closed", false);
10453
10454    // Methods
10455    JS_DefineFunction(
10456        cx,
10457        dir.handle().into(),
10458        c"readSync".as_ptr(),
10459        Some(dir_read_sync),
10460        0,
10461        JSPROP_ENUMERATE as u32,
10462    );
10463    JS_DefineFunction(
10464        cx,
10465        dir.handle().into(),
10466        c"read".as_ptr(),
10467        Some(dir_read),
10468        1,
10469        JSPROP_ENUMERATE as u32,
10470    );
10471    JS_DefineFunction(
10472        cx,
10473        dir.handle().into(),
10474        c"closeSync".as_ptr(),
10475        Some(dir_close_sync),
10476        0,
10477        JSPROP_ENUMERATE as u32,
10478    );
10479    JS_DefineFunction(
10480        cx,
10481        dir.handle().into(),
10482        c"close".as_ptr(),
10483        Some(dir_close),
10484        1,
10485        JSPROP_ENUMERATE as u32,
10486    );
10487    JS_DefineFunction(
10488        cx,
10489        dir.handle().into(),
10490        c"\x5B\x5D".as_ptr(),
10491        Some(dir_symbol_iterator),
10492        0,
10493        0,
10494    );
10495
10496    dir.get()
10497}
10498
10499#[allow(unsafe_op_in_unsafe_fn)]
10500unsafe fn dir_ensure_entries(cx: *mut JSContext, dir_obj: *mut JSObject) {
10501    // Check if _entries already exists
10502    let mut wrapped_cx =
10503        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10504    let cx_ref = &mut wrapped_cx;
10505    rooted!(&in(cx_ref) let dir_rooted = dir_obj);
10506    let mut entries_val = UndefinedValue();
10507    JS_GetProperty(
10508        cx,
10509        dir_rooted.handle().into(),
10510        c"_entries".as_ptr(),
10511        MutableHandle::<Value> {
10512            _phantom_0: ::std::marker::PhantomData,
10513            ptr: &mut entries_val,
10514        },
10515    );
10516    if !entries_val.is_undefined() {
10517        return;
10518    }
10519
10520    // Read directory and cache entries
10521    let dir_path = {
10522        let mut dp_val = UndefinedValue();
10523        JS_GetProperty(
10524            cx,
10525            dir_rooted.handle().into(),
10526            c"_dirPath".as_ptr(),
10527            MutableHandle::<Value> {
10528                _phantom_0: ::std::marker::PhantomData,
10529                ptr: &mut dp_val,
10530            },
10531        );
10532        if dp_val.is_string() {
10533            crate::jsstr_to_rust_string(cx, dp_val.to_string())
10534        } else {
10535            return;
10536        }
10537    };
10538
10539    match fs::read_dir(&dir_path) {
10540        Ok(raw_entries) => {
10541            let entries: Vec<(String, bool)> = raw_entries
10542                .flatten()
10543                .map(|e| {
10544                    let name = e.file_name().to_string_lossy().into_owned();
10545                    let is_dir = e.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
10546                    (name, is_dir)
10547                })
10548                .collect();
10549
10550            rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, entries.len()));
10551            if !arr.get().is_null() {
10552                for (idx, (name, is_dir)) in entries.iter().enumerate() {
10553                    let dirent = create_dirent(cx, name, *is_dir);
10554                    rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(dirent));
10555                    JS_DefineElement(
10556                        cx,
10557                        arr.handle().into(),
10558                        idx as u32,
10559                        val.handle().into(),
10560                        JSPROP_ENUMERATE as u32,
10561                    );
10562                }
10563            }
10564            rooted!(&in(cx_ref) let arr_val = mozjs::jsval::ObjectValue(arr.get()));
10565            JS_DefineProperty(
10566                cx,
10567                dir_rooted.handle().into(),
10568                c"_entries".as_ptr(),
10569                arr_val.handle().into(),
10570                0,
10571            );
10572        }
10573        Err(_) => {
10574            // On error, set empty entries array
10575            rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, 0));
10576            rooted!(&in(cx_ref) let arr_val = mozjs::jsval::ObjectValue(arr.get()));
10577            JS_DefineProperty(
10578                cx,
10579                dir_rooted.handle().into(),
10580                c"_entries".as_ptr(),
10581                arr_val.handle().into(),
10582                0,
10583            );
10584        }
10585    }
10586}
10587
10588#[allow(unsafe_op_in_unsafe_fn)]
10589unsafe extern "C" fn dir_read_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10590    let args = CallArgs::from_vp(vp, argc);
10591    let mut wrapped_cx =
10592        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10593    let cx_ref = &mut wrapped_cx;
10594    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10595
10596    let closed = get_hidden_bool(cx, this.get(), "_closed");
10597    if closed {
10598        let c_msg = ZBox::from_bytes("Dir is already closed".as_bytes());
10599        JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
10600        return false;
10601    }
10602
10603    dir_ensure_entries(cx, this.get());
10604
10605    let idx = get_hidden_int(cx, this.get(), "_entriesIndex") as u32;
10606
10607    let mut entries_val = UndefinedValue();
10608    JS_GetProperty(
10609        cx,
10610        this.handle().into(),
10611        c"_entries".as_ptr(),
10612        MutableHandle::<Value> {
10613            _phantom_0: ::std::marker::PhantomData,
10614            ptr: &mut entries_val,
10615        },
10616    );
10617    if entries_val.is_object() {
10618        let entries_obj = entries_val.to_object();
10619        rooted!(&in(cx_ref) let eo = entries_obj);
10620        let mut len_val = UndefinedValue();
10621        JS_GetProperty(
10622            cx,
10623            eo.handle().into(),
10624            c"length".as_ptr(),
10625            MutableHandle::<Value> {
10626                _phantom_0: ::std::marker::PhantomData,
10627                ptr: &mut len_val,
10628            },
10629        );
10630        let len = if len_val.is_int32() {
10631            len_val.to_int32() as u32
10632        } else {
10633            0
10634        };
10635
10636        if idx < len {
10637            let mut elem = UndefinedValue();
10638            JS_GetElement(
10639                cx,
10640                eo.handle().into(),
10641                idx,
10642                MutableHandle::<Value> {
10643                    _phantom_0: ::std::marker::PhantomData,
10644                    ptr: &mut elem,
10645                },
10646            );
10647            args.rval().set(elem);
10648            set_hidden_int(cx, this.get(), "_entriesIndex", (idx + 1) as i32);
10649        } else {
10650            args.rval().set(mozjs::jsval::NullValue());
10651        }
10652    } else {
10653        args.rval().set(mozjs::jsval::NullValue());
10654    }
10655    true
10656}
10657
10658#[allow(unsafe_op_in_unsafe_fn)]
10659unsafe extern "C" fn dir_read(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10660    let args = CallArgs::from_vp(vp, argc);
10661    let mut wrapped_cx =
10662        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10663    let cx_ref = &mut wrapped_cx;
10664
10665    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10666
10667    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 0) {
10668        // Synchronously read next entry, then invoke callback
10669        let closed = get_hidden_bool(cx, this.get(), "_closed");
10670        if closed {
10671            rooted!(&in(cx_ref) let err_obj = JS_NewPlainObject(cx));
10672            if !err_obj.get().is_null() {
10673                let c_msg = ZBox::from_bytes("Dir is already closed".as_bytes());
10674                let msg_str = JS_NewStringCopyZ(cx, c_msg.as_ptr());
10675                if !msg_str.is_null() {
10676                    rooted!(&in(cx_ref) let msg_val = mozjs::jsval::StringValue(&*msg_str));
10677                    JS_DefineProperty(
10678                        cx,
10679                        err_obj.handle().into(),
10680                        c"message".as_ptr(),
10681                        msg_val.handle().into(),
10682                        JSPROP_ENUMERATE as u32,
10683                    );
10684                }
10685            }
10686            rooted!(&in(cx_ref) let err_val = mozjs::jsval::ObjectValue(err_obj.get()));
10687            let err_args = HandleValueArray {
10688                length_: 1,
10689                elements_: &err_val.get() as *const JSVal,
10690            };
10691            let global = CurrentGlobalOrNull(cx);
10692            if !global.is_null() {
10693                rooted!(&in(cx_ref) let global_rooted = global);
10694                rooted!(&in(cx_ref) let cb = callback);
10695                rooted!(&in(cx_ref) let cb_val = mozjs::jsval::ObjectValue(cb.get()));
10696                let mut rval = UndefinedValue();
10697                JS_CallFunctionValue(
10698                    cx,
10699                    global_rooted.handle().into(),
10700                    cb_val.handle().into(),
10701                    &err_args,
10702                    MutableHandle::<Value> {
10703                        _phantom_0: ::std::marker::PhantomData,
10704                        ptr: &mut rval,
10705                    },
10706                );
10707                JS_ClearPendingException(cx);
10708            }
10709            args.rval().set(UndefinedValue());
10710            return true;
10711        }
10712
10713        dir_ensure_entries(cx, this.get());
10714        let idx = get_hidden_int(cx, this.get(), "_entriesIndex") as u32;
10715        let mut entries_val = UndefinedValue();
10716        JS_GetProperty(
10717            cx,
10718            this.handle().into(),
10719            c"_entries".as_ptr(),
10720            MutableHandle::<Value> {
10721                _phantom_0: ::std::marker::PhantomData,
10722                ptr: &mut entries_val,
10723            },
10724        );
10725
10726        let next_val = if entries_val.is_object() {
10727            let entries_obj = entries_val.to_object();
10728            rooted!(&in(cx_ref) let eo = entries_obj);
10729            let mut len_val = UndefinedValue();
10730            JS_GetProperty(
10731                cx,
10732                eo.handle().into(),
10733                c"length".as_ptr(),
10734                MutableHandle::<Value> {
10735                    _phantom_0: ::std::marker::PhantomData,
10736                    ptr: &mut len_val,
10737                },
10738            );
10739            let len = if len_val.is_int32() {
10740                len_val.to_int32() as u32
10741            } else {
10742                0
10743            };
10744            if idx < len {
10745                let mut elem = UndefinedValue();
10746                JS_GetElement(
10747                    cx,
10748                    eo.handle().into(),
10749                    idx,
10750                    MutableHandle::<Value> {
10751                        _phantom_0: ::std::marker::PhantomData,
10752                        ptr: &mut elem,
10753                    },
10754                );
10755                set_hidden_int(cx, this.get(), "_entriesIndex", (idx + 1) as i32);
10756                elem
10757            } else {
10758                mozjs::jsval::NullValue()
10759            }
10760        } else {
10761            mozjs::jsval::NullValue()
10762        };
10763
10764        rooted!(&in(cx_ref) let next_rooted = next_val);
10765        rooted!(&in(cx_ref) let cb = callback);
10766        rooted!(&in(cx_ref) let cb_val = mozjs::jsval::ObjectValue(cb.get()));
10767        let args_arr = [UndefinedValue(), next_rooted.get()];
10768        let cb_args = HandleValueArray {
10769            length_: 2,
10770            elements_: args_arr.as_ptr(),
10771        };
10772        let global = CurrentGlobalOrNull(cx);
10773        if !global.is_null() {
10774            rooted!(&in(cx_ref) let global_rooted = global);
10775            let mut rval = UndefinedValue();
10776            JS_CallFunctionValue(
10777                cx,
10778                global_rooted.handle().into(),
10779                cb_val.handle().into(),
10780                &cb_args,
10781                MutableHandle::<Value> {
10782                    _phantom_0: ::std::marker::PhantomData,
10783                    ptr: &mut rval,
10784                },
10785            );
10786            JS_ClearPendingException(cx);
10787        }
10788        args.rval().set(UndefinedValue());
10789        return true;
10790    }
10791
10792    // No callback: return a Promise
10793    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
10794    if promise.get().is_null() {
10795        args.rval().set(UndefinedValue());
10796        return false;
10797    }
10798
10799    let closed = get_hidden_bool(cx, this.get(), "_closed");
10800    if closed {
10801        reject_with_error(cx, promise.get(), "Dir is already closed");
10802    } else {
10803        dir_ensure_entries(cx, this.get());
10804        let idx = get_hidden_int(cx, this.get(), "_entriesIndex") as u32;
10805        let mut entries_val = UndefinedValue();
10806        JS_GetProperty(
10807            cx,
10808            this.handle().into(),
10809            c"_entries".as_ptr(),
10810            MutableHandle::<Value> {
10811                _phantom_0: ::std::marker::PhantomData,
10812                ptr: &mut entries_val,
10813            },
10814        );
10815
10816        let next_val = if entries_val.is_object() {
10817            let entries_obj = entries_val.to_object();
10818            rooted!(&in(cx_ref) let eo = entries_obj);
10819            let mut len_val = UndefinedValue();
10820            JS_GetProperty(
10821                cx,
10822                eo.handle().into(),
10823                c"length".as_ptr(),
10824                MutableHandle::<Value> {
10825                    _phantom_0: ::std::marker::PhantomData,
10826                    ptr: &mut len_val,
10827                },
10828            );
10829            let len = if len_val.is_int32() {
10830                len_val.to_int32() as u32
10831            } else {
10832                0
10833            };
10834            if idx < len {
10835                let mut elem = UndefinedValue();
10836                JS_GetElement(
10837                    cx,
10838                    eo.handle().into(),
10839                    idx,
10840                    MutableHandle::<Value> {
10841                        _phantom_0: ::std::marker::PhantomData,
10842                        ptr: &mut elem,
10843                    },
10844                );
10845                set_hidden_int(cx, this.get(), "_entriesIndex", (idx + 1) as i32);
10846                elem
10847            } else {
10848                mozjs::jsval::NullValue()
10849            }
10850        } else {
10851            mozjs::jsval::NullValue()
10852        };
10853
10854        rooted!(&in(cx_ref) let val = next_val);
10855        unsafe {
10856            mozjs_sys::jsapi::JS::ResolvePromise(cx, promise.handle().into(), val.handle().into());
10857        }
10858    }
10859    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
10860    true
10861}
10862
10863#[allow(unsafe_op_in_unsafe_fn)]
10864unsafe extern "C" fn dir_close_sync(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10865    let args = CallArgs::from_vp(vp, argc);
10866    let mut wrapped_cx =
10867        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10868    let cx_ref = &mut wrapped_cx;
10869    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10870    set_hidden_bool(cx, this.get(), "_closed", true);
10871    args.rval().set(UndefinedValue());
10872    true
10873}
10874
10875#[allow(unsafe_op_in_unsafe_fn)]
10876unsafe extern "C" fn dir_close(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10877    let args = CallArgs::from_vp(vp, argc);
10878    let mut wrapped_cx =
10879        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10880    let cx_ref = &mut wrapped_cx;
10881    rooted!(&in(cx_ref) let this = args.thisv().to_object());
10882    set_hidden_bool(cx, this.get(), "_closed", true);
10883
10884    if let Some((callback, _)) = extract_callback_and_encoding(cx, &args, 0) {
10885        rooted!(&in(cx_ref) let cb = callback);
10886        rooted!(&in(cx_ref) let cb_val = mozjs::jsval::ObjectValue(cb.get()));
10887        let null_args = HandleValueArray::empty();
10888        let global = CurrentGlobalOrNull(cx);
10889        if !global.is_null() {
10890            rooted!(&in(cx_ref) let global_rooted = global);
10891            let mut rval = UndefinedValue();
10892            JS_CallFunctionValue(
10893                cx,
10894                global_rooted.handle().into(),
10895                cb_val.handle().into(),
10896                &null_args,
10897                MutableHandle::<Value> {
10898                    _phantom_0: ::std::marker::PhantomData,
10899                    ptr: &mut rval,
10900                },
10901            );
10902            JS_ClearPendingException(cx);
10903        }
10904        args.rval().set(UndefinedValue());
10905        return true;
10906    }
10907
10908    args.rval().set(UndefinedValue());
10909    true
10910}
10911
10912#[allow(unsafe_op_in_unsafe_fn)]
10913unsafe extern "C" fn dir_symbol_iterator(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10914    // Return an object with next() method for for-await-of
10915    let args = CallArgs::from_vp(vp, argc);
10916    let mut wrapped_cx =
10917        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10918    let cx_ref = &mut wrapped_cx;
10919    rooted!(&in(cx_ref) let iter = JS_NewPlainObject(cx));
10920    if iter.get().is_null() {
10921        args.rval().set(UndefinedValue());
10922        return true;
10923    }
10924
10925    // Store reference to the Dir object on the iterator
10926    let this_val = args.thisv();
10927    if this_val.is_object() {
10928        rooted!(&in(cx_ref) let dir_ref = this_val.to_object());
10929        rooted!(&in(cx_ref) let dir_ref_val = mozjs::jsval::ObjectValue(dir_ref.get()));
10930        JS_DefineProperty(
10931            cx,
10932            iter.handle().into(),
10933            c"_dirRef".as_ptr(),
10934            dir_ref_val.handle().into(),
10935            0,
10936        );
10937    }
10938
10939    JS_DefineFunction(
10940        cx,
10941        iter.handle().into(),
10942        c"next".as_ptr(),
10943        Some(dir_iterator_next),
10944        0,
10945        JSPROP_ENUMERATE as u32,
10946    );
10947    args.rval().set(mozjs::jsval::ObjectValue(iter.get()));
10948    true
10949}
10950
10951#[allow(unsafe_op_in_unsafe_fn)]
10952unsafe extern "C" fn dir_iterator_next(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
10953    let args = CallArgs::from_vp(vp, argc);
10954    let mut wrapped_cx =
10955        mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
10956    let cx_ref = &mut wrapped_cx;
10957
10958    rooted!(&in(cx_ref) let promise = unsafe { mozjs_sys::jsapi::JS::NewPromiseObject(cx, HandleObject::null()) });
10959    if promise.get().is_null() {
10960        args.rval().set(UndefinedValue());
10961        return false;
10962    }
10963
10964    // Get the Dir object reference
10965    let this_val = args.thisv();
10966    if !this_val.is_object() {
10967        reject_with_error(
10968            cx,
10969            promise.get(),
10970            "Dir iterator next() called on wrong object",
10971        );
10972        args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
10973        return true;
10974    }
10975    let iter_obj = this_val.to_object();
10976    rooted!(&in(cx_ref) let iter_rooted = iter_obj);
10977    let mut dir_ref_val = UndefinedValue();
10978    JS_GetProperty(
10979        cx,
10980        iter_rooted.handle().into(),
10981        c"_dirRef".as_ptr(),
10982        MutableHandle::<Value> {
10983            _phantom_0: ::std::marker::PhantomData,
10984            ptr: &mut dir_ref_val,
10985        },
10986    );
10987
10988    if !dir_ref_val.is_object() {
10989        // No dir ref, return {done: true}
10990        rooted!(&in(cx_ref) let result_obj = JS_NewPlainObject(cx));
10991        if !result_obj.get().is_null() {
10992            define_bool_prop(cx, result_obj.get(), "done", true);
10993        }
10994        rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(result_obj.get()));
10995        unsafe {
10996            mozjs_sys::jsapi::JS::ResolvePromise(cx, promise.handle().into(), val.handle().into());
10997        }
10998        args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
10999        return true;
11000    }
11001
11002    let dir_obj = dir_ref_val.to_object();
11003    let closed = get_hidden_bool(cx, dir_obj, "_closed");
11004    if closed {
11005        rooted!(&in(cx_ref) let result_obj = JS_NewPlainObject(cx));
11006        if !result_obj.get().is_null() {
11007            define_bool_prop(cx, result_obj.get(), "done", true);
11008        }
11009        rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(result_obj.get()));
11010        unsafe {
11011            mozjs_sys::jsapi::JS::ResolvePromise(cx, promise.handle().into(), val.handle().into());
11012        }
11013        args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
11014        return true;
11015    }
11016
11017    dir_ensure_entries(cx, dir_obj);
11018    let idx = get_hidden_int(cx, dir_obj, "_entriesIndex") as u32;
11019
11020    rooted!(&in(cx_ref) let dir_rooted = dir_obj);
11021    let mut entries_val = UndefinedValue();
11022    JS_GetProperty(
11023        cx,
11024        dir_rooted.handle().into(),
11025        c"_entries".as_ptr(),
11026        MutableHandle::<Value> {
11027            _phantom_0: ::std::marker::PhantomData,
11028            ptr: &mut entries_val,
11029        },
11030    );
11031
11032    let next_dirent = if entries_val.is_object() {
11033        let entries_arr = entries_val.to_object();
11034        rooted!(&in(cx_ref) let ea = entries_arr);
11035        let mut len_val = UndefinedValue();
11036        JS_GetProperty(
11037            cx,
11038            ea.handle().into(),
11039            c"length".as_ptr(),
11040            MutableHandle::<Value> {
11041                _phantom_0: ::std::marker::PhantomData,
11042                ptr: &mut len_val,
11043            },
11044        );
11045        let len = if len_val.is_int32() {
11046            len_val.to_int32() as u32
11047        } else {
11048            0
11049        };
11050        if idx < len {
11051            let mut elem = UndefinedValue();
11052            JS_GetElement(
11053                cx,
11054                ea.handle().into(),
11055                idx,
11056                MutableHandle::<Value> {
11057                    _phantom_0: ::std::marker::PhantomData,
11058                    ptr: &mut elem,
11059                },
11060            );
11061            set_hidden_int(cx, dir_obj, "_entriesIndex", (idx + 1) as i32);
11062            elem
11063        } else {
11064            mozjs::jsval::NullValue()
11065        }
11066    } else {
11067        mozjs::jsval::NullValue()
11068    };
11069
11070    rooted!(&in(cx_ref) let result_obj = JS_NewPlainObject(cx));
11071    if !result_obj.get().is_null() {
11072        if next_dirent.is_null() {
11073            define_bool_prop(cx, result_obj.get(), "done", true);
11074        } else {
11075            define_bool_prop(cx, result_obj.get(), "done", false);
11076            rooted!(&in(cx_ref) let dv = next_dirent);
11077            JS_DefineProperty(
11078                cx,
11079                result_obj.handle().into(),
11080                c"value".as_ptr(),
11081                dv.handle().into(),
11082                JSPROP_ENUMERATE as u32,
11083            );
11084        }
11085    }
11086    rooted!(&in(cx_ref) let val = mozjs::jsval::ObjectValue(result_obj.get()));
11087    unsafe {
11088        mozjs_sys::jsapi::JS::ResolvePromise(cx, promise.handle().into(), val.handle().into());
11089    }
11090    args.rval().set(mozjs::jsval::ObjectValue(promise.get()));
11091    true
11092}
11093
11094#[cfg(test)]
11095mod tests {
11096    use super::mkdtemp_inner;
11097
11098    // Node parity: an empty prefix must fail with EINVAL before touching the
11099    // filesystem, never create a bare random directory in cwd (upstream b7a043103).
11100    #[test]
11101    fn mkdtemp_empty_prefix_returns_einval() {
11102        let err = mkdtemp_inner("").expect_err("empty prefix must be rejected");
11103        assert_eq!(err.raw_os_error(), Some(libc::EINVAL));
11104    }
11105}