use crate::proto::{read_native, Out, Peer};
use crate::session::Session;
use std::io::{self, BufRead, Write};
use std::path::{Path, PathBuf};
pub struct ServeConfig {
pub socket: PathBuf,
pub tcp: Option<String>,
pub token: Option<String>,
pub name: Option<String>,
pub peers: Vec<String>,
}
pub fn stdio() {
let out = Peer::native(Box::new(io::stdout()));
let mut sess = Session::new();
let mut stdin = io::stdin();
while let Some(msg) = read_native(&mut stdin) {
sess.handle(&out, &msg);
}
}
fn die(msg: &str) -> ! {
let _ = writeln!(io::stderr(), "zwire-host: {msg}");
std::process::exit(1);
}
pub(crate) fn serve_conn(mut reader: impl BufRead, out: Out, mut sess: Session) {
while let Some(msg) = crate::proto::read_ndjson(&mut reader) {
sess.handle(&out, &msg);
}
}
#[cfg(any(unix, windows))]
fn run_client(reader: impl BufRead, mut writer: impl Write, request: &str, follow: bool) -> ! {
let line = format!("{}\n", request.trim());
if let Err(e) = writer
.write_all(line.as_bytes())
.and_then(|()| writer.flush())
{
die(&format!("send: {e}"));
}
let mut reader = reader;
let mut stdout = io::stdout();
loop {
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) | Err(_) => break,
Ok(_) => {
if stdout
.write_all(line.as_bytes())
.and_then(|()| stdout.flush())
.is_err()
{
break;
}
if !follow {
break; }
}
}
}
std::process::exit(0);
}
#[cfg(unix)]
mod platform {
use super::*;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::{UnixListener, UnixStream};
pub fn serve(sock: &Path) -> ! {
if let Some(parent) = sock.parent() {
let _ = std::fs::create_dir_all(parent);
let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700));
}
let _ = std::fs::remove_file(sock);
let listener = match UnixListener::bind(sock) {
Ok(l) => l,
Err(e) => die(&format!("bind {}: {e}", sock.display())),
};
let _ = std::fs::set_permissions(sock, std::fs::Permissions::from_mode(0o600));
let _ = writeln!(io::stderr(), "zwire-host: listening on {}", sock.display());
for conn in listener.incoming() {
match conn {
Ok(stream) => {
std::thread::spawn(move || handle(stream));
}
Err(e) => {
let _ = writeln!(io::stderr(), "zwire-host: accept: {e}");
}
}
}
std::process::exit(0);
}
fn handle(stream: UnixStream) {
let Ok(rclone) = stream.try_clone() else {
return;
};
let out = Peer::ndjson(Box::new(stream));
serve_conn(io::BufReader::new(rclone), out, Session::new());
}
pub fn call(sock: &Path, request: &str, follow: bool) -> ! {
let stream = match UnixStream::connect(sock) {
Ok(s) => s,
Err(e) => die(&format!(
"connect {}: {e} (is the daemon running? `zwire-host serve`)",
sock.display()
)),
};
let rclone = match stream.try_clone() {
Ok(r) => r,
Err(e) => die(&format!("clone: {e}")),
};
run_client(io::BufReader::new(rclone), stream, request, follow);
}
}
#[cfg(windows)]
mod platform {
use super::*;
use interprocess::local_socket::{prelude::*, GenericNamespaced, ListenerOptions, Stream};
fn pipe_name(sock: &Path) -> String {
sock.file_name()
.map(|s| s.to_string_lossy().into_owned())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "zwire-host".to_string())
}
pub fn serve(sock: &Path) -> ! {
let raw = pipe_name(sock);
let name = match raw.clone().to_ns_name::<GenericNamespaced>() {
Ok(n) => n,
Err(e) => die(&format!("bad pipe name {raw}: {e}")),
};
let listener = match ListenerOptions::new().name(name).create_sync() {
Ok(l) => l,
Err(e) => die(&format!(r"bind \\.\pipe\{raw}: {e}")),
};
let _ = writeln!(io::stderr(), r"zwire-host: listening on \\.\pipe\{raw}");
loop {
match listener.accept() {
Ok(stream) => {
std::thread::spawn(move || handle(stream));
}
Err(e) => {
let _ = writeln!(io::stderr(), "zwire-host: accept: {e}");
}
}
}
}
fn handle(stream: Stream) {
let (recv, send) = stream.split();
let out = Peer::ndjson(Box::new(send));
serve_conn(io::BufReader::new(recv), out, Session::new());
}
pub fn call(sock: &Path, request: &str, follow: bool) -> ! {
let raw = pipe_name(sock);
let name = match raw.clone().to_ns_name::<GenericNamespaced>() {
Ok(n) => n,
Err(e) => die(&format!("bad pipe name {raw}: {e}")),
};
let stream = match Stream::connect(name) {
Ok(s) => s,
Err(e) => die(&format!(
r"connect \\.\pipe\{raw}: {e} (is the daemon running? `zwire-host serve`)"
)),
};
let (recv, send) = stream.split();
run_client(io::BufReader::new(recv), send, request, follow);
}
}
#[cfg(not(any(unix, windows)))]
mod platform {
use super::*;
pub fn serve(_sock: &Path) -> ! {
die("`serve` is not supported on this platform");
}
pub fn call(_sock: &Path, _request: &str, _follow: bool) -> ! {
die("`call` is not supported on this platform");
}
}
pub use platform::call;
pub fn serve(cfg: ServeConfig) -> ! {
crate::peer::configure(cfg.token, cfg.name);
if let Some(addr) = cfg.tcp {
crate::peer::listen_tcp(addr);
}
for peer in cfg.peers {
crate::peer::dial(peer);
}
platform::serve(&cfg.socket)
}