diff --git a/Cargo.lock b/Cargo.lock index b249915..4417099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,7 +330,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -621,6 +621,26 @@ dependencies = [ "litrs", ] +[[package]] +name = "door-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78929f3574c3a17e7a7a844bcb1cbcbf165d3e366432f81cef4e8cb6e8c2a715" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "doors" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571031accecc9496ab74c0ce6d9d99ef779a7344dd27f504e1574edd344cd479" +dependencies = [ + "door-macros", + "libc", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -738,7 +758,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1033,7 +1053,7 @@ checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1079,7 +1099,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.117", ] [[package]] @@ -1107,7 +1127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1309,7 +1329,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1448,7 +1468,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1795,7 +1815,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1910,7 +1930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -1947,7 +1967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2399,7 +2419,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2641,7 +2661,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.117", ] [[package]] @@ -2671,6 +2691,17 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -2750,7 +2781,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2761,7 +2792,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2856,7 +2887,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2950,7 +2981,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3140,7 +3171,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -3338,6 +3369,7 @@ dependencies = [ name = "wayray-protocol" version = "0.1.0" dependencies = [ + "doors", "postcard", "serde", "serde_json", @@ -3535,7 +3567,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3546,7 +3578,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3831,7 +3863,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3847,7 +3879,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3904,10 +3936,6 @@ name = "wradm" version = "0.1.0" dependencies = [ "miette", - "serde_json", - "tokio", - "tracing", - "tracing-subscriber", "wayray-protocol", ] @@ -3936,9 +3964,6 @@ name = "wrlogin" version = "0.1.0" dependencies = [ "miette", - "serde", - "serde_json", - "tokio", "tracing", "tracing-subscriber", "wayray-protocol", @@ -4076,7 +4101,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/crates/wayray-protocol/Cargo.toml b/crates/wayray-protocol/Cargo.toml index 9f53eef..4f0d090 100644 --- a/crates/wayray-protocol/Cargo.toml +++ b/crates/wayray-protocol/Cargo.toml @@ -4,6 +4,10 @@ edition.workspace = true version.workspace = true license.workspace = true +[features] +default = [] +doors = ["dep:doors"] + [dependencies] serde.workspace = true postcard.workspace = true @@ -12,3 +16,6 @@ tracing.workspace = true zstd.workspace = true toml = "0.8" serde_json = "1" + +[target.'cfg(target_os = "illumos")'.dependencies] +doors = { version = "0.8", optional = true } diff --git a/crates/wayray-protocol/src/lib.rs b/crates/wayray-protocol/src/lib.rs index dcf04ed..52ce642 100644 --- a/crates/wayray-protocol/src/lib.rs +++ b/crates/wayray-protocol/src/lib.rs @@ -9,6 +9,7 @@ pub mod encoding; pub mod launcher; pub mod messages; pub mod session_config; +pub mod transport; /// Current protocol version. Incremented on breaking changes. pub const PROTOCOL_VERSION: u32 = 1; diff --git a/crates/wayray-protocol/src/transport.rs b/crates/wayray-protocol/src/transport.rs new file mode 100644 index 0000000..478ac7d --- /dev/null +++ b/crates/wayray-protocol/src/transport.rs @@ -0,0 +1,114 @@ +//! IPC transport abstraction for the launcher protocol. +//! +//! Provides platform-specific transports for request/response communication +//! between WayRay components (wrsessd, wradm, wrlogin, wrsrvd). +//! +//! - **Linux**: Unix domain sockets (async, via tokio) +//! - **illumos**: Doors IPC (sync, high-speed RPC) with Unix socket fallback +//! +//! The transport moves JSON bytes — serialization is handled by the caller +//! using [`LauncherRequest`] and [`LauncherResponse`]. + +use std::io; +use std::path::{Path, PathBuf}; + +use crate::launcher::{LauncherRequest, LauncherResponse}; + +/// Default IPC endpoint path. +/// +/// On illumos with doors enabled, this is a door file. +/// On Linux, this is a Unix socket. +pub fn default_ipc_path() -> PathBuf { + let runtime_dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()); + PathBuf::from(runtime_dir).join("wayray-launcher.sock") +} + +/// Send a request and receive a response (synchronous, blocking). +/// +/// Automatically selects the transport based on the platform: +/// - illumos with `doors` feature: uses doors IPC +/// - everywhere else: uses Unix socket with blocking I/O +pub fn send_request_sync(path: &Path, request: &LauncherRequest) -> io::Result { + #[cfg(all(target_os = "illumos", feature = "doors"))] + { + doors_transport::send_request(path, request) + } + + #[cfg(not(all(target_os = "illumos", feature = "doors")))] + { + unix_transport::send_request(path, request) + } +} + +// ============================================================================= +// Unix socket transport (all platforms, used as default) +// ============================================================================= + +pub mod unix_transport { + use std::io::{self, BufRead, BufReader, Write}; + use std::os::unix::net::UnixStream; + use std::path::Path; + + use crate::launcher::{LauncherRequest, LauncherResponse}; + + /// Send a request over a Unix socket and read the response. + pub fn send_request(path: &Path, request: &LauncherRequest) -> io::Result { + let stream = UnixStream::connect(path)?; + let mut writer = io::BufWriter::new(&stream); + let mut reader = BufReader::new(&stream); + + let mut json = serde_json::to_string(request) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + json.push('\n'); + writer.write_all(json.as_bytes())?; + writer.flush()?; + + let mut response_line = String::new(); + reader.read_line(&mut response_line)?; + + serde_json::from_str(response_line.trim()) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } +} + +// ============================================================================= +// Doors transport (illumos only) +// ============================================================================= + +#[cfg(all(target_os = "illumos", feature = "doors"))] +pub mod doors_transport { + use std::io; + use std::path::Path; + + use crate::launcher::{LauncherRequest, LauncherResponse}; + + /// Send a request via illumos doors IPC. + /// + /// Doors are synchronous RPC: call_with_data sends bytes and blocks + /// until the server procedure returns a response. + pub fn send_request(path: &Path, request: &LauncherRequest) -> io::Result { + let json = serde_json::to_vec(request) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + let client = doors::Client::open(path) + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, format!("{e:?}")))?; + + let response_bytes = client + .call_with_data(&json) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{e:?}")))?; + + serde_json::from_slice(&response_bytes) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_path_is_reasonable() { + let path = default_ipc_path(); + assert!(path.to_str().unwrap().contains("wayray-launcher")); + } +} diff --git a/crates/wradm/Cargo.toml b/crates/wradm/Cargo.toml index 9ffd791..59379e0 100644 --- a/crates/wradm/Cargo.toml +++ b/crates/wradm/Cargo.toml @@ -6,8 +6,4 @@ license.workspace = true [dependencies] wayray-protocol.workspace = true -tracing.workspace = true -tracing-subscriber.workspace = true miette.workspace = true -serde_json = "1" -tokio = { workspace = true, features = ["rt", "net", "io-util", "macros"] } diff --git a/crates/wradm/src/main.rs b/crates/wradm/src/main.rs index 3975238..54c760d 100644 --- a/crates/wradm/src/main.rs +++ b/crates/wradm/src/main.rs @@ -1,61 +1,33 @@ //! wradm -- WayRay administration CLI. //! //! Provides session management commands following the illumos `zoneadm`/`svcadm` -//! pattern. Communicates with the session launcher (wrsessd) via Unix socket. +//! pattern. Communicates with the session launcher (wrsessd) via the platform +//! IPC transport (Unix sockets on Linux, doors on illumos). //! //! ## Commands //! //! - `wradm list` — List all managed sessions //! - `wradm kill ` — Kill a session by token -//! - `wradm show ` — Show details for a session use std::path::PathBuf; use miette::Result; -use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; -use tokio::net::UnixStream; use wayray_protocol::launcher::{LauncherRequest, LauncherResponse}; +use wayray_protocol::transport; -/// Default launcher socket path. -fn default_socket_path() -> PathBuf { - let runtime_dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(runtime_dir).join("wayray-launcher.sock") +fn ipc_path() -> PathBuf { + std::env::var("WAYRAY_LAUNCHER_SOCKET") + .map(PathBuf::from) + .unwrap_or_else(|_| transport::default_ipc_path()) } -/// Send a request to the launcher and read the response. -async fn send_request(request: &LauncherRequest) -> Result { - let socket_path = std::env::var("WAYRAY_LAUNCHER_SOCKET") - .map(PathBuf::from) - .unwrap_or_else(|_| default_socket_path()); - - let stream = UnixStream::connect(&socket_path).await.map_err(|e| { +fn send(request: &LauncherRequest) -> Result { + transport::send_request_sync(&ipc_path(), request).map_err(|e| { miette::miette!( - "failed to connect to launcher at {}: {}\n\nIs wrsessd running?", - socket_path.display(), + "launcher communication failed: {}\n\nIs wrsessd running?", e ) - })?; - - let (reader, mut writer) = stream.into_split(); - - let mut json = serde_json::to_string(request) - .map_err(|e| miette::miette!("serialization error: {}", e))?; - json.push('\n'); - - writer - .write_all(json.as_bytes()) - .await - .map_err(|e| miette::miette!("failed to send request: {}", e))?; - - let mut reader = BufReader::new(reader); - let mut response_line = String::new(); - reader - .read_line(&mut response_line) - .await - .map_err(|e| miette::miette!("failed to read response: {}", e))?; - - serde_json::from_str(response_line.trim()) - .map_err(|e| miette::miette!("failed to parse response: {}", e)) + }) } fn print_usage() { @@ -66,15 +38,7 @@ fn print_usage() { eprintln!(" kill Kill a session by token"); } -#[tokio::main] -async fn main() -> Result<()> { - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")), - ) - .init(); - +fn main() -> Result<()> { let args: Vec = std::env::args().collect(); let command = args.get(1).map(String::as_str).unwrap_or_else(|| { @@ -84,7 +48,7 @@ async fn main() -> Result<()> { match command { "list" => { - let response = send_request(&LauncherRequest::ListSessions).await?; + let response = send(&LauncherRequest::ListSessions)?; match response { LauncherResponse::SessionList { sessions } => { if sessions.is_empty() { @@ -110,10 +74,9 @@ async fn main() -> Result<()> { let token = args .get(2) .ok_or_else(|| miette::miette!("Usage: wradm kill "))?; - let response = send_request(&LauncherRequest::KillSession { + let response = send(&LauncherRequest::KillSession { token: token.clone(), - }) - .await?; + })?; match response { LauncherResponse::SessionKilled { token } => { println!("Session {token} killed."); diff --git a/crates/wrlogin/Cargo.toml b/crates/wrlogin/Cargo.toml index adc38e0..d3b5be8 100644 --- a/crates/wrlogin/Cargo.toml +++ b/crates/wrlogin/Cargo.toml @@ -10,6 +10,3 @@ wayray-protocol.workspace = true tracing.workspace = true tracing-subscriber.workspace = true miette.workspace = true -serde.workspace = true -serde_json = "1" -tokio = { workspace = true, features = ["rt", "net", "io-util", "macros"] } diff --git a/crates/wrlogin/src/main.rs b/crates/wrlogin/src/main.rs index bb15fd0..adecb0c 100644 --- a/crates/wrlogin/src/main.rs +++ b/crates/wrlogin/src/main.rs @@ -13,17 +13,16 @@ //! The session launcher (wrsessd) starts wrlogin as the first client //! in a new session. wrlogin: //! 1. Prompts for username and password on the terminal -//! 2. Sends `session_authenticated` to wrsessd via the launcher socket +//! 2. Sends `session_authenticated` to wrsessd via the launcher IPC //! 3. Exits on success, allowing the desktop to start use std::io::{self, BufRead, Write}; use std::path::PathBuf; use miette::Result; -use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; -use tokio::net::UnixStream; use tracing::info; use wayray_protocol::launcher::{LauncherRequest, LauncherResponse}; +use wayray_protocol::transport; /// Read a line from stdin with a prompt. fn prompt(message: &str) -> io::Result { @@ -36,53 +35,13 @@ fn prompt(message: &str) -> io::Result { Ok(line.trim().to_string()) } -/// Send an authentication request to the session launcher. -async fn authenticate(socket_path: &PathBuf, token: &str, user: &str) -> Result { - let stream = UnixStream::connect(socket_path).await.map_err(|e| { - miette::miette!( - "failed to connect to launcher at {}: {}", - socket_path.display(), - e - ) - })?; - - let (reader, mut writer) = stream.into_split(); - - let request = LauncherRequest::SessionAuthenticated { - token: token.to_string(), - user: user.to_string(), - }; - - let mut json = serde_json::to_string(&request) - .map_err(|e| miette::miette!("failed to serialize request: {}", e))?; - json.push('\n'); - - writer - .write_all(json.as_bytes()) - .await - .map_err(|e| miette::miette!("failed to send to launcher: {}", e))?; - - let mut reader = tokio::io::BufReader::new(reader); - let mut response_line = String::new(); - reader - .read_line(&mut response_line) - .await - .map_err(|e| miette::miette!("failed to read launcher response: {}", e))?; - - let response: LauncherResponse = serde_json::from_str(response_line.trim()) - .map_err(|e| miette::miette!("failed to parse launcher response: {}", e))?; - - Ok(response) +fn ipc_path() -> PathBuf { + std::env::var("WAYRAY_LAUNCHER_SOCKET") + .map(PathBuf::from) + .unwrap_or_else(|_| transport::default_ipc_path()) } -/// Default launcher socket path. -fn default_socket_path() -> PathBuf { - let runtime_dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(runtime_dir).join("wayray-launcher.sock") -} - -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() @@ -90,10 +49,6 @@ async fn main() -> Result<()> { ) .init(); - let socket_path = std::env::var("WAYRAY_LAUNCHER_SOCKET") - .map(PathBuf::from) - .unwrap_or_else(|_| default_socket_path()); - // The session token is passed via environment variable by the launcher. let token = std::env::var("WAYRAY_SESSION_TOKEN").unwrap_or_else(|_| { eprintln!("warning: WAYRAY_SESSION_TOKEN not set, using 'unknown'"); @@ -105,6 +60,8 @@ async fn main() -> Result<()> { println!(" ============"); println!(); + let path = ipc_path(); + // Simple login loop. loop { let user = prompt(" Username: ").map_err(|e| miette::miette!("input error: {}", e))?; @@ -121,11 +78,15 @@ async fn main() -> Result<()> { info!(%user, "authenticating with session launcher"); - match authenticate(&socket_path, &token, &user).await { + let request = LauncherRequest::SessionAuthenticated { + token: token.clone(), + user: user.clone(), + }; + + match transport::send_request_sync(&path, &request) { Ok(LauncherResponse::DesktopStarted { user, .. }) => { println!(" Welcome, {user}! Starting desktop..."); println!(); - // Exit — the launcher starts the desktop components. return Ok(()); } Ok(LauncherResponse::Error { message, .. }) => { diff --git a/crates/wrsessd/src/main.rs b/crates/wrsessd/src/main.rs index 5ea81db..e7539e9 100644 --- a/crates/wrsessd/src/main.rs +++ b/crates/wrsessd/src/main.rs @@ -169,10 +169,9 @@ impl Launcher { } } -/// Default socket path for the launcher. +/// Default IPC path for the launcher (uses shared transport default). fn default_socket_path() -> PathBuf { - let runtime_dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(runtime_dir).join("wayray-launcher.sock") + wayray_protocol::transport::default_ipc_path() } #[tokio::main]