summaryrefslogtreecommitdiff
path: root/runtime/ops
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/ops')
-rw-r--r--runtime/ops/fs_events.rs121
-rw-r--r--runtime/ops/http.rs43
-rw-r--r--runtime/ops/mod.rs2
-rw-r--r--runtime/ops/os/mod.rs99
-rw-r--r--runtime/ops/os/sys_info.rs425
-rw-r--r--runtime/ops/otel.rs855
-rw-r--r--runtime/ops/permissions.rs43
-rw-r--r--runtime/ops/process.rs171
-rw-r--r--runtime/ops/runtime.rs6
-rw-r--r--runtime/ops/signal.rs663
-rw-r--r--runtime/ops/tty.rs54
-rw-r--r--runtime/ops/utils.rs12
-rw-r--r--runtime/ops/web_worker.rs10
-rw-r--r--runtime/ops/web_worker/sync_fetch.rs95
-rw-r--r--runtime/ops/worker_host.rs57
15 files changed, 1549 insertions, 1107 deletions
diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs
index d88a32d91..c8e0228bc 100644
--- a/runtime/ops/fs_events.rs
+++ b/runtime/ops/fs_events.rs
@@ -1,6 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
use deno_core::AsyncRefCell;
use deno_core::CancelFuture;
@@ -20,13 +19,14 @@ use notify::EventKind;
use notify::RecommendedWatcher;
use notify::RecursiveMode;
use notify::Watcher;
-use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
use std::cell::RefCell;
use std::convert::From;
+use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
+use std::sync::Arc;
use tokio::sync::mpsc;
deno_core::extension!(
@@ -35,9 +35,7 @@ deno_core::extension!(
);
struct FsEventsResource {
- #[allow(unused)]
- watcher: RecommendedWatcher,
- receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, AnyError>>>,
+ receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, NotifyError>>>,
cancel: CancelHandle,
}
@@ -59,7 +57,7 @@ impl Resource for FsEventsResource {
///
/// Feel free to expand this struct as long as you can add tests to demonstrate
/// the complexity.
-#[derive(Serialize, Debug)]
+#[derive(Serialize, Debug, Clone)]
struct FsEvent {
kind: &'static str,
paths: Vec<PathBuf>,
@@ -93,43 +91,102 @@ impl From<NotifyEvent> for FsEvent {
}
}
-#[derive(Deserialize)]
-pub struct OpenArgs {
- recursive: bool,
- paths: Vec<String>,
+type WatchSender = (Vec<String>, mpsc::Sender<Result<FsEvent, NotifyError>>);
+
+struct WatcherState {
+ senders: Arc<Mutex<Vec<WatchSender>>>,
+ watcher: RecommendedWatcher,
}
-#[op2]
-#[smi]
-fn op_fs_events_open(
+fn starts_with_canonicalized(path: &Path, prefix: &str) -> bool {
+ #[allow(clippy::disallowed_methods)]
+ let path = path.canonicalize().ok();
+ #[allow(clippy::disallowed_methods)]
+ let prefix = std::fs::canonicalize(prefix).ok();
+ match (path, prefix) {
+ (Some(path), Some(prefix)) => path.starts_with(prefix),
+ _ => false,
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum FsEventsError {
+ #[error(transparent)]
+ Resource(deno_core::error::AnyError),
+ #[error(transparent)]
+ Permission(#[from] deno_permissions::PermissionCheckError),
+ #[error(transparent)]
+ Notify(#[from] NotifyError),
+ #[error(transparent)]
+ Canceled(#[from] deno_core::Canceled),
+}
+
+fn start_watcher(
state: &mut OpState,
- #[serde] args: OpenArgs,
-) -> Result<ResourceId, AnyError> {
- let (sender, receiver) = mpsc::channel::<Result<FsEvent, AnyError>>(16);
- let sender = Mutex::new(sender);
- let mut watcher: RecommendedWatcher = Watcher::new(
+ paths: Vec<String>,
+ sender: mpsc::Sender<Result<FsEvent, NotifyError>>,
+) -> Result<(), FsEventsError> {
+ if let Some(watcher) = state.try_borrow_mut::<WatcherState>() {
+ watcher.senders.lock().push((paths, sender));
+ return Ok(());
+ }
+
+ let senders = Arc::new(Mutex::new(vec![(paths, sender)]));
+
+ let sender_clone = senders.clone();
+ let watcher: RecommendedWatcher = Watcher::new(
move |res: Result<NotifyEvent, NotifyError>| {
- let res2 = res.map(FsEvent::from).map_err(AnyError::from);
- let sender = sender.lock();
- // Ignore result, if send failed it means that watcher was already closed,
- // but not all messages have been flushed.
- let _ = sender.try_send(res2);
+ let res2 = res.map(FsEvent::from).map_err(FsEventsError::Notify);
+ for (paths, sender) in sender_clone.lock().iter() {
+ // Ignore result, if send failed it means that watcher was already closed,
+ // but not all messages have been flushed.
+
+ // Only send the event if the path matches one of the paths that the user is watching
+ if let Ok(event) = &res2 {
+ if paths.iter().any(|path| {
+ event.paths.iter().any(|event_path| {
+ same_file::is_same_file(event_path, path).unwrap_or(false)
+ || starts_with_canonicalized(event_path, path)
+ })
+ }) {
+ let _ = sender.try_send(Ok(event.clone()));
+ }
+ }
+ }
},
Default::default(),
)?;
- let recursive_mode = if args.recursive {
+
+ state.put::<WatcherState>(WatcherState { watcher, senders });
+
+ Ok(())
+}
+
+#[op2]
+#[smi]
+fn op_fs_events_open(
+ state: &mut OpState,
+ recursive: bool,
+ #[serde] paths: Vec<String>,
+) -> Result<ResourceId, FsEventsError> {
+ let (sender, receiver) = mpsc::channel::<Result<FsEvent, NotifyError>>(16);
+
+ start_watcher(state, paths.clone(), sender)?;
+
+ let recursive_mode = if recursive {
RecursiveMode::Recursive
} else {
RecursiveMode::NonRecursive
};
- for path in &args.paths {
+ for path in &paths {
let path = state
.borrow_mut::<PermissionsContainer>()
.check_read(path, "Deno.watchFs()")?;
- watcher.watch(&path, recursive_mode)?;
+
+ let watcher = state.borrow_mut::<WatcherState>();
+ watcher.watcher.watch(&path, recursive_mode)?;
}
let resource = FsEventsResource {
- watcher,
receiver: AsyncRefCell::new(receiver),
cancel: Default::default(),
};
@@ -142,14 +199,18 @@ fn op_fs_events_open(
async fn op_fs_events_poll(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
-) -> Result<Option<FsEvent>, AnyError> {
- let resource = state.borrow().resource_table.get::<FsEventsResource>(rid)?;
+) -> Result<Option<FsEvent>, FsEventsError> {
+ let resource = state
+ .borrow()
+ .resource_table
+ .get::<FsEventsResource>(rid)
+ .map_err(FsEventsError::Resource)?;
let mut receiver = RcRef::map(&resource, |r| &r.receiver).borrow_mut().await;
let cancel = RcRef::map(resource, |r| &r.cancel);
let maybe_result = receiver.recv().or_cancel(cancel).await?;
match maybe_result {
Some(Ok(value)) => Ok(Some(value)),
- Some(Err(err)) => Err(err),
+ Some(Err(err)) => Err(FsEventsError::Notify(err)),
None => Ok(None),
}
}
diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs
index cec8b0ef8..6e3157668 100644
--- a/runtime/ops/http.rs
+++ b/runtime/ops/http.rs
@@ -2,9 +2,6 @@
use std::rc::Rc;
-use deno_core::error::bad_resource_id;
-use deno_core::error::custom_error;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::ResourceId;
@@ -16,12 +13,31 @@ pub const UNSTABLE_FEATURE_NAME: &str = "http";
deno_core::extension!(deno_http_runtime, ops = [op_http_start],);
+#[derive(Debug, thiserror::Error)]
+pub enum HttpStartError {
+ #[error("TCP stream is currently in use")]
+ TcpStreamInUse,
+ #[error("TLS stream is currently in use")]
+ TlsStreamInUse,
+ #[error("Unix socket is currently in use")]
+ UnixSocketInUse,
+ #[error(transparent)]
+ ReuniteTcp(#[from] tokio::net::tcp::ReuniteError),
+ #[cfg(unix)]
+ #[error(transparent)]
+ ReuniteUnix(#[from] tokio::net::unix::ReuniteError),
+ #[error("{0}")]
+ Io(#[from] std::io::Error),
+ #[error(transparent)]
+ Other(deno_core::error::AnyError),
+}
+
#[op2(fast)]
#[smi]
fn op_http_start(
state: &mut OpState,
#[smi] tcp_stream_rid: ResourceId,
-) -> Result<ResourceId, AnyError> {
+) -> Result<ResourceId, HttpStartError> {
if let Ok(resource_rc) = state
.resource_table
.take::<TcpStreamResource>(tcp_stream_rid)
@@ -30,11 +46,11 @@ fn op_http_start(
// process of starting a HTTP server on top of this TCP connection, so we just return a Busy error.
// See also: https://github.com/denoland/deno/pull/16242
let resource = Rc::try_unwrap(resource_rc)
- .map_err(|_| custom_error("Busy", "TCP stream is currently in use"))?;
+ .map_err(|_| HttpStartError::TcpStreamInUse)?;
let (read_half, write_half) = resource.into_inner();
let tcp_stream = read_half.reunite(write_half)?;
let addr = tcp_stream.local_addr()?;
- return http_create_conn_resource(state, tcp_stream, addr, "http");
+ return Ok(http_create_conn_resource(state, tcp_stream, addr, "http"));
}
if let Ok(resource_rc) = state
@@ -45,11 +61,11 @@ fn op_http_start(
// process of starting a HTTP server on top of this TLS connection, so we just return a Busy error.
// See also: https://github.com/denoland/deno/pull/16242
let resource = Rc::try_unwrap(resource_rc)
- .map_err(|_| custom_error("Busy", "TLS stream is currently in use"))?;
+ .map_err(|_| HttpStartError::TlsStreamInUse)?;
let (read_half, write_half) = resource.into_inner();
let tls_stream = read_half.unsplit(write_half);
let addr = tls_stream.local_addr()?;
- return http_create_conn_resource(state, tls_stream, addr, "https");
+ return Ok(http_create_conn_resource(state, tls_stream, addr, "https"));
}
#[cfg(unix)]
@@ -61,12 +77,17 @@ fn op_http_start(
// process of starting a HTTP server on top of this UNIX socket, so we just return a Busy error.
// See also: https://github.com/denoland/deno/pull/16242
let resource = Rc::try_unwrap(resource_rc)
- .map_err(|_| custom_error("Busy", "Unix socket is currently in use"))?;
+ .map_err(|_| HttpStartError::UnixSocketInUse)?;
let (read_half, write_half) = resource.into_inner();
let unix_stream = read_half.reunite(write_half)?;
let addr = unix_stream.local_addr()?;
- return http_create_conn_resource(state, unix_stream, addr, "http+unix");
+ return Ok(http_create_conn_resource(
+ state,
+ unix_stream,
+ addr,
+ "http+unix",
+ ));
}
- Err(bad_resource_id())
+ Err(HttpStartError::Other(deno_core::error::bad_resource_id()))
}
diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs
index feed5052b..c2e402f33 100644
--- a/runtime/ops/mod.rs
+++ b/runtime/ops/mod.rs
@@ -4,12 +4,12 @@ pub mod bootstrap;
pub mod fs_events;
pub mod http;
pub mod os;
+pub mod otel;
pub mod permissions;
pub mod process;
pub mod runtime;
pub mod signal;
pub mod tty;
-mod utils;
pub mod web_worker;
pub mod worker_host;
diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs
index bd9260e97..74c708c53 100644
--- a/runtime/ops/os/mod.rs
+++ b/runtime/ops/os/mod.rs
@@ -1,9 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use super::utils::into_string;
+use crate::sys_info;
use crate::worker::ExitCode;
-use deno_core::error::type_error;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::v8;
use deno_core::OpState;
@@ -14,8 +12,6 @@ use serde::Serialize;
use std::collections::HashMap;
use std::env;
-mod sys_info;
-
deno_core::extension!(
deno_os,
ops = [
@@ -73,9 +69,27 @@ deno_core::extension!(
},
);
+#[derive(Debug, thiserror::Error)]
+pub enum OsError {
+ #[error(transparent)]
+ Permission(#[from] deno_permissions::PermissionCheckError),
+ #[error("File name or path {0:?} is not valid UTF-8")]
+ InvalidUtf8(std::ffi::OsString),
+ #[error("Key is an empty string.")]
+ EnvEmptyKey,
+ #[error("Key contains invalid characters: {0:?}")]
+ EnvInvalidKey(String),
+ #[error("Value contains invalid characters: {0:?}")]
+ EnvInvalidValue(String),
+ #[error(transparent)]
+ Var(#[from] env::VarError),
+ #[error("{0}")]
+ Io(#[from] std::io::Error),
+}
+
#[op2]
#[string]
-fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> {
+fn op_exec_path(state: &mut OpState) -> Result<String, OsError> {
let current_exe = env::current_exe().unwrap();
state
.borrow_mut::<PermissionsContainer>()
@@ -83,7 +97,10 @@ fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> {
// normalize path so it doesn't include '.' or '..' components
let path = normalize_path(current_exe);
- into_string(path.into_os_string())
+ path
+ .into_os_string()
+ .into_string()
+ .map_err(OsError::InvalidUtf8)
}
#[op2(fast)]
@@ -91,20 +108,16 @@ fn op_set_env(
state: &mut OpState,
#[string] key: &str,
#[string] value: &str,
-) -> Result<(), AnyError> {
+) -> Result<(), OsError> {
state.borrow_mut::<PermissionsContainer>().check_env(key)?;
if key.is_empty() {
- return Err(type_error("Key is an empty string."));
+ return Err(OsError::EnvEmptyKey);
}
if key.contains(&['=', '\0'] as &[char]) {
- return Err(type_error(format!(
- "Key contains invalid characters: {key:?}"
- )));
+ return Err(OsError::EnvInvalidKey(key.to_string()));
}
if value.contains('\0') {
- return Err(type_error(format!(
- "Value contains invalid characters: {value:?}"
- )));
+ return Err(OsError::EnvInvalidValue(value.to_string()));
}
env::set_var(key, value);
Ok(())
@@ -112,7 +125,9 @@ fn op_set_env(
#[op2]
#[serde]
-fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> {
+fn op_env(
+ state: &mut OpState,
+) -> Result<HashMap<String, String>, deno_core::error::AnyError> {
state.borrow_mut::<PermissionsContainer>().check_env_all()?;
Ok(env::vars().collect())
}
@@ -122,7 +137,7 @@ fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> {
fn op_get_env(
state: &mut OpState,
#[string] key: String,
-) -> Result<Option<String>, AnyError> {
+) -> Result<Option<String>, OsError> {
let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key);
if !skip_permission_check {
@@ -130,13 +145,11 @@ fn op_get_env(
}
if key.is_empty() {
- return Err(type_error("Key is an empty string."));
+ return Err(OsError::EnvEmptyKey);
}
if key.contains(&['=', '\0'] as &[char]) {
- return Err(type_error(format!(
- "Key contains invalid characters: {key:?}"
- )));
+ return Err(OsError::EnvInvalidKey(key.to_string()));
}
let r = match env::var(key) {
@@ -150,10 +163,10 @@ fn op_get_env(
fn op_delete_env(
state: &mut OpState,
#[string] key: String,
-) -> Result<(), AnyError> {
+) -> Result<(), OsError> {
state.borrow_mut::<PermissionsContainer>().check_env(&key)?;
if key.is_empty() || key.contains(&['=', '\0'] as &[char]) {
- return Err(type_error("Key contains invalid characters."));
+ return Err(OsError::EnvInvalidKey(key.to_string()));
}
env::remove_var(key);
Ok(())
@@ -173,12 +186,14 @@ fn op_get_exit_code(state: &mut OpState) -> i32 {
#[op2(fast)]
fn op_exit(state: &mut OpState) {
let code = state.borrow::<ExitCode>().get();
- std::process::exit(code)
+ crate::exit(code)
}
#[op2]
#[serde]
-fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> {
+fn op_loadavg(
+ state: &mut OpState,
+) -> Result<(f64, f64, f64), deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("loadavg", "Deno.loadavg()")?;
@@ -187,7 +202,9 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> {
#[op2]
#[string]
-fn op_hostname(state: &mut OpState) -> Result<String, AnyError> {
+fn op_hostname(
+ state: &mut OpState,
+) -> Result<String, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("hostname", "Deno.hostname()")?;
@@ -196,7 +213,9 @@ fn op_hostname(state: &mut OpState) -> Result<String, AnyError> {
#[op2]
#[string]
-fn op_os_release(state: &mut OpState) -> Result<String, AnyError> {
+fn op_os_release(
+ state: &mut OpState,
+) -> Result<String, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("osRelease", "Deno.osRelease()")?;
@@ -207,7 +226,7 @@ fn op_os_release(state: &mut OpState) -> Result<String, AnyError> {
#[serde]
fn op_network_interfaces(
state: &mut OpState,
-) -> Result<Vec<NetworkInterface>, AnyError> {
+) -> Result<Vec<NetworkInterface>, OsError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("networkInterfaces", "Deno.networkInterfaces()")?;
@@ -259,7 +278,7 @@ impl From<netif::Interface> for NetworkInterface {
#[serde]
fn op_system_memory_info(
state: &mut OpState,
-) -> Result<Option<sys_info::MemInfo>, AnyError> {
+) -> Result<Option<sys_info::MemInfo>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?;
@@ -269,7 +288,9 @@ fn op_system_memory_info(
#[cfg(not(windows))]
#[op2]
#[smi]
-fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
+fn op_gid(
+ state: &mut OpState,
+) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("gid", "Deno.gid()")?;
@@ -283,7 +304,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(windows)]
#[op2]
#[smi]
-fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
+fn op_gid(
+ state: &mut OpState,
+) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("gid", "Deno.gid()")?;
@@ -293,7 +316,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(not(windows))]
#[op2]
#[smi]
-fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
+fn op_uid(
+ state: &mut OpState,
+) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("uid", "Deno.uid()")?;
@@ -307,7 +332,9 @@ fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(windows)]
#[op2]
#[smi]
-fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
+fn op_uid(
+ state: &mut OpState,
+) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("uid", "Deno.uid()")?;
@@ -485,7 +512,7 @@ fn rss() -> usize {
}
}
-fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> {
+fn os_uptime(state: &mut OpState) -> Result<u64, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("osUptime", "Deno.osUptime()")?;
@@ -494,6 +521,8 @@ fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> {
#[op2(fast)]
#[number]
-fn op_os_uptime(state: &mut OpState) -> Result<u64, AnyError> {
+fn op_os_uptime(
+ state: &mut OpState,
+) -> Result<u64, deno_core::error::AnyError> {
os_uptime(state)
}
diff --git a/runtime/ops/os/sys_info.rs b/runtime/ops/os/sys_info.rs
deleted file mode 100644
index cffc90e9d..000000000
--- a/runtime/ops/os/sys_info.rs
+++ /dev/null
@@ -1,425 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-#[cfg(target_family = "windows")]
-use std::sync::Once;
-
-type LoadAvg = (f64, f64, f64);
-const DEFAULT_LOADAVG: LoadAvg = (0.0, 0.0, 0.0);
-
-pub fn loadavg() -> LoadAvg {
- #[cfg(any(target_os = "android", 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")]
- {
- #[allow(clippy::disallowed_methods)]
- match std::fs::read_to_string("/proc/sys/kernel/osrelease") {
- Ok(mut s) => {
- s.pop(); // pop '\n'
- s
- }
- _ => String::from(""),
- }
- }
- #[cfg(target_os = "android")]
- {
- let mut info = std::mem::MaybeUninit::uninit();
- // SAFETY: `info` is a valid pointer to a `libc::utsname` struct.
- let res = unsafe { libc::uname(info.as_mut_ptr()) };
- if res != 0 {
- return String::from("");
- }
- // SAFETY: `uname` returns 0 on success, and `info` is initialized.
- let mut info = unsafe { info.assume_init() };
- let len = info.release.len();
- info.release[len - 1] = 0;
- // SAFETY: `info.release` is a valid pointer and NUL-terminated.
- let c_str = unsafe { std::ffi::CStr::from_ptr(info.release.as_ptr()) };
- c_str.to_string_lossy().into_owned()
- }
- #[cfg(any(
- target_vendor = "apple",
- target_os = "freebsd",
- target_os = "openbsd"
- ))]
- {
- 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
- )
- }
- }
-}
-
-#[cfg(target_family = "windows")]
-static WINSOCKET_INIT: Once = Once::new();
-
-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::mem;
- use std::os::windows::ffi::OsStringExt;
- use winapi::shared::minwindef::MAKEWORD;
- use winapi::um::winsock2::GetHostNameW;
- use winapi::um::winsock2::WSAStartup;
-
- let namelen = 256;
- let mut name: Vec<u16> = vec![0u16; namelen];
- // Start winsock to make `GetHostNameW` work correctly
- // https://github.com/retep998/winapi-rs/issues/296
- // SAFETY: winapi call
- WINSOCKET_INIT.call_once(|| unsafe {
- let mut data = mem::zeroed();
- let wsa_startup_result = WSAStartup(MAKEWORD(2, 2), &mut data);
- if wsa_startup_result != 0 {
- panic!("Failed to start winsocket");
- }
- });
- 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(any(target_os = "android", 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.available = mem_info.free;
- mem_info.buffers = info.bufferram * mem_unit;
- }
-
- // Gets the available memory from /proc/meminfo in linux for compatibility
- #[allow(clippy::disallowed_methods)]
- if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
- let line = meminfo.lines().find(|l| l.starts_with("MemAvailable:"));
- if let Some(line) = line {
- let mem = line.split_whitespace().nth(1);
- let mem = mem.and_then(|v| v.parse::<u64>().ok());
- mem_info.available = mem.unwrap_or(0) * 1024;
- }
- }
- }
- #[cfg(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,
- );
-
- 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 = 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::psapi::GetPerformanceInfo;
- use winapi::um::psapi::PERFORMANCE_INFORMATION;
- 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;
- mem_info.available = 0;
- mem_info.free = stat.ullAvailPhys;
- mem_info.cached = 0;
- mem_info.buffers = 0;
-
- // `stat.ullTotalPageFile` is reliable only from GetPerformanceInfo()
- //
- // See https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
- // and https://github.com/GuillaumeGomez/sysinfo/issues/534
-
- let mut perf_info = mem::MaybeUninit::<PERFORMANCE_INFORMATION>::uninit();
- let result = GetPerformanceInfo(
- perf_info.as_mut_ptr(),
- mem::size_of::<PERFORMANCE_INFORMATION>() as minwindef::DWORD,
- );
- if result == minwindef::TRUE {
- let perf_info = perf_info.assume_init();
- let swap_total = perf_info.PageSize
- * perf_info
- .CommitLimit
- .saturating_sub(perf_info.PhysicalTotal);
- let swap_free = perf_info.PageSize
- * perf_info
- .CommitLimit
- .saturating_sub(perf_info.PhysicalTotal)
- .saturating_sub(perf_info.PhysicalAvailable);
- mem_info.swap_total = (swap_total / 1000) as u64;
- mem_info.swap_free = (swap_free / 1000) as u64;
- }
- }
- }
-
- Some(mem_info)
-}
-
-pub fn os_uptime() -> u64 {
- let uptime: u64;
-
- #[cfg(any(target_os = "android", 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()) };
- uptime = if res == 0 {
- // SAFETY: `sysinfo` initializes the struct.
- let info = unsafe { info.assume_init() };
- info.uptime as u64
- } else {
- 0
- }
- }
-
- #[cfg(any(
- target_vendor = "apple",
- target_os = "freebsd",
- target_os = "openbsd"
- ))]
- {
- use std::mem;
- use std::time::Duration;
- use std::time::SystemTime;
- let mut request = [libc::CTL_KERN, libc::KERN_BOOTTIME];
- // SAFETY: `boottime` is only accessed if sysctl() succeeds
- // and agrees with the `size` set by sysctl().
- let mut boottime: libc::timeval = unsafe { mem::zeroed() };
- let mut size: libc::size_t = mem::size_of_val(&boottime) as libc::size_t;
- // SAFETY: `sysctl` is thread-safe.
- let res = unsafe {
- libc::sysctl(
- &mut request[0],
- 2,
- &mut boottime as *mut libc::timeval as *mut libc::c_void,
- &mut size,
- std::ptr::null_mut(),
- 0,
- )
- };
- uptime = if res == 0 {
- SystemTime::now()
- .duration_since(SystemTime::UNIX_EPOCH)
- .map(|d| {
- (d - Duration::new(
- boottime.tv_sec as u64,
- boottime.tv_usec as u32 * 1000,
- ))
- .as_secs()
- })
- .unwrap_or_default()
- } else {
- 0
- }
- }
-
- #[cfg(target_family = "windows")]
- // SAFETY: windows API usage
- unsafe {
- // Windows is the only one that returns `uptime` in millisecond precision,
- // so we need to get the seconds out of it to be in sync with other envs.
- uptime = winapi::um::sysinfoapi::GetTickCount64() / 1000;
- }
-
- uptime
-}
diff --git a/runtime/ops/otel.rs b/runtime/ops/otel.rs
new file mode 100644
index 000000000..61a7b0ef0
--- /dev/null
+++ b/runtime/ops/otel.rs
@@ -0,0 +1,855 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::tokio_util::create_basic_runtime;
+use deno_core::anyhow;
+use deno_core::anyhow::anyhow;
+use deno_core::futures::channel::mpsc;
+use deno_core::futures::channel::mpsc::UnboundedSender;
+use deno_core::futures::future::BoxFuture;
+use deno_core::futures::stream;
+use deno_core::futures::Stream;
+use deno_core::futures::StreamExt;
+use deno_core::op2;
+use deno_core::v8;
+use deno_core::OpState;
+use once_cell::sync::Lazy;
+use once_cell::sync::OnceCell;
+use opentelemetry::logs::AnyValue;
+use opentelemetry::logs::LogRecord as LogRecordTrait;
+use opentelemetry::logs::Severity;
+use opentelemetry::trace::SpanContext;
+use opentelemetry::trace::SpanId;
+use opentelemetry::trace::SpanKind;
+use opentelemetry::trace::Status as SpanStatus;
+use opentelemetry::trace::TraceFlags;
+use opentelemetry::trace::TraceId;
+use opentelemetry::Key;
+use opentelemetry::KeyValue;
+use opentelemetry::StringValue;
+use opentelemetry::Value;
+use opentelemetry_otlp::HttpExporterBuilder;
+use opentelemetry_otlp::Protocol;
+use opentelemetry_otlp::WithExportConfig;
+use opentelemetry_otlp::WithHttpConfig;
+use opentelemetry_sdk::export::trace::SpanData;
+use opentelemetry_sdk::logs::BatchLogProcessor;
+use opentelemetry_sdk::logs::LogProcessor as LogProcessorTrait;
+use opentelemetry_sdk::logs::LogRecord;
+use opentelemetry_sdk::trace::BatchSpanProcessor;
+use opentelemetry_sdk::trace::SpanProcessor as SpanProcessorTrait;
+use opentelemetry_sdk::Resource;
+use opentelemetry_semantic_conventions::resource::PROCESS_RUNTIME_NAME;
+use opentelemetry_semantic_conventions::resource::PROCESS_RUNTIME_VERSION;
+use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_LANGUAGE;
+use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_NAME;
+use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_VERSION;
+use serde::Deserialize;
+use serde::Serialize;
+use std::borrow::Cow;
+use std::env;
+use std::fmt::Debug;
+use std::pin::Pin;
+use std::task::Context;
+use std::task::Poll;
+use std::thread;
+use std::time::Duration;
+use std::time::SystemTime;
+
+type SpanProcessor = BatchSpanProcessor<OtelSharedRuntime>;
+type LogProcessor = BatchLogProcessor<OtelSharedRuntime>;
+
+deno_core::extension!(
+ deno_otel,
+ ops = [
+ op_otel_log,
+ op_otel_instrumentation_scope_create_and_enter,
+ op_otel_instrumentation_scope_enter,
+ op_otel_instrumentation_scope_enter_builtin,
+ op_otel_span_start,
+ op_otel_span_continue,
+ op_otel_span_attribute,
+ op_otel_span_attribute2,
+ op_otel_span_attribute3,
+ op_otel_span_set_dropped,
+ op_otel_span_flush,
+ ],
+);
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct OtelConfig {
+ pub runtime_name: Cow<'static, str>,
+ pub runtime_version: Cow<'static, str>,
+ pub console: OtelConsoleConfig,
+ pub deterministic: bool,
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+#[repr(u8)]
+pub enum OtelConsoleConfig {
+ Ignore = 0,
+ Capture = 1,
+ Replace = 2,
+}
+
+impl Default for OtelConfig {
+ fn default() -> Self {
+ Self {
+ runtime_name: Cow::Borrowed(env!("CARGO_PKG_NAME")),
+ runtime_version: Cow::Borrowed(env!("CARGO_PKG_VERSION")),
+ console: OtelConsoleConfig::Capture,
+ deterministic: false,
+ }
+ }
+}
+
+static OTEL_SHARED_RUNTIME_SPAWN_TASK_TX: Lazy<
+ UnboundedSender<BoxFuture<'static, ()>>,
+> = Lazy::new(otel_create_shared_runtime);
+
+fn otel_create_shared_runtime() -> UnboundedSender<BoxFuture<'static, ()>> {
+ let (spawn_task_tx, mut spawn_task_rx) =
+ mpsc::unbounded::<BoxFuture<'static, ()>>();
+
+ thread::spawn(move || {
+ let rt = create_basic_runtime();
+ rt.block_on(async move {
+ while let Some(task) = spawn_task_rx.next().await {
+ tokio::spawn(task);
+ }
+ });
+ });
+
+ spawn_task_tx
+}
+
+#[derive(Clone, Copy)]
+struct OtelSharedRuntime;
+
+impl hyper::rt::Executor<BoxFuture<'static, ()>> for OtelSharedRuntime {
+ fn execute(&self, fut: BoxFuture<'static, ()>) {
+ (*OTEL_SHARED_RUNTIME_SPAWN_TASK_TX)
+ .unbounded_send(fut)
+ .expect("failed to send task to shared OpenTelemetry runtime");
+ }
+}
+
+impl opentelemetry_sdk::runtime::Runtime for OtelSharedRuntime {
+ type Interval = Pin<Box<dyn Stream<Item = ()> + Send + 'static>>;
+ type Delay = Pin<Box<tokio::time::Sleep>>;
+
+ fn interval(&self, period: Duration) -> Self::Interval {
+ stream::repeat(())
+ .then(move |_| tokio::time::sleep(period))
+ .boxed()
+ }
+
+ fn spawn(&self, future: BoxFuture<'static, ()>) {
+ (*OTEL_SHARED_RUNTIME_SPAWN_TASK_TX)
+ .unbounded_send(future)
+ .expect("failed to send task to shared OpenTelemetry runtime");
+ }
+
+ fn delay(&self, duration: Duration) -> Self::Delay {
+ Box::pin(tokio::time::sleep(duration))
+ }
+}
+
+impl opentelemetry_sdk::runtime::RuntimeChannel for OtelSharedRuntime {
+ type Receiver<T: Debug + Send> = BatchMessageChannelReceiver<T>;
+ type Sender<T: Debug + Send> = BatchMessageChannelSender<T>;
+
+ fn batch_message_channel<T: Debug + Send>(
+ &self,
+ capacity: usize,
+ ) -> (Self::Sender<T>, Self::Receiver<T>) {
+ let (batch_tx, batch_rx) = tokio::sync::mpsc::channel::<T>(capacity);
+ (batch_tx.into(), batch_rx.into())
+ }
+}
+
+#[derive(Debug)]
+pub struct BatchMessageChannelSender<T: Send> {
+ sender: tokio::sync::mpsc::Sender<T>,
+}
+
+impl<T: Send> From<tokio::sync::mpsc::Sender<T>>
+ for BatchMessageChannelSender<T>
+{
+ fn from(sender: tokio::sync::mpsc::Sender<T>) -> Self {
+ Self { sender }
+ }
+}
+
+impl<T: Send> opentelemetry_sdk::runtime::TrySend
+ for BatchMessageChannelSender<T>
+{
+ type Message = T;
+
+ fn try_send(
+ &self,
+ item: Self::Message,
+ ) -> Result<(), opentelemetry_sdk::runtime::TrySendError> {
+ self.sender.try_send(item).map_err(|err| match err {
+ tokio::sync::mpsc::error::TrySendError::Full(_) => {
+ opentelemetry_sdk::runtime::TrySendError::ChannelFull
+ }
+ tokio::sync::mpsc::error::TrySendError::Closed(_) => {
+ opentelemetry_sdk::runtime::TrySendError::ChannelClosed
+ }
+ })
+ }
+}
+
+pub struct BatchMessageChannelReceiver<T> {
+ receiver: tokio::sync::mpsc::Receiver<T>,
+}
+
+impl<T> From<tokio::sync::mpsc::Receiver<T>>
+ for BatchMessageChannelReceiver<T>
+{
+ fn from(receiver: tokio::sync::mpsc::Receiver<T>) -> Self {
+ Self { receiver }
+ }
+}
+
+impl<T> Stream for BatchMessageChannelReceiver<T> {
+ type Item = T;
+
+ fn poll_next(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ ) -> Poll<Option<Self::Item>> {
+ self.receiver.poll_recv(cx)
+ }
+}
+
+mod hyper_client {
+ use http_body_util::BodyExt;
+ use http_body_util::Full;
+ use hyper::body::Body as HttpBody;
+ use hyper::body::Frame;
+ use hyper_util::client::legacy::connect::HttpConnector;
+ use hyper_util::client::legacy::Client;
+ use opentelemetry_http::Bytes;
+ use opentelemetry_http::HttpError;
+ use opentelemetry_http::Request;
+ use opentelemetry_http::Response;
+ use opentelemetry_http::ResponseExt;
+ use std::fmt::Debug;
+ use std::pin::Pin;
+ use std::task::Poll;
+ use std::task::{self};
+
+ use super::OtelSharedRuntime;
+
+ // same as opentelemetry_http::HyperClient except it uses OtelSharedRuntime
+ #[derive(Debug, Clone)]
+ pub struct HyperClient {
+ inner: Client<HttpConnector, Body>,
+ }
+
+ impl HyperClient {
+ pub fn new() -> Self {
+ Self {
+ inner: Client::builder(OtelSharedRuntime).build(HttpConnector::new()),
+ }
+ }
+ }
+
+ #[async_trait::async_trait]
+ impl opentelemetry_http::HttpClient for HyperClient {
+ async fn send(
+ &self,
+ request: Request<Vec<u8>>,
+ ) -> Result<Response<Bytes>, HttpError> {
+ let (parts, body) = request.into_parts();
+ let request = Request::from_parts(parts, Body(Full::from(body)));
+ let mut response = self.inner.request(request).await?;
+ let headers = std::mem::take(response.headers_mut());
+
+ let mut http_response = Response::builder()
+ .status(response.status())
+ .body(response.into_body().collect().await?.to_bytes())?;
+ *http_response.headers_mut() = headers;
+
+ Ok(http_response.error_for_status()?)
+ }
+ }
+
+ #[pin_project::pin_project]
+ pub struct Body(#[pin] Full<Bytes>);
+
+ impl HttpBody for Body {
+ type Data = Bytes;
+ type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
+
+ #[inline]
+ fn poll_frame(
+ self: Pin<&mut Self>,
+ cx: &mut task::Context<'_>,
+ ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
+ self.project().0.poll_frame(cx).map_err(Into::into)
+ }
+
+ #[inline]
+ fn is_end_stream(&self) -> bool {
+ self.0.is_end_stream()
+ }
+
+ #[inline]
+ fn size_hint(&self) -> hyper::body::SizeHint {
+ self.0.size_hint()
+ }
+ }
+}
+
+static OTEL_PROCESSORS: OnceCell<(SpanProcessor, LogProcessor)> =
+ OnceCell::new();
+
+static BUILT_IN_INSTRUMENTATION_SCOPE: OnceCell<
+ opentelemetry::InstrumentationScope,
+> = OnceCell::new();
+
+pub fn init(config: OtelConfig) -> anyhow::Result<()> {
+ // Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_*
+ // crates don't do this automatically.
+ // TODO(piscisaureus): enable GRPC support.
+ let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() {
+ Ok("http/protobuf") => Protocol::HttpBinary,
+ Ok("http/json") => Protocol::HttpJson,
+ Ok("") | Err(env::VarError::NotPresent) => {
+ return Ok(());
+ }
+ Ok(protocol) => {
+ return Err(anyhow!(
+ "Env var OTEL_EXPORTER_OTLP_PROTOCOL specifies an unsupported protocol: {}",
+ protocol
+ ));
+ }
+ Err(err) => {
+ return Err(anyhow!(
+ "Failed to read env var OTEL_EXPORTER_OTLP_PROTOCOL: {}",
+ err
+ ));
+ }
+ };
+
+ // Define the resource attributes that will be attached to all log records.
+ // These attributes are sourced as follows (in order of precedence):
+ // * The `service.name` attribute from the `OTEL_SERVICE_NAME` env var.
+ // * Additional attributes from the `OTEL_RESOURCE_ATTRIBUTES` env var.
+ // * Default attribute values defined here.
+ // TODO(piscisaureus): add more default attributes (e.g. script path).
+ let mut resource = Resource::default();
+
+ // Add the runtime name and version to the resource attributes. Also override
+ // the `telemetry.sdk` attributes to include the Deno runtime.
+ resource = resource.merge(&Resource::new(vec![
+ KeyValue::new(PROCESS_RUNTIME_NAME, config.runtime_name),
+ KeyValue::new(PROCESS_RUNTIME_VERSION, config.runtime_version.clone()),
+ KeyValue::new(
+ TELEMETRY_SDK_LANGUAGE,
+ format!(
+ "deno-{}",
+ resource.get(Key::new(TELEMETRY_SDK_LANGUAGE)).unwrap()
+ ),
+ ),
+ KeyValue::new(
+ TELEMETRY_SDK_NAME,
+ format!(
+ "deno-{}",
+ resource.get(Key::new(TELEMETRY_SDK_NAME)).unwrap()
+ ),
+ ),
+ KeyValue::new(
+ TELEMETRY_SDK_VERSION,
+ format!(
+ "{}-{}",
+ config.runtime_version,
+ resource.get(Key::new(TELEMETRY_SDK_VERSION)).unwrap()
+ ),
+ ),
+ ]));
+
+ // The OTLP endpoint is automatically picked up from the
+ // `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Additional headers can
+ // be specified using `OTEL_EXPORTER_OTLP_HEADERS`.
+
+ let client = hyper_client::HyperClient::new();
+
+ let span_exporter = HttpExporterBuilder::default()
+ .with_http_client(client.clone())
+ .with_protocol(protocol)
+ .build_span_exporter()?;
+ let mut span_processor =
+ BatchSpanProcessor::builder(span_exporter, OtelSharedRuntime).build();
+ span_processor.set_resource(&resource);
+
+ let log_exporter = HttpExporterBuilder::default()
+ .with_http_client(client)
+ .with_protocol(protocol)
+ .build_log_exporter()?;
+ let log_processor =
+ BatchLogProcessor::builder(log_exporter, OtelSharedRuntime).build();
+ log_processor.set_resource(&resource);
+
+ OTEL_PROCESSORS
+ .set((span_processor, log_processor))
+ .map_err(|_| anyhow!("failed to init otel"))?;
+
+ let builtin_instrumentation_scope =
+ opentelemetry::InstrumentationScope::builder("deno")
+ .with_version(config.runtime_version.clone())
+ .build();
+ BUILT_IN_INSTRUMENTATION_SCOPE
+ .set(builtin_instrumentation_scope)
+ .map_err(|_| anyhow!("failed to init otel"))?;
+
+ Ok(())
+}
+
+/// This function is called by the runtime whenever it is about to call
+/// `process::exit()`, to ensure that all OpenTelemetry logs are properly
+/// flushed before the process terminates.
+pub fn flush() {
+ if let Some((span_processor, log_processor)) = OTEL_PROCESSORS.get() {
+ let _ = span_processor.force_flush();
+ let _ = log_processor.force_flush();
+ }
+}
+
+pub fn handle_log(record: &log::Record) {
+ use log::Level;
+
+ let Some((_, log_processor)) = OTEL_PROCESSORS.get() else {
+ return;
+ };
+
+ let mut log_record = LogRecord::default();
+
+ log_record.set_observed_timestamp(SystemTime::now());
+ log_record.set_severity_number(match record.level() {
+ Level::Error => Severity::Error,
+ Level::Warn => Severity::Warn,
+ Level::Info => Severity::Info,
+ Level::Debug => Severity::Debug,
+ Level::Trace => Severity::Trace,
+ });
+ log_record.set_severity_text(record.level().as_str());
+ log_record.set_body(record.args().to_string().into());
+ log_record.set_target(record.metadata().target().to_string());
+
+ struct Visitor<'s>(&'s mut LogRecord);
+
+ impl<'s, 'kvs> log::kv::VisitSource<'kvs> for Visitor<'s> {
+ fn visit_pair(
+ &mut self,
+ key: log::kv::Key<'kvs>,
+ value: log::kv::Value<'kvs>,
+ ) -> Result<(), log::kv::Error> {
+ #[allow(clippy::manual_map)]
+ let value = if let Some(v) = value.to_bool() {
+ Some(AnyValue::Boolean(v))
+ } else if let Some(v) = value.to_borrowed_str() {
+ Some(AnyValue::String(v.to_owned().into()))
+ } else if let Some(v) = value.to_f64() {
+ Some(AnyValue::Double(v))
+ } else if let Some(v) = value.to_i64() {
+ Some(AnyValue::Int(v))
+ } else {
+ None
+ };
+
+ if let Some(value) = value {
+ let key = Key::from(key.as_str().to_owned());
+ self.0.add_attribute(key, value);
+ }
+
+ Ok(())
+ }
+ }
+
+ let _ = record.key_values().visit(&mut Visitor(&mut log_record));
+
+ log_processor.emit(
+ &mut log_record,
+ BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(),
+ );
+}
+
+fn parse_trace_id(
+ scope: &mut v8::HandleScope<'_>,
+ trace_id: v8::Local<'_, v8::Value>,
+) -> TraceId {
+ if let Ok(string) = trace_id.try_cast() {
+ let value_view = v8::ValueView::new(scope, string);
+ match value_view.data() {
+ v8::ValueViewData::OneByte(bytes) => {
+ TraceId::from_hex(&String::from_utf8_lossy(bytes))
+ .unwrap_or(TraceId::INVALID)
+ }
+
+ _ => TraceId::INVALID,
+ }
+ } else if let Ok(uint8array) = trace_id.try_cast::<v8::Uint8Array>() {
+ let data = uint8array.data();
+ let byte_length = uint8array.byte_length();
+ if byte_length != 16 {
+ return TraceId::INVALID;
+ }
+ // SAFETY: We have ensured that the byte length is 16, so it is safe to
+ // cast the data to an array of 16 bytes.
+ let bytes = unsafe { &*(data as *const u8 as *const [u8; 16]) };
+ TraceId::from_bytes(*bytes)
+ } else {
+ TraceId::INVALID
+ }
+}
+
+fn parse_span_id(
+ scope: &mut v8::HandleScope<'_>,
+ span_id: v8::Local<'_, v8::Value>,
+) -> SpanId {
+ if let Ok(string) = span_id.try_cast() {
+ let value_view = v8::ValueView::new(scope, string);
+ match value_view.data() {
+ v8::ValueViewData::OneByte(bytes) => {
+ SpanId::from_hex(&String::from_utf8_lossy(bytes))
+ .unwrap_or(SpanId::INVALID)
+ }
+ _ => SpanId::INVALID,
+ }
+ } else if let Ok(uint8array) = span_id.try_cast::<v8::Uint8Array>() {
+ let data = uint8array.data();
+ let byte_length = uint8array.byte_length();
+ if byte_length != 8 {
+ return SpanId::INVALID;
+ }
+ // SAFETY: We have ensured that the byte length is 8, so it is safe to
+ // cast the data to an array of 8 bytes.
+ let bytes = unsafe { &*(data as *const u8 as *const [u8; 8]) };
+ SpanId::from_bytes(*bytes)
+ } else {
+ SpanId::INVALID
+ }
+}
+
+macro_rules! attr {
+ ($scope:ident, $attributes:expr $(=> $dropped_attributes_count:expr)?, $name:expr, $value:expr) => {
+ let name = if let Ok(name) = $name.try_cast() {
+ let view = v8::ValueView::new($scope, name);
+ match view.data() {
+ v8::ValueViewData::OneByte(bytes) => {
+ Some(String::from_utf8_lossy(bytes).into_owned())
+ }
+ v8::ValueViewData::TwoByte(bytes) => {
+ Some(String::from_utf16_lossy(bytes))
+ }
+ }
+ } else {
+ None
+ };
+ let value = if let Ok(string) = $value.try_cast::<v8::String>() {
+ Some(Value::String(StringValue::from({
+ let x = v8::ValueView::new($scope, string);
+ match x.data() {
+ v8::ValueViewData::OneByte(bytes) => {
+ String::from_utf8_lossy(bytes).into_owned()
+ }
+ v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes),
+ }
+ })))
+ } else if let Ok(number) = $value.try_cast::<v8::Number>() {
+ Some(Value::F64(number.value()))
+ } else if let Ok(boolean) = $value.try_cast::<v8::Boolean>() {
+ Some(Value::Bool(boolean.is_true()))
+ } else if let Ok(bigint) = $value.try_cast::<v8::BigInt>() {
+ let (i64_value, _lossless) = bigint.i64_value();
+ Some(Value::I64(i64_value))
+ } else {
+ None
+ };
+ if let (Some(name), Some(value)) = (name, value) {
+ $attributes.push(KeyValue::new(name, value));
+ }
+ $(
+ else {
+ $dropped_attributes_count += 1;
+ }
+ )?
+ };
+}
+
+#[derive(Debug, Clone)]
+struct InstrumentationScope(opentelemetry::InstrumentationScope);
+
+impl deno_core::GarbageCollected for InstrumentationScope {}
+
+#[op2]
+#[cppgc]
+fn op_otel_instrumentation_scope_create_and_enter(
+ state: &mut OpState,
+ #[string] name: String,
+ #[string] version: Option<String>,
+ #[string] schema_url: Option<String>,
+) -> InstrumentationScope {
+ let mut builder = opentelemetry::InstrumentationScope::builder(name);
+ if let Some(version) = version {
+ builder = builder.with_version(version);
+ }
+ if let Some(schema_url) = schema_url {
+ builder = builder.with_schema_url(schema_url);
+ }
+ let scope = InstrumentationScope(builder.build());
+ state.put(scope.clone());
+ scope
+}
+
+#[op2(fast)]
+fn op_otel_instrumentation_scope_enter(
+ state: &mut OpState,
+ #[cppgc] scope: &InstrumentationScope,
+) {
+ state.put(scope.clone());
+}
+
+#[op2(fast)]
+fn op_otel_instrumentation_scope_enter_builtin(state: &mut OpState) {
+ state.put(InstrumentationScope(
+ BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap().clone(),
+ ));
+}
+
+#[op2(fast)]
+fn op_otel_log(
+ scope: &mut v8::HandleScope<'_>,
+ #[string] message: String,
+ #[smi] level: i32,
+ trace_id: v8::Local<'_, v8::Value>,
+ span_id: v8::Local<'_, v8::Value>,
+ #[smi] trace_flags: u8,
+) {
+ let Some((_, log_processor)) = OTEL_PROCESSORS.get() else {
+ return;
+ };
+
+ // Convert the integer log level that ext/console uses to the corresponding
+ // OpenTelemetry log severity.
+ let severity = match level {
+ ..=0 => Severity::Debug,
+ 1 => Severity::Info,
+ 2 => Severity::Warn,
+ 3.. => Severity::Error,
+ };
+
+ let trace_id = parse_trace_id(scope, trace_id);
+ let span_id = parse_span_id(scope, span_id);
+
+ let mut log_record = LogRecord::default();
+
+ log_record.set_observed_timestamp(SystemTime::now());
+ log_record.set_body(message.into());
+ log_record.set_severity_number(severity);
+ log_record.set_severity_text(severity.name());
+ if trace_id != TraceId::INVALID && span_id != SpanId::INVALID {
+ log_record.set_trace_context(
+ trace_id,
+ span_id,
+ Some(TraceFlags::new(trace_flags)),
+ );
+ }
+
+ log_processor.emit(
+ &mut log_record,
+ BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(),
+ );
+}
+
+struct TemporarySpan(SpanData);
+
+#[allow(clippy::too_many_arguments)]
+#[op2(fast)]
+fn op_otel_span_start<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ state: &mut OpState,
+ trace_id: v8::Local<'s, v8::Value>,
+ span_id: v8::Local<'s, v8::Value>,
+ parent_span_id: v8::Local<'s, v8::Value>,
+ #[smi] span_kind: u8,
+ name: v8::Local<'s, v8::Value>,
+ start_time: f64,
+ end_time: f64,
+) -> Result<(), anyhow::Error> {
+ if let Some(temporary_span) = state.try_take::<TemporarySpan>() {
+ let Some((span_processor, _)) = OTEL_PROCESSORS.get() else {
+ return Ok(());
+ };
+ span_processor.on_end(temporary_span.0);
+ };
+
+ let Some(InstrumentationScope(instrumentation_scope)) =
+ state.try_borrow::<InstrumentationScope>()
+ else {
+ return Err(anyhow!("instrumentation scope not available"));
+ };
+
+ let trace_id = parse_trace_id(scope, trace_id);
+ if trace_id == TraceId::INVALID {
+ return Err(anyhow!("invalid trace_id"));
+ }
+
+ let span_id = parse_span_id(scope, span_id);
+ if span_id == SpanId::INVALID {
+ return Err(anyhow!("invalid span_id"));
+ }
+
+ let parent_span_id = parse_span_id(scope, parent_span_id);
+
+ let name = {
+ let x = v8::ValueView::new(scope, name.try_cast()?);
+ match x.data() {
+ v8::ValueViewData::OneByte(bytes) => {
+ String::from_utf8_lossy(bytes).into_owned()
+ }
+ v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes),
+ }
+ };
+
+ let temporary_span = TemporarySpan(SpanData {
+ span_context: SpanContext::new(
+ trace_id,
+ span_id,
+ TraceFlags::SAMPLED,
+ false,
+ Default::default(),
+ ),
+ parent_span_id,
+ span_kind: match span_kind {
+ 0 => SpanKind::Internal,
+ 1 => SpanKind::Server,
+ 2 => SpanKind::Client,
+ 3 => SpanKind::Producer,
+ 4 => SpanKind::Consumer,
+ _ => return Err(anyhow!("invalid span kind")),
+ },
+ name: Cow::Owned(name),
+ start_time: SystemTime::UNIX_EPOCH
+ .checked_add(std::time::Duration::from_secs_f64(start_time))
+ .ok_or_else(|| anyhow!("invalid start time"))?,
+ end_time: SystemTime::UNIX_EPOCH
+ .checked_add(std::time::Duration::from_secs_f64(end_time))
+ .ok_or_else(|| anyhow!("invalid start time"))?,
+ attributes: Vec::new(),
+ dropped_attributes_count: 0,
+ events: Default::default(),
+ links: Default::default(),
+ status: SpanStatus::Unset,
+ instrumentation_scope: instrumentation_scope.clone(),
+ });
+ state.put(temporary_span);
+
+ Ok(())
+}
+
+#[op2(fast)]
+fn op_otel_span_continue(
+ state: &mut OpState,
+ #[smi] status: u8,
+ #[string] error_description: Cow<'_, str>,
+) {
+ if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() {
+ temporary_span.0.status = match status {
+ 0 => SpanStatus::Unset,
+ 1 => SpanStatus::Ok,
+ 2 => SpanStatus::Error {
+ description: Cow::Owned(error_description.into_owned()),
+ },
+ _ => return,
+ };
+ }
+}
+
+#[op2(fast)]
+fn op_otel_span_attribute<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ state: &mut OpState,
+ #[smi] capacity: u32,
+ key: v8::Local<'s, v8::Value>,
+ value: v8::Local<'s, v8::Value>,
+) {
+ if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() {
+ temporary_span.0.attributes.reserve_exact(
+ (capacity as usize) - temporary_span.0.attributes.capacity(),
+ );
+ attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key, value);
+ }
+}
+
+#[op2(fast)]
+fn op_otel_span_attribute2<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ state: &mut OpState,
+ #[smi] capacity: u32,
+ key1: v8::Local<'s, v8::Value>,
+ value1: v8::Local<'s, v8::Value>,
+ key2: v8::Local<'s, v8::Value>,
+ value2: v8::Local<'s, v8::Value>,
+) {
+ if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() {
+ temporary_span.0.attributes.reserve_exact(
+ (capacity as usize) - temporary_span.0.attributes.capacity(),
+ );
+ attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1);
+ attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2);
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+#[op2(fast)]
+fn op_otel_span_attribute3<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ state: &mut OpState,
+ #[smi] capacity: u32,
+ key1: v8::Local<'s, v8::Value>,
+ value1: v8::Local<'s, v8::Value>,
+ key2: v8::Local<'s, v8::Value>,
+ value2: v8::Local<'s, v8::Value>,
+ key3: v8::Local<'s, v8::Value>,
+ value3: v8::Local<'s, v8::Value>,
+) {
+ if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() {
+ temporary_span.0.attributes.reserve_exact(
+ (capacity as usize) - temporary_span.0.attributes.capacity(),
+ );
+ attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1);
+ attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2);
+ attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key3, value3);
+ }
+}
+
+#[op2(fast)]
+fn op_otel_span_set_dropped(
+ state: &mut OpState,
+ #[smi] dropped_attributes_count: u32,
+ #[smi] dropped_links_count: u32,
+ #[smi] dropped_events_count: u32,
+) {
+ if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() {
+ temporary_span.0.dropped_attributes_count = dropped_attributes_count;
+ temporary_span.0.links.dropped_count = dropped_links_count;
+ temporary_span.0.events.dropped_count = dropped_events_count;
+ }
+}
+
+#[op2(fast)]
+fn op_otel_span_flush(state: &mut OpState) {
+ let Some(temporary_span) = state.try_take::<TemporarySpan>() else {
+ return;
+ };
+
+ let Some((span_processor, _)) = OTEL_PROCESSORS.get() else {
+ return;
+ };
+
+ span_processor.on_end(temporary_span.0);
+}
diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs
index 1dbc85259..9ad963f3b 100644
--- a/runtime/ops/permissions.rs
+++ b/runtime/ops/permissions.rs
@@ -2,8 +2,6 @@
use ::deno_permissions::PermissionState;
use ::deno_permissions::PermissionsContainer;
-use deno_core::error::custom_error;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use serde::Deserialize;
@@ -47,12 +45,26 @@ impl From<PermissionState> for PermissionStatus {
}
}
+#[derive(Debug, thiserror::Error)]
+pub enum PermissionError {
+ #[error("No such permission name: {0}")]
+ InvalidPermissionName(String),
+ #[error("{0}")]
+ PathResolve(#[from] ::deno_permissions::PathResolveError),
+ #[error("{0}")]
+ NetDescriptorParse(#[from] ::deno_permissions::NetDescriptorParseError),
+ #[error("{0}")]
+ SysDescriptorParse(#[from] ::deno_permissions::SysDescriptorParseError),
+ #[error("{0}")]
+ RunDescriptorParse(#[from] ::deno_permissions::RunDescriptorParseError),
+}
+
#[op2]
#[serde]
pub fn op_query_permission(
state: &mut OpState,
#[serde] args: PermissionArgs,
-) -> Result<PermissionStatus, AnyError> {
+) -> Result<PermissionStatus, PermissionError> {
let permissions = state.borrow::<PermissionsContainer>();
let perm = match args.name.as_ref() {
"read" => permissions.query_read(args.path.as_deref())?,
@@ -62,12 +74,7 @@ pub fn op_query_permission(
"sys" => permissions.query_sys(args.kind.as_deref())?,
"run" => permissions.query_run(args.command.as_deref())?,
"ffi" => permissions.query_ffi(args.path.as_deref())?,
- n => {
- return Err(custom_error(
- "ReferenceError",
- format!("No such permission name: {n}"),
- ))
- }
+ _ => return Err(PermissionError::InvalidPermissionName(args.name)),
};
Ok(PermissionStatus::from(perm))
}
@@ -77,7 +84,7 @@ pub fn op_query_permission(
pub fn op_revoke_permission(
state: &mut OpState,
#[serde] args: PermissionArgs,
-) -> Result<PermissionStatus, AnyError> {
+) -> Result<PermissionStatus, PermissionError> {
let permissions = state.borrow::<PermissionsContainer>();
let perm = match args.name.as_ref() {
"read" => permissions.revoke_read(args.path.as_deref())?,
@@ -87,12 +94,7 @@ pub fn op_revoke_permission(
"sys" => permissions.revoke_sys(args.kind.as_deref())?,
"run" => permissions.revoke_run(args.command.as_deref())?,
"ffi" => permissions.revoke_ffi(args.path.as_deref())?,
- n => {
- return Err(custom_error(
- "ReferenceError",
- format!("No such permission name: {n}"),
- ))
- }
+ _ => return Err(PermissionError::InvalidPermissionName(args.name)),
};
Ok(PermissionStatus::from(perm))
}
@@ -102,7 +104,7 @@ pub fn op_revoke_permission(
pub fn op_request_permission(
state: &mut OpState,
#[serde] args: PermissionArgs,
-) -> Result<PermissionStatus, AnyError> {
+) -> Result<PermissionStatus, PermissionError> {
let permissions = state.borrow::<PermissionsContainer>();
let perm = match args.name.as_ref() {
"read" => permissions.request_read(args.path.as_deref())?,
@@ -112,12 +114,7 @@ pub fn op_request_permission(
"sys" => permissions.request_sys(args.kind.as_deref())?,
"run" => permissions.request_run(args.command.as_deref())?,
"ffi" => permissions.request_ffi(args.path.as_deref())?,
- n => {
- return Err(custom_error(
- "ReferenceError",
- format!("No such permission name: {n}"),
- ))
- }
+ _ => return Err(PermissionError::InvalidPermissionName(args.name)),
};
Ok(PermissionStatus::from(perm))
}
diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs
index f6555e932..ee2f660dc 100644
--- a/runtime/ops/process.rs
+++ b/runtime/ops/process.rs
@@ -1,8 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use deno_core::anyhow::Context;
-use deno_core::error::type_error;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::serde_json;
use deno_core::AsyncMutFuture;
@@ -35,6 +32,7 @@ use tokio::process::Command;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
+use crate::ops::signal::SignalError;
#[cfg(unix)]
use std::os::unix::prelude::ExitStatusExt;
#[cfg(unix)]
@@ -105,11 +103,12 @@ impl StdioOrRid {
pub fn as_stdio(
&self,
state: &mut OpState,
- ) -> Result<std::process::Stdio, AnyError> {
+ ) -> Result<std::process::Stdio, ProcessError> {
match &self {
StdioOrRid::Stdio(val) => Ok(val.as_stdio()),
StdioOrRid::Rid(rid) => {
FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?))
+ .map_err(ProcessError::Resource)
}
}
}
@@ -191,6 +190,41 @@ pub struct SpawnArgs {
needs_npm_process_state: bool,
}
+#[derive(Debug, thiserror::Error)]
+pub enum ProcessError {
+ #[error("Failed to spawn '{command}': {error}")]
+ SpawnFailed {
+ command: String,
+ #[source]
+ error: Box<ProcessError>,
+ },
+ #[error("{0}")]
+ Io(#[from] std::io::Error),
+ #[cfg(unix)]
+ #[error(transparent)]
+ Nix(nix::Error),
+ #[error("failed resolving cwd: {0}")]
+ FailedResolvingCwd(#[source] std::io::Error),
+ #[error(transparent)]
+ Permission(#[from] deno_permissions::PermissionCheckError),
+ #[error(transparent)]
+ RunPermission(#[from] CheckRunPermissionError),
+ #[error(transparent)]
+ Resource(deno_core::error::AnyError),
+ #[error(transparent)]
+ BorrowMut(std::cell::BorrowMutError),
+ #[error(transparent)]
+ Which(which::Error),
+ #[error("Child process has already terminated.")]
+ ChildProcessAlreadyTerminated,
+ #[error("Invalid pid")]
+ InvalidPid,
+ #[error(transparent)]
+ Signal(#[from] SignalError),
+ #[error("Missing cmd")]
+ MissingCmd, // only for Deno.run
+}
+
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChildStdio {
@@ -208,7 +242,7 @@ pub struct ChildStatus {
}
impl TryFrom<ExitStatus> for ChildStatus {
- type Error = AnyError;
+ type Error = SignalError;
fn try_from(status: ExitStatus) -> Result<Self, Self::Error> {
let code = status.code();
@@ -259,7 +293,7 @@ type CreateCommand = (
pub fn npm_process_state_tempfile(
contents: &[u8],
-) -> Result<deno_io::RawIoHandle, AnyError> {
+) -> Result<deno_io::RawIoHandle, std::io::Error> {
let mut temp_file = tempfile::tempfile()?;
temp_file.write_all(contents)?;
let handle = temp_file.into_raw_io_handle();
@@ -301,7 +335,7 @@ fn create_command(
state: &mut OpState,
mut args: SpawnArgs,
api_name: &str,
-) -> Result<CreateCommand, AnyError> {
+) -> Result<CreateCommand, ProcessError> {
let maybe_npm_process_state = if args.needs_npm_process_state {
let provider = state.borrow::<NpmProcessStateProviderRc>();
let process_state = provider.get_npm_process_state();
@@ -505,7 +539,7 @@ fn spawn_child(
ipc_pipe_rid: Option<ResourceId>,
extra_pipe_rids: Vec<Option<ResourceId>>,
detached: bool,
-) -> Result<Child, AnyError> {
+) -> Result<Child, ProcessError> {
let mut command = tokio::process::Command::from(command);
// TODO(@crowlkats): allow detaching processes.
// currently deno will orphan a process when exiting with an error or Deno.exit()
@@ -554,10 +588,10 @@ fn spawn_child(
}
}
- return Err(AnyError::from(err).context(format!(
- "Failed to spawn '{}'",
- command.get_program().to_string_lossy()
- )));
+ return Err(ProcessError::SpawnFailed {
+ command: command.get_program().to_string_lossy().to_string(),
+ error: Box::new(err.into()),
+ });
}
};
@@ -600,11 +634,19 @@ fn compute_run_cmd_and_check_permissions(
arg_clear_env: bool,
state: &mut OpState,
api_name: &str,
-) -> Result<(PathBuf, RunEnv), AnyError> {
- let run_env = compute_run_env(arg_cwd, arg_envs, arg_clear_env)
- .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?;
- let cmd = resolve_cmd(arg_cmd, &run_env)
- .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?;
+) -> Result<(PathBuf, RunEnv), ProcessError> {
+ let run_env =
+ compute_run_env(arg_cwd, arg_envs, arg_clear_env).map_err(|e| {
+ ProcessError::SpawnFailed {
+ command: arg_cmd.to_string(),
+ error: Box::new(e),
+ }
+ })?;
+ let cmd =
+ resolve_cmd(arg_cmd, &run_env).map_err(|e| ProcessError::SpawnFailed {
+ command: arg_cmd.to_string(),
+ error: Box::new(e),
+ })?;
check_run_permission(
state,
&RunQueryDescriptor::Path {
@@ -631,9 +673,10 @@ fn compute_run_env(
arg_cwd: Option<&str>,
arg_envs: &[(String, String)],
arg_clear_env: bool,
-) -> Result<RunEnv, AnyError> {
+) -> Result<RunEnv, ProcessError> {
#[allow(clippy::disallowed_methods)]
- let cwd = std::env::current_dir().context("failed resolving cwd")?;
+ let cwd =
+ std::env::current_dir().map_err(ProcessError::FailedResolvingCwd)?;
let cwd = arg_cwd
.map(|cwd_arg| resolve_path(cwd_arg, &cwd))
.unwrap_or(cwd);
@@ -670,7 +713,7 @@ fn compute_run_env(
Ok(RunEnv { envs, cwd })
}
-fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> {
+fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, ProcessError> {
let is_path = cmd.contains('/');
#[cfg(windows)]
let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute();
@@ -683,7 +726,7 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> {
Err(which::Error::CannotFindBinaryPath) => {
Err(std::io::Error::from(std::io::ErrorKind::NotFound).into())
}
- Err(err) => Err(err.into()),
+ Err(err) => Err(ProcessError::Which(err)),
}
}
}
@@ -692,12 +735,20 @@ fn resolve_path(path: &str, cwd: &Path) -> PathBuf {
deno_path_util::normalize_path(cwd.join(path))
}
+#[derive(Debug, thiserror::Error)]
+pub enum CheckRunPermissionError {
+ #[error(transparent)]
+ Permission(#[from] deno_permissions::PermissionCheckError),
+ #[error("{0}")]
+ Other(deno_core::error::AnyError),
+}
+
fn check_run_permission(
state: &mut OpState,
cmd: &RunQueryDescriptor,
run_env: &RunEnv,
api_name: &str,
-) -> Result<(), AnyError> {
+) -> Result<(), CheckRunPermissionError> {
let permissions = state.borrow_mut::<PermissionsContainer>();
if !permissions.query_run_all(api_name) {
// error the same on all platforms
@@ -705,13 +756,16 @@ fn check_run_permission(
if !env_var_names.is_empty() {
// we don't allow users to launch subprocesses with any LD_ or DYLD_*
// env vars set because this allows executing code (ex. LD_PRELOAD)
- return Err(deno_core::error::custom_error(
- "NotCapable",
- format!(
- "Requires --allow-all permissions to spawn subprocess with {} environment variable{}.",
- env_var_names.join(", "),
- if env_var_names.len() != 1 { "s" } else { "" }
- )
+ return Err(CheckRunPermissionError::Other(
+ deno_core::error::custom_error(
+ "NotCapable",
+ format!(
+ "Requires --allow-run permissions to spawn subprocess with {0} environment variable{1}. Alternatively, spawn with {2} environment variable{1} unset.",
+ env_var_names.join(", "),
+ if env_var_names.len() != 1 { "s" } else { "" },
+ if env_var_names.len() != 1 { "these" } else { "the" }
+ ),
+ ),
));
}
permissions.check_run(cmd, api_name)?;
@@ -754,7 +808,7 @@ fn op_spawn_child(
state: &mut OpState,
#[serde] args: SpawnArgs,
#[string] api_name: String,
-) -> Result<Child, AnyError> {
+) -> Result<Child, ProcessError> {
let detached = args.detached;
let (command, pipe_rid, extra_pipe_rids, handles_to_close) =
create_command(state, args, &api_name)?;
@@ -771,16 +825,23 @@ fn op_spawn_child(
async fn op_spawn_wait(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
-) -> Result<ChildStatus, AnyError> {
+) -> Result<ChildStatus, ProcessError> {
let resource = state
.borrow_mut()
.resource_table
- .get::<ChildResource>(rid)?;
- let result = resource.0.try_borrow_mut()?.wait().await?.try_into();
+ .get::<ChildResource>(rid)
+ .map_err(ProcessError::Resource)?;
+ let result = resource
+ .0
+ .try_borrow_mut()
+ .map_err(ProcessError::BorrowMut)?
+ .wait()
+ .await?
+ .try_into()?;
if let Ok(resource) = state.borrow_mut().resource_table.take_any(rid) {
resource.close();
}
- result
+ Ok(result)
}
#[op2]
@@ -788,16 +849,14 @@ async fn op_spawn_wait(
fn op_spawn_sync(
state: &mut OpState,
#[serde] args: SpawnArgs,
-) -> Result<SpawnOutput, AnyError> {
+) -> Result<SpawnOutput, ProcessError> {
let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped));
let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped));
let (mut command, _, _, _) =
create_command(state, args, "Deno.Command().outputSync()")?;
- let output = command.output().with_context(|| {
- format!(
- "Failed to spawn '{}'",
- command.get_program().to_string_lossy()
- )
+ let output = command.output().map_err(|e| ProcessError::SpawnFailed {
+ command: command.get_program().to_string_lossy().to_string(),
+ error: Box::new(e.into()),
})?;
Ok(SpawnOutput {
@@ -820,17 +879,15 @@ fn op_spawn_kill(
state: &mut OpState,
#[smi] rid: ResourceId,
#[string] signal: String,
-) -> Result<(), AnyError> {
+) -> Result<(), ProcessError> {
if let Ok(child_resource) = state.resource_table.get::<ChildResource>(rid) {
deprecated::kill(child_resource.1 as i32, &signal)?;
return Ok(());
}
- Err(type_error("Child process has already terminated."))
+ Err(ProcessError::ChildProcessAlreadyTerminated)
}
mod deprecated {
- use deno_core::anyhow;
-
use super::*;
#[derive(Deserialize)]
@@ -876,9 +933,9 @@ mod deprecated {
pub fn op_run(
state: &mut OpState,
#[serde] run_args: RunArgs,
- ) -> Result<RunInfo, AnyError> {
+ ) -> Result<RunInfo, ProcessError> {
let args = run_args.cmd;
- let cmd = args.first().ok_or_else(|| anyhow::anyhow!("Missing cmd"))?;
+ let cmd = args.first().ok_or(ProcessError::MissingCmd)?;
let (cmd, run_env) = compute_run_cmd_and_check_permissions(
cmd,
run_args.cwd.as_deref(),
@@ -990,11 +1047,12 @@ mod deprecated {
pub async fn op_run_status(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
- ) -> Result<ProcessStatus, AnyError> {
+ ) -> Result<ProcessStatus, ProcessError> {
let resource = state
.borrow_mut()
.resource_table
- .get::<ChildResource>(rid)?;
+ .get::<ChildResource>(rid)
+ .map_err(ProcessError::Resource)?;
let mut child = resource.borrow_mut().await;
let run_status = child.wait().await?;
let code = run_status.code();
@@ -1017,17 +1075,17 @@ mod deprecated {
}
#[cfg(unix)]
- pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
+ pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> {
let signo = super::super::signal::signal_str_to_int(signal)?;
use nix::sys::signal::kill as unix_kill;
use nix::sys::signal::Signal;
use nix::unistd::Pid;
- let sig = Signal::try_from(signo)?;
- unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from)
+ let sig = Signal::try_from(signo).map_err(ProcessError::Nix)?;
+ unix_kill(Pid::from_raw(pid), Some(sig)).map_err(ProcessError::Nix)
}
#[cfg(not(unix))]
- pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
+ pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> {
use std::io::Error;
use std::io::ErrorKind::NotFound;
use winapi::shared::minwindef::DWORD;
@@ -1041,9 +1099,9 @@ mod deprecated {
use winapi::um::winnt::PROCESS_TERMINATE;
if !matches!(signal, "SIGKILL" | "SIGTERM") {
- Err(type_error(format!("Invalid signal: {signal}")))
+ Err(SignalError::InvalidSignalStr(signal.to_string()).into())
} else if pid <= 0 {
- Err(type_error("Invalid pid"))
+ Err(ProcessError::InvalidPid)
} else {
let handle =
// SAFETY: winapi call
@@ -1077,11 +1135,10 @@ mod deprecated {
#[smi] pid: i32,
#[string] signal: String,
#[string] api_name: String,
- ) -> Result<(), AnyError> {
+ ) -> Result<(), ProcessError> {
state
.borrow_mut::<PermissionsContainer>()
.check_run_all(&api_name)?;
- kill(pid, &signal)?;
- Ok(())
+ kill(pid, &signal)
}
}
diff --git a/runtime/ops/runtime.rs b/runtime/ops/runtime.rs
index 419274ebd..8d54783fc 100644
--- a/runtime/ops/runtime.rs
+++ b/runtime/ops/runtime.rs
@@ -1,6 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
@@ -16,10 +15,9 @@ deno_core::extension!(
#[op2]
#[string]
-fn op_main_module(state: &mut OpState) -> Result<String, AnyError> {
+fn op_main_module(state: &mut OpState) -> String {
let main_url = state.borrow::<ModuleSpecifier>();
- let main_path = main_url.to_string();
- Ok(main_path)
+ main_url.to_string()
}
/// This is an op instead of being done at initialization time because
diff --git a/runtime/ops/signal.rs b/runtime/ops/signal.rs
index ebc6db6d1..e1e4ab68b 100644
--- a/runtime/ops/signal.rs
+++ b/runtime/ops/signal.rs
@@ -1,6 +1,4 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use deno_core::error::type_error;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::AsyncRefCell;
use deno_core::CancelFuture;
@@ -46,6 +44,42 @@ deno_core::extension!(
}
);
+#[derive(Debug, thiserror::Error)]
+pub enum SignalError {
+ #[cfg(any(
+ target_os = "android",
+ target_os = "linux",
+ target_os = "openbsd",
+ target_os = "openbsd",
+ target_os = "macos",
+ target_os = "solaris",
+ target_os = "illumos"
+ ))]
+ #[error("Invalid signal: {0}")]
+ InvalidSignalStr(String),
+ #[cfg(any(
+ target_os = "android",
+ target_os = "linux",
+ target_os = "openbsd",
+ target_os = "openbsd",
+ target_os = "macos",
+ target_os = "solaris",
+ target_os = "illumos"
+ ))]
+ #[error("Invalid signal: {0}")]
+ InvalidSignalInt(libc::c_int),
+ #[cfg(target_os = "windows")]
+ #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")]
+ InvalidSignalStr(String),
+ #[cfg(target_os = "windows")]
+ #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")]
+ InvalidSignalInt(libc::c_int),
+ #[error("Binding to signal '{0}' is not allowed")]
+ SignalNotAllowed(String),
+ #[error("{0}")]
+ Io(#[from] std::io::Error),
+}
+
#[cfg(unix)]
#[derive(Default)]
struct SignalState {
@@ -147,438 +181,217 @@ impl Resource for SignalStreamResource {
}
}
-#[cfg(target_os = "freebsd")]
-pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> {
- match s {
- "SIGHUP" => Ok(1),
- "SIGINT" => Ok(2),
- "SIGQUIT" => Ok(3),
- "SIGILL" => Ok(4),
- "SIGTRAP" => Ok(5),
- "SIGIOT" => Ok(6),
- "SIGABRT" => Ok(6),
- "SIGEMT" => Ok(7),
- "SIGFPE" => Ok(8),
- "SIGKILL" => Ok(9),
- "SIGBUS" => Ok(10),
- "SIGSEGV" => Ok(11),
- "SIGSYS" => Ok(12),
- "SIGPIPE" => Ok(13),
- "SIGALRM" => Ok(14),
- "SIGTERM" => Ok(15),
- "SIGURG" => Ok(16),
- "SIGSTOP" => Ok(17),
- "SIGTSTP" => Ok(18),
- "SIGCONT" => Ok(19),
- "SIGCHLD" => Ok(20),
- "SIGTTIN" => Ok(21),
- "SIGTTOU" => Ok(22),
- "SIGIO" => Ok(23),
- "SIGXCPU" => Ok(24),
- "SIGXFSZ" => Ok(25),
- "SIGVTALRM" => Ok(26),
- "SIGPROF" => Ok(27),
- "SIGWINCH" => Ok(28),
- "SIGINFO" => Ok(29),
- "SIGUSR1" => Ok(30),
- "SIGUSR2" => Ok(31),
- "SIGTHR" => Ok(32),
- "SIGLIBRT" => Ok(33),
- _ => Err(type_error(format!("Invalid signal : {}", s))),
- }
+macro_rules! first_literal {
+ ($head:literal $(, $tail:literal)*) => {
+ $head
+ };
}
+macro_rules! signal_dict {
+ ($(($number:literal, $($name:literal)|+)),*) => {
+ pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, SignalError> {
+ match s {
+ $($($name)|* => Ok($number),)*
+ _ => Err(SignalError::InvalidSignalStr(s.to_string())),
+ }
+ }
-#[cfg(target_os = "freebsd")]
-pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> {
- match s {
- 1 => Ok("SIGHUP"),
- 2 => Ok("SIGINT"),
- 3 => Ok("SIGQUIT"),
- 4 => Ok("SIGILL"),
- 5 => Ok("SIGTRAP"),
- 6 => Ok("SIGABRT"),
- 7 => Ok("SIGEMT"),
- 8 => Ok("SIGFPE"),
- 9 => Ok("SIGKILL"),
- 10 => Ok("SIGBUS"),
- 11 => Ok("SIGSEGV"),
- 12 => Ok("SIGSYS"),
- 13 => Ok("SIGPIPE"),
- 14 => Ok("SIGALRM"),
- 15 => Ok("SIGTERM"),
- 16 => Ok("SIGURG"),
- 17 => Ok("SIGSTOP"),
- 18 => Ok("SIGTSTP"),
- 19 => Ok("SIGCONT"),
- 20 => Ok("SIGCHLD"),
- 21 => Ok("SIGTTIN"),
- 22 => Ok("SIGTTOU"),
- 23 => Ok("SIGIO"),
- 24 => Ok("SIGXCPU"),
- 25 => Ok("SIGXFSZ"),
- 26 => Ok("SIGVTALRM"),
- 27 => Ok("SIGPROF"),
- 28 => Ok("SIGWINCH"),
- 29 => Ok("SIGINFO"),
- 30 => Ok("SIGUSR1"),
- 31 => Ok("SIGUSR2"),
- 32 => Ok("SIGTHR"),
- 33 => Ok("SIGLIBRT"),
- _ => Err(type_error(format!("Invalid signal : {}", s))),
+ pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, SignalError> {
+ match s {
+ $($number => Ok(first_literal!($($name),+)),)*
+ _ => Err(SignalError::InvalidSignalInt(s)),
+ }
+ }
}
}
-#[cfg(target_os = "openbsd")]
-pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> {
- match s {
- "SIGHUP" => Ok(1),
- "SIGINT" => Ok(2),
- "SIGQUIT" => Ok(3),
- "SIGILL" => Ok(4),
- "SIGTRAP" => Ok(5),
- "SIGIOT" => Ok(6),
- "SIGABRT" => Ok(6),
- "SIGEMT" => Ok(7),
- "SIGFPE" => Ok(8),
- "SIGKILL" => Ok(9),
- "SIGBUS" => Ok(10),
- "SIGSEGV" => Ok(11),
- "SIGSYS" => Ok(12),
- "SIGPIPE" => Ok(13),
- "SIGALRM" => Ok(14),
- "SIGTERM" => Ok(15),
- "SIGURG" => Ok(16),
- "SIGSTOP" => Ok(17),
- "SIGTSTP" => Ok(18),
- "SIGCONT" => Ok(19),
- "SIGCHLD" => Ok(20),
- "SIGTTIN" => Ok(21),
- "SIGTTOU" => Ok(22),
- "SIGIO" => Ok(23),
- "SIGXCPU" => Ok(24),
- "SIGXFSZ" => Ok(25),
- "SIGVTALRM" => Ok(26),
- "SIGPROF" => Ok(27),
- "SIGWINCH" => Ok(28),
- "SIGINFO" => Ok(29),
- "SIGUSR1" => Ok(30),
- "SIGUSR2" => Ok(31),
- "SIGTHR" => Ok(32),
- _ => Err(type_error(format!("Invalid signal : {}", s))),
- }
-}
+#[cfg(target_os = "freebsd")]
+signal_dict!(
+ (1, "SIGHUP"),
+ (2, "SIGINT"),
+ (3, "SIGQUIT"),
+ (4, "SIGILL"),
+ (5, "SIGTRAP"),
+ (6, "SIGABRT" | "SIGIOT"),
+ (7, "SIGEMT"),
+ (8, "SIGFPE"),
+ (9, "SIGKILL"),
+ (10, "SIGBUS"),
+ (11, "SIGSEGV"),
+ (12, "SIGSYS"),
+ (13, "SIGPIPE"),
+ (14, "SIGALRM"),
+ (15, "SIGTERM"),
+ (16, "SIGURG"),
+ (17, "SIGSTOP"),
+ (18, "SIGTSTP"),
+ (19, "SIGCONT"),
+ (20, "SIGCHLD"),
+ (21, "SIGTTIN"),
+ (22, "SIGTTOU"),
+ (23, "SIGIO"),
+ (24, "SIGXCPU"),
+ (25, "SIGXFSZ"),
+ (26, "SIGVTALRM"),
+ (27, "SIGPROF"),
+ (28, "SIGWINCH"),
+ (29, "SIGINFO"),
+ (30, "SIGUSR1"),
+ (31, "SIGUSR2"),
+ (32, "SIGTHR"),
+ (33, "SIGLIBRT")
+);
#[cfg(target_os = "openbsd")]
-pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> {
- match s {
- 1 => Ok("SIGHUP"),
- 2 => Ok("SIGINT"),
- 3 => Ok("SIGQUIT"),
- 4 => Ok("SIGILL"),
- 5 => Ok("SIGTRAP"),
- 6 => Ok("SIGABRT"),
- 7 => Ok("SIGEMT"),
- 8 => Ok("SIGFPE"),
- 9 => Ok("SIGKILL"),
- 10 => Ok("SIGBUS"),
- 11 => Ok("SIGSEGV"),
- 12 => Ok("SIGSYS"),
- 13 => Ok("SIGPIPE"),
- 14 => Ok("SIGALRM"),
- 15 => Ok("SIGTERM"),
- 16 => Ok("SIGURG"),
- 17 => Ok("SIGSTOP"),
- 18 => Ok("SIGTSTP"),
- 19 => Ok("SIGCONT"),
- 20 => Ok("SIGCHLD"),
- 21 => Ok("SIGTTIN"),
- 22 => Ok("SIGTTOU"),
- 23 => Ok("SIGIO"),
- 24 => Ok("SIGXCPU"),
- 25 => Ok("SIGXFSZ"),
- 26 => Ok("SIGVTALRM"),
- 27 => Ok("SIGPROF"),
- 28 => Ok("SIGWINCH"),
- 29 => Ok("SIGINFO"),
- 30 => Ok("SIGUSR1"),
- 31 => Ok("SIGUSR2"),
- 32 => Ok("SIGTHR"),
- _ => Err(type_error(format!("Invalid signal : {}", s))),
- }
-}
-
-#[cfg(any(target_os = "android", target_os = "linux"))]
-pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> {
- match s {
- "SIGHUP" => Ok(1),
- "SIGINT" => Ok(2),
- "SIGQUIT" => Ok(3),
- "SIGILL" => Ok(4),
- "SIGTRAP" => Ok(5),
- "SIGIOT" => Ok(6),
- "SIGABRT" => Ok(6),
- "SIGBUS" => Ok(7),
- "SIGFPE" => Ok(8),
- "SIGKILL" => Ok(9),
- "SIGUSR1" => Ok(10),
- "SIGSEGV" => Ok(11),
- "SIGUSR2" => Ok(12),
- "SIGPIPE" => Ok(13),
- "SIGALRM" => Ok(14),
- "SIGTERM" => Ok(15),
- "SIGSTKFLT" => Ok(16),
- "SIGCHLD" => Ok(17),
- "SIGCONT" => Ok(18),
- "SIGSTOP" => Ok(19),
- "SIGTSTP" => Ok(20),
- "SIGTTIN" => Ok(21),
- "SIGTTOU" => Ok(22),
- "SIGURG" => Ok(23),
- "SIGXCPU" => Ok(24),
- "SIGXFSZ" => Ok(25),
- "SIGVTALRM" => Ok(26),
- "SIGPROF" => Ok(27),
- "SIGWINCH" => Ok(28),
- "SIGIO" | "SIGPOLL" => Ok(29),
- "SIGPWR" => Ok(30),
- "SIGSYS" | "SIGUNUSED" => Ok(31),
- _ => Err(type_error(format!("Invalid signal : {s}"))),
- }
-}
+signal_dict!(
+ (1, "SIGHUP"),
+ (2, "SIGINT"),
+ (3, "SIGQUIT"),
+ (4, "SIGILL"),
+ (5, "SIGTRAP"),
+ (6, "SIGABRT" | "SIGIOT"),
+ (7, "SIGEMT"),
+ (8, "SIGKILL"),
+ (10, "SIGBUS"),
+ (11, "SIGSEGV"),
+ (12, "SIGSYS"),
+ (13, "SIGPIPE"),
+ (14, "SIGALRM"),
+ (15, "SIGTERM"),
+ (16, "SIGURG"),
+ (17, "SIGSTOP"),
+ (18, "SIGTSTP"),
+ (19, "SIGCONT"),
+ (20, "SIGCHLD"),
+ (21, "SIGTTIN"),
+ (22, "SIGTTOU"),
+ (23, "SIGIO"),
+ (24, "SIGXCPU"),
+ (25, "SIGXFSZ"),
+ (26, "SIGVTALRM"),
+ (27, "SIGPROF"),
+ (28, "SIGWINCH"),
+ (29, "SIGINFO"),
+ (30, "SIGUSR1"),
+ (31, "SIGUSR2"),
+ (32, "SIGTHR")
+);
#[cfg(any(target_os = "android", target_os = "linux"))]
-pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> {
- match s {
- 1 => Ok("SIGHUP"),
- 2 => Ok("SIGINT"),
- 3 => Ok("SIGQUIT"),
- 4 => Ok("SIGILL"),
- 5 => Ok("SIGTRAP"),
- 6 => Ok("SIGABRT"),
- 7 => Ok("SIGBUS"),
- 8 => Ok("SIGFPE"),
- 9 => Ok("SIGKILL"),
- 10 => Ok("SIGUSR1"),
- 11 => Ok("SIGSEGV"),
- 12 => Ok("SIGUSR2"),
- 13 => Ok("SIGPIPE"),
- 14 => Ok("SIGALRM"),
- 15 => Ok("SIGTERM"),
- 16 => Ok("SIGSTKFLT"),
- 17 => Ok("SIGCHLD"),
- 18 => Ok("SIGCONT"),
- 19 => Ok("SIGSTOP"),
- 20 => Ok("SIGTSTP"),
- 21 => Ok("SIGTTIN"),
- 22 => Ok("SIGTTOU"),
- 23 => Ok("SIGURG"),
- 24 => Ok("SIGXCPU"),
- 25 => Ok("SIGXFSZ"),
- 26 => Ok("SIGVTALRM"),
- 27 => Ok("SIGPROF"),
- 28 => Ok("SIGWINCH"),
- 29 => Ok("SIGIO"),
- 30 => Ok("SIGPWR"),
- 31 => Ok("SIGSYS"),
- _ => Err(type_error(format!("Invalid signal : {s}"))),
- }
-}
-
-#[cfg(target_os = "macos")]
-pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> {
- match s {
- "SIGHUP" => Ok(1),
- "SIGINT" => Ok(2),
- "SIGQUIT" => Ok(3),
- "SIGILL" => Ok(4),
- "SIGTRAP" => Ok(5),
- "SIGIOT" => Ok(6),
- "SIGABRT" => Ok(6),
- "SIGEMT" => Ok(7),
- "SIGFPE" => Ok(8),
- "SIGKILL" => Ok(9),
- "SIGBUS" => Ok(10),
- "SIGSEGV" => Ok(11),
- "SIGSYS" => Ok(12),
- "SIGPIPE" => Ok(13),
- "SIGALRM" => Ok(14),
- "SIGTERM" => Ok(15),
- "SIGURG" => Ok(16),
- "SIGSTOP" => Ok(17),
- "SIGTSTP" => Ok(18),
- "SIGCONT" => Ok(19),
- "SIGCHLD" => Ok(20),
- "SIGTTIN" => Ok(21),
- "SIGTTOU" => Ok(22),
- "SIGIO" => Ok(23),
- "SIGXCPU" => Ok(24),
- "SIGXFSZ" => Ok(25),
- "SIGVTALRM" => Ok(26),
- "SIGPROF" => Ok(27),
- "SIGWINCH" => Ok(28),
- "SIGINFO" => Ok(29),
- "SIGUSR1" => Ok(30),
- "SIGUSR2" => Ok(31),
- _ => Err(type_error(format!("Invalid signal: {s}"))),
- }
-}
+signal_dict!(
+ (1, "SIGHUP"),
+ (2, "SIGINT"),
+ (3, "SIGQUIT"),
+ (4, "SIGILL"),
+ (5, "SIGTRAP"),
+ (6, "SIGABRT" | "SIGIOT"),
+ (7, "SIGBUS"),
+ (8, "SIGFPE"),
+ (9, "SIGKILL"),
+ (10, "SIGUSR1"),
+ (11, "SIGSEGV"),
+ (12, "SIGUSR2"),
+ (13, "SIGPIPE"),
+ (14, "SIGALRM"),
+ (15, "SIGTERM"),
+ (16, "SIGSTKFLT"),
+ (17, "SIGCHLD"),
+ (18, "SIGCONT"),
+ (19, "SIGSTOP"),
+ (20, "SIGTSTP"),
+ (21, "SIGTTIN"),
+ (22, "SIGTTOU"),
+ (23, "SIGURG"),
+ (24, "SIGXCPU"),
+ (25, "SIGXFSZ"),
+ (26, "SIGVTALRM"),
+ (27, "SIGPROF"),
+ (28, "SIGWINCH"),
+ (29, "SIGIO" | "SIGPOLL"),
+ (30, "SIGPWR"),
+ (31, "SIGSYS" | "SIGUNUSED")
+);
#[cfg(target_os = "macos")]
-pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> {
- match s {
- 1 => Ok("SIGHUP"),
- 2 => Ok("SIGINT"),
- 3 => Ok("SIGQUIT"),
- 4 => Ok("SIGILL"),
- 5 => Ok("SIGTRAP"),
- 6 => Ok("SIGABRT"),
- 7 => Ok("SIGEMT"),
- 8 => Ok("SIGFPE"),
- 9 => Ok("SIGKILL"),
- 10 => Ok("SIGBUS"),
- 11 => Ok("SIGSEGV"),
- 12 => Ok("SIGSYS"),
- 13 => Ok("SIGPIPE"),
- 14 => Ok("SIGALRM"),
- 15 => Ok("SIGTERM"),
- 16 => Ok("SIGURG"),
- 17 => Ok("SIGSTOP"),
- 18 => Ok("SIGTSTP"),
- 19 => Ok("SIGCONT"),
- 20 => Ok("SIGCHLD"),
- 21 => Ok("SIGTTIN"),
- 22 => Ok("SIGTTOU"),
- 23 => Ok("SIGIO"),
- 24 => Ok("SIGXCPU"),
- 25 => Ok("SIGXFSZ"),
- 26 => Ok("SIGVTALRM"),
- 27 => Ok("SIGPROF"),
- 28 => Ok("SIGWINCH"),
- 29 => Ok("SIGINFO"),
- 30 => Ok("SIGUSR1"),
- 31 => Ok("SIGUSR2"),
- _ => Err(type_error(format!("Invalid signal: {s}"))),
- }
-}
-
-#[cfg(any(target_os = "solaris", target_os = "illumos"))]
-pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> {
- match s {
- "SIGHUP" => Ok(1),
- "SIGINT" => Ok(2),
- "SIGQUIT" => Ok(3),
- "SIGILL" => Ok(4),
- "SIGTRAP" => Ok(5),
- "SIGIOT" => Ok(6),
- "SIGABRT" => Ok(6),
- "SIGEMT" => Ok(7),
- "SIGFPE" => Ok(8),
- "SIGKILL" => Ok(9),
- "SIGBUS" => Ok(10),
- "SIGSEGV" => Ok(11),
- "SIGSYS" => Ok(12),
- "SIGPIPE" => Ok(13),
- "SIGALRM" => Ok(14),
- "SIGTERM" => Ok(15),
- "SIGUSR1" => Ok(16),
- "SIGUSR2" => Ok(17),
- "SIGCLD" => Ok(18),
- "SIGCHLD" => Ok(18),
- "SIGPWR" => Ok(19),
- "SIGWINCH" => Ok(20),
- "SIGURG" => Ok(21),
- "SIGPOLL" => Ok(22),
- "SIGIO" => Ok(22),
- "SIGSTOP" => Ok(23),
- "SIGTSTP" => Ok(24),
- "SIGCONT" => Ok(25),
- "SIGTTIN" => Ok(26),
- "SIGTTOU" => Ok(27),
- "SIGVTALRM" => Ok(28),
- "SIGPROF" => Ok(29),
- "SIGXCPU" => Ok(30),
- "SIGXFSZ" => Ok(31),
- "SIGWAITING" => Ok(32),
- "SIGLWP" => Ok(33),
- "SIGFREEZE" => Ok(34),
- "SIGTHAW" => Ok(35),
- "SIGCANCEL" => Ok(36),
- "SIGLOST" => Ok(37),
- "SIGXRES" => Ok(38),
- "SIGJVM1" => Ok(39),
- "SIGJVM2" => Ok(40),
- _ => Err(type_error(format!("Invalid signal : {}", s))),
- }
-}
+signal_dict!(
+ (1, "SIGHUP"),
+ (2, "SIGINT"),
+ (3, "SIGQUIT"),
+ (4, "SIGILL"),
+ (5, "SIGTRAP"),
+ (6, "SIGABRT" | "SIGIOT"),
+ (7, "SIGEMT"),
+ (8, "SIGFPE"),
+ (9, "SIGKILL"),
+ (10, "SIGBUS"),
+ (11, "SIGSEGV"),
+ (12, "SIGSYS"),
+ (13, "SIGPIPE"),
+ (14, "SIGALRM"),
+ (15, "SIGTERM"),
+ (16, "SIGURG"),
+ (17, "SIGSTOP"),
+ (18, "SIGTSTP"),
+ (19, "SIGCONT"),
+ (20, "SIGCHLD"),
+ (21, "SIGTTIN"),
+ (22, "SIGTTOU"),
+ (23, "SIGIO"),
+ (24, "SIGXCPU"),
+ (25, "SIGXFSZ"),
+ (26, "SIGVTALRM"),
+ (27, "SIGPROF"),
+ (28, "SIGWINCH"),
+ (29, "SIGINFO"),
+ (30, "SIGUSR1"),
+ (31, "SIGUSR2")
+);
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
-pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> {
- match s {
- 1 => Ok("SIGHUP"),
- 2 => Ok("SIGINT"),
- 3 => Ok("SIGQUIT"),
- 4 => Ok("SIGILL"),
- 5 => Ok("SIGTRAP"),
- 6 => Ok("SIGABRT"),
- 7 => Ok("SIGEMT"),
- 8 => Ok("SIGFPE"),
- 9 => Ok("SIGKILL"),
- 10 => Ok("SIGBUS"),
- 11 => Ok("SIGSEGV"),
- 12 => Ok("SIGSYS"),
- 13 => Ok("SIGPIPE"),
- 14 => Ok("SIGALRM"),
- 15 => Ok("SIGTERM"),
- 16 => Ok("SIGUSR1"),
- 17 => Ok("SIGUSR2"),
- 18 => Ok("SIGCHLD"),
- 19 => Ok("SIGPWR"),
- 20 => Ok("SIGWINCH"),
- 21 => Ok("SIGURG"),
- 22 => Ok("SIGPOLL"),
- 23 => Ok("SIGSTOP"),
- 24 => Ok("SIGTSTP"),
- 25 => Ok("SIGCONT"),
- 26 => Ok("SIGTTIN"),
- 27 => Ok("SIGTTOU"),
- 28 => Ok("SIGVTALRM"),
- 29 => Ok("SIGPROF"),
- 30 => Ok("SIGXCPU"),
- 31 => Ok("SIGXFSZ"),
- 32 => Ok("SIGWAITING"),
- 33 => Ok("SIGLWP"),
- 34 => Ok("SIGFREEZE"),
- 35 => Ok("SIGTHAW"),
- 36 => Ok("SIGCANCEL"),
- 37 => Ok("SIGLOST"),
- 38 => Ok("SIGXRES"),
- 39 => Ok("SIGJVM1"),
- 40 => Ok("SIGJVM2"),
- _ => Err(type_error(format!("Invalid signal : {}", s))),
- }
-}
-
-#[cfg(target_os = "windows")]
-pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> {
- match s {
- "SIGINT" => Ok(2),
- "SIGBREAK" => Ok(21),
- _ => Err(type_error(
- "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).",
- )),
- }
-}
+signal_dict!(
+ (1, "SIGHUP"),
+ (2, "SIGINT"),
+ (3, "SIGQUIT"),
+ (4, "SIGILL"),
+ (5, "SIGTRAP"),
+ (6, "SIGABRT" | "SIGIOT"),
+ (7, "SIGEMT"),
+ (8, "SIGFPE"),
+ (9, "SIGKILL"),
+ (10, "SIGBUS"),
+ (11, "SIGSEGV"),
+ (12, "SIGSYS"),
+ (13, "SIGPIPE"),
+ (14, "SIGALRM"),
+ (15, "SIGTERM"),
+ (16, "SIGUSR1"),
+ (17, "SIGUSR2"),
+ (18, "SIGCHLD"),
+ (19, "SIGPWR"),
+ (20, "SIGWINCH"),
+ (21, "SIGURG"),
+ (22, "SIGPOLL"),
+ (23, "SIGSTOP"),
+ (24, "SIGTSTP"),
+ (25, "SIGCONT"),
+ (26, "SIGTTIN"),
+ (27, "SIGTTOU"),
+ (28, "SIGVTALRM"),
+ (29, "SIGPROF"),
+ (30, "SIGXCPU"),
+ (31, "SIGXFSZ"),
+ (32, "SIGWAITING"),
+ (33, "SIGLWP"),
+ (34, "SIGFREEZE"),
+ (35, "SIGTHAW"),
+ (36, "SIGCANCEL"),
+ (37, "SIGLOST"),
+ (38, "SIGXRES"),
+ (39, "SIGJVM1"),
+ (40, "SIGJVM2")
+);
#[cfg(target_os = "windows")]
-pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> {
- match s {
- 2 => Ok("SIGINT"),
- 21 => Ok("SIGBREAK"),
- _ => Err(type_error(
- "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).",
- )),
- }
-}
+signal_dict!((2, "SIGINT"), (21, "SIGBREAK"));
#[cfg(unix)]
#[op2(fast)]
@@ -586,12 +399,10 @@ pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> {
fn op_signal_bind(
state: &mut OpState,
#[string] sig: &str,
-) -> Result<ResourceId, AnyError> {
+) -> Result<ResourceId, SignalError> {
let signo = signal_str_to_int(sig)?;
if signal_hook_registry::FORBIDDEN.contains(&signo) {
- return Err(type_error(format!(
- "Binding to signal '{sig}' is not allowed",
- )));
+ return Err(SignalError::SignalNotAllowed(sig.to_string()));
}
let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?);
@@ -625,7 +436,7 @@ fn op_signal_bind(
fn op_signal_bind(
state: &mut OpState,
#[string] sig: &str,
-) -> Result<ResourceId, AnyError> {
+) -> Result<ResourceId, SignalError> {
let signo = signal_str_to_int(sig)?;
let resource = SignalStreamResource {
signal: AsyncRefCell::new(match signo {
@@ -649,7 +460,7 @@ fn op_signal_bind(
async fn op_signal_poll(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
-) -> Result<bool, AnyError> {
+) -> Result<bool, deno_core::error::AnyError> {
let resource = state
.borrow_mut()
.resource_table
@@ -668,7 +479,7 @@ async fn op_signal_poll(
pub fn op_signal_unbind(
state: &mut OpState,
#[smi] rid: ResourceId,
-) -> Result<(), AnyError> {
+) -> Result<(), deno_core::error::AnyError> {
let resource = state.resource_table.take::<SignalStreamResource>(rid)?;
#[cfg(unix)]
diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs
index 5b49e3a24..7849185fa 100644
--- a/runtime/ops/tty.rs
+++ b/runtime/ops/tty.rs
@@ -2,7 +2,6 @@
use std::io::Error;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use rustyline::config::Configurer;
@@ -64,6 +63,19 @@ deno_core::extension!(
},
);
+#[derive(Debug, thiserror::Error)]
+pub enum TtyError {
+ #[error(transparent)]
+ Resource(deno_core::error::AnyError),
+ #[error("{0}")]
+ Io(#[from] std::io::Error),
+ #[cfg(unix)]
+ #[error(transparent)]
+ Nix(nix::Error),
+ #[error(transparent)]
+ Other(deno_core::error::AnyError),
+}
+
// ref: <https://learn.microsoft.com/en-us/windows/console/setconsolemode>
#[cfg(windows)]
const COOKED_MODE: DWORD =
@@ -90,8 +102,11 @@ fn op_set_raw(
rid: u32,
is_raw: bool,
cbreak: bool,
-) -> Result<(), AnyError> {
- let handle_or_fd = state.resource_table.get_fd(rid)?;
+) -> Result<(), TtyError> {
+ let handle_or_fd = state
+ .resource_table
+ .get_fd(rid)
+ .map_err(TtyError::Resource)?;
// From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs
// and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs
@@ -107,7 +122,7 @@ fn op_set_raw(
let handle = handle_or_fd;
if cbreak {
- return Err(deno_core::error::not_supported());
+ return Err(TtyError::Other(deno_core::error::not_supported()));
}
let mut original_mode: DWORD = 0;
@@ -115,7 +130,7 @@ fn op_set_raw(
if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) }
== FALSE
{
- return Err(Error::last_os_error().into());
+ return Err(TtyError::Io(Error::last_os_error()));
}
let new_mode = if is_raw {
@@ -185,7 +200,7 @@ fn op_set_raw(
winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0)
} == FALSE
{
- return Err(Error::last_os_error().into());
+ return Err(TtyError::Io(Error::last_os_error()));
}
/* Wait for read thread to acknowledge the cancellation to ensure that nothing
@@ -199,7 +214,7 @@ fn op_set_raw(
// SAFETY: winapi call
if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE {
- return Err(Error::last_os_error().into());
+ return Err(TtyError::Io(Error::last_os_error()));
}
Ok(())
@@ -244,14 +259,16 @@ fn op_set_raw(
let tty_mode_store = state.borrow::<TtyModeStore>().clone();
let previous_mode = tty_mode_store.get(rid);
- let raw_fd = handle_or_fd;
+ // SAFETY: Nix crate requires value to implement the AsFd trait
+ let raw_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(handle_or_fd) };
if is_raw {
let mut raw = match previous_mode {
Some(mode) => mode,
None => {
// Save original mode.
- let original_mode = termios::tcgetattr(raw_fd)?;
+ let original_mode =
+ termios::tcgetattr(raw_fd).map_err(TtyError::Nix)?;
tty_mode_store.set(rid, original_mode.clone());
original_mode
}
@@ -273,11 +290,13 @@ fn op_set_raw(
}
raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1;
raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0;
- termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?;
+ termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)
+ .map_err(TtyError::Nix)?;
} else {
// Try restore saved mode.
if let Some(mode) = tty_mode_store.take(rid) {
- termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?;
+ termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)
+ .map_err(TtyError::Nix)?;
}
}
@@ -289,13 +308,16 @@ fn op_set_raw(
fn op_console_size(
state: &mut OpState,
#[buffer] result: &mut [u32],
-) -> Result<(), AnyError> {
+) -> Result<(), TtyError> {
fn check_console_size(
state: &mut OpState,
result: &mut [u32],
rid: u32,
- ) -> Result<(), AnyError> {
- let fd = state.resource_table.get_fd(rid)?;
+ ) -> Result<(), TtyError> {
+ let fd = state
+ .resource_table
+ .get_fd(rid)
+ .map_err(TtyError::Resource)?;
let size = console_size_from_fd(fd)?;
result[0] = size.cols;
result[1] = size.rows;
@@ -418,7 +440,7 @@ mod tests {
pub fn op_read_line_prompt(
#[string] prompt_text: &str,
#[string] default_value: &str,
-) -> Result<Option<String>, AnyError> {
+) -> Result<Option<String>, ReadlineError> {
let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new()
.expect("Failed to create editor.");
@@ -438,6 +460,6 @@ pub fn op_read_line_prompt(
Ok(None)
}
Err(ReadlineError::Eof) => Ok(None),
- Err(err) => Err(err.into()),
+ Err(err) => Err(err),
}
}
diff --git a/runtime/ops/utils.rs b/runtime/ops/utils.rs
deleted file mode 100644
index d5ce61c1f..000000000
--- a/runtime/ops/utils.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-use deno_core::error::custom_error;
-use deno_core::error::AnyError;
-
-/// A utility function to map OsStrings to Strings
-pub fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> {
- s.into_string().map_err(|s| {
- let message = format!("File name or path {s:?} is not valid UTF-8");
- custom_error("InvalidData", message)
- })
-}
diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs
index 0ed76ebd5..d0c3eea66 100644
--- a/runtime/ops/web_worker.rs
+++ b/runtime/ops/web_worker.rs
@@ -4,15 +4,16 @@ mod sync_fetch;
use crate::web_worker::WebWorkerInternalHandle;
use crate::web_worker::WebWorkerType;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::CancelFuture;
use deno_core::OpState;
use deno_web::JsMessageData;
+use deno_web::MessagePortError;
use std::cell::RefCell;
use std::rc::Rc;
use self::sync_fetch::op_worker_sync_fetch;
+pub use sync_fetch::SyncFetchError;
deno_core::extension!(
deno_web_worker,
@@ -30,17 +31,16 @@ deno_core::extension!(
fn op_worker_post_message(
state: &mut OpState,
#[serde] data: JsMessageData,
-) -> Result<(), AnyError> {
+) -> Result<(), MessagePortError> {
let handle = state.borrow::<WebWorkerInternalHandle>().clone();
- handle.port.send(state, data)?;
- Ok(())
+ handle.port.send(state, data)
}
#[op2(async(lazy), fast)]
#[serde]
async fn op_worker_recv_message(
state: Rc<RefCell<OpState>>,
-) -> Result<Option<JsMessageData>, AnyError> {
+) -> Result<Option<JsMessageData>, MessagePortError> {
let handle = {
let state = state.borrow();
state.borrow::<WebWorkerInternalHandle>().clone()
diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs
index cdb151a86..d1f133d3d 100644
--- a/runtime/ops/web_worker/sync_fetch.rs
+++ b/runtime/ops/web_worker/sync_fetch.rs
@@ -4,15 +4,13 @@ use std::sync::Arc;
use crate::web_worker::WebWorkerInternalHandle;
use crate::web_worker::WebWorkerType;
-use deno_core::error::type_error;
-use deno_core::error::AnyError;
use deno_core::futures::StreamExt;
use deno_core::op2;
use deno_core::url::Url;
use deno_core::OpState;
use deno_fetch::data_url::DataUrl;
+use deno_fetch::FetchError;
use deno_web::BlobStore;
-use deno_websocket::DomExceptionNetworkError;
use http_body_util::BodyExt;
use hyper::body::Bytes;
use serde::Deserialize;
@@ -27,6 +25,32 @@ fn mime_type_essence(mime_type: &str) -> String {
essence.trim().to_ascii_lowercase()
}
+#[derive(Debug, thiserror::Error)]
+pub enum SyncFetchError {
+ #[error("Blob URLs are not supported in this context.")]
+ BlobUrlsNotSupportedInContext,
+ #[error("{0}")]
+ Io(#[from] std::io::Error),
+ #[error("Invalid script URL")]
+ InvalidScriptUrl,
+ #[error("http status error: {0}")]
+ InvalidStatusCode(http::StatusCode),
+ #[error("Classic scripts with scheme {0}: are not supported in workers")]
+ ClassicScriptSchemeUnsupportedInWorkers(String),
+ #[error("{0}")]
+ InvalidUri(#[from] http::uri::InvalidUri),
+ #[error("Invalid MIME type {0:?}.")]
+ InvalidMimeType(String),
+ #[error("Missing MIME type.")]
+ MissingMimeType,
+ #[error(transparent)]
+ Fetch(#[from] FetchError),
+ #[error(transparent)]
+ Join(#[from] tokio::task::JoinError),
+ #[error(transparent)]
+ Other(deno_core::error::AnyError),
+}
+
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncFetchScript {
@@ -40,21 +64,22 @@ pub fn op_worker_sync_fetch(
state: &mut OpState,
#[serde] scripts: Vec<String>,
loose_mime_checks: bool,
-) -> Result<Vec<SyncFetchScript>, AnyError> {
+) -> Result<Vec<SyncFetchScript>, SyncFetchError> {
let handle = state.borrow::<WebWorkerInternalHandle>().clone();
assert_eq!(handle.worker_type, WebWorkerType::Classic);
// it's not safe to share a client across tokio runtimes, so create a fresh one
// https://github.com/seanmonstar/reqwest/issues/1148#issuecomment-910868788
let options = state.borrow::<deno_fetch::Options>().clone();
- let client = deno_fetch::create_client_from_options(&options)?;
+ let client = deno_fetch::create_client_from_options(&options)
+ .map_err(FetchError::ClientCreate)?;
// TODO(andreubotella) It's not good to throw an exception related to blob
// URLs when none of the script URLs use the blob scheme.
// Also, in which contexts are blob URLs not supported?
let blob_store = state
.try_borrow::<Arc<BlobStore>>()
- .ok_or_else(|| type_error("Blob URLs are not supported in this context."))?
+ .ok_or(SyncFetchError::BlobUrlsNotSupportedInContext)?
.clone();
// TODO(andreubotella): make the below thread into a resource that can be
@@ -74,7 +99,7 @@ pub fn op_worker_sync_fetch(
let blob_store = blob_store.clone();
deno_core::unsync::spawn(async move {
let script_url = Url::parse(&script)
- .map_err(|_| type_error("Invalid script URL"))?;
+ .map_err(|_| SyncFetchError::InvalidScriptUrl)?;
let mut loose_mime_checks = loose_mime_checks;
let (body, mime_type, res_url) = match script_url.scheme() {
@@ -86,15 +111,13 @@ pub fn op_worker_sync_fetch(
);
*req.uri_mut() = script_url.as_str().parse()?;
- let resp = client.send(req).await?;
+ let resp =
+ client.send(req).await.map_err(FetchError::ClientSend)?;
if resp.status().is_client_error()
|| resp.status().is_server_error()
{
- return Err(type_error(format!(
- "http status error: {}",
- resp.status()
- )));
+ return Err(SyncFetchError::InvalidStatusCode(resp.status()));
}
// TODO(andreubotella) Properly run fetch's "extract a MIME type".
@@ -107,42 +130,45 @@ pub fn op_worker_sync_fetch(
// Always check the MIME type with HTTP(S).
loose_mime_checks = false;
- let body = resp.collect().await?.to_bytes();
+ let body = resp
+ .collect()
+ .await
+ .map_err(SyncFetchError::Other)?
+ .to_bytes();
(body, mime_type, script)
}
"data" => {
- let data_url = DataUrl::process(&script)
- .map_err(|e| type_error(format!("{e:?}")))?;
+ let data_url =
+ DataUrl::process(&script).map_err(FetchError::DataUrl)?;
let mime_type = {
let mime = data_url.mime_type();
format!("{}/{}", mime.type_, mime.subtype)
};
- let (body, _) = data_url
- .decode_to_vec()
- .map_err(|e| type_error(format!("{e:?}")))?;
+ let (body, _) =
+ data_url.decode_to_vec().map_err(FetchError::Base64)?;
(Bytes::from(body), Some(mime_type), script)
}
"blob" => {
- let blob =
- blob_store.get_object_url(script_url).ok_or_else(|| {
- type_error("Blob for the given URL not found.")
- })?;
+ let blob = blob_store
+ .get_object_url(script_url)
+ .ok_or(FetchError::BlobNotFound)?;
let mime_type = mime_type_essence(&blob.media_type);
- let body = blob.read_all().await?;
+ let body = blob.read_all().await;
(Bytes::from(body), Some(mime_type), script)
}
_ => {
- return Err(type_error(format!(
- "Classic scripts with scheme {}: are not supported in workers.",
- script_url.scheme()
- )))
+ return Err(
+ SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers(
+ script_url.scheme().to_string(),
+ ),
+ )
}
};
@@ -151,18 +177,11 @@ pub fn op_worker_sync_fetch(
match mime_type.as_deref() {
Some("application/javascript" | "text/javascript") => {}
Some(mime_type) => {
- return Err(
- DomExceptionNetworkError {
- msg: format!("Invalid MIME type {mime_type:?}."),
- }
- .into(),
- )
- }
- None => {
- return Err(
- DomExceptionNetworkError::new("Missing MIME type.").into(),
- )
+ return Err(SyncFetchError::InvalidMimeType(
+ mime_type.to_string(),
+ ))
}
+ None => return Err(SyncFetchError::MissingMimeType),
}
}
diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs
index b9fd06654..521284a6a 100644
--- a/runtime/ops/worker_host.rs
+++ b/runtime/ops/worker_host.rs
@@ -10,7 +10,6 @@ use crate::web_worker::WorkerControlEvent;
use crate::web_worker::WorkerId;
use crate::web_worker::WorkerMetadata;
use crate::worker::FormatJsErrorFn;
-use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::serde::Deserialize;
use deno_core::CancelFuture;
@@ -21,6 +20,7 @@ use deno_permissions::ChildPermissionsArg;
use deno_permissions::PermissionsContainer;
use deno_web::deserialize_js_transferables;
use deno_web::JsMessageData;
+use deno_web::MessagePortError;
use log::debug;
use std::cell::RefCell;
use std::collections::HashMap;
@@ -118,6 +118,20 @@ pub struct CreateWorkerArgs {
close_on_idle: bool,
}
+#[derive(Debug, thiserror::Error)]
+pub enum CreateWorkerError {
+ #[error("Classic workers are not supported.")]
+ ClassicWorkers,
+ #[error(transparent)]
+ Permission(deno_permissions::ChildPermissionError),
+ #[error(transparent)]
+ ModuleResolution(#[from] deno_core::ModuleResolutionError),
+ #[error(transparent)]
+ MessagePort(#[from] MessagePortError),
+ #[error("{0}")]
+ Io(#[from] std::io::Error),
+}
+
/// Create worker as the host
#[op2]
#[serde]
@@ -125,7 +139,7 @@ fn op_create_worker(
state: &mut OpState,
#[serde] args: CreateWorkerArgs,
#[serde] maybe_worker_metadata: Option<JsMessageData>,
-) -> Result<WorkerId, AnyError> {
+) -> Result<WorkerId, CreateWorkerError> {
let specifier = args.specifier.clone();
let maybe_source_code = if args.has_source_code {
Some(args.source_code.clone())
@@ -136,12 +150,7 @@ fn op_create_worker(
let worker_type = args.worker_type;
if let WebWorkerType::Classic = worker_type {
if let TestingFeaturesEnabled(false) = state.borrow() {
- return Err(
- deno_webstorage::DomExceptionNotSupportedError::new(
- "Classic workers are not supported.",
- )
- .into(),
- );
+ return Err(CreateWorkerError::ClassicWorkers);
}
}
@@ -155,7 +164,9 @@ fn op_create_worker(
let parent_permissions = state.borrow_mut::<PermissionsContainer>();
let worker_permissions = if let Some(child_permissions_arg) = args.permissions
{
- parent_permissions.create_child_permissions(child_permissions_arg)?
+ parent_permissions
+ .create_child_permissions(child_permissions_arg)
+ .map_err(CreateWorkerError::Permission)?
} else {
parent_permissions.clone()
};
@@ -167,9 +178,8 @@ fn op_create_worker(
let module_specifier = deno_core::resolve_url(&specifier)?;
let worker_name = args_name.unwrap_or_default();
- let (handle_sender, handle_receiver) = std::sync::mpsc::sync_channel::<
- Result<SendableWebWorkerHandle, AnyError>,
- >(1);
+ let (handle_sender, handle_receiver) =
+ std::sync::mpsc::sync_channel::<SendableWebWorkerHandle>(1);
// Setup new thread
let thread_builder = std::thread::Builder::new().name(format!("{worker_id}"));
@@ -203,7 +213,7 @@ fn op_create_worker(
});
// Send thread safe handle from newly created worker to host thread
- handle_sender.send(Ok(external_handle)).unwrap();
+ handle_sender.send(external_handle).unwrap();
drop(handle_sender);
// At this point the only method of communication with host
@@ -219,7 +229,7 @@ fn op_create_worker(
})?;
// Receive WebWorkerHandle from newly created worker
- let worker_handle = handle_receiver.recv().unwrap()?;
+ let worker_handle = handle_receiver.recv().unwrap();
let worker_thread = WorkerThread {
worker_handle: worker_handle.into(),
@@ -292,7 +302,7 @@ fn close_channel(
async fn op_host_recv_ctrl(
state: Rc<RefCell<OpState>>,
#[serde] id: WorkerId,
-) -> Result<WorkerControlEvent, AnyError> {
+) -> WorkerControlEvent {
let (worker_handle, cancel_handle) = {
let state = state.borrow();
let workers_table = state.borrow::<WorkersTable>();
@@ -301,7 +311,7 @@ async fn op_host_recv_ctrl(
(handle.worker_handle.clone(), handle.cancel_handle.clone())
} else {
// If handle was not found it means worker has already shutdown
- return Ok(WorkerControlEvent::Close);
+ return WorkerControlEvent::Close;
}
};
@@ -310,22 +320,21 @@ async fn op_host_recv_ctrl(
.or_cancel(cancel_handle)
.await;
match maybe_event {
- Ok(Ok(Some(event))) => {
+ Ok(Some(event)) => {
// Terminal error means that worker should be removed from worker table.
if let WorkerControlEvent::TerminalError(_) = &event {
close_channel(state, id, WorkerChannel::Ctrl);
}
- Ok(event)
+ event
}
- Ok(Ok(None)) => {
+ Ok(None) => {
// If there was no event from worker it means it has already been closed.
close_channel(state, id, WorkerChannel::Ctrl);
- Ok(WorkerControlEvent::Close)
+ WorkerControlEvent::Close
}
- Ok(Err(err)) => Err(err),
Err(_) => {
// The worker was terminated.
- Ok(WorkerControlEvent::Close)
+ WorkerControlEvent::Close
}
}
}
@@ -335,7 +344,7 @@ async fn op_host_recv_ctrl(
async fn op_host_recv_message(
state: Rc<RefCell<OpState>>,
#[serde] id: WorkerId,
-) -> Result<Option<JsMessageData>, AnyError> {
+) -> Result<Option<JsMessageData>, MessagePortError> {
let (worker_handle, cancel_handle) = {
let s = state.borrow();
let workers_table = s.borrow::<WorkersTable>();
@@ -374,7 +383,7 @@ fn op_host_post_message(
state: &mut OpState,
#[serde] id: WorkerId,
#[serde] data: JsMessageData,
-) -> Result<(), AnyError> {
+) -> Result<(), MessagePortError> {
if let Some(worker_thread) = state.borrow::<WorkersTable>().get(&id) {
debug!("post message to worker {}", id);
let worker_handle = worker_thread.worker_handle.clone();