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 close_fd(fd: i32) -> bool {
156 FD_TABLE.with(|t| t.borrow_mut().remove(&fd).is_some())
157}
158
159pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
162 Some(match method {
163 "readFileSync" => read_file_sync(args),
165 "writeFileSync" => write_file_impl(args),
166 "appendFileSync" => append_file_impl(args),
167 "existsSync" => Ok(Value::Bool(Path::new(&arg_str(args, 0)).exists())),
168 "readdirSync" => readdir_impl(args),
169 "mkdirSync" => mkdir_impl(args),
170 "rmdirSync" | "unlinkSync" | "rmSync" => rm_impl(method, args),
171 "statSync" => stat_impl("statSync", args, true),
172 "lstatSync" => stat_impl("lstatSync", args, false),
173 "statfsSync" => statfs_impl(args),
174 "accessSync" => access_impl(args),
175 "chmodSync" => chmod_impl(args),
176 "chownSync" => chown_impl(args, true),
177 "lchownSync" => chown_impl(args, false),
178 "copyFileSync" => copy_file_impl(args),
179 "cpSync" => cp_impl(args),
180 "linkSync" => link_impl(args),
181 "symlinkSync" => symlink_impl(args),
182 "readlinkSync" => readlink_impl(args),
183 "realpathSync" => realpath_impl(args),
184 "renameSync" => rename_impl(args),
185 "truncateSync" => truncate_impl(args),
186 "utimesSync" => utimes_impl(args, true),
187 "lutimesSync" => utimes_impl(args, false),
188 "mkdtempSync" => mkdtemp_impl(args),
189 "opendirSync" => opendir_impl(args),
190 "globSync" => glob_impl(args),
191 "openSync" => open_impl(args),
193 "closeSync" => close_impl(args),
194 "readSync" => read_impl(args).map(|n| Value::Float(n as f64)),
195 "writeSync" => write_impl(args).map(|n| Value::Float(n as f64)),
196 "readvSync" => readv_impl(args).map(|n| Value::Float(n as f64)),
197 "writevSync" => writev_impl(args).map(|n| Value::Float(n as f64)),
198 "fstatSync" => fstat_impl(args),
199 "fchmodSync" => fchmod_impl(args),
200 "fchownSync" => fchown_impl(args),
201 "ftruncateSync" => ftruncate_impl(args),
202 "futimesSync" => futimes_impl(args),
203 "fsyncSync" => fsync_impl(args, false),
204 "fdatasyncSync" => fsync_impl(args, true),
205 "readFile" => return Some(read_file_async(args)),
207 "writeFile" => async_cb(args, write_file_impl(args)),
208 "appendFile" => async_cb(args, append_file_impl(args)),
209 "readdir" => async_cb(args, readdir_impl(args)),
210 "mkdir" => async_cb(args, mkdir_impl(args)),
211 "rmdir" => async_cb(args, rm_impl("rmdir", args)),
212 "rm" => async_cb(args, rm_impl("rm", args)),
213 "unlink" => async_cb(args, rm_impl("unlink", args)),
214 "stat" => async_cb(args, stat_impl("stat", args, true)),
215 "lstat" => async_cb(args, stat_impl("lstat", args, false)),
216 "statfs" => async_cb(args, statfs_impl(args)),
217 "access" => async_cb(args, access_impl(args)),
218 "chmod" => async_cb(args, chmod_impl(args)),
219 "chown" => async_cb(args, chown_impl(args, true)),
220 "lchown" => async_cb(args, chown_impl(args, false)),
221 "copyFile" => async_cb(args, copy_file_impl(args)),
222 "cp" => async_cb(args, cp_impl(args)),
223 "link" => async_cb(args, link_impl(args)),
224 "symlink" => async_cb(args, symlink_impl(args)),
225 "readlink" => async_cb(args, readlink_impl(args)),
226 "realpath" => async_cb(args, realpath_impl(args)),
227 "rename" => async_cb(args, rename_impl(args)),
228 "truncate" => async_cb(args, truncate_impl(args)),
229 "utimes" => async_cb(args, utimes_impl(args, true)),
230 "lutimes" => async_cb(args, utimes_impl(args, false)),
231 "mkdtemp" => async_cb(args, mkdtemp_impl(args)),
232 "opendir" => async_cb(args, opendir_impl(args)),
233 "glob" => async_cb(args, glob_impl(args)),
234 "exists" => exists_async(args),
235 "open" => async_cb(args, open_impl(args)),
237 "close" => async_cb(args, close_impl(args)),
238 "read" => return Some(read_write_async(args, read_impl(args))),
239 "write" => return Some(read_write_async(args, write_impl(args))),
240 "readv" => async_cb(args, readv_impl(args).map(|n| Value::Float(n as f64))),
241 "writev" => async_cb(args, writev_impl(args).map(|n| Value::Float(n as f64))),
242 "fstat" => async_cb(args, fstat_impl(args)),
243 "fchmod" => async_cb(args, fchmod_impl(args)),
244 "fchown" => async_cb(args, fchown_impl(args)),
245 "ftruncate" => async_cb(args, ftruncate_impl(args)),
246 "futimes" => async_cb(args, futimes_impl(args)),
247 "fsync" => async_cb(args, fsync_impl(args, false)),
248 "fdatasync" => async_cb(args, fsync_impl(args, true)),
249 "watchFile" => watch_file(args),
251 "unwatchFile" => unwatch_file(args),
252 "createReadStream" => create_read_stream(args),
253 "createWriteStream" => create_write_stream(args),
254 _ => return None,
255 })
256}
257
258fn async_cb(args: &[Value], result: Result<Value, String>) -> Result<Value, String> {
264 let Some(cb) = args.last().cloned().filter(is_fn) else {
265 return Ok(Value::Undef);
266 };
267 match result {
268 Ok(v) => with_host(|h| {
269 let n = h.null();
270 h.queue_micro(cb, vec![n, v]);
271 }),
272 Err(e) => with_host(|h| {
273 let ev = h.new_str(e);
274 h.queue_micro(cb, vec![ev]);
275 }),
276 }
277 Ok(Value::Undef)
278}
279
280fn read_write_async(args: &[Value], result: Result<usize, String>) -> Result<Value, String> {
282 let Some(cb) = args.last().cloned().filter(is_fn) else {
283 return Ok(Value::Undef);
284 };
285 let buffer = args.get(1).cloned().unwrap_or(Value::Undef);
286 match result {
287 Ok(n) => with_host(|h| {
288 let nul = h.null();
289 h.queue_micro(cb, vec![nul, Value::Float(n as f64), buffer]);
290 }),
291 Err(e) => with_host(|h| {
292 let ev = h.new_str(e);
293 h.queue_micro(cb, vec![ev]);
294 }),
295 }
296 Ok(Value::Undef)
297}
298
299fn is_fn(v: &Value) -> bool {
300 with_host(|h| crate::host::is_callable(h, v))
301}
302
303fn read_file_sync(args: &[Value]) -> Result<Value, String> {
306 let path = arg_str(args, 0);
307 let enc = encoding_arg(args, 1);
308 match std::fs::read(&path) {
309 Ok(bytes) => Ok(match enc {
310 Some(_) => with_host(|h| h.new_str(String::from_utf8_lossy(&bytes).into_owned())),
311 None => super::buffer::from_bytes(&bytes),
312 }),
313 Err(e) => Err(err_str("readFileSync", &path, &e)),
314 }
315}
316
317fn write_file_impl(args: &[Value]) -> Result<Value, String> {
318 let path = arg_str(args, 0);
319 let data = value_bytes(args.get(1).unwrap_or(&Value::Undef));
320 match std::fs::write(&path, data) {
321 Ok(_) => Ok(Value::Undef),
322 Err(e) => Err(err_str("writeFile", &path, &e)),
323 }
324}
325
326fn append_file_impl(args: &[Value]) -> Result<Value, String> {
327 let path = arg_str(args, 0);
328 let data = value_bytes(args.get(1).unwrap_or(&Value::Undef));
329 let r = std::fs::OpenOptions::new()
330 .create(true)
331 .append(true)
332 .open(&path)
333 .and_then(|mut f| f.write_all(&data));
334 match r {
335 Ok(_) => Ok(Value::Undef),
336 Err(e) => Err(err_str("appendFile", &path, &e)),
337 }
338}
339
340fn read_file_async(args: &[Value]) -> Result<Value, String> {
341 let path = arg_str(args, 0);
342 let Some(cb) = args.last().cloned().filter(is_fn) else {
343 return Ok(Value::Undef);
344 };
345 let enc = if args.len() >= 3 {
346 encoding_arg(args, 1)
347 } else {
348 None
349 };
350 let (err, data) = match std::fs::read(&path) {
351 Ok(bytes) => (
352 with_host(|h| h.null()),
353 match enc {
354 Some(_) => with_host(|h| h.new_str(String::from_utf8_lossy(&bytes).into_owned())),
355 None => super::buffer::from_bytes(&bytes),
356 },
357 ),
358 Err(e) => (
359 with_host(|h| h.new_str(err_str("readFile", &path, &e))),
360 Value::Undef,
361 ),
362 };
363 with_host(|h| h.queue_micro(cb, vec![err, data]));
364 Ok(Value::Undef)
365}
366
367fn exists_async(args: &[Value]) -> Result<Value, String> {
368 let path = arg_str(args, 0);
369 let Some(cb) = args.last().cloned().filter(is_fn) else {
370 return Ok(Value::Undef);
371 };
372 let ex = Path::new(&path).exists();
373 with_host(|h| h.queue_micro(cb, vec![Value::Bool(ex)]));
374 Ok(Value::Undef)
375}
376
377fn mkdir_impl(args: &[Value]) -> Result<Value, String> {
380 let path = arg_str(args, 0);
381 let recursive = opt_flag(args, "recursive");
382 let r = if recursive {
383 std::fs::create_dir_all(&path)
384 } else {
385 std::fs::create_dir(&path)
386 };
387 match r {
388 Ok(_) => Ok(Value::Undef),
389 Err(e) => Err(err_str("mkdir", &path, &e)),
390 }
391}
392
393fn rm_impl(op: &str, args: &[Value]) -> Result<Value, String> {
394 let path = arg_str(args, 0);
395 let p = Path::new(&path);
396 let force = opt_flag(args, "force");
397 let r = if p.is_dir() {
398 if opt_flag(args, "recursive") {
399 std::fs::remove_dir_all(p)
400 } else {
401 std::fs::remove_dir(p)
402 }
403 } else {
404 std::fs::remove_file(p)
405 };
406 match r {
407 Ok(_) => Ok(Value::Undef),
408 Err(e) if force && e.kind() == std::io::ErrorKind::NotFound => Ok(Value::Undef),
409 Err(e) => Err(err_str(op, &path, &e)),
410 }
411}
412
413fn readdir_impl(args: &[Value]) -> Result<Value, String> {
414 let path = arg_str(args, 0);
415 let file_types = opt_flag(args, "withFileTypes");
416 let recursive = opt_flag(args, "recursive");
417 let mut names: Vec<(String, String, std::fs::FileType)> = Vec::new();
418 collect_dir(Path::new(&path), &path, "", recursive, &mut names)
419 .map_err(|e| err_str("readdir", &path, &e))?;
420 names.sort_by(|a, b| a.0.cmp(&b.0));
421 Ok(with_host(|h| {
422 let items: Vec<Value> = names
423 .into_iter()
424 .map(|(rel, parent, ft)| {
425 if file_types {
426 let base = rel.rsplit('/').next().unwrap_or(&rel).to_string();
427 build_dirent(h, base, &parent, ft)
428 } else {
429 h.new_str(rel)
430 }
431 })
432 .collect();
433 h.new_array(items)
434 }))
435}
436
437fn collect_dir(
441 dir: &Path,
442 parent: &str,
443 rel_prefix: &str,
444 recursive: bool,
445 out: &mut Vec<(String, String, std::fs::FileType)>,
446) -> std::io::Result<()> {
447 for e in std::fs::read_dir(dir)? {
448 let e = e?;
449 let name = e.file_name().to_string_lossy().into_owned();
450 let rel = if rel_prefix.is_empty() {
451 name.clone()
452 } else {
453 format!("{rel_prefix}/{name}")
454 };
455 let ft = e.file_type()?;
456 out.push((rel.clone(), parent.to_string(), ft));
457 if recursive && ft.is_dir() {
458 let sub = e.path();
459 let sub_parent = sub.to_string_lossy().into_owned();
460 collect_dir(&sub, &sub_parent, &rel, recursive, out)?;
461 }
462 }
463 Ok(())
464}
465
466fn opendir_impl(args: &[Value]) -> Result<Value, String> {
467 let path = arg_str(args, 0);
468 let rd = std::fs::read_dir(&path).map_err(|e| err_str("opendir", &path, &e))?;
469 let mut entries: Vec<(String, std::fs::FileType)> = rd
470 .filter_map(|e| e.ok())
471 .filter_map(|e| {
472 e.file_type()
473 .ok()
474 .map(|ft| (e.file_name().to_string_lossy().into_owned(), ft))
475 })
476 .collect();
477 entries.sort_by(|a, b| a.0.cmp(&b.0));
478 Ok(with_host(|h| {
479 let dirents: Vec<Value> = entries
480 .into_iter()
481 .map(|(name, ft)| build_dirent(h, name, &path, ft))
482 .collect();
483 let arr = h.new_array(dirents);
484 let mut m = IndexMap::new();
485 m.insert("@@native".into(), h.new_str("Dir"));
486 m.insert("path".into(), h.new_str(path.clone()));
487 m.insert("@@entries".into(), arr);
488 m.insert("@@pos".into(), Value::Float(0.0));
489 h.new_object(m)
490 }))
491}
492
493fn access_impl(args: &[Value]) -> Result<Value, String> {
496 let path = arg_str(args, 0);
497 match std::fs::metadata(&path) {
498 Ok(_) => Ok(Value::Undef),
499 Err(e) => Err(err_str("access", &path, &e)),
500 }
501}
502
503fn chmod_impl(args: &[Value]) -> Result<Value, String> {
504 let path = arg_str(args, 0);
505 let mode = arg_num(args, 1) as u32;
506 match std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)) {
507 Ok(_) => Ok(Value::Undef),
508 Err(e) => Err(err_str("chmod", &path, &e)),
509 }
510}
511
512fn chown_impl(args: &[Value], follow: bool) -> Result<Value, String> {
513 let path = arg_str(args, 0);
514 let uid = arg_num(args, 1) as libc::uid_t;
515 let gid = arg_num(args, 2) as libc::gid_t;
516 let c = cpath(&path, "chown")?;
517 let rc = unsafe {
518 if follow {
519 libc::chown(c.as_ptr(), uid, gid)
520 } else {
521 libc::lchown(c.as_ptr(), uid, gid)
522 }
523 };
524 ok_or_errno(rc, "chown", &path)
525}
526
527fn utimes_impl(args: &[Value], follow: bool) -> Result<Value, String> {
528 let path = arg_str(args, 0);
529 let times = [
530 to_timeval(time_secs(args, 1)),
531 to_timeval(time_secs(args, 2)),
532 ];
533 let c = cpath(&path, "utimes")?;
534 let rc = unsafe {
535 if follow {
536 libc::utimes(c.as_ptr(), times.as_ptr())
537 } else {
538 libc::lutimes(c.as_ptr(), times.as_ptr())
539 }
540 };
541 ok_or_errno(rc, "utimes", &path)
542}
543
544const COPYFILE_EXCL: u32 = 1;
547
548fn copy_file_impl(args: &[Value]) -> Result<Value, String> {
549 let src = arg_str(args, 0);
550 let dest = arg_str(args, 1);
551 let mode = arg_num(args, 2);
552 if !mode.is_nan() && (mode as u32) & COPYFILE_EXCL != 0 && Path::new(&dest).exists() {
553 return Err(format!(
554 "Error: EEXIST: file already exists, copyfile '{src}' -> '{dest}'"
555 ));
556 }
557 match std::fs::copy(&src, &dest) {
558 Ok(_) => Ok(Value::Undef),
559 Err(e) => Err(err_str("copyFile", &src, &e)),
560 }
561}
562
563fn cp_impl(args: &[Value]) -> Result<Value, String> {
564 let src = arg_str(args, 0);
565 let dest = arg_str(args, 1);
566 let recursive = opt_flag(args, "recursive");
567 let r = if recursive {
568 cp_recursive(Path::new(&src), Path::new(&dest))
569 } else {
570 std::fs::copy(&src, &dest).map(|_| ())
571 };
572 match r {
573 Ok(_) => Ok(Value::Undef),
574 Err(e) => Err(err_str("cp", &src, &e)),
575 }
576}
577
578fn cp_recursive(src: &Path, dest: &Path) -> std::io::Result<()> {
579 if src.is_dir() {
580 std::fs::create_dir_all(dest)?;
581 for e in std::fs::read_dir(src)? {
582 let e = e?;
583 cp_recursive(&e.path(), &dest.join(e.file_name()))?;
584 }
585 Ok(())
586 } else {
587 if let Some(parent) = dest.parent() {
588 std::fs::create_dir_all(parent).ok();
589 }
590 std::fs::copy(src, dest).map(|_| ())
591 }
592}
593
594fn link_impl(args: &[Value]) -> Result<Value, String> {
595 let existing = arg_str(args, 0);
596 let new = arg_str(args, 1);
597 match std::fs::hard_link(&existing, &new) {
598 Ok(_) => Ok(Value::Undef),
599 Err(e) => Err(err_str("link", &existing, &e)),
600 }
601}
602
603fn symlink_impl(args: &[Value]) -> Result<Value, String> {
604 let target = arg_str(args, 0);
605 let path = arg_str(args, 1);
606 match std::os::unix::fs::symlink(&target, &path) {
607 Ok(_) => Ok(Value::Undef),
608 Err(e) => Err(err_str("symlink", &path, &e)),
609 }
610}
611
612fn readlink_impl(args: &[Value]) -> Result<Value, String> {
613 let path = arg_str(args, 0);
614 match std::fs::read_link(&path) {
615 Ok(p) => Ok(with_host(|h| h.new_str(p.to_string_lossy().into_owned()))),
616 Err(e) => Err(err_str("readlink", &path, &e)),
617 }
618}
619
620fn realpath_impl(args: &[Value]) -> Result<Value, String> {
621 let path = arg_str(args, 0);
622 match std::fs::canonicalize(&path) {
623 Ok(p) => Ok(with_host(|h| h.new_str(p.to_string_lossy().into_owned()))),
624 Err(e) => Err(err_str("realpath", &path, &e)),
625 }
626}
627
628fn rename_impl(args: &[Value]) -> Result<Value, String> {
629 let from = arg_str(args, 0);
630 let to = arg_str(args, 1);
631 match std::fs::rename(&from, &to) {
632 Ok(_) => Ok(Value::Undef),
633 Err(e) => Err(err_str("rename", &from, &e)),
634 }
635}
636
637fn truncate_impl(args: &[Value]) -> Result<Value, String> {
638 let path = arg_str(args, 0);
639 let len = arg_num(args, 1);
640 let len = if len.is_nan() { 0 } else { len as u64 };
641 let r = std::fs::OpenOptions::new()
642 .write(true)
643 .open(&path)
644 .and_then(|f| f.set_len(len));
645 match r {
646 Ok(_) => Ok(Value::Undef),
647 Err(e) => Err(err_str("truncate", &path, &e)),
648 }
649}
650
651fn mkdtemp_impl(args: &[Value]) -> Result<Value, String> {
652 let prefix = arg_str(args, 0);
653 for _ in 0..64 {
654 let candidate = format!("{prefix}{}", random_suffix());
655 match std::fs::create_dir(&candidate) {
656 Ok(_) => return Ok(with_host(|h| h.new_str(candidate))),
657 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
658 Err(e) => return Err(err_str("mkdtemp", &prefix, &e)),
659 }
660 }
661 Err(format!(
662 "Error: EEXIST: file already exists, mkdtemp '{prefix}'"
663 ))
664}
665
666fn random_suffix() -> String {
668 const CHARS: &[u8; 62] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
669 let mut raw = [0u8; 6];
670 if getrandom::getrandom(&mut raw).is_err() {
671 let nanos = std::time::SystemTime::now()
673 .duration_since(UNIX_EPOCH)
674 .map(|d| d.subsec_nanos())
675 .unwrap_or(0);
676 raw = nanos
677 .to_le_bytes()
678 .iter()
679 .cycle()
680 .take(6)
681 .copied()
682 .collect::<Vec<_>>()
683 .try_into()
684 .unwrap();
685 }
686 raw.iter()
687 .map(|b| CHARS[(*b as usize) % 62] as char)
688 .collect()
689}
690
691fn open_impl(args: &[Value]) -> Result<Value, String> {
694 let path = arg_str(args, 0);
695 let flags = match args.get(1) {
696 Some(v) if !matches!(v, Value::Undef) && !is_fn(v) => arg_str(args, 1),
697 _ => "r".to_string(),
698 };
699 match open_options(&flags).open(&path) {
700 Ok(f) => Ok(Value::Float(register_fd(f) as f64)),
701 Err(e) => Err(err_str("open", &path, &e)),
702 }
703}
704
705fn open_options(flags: &str) -> std::fs::OpenOptions {
706 let mut o = std::fs::OpenOptions::new();
707 match flags {
708 "r" | "rs" | "sr" => {
709 o.read(true);
710 }
711 "r+" | "rs+" | "sr+" => {
712 o.read(true).write(true);
713 }
714 "w" => {
715 o.write(true).create(true).truncate(true);
716 }
717 "wx" | "xw" => {
718 o.write(true).create_new(true);
719 }
720 "w+" => {
721 o.read(true).write(true).create(true).truncate(true);
722 }
723 "wx+" | "xw+" => {
724 o.read(true).write(true).create_new(true);
725 }
726 "a" => {
727 o.append(true).create(true);
728 }
729 "ax" | "xa" => {
730 o.append(true).create_new(true);
731 }
732 "a+" => {
733 o.read(true).append(true).create(true);
734 }
735 "ax+" | "xa+" => {
736 o.read(true).append(true).create_new(true);
737 }
738 _ => {
739 o.read(true);
740 }
741 }
742 o
743}
744
745fn close_impl(args: &[Value]) -> Result<Value, String> {
746 let fd = arg_num(args, 0) as i32;
747 if close_fd(fd) {
748 Ok(Value::Undef)
749 } else {
750 Err("Error: EBADF: bad file descriptor, close".to_string())
751 }
752}
753
754fn read_impl(args: &[Value]) -> Result<usize, String> {
755 let fd = arg_num(args, 0) as i32;
756 let buffer = args.get(1).cloned().unwrap_or(Value::Undef);
757 let cap = buf_len(&buffer);
758 let offset = num_or(args, 2, 0.0) as usize;
759 let length = num_or(args, 3, (cap.saturating_sub(offset)) as f64) as usize;
760 let position = position_arg(args, 4);
761 let n = with_file(fd, |file| {
762 let mut fr: &File = file;
763 if let Some(pos) = position {
764 fr.seek(SeekFrom::Start(pos)).ok();
765 }
766 let mut buf = vec![0u8; length];
767 fr.read(&mut buf).map(|n| {
768 buf.truncate(n);
769 buf
770 })
771 });
772 match n {
773 Some(Ok(data)) => Ok(write_into_buffer(&buffer, offset, &data)),
774 Some(Err(e)) => Err(err_str("read", "", &e)),
775 None => Err("Error: EBADF: bad file descriptor, read".to_string()),
776 }
777}
778
779fn write_impl(args: &[Value]) -> Result<usize, String> {
780 let fd = arg_num(args, 0) as i32;
781 let src = args.get(1).cloned().unwrap_or(Value::Undef);
782 let is_buffer = native_tag(&src).as_deref() == Some("Buffer");
783 let (data, position) = if is_buffer {
786 let all = buf_bytes(&src);
787 let offset = num_or(args, 2, 0.0) as usize;
788 let length = num_or(args, 3, (all.len().saturating_sub(offset)) as f64) as usize;
789 let end = (offset + length).min(all.len());
790 (
791 all[offset.min(all.len())..end].to_vec(),
792 position_arg(args, 4),
793 )
794 } else {
795 (
796 with_host(|h| h.str_of(&src)).into_bytes(),
797 position_arg(args, 2),
798 )
799 };
800 let r = with_file(fd, |file| {
801 let mut fr: &File = file;
802 if let Some(pos) = position {
803 fr.seek(SeekFrom::Start(pos)).ok();
804 }
805 fr.write(&data)
806 });
807 match r {
808 Some(Ok(n)) => Ok(n),
809 Some(Err(e)) => Err(err_str("write", "", &e)),
810 None => Err("Error: EBADF: bad file descriptor, write".to_string()),
811 }
812}
813
814fn readv_impl(args: &[Value]) -> Result<usize, String> {
815 let fd = arg_num(args, 0) as i32;
816 let buffers = array_items(args.get(1));
817 let position = position_arg(args, 2);
818 let total = with_file(fd, |file| {
819 let mut fr: &File = file;
820 if let Some(pos) = position {
821 fr.seek(SeekFrom::Start(pos)).ok();
822 }
823 let mut chunks: Vec<(Value, Vec<u8>)> = Vec::new();
824 for b in &buffers {
825 let cap = buf_len(b);
826 let mut buf = vec![0u8; cap];
827 match fr.read(&mut buf) {
828 Ok(0) => break,
829 Ok(n) => {
830 buf.truncate(n);
831 chunks.push((b.clone(), buf));
832 }
833 Err(e) => return Err(e),
834 }
835 }
836 Ok(chunks)
837 });
838 match total {
839 Some(Ok(chunks)) => Ok(chunks.iter().map(|(b, d)| write_into_buffer(b, 0, d)).sum()),
840 Some(Err(e)) => Err(err_str("readv", "", &e)),
841 None => Err("Error: EBADF: bad file descriptor, readv".to_string()),
842 }
843}
844
845fn writev_impl(args: &[Value]) -> Result<usize, String> {
846 let fd = arg_num(args, 0) as i32;
847 let buffers = array_items(args.get(1));
848 let position = position_arg(args, 2);
849 let mut data = Vec::new();
850 for b in &buffers {
851 data.extend(buf_bytes(b));
852 }
853 let r = with_file(fd, |file| {
854 let mut fr: &File = file;
855 if let Some(pos) = position {
856 fr.seek(SeekFrom::Start(pos)).ok();
857 }
858 fr.write(&data)
859 });
860 match r {
861 Some(Ok(n)) => Ok(n),
862 Some(Err(e)) => Err(err_str("writev", "", &e)),
863 None => Err("Error: EBADF: bad file descriptor, writev".to_string()),
864 }
865}
866
867fn fstat_impl(args: &[Value]) -> Result<Value, String> {
868 let fd = arg_num(args, 0) as i32;
869 let md = with_file(fd, |file| file.metadata());
870 match md {
871 Some(Ok(md)) => Ok(with_host(|h| build_stats(h, &md))),
872 Some(Err(e)) => Err(err_str("fstat", "", &e)),
873 None => Err("Error: EBADF: bad file descriptor, fstat".to_string()),
874 }
875}
876
877fn fchmod_impl(args: &[Value]) -> Result<Value, String> {
878 let fd = arg_num(args, 0) as i32;
879 let mode = arg_num(args, 1) as libc::mode_t;
880 let rc = with_file(fd, |file| unsafe { libc::fchmod(file.as_raw_fd(), mode) });
881 fd_result(rc, "fchmod")
882}
883
884fn fchown_impl(args: &[Value]) -> Result<Value, String> {
885 let fd = arg_num(args, 0) as i32;
886 let uid = arg_num(args, 1) as libc::uid_t;
887 let gid = arg_num(args, 2) as libc::gid_t;
888 let rc = with_file(fd, |file| unsafe {
889 libc::fchown(file.as_raw_fd(), uid, gid)
890 });
891 fd_result(rc, "fchown")
892}
893
894fn futimes_impl(args: &[Value]) -> Result<Value, String> {
895 let fd = arg_num(args, 0) as i32;
896 let times = [
897 to_timeval(time_secs(args, 1)),
898 to_timeval(time_secs(args, 2)),
899 ];
900 let rc = with_file(fd, |file| unsafe {
901 libc::futimes(file.as_raw_fd(), times.as_ptr())
902 });
903 fd_result(rc, "futimes")
904}
905
906fn ftruncate_impl(args: &[Value]) -> Result<Value, String> {
907 let fd = arg_num(args, 0) as i32;
908 let len = arg_num(args, 1);
909 let len = if len.is_nan() { 0 } else { len as u64 };
910 match with_file(fd, |file| file.set_len(len)) {
911 Some(Ok(_)) => Ok(Value::Undef),
912 Some(Err(e)) => Err(err_str("ftruncate", "", &e)),
913 None => Err("Error: EBADF: bad file descriptor, ftruncate".to_string()),
914 }
915}
916
917fn fsync_impl(args: &[Value], data_only: bool) -> Result<Value, String> {
918 let fd = arg_num(args, 0) as i32;
919 let op = if data_only { "fdatasync" } else { "fsync" };
920 let r = with_file(fd, |file| {
921 if data_only {
922 file.sync_data()
923 } else {
924 file.sync_all()
925 }
926 });
927 match r {
928 Some(Ok(_)) => Ok(Value::Undef),
929 Some(Err(e)) => Err(err_str(op, "", &e)),
930 None => Err(format!("Error: EBADF: bad file descriptor, {op}")),
931 }
932}
933
934fn fd_result(rc: Option<libc::c_int>, op: &str) -> Result<Value, String> {
937 match rc {
938 Some(0) => Ok(Value::Undef),
939 Some(_) => Err(err_str(op, "", &std::io::Error::last_os_error())),
940 None => Err(format!("Error: EBADF: bad file descriptor, {op}")),
941 }
942}
943
944fn stat_impl(op: &str, args: &[Value], follow: bool) -> Result<Value, String> {
947 let path = arg_str(args, 0);
948 let md = if follow {
949 std::fs::metadata(&path)
950 } else {
951 std::fs::symlink_metadata(&path)
952 };
953 match md {
954 Ok(md) => Ok(with_host(|h| build_stats(h, &md))),
955 Err(e) => Err(err_str(op, &path, &e)),
956 }
957}
958
959fn statfs_impl(args: &[Value]) -> Result<Value, String> {
960 let path = arg_str(args, 0);
961 let c = cpath(&path, "statfs")?;
962 let mut st: libc::statvfs = unsafe { std::mem::zeroed() };
963 if unsafe { libc::statvfs(c.as_ptr(), &mut st) } != 0 {
964 return Err(err_str("statfs", &path, &std::io::Error::last_os_error()));
965 }
966 Ok(with_host(|h| {
967 let mut m = IndexMap::new();
968 m.insert("type".into(), Value::Float(st.f_fsid as f64));
969 m.insert("bsize".into(), Value::Float(st.f_bsize as f64));
970 m.insert("blocks".into(), Value::Float(st.f_blocks as f64));
971 m.insert("bfree".into(), Value::Float(st.f_bfree as f64));
972 m.insert("bavail".into(), Value::Float(st.f_bavail as f64));
973 m.insert("files".into(), Value::Float(st.f_files as f64));
974 m.insert("ffree".into(), Value::Float(st.f_ffree as f64));
975 h.new_object(m)
976 }))
977}
978
979fn build_stats(h: &mut crate::host::JsHost, md: &std::fs::Metadata) -> Value {
981 let ns = |s: i64, n: i64| s as f64 * 1000.0 + n as f64 / 1_000_000.0;
982 let atime_ms = ns(md.atime(), md.atime_nsec());
983 let mtime_ms = ns(md.mtime(), md.mtime_nsec());
984 let ctime_ms = ns(md.ctime(), md.ctime_nsec());
985 let birth_ms = md
986 .created()
987 .ok()
988 .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
989 .map(|d| d.as_secs_f64() * 1000.0)
990 .unwrap_or(mtime_ms);
991 let ft = md.file_type();
992 let date = |h: &mut crate::host::JsHost, ms: f64| {
993 let mut d = IndexMap::new();
994 d.insert("@@native".into(), h.new_str("Date"));
995 d.insert("@@ms".into(), Value::Float(ms));
996 h.new_object(d)
997 };
998 let mut m = IndexMap::new();
999 m.insert("@@native".into(), h.new_str("Stats"));
1000 m.insert("@@isFile".into(), Value::Bool(md.is_file()));
1001 m.insert("@@isDir".into(), Value::Bool(md.is_dir()));
1002 m.insert("@@isSymlink".into(), Value::Bool(ft.is_symlink()));
1003 m.insert("dev".into(), Value::Float(md.dev() as f64));
1004 m.insert("mode".into(), Value::Float(md.mode() as f64));
1005 m.insert("nlink".into(), Value::Float(md.nlink() as f64));
1006 m.insert("uid".into(), Value::Float(md.uid() as f64));
1007 m.insert("gid".into(), Value::Float(md.gid() as f64));
1008 m.insert("rdev".into(), Value::Float(md.rdev() as f64));
1009 m.insert("blksize".into(), Value::Float(md.blksize() as f64));
1010 m.insert("ino".into(), Value::Float(md.ino() as f64));
1011 m.insert("size".into(), Value::Float(md.len() as f64));
1012 m.insert("blocks".into(), Value::Float(md.blocks() as f64));
1013 m.insert("atimeMs".into(), Value::Float(atime_ms));
1014 m.insert("mtimeMs".into(), Value::Float(mtime_ms));
1015 m.insert("ctimeMs".into(), Value::Float(ctime_ms));
1016 m.insert("birthtimeMs".into(), Value::Float(birth_ms));
1017 let atime = date(h, atime_ms);
1018 m.insert("atime".into(), atime);
1019 let mtime = date(h, mtime_ms);
1020 m.insert("mtime".into(), mtime);
1021 let ctime = date(h, ctime_ms);
1022 m.insert("ctime".into(), ctime);
1023 let birthtime = date(h, birth_ms);
1024 m.insert("birthtime".into(), birthtime);
1025 h.new_object(m)
1026}
1027
1028fn watch_file(args: &[Value]) -> Result<Value, String> {
1031 let path = arg_str(args, 0);
1032 let Some(listener) = args.last().cloned().filter(is_fn) else {
1033 return Ok(Value::Undef);
1034 };
1035 let interval = interval_opt(args).unwrap_or(5007.0).max(1.0) as u64;
1036 let abs = std::fs::canonicalize(&path)
1037 .map(|p| p.to_string_lossy().into_owned())
1038 .unwrap_or_else(|_| path.clone());
1039
1040 let stop = Arc::new(AtomicBool::new(false));
1041 let id = NEXT_WATCH_ID.with(|n| {
1042 let v = *n.borrow();
1043 *n.borrow_mut() = v + 1;
1044 v
1045 });
1046 WATCHERS.with(|w| {
1047 w.borrow_mut().push(WatchEntry {
1048 id,
1049 path: abs.clone(),
1050 listener: listener.clone(),
1051 stop: stop.clone(),
1052 });
1053 });
1054 with_host(|h| h.incr_handle());
1055
1056 let tx = with_host(|h| h.io_sender());
1057 let poll_stop = stop.clone();
1058 let poll_listener = listener;
1059 std::thread::spawn(move || {
1060 let mut prev = stat_parts(&abs);
1061 loop {
1062 if poll_stop.load(Ordering::Acquire) {
1063 break;
1064 }
1065 std::thread::sleep(Duration::from_millis(interval));
1066 if poll_stop.load(Ordering::Acquire) {
1067 break;
1068 }
1069 let curr = stat_parts(&abs);
1070 if curr != prev {
1071 let l = poll_listener.clone();
1072 let (p0, p1, p2) = prev;
1073 let (c0, c1, c2) = curr;
1074 let _ = tx.send(Box::new(move || {
1075 let cur = with_host(|h| stats_from_parts(h, c0, c1, c2));
1076 let old = with_host(|h| stats_from_parts(h, p0, p1, p2));
1077 if let Err(e) = invoke(&l, vec![cur, old], None) {
1078 eprintln!("{e}");
1079 }
1080 Ok(())
1081 }));
1082 prev = curr;
1083 }
1084 }
1085 });
1086 Ok(Value::Undef)
1087}
1088
1089fn unwatch_file(args: &[Value]) -> Result<Value, String> {
1090 let path = arg_str(args, 0);
1091 let abs = std::fs::canonicalize(&path)
1092 .map(|p| p.to_string_lossy().into_owned())
1093 .unwrap_or_else(|_| path.clone());
1094 let listener = args.get(1).cloned().filter(is_fn);
1095 let removed = WATCHERS.with(|w| {
1096 let mut w = w.borrow_mut();
1097 let mut count = 0;
1098 w.retain(|e| {
1099 let matches =
1100 e.path == abs && listener.as_ref().map(|l| *l == e.listener).unwrap_or(true);
1101 if matches {
1102 e.stop.store(true, Ordering::Release);
1103 count += 1;
1104 }
1105 !matches
1106 });
1107 count
1108 });
1109 for _ in 0..removed {
1110 with_host(|h| h.decr_handle());
1111 }
1112 if removed > 0 {
1114 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
1115 }
1116 Ok(Value::Undef)
1117}
1118
1119fn stat_parts(path: &str) -> (bool, i64, u64) {
1121 match std::fs::metadata(path) {
1122 Ok(md) => (
1123 true,
1124 md.mtime() * 1000 + md.mtime_nsec() / 1_000_000,
1125 md.len(),
1126 ),
1127 Err(_) => (false, 0, 0),
1128 }
1129}
1130
1131fn stats_from_parts(h: &mut crate::host::JsHost, exists: bool, mtime_ms: i64, size: u64) -> Value {
1132 let ms = mtime_ms as f64;
1133 let date = |h: &mut crate::host::JsHost, ms: f64| {
1134 let mut d = IndexMap::new();
1135 d.insert("@@native".into(), h.new_str("Date"));
1136 d.insert("@@ms".into(), Value::Float(ms));
1137 h.new_object(d)
1138 };
1139 let mut m = IndexMap::new();
1140 m.insert("@@native".into(), h.new_str("Stats"));
1141 m.insert("@@isFile".into(), Value::Bool(exists));
1142 m.insert("@@isDir".into(), Value::Bool(false));
1143 m.insert("@@isSymlink".into(), Value::Bool(false));
1144 m.insert(
1145 "size".into(),
1146 Value::Float(if exists { size as f64 } else { 0.0 }),
1147 );
1148 m.insert("atimeMs".into(), Value::Float(ms));
1149 m.insert("mtimeMs".into(), Value::Float(ms));
1150 m.insert("ctimeMs".into(), Value::Float(ms));
1151 m.insert("birthtimeMs".into(), Value::Float(ms));
1152 let mt = date(h, ms);
1153 m.insert("mtime".into(), mt);
1154 let at = date(h, ms);
1155 m.insert("atime".into(), at);
1156 h.new_object(m)
1157}
1158
1159fn create_read_stream(args: &[Value]) -> Result<Value, String> {
1162 let path = arg_str(args, 0);
1163 let enc = encoding_arg(args, 1);
1164 let stream = with_host(|h| {
1165 let mut extra = IndexMap::new();
1166 extra.insert("path".into(), h.new_str(path.clone()));
1167 if let Some(e) = &enc {
1168 extra.insert("@@encoding".into(), h.new_str(e.clone()));
1169 }
1170 extra
1171 });
1172 let stream = super::net::new_emitter_object("FSReadStream", stream);
1173 with_host(|h| h.incr_handle());
1174 let recv = stream.clone();
1175 let p = path;
1176 with_host(|h| {
1177 h.queue_micro_native(Box::new(move || {
1178 read_stream_pump(&recv, &p);
1179 Ok(())
1180 }))
1181 });
1182 Ok(stream)
1183}
1184
1185fn read_stream_pump(recv: &Value, path: &str) {
1188 with_host(|h| h.decr_handle());
1189 let bytes = match std::fs::read(path) {
1190 Ok(b) => b,
1191 Err(e) => {
1192 let ev = with_host(|h| crate::builtins::synth_error(h, &err_str("open", path, &e)));
1193 let _ = super::events::instance_call(
1194 recv,
1195 "emit",
1196 vec![with_host(|h| h.new_str("error")), ev],
1197 );
1198 return;
1199 }
1200 };
1201 let enc = get_prop(recv, "@@encoding").map(|v| with_host(|h| h.str_of(&v)));
1202 let chunk = match enc.as_deref() {
1203 Some(e) if e != "buffer" => {
1204 with_host(|h| h.new_str(String::from_utf8_lossy(&bytes).into_owned()))
1205 }
1206 _ => super::buffer::from_bytes(&bytes),
1207 };
1208 if let Some(dest) = get_prop(recv, "@@pipeDest") {
1209 let _ = crate::host::call_method(&dest, "write", vec![chunk]);
1210 let _ = crate::host::call_method(&dest, "end", vec![]);
1211 } else {
1212 let name = with_host(|h| h.new_str("data"));
1213 let _ = super::events::instance_call(recv, "emit", vec![name, chunk]);
1214 }
1215 let _ = super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("end"))]);
1216 let _ = super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))]);
1217}
1218
1219pub const READ_STREAM_METHODS: &[&str] = &[
1220 "pipe",
1221 "pause",
1222 "resume",
1223 "setEncoding",
1224 "destroy",
1225 "close",
1226 "read",
1227];
1228
1229pub fn read_stream_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
1233 match method {
1234 "pipe" => {
1235 if let Some(dest) = args.first().cloned() {
1236 set_prop(recv, "@@pipeDest", dest.clone());
1237 Ok(dest)
1238 } else {
1239 Ok(recv.clone())
1240 }
1241 }
1242 "setEncoding" => {
1243 set_prop(
1244 recv,
1245 "@@encoding",
1246 with_host(|h| h.new_str(super::arg_str(&args, 0))),
1247 );
1248 Ok(recv.clone())
1249 }
1250 "pause" | "resume" | "read" => Ok(recv.clone()),
1251 "destroy" | "close" => {
1252 let _ =
1253 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))]);
1254 Ok(recv.clone())
1255 }
1256 _ => Err(crate::host::type_error(&format!(
1257 "stream.{method} is not a function"
1258 ))),
1259 }
1260}
1261
1262fn create_write_stream(args: &[Value]) -> Result<Value, String> {
1263 let path = arg_str(args, 0);
1264 let flags = match encoding_flag(args, "flags") {
1265 Some(f) => f,
1266 None => "w".to_string(),
1267 };
1268 let file = open_options(&flags)
1269 .open(&path)
1270 .map_err(|e| err_str("open", &path, &e))?;
1271 let fd = register_fd(file);
1272 let stream = with_host(|h| {
1273 let mut extra = IndexMap::new();
1274 extra.insert("path".into(), h.new_str(path));
1275 extra.insert("@@wfd".into(), Value::Float(fd as f64));
1276 extra.insert("bytesWritten".into(), Value::Float(0.0));
1277 extra
1278 });
1279 let stream = super::net::new_emitter_object("FSWriteStream", stream);
1280 with_host(|h| h.incr_handle());
1281 let recv = stream.clone();
1282 with_host(|h| {
1283 h.queue_micro_native(Box::new(move || {
1284 let _ =
1285 super::events::instance_call(&recv, "emit", vec![with_host(|h| h.new_str("open"))]);
1286 let _ = super::events::instance_call(
1287 &recv,
1288 "emit",
1289 vec![with_host(|h| h.new_str("ready"))],
1290 );
1291 Ok(())
1292 }))
1293 });
1294 Ok(stream)
1295}
1296
1297pub const WRITE_STREAM_METHODS: &[&str] = &[
1298 "write",
1299 "end",
1300 "destroy",
1301 "close",
1302 "cork",
1303 "uncork",
1304 "setDefaultEncoding",
1305];
1306
1307pub fn write_stream_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
1309 match method {
1310 "write" => {
1311 write_stream_bytes(recv, args.first());
1312 if let Some(cb) = args.iter().find(|v| is_fn(v)).cloned() {
1313 let _ = invoke(&cb, vec![], None);
1314 }
1315 Ok(Value::Bool(true))
1316 }
1317 "end" => {
1318 if let Some(chunk) = args
1319 .first()
1320 .filter(|v| !matches!(v, Value::Undef) && !is_fn(v))
1321 {
1322 write_stream_bytes(recv, Some(chunk));
1323 }
1324 if let Some(fd) = get_prop(recv, "@@wfd").map(|v| with_host(|h| h.to_number(&v)) as i32)
1325 {
1326 close_fd(fd);
1327 }
1328 let _ = super::events::instance_call(
1329 recv,
1330 "emit",
1331 vec![with_host(|h| h.new_str("finish"))],
1332 );
1333 let _ =
1334 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))]);
1335 with_host(|h| h.decr_handle());
1336 if let Some(cb) = args.iter().find(|v| is_fn(v)).cloned() {
1337 let _ = invoke(&cb, vec![], None);
1338 }
1339 Ok(recv.clone())
1340 }
1341 "destroy" | "close" => {
1342 if let Some(fd) = get_prop(recv, "@@wfd").map(|v| with_host(|h| h.to_number(&v)) as i32)
1343 {
1344 close_fd(fd);
1345 }
1346 let _ =
1347 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))]);
1348 with_host(|h| h.decr_handle());
1349 Ok(recv.clone())
1350 }
1351 "cork" | "uncork" | "setDefaultEncoding" => Ok(recv.clone()),
1352 _ => Err(crate::host::type_error(&format!(
1353 "stream.{method} is not a function"
1354 ))),
1355 }
1356}
1357
1358fn write_stream_bytes(recv: &Value, chunk: Option<&Value>) {
1359 let Some(chunk) = chunk else { return };
1360 let data = value_bytes(chunk);
1361 if let Some(fd) = get_prop(recv, "@@wfd").map(|v| with_host(|h| h.to_number(&v)) as i32) {
1362 let written = with_file(fd, |file| {
1363 let mut fr: &File = file;
1364 fr.write(&data).unwrap_or(0)
1365 })
1366 .unwrap_or(0);
1367 let prev = get_prop(recv, "bytesWritten")
1368 .map(|v| with_host(|h| h.to_number(&v)))
1369 .unwrap_or(0.0);
1370 set_prop(recv, "bytesWritten", Value::Float(prev + written as f64));
1371 }
1372}
1373
1374pub fn stats_call(recv: &Value, method: &str) -> Result<Value, String> {
1378 type_test(recv, method, "stats")
1379}
1380
1381pub const DIRENT_METHODS: &[&str] = &[
1382 "isFile",
1383 "isDirectory",
1384 "isSymbolicLink",
1385 "isBlockDevice",
1386 "isCharacterDevice",
1387 "isFIFO",
1388 "isSocket",
1389];
1390
1391pub fn dirent_call(recv: &Value, method: &str) -> Result<Value, String> {
1394 type_test(recv, method, "dirent")
1395}
1396
1397fn type_test(recv: &Value, method: &str, what: &str) -> Result<Value, String> {
1401 let read = |key: &str| {
1402 with_host(|h| match h.get(recv) {
1403 Some(JsObj::Object(p)) => matches!(p.get(key), Some(Value::Bool(true))),
1404 _ => false,
1405 })
1406 };
1407 match method {
1408 "isFile" => Ok(Value::Bool(read("@@isFile"))),
1409 "isDirectory" => Ok(Value::Bool(read("@@isDir"))),
1410 "isSymbolicLink" => Ok(Value::Bool(read("@@isSymlink"))),
1411 "isBlockDevice" | "isCharacterDevice" | "isFIFO" | "isSocket" => Ok(Value::Bool(false)),
1412 _ => Err(crate::host::type_error(&format!(
1413 "{what}.{method} is not a function"
1414 ))),
1415 }
1416}
1417
1418pub const DIR_METHODS: &[&str] = &["read", "readSync", "close", "closeSync"];
1419
1420pub fn dir_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
1422 match method {
1423 "readSync" => Ok(dir_next(recv)),
1424 "read" => {
1425 let v = dir_next(recv);
1426 if let Some(cb) = args.first().filter(|c| is_fn(c)).cloned() {
1427 with_host(|h| {
1428 let n = h.null();
1429 h.queue_micro(cb, vec![n, v]);
1430 });
1431 Ok(Value::Undef)
1432 } else {
1433 Ok(settled_ok(v))
1434 }
1435 }
1436 "closeSync" => Ok(Value::Undef),
1437 "close" => {
1438 if let Some(cb) = args.first().filter(|c| is_fn(c)).cloned() {
1439 with_host(|h| {
1440 let n = h.null();
1441 h.queue_micro(cb, vec![n]);
1442 });
1443 Ok(Value::Undef)
1444 } else {
1445 Ok(settled_ok(Value::Undef))
1446 }
1447 }
1448 _ => Err(crate::host::type_error(&format!(
1449 "dir.{method} is not a function"
1450 ))),
1451 }
1452}
1453
1454fn dir_next(recv: &Value) -> Value {
1456 with_host(|h| {
1457 let (entries, pos) = match h.get(recv) {
1458 Some(JsObj::Object(p)) => (
1459 p.get("@@entries").cloned(),
1460 p.get("@@pos").map(|v| h.to_number(v) as usize).unwrap_or(0),
1461 ),
1462 _ => (None, 0),
1463 };
1464 let item = match entries.as_ref().and_then(|e| h.get(e)) {
1465 Some(JsObj::Array(items)) => items.get(pos).cloned(),
1466 _ => None,
1467 };
1468 if item.is_some() {
1469 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1470 p.insert("@@pos".into(), Value::Float((pos + 1) as f64));
1471 }
1472 }
1473 item.unwrap_or_else(|| h.null())
1474 })
1475}
1476
1477fn build_dirent(
1478 h: &mut crate::host::JsHost,
1479 name: String,
1480 parent: &str,
1481 ft: std::fs::FileType,
1482) -> Value {
1483 let mut m = IndexMap::new();
1484 m.insert("@@native".into(), h.new_str("Dirent"));
1485 m.insert("name".into(), h.new_str(name));
1486 let pp = h.new_str(parent.to_string());
1487 m.insert("parentPath".into(), pp.clone());
1488 m.insert("path".into(), pp);
1489 m.insert("@@isFile".into(), Value::Bool(ft.is_file()));
1490 m.insert("@@isDir".into(), Value::Bool(ft.is_dir()));
1491 m.insert("@@isSymlink".into(), Value::Bool(ft.is_symlink()));
1492 h.new_object(m)
1493}
1494
1495fn glob_impl(args: &[Value]) -> Result<Value, String> {
1498 let pattern = arg_str(args, 0);
1499 let absolute = pattern.starts_with('/');
1500 let base = if absolute {
1501 PathBuf::from("/")
1502 } else {
1503 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
1504 };
1505 let segs: Vec<String> = pattern
1506 .split('/')
1507 .filter(|s| !s.is_empty())
1508 .map(String::from)
1509 .collect();
1510 let mut out: Vec<String> = Vec::new();
1511 let prefix = if absolute {
1512 "/".to_string()
1513 } else {
1514 String::new()
1515 };
1516 glob_walk(&base, &segs, 0, &prefix, &mut out);
1517 out.sort();
1518 out.dedup();
1519 Ok(with_host(|h| {
1520 let items: Vec<Value> = out.into_iter().map(|s| h.new_str(s)).collect();
1521 h.new_array(items)
1522 }))
1523}
1524
1525fn glob_walk(dir: &Path, segs: &[String], idx: usize, prefix: &str, out: &mut Vec<String>) {
1526 if idx >= segs.len() {
1527 if !prefix.is_empty() && prefix != "/" {
1528 out.push(prefix.trim_end_matches('/').to_string());
1529 }
1530 return;
1531 }
1532 let seg = &segs[idx];
1533 if seg == "**" {
1534 glob_walk(dir, segs, idx + 1, prefix, out);
1535 if let Ok(rd) = std::fs::read_dir(dir) {
1536 for e in rd.flatten() {
1537 if e.path().is_dir() {
1538 let name = e.file_name().to_string_lossy().into_owned();
1539 let np = join_glob(prefix, &name);
1540 glob_walk(&e.path(), segs, idx, &np, out);
1541 }
1542 }
1543 }
1544 return;
1545 }
1546 let last = idx + 1 == segs.len();
1547 if let Ok(rd) = std::fs::read_dir(dir) {
1548 for e in rd.flatten() {
1549 let name = e.file_name().to_string_lossy().into_owned();
1550 if name.starts_with('.') && !seg.starts_with('.') {
1551 continue;
1552 }
1553 if wildcard_match(seg, &name) {
1554 let np = join_glob(prefix, &name);
1555 if last {
1556 out.push(np);
1557 } else if e.path().is_dir() {
1558 glob_walk(&e.path(), segs, idx + 1, &np, out);
1559 }
1560 }
1561 }
1562 }
1563}
1564
1565fn join_glob(prefix: &str, name: &str) -> String {
1566 if prefix.is_empty() {
1567 name.to_string()
1568 } else if prefix.ends_with('/') {
1569 format!("{prefix}{name}")
1570 } else {
1571 format!("{prefix}/{name}")
1572 }
1573}
1574
1575fn wildcard_match(pat: &str, name: &str) -> bool {
1577 let p: Vec<char> = pat.chars().collect();
1578 let n: Vec<char> = name.chars().collect();
1579 let (mut pi, mut ni) = (0usize, 0usize);
1580 let (mut star, mut mark) = (None, 0usize);
1581 while ni < n.len() {
1582 if pi < p.len() && (p[pi] == '?' || p[pi] == n[ni]) {
1583 pi += 1;
1584 ni += 1;
1585 } else if pi < p.len() && p[pi] == '*' {
1586 star = Some(pi);
1587 mark = ni;
1588 pi += 1;
1589 } else if let Some(s) = star {
1590 pi = s + 1;
1591 mark += 1;
1592 ni = mark;
1593 } else {
1594 return false;
1595 }
1596 }
1597 while pi < p.len() && p[pi] == '*' {
1598 pi += 1;
1599 }
1600 pi == p.len()
1601}
1602
1603fn settled_ok(v: Value) -> Value {
1606 let p = with_host(|h| h.new_promise());
1607 let id = with_host(|h| h.promise_id(&p).unwrap_or(0));
1608 crate::host::resolve_promise_val(id, v);
1609 p
1610}
1611
1612fn get_prop(recv: &Value, key: &str) -> Option<Value> {
1613 with_host(|h| match h.get(recv) {
1614 Some(JsObj::Object(p)) => p.get(key).cloned(),
1615 _ => None,
1616 })
1617}
1618
1619fn set_prop(recv: &Value, key: &str, val: Value) {
1620 with_host(|h| {
1621 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1622 p.insert(key.to_string(), val);
1623 }
1624 });
1625}
1626
1627fn value_bytes(v: &Value) -> Vec<u8> {
1629 if native_tag(v).as_deref() == Some("Buffer") {
1630 buf_bytes(v)
1631 } else {
1632 with_host(|h| h.str_of(v)).into_bytes()
1633 }
1634}
1635
1636fn buf_bytes(v: &Value) -> Vec<u8> {
1637 with_host(|h| match h.get(v) {
1638 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
1639 Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
1640 _ => Vec::new(),
1641 },
1642 _ => Vec::new(),
1643 })
1644}
1645
1646fn buf_len(v: &Value) -> usize {
1647 with_host(|h| match h.get(v) {
1648 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
1649 Some(JsObj::Array(items)) => items.len(),
1650 _ => 0,
1651 },
1652 _ => 0,
1653 })
1654}
1655
1656fn write_into_buffer(buf: &Value, offset: usize, data: &[u8]) -> usize {
1659 let Some(arr) = get_prop(buf, "@@bytes") else {
1660 return 0;
1661 };
1662 with_host(|h| {
1663 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
1664 let mut n = 0;
1665 for (i, b) in data.iter().enumerate() {
1666 let idx = offset + i;
1667 if idx >= items.len() {
1668 break;
1669 }
1670 items[idx] = Value::Float(*b as f64);
1671 n += 1;
1672 }
1673 n
1674 } else {
1675 0
1676 }
1677 })
1678}
1679
1680fn array_items(v: Option<&Value>) -> Vec<Value> {
1681 match v {
1682 Some(v) => with_host(|h| match h.get(v) {
1683 Some(JsObj::Array(items)) => items.clone(),
1684 _ => Vec::new(),
1685 }),
1686 None => Vec::new(),
1687 }
1688}
1689
1690fn opt_flag(args: &[Value], key: &str) -> bool {
1691 with_host(|h| {
1692 args.iter().any(|v| {
1693 matches!(h.get(v), Some(JsObj::Object(p)) if matches!(p.get(key), Some(Value::Bool(true))))
1694 })
1695 })
1696}
1697
1698fn encoding_flag(args: &[Value], key: &str) -> Option<String> {
1699 with_host(|h| {
1700 for v in args {
1701 if let Some(JsObj::Object(p)) = h.get(v) {
1702 if let Some(val) = p.get(key) {
1703 return Some(h.str_of(val));
1704 }
1705 }
1706 }
1707 None
1708 })
1709}
1710
1711fn interval_opt(args: &[Value]) -> Option<f64> {
1712 with_host(|h| {
1713 for v in args {
1714 if let Some(JsObj::Object(p)) = h.get(v) {
1715 if let Some(val) = p.get("interval") {
1716 return Some(h.to_number(val));
1717 }
1718 }
1719 }
1720 None
1721 })
1722}
1723
1724fn num_or(args: &[Value], i: usize, default: f64) -> f64 {
1725 match args.get(i) {
1726 Some(v) if !matches!(v, Value::Undef) => {
1727 let n = with_host(|h| h.to_number(v));
1728 if n.is_nan() {
1729 default
1730 } else {
1731 n
1732 }
1733 }
1734 _ => default,
1735 }
1736}
1737
1738fn position_arg(args: &[Value], i: usize) -> Option<u64> {
1740 match args.get(i) {
1741 Some(Value::Undef) | None => None,
1742 Some(v) if with_host(|h| h.is_null(v)) => None,
1743 Some(v) => {
1744 let n = with_host(|h| h.to_number(v));
1745 if n.is_nan() || n < 0.0 {
1746 None
1747 } else {
1748 Some(n as u64)
1749 }
1750 }
1751 }
1752}
1753
1754fn time_secs(args: &[Value], i: usize) -> f64 {
1757 match args.get(i) {
1758 Some(v) if native_tag(v).as_deref() == Some("Date") => arg_num(args, i) / 1000.0,
1759 _ => arg_num(args, i),
1760 }
1761}
1762
1763fn to_timeval(secs: f64) -> libc::timeval {
1764 let s = secs.floor();
1765 let us = ((secs - s) * 1_000_000.0).round();
1766 libc::timeval {
1767 tv_sec: s as libc::time_t,
1768 tv_usec: us as libc::suseconds_t,
1769 }
1770}
1771
1772fn cpath(path: &str, op: &str) -> Result<CString, String> {
1773 CString::new(path).map_err(|_| format!("Error: EINVAL: invalid argument, {op} '{path}'"))
1774}
1775
1776fn ok_or_errno(rc: libc::c_int, op: &str, path: &str) -> Result<Value, String> {
1777 if rc == 0 {
1778 Ok(Value::Undef)
1779 } else {
1780 Err(err_str(op, path, &std::io::Error::last_os_error()))
1781 }
1782}
1783
1784fn encoding_arg(args: &[Value], i: usize) -> Option<String> {
1785 match args.get(i) {
1786 Some(Value::Undef) | None => None,
1787 Some(v) => {
1788 let s = with_host(|h| h.str_of(v));
1789 if s == "undefined" || s == "[object Object]" || s == "null" {
1790 None
1791 } else {
1792 Some(s)
1793 }
1794 }
1795 }
1796}
1797
1798fn err_str(op: &str, path: &str, e: &std::io::Error) -> String {
1799 use std::io::ErrorKind::*;
1800 let code = match e.kind() {
1801 NotFound => "ENOENT",
1802 PermissionDenied => "EACCES",
1803 AlreadyExists => "EEXIST",
1804 _ => "EIO",
1805 };
1806 let reason = e.to_string();
1807 let reason = reason.split(" (os error").next().unwrap_or("error");
1808 if path.is_empty() {
1809 format!("Error: {code}: {reason}, {op}")
1810 } else {
1811 format!("Error: {code}: {reason}, {op} '{path}'")
1812 }
1813}