Skip to content

Commit

Permalink
feat(node/os): implement getPriority, setPriority & userInfo (denolan…
Browse files Browse the repository at this point in the history
…d#19370)

Takes denoland#4202 over
Closes denoland#17850 

---------

Co-authored-by: ecyrbe <[email protected]>
  • Loading branch information
crowlKats and ecyrbe committed Jul 31, 2023
1 parent 78ceeec commit aa8078b
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 29 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion cli/tests/integration/shared_library_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
50 changes: 31 additions & 19 deletions cli/tests/unit_node/os_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import os from "node:os";
import {
assert,
assertEquals,
assertNotEquals,
assertThrows,
} from "../../../test_util/std/testing/asserts.ts";

Expand Down Expand Up @@ -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",
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.userInfo();
},
Error,
"Not implemented",
() => os.setPriority(child.pid, os.constants.priority.PRIORITY_HIGH),
Deno.errors.PermissionDenied,
);
os.getPriority(child.pid);
child.kill();
await child.status;
},
});
4 changes: 4 additions & 0 deletions ext/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
7 changes: 7 additions & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)]
Expand Down Expand Up @@ -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<P>,
ops::os::op_node_os_get_priority<P>,
ops::os::op_node_os_set_priority<P>,
ops::os::op_node_os_username<P>,
op_node_build_os,
op_is_any_arraybuffer,
op_node_is_promise_rejected,
Expand Down
1 change: 1 addition & 0 deletions ext/node/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
188 changes: 188 additions & 0 deletions ext/node/ops/os.rs
Original file line number Diff line number Diff line change
@@ -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<P>(
state: &mut OpState,
pid: u32,
) -> Result<i32, AnyError>
where
P: NodePermissions + 'static,
{
{
let permissions = state.borrow_mut::<P>();
permissions.check_sys("getPriority", "node:os.getPriority()")?;
}

priority::get_priority(pid)
}

#[op]
pub fn op_node_os_set_priority<P>(
state: &mut OpState,
pid: u32,
priority: i32,
) -> Result<(), AnyError>
where
P: NodePermissions + 'static,
{
{
let permissions = state.borrow_mut::<P>();
permissions.check_sys("setPriority", "node:os.setPriority()")?;
}

priority::set_priority(pid, priority)
}

#[op]
pub fn op_node_os_username<P>(state: &mut OpState) -> Result<String, AnyError>
where
P: NodePermissions + 'static,
{
{
let permissions = state.borrow_mut::<P>();
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<i32, AnyError> {
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<i32, 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 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
}
}
}
}
13 changes: 13 additions & 0 deletions ext/node/polyfills/internal/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit aa8078b

Please sign in to comment.