1use crate::sys_info;
4use crate::worker::ExitCode;
5use deno_core::op2;
6use deno_core::v8;
7use deno_core::OpState;
8use deno_node::NODE_ENV_VAR_ALLOWLIST;
9use deno_path_util::normalize_path;
10use deno_permissions::PermissionsContainer;
11use serde::Serialize;
12use std::collections::HashMap;
13use std::env;
14
15deno_core::extension!(
16 deno_os,
17 ops = [
18 op_env,
19 op_exec_path,
20 op_exit,
21 op_delete_env,
22 op_get_env,
23 op_gid,
24 op_hostname,
25 op_loadavg,
26 op_network_interfaces,
27 op_os_release,
28 op_os_uptime,
29 op_set_env,
30 op_set_exit_code,
31 op_get_exit_code,
32 op_system_memory_info,
33 op_uid,
34 op_runtime_memory_usage,
35 ],
36 options = {
37 exit_code: ExitCode,
38 },
39 state = |state, options| {
40 state.put::<ExitCode>(options.exit_code);
41 },
42);
43
44deno_core::extension!(
45 deno_os_worker,
46 ops = [
47 op_env,
48 op_exec_path,
49 op_exit,
50 op_delete_env,
51 op_get_env,
52 op_gid,
53 op_hostname,
54 op_loadavg,
55 op_network_interfaces,
56 op_os_release,
57 op_os_uptime,
58 op_set_env,
59 op_set_exit_code,
60 op_get_exit_code,
61 op_system_memory_info,
62 op_uid,
63 op_runtime_memory_usage,
64 ],
65 middleware = |op| match op.name {
66 "op_exit" | "op_set_exit_code" | "op_get_exit_code" =>
67 op.with_implementation_from(&deno_core::op_void_sync()),
68 _ => op,
69 },
70);
71
72#[derive(Debug, thiserror::Error)]
73pub enum OsError {
74 #[error(transparent)]
75 Permission(#[from] deno_permissions::PermissionCheckError),
76 #[error("File name or path {0:?} is not valid UTF-8")]
77 InvalidUtf8(std::ffi::OsString),
78 #[error("Key is an empty string.")]
79 EnvEmptyKey,
80 #[error("Key contains invalid characters: {0:?}")]
81 EnvInvalidKey(String),
82 #[error("Value contains invalid characters: {0:?}")]
83 EnvInvalidValue(String),
84 #[error(transparent)]
85 Var(#[from] env::VarError),
86 #[error("{0}")]
87 Io(#[from] std::io::Error),
88}
89
90#[op2(stack_trace)]
91#[string]
92fn op_exec_path(state: &mut OpState) -> Result<String, OsError> {
93 let current_exe = env::current_exe().unwrap();
94 state
95 .borrow_mut::<PermissionsContainer>()
96 .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()")?;
97 let path = normalize_path(current_exe);
99
100 path
101 .into_os_string()
102 .into_string()
103 .map_err(OsError::InvalidUtf8)
104}
105
106#[op2(fast, stack_trace)]
107fn op_set_env(
108 state: &mut OpState,
109 #[string] key: &str,
110 #[string] value: &str,
111) -> Result<(), OsError> {
112 state.borrow_mut::<PermissionsContainer>().check_env(key)?;
113 if key.is_empty() {
114 return Err(OsError::EnvEmptyKey);
115 }
116 if key.contains(&['=', '\0'] as &[char]) {
117 return Err(OsError::EnvInvalidKey(key.to_string()));
118 }
119 if value.contains('\0') {
120 return Err(OsError::EnvInvalidValue(value.to_string()));
121 }
122 env::set_var(key, value);
123 Ok(())
124}
125
126#[op2(stack_trace)]
127#[serde]
128fn op_env(
129 state: &mut OpState,
130) -> Result<HashMap<String, String>, deno_core::error::AnyError> {
131 state.borrow_mut::<PermissionsContainer>().check_env_all()?;
132 Ok(env::vars().collect())
133}
134
135#[op2(stack_trace)]
136#[string]
137fn op_get_env(
138 state: &mut OpState,
139 #[string] key: String,
140) -> Result<Option<String>, OsError> {
141 let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key);
142
143 if !skip_permission_check {
144 state.borrow_mut::<PermissionsContainer>().check_env(&key)?;
145 }
146
147 if key.is_empty() {
148 return Err(OsError::EnvEmptyKey);
149 }
150
151 if key.contains(&['=', '\0'] as &[char]) {
152 return Err(OsError::EnvInvalidKey(key.to_string()));
153 }
154
155 let r = match env::var(key) {
156 Err(env::VarError::NotPresent) => None,
157 v => Some(v?),
158 };
159 Ok(r)
160}
161
162#[op2(fast, stack_trace)]
163fn op_delete_env(
164 state: &mut OpState,
165 #[string] key: String,
166) -> Result<(), OsError> {
167 state.borrow_mut::<PermissionsContainer>().check_env(&key)?;
168 if key.is_empty() || key.contains(&['=', '\0'] as &[char]) {
169 return Err(OsError::EnvInvalidKey(key.to_string()));
170 }
171 env::remove_var(key);
172 Ok(())
173}
174
175#[op2(fast)]
176fn op_set_exit_code(state: &mut OpState, #[smi] code: i32) {
177 state.borrow_mut::<ExitCode>().set(code);
178}
179
180#[op2(fast)]
181#[smi]
182fn op_get_exit_code(state: &mut OpState) -> i32 {
183 state.borrow_mut::<ExitCode>().get()
184}
185
186#[op2(fast)]
187fn op_exit(state: &mut OpState) {
188 let code = state.borrow::<ExitCode>().get();
189 crate::exit(code)
190}
191
192#[op2(stack_trace)]
193#[serde]
194fn op_loadavg(
195 state: &mut OpState,
196) -> Result<(f64, f64, f64), deno_core::error::AnyError> {
197 state
198 .borrow_mut::<PermissionsContainer>()
199 .check_sys("loadavg", "Deno.loadavg()")?;
200 Ok(sys_info::loadavg())
201}
202
203#[op2(stack_trace, stack_trace)]
204#[string]
205fn op_hostname(
206 state: &mut OpState,
207) -> Result<String, deno_core::error::AnyError> {
208 state
209 .borrow_mut::<PermissionsContainer>()
210 .check_sys("hostname", "Deno.hostname()")?;
211 Ok(sys_info::hostname())
212}
213
214#[op2(stack_trace)]
215#[string]
216fn op_os_release(
217 state: &mut OpState,
218) -> Result<String, deno_core::error::AnyError> {
219 state
220 .borrow_mut::<PermissionsContainer>()
221 .check_sys("osRelease", "Deno.osRelease()")?;
222 Ok(sys_info::os_release())
223}
224
225#[op2(stack_trace)]
226#[serde]
227fn op_network_interfaces(
228 state: &mut OpState,
229) -> Result<Vec<NetworkInterface>, OsError> {
230 state
231 .borrow_mut::<PermissionsContainer>()
232 .check_sys("networkInterfaces", "Deno.networkInterfaces()")?;
233 Ok(netif::up()?.map(NetworkInterface::from).collect())
234}
235
236#[derive(serde::Serialize)]
237struct NetworkInterface {
238 family: &'static str,
239 name: String,
240 address: String,
241 netmask: String,
242 scopeid: Option<u32>,
243 cidr: String,
244 mac: String,
245}
246
247impl From<netif::Interface> for NetworkInterface {
248 fn from(ifa: netif::Interface) -> Self {
249 let family = match ifa.address() {
250 std::net::IpAddr::V4(_) => "IPv4",
251 std::net::IpAddr::V6(_) => "IPv6",
252 };
253
254 let (address, range) = ifa.cidr();
255 let cidr = format!("{address:?}/{range}");
256
257 let name = ifa.name().to_owned();
258 let address = format!("{:?}", ifa.address());
259 let netmask = format!("{:?}", ifa.netmask());
260 let scopeid = ifa.scope_id();
261
262 let [b0, b1, b2, b3, b4, b5] = ifa.mac();
263 let mac = format!("{b0:02x}:{b1:02x}:{b2:02x}:{b3:02x}:{b4:02x}:{b5:02x}");
264
265 Self {
266 family,
267 name,
268 address,
269 netmask,
270 scopeid,
271 cidr,
272 mac,
273 }
274 }
275}
276
277#[op2(stack_trace)]
278#[serde]
279fn op_system_memory_info(
280 state: &mut OpState,
281) -> Result<Option<sys_info::MemInfo>, deno_core::error::AnyError> {
282 state
283 .borrow_mut::<PermissionsContainer>()
284 .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?;
285 Ok(sys_info::mem_info())
286}
287
288#[cfg(not(windows))]
289#[op2(stack_trace)]
290#[smi]
291fn op_gid(
292 state: &mut OpState,
293) -> Result<Option<u32>, deno_core::error::AnyError> {
294 state
295 .borrow_mut::<PermissionsContainer>()
296 .check_sys("gid", "Deno.gid()")?;
297 #[allow(clippy::undocumented_unsafe_blocks)]
299 unsafe {
300 Ok(Some(libc::getgid()))
301 }
302}
303
304#[cfg(windows)]
305#[op2(stack_trace)]
306#[smi]
307fn op_gid(
308 state: &mut OpState,
309) -> Result<Option<u32>, deno_core::error::AnyError> {
310 state
311 .borrow_mut::<PermissionsContainer>()
312 .check_sys("gid", "Deno.gid()")?;
313 Ok(None)
314}
315
316#[cfg(not(windows))]
317#[op2(stack_trace)]
318#[smi]
319fn op_uid(
320 state: &mut OpState,
321) -> Result<Option<u32>, deno_core::error::AnyError> {
322 state
323 .borrow_mut::<PermissionsContainer>()
324 .check_sys("uid", "Deno.uid()")?;
325 #[allow(clippy::undocumented_unsafe_blocks)]
327 unsafe {
328 Ok(Some(libc::getuid()))
329 }
330}
331
332#[cfg(windows)]
333#[op2(stack_trace)]
334#[smi]
335fn op_uid(
336 state: &mut OpState,
337) -> Result<Option<u32>, deno_core::error::AnyError> {
338 state
339 .borrow_mut::<PermissionsContainer>()
340 .check_sys("uid", "Deno.uid()")?;
341 Ok(None)
342}
343
344#[derive(Serialize)]
346#[serde(rename_all = "camelCase")]
347struct MemoryUsage {
348 rss: usize,
349 heap_total: usize,
350 heap_used: usize,
351 external: usize,
352}
353
354#[op2]
355#[serde]
356fn op_runtime_memory_usage(scope: &mut v8::HandleScope) -> MemoryUsage {
357 let mut s = v8::HeapStatistics::default();
358 scope.get_heap_statistics(&mut s);
359 MemoryUsage {
360 rss: rss(),
361 heap_total: s.total_heap_size(),
362 heap_used: s.used_heap_size(),
363 external: s.external_memory(),
364 }
365}
366
367#[cfg(any(target_os = "android", target_os = "linux"))]
368fn rss() -> usize {
369 fn scan_int(string: &str) -> (usize, usize) {
376 let mut out = 0;
377 let mut idx = 0;
378 let mut chars = string.chars().peekable();
379 while let Some(' ') = chars.next_if_eq(&' ') {
380 idx += 1;
381 }
382 for n in chars {
383 idx += 1;
384 if n.is_ascii_digit() {
385 out *= 10;
386 out += n as usize - '0' as usize;
387 } else {
388 break;
389 }
390 }
391 (out, idx)
392 }
393
394 #[allow(clippy::disallowed_methods)]
395 let statm_content = if let Ok(c) = std::fs::read_to_string("/proc/self/statm")
396 {
397 c
398 } else {
399 return 0;
400 };
401
402 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
407
408 if page_size < 0 {
409 return 0;
410 }
411
412 let (_total_size_pages, idx) = scan_int(&statm_content);
413 let (total_rss_pages, _) = scan_int(&statm_content[idx..]);
414
415 total_rss_pages * page_size as usize
416}
417
418#[cfg(target_os = "macos")]
419fn rss() -> usize {
420 let mut task_info =
423 std::mem::MaybeUninit::<libc::mach_task_basic_info_data_t>::uninit();
424 let mut count = libc::MACH_TASK_BASIC_INFO_COUNT;
425 let r = unsafe {
427 libc::task_info(
428 libc::mach_task_self(),
429 libc::MACH_TASK_BASIC_INFO,
430 task_info.as_mut_ptr() as libc::task_info_t,
431 &mut count as *mut libc::mach_msg_type_number_t,
432 )
433 };
434 assert_eq!(r, libc::KERN_SUCCESS);
436 let task_info = unsafe { task_info.assume_init() };
438 task_info.resident_size as usize
439}
440
441#[cfg(target_os = "openbsd")]
442fn rss() -> usize {
443 let pid = unsafe { libc::getpid() };
449 let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
451 let mut kinfoproc = std::mem::MaybeUninit::<libc::kinfo_proc>::uninit();
453 let mut size = std::mem::size_of_val(&kinfoproc) as libc::size_t;
454 let mut mib = [
455 libc::CTL_KERN,
456 libc::KERN_PROC,
457 libc::KERN_PROC_PID,
458 pid,
459 size.try_into().unwrap(),
463 1,
464 ];
465 let res = unsafe {
468 libc::sysctl(
469 mib.as_mut_ptr(),
470 mib.len() as _,
471 kinfoproc.as_mut_ptr() as *mut libc::c_void,
472 &mut size,
473 std::ptr::null_mut(),
474 0,
475 )
476 };
477
478 if res == 0 {
479 pagesize * unsafe { (*kinfoproc.as_mut_ptr()).p_vm_rssize as usize }
483 } else {
484 0
485 }
486}
487
488#[cfg(windows)]
489fn rss() -> usize {
490 use winapi::shared::minwindef::DWORD;
491 use winapi::shared::minwindef::FALSE;
492 use winapi::um::processthreadsapi::GetCurrentProcess;
493 use winapi::um::psapi::GetProcessMemoryInfo;
494 use winapi::um::psapi::PROCESS_MEMORY_COUNTERS;
495
496 unsafe {
498 let current_process = GetCurrentProcess();
500 let mut pmc: PROCESS_MEMORY_COUNTERS = std::mem::zeroed();
501
502 if GetProcessMemoryInfo(
503 current_process,
504 &mut pmc,
505 std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as DWORD,
506 ) != FALSE
507 {
508 pmc.WorkingSetSize
509 } else {
510 0
511 }
512 }
513}
514
515fn os_uptime(state: &mut OpState) -> Result<u64, deno_core::error::AnyError> {
516 state
517 .borrow_mut::<PermissionsContainer>()
518 .check_sys("osUptime", "Deno.osUptime()")?;
519 Ok(sys_info::os_uptime())
520}
521
522#[op2(fast, stack_trace)]
523#[number]
524fn op_os_uptime(
525 state: &mut OpState,
526) -> Result<u64, deno_core::error::AnyError> {
527 os_uptime(state)
528}