1use crate::host::with_host;
8use fusevm::Value;
9use indexmap::IndexMap;
10
11pub const METHODS: &[&str] = &[
12 "platform",
13 "arch",
14 "type",
15 "release",
16 "hostname",
17 "homedir",
18 "tmpdir",
19 "endianness",
20 "cpus",
21 "totalmem",
22 "freemem",
23 "uptime",
24 "loadavg",
25 "userInfo",
26 "networkInterfaces",
27 "version",
28 "machine",
29 "availableParallelism",
30 "getPriority",
31 "setPriority",
32];
33
34pub fn constant(name: &str) -> Option<Value> {
36 match name {
37 "EOL" => Some(with_host(|h| h.new_str("\n"))),
38 "devNull" => Some(with_host(|h| h.new_str("/dev/null"))),
39 "constants" => {
45 let sig = super::constants::object(&super::constants::signals());
48 let prio = super::constants::object(&super::constants::priority());
49 let errno = super::constants::object(&super::constants::errno());
50 let dlopen = super::constants::object(&super::constants::dlopen());
51 Some(with_host(|h| {
52 let mut m = indexmap::IndexMap::new();
53 m.insert("UV_UDP_REUSEADDR".to_string(), Value::Float(4.0));
54 m.insert("dlopen".to_string(), dlopen);
55 m.insert("errno".to_string(), errno);
56 m.insert("signals".to_string(), sig);
57 m.insert("priority".to_string(), prio);
58 h.new_object(m)
59 }))
60 }
61 _ => None,
62 }
63}
64
65pub fn platform() -> &'static str {
67 match std::env::consts::OS {
68 "macos" => "darwin",
69 "windows" => "win32",
70 other => other,
71 }
72}
73
74pub fn arch() -> &'static str {
76 match std::env::consts::ARCH {
77 "aarch64" => "arm64",
78 "x86_64" => "x64",
79 other => other,
80 }
81}
82
83pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
84 let s = |v: &str| Ok(with_host(|h| h.new_str(v)));
85 Some(match method {
86 "platform" => s(platform()),
87 "arch" => s(arch()),
88 "machine" => s(std::env::consts::ARCH),
89 "type" => s(match std::env::consts::OS {
90 "macos" => "Darwin",
91 "linux" => "Linux",
92 "windows" => "Windows_NT",
93 other => other,
94 }),
95 "release" => s(""),
96 "version" => s(""),
97 "hostname" => s(&hostname()),
98 "homedir" => s(&dirs::home_dir()
99 .map(|p| p.to_string_lossy().into_owned())
100 .unwrap_or_default()),
101 "tmpdir" => s(std::env::temp_dir().to_string_lossy().trim_end_matches('/')),
102 "endianness" => s(if cfg!(target_endian = "big") {
103 "BE"
104 } else {
105 "LE"
106 }),
107 "totalmem" => Ok(Value::Float(phys_bytes(libc::_SC_PHYS_PAGES))),
111 "freemem" => Ok(Value::Float(free_bytes())),
112 "uptime" => Ok(Value::Float(uptime_secs())),
113 "cpus" => Ok(cpus()),
114 "loadavg" => {
116 let mut avg = [0f64; 3];
117 let n = unsafe { libc::getloadavg(avg.as_mut_ptr(), 3) };
119 let items: Vec<Value> = if n == 3 {
120 avg.iter().map(|v| Value::Float(*v)).collect()
121 } else {
122 vec![Value::Float(0.0); 3]
123 };
124 Ok(with_host(|h| h.new_array(items)))
125 }
126 "networkInterfaces" => Ok(network_interfaces()),
127 "userInfo" => Ok(user_info()),
128 "availableParallelism" => {
130 let n = std::thread::available_parallelism()
131 .map(|n| n.get())
132 .unwrap_or(1);
133 Ok(Value::Float(n as f64))
134 }
135 "getPriority" => {
137 let pid = if args.is_empty() {
138 0
139 } else {
140 super::arg_num(args, 0) as i32
141 };
142 let prio = unsafe { libc::getpriority(libc::PRIO_PROCESS as _, pid as _) };
144 Ok(Value::Float(prio as f64))
145 }
146 "setPriority" => {
149 let (pid, prio) = if args.len() >= 2 {
150 (
151 super::arg_num(args, 0) as i32,
152 super::arg_num(args, 1) as i32,
153 )
154 } else {
155 (0, super::arg_num(args, 0) as i32)
156 };
157 unsafe {
159 libc::setpriority(libc::PRIO_PROCESS as _, pid as _, prio as _);
160 }
161 Ok(Value::Undef)
162 }
163 _ => return None,
164 })
165}
166
167fn hostname() -> String {
168 std::process::Command::new("hostname")
169 .output()
170 .ok()
171 .and_then(|o| String::from_utf8(o.stdout).ok())
172 .map(|s| s.trim().to_string())
173 .unwrap_or_default()
174}
175
176fn user_info() -> Value {
177 with_host(|h| {
178 let mut m = IndexMap::new();
179 let user = std::env::var("USER")
180 .or_else(|_| std::env::var("USERNAME"))
181 .unwrap_or_default();
182 let home = dirs::home_dir()
183 .map(|p| p.to_string_lossy().into_owned())
184 .unwrap_or_default();
185 let shell = std::env::var("SHELL").unwrap_or_default();
186 m.insert("username".into(), h.new_str(user));
187 m.insert("homedir".into(), h.new_str(home));
188 m.insert("shell".into(), h.new_str(shell));
189 m.insert("uid".into(), Value::Float(-1.0));
190 m.insert("gid".into(), Value::Float(-1.0));
191 h.new_object(m)
192 })
193}
194
195fn phys_bytes(name: libc::c_int) -> f64 {
199 let pages = unsafe { libc::sysconf(name) };
201 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
202 if pages <= 0 || page <= 0 {
203 return 0.0;
204 }
205 pages as f64 * page as f64
206}
207
208#[cfg(target_os = "linux")]
211fn free_bytes() -> f64 {
212 phys_bytes(libc::_SC_AVPHYS_PAGES)
213}
214
215#[cfg(target_os = "macos")]
216fn free_bytes() -> f64 {
217 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
221 if page <= 0 {
222 return 0.0;
223 }
224 let free = sysctl_u32(c"vm.page_free_count").unwrap_or(0) as f64;
228 let spec = sysctl_u32(c"vm.page_speculative_count").unwrap_or(0) as f64;
229 (free + spec) * page as f64
230}
231
232#[cfg(not(any(target_os = "linux", target_os = "macos")))]
233fn free_bytes() -> f64 {
234 0.0
235}
236
237#[cfg(target_os = "linux")]
239fn uptime_secs() -> f64 {
240 std::fs::read_to_string("/proc/uptime")
241 .ok()
242 .and_then(|s| s.split_whitespace().next()?.parse::<f64>().ok())
243 .unwrap_or(0.0)
244}
245
246#[cfg(target_os = "macos")]
247fn uptime_secs() -> f64 {
248 let mut mib = [libc::CTL_KERN, libc::KERN_BOOTTIME];
249 let mut tv: libc::timeval = unsafe { std::mem::zeroed() };
250 let mut len = std::mem::size_of::<libc::timeval>();
251 let rc = unsafe {
253 libc::sysctl(
254 mib.as_mut_ptr(),
255 mib.len() as u32,
256 &mut tv as *mut _ as *mut libc::c_void,
257 &mut len,
258 std::ptr::null_mut(),
259 0,
260 )
261 };
262 if rc != 0 || tv.tv_sec == 0 {
263 return 0.0;
264 }
265 let now = std::time::SystemTime::now()
266 .duration_since(std::time::UNIX_EPOCH)
267 .map(|d| d.as_secs_f64())
268 .unwrap_or(0.0);
269 (now - tv.tv_sec as f64).max(0.0).floor()
270}
271
272#[cfg(not(any(target_os = "linux", target_os = "macos")))]
273fn uptime_secs() -> f64 {
274 0.0
275}
276
277fn cpus() -> Value {
283 let n = std::thread::available_parallelism()
284 .map(|n| n.get())
285 .unwrap_or(1);
286 let model = cpu_model();
287 let speed = cpu_speed_mhz();
288 with_host(|h| {
289 let items: Vec<Value> = (0..n)
290 .map(|_| {
291 let mut times = IndexMap::new();
292 for k in ["user", "nice", "sys", "idle", "irq"] {
297 times.insert(k.to_string(), Value::Float(0.0));
298 }
299 let times = h.new_object(times);
300 let mut m = IndexMap::new();
301 m.insert("model".into(), h.new_str(model.clone()));
302 m.insert("speed".into(), Value::Float(speed));
303 m.insert("times".into(), times);
304 h.new_object(m)
305 })
306 .collect();
307 h.new_array(items)
308 })
309}
310
311#[cfg(target_os = "macos")]
312fn cpu_model() -> String {
313 sysctl_string(c"machdep.cpu.brand_string").unwrap_or_else(|| "unknown".into())
314}
315
316#[cfg(target_os = "linux")]
317fn cpu_model() -> String {
318 std::fs::read_to_string("/proc/cpuinfo")
319 .ok()
320 .and_then(|s| {
321 s.lines()
322 .find(|l| l.starts_with("model name") || l.starts_with("Model"))
323 .and_then(|l| l.split_once(':'))
324 .map(|(_, v)| v.trim().to_string())
325 })
326 .unwrap_or_else(|| "unknown".into())
327}
328
329#[cfg(not(any(target_os = "linux", target_os = "macos")))]
330fn cpu_model() -> String {
331 "unknown".into()
332}
333
334#[cfg(target_os = "macos")]
335fn cpu_speed_mhz() -> f64 {
336 sysctl_u64(c"hw.cpufrequency").map_or(0.0, |hz| (hz / 1_000_000) as f64)
339}
340
341#[cfg(target_os = "linux")]
342fn cpu_speed_mhz() -> f64 {
343 std::fs::read_to_string("/proc/cpuinfo")
344 .ok()
345 .and_then(|s| {
346 s.lines()
347 .find(|l| l.starts_with("cpu MHz"))
348 .and_then(|l| l.split_once(':'))
349 .and_then(|(_, v)| v.trim().parse::<f64>().ok())
350 })
351 .map(|f| f.round())
352 .unwrap_or(0.0)
353}
354
355#[cfg(not(any(target_os = "linux", target_os = "macos")))]
356fn cpu_speed_mhz() -> f64 {
357 0.0
358}
359
360#[cfg(target_os = "macos")]
361fn sysctl_string(name: &std::ffi::CStr) -> Option<String> {
362 let mut len: usize = 0;
363 if unsafe {
365 libc::sysctlbyname(
366 name.as_ptr(),
367 std::ptr::null_mut(),
368 &mut len,
369 std::ptr::null_mut(),
370 0,
371 )
372 } != 0
373 || len == 0
374 {
375 return None;
376 }
377 let mut buf = vec![0u8; len];
378 if unsafe {
380 libc::sysctlbyname(
381 name.as_ptr(),
382 buf.as_mut_ptr() as *mut libc::c_void,
383 &mut len,
384 std::ptr::null_mut(),
385 0,
386 )
387 } != 0
388 {
389 return None;
390 }
391 buf.pop();
392 String::from_utf8(buf).ok()
393}
394
395#[cfg(target_os = "macos")]
396fn sysctl_u32(name: &std::ffi::CStr) -> Option<u32> {
397 let mut out: u32 = 0;
398 let mut len = std::mem::size_of::<u32>();
399 let rc = unsafe {
401 libc::sysctlbyname(
402 name.as_ptr(),
403 &mut out as *mut _ as *mut libc::c_void,
404 &mut len,
405 std::ptr::null_mut(),
406 0,
407 )
408 };
409 (rc == 0).then_some(out)
410}
411
412#[cfg(target_os = "macos")]
413fn sysctl_u64(name: &std::ffi::CStr) -> Option<u64> {
414 let mut out: u64 = 0;
415 let mut len = std::mem::size_of::<u64>();
416 let rc = unsafe {
418 libc::sysctlbyname(
419 name.as_ptr(),
420 &mut out as *mut _ as *mut libc::c_void,
421 &mut len,
422 std::ptr::null_mut(),
423 0,
424 )
425 };
426 (rc == 0).then_some(out)
427}
428
429fn network_interfaces() -> Value {
436 let mut head: *mut libc::ifaddrs = std::ptr::null_mut();
437 if unsafe { libc::getifaddrs(&mut head) } != 0 {
439 return with_host(|h| h.new_object(IndexMap::new()));
440 }
441 let mut macs: std::collections::HashMap<String, String> = std::collections::HashMap::new();
442 let mut addrs: Vec<(String, IfAddr)> = Vec::new();
443 let mut cur = head;
444 while !cur.is_null() {
445 let ifa = unsafe { &*cur };
447 cur = ifa.ifa_next;
448 if ifa.ifa_name.is_null() {
449 continue;
450 }
451 let name = unsafe { std::ffi::CStr::from_ptr(ifa.ifa_name) }
453 .to_string_lossy()
454 .into_owned();
455 if let Some(mac) = link_mac(ifa) {
456 macs.insert(name.clone(), mac);
457 continue;
458 }
459 if let Some(a) = ip_addr(ifa) {
460 addrs.push((name, a));
461 }
462 }
463 unsafe { libc::freeifaddrs(head) };
465 with_host(|h| {
466 let mut grouped: IndexMap<String, Vec<Value>> = IndexMap::new();
467 for (name, a) in addrs {
468 let mac = macs
469 .get(&name)
470 .cloned()
471 .unwrap_or_else(|| "00:00:00:00:00:00".into());
472 let mut m = IndexMap::new();
473 m.insert("address".into(), h.new_str(a.address.clone()));
474 m.insert("netmask".into(), h.new_str(a.netmask.clone()));
475 m.insert("family".into(), h.new_str(a.family.to_string()));
476 m.insert("mac".into(), h.new_str(mac));
477 m.insert("internal".into(), Value::Bool(a.internal));
478 m.insert(
479 "cidr".into(),
480 h.new_str(format!("{}/{}", a.address, a.prefix)),
481 );
482 grouped.entry(name).or_default().push(h.new_object(m));
483 }
484 let mut out = IndexMap::new();
485 for (name, list) in grouped {
486 let arr = h.new_array(list);
487 out.insert(name, arr);
488 }
489 h.new_object(out)
490 })
491}
492
493struct IfAddr {
494 address: String,
495 netmask: String,
496 family: &'static str,
497 internal: bool,
498 prefix: u32,
499}
500
501#[cfg(target_os = "macos")]
503fn link_mac(ifa: &libc::ifaddrs) -> Option<String> {
504 if ifa.ifa_addr.is_null() {
505 return None;
506 }
507 if unsafe { (*ifa.ifa_addr).sa_family } as i32 != libc::AF_LINK {
509 return None;
510 }
511 let dl = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_dl) };
513 let start = dl.sdl_nlen as usize;
514 let len = dl.sdl_alen as usize;
515 if len != 6 || start + len > dl.sdl_data.len() {
516 return None;
517 }
518 let b: Vec<String> = dl.sdl_data[start..start + len]
519 .iter()
520 .map(|c| format!("{:02x}", *c as u8))
521 .collect();
522 Some(b.join(":"))
523}
524
525#[cfg(target_os = "linux")]
526fn link_mac(ifa: &libc::ifaddrs) -> Option<String> {
527 if ifa.ifa_addr.is_null() {
528 return None;
529 }
530 if unsafe { (*ifa.ifa_addr).sa_family } as i32 != libc::AF_PACKET {
532 return None;
533 }
534 let ll = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_ll) };
536 let len = ll.sll_halen as usize;
537 if len != 6 {
538 return None;
539 }
540 let b: Vec<String> = ll.sll_addr[..len]
541 .iter()
542 .map(|c| format!("{c:02x}"))
543 .collect();
544 Some(b.join(":"))
545}
546
547#[cfg(not(any(target_os = "linux", target_os = "macos")))]
548fn link_mac(_ifa: &libc::ifaddrs) -> Option<String> {
549 None
550}
551
552fn ip_addr(ifa: &libc::ifaddrs) -> Option<IfAddr> {
554 if ifa.ifa_addr.is_null() {
555 return None;
556 }
557 let fam = unsafe { (*ifa.ifa_addr).sa_family } as i32;
559 let internal = ifa.ifa_flags & libc::IFF_LOOPBACK as u32 != 0;
560 if fam == libc::AF_INET {
561 let sin = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_in) };
563 let ip = std::net::Ipv4Addr::from(u32::from_be(sin.sin_addr.s_addr));
564 let mask = if ifa.ifa_netmask.is_null() {
565 std::net::Ipv4Addr::UNSPECIFIED
566 } else {
567 let m = unsafe { &*(ifa.ifa_netmask as *const libc::sockaddr_in) };
569 std::net::Ipv4Addr::from(u32::from_be(m.sin_addr.s_addr))
570 };
571 return Some(IfAddr {
572 address: ip.to_string(),
573 netmask: mask.to_string(),
574 family: "IPv4",
575 internal,
576 prefix: u32::from(mask).count_ones(),
577 });
578 }
579 if fam == libc::AF_INET6 {
580 let sin = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_in6) };
582 let ip = std::net::Ipv6Addr::from(sin.sin6_addr.s6_addr);
583 let mask = if ifa.ifa_netmask.is_null() {
584 std::net::Ipv6Addr::UNSPECIFIED
585 } else {
586 let m = unsafe { &*(ifa.ifa_netmask as *const libc::sockaddr_in6) };
588 std::net::Ipv6Addr::from(m.sin6_addr.s6_addr)
589 };
590 let prefix: u32 = mask.octets().iter().map(|b| b.count_ones()).sum();
591 return Some(IfAddr {
592 address: ip.to_string(),
593 netmask: mask.to_string(),
594 family: "IPv6",
595 internal,
596 prefix,
597 });
598 }
599 None
600}