diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2022-11-02 00:17:00 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-02 12:47:00 +0530 |
commit | ab7e80bde4513984046c093ed5eeb8c0640c4fe0 (patch) | |
tree | e1551b8f7c16bbc777b9ba6391238224fd550de2 /runtime/ops/os | |
parent | 5e4e324ceb657772f98bfae3c797e7417acc9837 (diff) |
chore(runtime): remove dependency on sys-info crate (#16441)
Fixes #9862
`loadavg`
| Target family | Syscall | Description |
| ------------- | ------- | ----------- |
| Linux | `sysinfo` | - |
| Windows | - | Returns `DEFAULT_LOADAVG`. There is no concept of
loadavg on Windows |
| macOS, BSD | `getloadavg` |
https://www.freebsd.org/cgi/man.cgi?query=getloadavg |
`os_release`
| Target family | Syscall | Description |
| ------------- | ------- | ----------- |
| Linux | `/proc/sys/kernel/osrelease` | - |
| Windows |
[`RtlGetVersion`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion)
| dwMajorVersion . dwMinorVersion . dwBuildNumber |
| macOS | `sysctl([CTL_KERN, KERN_OSRELEASE])` | - |
`hostname`
| Target family | Syscall | Description |
| ------------- | ------- | ----------- |
| Unix | `gethostname(sysconf(_SC_HOST_NAME_MAX))` | - |
| Windows | `GetHostNameW` | - |
`mem_info`
| Target family | Syscall | Description |
| ------------- | ------- | ----------- |
| Linux | sysinfo | - |
| Windows | `sysinfoapi::GlobalMemoryStatusEx` | - |
| macOS | <br> <pre> sysctl([CTL_HW, HW_MEMSIZE]); <br> sysctl([CTL_VM,
VM_SWAPUSAGE]); <br> host_statistics64(mach_host_self(), HOST_VM_INFO64)
</pre> | - |
Diffstat (limited to 'runtime/ops/os')
-rw-r--r-- | runtime/ops/os/README.md | 32 | ||||
-rw-r--r-- | runtime/ops/os/mod.rs | 307 | ||||
-rw-r--r-- | runtime/ops/os/sys_info.rs | 284 |
3 files changed, 623 insertions, 0 deletions
diff --git a/runtime/ops/os/README.md b/runtime/ops/os/README.md new file mode 100644 index 000000000..837bb7b3c --- /dev/null +++ b/runtime/ops/os/README.md @@ -0,0 +1,32 @@ +## `os` ops + +`loadavg` + +| Target family | Syscall | Description | +| ------------- | ------------ | -------------------------------------------------------------------- | +| Linux | `sysinfo` | - | +| Windows | - | Returns `DEFAULT_LOADAVG`. There is no concept of loadavg on Windows | +| macOS, BSD | `getloadavg` | https://www.freebsd.org/cgi/man.cgi?query=getloadavg | + +`os_release` + +| Target family | Syscall | Description | +| ------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| Linux | `/proc/sys/kernel/osrelease` | - | +| Windows | [`RtlGetVersion`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion) | dwMajorVersion . dwMinorVersion . dwBuildNumber | +| macOS | `sysctl([CTL_KERN, KERN_OSRELEASE])` | - | + +`hostname` + +| Target family | Syscall | Description | +| ------------- | ----------------------------------------- | ----------- | +| Unix | `gethostname(sysconf(_SC_HOST_NAME_MAX))` | - | +| Windows | `GetHostNameW` | - | + +`mem_info` + +| Target family | Syscall | Description | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| Linux | sysinfo | - | +| Windows | `sysinfoapi::GlobalMemoryStatusEx` | - | +| macOS | <br> <pre> sysctl([CTL_HW, HW_MEMSIZE]); <br> sysctl([CTL_VM, VM_SWAPUSAGE]); <br> host_statistics64(mach_host_self(), HOST_VM_INFO64) </pre> | - | diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs new file mode 100644 index 000000000..3b4645403 --- /dev/null +++ b/runtime/ops/os/mod.rs @@ -0,0 +1,307 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use super::utils::into_string; +use crate::permissions::Permissions; +use crate::worker::ExitCode; +use deno_core::error::{type_error, AnyError}; +use deno_core::url::Url; +use deno_core::Extension; +use deno_core::OpState; +use deno_core::{op, ExtensionBuilder}; +use deno_node::NODE_ENV_VAR_ALLOWLIST; +use std::collections::HashMap; +use std::env; + +mod sys_info; + +fn init_ops(builder: &mut ExtensionBuilder) -> &mut ExtensionBuilder { + builder.ops(vec![ + op_env::decl(), + op_exec_path::decl(), + op_exit::decl(), + op_delete_env::decl(), + op_get_env::decl(), + op_gid::decl(), + op_hostname::decl(), + op_loadavg::decl(), + op_network_interfaces::decl(), + op_os_release::decl(), + op_set_env::decl(), + op_set_exit_code::decl(), + op_system_memory_info::decl(), + op_uid::decl(), + ]) +} + +pub fn init(exit_code: ExitCode) -> Extension { + let mut builder = Extension::builder(); + init_ops(&mut builder) + .state(move |state| { + state.put::<ExitCode>(exit_code.clone()); + Ok(()) + }) + .build() +} + +pub fn init_for_worker() -> Extension { + let mut builder = Extension::builder(); + init_ops(&mut builder) + .middleware(|op| match op.name { + "op_exit" => noop_op::decl(), + "op_set_exit_code" => noop_op::decl(), + _ => op, + }) + .build() +} + +#[op] +fn noop_op() -> Result<(), AnyError> { + Ok(()) +} + +#[op] +fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> { + let current_exe = env::current_exe().unwrap(); + state.borrow_mut::<Permissions>().read.check_blind( + ¤t_exe, + "exec_path", + "Deno.execPath()", + )?; + // Now apply URL parser to current exe to get fully resolved path, otherwise + // we might get `./` and `../` bits in `exec_path` + let exe_url = Url::from_file_path(current_exe).unwrap(); + let path = exe_url.to_file_path().unwrap(); + + into_string(path.into_os_string()) +} + +#[op] +fn op_set_env( + state: &mut OpState, + key: String, + value: String, +) -> Result<(), AnyError> { + state.borrow_mut::<Permissions>().env.check(&key)?; + if key.is_empty() { + return Err(type_error("Key is an empty string.")); + } + if key.contains(&['=', '\0'] as &[char]) { + return Err(type_error(format!( + "Key contains invalid characters: {:?}", + key + ))); + } + if value.contains('\0') { + return Err(type_error(format!( + "Value contains invalid characters: {:?}", + value + ))); + } + env::set_var(key, value); + Ok(()) +} + +#[op] +fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> { + state.borrow_mut::<Permissions>().env.check_all()?; + Ok(env::vars().collect()) +} + +#[op] +fn op_get_env( + state: &mut OpState, + key: String, +) -> Result<Option<String>, AnyError> { + let skip_permission_check = + state.borrow::<crate::ops::UnstableChecker>().unstable + && NODE_ENV_VAR_ALLOWLIST.contains(&key); + + if !skip_permission_check { + state.borrow_mut::<Permissions>().env.check(&key)?; + } + + if key.is_empty() { + return Err(type_error("Key is an empty string.")); + } + + if key.contains(&['=', '\0'] as &[char]) { + return Err(type_error(format!( + "Key contains invalid characters: {:?}", + key + ))); + } + + let r = match env::var(key) { + Err(env::VarError::NotPresent) => None, + v => Some(v?), + }; + Ok(r) +} + +#[op] +fn op_delete_env(state: &mut OpState, key: String) -> Result<(), AnyError> { + state.borrow_mut::<Permissions>().env.check(&key)?; + if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { + return Err(type_error("Key contains invalid characters.")); + } + env::remove_var(key); + Ok(()) +} + +#[op] +fn op_set_exit_code(state: &mut OpState, code: i32) { + state.borrow_mut::<ExitCode>().set(code); +} + +#[op] +fn op_exit(state: &mut OpState) { + let code = state.borrow::<ExitCode>().get(); + std::process::exit(code) +} + +#[op] +fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { + state + .borrow_mut::<Permissions>() + .sys + .check("loadavg", Some("Deno.loadavg()"))?; + Ok(sys_info::loadavg()) +} + +#[op] +fn op_hostname(state: &mut OpState) -> Result<String, AnyError> { + state + .borrow_mut::<Permissions>() + .sys + .check("hostname", Some("Deno.hostname()"))?; + Ok(sys_info::hostname()) +} + +#[op] +fn op_os_release(state: &mut OpState) -> Result<String, AnyError> { + state + .borrow_mut::<Permissions>() + .sys + .check("osRelease", Some("Deno.osRelease()"))?; + Ok(sys_info::os_release()) +} + +#[op] +fn op_network_interfaces( + state: &mut OpState, +) -> Result<Vec<NetworkInterface>, AnyError> { + super::check_unstable(state, "Deno.networkInterfaces"); + state + .borrow_mut::<Permissions>() + .sys + .check("networkInterfaces", Some("Deno.networkInterfaces()"))?; + Ok(netif::up()?.map(NetworkInterface::from).collect()) +} + +#[derive(serde::Serialize)] +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!( + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + b0, b1, b2, b3, b4, b5 + ); + + Self { + family, + name, + address, + netmask, + scopeid, + cidr, + mac, + } + } +} + +#[op] +fn op_system_memory_info( + state: &mut OpState, +) -> Result<Option<sys_info::MemInfo>, AnyError> { + super::check_unstable(state, "Deno.systemMemoryInfo"); + state + .borrow_mut::<Permissions>() + .sys + .check("systemMemoryInfo", Some("Deno.systemMemoryInfo()"))?; + Ok(sys_info::mem_info()) +} + +#[cfg(not(windows))] +#[op] +fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { + super::check_unstable(state, "Deno.gid"); + state + .borrow_mut::<Permissions>() + .sys + .check("gid", Some("Deno.gid()"))?; + // TODO(bartlomieju): + #[allow(clippy::undocumented_unsafe_blocks)] + unsafe { + Ok(Some(libc::getgid())) + } +} + +#[cfg(windows)] +#[op] +fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { + super::check_unstable(state, "Deno.gid"); + state + .borrow_mut::<Permissions>() + .sys + .check("gid", Some("Deno.gid()"))?; + Ok(None) +} + +#[cfg(not(windows))] +#[op] +fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { + super::check_unstable(state, "Deno.uid"); + state + .borrow_mut::<Permissions>() + .sys + .check("uid", Some("Deno.uid()"))?; + // TODO(bartlomieju): + #[allow(clippy::undocumented_unsafe_blocks)] + unsafe { + Ok(Some(libc::getuid())) + } +} + +#[cfg(windows)] +#[op] +fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { + super::check_unstable(state, "Deno.uid"); + state + .borrow_mut::<Permissions>() + .sys + .check("uid", Some("Deno.uid()"))?; + Ok(None) +} diff --git a/runtime/ops/os/sys_info.rs b/runtime/ops/os/sys_info.rs new file mode 100644 index 000000000..3e6cd4a51 --- /dev/null +++ b/runtime/ops/os/sys_info.rs @@ -0,0 +1,284 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +type LoadAvg = (f64, f64, f64); +const DEFAULT_LOADAVG: LoadAvg = (0.0, 0.0, 0.0); + +pub fn loadavg() -> LoadAvg { + #[cfg(target_os = "linux")] + { + use libc::SI_LOAD_SHIFT; + + let mut info = std::mem::MaybeUninit::uninit(); + // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + if res == 0 { + // SAFETY: `sysinfo` returns 0 on success, and `info` is initialized. + let info = unsafe { info.assume_init() }; + ( + info.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, + info.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, + info.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, + ) + } else { + DEFAULT_LOADAVG + } + } + #[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "openbsd" + ))] + { + let mut l: [f64; 3] = [0.; 3]; + // SAFETY: `&mut l` is a valid pointer to an array of 3 doubles + if unsafe { libc::getloadavg(&mut l as *mut f64, l.len() as _) } < 3 { + DEFAULT_LOADAVG + } else { + (l[0], l[1], l[2]) + } + } + #[cfg(target_os = "windows")] + { + DEFAULT_LOADAVG + } +} + +pub fn os_release() -> String { + #[cfg(target_os = "linux")] + { + match std::fs::read_to_string("/proc/sys/kernel/osrelease") { + Ok(mut s) => { + s.pop(); // pop '\n' + s + } + _ => String::from(""), + } + } + #[cfg(target_vendor = "apple")] + { + let mut s = [0u8; 256]; + let mut mib = [libc::CTL_KERN, libc::KERN_OSRELEASE]; + // 256 is enough. + let mut len = s.len(); + // SAFETY: `sysctl` is thread-safe. + // `s` is only accessed if sysctl() succeeds and agrees with the `len` set + // by sysctl(). + if unsafe { + libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + s.as_mut_ptr() as _, + &mut len, + std::ptr::null_mut(), + 0, + ) + } == -1 + { + return String::from("Unknown"); + } + + // without the NUL terminator + return String::from_utf8_lossy(&s[..len - 1]).to_string(); + } + #[cfg(target_family = "windows")] + { + use ntapi::ntrtl::RtlGetVersion; + use winapi::shared::ntdef::NT_SUCCESS; + use winapi::um::winnt::RTL_OSVERSIONINFOEXW; + + let mut version_info = + std::mem::MaybeUninit::<RTL_OSVERSIONINFOEXW>::uninit(); + // SAFETY: we need to initialize dwOSVersionInfoSize. + unsafe { + (*version_info.as_mut_ptr()).dwOSVersionInfoSize = + std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32; + } + // SAFETY: `version_info` is pointer to a valid `RTL_OSVERSIONINFOEXW` struct and + // dwOSVersionInfoSize is set to the size of RTL_OSVERSIONINFOEXW. + if !NT_SUCCESS(unsafe { + RtlGetVersion(version_info.as_mut_ptr() as *mut _) + }) { + String::from("") + } else { + // SAFETY: we assume that RtlGetVersion() initializes the fields. + let version_info = unsafe { version_info.assume_init() }; + format!( + "{}.{}.{}", + version_info.dwMajorVersion, + version_info.dwMinorVersion, + version_info.dwBuildNumber + ) + } + } +} + +pub fn hostname() -> String { + #[cfg(target_family = "unix")] + // SAFETY: `sysconf` returns a system constant. + unsafe { + let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize; + let mut buf = vec![0u8; buf_size + 1]; + let len = buf.len(); + if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, len) < 0 { + return String::from(""); + } + // ensure NUL termination + buf[len - 1] = 0; + std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char) + .to_string_lossy() + .to_string() + } + #[cfg(target_family = "windows")] + { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + use winapi::um::winsock2::GetHostNameW; + + let namelen = 256; + let mut name: Vec<u16> = vec![0u16; namelen]; + let err = + // SAFETY: length of wide string is 256 chars or less. + // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew + unsafe { GetHostNameW(name.as_mut_ptr(), namelen as libc::c_int) }; + + if err == 0 { + // TODO(@littledivy): Probably not the most efficient way. + let len = name.iter().take_while(|&&c| c != 0).count(); + OsString::from_wide(&name[..len]) + .to_string_lossy() + .into_owned() + } else { + String::from("") + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MemInfo { + pub total: u64, + pub free: u64, + pub available: u64, + pub buffers: u64, + pub cached: u64, + pub swap_total: u64, + pub swap_free: u64, +} + +pub fn mem_info() -> Option<MemInfo> { + let mut mem_info = MemInfo { + total: 0, + free: 0, + available: 0, + buffers: 0, + cached: 0, + swap_total: 0, + swap_free: 0, + }; + #[cfg(target_os = "linux")] + { + let mut info = std::mem::MaybeUninit::uninit(); + // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + if res == 0 { + // SAFETY: `sysinfo` initializes the struct. + let info = unsafe { info.assume_init() }; + let mem_unit = info.mem_unit as u64; + mem_info.swap_total = info.totalswap * mem_unit; + mem_info.swap_free = info.freeswap * mem_unit; + mem_info.total = info.totalram * mem_unit; + mem_info.free = info.freeram * mem_unit; + mem_info.buffers = info.bufferram * mem_unit; + } + } + #[cfg(any(target_vendor = "apple"))] + { + let mut mib: [i32; 2] = [0, 0]; + mib[0] = libc::CTL_HW; + mib[1] = libc::HW_MEMSIZE; + // SAFETY: + // - We assume that `mach_host_self` always returns a valid value. + // - sysconf returns a system constant. + unsafe { + let mut size = std::mem::size_of::<u64>(); + libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut mem_info.total as *mut _ as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ); + mem_info.total /= 1024; + + let mut xs: libc::xsw_usage = std::mem::zeroed::<libc::xsw_usage>(); + mib[0] = libc::CTL_VM; + mib[1] = libc::VM_SWAPUSAGE; + + let mut size = std::mem::size_of::<libc::xsw_usage>(); + libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut xs as *mut _ as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ); + + mem_info.swap_total = xs.xsu_total; + mem_info.swap_free = xs.xsu_avail; + + let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; + let mut stat = unsafe { std::mem::zeroed::<libc::vm_statistics64>() }; + if libc::host_statistics64( + // TODO(@littledivy): Put this in a once_cell. + libc::mach_host_self(), + libc::HOST_VM_INFO64, + &mut stat as *mut libc::vm_statistics64 as *mut _, + &mut count, + ) == libc::KERN_SUCCESS + { + // TODO(@littledivy): Put this in a once_cell + let page_size = libc::sysconf(libc::_SC_PAGESIZE) as u64; + mem_info.available = + (stat.free_count as u64 + stat.inactive_count as u64) * page_size + / 1024; + mem_info.free = + (stat.free_count as u64 - stat.speculative_count as u64) * page_size + / 1024; + } + } + } + #[cfg(target_family = "windows")] + // SAFETY: + // - `mem_status` is a valid pointer to a `libc::MEMORYSTATUSEX` struct. + // - `dwLength` is set to the size of the struct. + unsafe { + use std::mem; + use winapi::shared::minwindef; + use winapi::um::sysinfoapi; + + let mut mem_status = + mem::MaybeUninit::<sysinfoapi::MEMORYSTATUSEX>::uninit(); + let length = + mem::size_of::<sysinfoapi::MEMORYSTATUSEX>() as minwindef::DWORD; + (*mem_status.as_mut_ptr()).dwLength = length; + + let result = sysinfoapi::GlobalMemoryStatusEx(mem_status.as_mut_ptr()); + if result != 0 { + let stat = mem_status.assume_init(); + mem_info.total = stat.ullTotalPhys / 1024; + mem_info.available = 0; + mem_info.free = stat.ullAvailPhys / 1024; + mem_info.cached = 0; + mem_info.buffers = 0; + mem_info.swap_total = (stat.ullTotalPageFile - stat.ullTotalPhys) / 1024; + mem_info.swap_free = (stat.ullAvailPageFile - stat.ullAvailPhys) / 1024; + if mem_info.swap_free > mem_info.swap_total { + mem_info.swap_free = mem_info.swap_total; + } + } + } + + Some(mem_info) +} |