1use std::collections::HashMap;
4use std::collections::HashSet;
5use std::env;
6use std::sync::atomic::AtomicI32;
7use std::sync::atomic::Ordering;
8use std::sync::Arc;
9
10use deno_core::op2;
11use deno_core::v8;
12use deno_core::OpState;
13use deno_path_util::normalize_path;
14use deno_permissions::PermissionCheckError;
15use deno_permissions::PermissionsContainer;
16use once_cell::sync::Lazy;
17use serde::Serialize;
18
19mod ops;
20pub mod signal;
21pub mod sys_info;
22
23pub use ops::signal::SignalError;
24
25pub static NODE_ENV_VAR_ALLOWLIST: Lazy<HashSet<String>> = Lazy::new(|| {
26 let mut set = HashSet::new();
29 set.insert("NODE_DEBUG".to_string());
30 set.insert("NODE_OPTIONS".to_string());
31 set
32});
33
34#[derive(Clone, Default)]
35pub struct ExitCode(Arc<AtomicI32>);
36
37impl ExitCode {
38 pub fn get(&self) -> i32 {
39 self.0.load(Ordering::Relaxed)
40 }
41
42 pub fn set(&mut self, code: i32) {
43 self.0.store(code, Ordering::Relaxed);
44 }
45}
46
47pub fn exit(code: i32) -> ! {
48 deno_telemetry::flush();
49 #[allow(clippy::disallowed_methods)]
50 std::process::exit(code);
51}
52
53deno_core::extension!(
54 deno_os,
55 ops = [
56 op_env,
57 op_exec_path,
58 op_exit,
59 op_delete_env,
60 op_get_env,
61 op_gid,
62 op_hostname,
63 op_loadavg,
64 op_network_interfaces,
65 op_os_release,
66 op_os_uptime,
67 op_set_env,
68 op_set_exit_code,
69 op_get_exit_code,
70 op_system_memory_info,
71 op_uid,
72 op_runtime_cpu_usage,
73 op_runtime_memory_usage,
74 ops::signal::op_signal_bind,
75 ops::signal::op_signal_unbind,
76 ops::signal::op_signal_poll,
77 ],
78 esm = ["30_os.js", "40_signals.js"],
79 options = {
80 exit_code: ExitCode,
81 },
82 state = |state, options| {
83 state.put::<ExitCode>(options.exit_code);
84 #[cfg(unix)]
85 {
86 state.put(ops::signal::SignalState::default());
87 }
88 }
89);
90
91deno_core::extension!(
92 deno_os_worker,
93 ops = [
94 op_env,
95 op_exec_path,
96 op_exit,
97 op_delete_env,
98 op_get_env,
99 op_gid,
100 op_hostname,
101 op_loadavg,
102 op_network_interfaces,
103 op_os_release,
104 op_os_uptime,
105 op_set_env,
106 op_set_exit_code,
107 op_get_exit_code,
108 op_system_memory_info,
109 op_uid,
110 op_runtime_cpu_usage,
111 op_runtime_memory_usage,
112 ops::signal::op_signal_bind,
113 ops::signal::op_signal_unbind,
114 ops::signal::op_signal_poll,
115 ],
116 esm = ["30_os.js", "40_signals.js"],
117 middleware = |op| match op.name {
118 "op_exit" | "op_set_exit_code" | "op_get_exit_code" =>
119 op.with_implementation_from(&deno_core::op_void_sync()),
120 _ => op,
121 },
122 state = |state| {
123 #[cfg(unix)]
124 {
125 state.put(ops::signal::SignalState::default());
126 }
127 }
128);
129
130#[derive(Debug, thiserror::Error, deno_error::JsError)]
131pub enum OsError {
132 #[class(inherit)]
133 #[error(transparent)]
134 Permission(#[from] PermissionCheckError),
135 #[class("InvalidData")]
136 #[error("File name or path {0:?} is not valid UTF-8")]
137 InvalidUtf8(std::ffi::OsString),
138 #[class(type)]
139 #[error("Key is an empty string.")]
140 EnvEmptyKey,
141 #[class(type)]
142 #[error("Key contains invalid characters: {0:?}")]
143 EnvInvalidKey(String),
144 #[class(type)]
145 #[error("Value contains invalid characters: {0:?}")]
146 EnvInvalidValue(String),
147 #[class(inherit)]
148 #[error(transparent)]
149 Var(#[from] env::VarError),
150 #[class(inherit)]
151 #[error("{0}")]
152 Io(#[from] std::io::Error),
153}
154
155#[op2(stack_trace)]
156#[string]
157fn op_exec_path(state: &mut OpState) -> Result<String, OsError> {
158 let current_exe = env::current_exe().unwrap();
159 state
160 .borrow_mut::<PermissionsContainer>()
161 .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()")?;
162 let path = normalize_path(current_exe);
164
165 path
166 .into_os_string()
167 .into_string()
168 .map_err(OsError::InvalidUtf8)
169}
170
171fn dt_change_notif(isolate: &mut v8::Isolate, key: &str) {
172 extern "C" {
173 #[cfg(unix)]
174 fn tzset();
175
176 #[cfg(windows)]
177 fn _tzset();
178 }
179
180 if key == "TZ" {
181 unsafe {
183 #[cfg(unix)]
184 tzset();
185
186 #[cfg(windows)]
187 _tzset();
188 }
189
190 isolate.date_time_configuration_change_notification(
191 v8::TimeZoneDetection::Redetect,
192 );
193 }
194}
195
196#[op2(fast, stack_trace)]
197fn op_set_env(
198 state: &mut OpState,
199 scope: &mut v8::HandleScope,
200 #[string] key: &str,
201 #[string] value: &str,
202) -> Result<(), OsError> {
203 state.borrow_mut::<PermissionsContainer>().check_env(key)?;
204 if key.is_empty() {
205 return Err(OsError::EnvEmptyKey);
206 }
207 if key.contains(&['=', '\0'] as &[char]) {
208 return Err(OsError::EnvInvalidKey(key.to_string()));
209 }
210 if value.contains('\0') {
211 return Err(OsError::EnvInvalidValue(value.to_string()));
212 }
213
214 env::set_var(key, value);
215 dt_change_notif(scope, key);
216 Ok(())
217}
218
219#[op2(stack_trace)]
220#[serde]
221fn op_env(
222 state: &mut OpState,
223) -> Result<HashMap<String, String>, PermissionCheckError> {
224 state.borrow_mut::<PermissionsContainer>().check_env_all()?;
225 Ok(env::vars().collect())
226}
227
228#[op2(stack_trace)]
229#[string]
230fn op_get_env(
231 state: &mut OpState,
232 #[string] key: String,
233) -> Result<Option<String>, OsError> {
234 let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key);
235
236 if !skip_permission_check {
237 state.borrow_mut::<PermissionsContainer>().check_env(&key)?;
238 }
239
240 if key.is_empty() {
241 return Err(OsError::EnvEmptyKey);
242 }
243
244 if key.contains(&['=', '\0'] as &[char]) {
245 return Err(OsError::EnvInvalidKey(key.to_string()));
246 }
247
248 let r = match env::var(key) {
249 Err(env::VarError::NotPresent) => None,
250 v => Some(v?),
251 };
252 Ok(r)
253}
254
255#[op2(fast, stack_trace)]
256fn op_delete_env(
257 state: &mut OpState,
258 #[string] key: String,
259) -> Result<(), OsError> {
260 state.borrow_mut::<PermissionsContainer>().check_env(&key)?;
261 if key.is_empty() || key.contains(&['=', '\0'] as &[char]) {
262 return Err(OsError::EnvInvalidKey(key.to_string()));
263 }
264 env::remove_var(key);
265 Ok(())
266}
267
268#[op2(fast)]
269fn op_set_exit_code(state: &mut OpState, #[smi] code: i32) {
270 state.borrow_mut::<ExitCode>().set(code);
271}
272
273#[op2(fast)]
274#[smi]
275fn op_get_exit_code(state: &mut OpState) -> i32 {
276 state.borrow_mut::<ExitCode>().get()
277}
278
279#[op2(fast)]
280fn op_exit(state: &mut OpState) {
281 let code = state.borrow::<ExitCode>().get();
282 exit(code)
283}
284
285#[op2(stack_trace)]
286#[serde]
287fn op_loadavg(
288 state: &mut OpState,
289) -> Result<(f64, f64, f64), PermissionCheckError> {
290 state
291 .borrow_mut::<PermissionsContainer>()
292 .check_sys("loadavg", "Deno.loadavg()")?;
293 Ok(sys_info::loadavg())
294}
295
296#[op2(stack_trace, stack_trace)]
297#[string]
298fn op_hostname(state: &mut OpState) -> Result<String, PermissionCheckError> {
299 state
300 .borrow_mut::<PermissionsContainer>()
301 .check_sys("hostname", "Deno.hostname()")?;
302 Ok(sys_info::hostname())
303}
304
305#[op2(stack_trace)]
306#[string]
307fn op_os_release(state: &mut OpState) -> Result<String, PermissionCheckError> {
308 state
309 .borrow_mut::<PermissionsContainer>()
310 .check_sys("osRelease", "Deno.osRelease()")?;
311 Ok(sys_info::os_release())
312}
313
314#[op2(stack_trace)]
315#[serde]
316fn op_network_interfaces(
317 state: &mut OpState,
318) -> Result<Vec<NetworkInterface>, OsError> {
319 state
320 .borrow_mut::<PermissionsContainer>()
321 .check_sys("networkInterfaces", "Deno.networkInterfaces()")?;
322 Ok(netif::up()?.map(NetworkInterface::from).collect())
323}
324
325#[derive(Serialize)]
326struct NetworkInterface {
327 family: &'static str,
328 name: String,
329 address: String,
330 netmask: String,
331 scopeid: Option<u32>,
332 cidr: String,
333 mac: String,
334}
335
336impl From<netif::Interface> for NetworkInterface {
337 fn from(ifa: netif::Interface) -> Self {
338 let family = match ifa.address() {
339 std::net::IpAddr::V4(_) => "IPv4",
340 std::net::IpAddr::V6(_) => "IPv6",
341 };
342
343 let (address, range) = ifa.cidr();
344 let cidr = format!("{address:?}/{range}");
345
346 let name = ifa.name().to_owned();
347 let address = format!("{:?}", ifa.address());
348 let netmask = format!("{:?}", ifa.netmask());
349 let scopeid = ifa.scope_id();
350
351 let [b0, b1, b2, b3, b4, b5] = ifa.mac();
352 let mac = format!("{b0:02x}:{b1:02x}:{b2:02x}:{b3:02x}:{b4:02x}:{b5:02x}");
353
354 Self {
355 family,
356 name,
357 address,
358 netmask,
359 scopeid,
360 cidr,
361 mac,
362 }
363 }
364}
365
366#[op2(stack_trace)]
367#[serde]
368fn op_system_memory_info(
369 state: &mut OpState,
370) -> Result<Option<sys_info::MemInfo>, PermissionCheckError> {
371 state
372 .borrow_mut::<PermissionsContainer>()
373 .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?;
374 Ok(sys_info::mem_info())
375}
376
377#[cfg(not(windows))]
378#[op2(stack_trace)]
379#[smi]
380fn op_gid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> {
381 state
382 .borrow_mut::<PermissionsContainer>()
383 .check_sys("gid", "Deno.gid()")?;
384 #[allow(clippy::undocumented_unsafe_blocks)]
386 unsafe {
387 Ok(Some(libc::getgid()))
388 }
389}
390
391#[cfg(windows)]
392#[op2(stack_trace)]
393#[smi]
394fn op_gid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> {
395 state
396 .borrow_mut::<PermissionsContainer>()
397 .check_sys("gid", "Deno.gid()")?;
398 Ok(None)
399}
400
401#[cfg(not(windows))]
402#[op2(stack_trace)]
403#[smi]
404fn op_uid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> {
405 state
406 .borrow_mut::<PermissionsContainer>()
407 .check_sys("uid", "Deno.uid()")?;
408 #[allow(clippy::undocumented_unsafe_blocks)]
410 unsafe {
411 Ok(Some(libc::getuid()))
412 }
413}
414
415#[cfg(windows)]
416#[op2(stack_trace)]
417#[smi]
418fn op_uid(state: &mut OpState) -> Result<Option<u32>, PermissionCheckError> {
419 state
420 .borrow_mut::<PermissionsContainer>()
421 .check_sys("uid", "Deno.uid()")?;
422 Ok(None)
423}
424
425#[op2]
426#[serde]
427fn op_runtime_cpu_usage() -> (usize, usize) {
428 let (sys, user) = get_cpu_usage();
429 (sys.as_micros() as usize, user.as_micros() as usize)
430}
431
432#[cfg(unix)]
433fn get_cpu_usage() -> (std::time::Duration, std::time::Duration) {
434 let mut rusage = std::mem::MaybeUninit::uninit();
435
436 let ret = unsafe { libc::getrusage(libc::RUSAGE_SELF, rusage.as_mut_ptr()) };
440 if ret != 0 {
441 return Default::default();
442 }
443
444 let rusage = unsafe { rusage.assume_init() };
446
447 let sys = std::time::Duration::from_micros(rusage.ru_stime.tv_usec as u64)
448 + std::time::Duration::from_secs(rusage.ru_stime.tv_sec as u64);
449 let user = std::time::Duration::from_micros(rusage.ru_utime.tv_usec as u64)
450 + std::time::Duration::from_secs(rusage.ru_utime.tv_sec as u64);
451
452 (sys, user)
453}
454
455#[cfg(windows)]
456fn get_cpu_usage() -> (std::time::Duration, std::time::Duration) {
457 use winapi::shared::minwindef::FALSE;
458 use winapi::shared::minwindef::FILETIME;
459 use winapi::shared::minwindef::TRUE;
460 use winapi::um::minwinbase::SYSTEMTIME;
461 use winapi::um::processthreadsapi::GetCurrentProcess;
462 use winapi::um::processthreadsapi::GetProcessTimes;
463 use winapi::um::timezoneapi::FileTimeToSystemTime;
464
465 fn convert_system_time(system_time: SYSTEMTIME) -> std::time::Duration {
466 std::time::Duration::from_secs(
467 system_time.wHour as u64 * 3600
468 + system_time.wMinute as u64 * 60
469 + system_time.wSecond as u64,
470 ) + std::time::Duration::from_millis(system_time.wMilliseconds as u64)
471 }
472
473 let mut creation_time = std::mem::MaybeUninit::<FILETIME>::uninit();
474 let mut exit_time = std::mem::MaybeUninit::<FILETIME>::uninit();
475 let mut kernel_time = std::mem::MaybeUninit::<FILETIME>::uninit();
476 let mut user_time = std::mem::MaybeUninit::<FILETIME>::uninit();
477
478 let ret = unsafe {
480 GetProcessTimes(
481 GetCurrentProcess(),
482 creation_time.as_mut_ptr(),
483 exit_time.as_mut_ptr(),
484 kernel_time.as_mut_ptr(),
485 user_time.as_mut_ptr(),
486 )
487 };
488
489 if ret != TRUE {
490 return std::default::Default::default();
491 }
492
493 let mut kernel_system_time = std::mem::MaybeUninit::<SYSTEMTIME>::uninit();
494 let mut user_system_time = std::mem::MaybeUninit::<SYSTEMTIME>::uninit();
495
496 unsafe {
498 let sys_ret = FileTimeToSystemTime(
499 kernel_time.assume_init_mut(),
500 kernel_system_time.as_mut_ptr(),
501 );
502 let user_ret = FileTimeToSystemTime(
503 user_time.assume_init_mut(),
504 user_system_time.as_mut_ptr(),
505 );
506
507 match (sys_ret, user_ret) {
508 (TRUE, TRUE) => (
509 convert_system_time(kernel_system_time.assume_init()),
510 convert_system_time(user_system_time.assume_init()),
511 ),
512 (TRUE, FALSE) => (
513 convert_system_time(kernel_system_time.assume_init()),
514 Default::default(),
515 ),
516 (FALSE, TRUE) => (
517 Default::default(),
518 convert_system_time(user_system_time.assume_init()),
519 ),
520 (_, _) => Default::default(),
521 }
522 }
523}
524
525#[cfg(not(any(windows, unix)))]
526fn get_cpu_usage() -> (std::time::Duration, std::time::Duration) {
527 Default::default()
528}
529
530#[op2]
531#[serde]
532fn op_runtime_memory_usage(
533 scope: &mut v8::HandleScope,
534) -> (usize, usize, usize, usize) {
535 let s = scope.get_heap_statistics();
536
537 let (rss, heap_total, heap_used, external) = (
538 rss(),
539 s.total_heap_size(),
540 s.used_heap_size(),
541 s.external_memory(),
542 );
543
544 (rss, heap_total, heap_used, external)
545}
546
547#[cfg(any(target_os = "android", target_os = "linux"))]
548fn rss() -> usize {
549 fn scan_int(string: &str) -> (usize, usize) {
556 let mut out = 0;
557 let mut idx = 0;
558 let mut chars = string.chars().peekable();
559 while let Some(' ') = chars.next_if_eq(&' ') {
560 idx += 1;
561 }
562 for n in chars {
563 idx += 1;
564 if n.is_ascii_digit() {
565 out *= 10;
566 out += n as usize - '0' as usize;
567 } else {
568 break;
569 }
570 }
571 (out, idx)
572 }
573
574 #[allow(clippy::disallowed_methods)]
575 let statm_content = if let Ok(c) = std::fs::read_to_string("/proc/self/statm")
576 {
577 c
578 } else {
579 return 0;
580 };
581
582 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
587
588 if page_size < 0 {
589 return 0;
590 }
591
592 let (_total_size_pages, idx) = scan_int(&statm_content);
593 let (total_rss_pages, _) = scan_int(&statm_content[idx..]);
594
595 total_rss_pages * page_size as usize
596}
597
598#[cfg(target_os = "macos")]
599fn rss() -> usize {
600 let mut task_info =
603 std::mem::MaybeUninit::<libc::mach_task_basic_info_data_t>::uninit();
604 let mut count = libc::MACH_TASK_BASIC_INFO_COUNT;
605 let r = unsafe {
607 extern "C" {
608 static mut mach_task_self_: std::ffi::c_uint;
609 }
610 libc::task_info(
611 mach_task_self_,
612 libc::MACH_TASK_BASIC_INFO,
613 task_info.as_mut_ptr() as libc::task_info_t,
614 &mut count as *mut libc::mach_msg_type_number_t,
615 )
616 };
617 assert_eq!(r, libc::KERN_SUCCESS);
619 let task_info = unsafe { task_info.assume_init() };
621 task_info.resident_size as usize
622}
623
624#[cfg(target_os = "openbsd")]
625fn rss() -> usize {
626 let pid = unsafe { libc::getpid() };
632 let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
634 let mut kinfoproc = std::mem::MaybeUninit::<libc::kinfo_proc>::uninit();
636 let mut size = std::mem::size_of_val(&kinfoproc) as libc::size_t;
637 let mut mib = [
638 libc::CTL_KERN,
639 libc::KERN_PROC,
640 libc::KERN_PROC_PID,
641 pid,
642 size.try_into().unwrap(),
646 1,
647 ];
648 let res = unsafe {
651 libc::sysctl(
652 mib.as_mut_ptr(),
653 mib.len() as _,
654 kinfoproc.as_mut_ptr() as *mut libc::c_void,
655 &mut size,
656 std::ptr::null_mut(),
657 0,
658 )
659 };
660
661 if res == 0 {
662 pagesize * unsafe { (*kinfoproc.as_mut_ptr()).p_vm_rssize as usize }
666 } else {
667 0
668 }
669}
670
671#[cfg(windows)]
672fn rss() -> usize {
673 use winapi::shared::minwindef::DWORD;
674 use winapi::shared::minwindef::FALSE;
675 use winapi::um::processthreadsapi::GetCurrentProcess;
676 use winapi::um::psapi::GetProcessMemoryInfo;
677 use winapi::um::psapi::PROCESS_MEMORY_COUNTERS;
678
679 unsafe {
681 let current_process = GetCurrentProcess();
683 let mut pmc: PROCESS_MEMORY_COUNTERS = std::mem::zeroed();
684
685 if GetProcessMemoryInfo(
686 current_process,
687 &mut pmc,
688 std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as DWORD,
689 ) != FALSE
690 {
691 pmc.WorkingSetSize
692 } else {
693 0
694 }
695 }
696}
697
698fn os_uptime(state: &mut OpState) -> Result<u64, PermissionCheckError> {
699 state
700 .borrow_mut::<PermissionsContainer>()
701 .check_sys("osUptime", "Deno.osUptime()")?;
702 Ok(sys_info::os_uptime())
703}
704
705#[op2(fast, stack_trace)]
706#[number]
707fn op_os_uptime(state: &mut OpState) -> Result<u64, PermissionCheckError> {
708 os_uptime(state)
709}