/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/os/lib.rs
973 строки
26 KB
Nathan Whitaker
fix(os): serialize process environment updates (#36074)
10 авг 2026, 22:22
Не верифицирован
10 авг 2026, 22:22
19c25e2
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use std::collections::HashMap; use std::env; use std::ffi::OsString; use std::ops::ControlFlow; use std::sync::Arc; use std::sync::LazyLock; use std::sync::Mutex; use std::sync::MutexGuard; use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering; use deno_core::OpState; use deno_core::ToV8; use deno_core::op2; use deno_core::v8; use deno_path_util::normalize_path; use deno_permissions::PermissionCheckError; use deno_permissions::PermissionState; use deno_permissions::PermissionsContainer; mod ops; pub mod sys_info; pub use ops::signal::SignalError; // The full list of environment variables supported by Node.js is available // at https://nodejs.org/api/cli.html#environment-variables const SORTED_NODE_ENV_VAR_ALLOWLIST: [&str; 4] = ["FORCE_COLOR", "NODE_DEBUG", "NODE_OPTIONS", "NO_COLOR"]; #[derive(Clone, Default)] pub struct ExitCode(Arc<AtomicI32>); impl ExitCode { pub fn get(&self) -> i32 { self.0.load(Ordering::Relaxed) } pub fn set(&mut self, code: i32) { self.0.store(code, Ordering::Relaxed); } } pub fn exit(code: i32) -> ! { deno_signals::run_exit(); #[allow( clippy::disallowed_methods, reason = "exit is the intended behavior" )] std::process::exit(code); } /// Callbacks to run before the process exits via `Deno.exit()`. /// /// Used to flush CPU profiling data and coverage data that would otherwise /// be lost when `Deno.exit()` calls `std::process::exit()` directly, /// bypassing normal cleanup in the worker's run loop. #[derive(Default)] pub struct OpExitCallbacks(Vec<Box<dyn FnMut()>>); impl OpExitCallbacks { pub fn push(&mut self, cb: Box<dyn FnMut()>) { self.0.push(cb); } fn run(&mut self) { for cb in self.0.iter_mut() { cb(); } } } /// Holds the main isolate's handle so that `op_exit` (i.e. `Deno.exit()`) can /// terminate the current isolate instead of the whole process. Installed by /// `deno run --watch` / `deno serve --watch` so a script calling `Deno.exit()` /// ends the current run without killing the file watcher. See issue #7590. pub struct WatcherExitHandle(pub v8::IsolateHandle); /// Marker set by `op_exit` when a [`WatcherExitHandle`] is present, recording /// that the script called `Deno.exit()`. The watcher reads this after the run /// to confirm the V8 termination it observes was caused by `Deno.exit()` rather /// than another error; the requested exit code is read separately from the /// worker's `ExitCode` (set by `Deno.exit()` before `op_exit` runs). #[derive(Clone, Copy, Debug)] pub struct WatcherExited; deno_core::extension!( deno_os, ops = [ op_env, op_exec_path, op_exit, op_delete_env, op_get_env, op_get_env_no_permission_check, op_gid, op_hostname, op_loadavg, op_network_interfaces, op_os_release, op_os_uptime, op_set_env, op_set_exit_code, op_get_exit_code, op_system_memory_info, op_uid, op_runtime_cpu_usage, op_runtime_memory_usage, ops::signal::op_signal_bind, ops::signal::op_signal_unbind, ops::signal::op_signal_poll, ], lazy_loaded_js = ["30_os.js", "40_signals.js"], options = { exit_code: Option<ExitCode>, }, state = |state, options| { if let Some(exit_code) = options.exit_code { state.put::<ExitCode>(exit_code); } } ); #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum OsError { #[class(inherit)] #[error(transparent)] Permission(#[from] PermissionCheckError), #[class("InvalidData")] #[error("File name or path {0:?} is not valid UTF-8")] InvalidUtf8(std::ffi::OsString), #[class(type)] #[error("Key is an empty string.")] EnvEmptyKey, #[class(type)] #[error("Key contains invalid characters: {0:?}")] EnvInvalidKey(String), #[class(type)] #[error("Value contains invalid characters: {0:?}")] EnvInvalidValue(String), #[class(inherit)] #[error(transparent)] Var(#[from] env::VarError), #[class(inherit)] #[error("{0}")] Io(#[from] std::io::Error), } #[op2] #[string] fn op_exec_path() -> Result<String, OsError> { let current_exe = env::current_exe().unwrap(); // normalize path so it doesn't include '.' or '..' components let path = normalize_path(Cow::Owned(current_exe)); path .into_owned() .into_os_string() .into_string() .map_err(OsError::InvalidUtf8) } static PROCESS_ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(Mutex::default); /// A guard for coordinated runtime access to the process environment. /// /// Runtime environment ops, Node dotenv loading, and watched dotenv reloads /// use this guard. It cannot synchronize other native code that accesses the /// process environment directly. pub struct ProcessEnvGuard { _guard: MutexGuard<'static, ()>, } impl ProcessEnvGuard { pub fn lock() -> Self { Self { _guard: PROCESS_ENV_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner), } } pub fn var(&self, key: &str) -> Result<String, env::VarError> { env::var(key) } pub fn var_os(&self, key: impl AsRef<std::ffi::OsStr>) -> Option<OsString> { env::var_os(key) } pub fn vars_os(&self) -> Vec<(OsString, OsString)> { env::vars_os().collect() } pub fn set_var( &self, key: impl AsRef<std::ffi::OsStr>, value: impl AsRef<std::ffi::OsStr>, ) { let key = key.as_ref(); // SAFETY: Deno exposes process environment mutation for compatibility. // This guard coordinates the runtime mutation paths with the native // timezone refresh below, but cannot coordinate arbitrary native readers. unsafe { env::set_var(key, value); } if key == "TZ" { refresh_timezone_from_process_env(); } } pub fn set_var_and_notify_timezone( &self, key: impl AsRef<std::ffi::OsStr>, value: impl AsRef<std::ffi::OsStr>, notify: impl FnOnce(), ) { let key = key.as_ref(); self.set_var(key, value); if key == "TZ" { notify(); } } pub fn remove_var(&self, key: impl AsRef<std::ffi::OsStr>) { let key = key.as_ref(); // SAFETY: Deno exposes process environment mutation for compatibility. // This guard coordinates the runtime mutation paths with the native // timezone refresh below, but cannot coordinate arbitrary native readers. unsafe { env::remove_var(key); } if key == "TZ" { refresh_timezone_from_process_env(); } } pub fn remove_var_and_notify_timezone( &self, key: impl AsRef<std::ffi::OsStr>, notify: impl FnOnce(), ) { let key = key.as_ref(); self.remove_var(key); if key == "TZ" { notify(); } } } fn refresh_timezone_from_process_env() { unsafe extern "C" { #[cfg(unix)] fn tzset(); #[cfg(windows)] fn _tzset(); } // SAFETY: The process environment guard is held while libc reloads its // timezone configuration. unsafe { #[cfg(unix)] tzset(); #[cfg(windows)] _tzset(); } } fn notify_date_time_configuration_change(isolate: &mut v8::Isolate) { isolate.date_time_configuration_change_notification( v8::TimeZoneDetection::Redetect, ); } #[op2(fast, stack_trace)] fn op_set_env( state: &mut OpState, scope: &mut v8::PinScope<'_, '_>, #[string] key: &str, #[string] value: &str, ) -> Result<(), OsError> { if check_env_with_maybe_exit(state, key)?.is_break() { return Ok(()); } if key.is_empty() { return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { return Err(OsError::EnvInvalidKey(key.to_string())); } if value.contains('\0') { return Err(OsError::EnvInvalidValue(value.to_string())); } let process_env = ProcessEnvGuard::lock(); process_env.set_var_and_notify_timezone(key, value, || { notify_date_time_configuration_change(scope); }); Ok(()) } fn check_env_with_maybe_exit( state: &mut OpState, key: &str, ) -> Result<ControlFlow<()>, PermissionCheckError> { match state.borrow_mut::<PermissionsContainer>().check_env(key) { Ok(()) => Ok(ControlFlow::Continue(())), Err(PermissionCheckError::PermissionDenied(err)) if err.state == PermissionState::Ignored => { Ok(ControlFlow::Break(())) } Err(err) => Err(err), } } #[op2(stack_trace)] #[serde] fn op_env( state: &mut OpState, ) -> Result<HashMap<String, String>, PermissionCheckError> { fn map_kv(kv: (OsString, OsString)) -> Option<(String, String)> { let key = kv.0.into_string().ok()?; // Skip keys that `Deno.env.get`/`set`/`delete` would reject, so that // enumeration stays consistent with the rest of the API. On Windows, // cmd.exe exposes hidden per-drive cwd variables such as `=C:` which // contain `=` and are not meant to be surfaced to users. if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return None; } let value = kv.1.into_string().ok()?; Some((key, value)) } let permissions_container = state.borrow::<PermissionsContainer>(); let grant_all = match permissions_container.check_env_all() { Ok(()) => true, Err(PermissionCheckError::PermissionDenied(err)) => match err.state { PermissionState::Granted | PermissionState::Prompt | PermissionState::Denied => return Err(err.into()), PermissionState::GrantedPartial | PermissionState::DeniedPartial | PermissionState::Ignored => false, }, Err(err) => return Err(err), }; let process_env = ProcessEnvGuard::lock(); Ok( process_env .vars_os() .into_iter() .filter_map(|kv| { let (k, v) = map_kv(kv)?; let state = if grant_all { PermissionState::Granted } else { permissions_container.query_env(Some(&k)) }; match state { PermissionState::Granted | PermissionState::GrantedPartial => { Some((k, v)) } PermissionState::Ignored | PermissionState::Prompt | PermissionState::Denied | PermissionState::DeniedPartial => None, } }) .collect(), ) } fn get_env_var(key: &str) -> Result<Option<String>, OsError> { if key.is_empty() { return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { return Err(OsError::EnvInvalidKey(key.to_string())); } let process_env = ProcessEnvGuard::lock(); let r = match process_env.var(key) { Err(env::VarError::NotPresent) => None, v => Some(v?), }; Ok(r) } #[op2] #[string] fn op_get_env_no_permission_check( #[string] key: &str, ) -> Result<Option<String>, OsError> { get_env_var(key) } #[op2(stack_trace)] #[string] fn op_get_env( state: &mut OpState, #[string] key: &str, ) -> Result<Option<String>, OsError> { let skip_permission_check = SORTED_NODE_ENV_VAR_ALLOWLIST.binary_search(&key).is_ok(); if !skip_permission_check && check_env_with_maybe_exit(state, key)?.is_break() { return Ok(None); } get_env_var(key) } #[op2(fast, stack_trace)] fn op_delete_env( state: &mut OpState, scope: &mut v8::PinScope<'_, '_>, #[string] key: &str, ) -> Result<(), OsError> { if check_env_with_maybe_exit(state, key)?.is_break() { return Ok(()); } if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(OsError::EnvInvalidKey(key.to_string())); } let process_env = ProcessEnvGuard::lock(); process_env.remove_var_and_notify_timezone(key, || { notify_date_time_configuration_change(scope); }); Ok(()) } #[op2(fast)] fn op_set_exit_code(state: &mut OpState, #[smi] code: i32) { if let Some(exit_code) = state.try_borrow_mut::<ExitCode>() { exit_code.set(code); } } #[op2(fast)] #[smi] fn op_get_exit_code(state: &mut OpState) -> i32 { state .try_borrow::<ExitCode>() .map(|e| e.get()) .unwrap_or_default() } #[op2(fast)] fn op_exit(state: &mut OpState) { // Under `deno run --watch`, terminate the current V8 isolate instead of // exiting the process so the file watcher survives and can restart the // script on the next file change. See issue #7590. // // We intentionally return before running `OpExitCallbacks` here: those flush // coverage/profiler data and notify the inspector as the process is about to // disappear. Under a watcher the process keeps running, so that teardown is // not wanted; the watcher run paths stop the coverage collector and profiler // themselves after the run completes. if let Some(handle) = state.try_borrow::<WatcherExitHandle>().map(|h| h.0.clone()) { state.put(WatcherExited); handle.terminate_execution(); return; } if let Some(cbs) = state.try_borrow_mut::<OpExitCallbacks>() { cbs.run(); } if let Some(exit_code) = state.try_borrow::<ExitCode>() { exit(exit_code.get()) } } #[cfg(test)] mod tests { use std::sync::mpsc; use super::PROCESS_ENV_LOCK; use super::ProcessEnvGuard; #[test] fn process_env_lock_is_held_through_timezone_notification() { let original_tz = ProcessEnvGuard::lock().var_os("TZ"); let (callback_started_tx, callback_started_rx) = mpsc::channel(); let (finish_callback_tx, finish_callback_rx) = mpsc::channel(); let setter = std::thread::spawn(move || { let process_env = ProcessEnvGuard::lock(); process_env.set_var_and_notify_timezone("TZ", "UTC", || { callback_started_tx.send(()).unwrap(); finish_callback_rx.recv().unwrap(); }); }); callback_started_rx.recv().unwrap(); let (reader_started_tx, reader_started_rx) = mpsc::channel(); let reader = std::thread::spawn(move || { reader_started_tx.send(()).unwrap(); ProcessEnvGuard::lock().var("TZ").unwrap() }); reader_started_rx.recv().unwrap(); assert!(PROCESS_ENV_LOCK.try_lock().is_err()); finish_callback_tx.send(()).unwrap(); setter.join().unwrap(); assert_eq!(reader.join().unwrap(), "UTC"); let process_env = ProcessEnvGuard::lock(); match original_tz { Some(value) => process_env.set_var("TZ", value), None => process_env.remove_var("TZ"), } } } #[op2(stack_trace)] fn op_loadavg( state: &mut OpState, ) -> Result<(f64, f64, f64), PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("loadavg", "Deno.loadavg()")?; Ok(sys_info::loadavg()) } #[op2(stack_trace, stack_trace)] #[string] fn op_hostname(state: &mut OpState) -> Result<String, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("hostname", "Deno.hostname()")?; Ok(sys_info::hostname()) } #[op2(stack_trace)] #[string] fn op_os_release(state: &mut OpState) -> Result<String, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("osRelease", "Deno.osRelease()")?; Ok(sys_info::os_release()) } #[op2(stack_trace)] fn op_network_interfaces( state: &mut OpState, ) -> Result<Vec<NetworkInterface>, OsError> { state .borrow_mut::<PermissionsContainer>() .check_sys("networkInterfaces", "Deno.networkInterfaces()")?; Ok(netif::up()?.map(NetworkInterface::from).collect()) } #[derive(ToV8)] struct NetworkInterface { family: &'static str, name: String, address: String, netmask: String, scopeid: Option<u32>, cidr: String, mac: String, } impl From<netif::Interface> for NetworkInterface { fn from(ifa: netif::Interface) -> Self { let family = match ifa.address() { std::net::IpAddr::V4(_) => "IPv4", std::net::IpAddr::V6(_) => "IPv6", }; let (address, range) = ifa.cidr(); let cidr = format!("{address:?}/{range}"); let name = ifa.name().to_owned(); let address = format!("{:?}", ifa.address()); let netmask = format!("{:?}", ifa.netmask()); let scopeid = ifa.scope_id(); let [b0, b1, b2, b3, b4, b5] = ifa.mac(); let mac = format!("{b0:02x}:{b1:02x}:{b2:02x}:{b3:02x}:{b4:02x}:{b5:02x}"); Self { family, name, address, netmask, scopeid, cidr, mac, } } } #[op2(stack_trace)] fn op_system_memory_info( state: &mut OpState, ) -> Result<Option<sys_info::MemInfo>, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?; Ok(sys_info::mem_info()) } #[cfg(not(windows))] #[op2(stack_trace)] #[smi] fn op_gid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("gid", "Deno.gid()")?; // TODO(bartlomieju): #[allow(clippy::undocumented_unsafe_blocks, reason = "libc call")] unsafe { Ok(Some(libc::getgid())) } } #[cfg(windows)] #[op2(stack_trace)] #[smi] fn op_gid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("gid", "Deno.gid()")?; Ok(None) } #[cfg(not(windows))] #[op2(stack_trace)] #[smi] fn op_uid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("uid", "Deno.uid()")?; // TODO(bartlomieju): #[allow(clippy::undocumented_unsafe_blocks, reason = "libc call")] unsafe { Ok(Some(libc::getuid())) } } #[cfg(windows)] #[op2(stack_trace)] #[smi] fn op_uid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("uid", "Deno.uid()")?; Ok(None) } #[op2(fast)] fn op_runtime_cpu_usage(#[buffer] out: &mut [f64]) { if out.len() < 2 { return; } let (sys, user) = get_cpu_usage(); out[0] = sys.as_micros() as f64; out[1] = user.as_micros() as f64; } #[cfg(unix)] fn get_cpu_usage() -> (std::time::Duration, std::time::Duration) { let mut rusage = std::mem::MaybeUninit::uninit(); // Uses POSIX getrusage from libc // to retrieve user and system times // SAFETY: libc call let ret = unsafe { libc::getrusage(libc::RUSAGE_SELF, rusage.as_mut_ptr()) }; if ret != 0 { return Default::default(); } // SAFETY: already checked the result let rusage = unsafe { rusage.assume_init() }; let sys = std::time::Duration::from_micros(rusage.ru_stime.tv_usec as u64) + std::time::Duration::from_secs(rusage.ru_stime.tv_sec as u64); let user = std::time::Duration::from_micros(rusage.ru_utime.tv_usec as u64) + std::time::Duration::from_secs(rusage.ru_utime.tv_sec as u64); (sys, user) } #[cfg(windows)] fn get_cpu_usage() -> (std::time::Duration, std::time::Duration) { use windows_sys::Win32::Foundation::FALSE; use windows_sys::Win32::Foundation::FILETIME; use windows_sys::Win32::Foundation::SYSTEMTIME; use windows_sys::Win32::Foundation::TRUE; use windows_sys::Win32::System::Threading::GetCurrentProcess; use windows_sys::Win32::System::Threading::GetProcessTimes; use windows_sys::Win32::System::Time::FileTimeToSystemTime; fn convert_system_time(system_time: SYSTEMTIME) -> std::time::Duration { std::time::Duration::from_secs( system_time.wHour as u64 * 3600 + system_time.wMinute as u64 * 60 + system_time.wSecond as u64, ) + std::time::Duration::from_millis(system_time.wMilliseconds as u64) } let mut creation_time = std::mem::MaybeUninit::<FILETIME>::uninit(); let mut exit_time = std::mem::MaybeUninit::<FILETIME>::uninit(); let mut kernel_time = std::mem::MaybeUninit::<FILETIME>::uninit(); let mut user_time = std::mem::MaybeUninit::<FILETIME>::uninit(); // SAFETY: Win32 calls let ret = unsafe { GetProcessTimes( GetCurrentProcess(), creation_time.as_mut_ptr(), exit_time.as_mut_ptr(), kernel_time.as_mut_ptr(), user_time.as_mut_ptr(), ) }; if ret != TRUE { return std::default::Default::default(); } let mut kernel_system_time = std::mem::MaybeUninit::<SYSTEMTIME>::uninit(); let mut user_system_time = std::mem::MaybeUninit::<SYSTEMTIME>::uninit(); // SAFETY: convert to system time unsafe { let sys_ret = FileTimeToSystemTime( kernel_time.assume_init_mut(), kernel_system_time.as_mut_ptr(), ); let user_ret = FileTimeToSystemTime( user_time.assume_init_mut(), user_system_time.as_mut_ptr(), ); match (sys_ret, user_ret) { (TRUE, TRUE) => ( convert_system_time(kernel_system_time.assume_init()), convert_system_time(user_system_time.assume_init()), ), (TRUE, FALSE) => ( convert_system_time(kernel_system_time.assume_init()), Default::default(), ), (FALSE, TRUE) => ( Default::default(), convert_system_time(user_system_time.assume_init()), ), (_, _) => Default::default(), } } } #[cfg(not(any(windows, unix)))] fn get_cpu_usage() -> (std::time::Duration, std::time::Duration) { Default::default() } #[op2(fast)] fn op_runtime_memory_usage( scope: &mut v8::PinScope<'_, '_>, #[buffer] out: &mut [f64], ) { if out.len() < 4 { return; } let s = scope.get_heap_statistics(); let (rss, heap_total, heap_used, external) = ( rss(), s.total_heap_size() as u64, s.used_heap_size() as u64, s.external_memory() as u64, ); out[0] = rss as f64; out[1] = heap_total as f64; out[2] = heap_used as f64; out[3] = external as f64; } #[cfg(any(target_os = "android", target_os = "linux"))] fn rss() -> u64 { // Inspired by https://github.com/Arc-blroth/memory-stats/blob/5364d0d09143de2a470d33161b2330914228fde9/src/linux.rs // Extracts a positive integer from a string that // may contain leading spaces and trailing chars. // Returns the extracted number and the index of // the next character in the string. fn scan_int(string: &str) -> (u64, usize) { let mut out = 0; let mut idx = 0; let mut chars = string.chars().peekable(); while let Some(' ') = chars.next_if_eq(&' ') { idx += 1; } for n in chars { idx += 1; if n.is_ascii_digit() { out *= 10; out += n as u64 - '0' as u64; } else { break; } } (out, idx) } #[allow( clippy::disallowed_methods, reason = "reading /proc/self requires direct fs access" )] let statm_content = if let Ok(c) = std::fs::read_to_string("/proc/self/statm") { c } else { return 0; }; // statm returns the virtual size and rss, in // multiples of the page size, as the first // two columns of output. // SAFETY: libc call let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; if page_size < 0 { return 0; } let (_total_size_pages, idx) = scan_int(&statm_content); let (total_rss_pages, _) = scan_int(&statm_content[idx..]); total_rss_pages * page_size as u64 } #[cfg(target_os = "macos")] fn rss() -> u64 { // Inspired by https://github.com/Arc-blroth/memory-stats/blob/5364d0d09143de2a470d33161b2330914228fde9/src/darwin.rs let mut task_info = std::mem::MaybeUninit::<libc::mach_task_basic_info_data_t>::uninit(); let mut count = libc::MACH_TASK_BASIC_INFO_COUNT; // SAFETY: libc calls let r = unsafe { unsafe extern "C" { static mut mach_task_self_: std::ffi::c_uint; } libc::task_info( mach_task_self_, libc::MACH_TASK_BASIC_INFO, task_info.as_mut_ptr() as libc::task_info_t, &mut count as *mut libc::mach_msg_type_number_t, ) }; // According to libuv this should never fail assert_eq!(r, libc::KERN_SUCCESS); // SAFETY: we just asserted that it was success let task_info = unsafe { task_info.assume_init() }; task_info.resident_size } #[cfg(target_os = "openbsd")] fn rss() -> u64 { // Uses OpenBSD's KERN_PROC_PID sysctl(2) // to retrieve information about the current // process, part of which is the RSS (p_vm_rssize) // SAFETY: libc call (get PID of own process) let pid = unsafe { libc::getpid() }; // SAFETY: libc call (get system page size) let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64; // KERN_PROC_PID returns a struct libc::kinfo_proc let mut kinfoproc = std::mem::MaybeUninit::<libc::kinfo_proc>::uninit(); let mut size = std::mem::size_of_val(&kinfoproc) as libc::size_t; let mut mib = [ libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid, // mib is an array of integers, size is of type size_t // conversion is safe, because the size of a libc::kinfo_proc // structure will not exceed i32::MAX size.try_into().unwrap(), 1, ]; // SAFETY: libc call, mib has been statically initialized, // kinfoproc is a valid pointer to a libc::kinfo_proc struct let res = unsafe { libc::sysctl( mib.as_mut_ptr(), mib.len() as _, kinfoproc.as_mut_ptr() as *mut libc::c_void, &mut size, std::ptr::null_mut(), 0, ) }; if res == 0 { // SAFETY: sysctl returns 0 on success and kinfoproc is initialized // p_vm_rssize contains size in pages -> multiply with pagesize to // get size in bytes. pagesize * unsafe { (*kinfoproc.as_mut_ptr()).p_vm_rssize as u64 } } else { 0 } } #[cfg(windows)] fn rss() -> u64 { use windows_sys::Win32::Foundation::FALSE; use windows_sys::Win32::System::ProcessStatus::GetProcessMemoryInfo; use windows_sys::Win32::System::ProcessStatus::PROCESS_MEMORY_COUNTERS; use windows_sys::Win32::System::Threading::GetCurrentProcess; // SAFETY: Win32 calls unsafe { // this handle is a constant—no need to close it let current_process = GetCurrentProcess(); let mut pmc: PROCESS_MEMORY_COUNTERS = std::mem::zeroed(); if GetProcessMemoryInfo( current_process, &mut pmc, std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32, ) != FALSE { pmc.WorkingSetSize as u64 } else { 0 } } } fn os_uptime(state: &mut OpState) -> Result<u64, PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_sys("osUptime", "Deno.osUptime()")?; Ok(sys_info::os_uptime()) } #[op2(fast, stack_trace)] #[number] fn op_os_uptime(state: &mut OpState) -> Result<u64, PermissionCheckError> { os_uptime(state) } #[cfg(test)] mod test { use crate::SORTED_NODE_ENV_VAR_ALLOWLIST; #[test] fn ensure_node_env_var_list_sorted() { // ensure this is sorted for binary search let mut items = SORTED_NODE_ENV_VAR_ALLOWLIST.to_vec(); items.sort(); assert_eq!(items, SORTED_NODE_ENV_VAR_ALLOWLIST); } }