#[cfg(unix)]
pub use unix::start_if_requested;
#[cfg(not(unix))]
pub fn start_if_requested() {
if std::env::var_os("YOG_CONTROL_SOCKET").is_some() {
yog_logging::warn!("control socket: not supported on this platform");
}
}
#[cfg(unix)]
mod unix {
use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::Path;
pub fn start_if_requested() {
let Ok(path) = std::env::var("YOG_CONTROL_SOCKET") else {
return;
};
let _ = std::fs::remove_file(&path);
let listener = match UnixListener::bind(&path) {
Ok(l) => l,
Err(e) => {
yog_logging::error!("control socket: failed to bind {}: {}", path, e);
return;
}
};
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
handle_client(stream);
}
});
}
#[cfg(target_os = "linux")]
fn allow_peer_to_trace(stream: &UnixStream) -> bool {
use std::os::fd::AsRawFd;
let mut cred = libc::ucred {
pid: 0,
uid: 0,
gid: 0,
};
let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;
let rc = unsafe {
libc::getsockopt(
stream.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERCRED,
(&mut cred as *mut libc::ucred).cast(),
&mut len,
)
};
if rc != 0 || cred.pid <= 0 || cred.uid != unsafe { libc::getuid() } {
return false;
}
let rc = unsafe { libc::prctl(libc::PR_SET_PTRACER, cred.pid as libc::c_ulong, 0, 0, 0) };
if rc == 0 {
return true;
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EINVAL) {
return true;
}
yog_logging::warn!(
"control socket: PR_SET_PTRACER({}) failed: {}",
cred.pid,
err
);
false
}
fn write_line(stream: &mut UnixStream, json: &serde_json::Value) {
let mut line = json.to_string();
line.push('\n');
let _ = stream.write_all(line.as_bytes());
}
fn mod_list_json() -> Vec<serde_json::Value> {
crate::MOD_INFOS
.lock()
.expect("mod infos lock poisoned")
.iter()
.map(|m| serde_json::json!({ "id": m[0], "name": m[1], "version": m[2] }))
.collect()
}
fn handle_client(mut stream: UnixStream) {
#[cfg(target_os = "linux")]
let ptracer = allow_peer_to_trace(&stream);
#[cfg(not(target_os = "linux"))]
let ptracer = false;
let ready = serde_json::json!({
"event": "ready",
"pid": std::process::id(),
"mods": mod_list_json(),
"ptracer": ptracer,
});
write_line(&mut stream, &ready);
let reader = match stream.try_clone() {
Ok(s) => BufReader::new(s),
Err(_) => return,
};
for line in reader.lines().map_while(Result::ok) {
let Ok(cmd) = serde_json::from_str::<serde_json::Value>(&line) else {
continue;
};
match cmd.get("cmd").and_then(|c| c.as_str()) {
Some("hot_reload") => {
let mod_id = cmd
.get("mod_id")
.and_then(|v| v.as_str())
.unwrap_or_default();
let yog_path = cmd
.get("yog_path")
.and_then(|v| v.as_str())
.unwrap_or_default();
let ok = crate::trigger_hot_reload(mod_id, Path::new(yog_path));
let response = serde_json::json!({ "event": "hot-reload-done", "mod_id": mod_id, "ok": ok });
write_line(&mut stream, &response);
}
Some("list_mods") => {
let response = serde_json::json!({ "event": "mods", "mods": mod_list_json() });
write_line(&mut stream, &response);
}
_ => {}
}
}
}
}