1use std::{collections::HashMap, process::Stdio};
2
3use anyhow::{anyhow, Result};
4use krata::idm::{
5 client::IdmClientStreamResponseHandle,
6 internal::{
7 exec_stream_request_update::Update, request::Request as RequestType,
8 ExecStreamResponseUpdate,
9 },
10 internal::{response::Response as ResponseType, Request, Response},
11};
12use tokio::{
13 io::{AsyncReadExt, AsyncWriteExt},
14 join,
15 process::Command,
16};
17
18pub struct GuestExecTask {
19 pub handle: IdmClientStreamResponseHandle<Request>,
20}
21
22impl GuestExecTask {
23 pub async fn run(&self) -> Result<()> {
24 let mut receiver = self.handle.take().await?;
25
26 let Some(ref request) = self.handle.initial.request else {
27 return Err(anyhow!("request was empty"));
28 };
29
30 let RequestType::ExecStream(update) = request else {
31 return Err(anyhow!("request was not an exec update"));
32 };
33
34 let Some(Update::Start(ref start)) = update.update else {
35 return Err(anyhow!("first request did not contain a start update"));
36 };
37
38 let mut cmd = start.command.clone();
39 if cmd.is_empty() {
40 return Err(anyhow!("command line was empty"));
41 }
42 let exe = cmd.remove(0);
43 let mut env = HashMap::new();
44 for entry in &start.environment {
45 env.insert(entry.key.clone(), entry.value.clone());
46 }
47
48 if !env.contains_key("PATH") {
49 env.insert(
50 "PATH".to_string(),
51 "/bin:/usr/bin:/usr/local/bin".to_string(),
52 );
53 }
54
55 let dir = if start.working_directory.is_empty() {
56 "/".to_string()
57 } else {
58 start.working_directory.clone()
59 };
60
61 let mut child = Command::new(exe)
62 .args(cmd)
63 .envs(env)
64 .current_dir(dir)
65 .stdin(Stdio::piped())
66 .stdout(Stdio::piped())
67 .stderr(Stdio::piped())
68 .kill_on_drop(true)
69 .spawn()
70 .map_err(|error| anyhow!("failed to spawn: {}", error))?;
71
72 let mut stdin = child
73 .stdin
74 .take()
75 .ok_or_else(|| anyhow!("stdin was missing"))?;
76 let mut stdout = child
77 .stdout
78 .take()
79 .ok_or_else(|| anyhow!("stdout was missing"))?;
80 let mut stderr = child
81 .stderr
82 .take()
83 .ok_or_else(|| anyhow!("stderr was missing"))?;
84
85 let stdout_handle = self.handle.clone();
86 let stdout_task = tokio::task::spawn(async move {
87 let mut stdout_buffer = vec![0u8; 8 * 1024];
88 loop {
89 let Ok(size) = stdout.read(&mut stdout_buffer).await else {
90 break;
91 };
92 if size > 0 {
93 let response = Response {
94 response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate {
95 exited: false,
96 exit_code: 0,
97 error: String::new(),
98 stdout: stdout_buffer[0..size].to_vec(),
99 stderr: vec![],
100 })),
101 };
102 let _ = stdout_handle.respond(response).await;
103 } else {
104 break;
105 }
106 }
107 });
108
109 let stderr_handle = self.handle.clone();
110 let stderr_task = tokio::task::spawn(async move {
111 let mut stderr_buffer = vec![0u8; 8 * 1024];
112 loop {
113 let Ok(size) = stderr.read(&mut stderr_buffer).await else {
114 break;
115 };
116 if size > 0 {
117 let response = Response {
118 response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate {
119 exited: false,
120 exit_code: 0,
121 error: String::new(),
122 stdout: vec![],
123 stderr: stderr_buffer[0..size].to_vec(),
124 })),
125 };
126 let _ = stderr_handle.respond(response).await;
127 } else {
128 break;
129 }
130 }
131 });
132
133 let stdin_task = tokio::task::spawn(async move {
134 loop {
135 let Some(request) = receiver.recv().await else {
136 break;
137 };
138
139 let Some(RequestType::ExecStream(update)) = request.request else {
140 continue;
141 };
142
143 let Some(Update::Stdin(update)) = update.update else {
144 continue;
145 };
146
147 if stdin.write_all(&update.data).await.is_err() {
148 break;
149 }
150 }
151 });
152
153 let exit = child.wait().await?;
154 let code = exit.code().unwrap_or(-1);
155
156 let _ = join!(stdout_task, stderr_task);
157 stdin_task.abort();
158
159 let response = Response {
160 response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate {
161 exited: true,
162 exit_code: code,
163 error: String::new(),
164 stdout: vec![],
165 stderr: vec![],
166 })),
167 };
168 self.handle.respond(response).await?;
169
170 Ok(())
171 }
172}