use clap::Parser;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_listener::{self, SystemOptions, UserOptions};
use tracing::{debug, error, info, instrument, Level};
use nix_compat::wire;
use nix_compat::worker_protocol::{self, server_handshake_client, ClientSettings, Trust};
#[derive(Parser, Debug)]
struct Cli {
/// Listening unix socket path
#[arg(short, long)]
socket: Option<String>,
/// Log verbosity level. Can be "error", "warn", "info", "debug", "trace", or a number 1-5
#[arg(short, long, env)]
verbosity: Option<Level>,
}
#[tokio::main]
#[instrument()]
async fn main() {
let args = Cli::parse();
tracing_subscriber::fmt()
.compact()
.with_max_level(
args.verbosity
.unwrap_or_else(|| panic!("Can't parse log verbosity")),
)
.try_init()
.unwrap();
info!("Started Tvix daemon");
let addr = args
.socket
.unwrap_or_else(|| "sd_listen_unix".to_string())
.parse()
.expect("Invalid listening socket address");
let system_options: SystemOptions = Default::default();
let mut user_options: UserOptions = Default::default();
user_options.recv_buffer_size = Some(1024);
user_options.send_buffer_size = Some(1024);
info!(user_options.send_buffer_size);
info!(user_options.recv_buffer_size);
let mut listener = tokio_listener::Listener::bind(&addr, &system_options, &user_options)
.await
.unwrap();
info!(listener_address = ?listener, "Listening for incoming connections");
while let Ok((conn, addr)) = listener.accept().await {
info!(addr = %addr, "Incoming connection");
tokio::spawn(async move { worker(conn).await });
}
}
/// Structure used to hold the client socket connection and some
/// metadata about the connection.
#[derive(Debug)]
struct ClientConnection<R: AsyncReadExt + AsyncWriteExt + Unpin> {
pub conn: R,
pub version_minor: u64,
pub client_settings: Option<ClientSettings>,
}
/// Worker in charge to respond a Nix client using the Nix wire
/// protocol.
#[instrument()]
async fn worker<R>(mut conn: R)
where
R: AsyncReadExt + AsyncWriteExt + Unpin + std::fmt::Debug,
{
match server_handshake_client(&mut conn, "2.18.2", Trust::Trusted).await {
Ok(client_protocol_version) => {
let mut client_connection = ClientConnection {
conn,
version_minor: client_protocol_version,
client_settings: None,
};
debug!("Client hanshake succeeded");
debug!(client_protocol_version = ?client_protocol_version);
// TODO: implement logging. For now, we'll just send
// STDERR_LAST, which is good enough to get Nix respond to
// us.
wire::write_u64(&mut client_connection.conn, worker_protocol::STDERR_LAST)
.await
.unwrap();
loop {
let op = worker_protocol::read_op(&mut client_connection.conn)
.await
.unwrap();
match op {
worker_protocol::Operation::SetOptions => {
let settings = op_set_options(&mut client_connection).await.unwrap();
client_connection.client_settings = Some(settings);
debug!(settings = ?client_connection.client_settings, "Received client settings");
}
_ => {
error!(op = ?op, "Unimplemented operation");
break;
}
}
}
}
Err(e) => error!("Client handshake failed: {}", e),
}
}
async fn op_set_options<R>(conn: &mut ClientConnection<R>) -> std::io::Result<ClientSettings>
where
R: AsyncReadExt + AsyncWriteExt + Unpin + std::fmt::Debug,
{
let settings =
worker_protocol::read_client_settings(&mut conn.conn, conn.version_minor).await?;
// The client expects us to send some logs when we're processing
// the settings. Sending STDERR_LAST signal we're done processing.
wire::write_u64(&mut conn.conn, worker_protocol::STDERR_LAST).await?;
Ok(settings)
}