diff --git a/src/args.rs b/src/args.rs index 5c9bb73..62fb5ff 100644 --- a/src/args.rs +++ b/src/args.rs @@ -4,6 +4,7 @@ use std::{ env, + net::SocketAddr, sync::{Arc, LazyLock}, }; @@ -35,6 +36,12 @@ pub struct DaemonCmd { #[arg(long)] #[arg(conflicts_with = "stdin")] pub socket: Option, + + /// Use a TCP port instead of a Unix socket or stdin. e.g.: 0.0.0.0:42420 + #[arg(long)] + #[arg(conflicts_with = "socket")] + #[arg(conflicts_with = "stdin")] + pub tcp: Option, } #[derive(Debug, Clone, PartialEq, clap::Subcommand)] diff --git a/src/daemon.rs b/src/daemon.rs index c6c944b..656fd11 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,5 +1,7 @@ use std::{ + borrow::Cow, env, io, + net::SocketAddr, os::fd::{AsFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd}, process::{Command, Stdio}, sync::{ @@ -11,7 +13,12 @@ use std::{ use iddqd::{BiHashMap, IdOrdMap}; -use mio::{Events, Interest, Poll, Token, event::Event, net::UnixListener, unix::SourceFd}; +use mio::{ + Events, Interest, Poll, Token, + event::Event, + net::{TcpListener, UnixListener}, + unix::SourceFd, +}; use rustix::{ buffer::spare_capacity, @@ -56,14 +63,6 @@ pub static TMPDIR: LazyLock<&'static Path> = LazyLock::new(|| { Box::leak(dir) }); -pub static NIXOS_REBUILD: LazyLock<&'static Path> = LazyLock::new(|| { - which::which("nixos-rebuild") - .inspect_err(|e| error!("couldn't find `nixos-rebuild` in PATH: {e}")) - .map(PathBuf::into_boxed_path) - .map(|boxed| &*Box::leak(boxed)) - .unwrap_or(Path::new("/run/current-system/sw/bin/nixos-rebuild")) -}); - pub static NIX: LazyLock<&'static Path> = LazyLock::new(|| { which::which("nix") .inspect_err(|e| error!("couldn't find `nix` in PATH: {e}")) @@ -243,7 +242,11 @@ impl Daemon { .insert_unique(FdInfo::new(fd.as_raw_fd(), kind)) .unwrap_or_else(|e| unreachable!("{e}")); - debug!("opened daemon to {:?} file descriptor {fd:?}", name); + if let Some(name) = &name { + info!("opened daemon to {}", name.to_string_lossy()); + } else { + debug!("opened daemon to {name:?} (fd {fd:?})"); + } let path = name .as_ref() @@ -260,6 +263,24 @@ impl Daemon { } } + pub fn from_tcp_socket_addr(config_path: Arc, addr: SocketAddr) -> Result { + let listener = TcpListener::bind(addr.clone()) + .inspect_err(|e| error!("failed to bind to '{addr}': {e}"))?; + + let listener_owned_fd = OwnedFd::from(listener); + // FIXME: should we KEEP_ALIVE? + rustix::net::sockopt::set_socket_keepalive(&listener_owned_fd, true).unwrap(); + + let name = OsString::from(addr.to_string()).into_boxed_os_str(); + + Ok(Self::new( + config_path, + listener_owned_fd, + FdKind::Socket, + Some(name), + )) + } + pub fn from_unix_socket_path(config_path: Arc, path: &Path) -> Result { // We unconditionally unlink() `path` before binding, but completely ignore the result. let _ = rustix::fs::unlink(path); diff --git a/src/daemon_tokfd.rs b/src/daemon_tokfd.rs index 8342685..d80b4a2 100644 --- a/src/daemon_tokfd.rs +++ b/src/daemon_tokfd.rs @@ -93,7 +93,7 @@ impl<'a> Display for FdInfoDisplay<'a> { write!( f, "{} fd {} ({})", - self.inner.kind.name_str(), + self.inner.kind, self.inner.fd, self.inner.name().to_string_lossy(), )?; @@ -136,6 +136,19 @@ impl FdKind { } } +impl Display for FdKind { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + use FdKind::*; + let name = self.name_str(); + match self { + ChildStdout(pid) | ChildStderr(pid) | Pid(pid) => write!(f, "{name} for {pid}")?, + _ => write!(f, "{name}")?, + }; + + Ok(()) + } +} + #[derive(Copy)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TokenFd { diff --git a/src/lib.rs b/src/lib.rs index cfa921f..fe79677 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,6 +162,7 @@ pub fn do_daemon(args: Arc, daemon_args: DaemonCmd) -> Result<(), BoxDynEr let mut daemon = match daemon_args { DaemonCmd { stdin: true, .. } => Daemon::from_stdin(config_file), + DaemonCmd { tcp: Some(tcp), .. } => Daemon::from_tcp_socket_addr(config_file, tcp)?, DaemonCmd { socket: None, .. } => Daemon::open_default_socket(config_file)?, DaemonCmd { socket: Some(socket),