Skip to main content

nodejs/stdlib/
os.rs

1//! Node `os` module. Values that Node derives from the host (platform, arch,
2//! hostname, home/tmp dirs, endianness, EOL) are returned faithfully; the
3//! machine-specific numeric readings (`cpus`, `totalmem`, `freemem`, `loadavg`,
4//! `uptime`) return best-effort placeholders (not fuzzed — they vary per host on
5//! reference Node too).
6
7use 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
34/// `os.EOL` constant.
35pub 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        // os.constants.signals (POSIX signal numbers) + priority levels.
40        "constants" => Some(with_host(|h| {
41            let mut signals = indexmap::IndexMap::new();
42            for (k, v) in [
43                ("SIGHUP", 1),
44                ("SIGINT", 2),
45                ("SIGQUIT", 3),
46                ("SIGILL", 4),
47                ("SIGTRAP", 5),
48                ("SIGABRT", 6),
49                ("SIGBUS", 10),
50                ("SIGFPE", 8),
51                ("SIGKILL", 9),
52                ("SIGUSR1", 30),
53                ("SIGSEGV", 11),
54                ("SIGUSR2", 31),
55                ("SIGPIPE", 13),
56                ("SIGALRM", 14),
57                ("SIGTERM", 15),
58                ("SIGCHLD", 20),
59                ("SIGCONT", 19),
60                ("SIGSTOP", 17),
61                ("SIGTSTP", 18),
62                ("SIGWINCH", 28),
63            ] {
64                signals.insert(k.to_string(), Value::Float(v as f64));
65            }
66            let sig = h.new_object(signals);
67            let mut priority = indexmap::IndexMap::new();
68            for (k, v) in [
69                ("PRIORITY_LOW", 19),
70                ("PRIORITY_BELOW_NORMAL", 10),
71                ("PRIORITY_NORMAL", 0),
72                ("PRIORITY_ABOVE_NORMAL", -7),
73                ("PRIORITY_HIGH", -14),
74                ("PRIORITY_HIGHEST", -20),
75            ] {
76                priority.insert(k.to_string(), Value::Float(v as f64));
77            }
78            let prio = h.new_object(priority);
79            let mut m = indexmap::IndexMap::new();
80            m.insert("signals".to_string(), sig);
81            m.insert("priority".to_string(), prio);
82            h.new_object(m)
83        })),
84        _ => None,
85    }
86}
87
88/// Node's `process.platform`/`os.platform()` string for the build target.
89pub fn platform() -> &'static str {
90    match std::env::consts::OS {
91        "macos" => "darwin",
92        "windows" => "win32",
93        other => other,
94    }
95}
96
97/// Node's `os.arch()`/`process.arch` string for the build target.
98pub fn arch() -> &'static str {
99    match std::env::consts::ARCH {
100        "aarch64" => "arm64",
101        "x86_64" => "x64",
102        other => other,
103    }
104}
105
106pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
107    let s = |v: &str| Ok(with_host(|h| h.new_str(v)));
108    Some(match method {
109        "platform" => s(platform()),
110        "arch" => s(arch()),
111        "machine" => s(std::env::consts::ARCH),
112        "type" => s(match std::env::consts::OS {
113            "macos" => "Darwin",
114            "linux" => "Linux",
115            "windows" => "Windows_NT",
116            other => other,
117        }),
118        "release" => s(""),
119        "version" => s(""),
120        "hostname" => s(&hostname()),
121        "homedir" => s(&dirs::home_dir()
122            .map(|p| p.to_string_lossy().into_owned())
123            .unwrap_or_default()),
124        "tmpdir" => s(std::env::temp_dir().to_string_lossy().trim_end_matches('/')),
125        "endianness" => s(if cfg!(target_endian = "big") {
126            "BE"
127        } else {
128            "LE"
129        }),
130        "totalmem" => Ok(Value::Float(0.0)),
131        "freemem" => Ok(Value::Float(0.0)),
132        "uptime" => Ok(Value::Float(0.0)),
133        "cpus" => Ok(with_host(|h| h.new_array(Vec::new()))),
134        // Real 1/5/15-minute load averages via `getloadavg(3)`.
135        "loadavg" => {
136            let mut avg = [0f64; 3];
137            // SAFETY: writes at most 3 doubles into a 3-element buffer.
138            let n = unsafe { libc::getloadavg(avg.as_mut_ptr(), 3) };
139            let items: Vec<Value> = if n == 3 {
140                avg.iter().map(|v| Value::Float(*v)).collect()
141            } else {
142                vec![Value::Float(0.0); 3]
143            };
144            Ok(with_host(|h| h.new_array(items)))
145        }
146        "networkInterfaces" => Ok(with_host(|h| h.new_object(IndexMap::new()))),
147        "userInfo" => Ok(user_info()),
148        // Logical CPU count (Node uses libuv's available parallelism).
149        "availableParallelism" => {
150            let n = std::thread::available_parallelism()
151                .map(|n| n.get())
152                .unwrap_or(1);
153            Ok(Value::Float(n as f64))
154        }
155        // `os.getPriority([pid])` — the nice value of `pid` (0 = current process).
156        "getPriority" => {
157            let pid = if args.is_empty() {
158                0
159            } else {
160                super::arg_num(args, 0) as i32
161            };
162            // SAFETY: pure query; PRIO_PROCESS with a pid.
163            let prio = unsafe { libc::getpriority(libc::PRIO_PROCESS as _, pid as _) };
164            Ok(Value::Float(prio as f64))
165        }
166        // `os.setPriority([pid, ]priority)` — best-effort (needs privilege to lower
167        // the nice value); returns undefined.
168        "setPriority" => {
169            let (pid, prio) = if args.len() >= 2 {
170                (
171                    super::arg_num(args, 0) as i32,
172                    super::arg_num(args, 1) as i32,
173                )
174            } else {
175                (0, super::arg_num(args, 0) as i32)
176            };
177            // SAFETY: PRIO_PROCESS with a pid and nice value; failure returns -1.
178            unsafe {
179                libc::setpriority(libc::PRIO_PROCESS as _, pid as _, prio as _);
180            }
181            Ok(Value::Undef)
182        }
183        _ => return None,
184    })
185}
186
187fn hostname() -> String {
188    std::process::Command::new("hostname")
189        .output()
190        .ok()
191        .and_then(|o| String::from_utf8(o.stdout).ok())
192        .map(|s| s.trim().to_string())
193        .unwrap_or_default()
194}
195
196fn user_info() -> Value {
197    with_host(|h| {
198        let mut m = IndexMap::new();
199        let user = std::env::var("USER")
200            .or_else(|_| std::env::var("USERNAME"))
201            .unwrap_or_default();
202        let home = dirs::home_dir()
203            .map(|p| p.to_string_lossy().into_owned())
204            .unwrap_or_default();
205        let shell = std::env::var("SHELL").unwrap_or_default();
206        m.insert("username".into(), h.new_str(user));
207        m.insert("homedir".into(), h.new_str(home));
208        m.insert("shell".into(), h.new_str(shell));
209        m.insert("uid".into(), Value::Float(-1.0));
210        m.insert("gid".into(), Value::Float(-1.0));
211        h.new_object(m)
212    })
213}