1use super::{arg_num, arg_str, native_tag};
13use crate::host::{invoke, with_host, JsObj};
14use fusevm::Value;
15use indexmap::IndexMap;
16use std::cell::RefCell;
17use std::collections::HashMap;
18use std::ffi::CString;
19use std::fs::File;
20use std::io::{Read, Seek, SeekFrom, Write};
21use std::os::unix::fs::{MetadataExt, PermissionsExt};
22use std::os::unix::io::AsRawFd;
23use std::path::{Path, PathBuf};
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::sync::Arc;
26use std::time::{Duration, UNIX_EPOCH};
27
28pub const METHODS: &[&str] = &[
29 "readFileSync",
31 "writeFileSync",
32 "appendFileSync",
33 "existsSync",
34 "readdirSync",
35 "mkdirSync",
36 "rmdirSync",
37 "unlinkSync",
38 "rmSync",
39 "statSync",
40 "lstatSync",
41 "statfsSync",
42 "accessSync",
43 "chmodSync",
44 "chownSync",
45 "lchownSync",
46 "copyFileSync",
47 "cpSync",
48 "linkSync",
49 "symlinkSync",
50 "readlinkSync",
51 "realpathSync",
52 "renameSync",
53 "truncateSync",
54 "utimesSync",
55 "lutimesSync",
56 "mkdtempSync",
57 "opendirSync",
58 "globSync",
59 "openSync",
61 "closeSync",
62 "readSync",
63 "writeSync",
64 "readvSync",
65 "writevSync",
66 "fstatSync",
67 "fchmodSync",
68 "fchownSync",
69 "ftruncateSync",
70 "futimesSync",
71 "fsyncSync",
72 "fdatasyncSync",
73 "readFile",
75 "writeFile",
76 "appendFile",
77 "readdir",
78 "mkdir",
79 "rmdir",
80 "rm",
81 "unlink",
82 "stat",
83 "lstat",
84 "statfs",
85 "access",
86 "chmod",
87 "chown",
88 "lchown",
89 "copyFile",
90 "cp",
91 "link",
92 "symlink",
93 "readlink",
94 "realpath",
95 "rename",
96 "truncate",
97 "utimes",
98 "lutimes",
99 "mkdtemp",
100 "opendir",
101 "glob",
102 "exists",
103 "open",
105 "close",
106 "read",
107 "write",
108 "readv",
109 "writev",
110 "fstat",
111 "fchmod",
112 "fchown",
113 "ftruncate",
114 "futimes",
115 "fsync",
116 "fdatasync",
117 "watchFile",
119 "unwatchFile",
120 "createReadStream",
121 "createWriteStream",
122];
123
124thread_local! {
127 static FD_TABLE: RefCell<HashMap<i32, File>> = RefCell::new(HashMap::new());
128 static NEXT_FD: RefCell<i32> = const { RefCell::new(3) };
129 static WATCHERS: RefCell<Vec<WatchEntry>> = const { RefCell::new(Vec::new()) };
130 static NEXT_WATCH_ID: RefCell<u64> = const { RefCell::new(1) };
131}
132
133struct WatchEntry {
134 #[allow(dead_code)]
136 id: u64,
137 path: String,
138 listener: Value,
139 stop: Arc<AtomicBool>,
140}
141
142fn register_fd(file: File) -> i32 {
143 NEXT_FD.with(|n| {
144 let fd = *n.borrow();
145 *n.borrow_mut() = fd + 1;
146 FD_TABLE.with(|t| t.borrow_mut().insert(fd, file));
147 fd
148 })
149}
150
151fn with_file<R>(fd: i32, f: impl FnOnce(&File) -> R) -> Option<R> {
152 FD_TABLE.with(|t| t.borrow().get(&fd).map(f))
153}
154
155fn fd_arg(v: Option<&Value>) -> Option<i32> {
158 match v? {
159 Value::Int(n) => Some(*n as i32),
160 Value::Float(n) if n.fract() == 0.0 => Some(*n as i32),
161 _ => None,
162 }
163}
164
165fn read_fd_to_end(fd: i32) -> Result<Vec<u8>, String> {
169 let mut out = Vec::new();
170 let r = if fd == 0 {
171 std::io::stdin().lock().read_to_end(&mut out)
174 } else {
175 match with_file(fd, |file| {
176 let mut fr: &File = file;
177 fr.read_to_end(&mut out)
178 }) {
179 Some(r) => r,
180 None => return Err("Error: EBADF: bad file descriptor, fstat".to_string()),
181 }
182 };
183 r.map(|_| out).map_err(|e| err_str("read", "", &e))
184}
185
186fn close_fd(fd: i32) -> bool {
187 FD_TABLE.with(|t| t.borrow_mut().remove(&fd).is_some())
188}
189
190pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
193 Some(match method {
194 "readFileSync" => read_file_sync(args),
196 "writeFileSync" => write_file_impl(args),
197 "appendFileSync" => append_file_impl(args),
198 "existsSync" => Ok(Value::Bool(Path::new(&arg_str(args, 0)).exists())),
199 "readdirSync" => readdir_impl(args),
200 "mkdirSync" => mkdir_impl(args),
201 "rmdirSync" | "unlinkSync" | "rmSync" => rm_impl(method, args),
202 "statSync" => stat_impl("statSync", args, true),
203 "lstatSync" => stat_impl("lstatSync", args, false),
204 "statfsSync" => statfs_impl(args),
205 "accessSync" => access_impl(args),
206 "chmodSync" => chmod_impl(args),
207 "chownSync" => chown_impl(args, true),
208 "lchownSync" => chown_impl(args, false),
209 "copyFileSync" => copy_file_impl(args),
210 "cpSync" => cp_impl(args),
211 "linkSync" => link_impl(args),
212 "symlinkSync" => symlink_impl(args),
213 "readlinkSync" => readlink_impl(args),
214 "realpathSync" => realpath_impl(args),
215 "renameSync" => rename_impl(args),
216 "truncateSync" => truncate_impl(args),
217 "utimesSync" => utimes_impl(args, true),
218 "lutimesSync" => utimes_impl(args, false),
219 "mkdtempSync" => mkdtemp_impl(args),
220 "opendirSync" => opendir_impl(args),
221 "globSync" => glob_impl(args),
222 "openSync" => open_impl(args),
224 "closeSync" => close_impl(args),
225 "readSync" => read_impl(args).map(|n| Value::Float(n as f64)),
226 "writeSync" => write_impl(args).map(|n| Value::Float(n as f64)),
227 "readvSync" => readv_impl(args).map(|n| Value::Float(n as f64)),
228 "writevSync" => writev_impl(args).map(|n| Value::Float(n as f64)),
229 "fstatSync" => fstat_impl(args),
230 "fchmodSync" => fchmod_impl(args),
231 "fchownSync" => fchown_impl(args),
232 "ftruncateSync" => ftruncate_impl(args),
233 "futimesSync" => futimes_impl(args),
234 "fsyncSync" => fsync_impl(args, false),
235 "fdatasyncSync" => fsync_impl(args, true),
236 "readFile" => return Some(read_file_async(args)),
238 "writeFile" => async_cb(args, write_file_impl(args)),
239 "appendFile" => async_cb(args, append_file_impl(args)),
240 "readdir" => async_cb(args, readdir_impl(args)),
241 "mkdir" => async_cb(args, mkdir_impl(args)),
242 "rmdir" => async_cb(args, rm_impl("rmdir", args)),
243 "rm" => async_cb(args, rm_impl("rm", args)),
244 "unlink" => async_cb(args, rm_impl("unlink", args)),
245 "stat" => async_cb(args, stat_impl("stat", args, true)),
246 "lstat" => async_cb(args, stat_impl("lstat", args, false)),
247 "statfs" => async_cb(args, statfs_impl(args)),
248 "access" => async_cb(args, access_impl(args)),
249 "chmod" => async_cb(args, chmod_impl(args)),
250 "chown" => async_cb(args, chown_impl(args, true)),
251 "lchown" => async_cb(args, chown_impl(args, false)),
252 "copyFile" => async_cb(args, copy_file_impl(args)),
253 "cp" => async_cb(args, cp_impl(args)),
254 "link" => async_cb(args, link_impl(args)),
255 "symlink" => async_cb(args, symlink_impl(args)),
256 "readlink" => async_cb(args, readlink_impl(args)),
257 "realpath" => async_cb(args, realpath_impl(args)),
258 "rename" => async_cb(args, rename_impl(args)),
259 "truncate" => async_cb(args, truncate_impl(args)),
260 "utimes" => async_cb(args, utimes_impl(args, true)),
261 "lutimes" => async_cb(args, utimes_impl(args, false)),
262 "mkdtemp" => async_cb(args, mkdtemp_impl(args)),
263 "opendir" => async_cb(args, opendir_impl(args)),
264 "glob" => async_cb(args, glob_impl(args)),
265 "exists" => exists_async(args),
266 "open" => async_cb(args, open_impl(args)),
268 "close" => async_cb(args, close_impl(args)),
269 "read" => return Some(read_write_async(args, read_impl(args))),
270 "write" => return Some(read_write_async(args, write_impl(args))),
271 "readv" => async_cb(args, readv_impl(args).map(|n| Value::Float(n as f64))),
272 "writev" => async_cb(args, writev_impl(args).map(|n| Value::Float(n as f64))),
273 "fstat" => async_cb(args, fstat_impl(args)),
274 "fchmod" => async_cb(args, fchmod_impl(args)),
275 "fchown" => async_cb(args, fchown_impl(args)),
276 "ftruncate" => async_cb(args, ftruncate_impl(args)),
277 "futimes" => async_cb(args, futimes_impl(args)),
278 "fsync" => async_cb(args, fsync_impl(args, false)),
279 "fdatasync" => async_cb(args, fsync_impl(args, true)),
280 "watchFile" => watch_file(args),
282 "unwatchFile" => unwatch_file(args),
283 "createReadStream" => create_read_stream(args),
284 "createWriteStream" => create_write_stream(args),
285 _ => return None,
286 })
287}
288
289fn async_cb(args: &[Value], result: Result<Value, String>) -> Result<Value, String> {
295 let Some(cb) = args.last().cloned().filter(is_fn) else {
296 return Ok(Value::Undef);
297 };
298 match result {
299 Ok(v) => with_host(|h| {
300 let n = h.null();
301 h.queue_micro(cb, vec![n, v]);
302 }),
303 Err(e) => with_host(|h| {
310 let ev = crate::builtins::synth_error(h, &e);
311 h.queue_micro(cb, vec![ev]);
312 }),
313 }
314 Ok(Value::Undef)
315}
316
317fn read_write_async(args: &[Value], result: Result<usize, String>) -> Result<Value, String> {
319 let Some(cb) = args.last().cloned().filter(is_fn) else {
320 return Ok(Value::Undef);
321 };
322 let buffer = args.get(1).cloned().unwrap_or(Value::Undef);
323 match result {
324 Ok(n) => with_host(|h| {
325 let nul = h.null();
326 h.queue_micro(cb, vec![nul, Value::Float(n as f64), buffer]);
327 }),
328 Err(e) => with_host(|h| {
329 let ev = crate::builtins::synth_error(h, &e);
330 h.queue_micro(cb, vec![ev]);
331 }),
332 }
333 Ok(Value::Undef)
334}
335
336fn is_fn(v: &Value) -> bool {
337 with_host(|h| crate::host::is_callable(h, v))
338}
339
340fn read_file_sync(args: &[Value]) -> Result<Value, String> {
343 let enc = encoding_arg(args, 1);
344 if let Some(fd) = fd_arg(args.first()) {
348 let bytes = read_fd_to_end(fd)?;
349 return Ok(match enc {
350 Some(e) => with_host(|h| h.new_str(super::buffer::encode_bytes(&bytes, &e))),
351 None => super::buffer::from_bytes(&bytes),
352 });
353 }
354 let path = arg_str(args, 0);
355
356 match std::fs::read(&path) {
357 Ok(bytes) => Ok(match enc {
362 Some(e) => with_host(|h| h.new_str(super::buffer::encode_bytes(&bytes, &e))),
363 None => super::buffer::from_bytes(&bytes),
364 }),
365 Err(e) if e.raw_os_error() == Some(libc::EISDIR) => Err(err_str("read", "", &e)),
372 Err(e) => Err(err_str("readFileSync", &path, &e)),
373 }
374}
375
376fn encoded_bytes(args: &[Value]) -> Vec<u8> {
384 let v = args.get(1).cloned().unwrap_or(Value::Undef);
385 if let Some(bytes) = super::buffer::view_bytes(&v) {
389 return bytes;
390 }
391 let text = with_host(|h| h.str_of(&v));
392 match encoding_arg(args, 2) {
393 Some(e) => super::buffer::decode_str(&text, &e),
394 None => text.into_bytes(),
395 }
396}
397
398fn write_file_impl(args: &[Value]) -> Result<Value, String> {
399 let path = arg_str(args, 0);
400 let data = encoded_bytes(args);
401 match std::fs::write(&path, data) {
402 Ok(_) => Ok(Value::Undef),
403 Err(e) => Err(err_str("writeFile", &path, &e)),
404 }
405}
406
407fn append_file_impl(args: &[Value]) -> Result<Value, String> {
408 let path = arg_str(args, 0);
409 let data = encoded_bytes(args);
410 let r = std::fs::OpenOptions::new()
411 .create(true)
412 .append(true)
413 .open(&path)
414 .and_then(|mut f| f.write_all(&data));
415 match r {
416 Ok(_) => Ok(Value::Undef),
417 Err(e) => Err(err_str("appendFile", &path, &e)),
418 }
419}
420
421fn read_file_async(args: &[Value]) -> Result<Value, String> {
422 let path = arg_str(args, 0);
423 let Some(cb) = args.last().cloned().filter(is_fn) else {
424 return Ok(Value::Undef);
425 };
426 let enc = if args.len() >= 3 {
427 encoding_arg(args, 1)
428 } else {
429 None
430 };
431 let (err, data) = match std::fs::read(&path) {
432 Ok(bytes) => (
433 with_host(|h| h.null()),
434 match enc {
438 Some(e) => with_host(|h| h.new_str(super::buffer::encode_bytes(&bytes, &e))),
439 None => super::buffer::from_bytes(&bytes),
440 },
441 ),
442 Err(e) => (
443 with_host(|h| {
446 let msg = if e.raw_os_error() == Some(libc::EISDIR) {
451 err_str("read", &path, &e)
452 } else {
453 err_str("readFile", &path, &e)
454 };
455 crate::builtins::synth_error(h, &msg)
456 }),
457 Value::Undef,
458 ),
459 };
460 with_host(|h| h.queue_micro(cb, vec![err, data]));
461 Ok(Value::Undef)
462}
463
464fn exists_async(args: &[Value]) -> Result<Value, String> {
465 let path = arg_str(args, 0);
466 let Some(cb) = args.last().cloned().filter(is_fn) else {
467 return Ok(Value::Undef);
468 };
469 let ex = Path::new(&path).exists();
470 with_host(|h| h.queue_micro(cb, vec![Value::Bool(ex)]));
471 Ok(Value::Undef)
472}
473
474fn mkdir_impl(args: &[Value]) -> Result<Value, String> {
477 let path = arg_str(args, 0);
478 let recursive = opt_flag(args, "recursive");
479 let r = if recursive {
480 std::fs::create_dir_all(&path)
481 } else {
482 std::fs::create_dir(&path)
483 };
484 match r {
485 Ok(_) => Ok(Value::Undef),
486 Err(e) => Err(err_str("mkdir", &path, &e)),
487 }
488}
489
490fn rm_impl(op: &str, args: &[Value]) -> Result<Value, String> {
491 let path = arg_str(args, 0);
492 let p = Path::new(&path);
493 let force = opt_flag(args, "force");
494 let r = if p.is_dir() {
495 if opt_flag(args, "recursive") {
496 std::fs::remove_dir_all(p)
497 } else {
498 std::fs::remove_dir(p)
499 }
500 } else {
501 std::fs::remove_file(p)
502 };
503 match r {
504 Ok(_) => Ok(Value::Undef),
505 Err(e) if force && e.kind() == std::io::ErrorKind::NotFound => Ok(Value::Undef),
506 Err(e) => Err(err_str(op, &path, &e)),
507 }
508}
509
510fn readdir_impl(args: &[Value]) -> Result<Value, String> {
511 let path = arg_str(args, 0);
512 let file_types = opt_flag(args, "withFileTypes");
513 let recursive = opt_flag(args, "recursive");
514 let mut names: Vec<(String, String, std::fs::FileType)> = Vec::new();
515 collect_dir(Path::new(&path), &path, "", recursive, &mut names)
516 .map_err(|e| err_str("readdir", &path, &e))?;
517 names.sort_by(|a, b| a.0.cmp(&b.0));
518 Ok(with_host(|h| {
519 let items: Vec<Value> = names
520 .into_iter()
521 .map(|(rel, parent, ft)| {
522 if file_types {
523 let base = rel.rsplit('/').next().unwrap_or(&rel).to_string();
524 build_dirent(h, base, &parent, ft)
525 } else {
526 h.new_str(rel)
527 }
528 })
529 .collect();
530 h.new_array(items)
531 }))
532}
533
534fn collect_dir(
538 dir: &Path,
539 parent: &str,
540 rel_prefix: &str,
541 recursive: bool,
542 out: &mut Vec<(String, String, std::fs::FileType)>,
543) -> std::io::Result<()> {
544 for e in std::fs::read_dir(dir)? {
545 let e = e?;
546 let name = e.file_name().to_string_lossy().into_owned();
547 let rel = if rel_prefix.is_empty() {
548 name.clone()
549 } else {
550 format!("{rel_prefix}/{name}")
551 };
552 let ft = e.file_type()?;
553 out.push((rel.clone(), parent.to_string(), ft));
554 if recursive && ft.is_dir() {
555 let sub = e.path();
556 let sub_parent = sub.to_string_lossy().into_owned();
557 collect_dir(&sub, &sub_parent, &rel, recursive, out)?;
558 }
559 }
560 Ok(())
561}
562
563fn opendir_impl(args: &[Value]) -> Result<Value, String> {
564 let path = arg_str(args, 0);
565 let rd = std::fs::read_dir(&path).map_err(|e| err_str("opendir", &path, &e))?;
566 let mut entries: Vec<(String, std::fs::FileType)> = rd
567 .filter_map(|e| e.ok())
568 .filter_map(|e| {
569 e.file_type()
570 .ok()
571 .map(|ft| (e.file_name().to_string_lossy().into_owned(), ft))
572 })
573 .collect();
574 entries.sort_by(|a, b| a.0.cmp(&b.0));
575 Ok(with_host(|h| {
576 let dirents: Vec<Value> = entries
577 .into_iter()
578 .map(|(name, ft)| build_dirent(h, name, &path, ft))
579 .collect();
580 let arr = h.new_array(dirents);
581 let mut m = IndexMap::new();
582 m.insert("@@native".into(), h.new_str("Dir"));
583 m.insert("path".into(), h.new_str(path.clone()));
584 m.insert("@@entries".into(), arr);
585 m.insert("@@pos".into(), Value::Float(0.0));
586 h.new_object(m)
587 }))
588}
589
590fn access_impl(args: &[Value]) -> Result<Value, String> {
593 let path = arg_str(args, 0);
594 match std::fs::metadata(&path) {
595 Ok(_) => Ok(Value::Undef),
596 Err(e) => Err(err_str("access", &path, &e)),
597 }
598}
599
600fn chmod_impl(args: &[Value]) -> Result<Value, String> {
601 let path = arg_str(args, 0);
602 let mode = arg_num(args, 1) as u32;
603 match std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)) {
604 Ok(_) => Ok(Value::Undef),
605 Err(e) => Err(err_str("chmod", &path, &e)),
606 }
607}
608
609fn chown_impl(args: &[Value], follow: bool) -> Result<Value, String> {
610 let path = arg_str(args, 0);
611 let uid = arg_num(args, 1) as libc::uid_t;
612 let gid = arg_num(args, 2) as libc::gid_t;
613 let c = cpath(&path, "chown")?;
614 let rc = unsafe {
615 if follow {
616 libc::chown(c.as_ptr(), uid, gid)
617 } else {
618 libc::lchown(c.as_ptr(), uid, gid)
619 }
620 };
621 ok_or_errno(rc, "chown", &path)
622}
623
624fn utimes_impl(args: &[Value], follow: bool) -> Result<Value, String> {
625 let path = arg_str(args, 0);
626 let times = [
627 to_timeval(time_secs(args, 1)),
628 to_timeval(time_secs(args, 2)),
629 ];
630 let c = cpath(&path, "utimes")?;
631 let rc = unsafe {
632 if follow {
633 libc::utimes(c.as_ptr(), times.as_ptr())
634 } else {
635 libc::lutimes(c.as_ptr(), times.as_ptr())
636 }
637 };
638 ok_or_errno(rc, "utimes", &path)
639}
640
641const COPYFILE_EXCL: u32 = 1;
644
645fn copy_file_impl(args: &[Value]) -> Result<Value, String> {
646 let src = arg_str(args, 0);
647 let dest = arg_str(args, 1);
648 let mode = arg_num(args, 2);
649 if !mode.is_nan() && (mode as u32) & COPYFILE_EXCL != 0 && Path::new(&dest).exists() {
650 return Err(format!(
651 "Error: EEXIST: file already exists, copyfile '{src}' -> '{dest}'"
652 ));
653 }
654 match std::fs::copy(&src, &dest) {
655 Ok(_) => Ok(Value::Undef),
656 Err(e) => Err(err_str2("copyFile", &src, &dest, &e)),
657 }
658}
659
660fn cp_impl(args: &[Value]) -> Result<Value, String> {
661 let src = arg_str(args, 0);
662 let dest = arg_str(args, 1);
663 let recursive = opt_flag(args, "recursive");
664 let r = if recursive {
665 cp_recursive(Path::new(&src), Path::new(&dest))
666 } else {
667 std::fs::copy(&src, &dest).map(|_| ())
668 };
669 match r {
670 Ok(_) => Ok(Value::Undef),
671 Err(e) => Err(err_str("cp", &src, &e)),
672 }
673}
674
675fn cp_recursive(src: &Path, dest: &Path) -> std::io::Result<()> {
676 if src.is_dir() {
677 std::fs::create_dir_all(dest)?;
678 for e in std::fs::read_dir(src)? {
679 let e = e?;
680 cp_recursive(&e.path(), &dest.join(e.file_name()))?;
681 }
682 Ok(())
683 } else {
684 if let Some(parent) = dest.parent() {
685 std::fs::create_dir_all(parent).ok();
686 }
687 std::fs::copy(src, dest).map(|_| ())
688 }
689}
690
691fn link_impl(args: &[Value]) -> Result<Value, String> {
692 let existing = arg_str(args, 0);
693 let new = arg_str(args, 1);
694 match std::fs::hard_link(&existing, &new) {
695 Ok(_) => Ok(Value::Undef),
696 Err(e) => Err(err_str("link", &existing, &e)),
697 }
698}
699
700fn symlink_impl(args: &[Value]) -> Result<Value, String> {
701 let target = arg_str(args, 0);
702 let path = arg_str(args, 1);
703 match std::os::unix::fs::symlink(&target, &path) {
704 Ok(_) => Ok(Value::Undef),
705 Err(e) => Err(err_str("symlink", &path, &e)),
706 }
707}
708
709fn readlink_impl(args: &[Value]) -> Result<Value, String> {
710 let path = arg_str(args, 0);
711 match std::fs::read_link(&path) {
712 Ok(p) => Ok(with_host(|h| h.new_str(p.to_string_lossy().into_owned()))),
713 Err(e) => Err(err_str("readlink", &path, &e)),
714 }
715}
716
717fn realpath_impl(args: &[Value]) -> Result<Value, String> {
718 let path = arg_str(args, 0);
719 match std::fs::canonicalize(&path) {
720 Ok(p) => Ok(with_host(|h| h.new_str(p.to_string_lossy().into_owned()))),
721 Err(e) => Err(err_str("realpath", &resolved_prefix(&path), &e)),
726 }
727}
728
729fn resolved_prefix(path: &str) -> String {
733 let p = Path::new(path);
734 let mut rest: Vec<&std::ffi::OsStr> = Vec::new();
735 let mut cur = p;
736 loop {
737 if let Ok(base) = std::fs::canonicalize(cur) {
738 let mut out = base;
739 for part in rest.iter().rev() {
740 out.push(part);
741 }
742 return out.to_string_lossy().into_owned();
743 }
744 match (cur.file_name(), cur.parent()) {
745 (Some(name), Some(parent)) if !parent.as_os_str().is_empty() => {
746 rest.push(name);
747 cur = parent;
748 }
749 _ => return path.to_string(),
750 }
751 }
752}
753
754fn rename_impl(args: &[Value]) -> Result<Value, String> {
755 let from = arg_str(args, 0);
756 let to = arg_str(args, 1);
757 match std::fs::rename(&from, &to) {
758 Ok(_) => Ok(Value::Undef),
759 Err(e) => Err(err_str2("rename", &from, &to, &e)),
760 }
761}
762
763fn truncate_impl(args: &[Value]) -> Result<Value, String> {
764 let path = arg_str(args, 0);
765 let len = arg_num(args, 1);
766 let len = if len.is_nan() { 0 } else { len as u64 };
767 let r = std::fs::OpenOptions::new()
768 .write(true)
769 .open(&path)
770 .and_then(|f| f.set_len(len));
771 match r {
772 Ok(_) => Ok(Value::Undef),
773 Err(e) => Err(err_str("open", &path, &e)),
776 }
777}
778
779fn mkdtemp_impl(args: &[Value]) -> Result<Value, String> {
780 let prefix = arg_str(args, 0);
781 for _ in 0..64 {
782 let candidate = format!("{prefix}{}", random_suffix());
783 match std::fs::create_dir(&candidate) {
784 Ok(_) => return Ok(with_host(|h| h.new_str(candidate))),
785 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
786 Err(e) => return Err(err_str("mkdtemp", &prefix, &e)),
787 }
788 }
789 Err(format!(
790 "Error: EEXIST: file already exists, mkdtemp '{prefix}'"
791 ))
792}
793
794fn random_suffix() -> String {
796 const CHARS: &[u8; 62] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
797 let mut raw = [0u8; 6];
798 if getrandom::getrandom(&mut raw).is_err() {
799 let nanos = std::time::SystemTime::now()
801 .duration_since(UNIX_EPOCH)
802 .map(|d| d.subsec_nanos())
803 .unwrap_or(0);
804 raw = nanos
805 .to_le_bytes()
806 .iter()
807 .cycle()
808 .take(6)
809 .copied()
810 .collect::<Vec<_>>()
811 .try_into()
812 .unwrap();
813 }
814 raw.iter()
815 .map(|b| CHARS[(*b as usize) % 62] as char)
816 .collect()
817}
818
819fn open_impl(args: &[Value]) -> Result<Value, String> {
822 let path = arg_str(args, 0);
823 let flags = match args.get(1) {
824 Some(v) if !matches!(v, Value::Undef) && !is_fn(v) => arg_str(args, 1),
825 _ => "r".to_string(),
826 };
827 match open_options(&flags).open(&path) {
828 Ok(f) => Ok(Value::Float(register_fd(f) as f64)),
829 Err(e) => Err(err_str("open", &path, &e)),
830 }
831}
832
833fn open_options(flags: &str) -> std::fs::OpenOptions {
834 let mut o = std::fs::OpenOptions::new();
835 match flags {
836 "r" | "rs" | "sr" => {
837 o.read(true);
838 }
839 "r+" | "rs+" | "sr+" => {
840 o.read(true).write(true);
841 }
842 "w" => {
843 o.write(true).create(true).truncate(true);
844 }
845 "wx" | "xw" => {
846 o.write(true).create_new(true);
847 }
848 "w+" => {
849 o.read(true).write(true).create(true).truncate(true);
850 }
851 "wx+" | "xw+" => {
852 o.read(true).write(true).create_new(true);
853 }
854 "a" => {
855 o.append(true).create(true);
856 }
857 "ax" | "xa" => {
858 o.append(true).create_new(true);
859 }
860 "a+" => {
861 o.read(true).append(true).create(true);
862 }
863 "ax+" | "xa+" => {
864 o.read(true).append(true).create_new(true);
865 }
866 _ => {
867 o.read(true);
868 }
869 }
870 o
871}
872
873fn close_impl(args: &[Value]) -> Result<Value, String> {
874 let fd = arg_num(args, 0) as i32;
875 if close_fd(fd) {
876 Ok(Value::Undef)
877 } else {
878 Err("Error: EBADF: bad file descriptor, close".to_string())
879 }
880}
881
882fn read_impl(args: &[Value]) -> Result<usize, String> {
883 let fd = arg_num(args, 0) as i32;
884 let buffer = args.get(1).cloned().unwrap_or(Value::Undef);
885 let cap = buf_len(&buffer);
886 let offset = num_or(args, 2, 0.0) as usize;
887 let length = num_or(args, 3, (cap.saturating_sub(offset)) as f64) as usize;
888 let position = position_arg(args, 4);
889 if fd == 0 {
892 let mut buf = vec![0u8; length];
893 return match std::io::stdin().lock().read(&mut buf) {
894 Ok(n) => Ok(write_into_buffer(&buffer, offset, &buf[..n])),
895 Err(e) => Err(err_str("read", "", &e)),
896 };
897 }
898 let n = with_file(fd, |file| {
899 let mut fr: &File = file;
900 if let Some(pos) = position {
901 fr.seek(SeekFrom::Start(pos)).ok();
902 }
903 let mut buf = vec![0u8; length];
904 fr.read(&mut buf).map(|n| {
905 buf.truncate(n);
906 buf
907 })
908 });
909 match n {
910 Some(Ok(data)) => Ok(write_into_buffer(&buffer, offset, &data)),
911 Some(Err(e)) => Err(err_str("read", "", &e)),
912 None => Err("Error: EBADF: bad file descriptor, read".to_string()),
913 }
914}
915
916fn write_impl(args: &[Value]) -> Result<usize, String> {
917 let fd = arg_num(args, 0) as i32;
918 let src = args.get(1).cloned().unwrap_or(Value::Undef);
919 let view = super::buffer::view_bytes(&src);
922 let (data, position) = if let Some(all) = view {
925 let offset = num_or(args, 2, 0.0) as usize;
926 let length = num_or(args, 3, (all.len().saturating_sub(offset)) as f64) as usize;
927 let end = (offset + length).min(all.len());
928 (
929 all[offset.min(all.len())..end].to_vec(),
930 position_arg(args, 4),
931 )
932 } else {
933 (
934 with_host(|h| h.str_of(&src)).into_bytes(),
935 position_arg(args, 2),
936 )
937 };
938 let r = with_file(fd, |file| {
939 let mut fr: &File = file;
940 if let Some(pos) = position {
941 fr.seek(SeekFrom::Start(pos)).ok();
942 }
943 fr.write(&data)
944 });
945 match r {
946 Some(Ok(n)) => Ok(n),
947 Some(Err(e)) => Err(err_str("write", "", &e)),
948 None => Err("Error: EBADF: bad file descriptor, write".to_string()),
949 }
950}
951
952fn readv_impl(args: &[Value]) -> Result<usize, String> {
953 let fd = arg_num(args, 0) as i32;
954 let buffers = array_items(args.get(1));
955 let position = position_arg(args, 2);
956 let total = with_file(fd, |file| {
957 let mut fr: &File = file;
958 if let Some(pos) = position {
959 fr.seek(SeekFrom::Start(pos)).ok();
960 }
961 let mut chunks: Vec<(Value, Vec<u8>)> = Vec::new();
962 for b in &buffers {
963 let cap = buf_len(b);
964 let mut buf = vec![0u8; cap];
965 match fr.read(&mut buf) {
966 Ok(0) => break,
967 Ok(n) => {
968 buf.truncate(n);
969 chunks.push((b.clone(), buf));
970 }
971 Err(e) => return Err(e),
972 }
973 }
974 Ok(chunks)
975 });
976 match total {
977 Some(Ok(chunks)) => Ok(chunks.iter().map(|(b, d)| write_into_buffer(b, 0, d)).sum()),
978 Some(Err(e)) => Err(err_str("readv", "", &e)),
979 None => Err("Error: EBADF: bad file descriptor, readv".to_string()),
980 }
981}
982
983fn writev_impl(args: &[Value]) -> Result<usize, String> {
984 let fd = arg_num(args, 0) as i32;
985 let buffers = array_items(args.get(1));
986 let position = position_arg(args, 2);
987 let mut data = Vec::new();
988 for b in &buffers {
989 data.extend(buf_bytes(b));
990 }
991 let r = with_file(fd, |file| {
992 let mut fr: &File = file;
993 if let Some(pos) = position {
994 fr.seek(SeekFrom::Start(pos)).ok();
995 }
996 fr.write(&data)
997 });
998 match r {
999 Some(Ok(n)) => Ok(n),
1000 Some(Err(e)) => Err(err_str("writev", "", &e)),
1001 None => Err("Error: EBADF: bad file descriptor, writev".to_string()),
1002 }
1003}
1004
1005fn fstat_impl(args: &[Value]) -> Result<Value, String> {
1006 let fd = arg_num(args, 0) as i32;
1007 let md = with_file(fd, |file| file.metadata());
1008 match md {
1009 Some(Ok(md)) => Ok(with_host(|h| build_stats(h, &md))),
1010 Some(Err(e)) => Err(err_str("fstat", "", &e)),
1011 None => Err("Error: EBADF: bad file descriptor, fstat".to_string()),
1012 }
1013}
1014
1015fn fchmod_impl(args: &[Value]) -> Result<Value, String> {
1016 let fd = arg_num(args, 0) as i32;
1017 let mode = arg_num(args, 1) as libc::mode_t;
1018 let rc = with_file(fd, |file| unsafe { libc::fchmod(file.as_raw_fd(), mode) });
1019 fd_result(rc, "fchmod")
1020}
1021
1022fn fchown_impl(args: &[Value]) -> Result<Value, String> {
1023 let fd = arg_num(args, 0) as i32;
1024 let uid = arg_num(args, 1) as libc::uid_t;
1025 let gid = arg_num(args, 2) as libc::gid_t;
1026 let rc = with_file(fd, |file| unsafe {
1027 libc::fchown(file.as_raw_fd(), uid, gid)
1028 });
1029 fd_result(rc, "fchown")
1030}
1031
1032fn futimes_impl(args: &[Value]) -> Result<Value, String> {
1033 let fd = arg_num(args, 0) as i32;
1034 let times = [
1035 to_timeval(time_secs(args, 1)),
1036 to_timeval(time_secs(args, 2)),
1037 ];
1038 let rc = with_file(fd, |file| unsafe {
1039 libc::futimes(file.as_raw_fd(), times.as_ptr())
1040 });
1041 fd_result(rc, "futimes")
1042}
1043
1044fn ftruncate_impl(args: &[Value]) -> Result<Value, String> {
1045 let fd = arg_num(args, 0) as i32;
1046 let len = arg_num(args, 1);
1047 let len = if len.is_nan() { 0 } else { len as u64 };
1048 match with_file(fd, |file| file.set_len(len)) {
1049 Some(Ok(_)) => Ok(Value::Undef),
1050 Some(Err(e)) => Err(err_str("ftruncate", "", &e)),
1051 None => Err("Error: EBADF: bad file descriptor, ftruncate".to_string()),
1052 }
1053}
1054
1055fn fsync_impl(args: &[Value], data_only: bool) -> Result<Value, String> {
1056 let fd = arg_num(args, 0) as i32;
1057 let op = if data_only { "fdatasync" } else { "fsync" };
1058 let r = with_file(fd, |file| {
1059 if data_only {
1060 file.sync_data()
1061 } else {
1062 file.sync_all()
1063 }
1064 });
1065 match r {
1066 Some(Ok(_)) => Ok(Value::Undef),
1067 Some(Err(e)) => Err(err_str(op, "", &e)),
1068 None => Err(format!("Error: EBADF: bad file descriptor, {op}")),
1069 }
1070}
1071
1072fn fd_result(rc: Option<libc::c_int>, op: &str) -> Result<Value, String> {
1075 match rc {
1076 Some(0) => Ok(Value::Undef),
1077 Some(_) => Err(err_str(op, "", &std::io::Error::last_os_error())),
1078 None => Err(format!("Error: EBADF: bad file descriptor, {op}")),
1079 }
1080}
1081
1082fn stat_impl(op: &str, args: &[Value], follow: bool) -> Result<Value, String> {
1085 let path = arg_str(args, 0);
1086 let md = if follow {
1087 std::fs::metadata(&path)
1088 } else {
1089 std::fs::symlink_metadata(&path)
1090 };
1091 match md {
1092 Ok(md) => Ok(with_host(|h| build_stats(h, &md))),
1093 Err(e) => Err(err_str(op, &path, &e)),
1094 }
1095}
1096
1097fn statfs_impl(args: &[Value]) -> Result<Value, String> {
1098 let path = arg_str(args, 0);
1099 let c = cpath(&path, "statfs")?;
1100 let mut st: libc::statvfs = unsafe { std::mem::zeroed() };
1101 if unsafe { libc::statvfs(c.as_ptr(), &mut st) } != 0 {
1102 return Err(err_str("statfs", &path, &std::io::Error::last_os_error()));
1103 }
1104 Ok(with_host(|h| {
1105 let mut m = IndexMap::new();
1106 m.insert("type".into(), Value::Float(st.f_fsid as f64));
1107 m.insert("bsize".into(), Value::Float(st.f_bsize as f64));
1108 m.insert("blocks".into(), Value::Float(st.f_blocks as f64));
1109 m.insert("bfree".into(), Value::Float(st.f_bfree as f64));
1110 m.insert("bavail".into(), Value::Float(st.f_bavail as f64));
1111 m.insert("files".into(), Value::Float(st.f_files as f64));
1112 m.insert("ffree".into(), Value::Float(st.f_ffree as f64));
1113 h.new_object(m)
1114 }))
1115}
1116
1117fn build_stats(h: &mut crate::host::JsHost, md: &std::fs::Metadata) -> Value {
1119 let ns = |s: i64, n: i64| s as f64 * 1000.0 + n as f64 / 1_000_000.0;
1120 let atime_ms = ns(md.atime(), md.atime_nsec());
1121 let mtime_ms = ns(md.mtime(), md.mtime_nsec());
1122 let ctime_ms = ns(md.ctime(), md.ctime_nsec());
1123 let birth_ms = md
1124 .created()
1125 .ok()
1126 .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
1127 .map(|d| d.as_secs_f64() * 1000.0)
1128 .unwrap_or(mtime_ms);
1129 let ft = md.file_type();
1130 let date = |h: &mut crate::host::JsHost, ms: f64| {
1131 let mut d = IndexMap::new();
1132 d.insert("@@native".into(), h.new_str("Date"));
1133 d.insert("@@ms".into(), Value::Float(ms));
1134 h.new_object(d)
1135 };
1136 let mut m = IndexMap::new();
1137 m.insert("@@native".into(), h.new_str("Stats"));
1138 m.insert("@@isFile".into(), Value::Bool(md.is_file()));
1139 m.insert("@@isDir".into(), Value::Bool(md.is_dir()));
1140 m.insert("@@isSymlink".into(), Value::Bool(ft.is_symlink()));
1141 m.insert("dev".into(), Value::Float(md.dev() as f64));
1142 m.insert("mode".into(), Value::Float(md.mode() as f64));
1143 m.insert("nlink".into(), Value::Float(md.nlink() as f64));
1144 m.insert("uid".into(), Value::Float(md.uid() as f64));
1145 m.insert("gid".into(), Value::Float(md.gid() as f64));
1146 m.insert("rdev".into(), Value::Float(md.rdev() as f64));
1147 m.insert("blksize".into(), Value::Float(md.blksize() as f64));
1148 m.insert("ino".into(), Value::Float(md.ino() as f64));
1149 m.insert("size".into(), Value::Float(md.len() as f64));
1150 m.insert("blocks".into(), Value::Float(md.blocks() as f64));
1151 m.insert("atimeMs".into(), Value::Float(atime_ms));
1152 m.insert("mtimeMs".into(), Value::Float(mtime_ms));
1153 m.insert("ctimeMs".into(), Value::Float(ctime_ms));
1154 m.insert("birthtimeMs".into(), Value::Float(birth_ms));
1155 let atime = date(h, atime_ms);
1156 m.insert("atime".into(), atime);
1157 let mtime = date(h, mtime_ms);
1158 m.insert("mtime".into(), mtime);
1159 let ctime = date(h, ctime_ms);
1160 m.insert("ctime".into(), ctime);
1161 let birthtime = date(h, birth_ms);
1162 m.insert("birthtime".into(), birthtime);
1163 let obj = h.new_object(m);
1164 for k in ["atime", "mtime", "ctime", "birthtime"] {
1170 h.hide_prop(&obj, k);
1171 }
1172 obj
1173}
1174
1175fn watch_file(args: &[Value]) -> Result<Value, String> {
1178 let path = arg_str(args, 0);
1179 let Some(listener) = args.last().cloned().filter(is_fn) else {
1180 return Ok(Value::Undef);
1181 };
1182 let interval = interval_opt(args).unwrap_or(5007.0).max(1.0) as u64;
1183 let abs = std::fs::canonicalize(&path)
1184 .map(|p| p.to_string_lossy().into_owned())
1185 .unwrap_or_else(|_| path.clone());
1186
1187 let stop = Arc::new(AtomicBool::new(false));
1188 let id = NEXT_WATCH_ID.with(|n| {
1189 let v = *n.borrow();
1190 *n.borrow_mut() = v + 1;
1191 v
1192 });
1193 WATCHERS.with(|w| {
1194 w.borrow_mut().push(WatchEntry {
1195 id,
1196 path: abs.clone(),
1197 listener: listener.clone(),
1198 stop: stop.clone(),
1199 });
1200 });
1201 with_host(|h| h.incr_handle());
1202
1203 let tx = with_host(|h| h.io_sender());
1204 let poll_stop = stop.clone();
1205 let poll_listener = listener;
1206 std::thread::spawn(move || {
1207 let mut prev = stat_parts(&abs);
1208 loop {
1209 if poll_stop.load(Ordering::Acquire) {
1210 break;
1211 }
1212 std::thread::sleep(Duration::from_millis(interval));
1213 if poll_stop.load(Ordering::Acquire) {
1214 break;
1215 }
1216 let curr = stat_parts(&abs);
1217 if curr != prev {
1218 let l = poll_listener.clone();
1219 let (p0, p1, p2) = prev;
1220 let (c0, c1, c2) = curr;
1221 let _ = tx.send(Box::new(move || {
1222 let cur = with_host(|h| stats_from_parts(h, c0, c1, c2));
1223 let old = with_host(|h| stats_from_parts(h, p0, p1, p2));
1224 if let Err(e) = invoke(&l, vec![cur, old], None) {
1225 eprintln!("{e}");
1226 }
1227 Ok(())
1228 }));
1229 prev = curr;
1230 }
1231 }
1232 });
1233 Ok(Value::Undef)
1234}
1235
1236fn unwatch_file(args: &[Value]) -> Result<Value, String> {
1237 let path = arg_str(args, 0);
1238 let abs = std::fs::canonicalize(&path)
1239 .map(|p| p.to_string_lossy().into_owned())
1240 .unwrap_or_else(|_| path.clone());
1241 let listener = args.get(1).cloned().filter(is_fn);
1242 let removed = WATCHERS.with(|w| {
1243 let mut w = w.borrow_mut();
1244 let mut count = 0;
1245 w.retain(|e| {
1246 let matches =
1247 e.path == abs && listener.as_ref().map(|l| *l == e.listener).unwrap_or(true);
1248 if matches {
1249 e.stop.store(true, Ordering::Release);
1250 count += 1;
1251 }
1252 !matches
1253 });
1254 count
1255 });
1256 for _ in 0..removed {
1257 with_host(|h| h.decr_handle());
1258 }
1259 if removed > 0 {
1261 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
1262 }
1263 Ok(Value::Undef)
1264}
1265
1266fn stat_parts(path: &str) -> (bool, i64, u64) {
1268 match std::fs::metadata(path) {
1269 Ok(md) => (
1270 true,
1271 md.mtime() * 1000 + md.mtime_nsec() / 1_000_000,
1272 md.len(),
1273 ),
1274 Err(_) => (false, 0, 0),
1275 }
1276}
1277
1278fn stats_from_parts(h: &mut crate::host::JsHost, exists: bool, mtime_ms: i64, size: u64) -> Value {
1279 let ms = mtime_ms as f64;
1280 let date = |h: &mut crate::host::JsHost, ms: f64| {
1281 let mut d = IndexMap::new();
1282 d.insert("@@native".into(), h.new_str("Date"));
1283 d.insert("@@ms".into(), Value::Float(ms));
1284 h.new_object(d)
1285 };
1286 let mut m = IndexMap::new();
1287 m.insert("@@native".into(), h.new_str("Stats"));
1288 m.insert("@@isFile".into(), Value::Bool(exists));
1289 m.insert("@@isDir".into(), Value::Bool(false));
1290 m.insert("@@isSymlink".into(), Value::Bool(false));
1291 m.insert(
1292 "size".into(),
1293 Value::Float(if exists { size as f64 } else { 0.0 }),
1294 );
1295 m.insert("atimeMs".into(), Value::Float(ms));
1296 m.insert("mtimeMs".into(), Value::Float(ms));
1297 m.insert("ctimeMs".into(), Value::Float(ms));
1298 m.insert("birthtimeMs".into(), Value::Float(ms));
1299 let mt = date(h, ms);
1300 m.insert("mtime".into(), mt);
1301 let at = date(h, ms);
1302 m.insert("atime".into(), at);
1303 h.new_object(m)
1304}
1305
1306fn create_read_stream(args: &[Value]) -> Result<Value, String> {
1309 let path = arg_str(args, 0);
1310 let enc = encoding_arg(args, 1);
1311 let stream = with_host(|h| {
1312 let mut extra = IndexMap::new();
1313 extra.insert("path".into(), h.new_str(path.clone()));
1314 if let Some(e) = &enc {
1315 extra.insert("@@encoding".into(), h.new_str(e.clone()));
1316 }
1317 extra
1318 });
1319 let stream = super::net::new_emitter_object("FSReadStream", stream);
1320 with_host(|h| h.incr_handle());
1321 let recv = stream.clone();
1322 let p = path;
1323 with_host(|h| {
1324 h.queue_micro_native(Box::new(move || {
1325 read_stream_pump(&recv, &p);
1326 Ok(())
1327 }))
1328 });
1329 Ok(stream)
1330}
1331
1332fn read_stream_pump(recv: &Value, path: &str) {
1335 with_host(|h| h.decr_handle());
1336 let opened = File::open(path).and_then(|f| {
1337 let mut bytes = Vec::new();
1338 (&f).read_to_end(&mut bytes).map(|_| (f, bytes))
1339 });
1340 let bytes = match opened {
1341 Ok((file, b)) => {
1342 let fd = register_fd(file);
1345 let open = with_host(|h| h.new_str("open"));
1346 let _ = super::events::instance_call(recv, "emit", vec![open, Value::Float(fd as f64)]);
1347 let _ =
1348 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("ready"))]);
1349 close_fd(fd);
1350 b
1351 }
1352
1353 Err(e) => {
1354 let ev = with_host(|h| crate::builtins::synth_error(h, &err_str("open", path, &e)));
1355 let _ = super::events::instance_call(
1356 recv,
1357 "emit",
1358 vec![with_host(|h| h.new_str("error")), ev],
1359 );
1360 return;
1361 }
1362 };
1363 let enc = get_prop(recv, "@@encoding").map(|v| with_host(|h| h.str_of(&v)));
1364 let chunk = match enc.as_deref() {
1365 Some(e) if e != "buffer" => {
1366 with_host(|h| h.new_str(String::from_utf8_lossy(&bytes).into_owned()))
1367 }
1368 _ => super::buffer::from_bytes(&bytes),
1369 };
1370 if let Some(dest) = get_prop(recv, "@@pipeDest") {
1371 let _ = crate::host::call_method(&dest, "write", vec![chunk]);
1372 let _ = crate::host::call_method(&dest, "end", vec![]);
1373 } else {
1374 let name = with_host(|h| h.new_str("data"));
1375 let _ = super::events::instance_call(recv, "emit", vec![name, chunk]);
1376 }
1377 let target = recv.clone();
1380 with_host(|h| {
1381 h.queue_micro_native(Box::new(move || {
1382 let end = with_host(|h| h.new_str("end"));
1383 super::events::instance_call(&target, "emit", vec![end])?;
1384 with_host(|h| {
1385 h.queue_micro_native(Box::new(move || {
1386 let close = with_host(|h| h.new_str("close"));
1387 super::events::instance_call(&target, "emit", vec![close]).map(|_| ())
1388 }))
1389 });
1390 Ok(())
1391 }))
1392 });
1393}
1394
1395pub const READ_STREAM_METHODS: &[&str] = &[
1396 "pipe",
1397 "pause",
1398 "resume",
1399 "setEncoding",
1400 "destroy",
1401 "close",
1402 "read",
1403];
1404
1405pub fn read_stream_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
1410 if !READ_STREAM_METHODS.contains(&method) && super::events::METHODS.contains(&method) {
1411 return super::events::instance_call(recv, method, args);
1412 }
1413 match method {
1414 "pipe" => {
1415 if let Some(dest) = args.first().cloned() {
1416 set_prop(recv, "@@pipeDest", dest.clone());
1417 Ok(dest)
1418 } else {
1419 Ok(recv.clone())
1420 }
1421 }
1422 "setEncoding" => {
1423 set_prop(
1424 recv,
1425 "@@encoding",
1426 with_host(|h| h.new_str(super::arg_str(&args, 0))),
1427 );
1428 Ok(recv.clone())
1429 }
1430 "pause" | "resume" | "read" => Ok(recv.clone()),
1431 "destroy" | "close" => {
1432 let _ =
1433 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))]);
1434 Ok(recv.clone())
1435 }
1436 _ => Err(crate::host::type_error(&format!(
1437 "stream.{method} is not a function"
1438 ))),
1439 }
1440}
1441
1442fn create_write_stream(args: &[Value]) -> Result<Value, String> {
1443 let path = arg_str(args, 0);
1444 let flags = match encoding_flag(args, "flags") {
1445 Some(f) => f,
1446 None => "w".to_string(),
1447 };
1448 let file = open_options(&flags)
1449 .open(&path)
1450 .map_err(|e| err_str("open", &path, &e))?;
1451 let fd = register_fd(file);
1452 let stream = with_host(|h| {
1453 let mut extra = IndexMap::new();
1454 extra.insert("path".into(), h.new_str(path));
1455 extra.insert("@@wfd".into(), Value::Float(fd as f64));
1456 extra.insert("bytesWritten".into(), Value::Float(0.0));
1457 extra
1458 });
1459 let stream = super::net::new_emitter_object("FSWriteStream", stream);
1460 with_host(|h| h.incr_handle());
1461 let recv = stream.clone();
1462 with_host(|h| {
1463 h.queue_micro_native(Box::new(move || {
1464 let open = with_host(|h| h.new_str("open"));
1465 let _ =
1466 super::events::instance_call(&recv, "emit", vec![open, Value::Float(fd as f64)]);
1467 let _ = super::events::instance_call(
1468 &recv,
1469 "emit",
1470 vec![with_host(|h| h.new_str("ready"))],
1471 );
1472 Ok(())
1473 }))
1474 });
1475 Ok(stream)
1476}
1477
1478pub const WRITE_STREAM_METHODS: &[&str] = &[
1479 "write",
1480 "end",
1481 "destroy",
1482 "close",
1483 "cork",
1484 "uncork",
1485 "setDefaultEncoding",
1486];
1487
1488pub fn write_stream_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
1491 if !WRITE_STREAM_METHODS.contains(&method) && super::events::METHODS.contains(&method) {
1492 return super::events::instance_call(recv, method, args);
1493 }
1494 match method {
1495 "write" => {
1496 write_stream_bytes(recv, args.first());
1497 if let Some(cb) = args.iter().find(|v| is_fn(v)).cloned() {
1499 with_host(|h| {
1500 h.queue_micro_native(Box::new(move || {
1501 let null = with_host(|h| h.null());
1502 invoke(&cb, vec![null], None).map(|_| ())
1503 }))
1504 });
1505 }
1506 Ok(Value::Bool(true))
1507 }
1508 "end" => {
1509 if let Some(chunk) = args
1510 .first()
1511 .filter(|v| !matches!(v, Value::Undef) && !is_fn(v))
1512 {
1513 write_stream_bytes(recv, Some(chunk));
1514 }
1515 if let Some(fd) = get_prop(recv, "@@wfd").map(|v| with_host(|h| h.to_number(&v)) as i32)
1516 {
1517 close_fd(fd);
1518 }
1519 with_host(|h| h.decr_handle());
1520 let target = recv.clone();
1524 let cb = args.iter().find(|v| is_fn(v)).cloned();
1525 with_host(|h| {
1526 h.queue_micro_native(Box::new(move || {
1527 let ev = |name: &str| {
1528 let n = with_host(|h| h.new_str(name));
1529 super::events::instance_call(&target, "emit", vec![n]).map(|_| ())
1530 };
1531 if let Some(cb) = cb {
1534 let null = with_host(|h| h.null());
1535 invoke(&cb, vec![null], None)?;
1536 }
1537 ev("finish")?;
1538 ev("close")
1539 }))
1540 });
1541 Ok(recv.clone())
1542 }
1543
1544 "destroy" | "close" => {
1545 if let Some(fd) = get_prop(recv, "@@wfd").map(|v| with_host(|h| h.to_number(&v)) as i32)
1546 {
1547 close_fd(fd);
1548 }
1549 let _ =
1550 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))]);
1551 with_host(|h| h.decr_handle());
1552 Ok(recv.clone())
1553 }
1554 "cork" | "uncork" | "setDefaultEncoding" => Ok(recv.clone()),
1555 _ => Err(crate::host::type_error(&format!(
1556 "stream.{method} is not a function"
1557 ))),
1558 }
1559}
1560
1561fn write_stream_bytes(recv: &Value, chunk: Option<&Value>) {
1562 let Some(chunk) = chunk else { return };
1563 let data = value_bytes(chunk);
1564 if let Some(fd) = get_prop(recv, "@@wfd").map(|v| with_host(|h| h.to_number(&v)) as i32) {
1565 let written = with_file(fd, |file| {
1566 let mut fr: &File = file;
1567 fr.write(&data).unwrap_or(0)
1568 })
1569 .unwrap_or(0);
1570 let prev = get_prop(recv, "bytesWritten")
1571 .map(|v| with_host(|h| h.to_number(&v)))
1572 .unwrap_or(0.0);
1573 set_prop(recv, "bytesWritten", Value::Float(prev + written as f64));
1574 }
1575}
1576
1577pub fn stats_call(recv: &Value, method: &str) -> Result<Value, String> {
1581 type_test(recv, method, "stats")
1582}
1583
1584pub const DIRENT_METHODS: &[&str] = &[
1585 "isFile",
1586 "isDirectory",
1587 "isSymbolicLink",
1588 "isBlockDevice",
1589 "isCharacterDevice",
1590 "isFIFO",
1591 "isSocket",
1592];
1593
1594pub fn dirent_call(recv: &Value, method: &str) -> Result<Value, String> {
1597 type_test(recv, method, "dirent")
1598}
1599
1600fn type_test(recv: &Value, method: &str, what: &str) -> Result<Value, String> {
1604 let read = |key: &str| {
1605 with_host(|h| match h.get(recv) {
1606 Some(JsObj::Object(p)) => matches!(p.get(key), Some(Value::Bool(true))),
1607 _ => false,
1608 })
1609 };
1610 match method {
1611 "isFile" => Ok(Value::Bool(read("@@isFile"))),
1612 "isDirectory" => Ok(Value::Bool(read("@@isDir"))),
1613 "isSymbolicLink" => Ok(Value::Bool(read("@@isSymlink"))),
1614 "isBlockDevice" | "isCharacterDevice" | "isFIFO" | "isSocket" => Ok(Value::Bool(false)),
1615 _ => Err(crate::host::type_error(&format!(
1616 "{what}.{method} is not a function"
1617 ))),
1618 }
1619}
1620
1621pub const DIR_METHODS: &[&str] = &["read", "readSync", "close", "closeSync"];
1622
1623pub fn dir_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
1625 match method {
1626 "readSync" => Ok(dir_next(recv)),
1627 "read" => {
1628 let v = dir_next(recv);
1629 if let Some(cb) = args.first().filter(|c| is_fn(c)).cloned() {
1630 with_host(|h| {
1631 let n = h.null();
1632 h.queue_micro(cb, vec![n, v]);
1633 });
1634 Ok(Value::Undef)
1635 } else {
1636 Ok(settled_ok(v))
1637 }
1638 }
1639 "closeSync" => Ok(Value::Undef),
1640 "close" => {
1641 if let Some(cb) = args.first().filter(|c| is_fn(c)).cloned() {
1642 with_host(|h| {
1643 let n = h.null();
1644 h.queue_micro(cb, vec![n]);
1645 });
1646 Ok(Value::Undef)
1647 } else {
1648 Ok(settled_ok(Value::Undef))
1649 }
1650 }
1651 _ => Err(crate::host::type_error(&format!(
1652 "dir.{method} is not a function"
1653 ))),
1654 }
1655}
1656
1657fn dir_next(recv: &Value) -> Value {
1659 with_host(|h| {
1660 let (entries, pos) = match h.get(recv) {
1661 Some(JsObj::Object(p)) => (
1662 p.get("@@entries").cloned(),
1663 p.get("@@pos").map(|v| h.to_number(v) as usize).unwrap_or(0),
1664 ),
1665 _ => (None, 0),
1666 };
1667 let item = match entries.as_ref().and_then(|e| h.get(e)) {
1668 Some(JsObj::Array(items)) => items.get(pos).cloned(),
1669 _ => None,
1670 };
1671 if item.is_some() {
1672 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1673 p.insert("@@pos".into(), Value::Float((pos + 1) as f64));
1674 }
1675 }
1676 item.unwrap_or_else(|| h.null())
1677 })
1678}
1679
1680fn build_dirent(
1681 h: &mut crate::host::JsHost,
1682 name: String,
1683 parent: &str,
1684 ft: std::fs::FileType,
1685) -> Value {
1686 let mut m = IndexMap::new();
1687 m.insert("@@native".into(), h.new_str("Dirent"));
1688 m.insert("name".into(), h.new_str(name));
1689 let pp = h.new_str(parent.to_string());
1690 m.insert("parentPath".into(), pp.clone());
1691 m.insert("path".into(), pp);
1692 m.insert("@@isFile".into(), Value::Bool(ft.is_file()));
1693 m.insert("@@isDir".into(), Value::Bool(ft.is_dir()));
1694 m.insert("@@isSymlink".into(), Value::Bool(ft.is_symlink()));
1695 h.new_object(m)
1696}
1697
1698fn glob_impl(args: &[Value]) -> Result<Value, String> {
1701 let pattern = arg_str(args, 0);
1702 let absolute = pattern.starts_with('/');
1703 let base = if absolute {
1704 PathBuf::from("/")
1705 } else {
1706 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
1707 };
1708 let segs: Vec<String> = pattern
1709 .split('/')
1710 .filter(|s| !s.is_empty())
1711 .map(String::from)
1712 .collect();
1713 let mut out: Vec<String> = Vec::new();
1714 let prefix = if absolute {
1715 "/".to_string()
1716 } else {
1717 String::new()
1718 };
1719 glob_walk(&base, &segs, 0, &prefix, &mut out);
1720 out.sort();
1721 out.dedup();
1722 Ok(with_host(|h| {
1723 let items: Vec<Value> = out.into_iter().map(|s| h.new_str(s)).collect();
1724 h.new_array(items)
1725 }))
1726}
1727
1728fn glob_walk(dir: &Path, segs: &[String], idx: usize, prefix: &str, out: &mut Vec<String>) {
1729 if idx >= segs.len() {
1730 if !prefix.is_empty() && prefix != "/" {
1731 out.push(prefix.trim_end_matches('/').to_string());
1732 }
1733 return;
1734 }
1735 let seg = &segs[idx];
1736 if seg == "**" {
1737 glob_walk(dir, segs, idx + 1, prefix, out);
1738 if let Ok(rd) = std::fs::read_dir(dir) {
1739 for e in rd.flatten() {
1740 if e.path().is_dir() {
1741 let name = e.file_name().to_string_lossy().into_owned();
1742 let np = join_glob(prefix, &name);
1743 glob_walk(&e.path(), segs, idx, &np, out);
1744 }
1745 }
1746 }
1747 return;
1748 }
1749 let last = idx + 1 == segs.len();
1750 if let Ok(rd) = std::fs::read_dir(dir) {
1751 for e in rd.flatten() {
1752 let name = e.file_name().to_string_lossy().into_owned();
1753 if name.starts_with('.') && !seg.starts_with('.') {
1754 continue;
1755 }
1756 if wildcard_match(seg, &name) {
1757 let np = join_glob(prefix, &name);
1758 if last {
1759 out.push(np);
1760 } else if e.path().is_dir() {
1761 glob_walk(&e.path(), segs, idx + 1, &np, out);
1762 }
1763 }
1764 }
1765 }
1766}
1767
1768fn join_glob(prefix: &str, name: &str) -> String {
1769 if prefix.is_empty() {
1770 name.to_string()
1771 } else if prefix.ends_with('/') {
1772 format!("{prefix}{name}")
1773 } else {
1774 format!("{prefix}/{name}")
1775 }
1776}
1777
1778fn wildcard_match(pat: &str, name: &str) -> bool {
1780 let p: Vec<char> = pat.chars().collect();
1781 let n: Vec<char> = name.chars().collect();
1782 let (mut pi, mut ni) = (0usize, 0usize);
1783 let (mut star, mut mark) = (None, 0usize);
1784 while ni < n.len() {
1785 if pi < p.len() && (p[pi] == '?' || p[pi] == n[ni]) {
1786 pi += 1;
1787 ni += 1;
1788 } else if pi < p.len() && p[pi] == '*' {
1789 star = Some(pi);
1790 mark = ni;
1791 pi += 1;
1792 } else if let Some(s) = star {
1793 pi = s + 1;
1794 mark += 1;
1795 ni = mark;
1796 } else {
1797 return false;
1798 }
1799 }
1800 while pi < p.len() && p[pi] == '*' {
1801 pi += 1;
1802 }
1803 pi == p.len()
1804}
1805
1806fn settled_ok(v: Value) -> Value {
1809 let p = with_host(|h| h.new_promise());
1810 let id = with_host(|h| h.promise_id(&p).unwrap_or(0));
1811 crate::host::resolve_promise_val(id, v);
1812 p
1813}
1814
1815fn get_prop(recv: &Value, key: &str) -> Option<Value> {
1816 with_host(|h| match h.get(recv) {
1817 Some(JsObj::Object(p)) => p.get(key).cloned(),
1818 _ => None,
1819 })
1820}
1821
1822fn set_prop(recv: &Value, key: &str, val: Value) {
1823 with_host(|h| {
1824 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1825 p.insert(key.to_string(), val);
1826 }
1827 });
1828}
1829
1830fn value_bytes(v: &Value) -> Vec<u8> {
1832 match super::buffer::view_bytes(v) {
1833 Some(b) => b,
1834 None => with_host(|h| h.str_of(v)).into_bytes(),
1835 }
1836}
1837
1838fn buf_bytes(v: &Value) -> Vec<u8> {
1839 with_host(|h| match h.get(v) {
1840 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
1841 Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
1842 _ => Vec::new(),
1843 },
1844 _ => Vec::new(),
1845 })
1846}
1847
1848fn buf_len(v: &Value) -> usize {
1849 with_host(|h| match h.get(v) {
1850 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
1851 Some(JsObj::Array(items)) => items.len(),
1852 _ => 0,
1853 },
1854 _ => 0,
1855 })
1856}
1857
1858fn write_into_buffer(buf: &Value, offset: usize, data: &[u8]) -> usize {
1861 let Some(arr) = get_prop(buf, "@@bytes") else {
1862 return 0;
1863 };
1864 with_host(|h| {
1865 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
1866 let mut n = 0;
1867 for (i, b) in data.iter().enumerate() {
1868 let idx = offset + i;
1869 if idx >= items.len() {
1870 break;
1871 }
1872 items[idx] = Value::Float(*b as f64);
1873 n += 1;
1874 }
1875 n
1876 } else {
1877 0
1878 }
1879 })
1880}
1881
1882fn array_items(v: Option<&Value>) -> Vec<Value> {
1883 match v {
1884 Some(v) => with_host(|h| match h.get(v) {
1885 Some(JsObj::Array(items)) => items.clone(),
1886 _ => Vec::new(),
1887 }),
1888 None => Vec::new(),
1889 }
1890}
1891
1892fn opt_flag(args: &[Value], key: &str) -> bool {
1893 with_host(|h| {
1894 args.iter().any(|v| {
1895 matches!(h.get(v), Some(JsObj::Object(p)) if matches!(p.get(key), Some(Value::Bool(true))))
1896 })
1897 })
1898}
1899
1900fn encoding_flag(args: &[Value], key: &str) -> Option<String> {
1901 with_host(|h| {
1902 for v in args {
1903 if let Some(JsObj::Object(p)) = h.get(v) {
1904 if let Some(val) = p.get(key) {
1905 return Some(h.str_of(val));
1906 }
1907 }
1908 }
1909 None
1910 })
1911}
1912
1913fn interval_opt(args: &[Value]) -> Option<f64> {
1914 with_host(|h| {
1915 for v in args {
1916 if let Some(JsObj::Object(p)) = h.get(v) {
1917 if let Some(val) = p.get("interval") {
1918 return Some(h.to_number(val));
1919 }
1920 }
1921 }
1922 None
1923 })
1924}
1925
1926fn num_or(args: &[Value], i: usize, default: f64) -> f64 {
1927 match args.get(i) {
1928 Some(v) if !matches!(v, Value::Undef) => {
1929 let n = with_host(|h| h.to_number(v));
1930 if n.is_nan() {
1931 default
1932 } else {
1933 n
1934 }
1935 }
1936 _ => default,
1937 }
1938}
1939
1940fn position_arg(args: &[Value], i: usize) -> Option<u64> {
1942 match args.get(i) {
1943 Some(Value::Undef) | None => None,
1944 Some(v) if with_host(|h| h.is_null(v)) => None,
1945 Some(v) => {
1946 let n = with_host(|h| h.to_number(v));
1947 if n.is_nan() || n < 0.0 {
1948 None
1949 } else {
1950 Some(n as u64)
1951 }
1952 }
1953 }
1954}
1955
1956fn time_secs(args: &[Value], i: usize) -> f64 {
1959 match args.get(i) {
1960 Some(v) if native_tag(v).as_deref() == Some("Date") => arg_num(args, i) / 1000.0,
1961 _ => arg_num(args, i),
1962 }
1963}
1964
1965fn to_timeval(secs: f64) -> libc::timeval {
1966 let s = secs.floor();
1967 let us = ((secs - s) * 1_000_000.0).round();
1968 libc::timeval {
1969 tv_sec: s as libc::time_t,
1970 tv_usec: us as libc::suseconds_t,
1971 }
1972}
1973
1974fn cpath(path: &str, op: &str) -> Result<CString, String> {
1975 CString::new(path).map_err(|_| format!("Error: EINVAL: invalid argument, {op} '{path}'"))
1976}
1977
1978fn ok_or_errno(rc: libc::c_int, op: &str, path: &str) -> Result<Value, String> {
1979 if rc == 0 {
1980 Ok(Value::Undef)
1981 } else {
1982 Err(err_str(op, path, &std::io::Error::last_os_error()))
1983 }
1984}
1985
1986fn encoding_arg(args: &[Value], i: usize) -> Option<String> {
1995 match args.get(i) {
1996 Some(Value::Undef) | None => None,
1997 Some(v) => {
1998 if with_host(|h| matches!(h.get(v), Some(JsObj::Object(_)))) {
1999 let e = crate::builtins::get_property(v, "encoding").ok()?;
2000 if matches!(e, Value::Undef) || with_host(|h| h.is_null(&e)) {
2001 return None;
2002 }
2003 return Some(with_host(|h| h.str_of(&e)));
2004 }
2005 let s = with_host(|h| h.str_of(v));
2006 if s == "undefined" || s == "null" {
2007 None
2008 } else {
2009 Some(s)
2010 }
2011 }
2012 }
2013}
2014
2015fn syscall_name(op: &str) -> &str {
2020 match op {
2021 "readFileSync" | "readFile" | "writeFile" | "writeFileSync" | "appendFile"
2022 | "appendFileSync" | "createReadStream" | "createWriteStream" => "open",
2023 "readdir" | "readdirSync" | "opendir" => "scandir",
2024 "copyFile" | "copyFileSync" => "copyfile",
2030 "realpath" | "realpathSync" => "lstat",
2031 other => other.strip_suffix("Sync").unwrap_or(other),
2032 }
2033}
2034
2035fn err_str2(op: &str, from: &str, to: &str, e: &std::io::Error) -> String {
2039 format!("{} -> '{to}'", err_str(op, from, e))
2040}
2041
2042pub(crate) fn libuv_code(e: &std::io::Error) -> &'static str {
2052 use std::io::ErrorKind::*;
2053 match e.kind() {
2054 NotFound => "ENOENT",
2055 PermissionDenied => "EACCES",
2056 AlreadyExists => "EEXIST",
2057 NotADirectory => "ENOTDIR",
2058 IsADirectory => "EISDIR",
2059 DirectoryNotEmpty => "ENOTEMPTY",
2060 InvalidInput => "EINVAL",
2061 BrokenPipe => "EPIPE",
2062 _ => "EIO",
2063 }
2064}
2065
2066pub(crate) fn libuv_message(e: &std::io::Error) -> String {
2067 let code = libuv_code(e);
2068 format!("{code}: {}", libuv_reason(code))
2069}
2070
2071fn libuv_reason(code: &str) -> &'static str {
2073 match code {
2074 "ENOENT" => "no such file or directory",
2075 "EACCES" => "permission denied",
2076 "EEXIST" => "file already exists",
2077 "ENOTDIR" => "not a directory",
2078 "EISDIR" => "illegal operation on a directory",
2079 "ENOTEMPTY" => "directory not empty",
2080 "EINVAL" => "invalid argument",
2081 "EPIPE" => "broken pipe",
2082 _ => "i/o error",
2083 }
2084}
2085
2086fn err_str(op: &str, path: &str, e: &std::io::Error) -> String {
2087 let code = libuv_code(e);
2088 let reason = libuv_reason(code);
2092 let op = syscall_name(op);
2093 if path.is_empty() {
2094 format!("Error: {code}: {reason}, {op}")
2095 } else {
2096 format!("Error: {code}: {reason}, {op} '{path}'")
2097 }
2098}