use {
std::{
io::{BufReader, Error, ErrorKind, Read},
os::unix::{
net::UnixStream,
process::CommandExt,
},
process::Command,
},
crate::{MAP_KIND, shared},
blackhole::Job,
nairud::{Array, Decoder, Encoder, Map, Nairud},
namaste::UdsxUnixStream,
r50::Result,
};
#[derive(Debug)]
pub struct ClientHandler {
stream: UnixStream,
}
impl ClientHandler {
pub const fn new(stream: UnixStream) -> Self {
Self {
stream,
}
}
}
impl Job for ClientHandler {
fn run(&mut self) -> Option<Box<dyn Job>> {
let [stdin, stdout, stderr] = match unsafe {
self.stream.recv_ioe(shared::ID_OF_IOE)
} {
Ok(std) => std,
Err(err) => {
eprintln!("[{stream:?}] <- failed to receive standard streams: {err:?}", stream=self.stream, err=err);
return None;
},
};
let cred = match self.stream.peer_cred() {
Ok(cred) => cred,
Err(err) => {
eprintln!("[{stream:?}] <- failed to receive credentials: {err:?}", stream=self.stream, err=err);
return None;
},
};
let mut cmd = match recv_cmd(&mut self.stream) {
Ok(cmd) => cmd,
Err(err) => {
eprintln!("[{stream:?}] <- failed to receive command: {err:?}", stream=self.stream, err=err);
return None;
},
};
let cmd_as_string = format!("{:?}", &cmd);
match cmd.uid(cred.uid).gid(cred.gid).stdin(stdin).stdout(stdout).stderr(stderr).spawn() {
Ok(mut proc) => {
if let Err(err) = self.stream.encode(proc.id()) {
eprintln!(
"[{stream:?}] Failed to send process ID #{proc_id} to client: {err:?}", stream=self.stream, proc_id=proc.id(), err=err,
);
}
match proc.wait() {
Ok(status) => {
if let Err(err) = self.stream.encode(status.code()) {
eprintln!(
"[{stream:?}] Failed to send exit-status {status:?} to client: {err:?}",
stream=self.stream, status=status, err=err,
);
}
},
Err(err) => if let Err(err) = self.stream.encode(format!(
"Failed to wait for process #{proc_id}: {err:?}", proc_id=proc.id(), err=err,
)) {
eprintln!("[{stream:?}] Failed to send error message to client: {err:?}", stream=self.stream, err=err);
},
};
},
Err(err) => if let Err(err) = self.stream.encode(format!(
"Failed to run command {cmd:?}: {err:?}", cmd=cmd_as_string, err=err,
)) {
eprintln!("[{stream:?}] Failed to send error message to client: {err:?}", stream=self.stream, err=err);
},
};
None
}
}
fn recv_cmd(stream: &mut UnixStream) -> Result<Command> {
let mut stream = BufReader::new(stream);
let content_size = u16::try_from(stream.decode(MAP_KIND)?.ok_or_else(|| Error::new(ErrorKind::InvalidData, "Missing content size"))?)?;
let mut stream = stream.take(content_size.into());
let work_dir = String::try_from(stream.decode(MAP_KIND)?.ok_or_else(|| Error::new(ErrorKind::InvalidData, "Missing work directory"))?)?;
let mut args = Array::try_from(stream.decode(MAP_KIND)?.ok_or_else(|| Error::new(ErrorKind::InvalidData, "Missing command"))?)?;
let env_vars = match stream.decode(MAP_KIND)? {
None => None,
Some(env_vars) => Some(Map::try_from(env_vars)?),
};
if args.is_empty() {
return Err(Error::new(ErrorKind::InvalidData, "Missing command name"));
}
let mut result = match args.remove(0) {
Nairud::String(cmd) => Command::new(cmd),
other => return Err(Error::new(
ErrorKind::InvalidData, format!("Invalid command name: expected Nairud::String, got: {:?}", other),
)),
};
result.env_clear();
result.current_dir(work_dir);
for arg in args {
match arg {
Nairud::String(arg) => result.arg(arg),
other => return Err(Error::new(
ErrorKind::InvalidData, format!("Invalid command argument: expected Nairud::String, got: {:?}", other),
)),
};
}
if let Some(env_vars) = env_vars {
for (key, value) in env_vars.into_iter() {
match value {
Nairud::String(value) => result.env(
String::from_utf8(key).map_err(|_| Error::new(ErrorKind::InvalidData, "Key is not UTF-8 string"))?,
value,
),
other => return Err(Error::new(
ErrorKind::InvalidData, format!("Invalid environment value of {:?}: expected Nairud::String, got: {:?}", key, other),
)),
};
}
}
Ok(result)
}