deno_runtime/ops/os/
mod.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2
3use 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(&current_exe, "exec_path", "Deno.execPath()")?;
97  // normalize path so it doesn't include '.' or '..' components
98  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  // TODO(bartlomieju):
298  #[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  // TODO(bartlomieju):
326  #[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// HeapStats stores values from a isolate.get_heap_statistics() call
345#[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  // Inspired by https://github.com/Arc-blroth/memory-stats/blob/5364d0d09143de2a470d33161b2330914228fde9/src/linux.rs
370
371  // Extracts a positive integer from a string that
372  // may contain leading spaces and trailing chars.
373  // Returns the extracted number and the index of
374  // the next character in the string.
375  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  // statm returns the virtual size and rss, in
403  // multiples of the page size, as the first
404  // two columns of output.
405  // SAFETY: libc call
406  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  // Inspired by https://github.com/Arc-blroth/memory-stats/blob/5364d0d09143de2a470d33161b2330914228fde9/src/darwin.rs
421
422  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  // SAFETY: libc calls
426  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  // According to libuv this should never fail
435  assert_eq!(r, libc::KERN_SUCCESS);
436  // SAFETY: we just asserted that it was success
437  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  // Uses OpenBSD's KERN_PROC_PID sysctl(2)
444  // to retrieve information about the current
445  // process, part of which is the RSS (p_vm_rssize)
446
447  // SAFETY: libc call (get PID of own process)
448  let pid = unsafe { libc::getpid() };
449  // SAFETY: libc call (get system page size)
450  let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
451  // KERN_PROC_PID returns a struct libc::kinfo_proc
452  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    // mib is an array of integers, size is of type size_t
460    // conversion is safe, because the size of a libc::kinfo_proc
461    // structure will not exceed i32::MAX
462    size.try_into().unwrap(),
463    1,
464  ];
465  // SAFETY: libc call, mib has been statically initialized,
466  // kinfoproc is a valid pointer to a libc::kinfo_proc struct
467  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    // SAFETY: sysctl returns 0 on success and kinfoproc is initialized
480    // p_vm_rssize contains size in pages -> multiply with pagesize to
481    // get size in bytes.
482    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  // SAFETY: winapi calls
497  unsafe {
498    // this handle is a constant—no need to close it
499    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}