diff --git a/Cargo.lock b/Cargo.lock
index 7ba53ae628..e736e86ca6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1296,11 +1296,13 @@ dependencies = [
"dsa",
"ecb",
"elliptic-curve 0.13.4",
+ "errno",
"hex",
"hkdf",
"idna 0.3.0",
"indexmap 1.9.2",
"lazy-regex",
+ "libc",
"libz-sys",
"md-5",
"md4",
@@ -1328,6 +1330,8 @@ dependencies = [
"signature 1.6.4",
"tokio",
"typenum",
+ "whoami",
+ "winapi",
"x25519-dalek",
"x509-parser",
]
@@ -6089,6 +6093,16 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "whoami"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
[[package]]
name = "widestring"
version = "0.5.1"
diff --git a/cli/tests/integration/shared_library_tests.rs b/cli/tests/integration/shared_library_tests.rs
index 531fdd5169..641deab4c1 100644
--- a/cli/tests/integration/shared_library_tests.rs
+++ b/cli/tests/integration/shared_library_tests.rs
@@ -45,12 +45,14 @@ fn macos_shared_libraries() {
// target/release/deno:
// /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1953.255.0)
// /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1228.0.0)
+ // /System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration (compatibility version 1.0.0, current version 1241.100.11)
// /System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 60420.60.24)
// /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
// /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)
- const EXPECTED: [&str; 6] =
+ const EXPECTED: [&str; 7] =
["/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
+ "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
"/System/Library/Frameworks/Security.framework/Versions/A/Security",
"/usr/lib/libiconv.2.dylib",
"/usr/lib/libSystem.B.dylib",
diff --git a/cli/tests/unit_node/os_test.ts b/cli/tests/unit_node/os_test.ts
index 85164d1e60..2d0d3a8c80 100644
--- a/cli/tests/unit_node/os_test.ts
+++ b/cli/tests/unit_node/os_test.ts
@@ -5,6 +5,7 @@ import os from "node:os";
import {
assert,
assertEquals,
+ assertNotEquals,
assertThrows,
} from "../../../test_util/std/testing/asserts.ts";
@@ -252,28 +253,39 @@ Deno.test({
});
Deno.test({
- name: "APIs not yet implemented",
+ name: "os.setPriority() & os.getPriority()",
+ // disabled because os.getPriority() doesn't work without sudo
+ ignore: true,
fn() {
- assertThrows(
- () => {
- os.getPriority();
- },
- Error,
- "Not implemented",
- );
- assertThrows(
- () => {
- os.setPriority(0);
- },
- Error,
- "Not implemented",
- );
- assertThrows(
- () => {
- os.userInfo();
- },
- Error,
- "Not implemented",
+ const child = new Deno.Command(Deno.execPath(), {
+ args: ["eval", "while (true) { console.log('foo') }"],
+ }).spawn();
+ const originalPriority = os.getPriority(child.pid);
+ assertNotEquals(originalPriority, os.constants.priority.PRIORITY_HIGH);
+ os.setPriority(child.pid, os.constants.priority.PRIORITY_HIGH);
+ assertEquals(
+ os.getPriority(child.pid),
+ os.constants.priority.PRIORITY_HIGH,
);
+ os.setPriority(child.pid, originalPriority);
+ assertEquals(os.getPriority(child.pid), originalPriority);
+ child.kill();
+ },
+});
+
+Deno.test({
+ name:
+ "os.setPriority() throw os permission denied error & os.getPriority() doesn't",
+ async fn() {
+ const child = new Deno.Command(Deno.execPath(), {
+ args: ["eval", "while (true) { console.log('foo') }"],
+ }).spawn();
+ assertThrows(
+ () => os.setPriority(child.pid, os.constants.priority.PRIORITY_HIGH),
+ Deno.errors.PermissionDenied,
+ );
+ os.getPriority(child.pid);
+ child.kill();
+ await child.status;
},
});
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index 0c0beda650..834493d5a3 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -28,11 +28,13 @@ digest = { version = "0.10.5", features = ["core-api", "std"] }
dsa = "0.6.1"
ecb.workspace = true
elliptic-curve.workspace = true
+errno = "0.2.8"
hex.workspace = true
hkdf.workspace = true
idna = "0.3.0"
indexmap.workspace = true
lazy-regex.workspace = true
+libc.workspace = true
libz-sys = { version = "1.1.8", features = ["static"] }
md-5 = "0.10.5"
md4 = "0.10.2"
@@ -60,6 +62,8 @@ sha2.workspace = true
signature.workspace = true
tokio.workspace = true
typenum = "1.15.0"
+whoami = "1.4.0"
+winapi.workspace = true
# https://github.com/dalek-cryptography/x25519-dalek/pull/89
x25519-dalek = "2.0.0-pre.1"
x509-parser = "0.15.0"
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 8f910ac63f..318de77e1c 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -53,6 +53,7 @@ pub trait NodePermissions {
api_name: &str,
) -> Result<(), AnyError>;
fn check_read(&self, path: &Path) -> Result<(), AnyError>;
+ fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError>;
}
pub(crate) struct AllowAllNodePermissions;
@@ -68,6 +69,9 @@ impl NodePermissions for AllowAllNodePermissions {
fn check_read(&self, _path: &Path) -> Result<(), AnyError> {
Ok(())
}
+ fn check_sys(&self, _kind: &str, _api_name: &str) -> Result<(), AnyError> {
+ Ok(())
+ }
}
#[allow(clippy::disallowed_types)]
@@ -243,6 +247,9 @@ deno_core::extension!(deno_node,
ops::zlib::brotli::op_brotli_decompress_stream,
ops::zlib::brotli::op_brotli_decompress_stream_end,
ops::http::op_node_http_request
,
+ ops::os::op_node_os_get_priority
,
+ ops::os::op_node_os_set_priority
,
+ ops::os::op_node_os_username
,
op_node_build_os,
op_is_any_arraybuffer,
op_node_is_promise_rejected,
diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs
index 2bbf02d343..22ad546e89 100644
--- a/ext/node/ops/mod.rs
+++ b/ext/node/ops/mod.rs
@@ -3,6 +3,7 @@
pub mod crypto;
pub mod http;
pub mod idna;
+pub mod os;
pub mod require;
pub mod v8;
pub mod winerror;
diff --git a/ext/node/ops/os.rs b/ext/node/ops/os.rs
new file mode 100644
index 0000000000..0a841a72ac
--- /dev/null
+++ b/ext/node/ops/os.rs
@@ -0,0 +1,188 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use crate::NodePermissions;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::OpState;
+use errno::errno;
+use errno::set_errno;
+use errno::Errno;
+
+#[op]
+pub fn op_node_os_get_priority
(
+ state: &mut OpState,
+ pid: u32,
+) -> Result
+where
+ P: NodePermissions + 'static,
+{
+ {
+ let permissions = state.borrow_mut::();
+ permissions.check_sys("getPriority", "node:os.getPriority()")?;
+ }
+
+ priority::get_priority(pid)
+}
+
+#[op]
+pub fn op_node_os_set_priority
(
+ state: &mut OpState,
+ pid: u32,
+ priority: i32,
+) -> Result<(), AnyError>
+where
+ P: NodePermissions + 'static,
+{
+ {
+ let permissions = state.borrow_mut::
();
+ permissions.check_sys("setPriority", "node:os.setPriority()")?;
+ }
+
+ priority::set_priority(pid, priority)
+}
+
+#[op]
+pub fn op_node_os_username
(state: &mut OpState) -> Result
+where
+ P: NodePermissions + 'static,
+{
+ {
+ let permissions = state.borrow_mut::();
+ permissions.check_sys("userInfo", "node:os.userInfo()")?;
+ }
+
+ Ok(whoami::username())
+}
+
+const PRIORITY_HIGH: i32 = -14;
+
+#[cfg(unix)]
+mod priority {
+ use super::*;
+ use libc::id_t;
+ use libc::PRIO_PROCESS;
+
+ #[cfg(target_os = "macos")]
+ #[allow(non_camel_case_types)]
+ type priority_t = i32;
+ #[cfg(target_os = "linux")]
+ #[allow(non_camel_case_types)]
+ type priority_t = u32;
+
+ // Ref: https://github.com/libuv/libuv/blob/55376b044b74db40772e8a6e24d67a8673998e02/src/unix/core.c#L1533-L1547
+ pub fn get_priority(pid: u32) -> Result {
+ set_errno(Errno(0));
+ match (
+ // SAFETY: libc::getpriority is unsafe
+ unsafe { libc::getpriority(PRIO_PROCESS as priority_t, pid as id_t) },
+ errno(),
+ ) {
+ (-1, Errno(0)) => Ok(PRIORITY_HIGH),
+ (-1, _) => Err(std::io::Error::last_os_error().into()),
+ (priority, _) => Ok(priority),
+ }
+ }
+
+ pub fn set_priority(pid: u32, priority: i32) -> Result<(), AnyError> {
+ // SAFETY: libc::setpriority is unsafe
+ match unsafe {
+ libc::setpriority(PRIO_PROCESS as priority_t, pid as id_t, priority)
+ } {
+ -1 => Err(std::io::Error::last_os_error().into()),
+ _ => Ok(()),
+ }
+ }
+}
+
+#[cfg(windows)]
+mod priority {
+ use super::*;
+ use deno_core::error::type_error;
+ use winapi::shared::minwindef::DWORD;
+ use winapi::shared::minwindef::FALSE;
+ use winapi::shared::ntdef::NULL;
+ use winapi::um::handleapi::CloseHandle;
+ use winapi::um::processthreadsapi::GetCurrentProcess;
+ use winapi::um::processthreadsapi::GetPriorityClass;
+ use winapi::um::processthreadsapi::OpenProcess;
+ use winapi::um::processthreadsapi::SetPriorityClass;
+ use winapi::um::winbase::ABOVE_NORMAL_PRIORITY_CLASS;
+ use winapi::um::winbase::BELOW_NORMAL_PRIORITY_CLASS;
+ use winapi::um::winbase::HIGH_PRIORITY_CLASS;
+ use winapi::um::winbase::IDLE_PRIORITY_CLASS;
+ use winapi::um::winbase::NORMAL_PRIORITY_CLASS;
+ use winapi::um::winbase::REALTIME_PRIORITY_CLASS;
+ use winapi::um::winnt::PROCESS_QUERY_LIMITED_INFORMATION;
+
+ // Taken from: https://github.com/libuv/libuv/blob/a877ca2435134ef86315326ef4ef0c16bdbabf17/include/uv.h#L1318-L1323
+ const PRIORITY_LOW: i32 = 19;
+ const PRIORITY_BELOW_NORMAL: i32 = 10;
+ const PRIORITY_NORMAL: i32 = 0;
+ const PRIORITY_ABOVE_NORMAL: i32 = -7;
+ const PRIORITY_HIGHEST: i32 = -20;
+
+ // Ported from: https://github.com/libuv/libuv/blob/a877ca2435134ef86315326ef4ef0c16bdbabf17/src/win/util.c#L1649-L1685
+ pub fn get_priority(pid: u32) -> Result {
+ unsafe {
+ let handle = if pid == 0 {
+ GetCurrentProcess()
+ } else {
+ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid as DWORD)
+ };
+ if handle == NULL {
+ Err(std::io::Error::last_os_error().into())
+ } else {
+ let result = match GetPriorityClass(handle) {
+ 0 => Err(std::io::Error::last_os_error().into()),
+ REALTIME_PRIORITY_CLASS => Ok(PRIORITY_HIGHEST),
+ HIGH_PRIORITY_CLASS => Ok(PRIORITY_HIGH),
+ ABOVE_NORMAL_PRIORITY_CLASS => Ok(PRIORITY_ABOVE_NORMAL),
+ NORMAL_PRIORITY_CLASS => Ok(PRIORITY_NORMAL),
+ BELOW_NORMAL_PRIORITY_CLASS => Ok(PRIORITY_BELOW_NORMAL),
+ IDLE_PRIORITY_CLASS => Ok(PRIORITY_LOW),
+ _ => Ok(PRIORITY_LOW),
+ };
+ CloseHandle(handle);
+ result
+ }
+ }
+ }
+
+ // Ported from: https://github.com/libuv/libuv/blob/a877ca2435134ef86315326ef4ef0c16bdbabf17/src/win/util.c#L1688-L1719
+ pub fn set_priority(pid: u32, priority: i32) -> Result<(), AnyError> {
+ unsafe {
+ let handle = if pid == 0 {
+ GetCurrentProcess()
+ } else {
+ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid as DWORD)
+ };
+ if handle == NULL {
+ Err(std::io::Error::last_os_error().into())
+ } else {
+ let priority_class =
+ if priority < PRIORITY_HIGHEST || priority > PRIORITY_LOW {
+ return Err(type_error("Invalid priority"));
+ } else if priority < PRIORITY_HIGH {
+ REALTIME_PRIORITY_CLASS
+ } else if priority < PRIORITY_ABOVE_NORMAL {
+ HIGH_PRIORITY_CLASS
+ } else if priority < PRIORITY_NORMAL {
+ ABOVE_NORMAL_PRIORITY_CLASS
+ } else if priority < PRIORITY_BELOW_NORMAL {
+ NORMAL_PRIORITY_CLASS
+ } else if priority < PRIORITY_LOW {
+ BELOW_NORMAL_PRIORITY_CLASS
+ } else {
+ IDLE_PRIORITY_CLASS
+ };
+
+ let result = match SetPriorityClass(handle, priority_class) {
+ FALSE => Err(std::io::Error::last_os_error().into()),
+ _ => Ok(()),
+ };
+ CloseHandle(handle);
+ result
+ }
+ }
+ }
+}
diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts
index 55098d79bc..2ba7ec28e4 100644
--- a/ext/node/polyfills/internal/errors.ts
+++ b/ext/node/polyfills/internal/errors.ts
@@ -2497,6 +2497,19 @@ export class ERR_FS_RMDIR_ENOTDIR extends NodeSystemError {
}
}
+export class ERR_OS_NO_HOMEDIR extends NodeSystemError {
+ constructor() {
+ const code = isWindows ? "ENOENT" : "ENOTDIR";
+ const ctx: NodeSystemErrorCtx = {
+ message: "not a directory",
+ syscall: "home",
+ code,
+ errno: isWindows ? osConstants.errno.ENOENT : osConstants.errno.ENOTDIR,
+ };
+ super(code, ctx, "Path is not a directory");
+ }
+}
+
interface UvExceptionContext {
syscall: string;
path?: string;
diff --git a/ext/node/polyfills/os.ts b/ext/node/polyfills/os.ts
index acdc1c9770..a874c942cb 100644
--- a/ext/node/polyfills/os.ts
+++ b/ext/node/polyfills/os.ts
@@ -23,15 +23,16 @@
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
-import { notImplemented } from "ext:deno_node/_utils.ts";
+const core = globalThis.__bootstrap.core;
import { validateIntegerRange } from "ext:deno_node/_utils.ts";
import process from "node:process";
import { isWindows, osType } from "ext:deno_node/_util/os.ts";
+import { ERR_OS_NO_HOMEDIR } from "ext:deno_node/internal/errors.ts";
import { os } from "ext:deno_node/internal_binding/constants.ts";
import { osUptime } from "ext:runtime/30_os.js";
-export const constants = os;
+import { Buffer } from "ext:deno_node/internal/buffer.mjs";
-const SEE_GITHUB_ISSUE = "See https://github.com/denoland/deno_std/issues/1436";
+export const constants = os;
interface CPUTimes {
/** The number of milliseconds the CPU has spent in user mode */
@@ -93,8 +94,8 @@ interface UserInfo {
username: string;
uid: number;
gid: number;
- shell: string;
- homedir: string;
+ shell: string | null;
+ homedir: string | null;
}
export function arch(): string {
@@ -161,7 +162,7 @@ export function freemem(): number {
/** Not yet implemented */
export function getPriority(pid = 0): number {
validateIntegerRange(pid, "pid");
- notImplemented(SEE_GITHUB_ISSUE);
+ return core.ops.op_node_os_get_priority(pid);
}
/** Returns the string path of the current user's home directory. */
@@ -257,7 +258,7 @@ export function setPriority(pid: number, priority?: number) {
validateIntegerRange(pid, "pid");
validateIntegerRange(priority, "priority", -20, 19);
- notImplemented(SEE_GITHUB_ISSUE);
+ core.ops.op_node_os_set_priority(pid, priority);
}
/** Returns the operating system's default directory for temporary files as a string. */
@@ -317,10 +318,40 @@ export function uptime(): number {
/** Not yet implemented */
export function userInfo(
- // deno-lint-ignore no-unused-vars
options: UserInfoOptions = { encoding: "utf-8" },
): UserInfo {
- notImplemented(SEE_GITHUB_ISSUE);
+ const uid = Deno.uid();
+ const gid = Deno.gid();
+
+ if (isWindows) {
+ uid = -1;
+ gid = -1;
+ }
+
+ // TODO(@crowlKats): figure out how to do this correctly:
+ // The value of homedir returned by os.userInfo() is provided by the operating system.
+ // This differs from the result of os.homedir(), which queries environment
+ // variables for the home directory before falling back to the operating system response.
+ let _homedir = homedir();
+ if (!_homedir) {
+ throw new ERR_OS_NO_HOMEDIR();
+ }
+ let shell = isWindows ? (Deno.env.get("SHELL") || null) : null;
+ let username = core.ops.op_node_os_username();
+
+ if (options?.encoding === "buffer") {
+ _homedir = _homedir ? Buffer.from(_homedir) : _homedir;
+ shell = shell ? Buffer.from(shell) : shell;
+ username = Buffer.from(username);
+ }
+
+ return {
+ uid,
+ gid,
+ homedir: _homedir,
+ shell,
+ username,
+ };
}
export const EOL = isWindows ? "\r\n" : "\n";
diff --git a/runtime/build.rs b/runtime/build.rs
index d7de298832..828bc3c536 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -139,6 +139,13 @@ mod startup_snapshot {
fn check_read(&self, _p: &Path) -> Result<(), deno_core::error::AnyError> {
unreachable!("snapshotting!")
}
+ fn check_sys(
+ &self,
+ _kind: &str,
+ _api_name: &str,
+ ) -> Result<(), deno_core::error::AnyError> {
+ unreachable!("snapshotting!")
+ }
}
impl deno_net::NetPermissions for Permissions {
diff --git a/runtime/permissions/mod.rs b/runtime/permissions/mod.rs
index 84fcfa6aa0..56ebf83273 100644
--- a/runtime/permissions/mod.rs
+++ b/runtime/permissions/mod.rs
@@ -1886,6 +1886,10 @@ impl deno_node::NodePermissions for PermissionsContainer {
fn check_read(&self, path: &Path) -> Result<(), AnyError> {
self.0.lock().read.check(path, None)
}
+
+ fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError> {
+ self.0.lock().sys.check(kind, Some(api_name))
+ }
}
impl deno_net::NetPermissions for PermissionsContainer {