1#![cfg_attr(target_os = "linux", expect(unsafe_code))]
8
9use futures::executor::block_on;
10use futures::io::AllowStdIo;
11#[cfg(target_os = "linux")]
12use std::os::unix::process::CommandExt;
13use std::process::Stdio;
14
15pub fn handle_execute(
16 mut request: pipette_protocol::ExecuteRequest,
17) -> anyhow::Result<pipette_protocol::ExecuteResponse> {
18 tracing::debug!(?request, "execute request");
19
20 let mut command = std::process::Command::new(&request.program);
21 command.args(&request.args);
22 if let Some(dir) = &request.current_dir {
23 command.current_dir(dir);
24 }
25
26 if let Some(ref root) = request.chroot {
28 #[cfg(target_os = "linux")]
29 {
30 let root = std::ffi::CString::new(root.as_str())?;
31 unsafe {
34 command.pre_exec(move || {
35 if libc::chroot(root.as_ptr()) != 0 {
36 return Err(std::io::Error::last_os_error());
37 }
38 if libc::chdir(c"/".as_ptr()) != 0 {
39 return Err(std::io::Error::last_os_error());
40 }
41 Ok(())
42 });
43 }
44 }
45 #[cfg(not(target_os = "linux"))]
46 {
47 let _ = root;
48 anyhow::bail!("chroot is only supported on Linux");
49 }
50 }
51
52 if request.clear_env {
53 command.env_clear();
54 }
55 for pipette_protocol::EnvPair { name, value } in request.env {
56 if let Some(value) = value {
57 command.env(name, value);
58 } else {
59 command.env_remove(name);
60 }
61 }
62 if request.stdin.is_some() {
63 command.stdin(Stdio::piped());
64 } else {
65 command.stdin(Stdio::null());
66 }
67 if request.stdout.is_some() {
68 command.stdout(Stdio::piped());
69 } else {
70 command.stdout(Stdio::null());
71 }
72 if request.stderr.is_some() {
73 command.stderr(Stdio::piped());
74 } else {
75 command.stderr(Stdio::null());
76 }
77 let mut child = command.spawn()?;
78 let pid = child.id();
79 let (send, recv) = mesh::oneshot();
80
81 if let (Some(stdin_write), Some(stdin_read)) = (child.stdin.take(), request.stdin.take()) {
82 std::thread::spawn(move || {
83 let _ = block_on(futures::io::copy(
84 stdin_read,
85 &mut AllowStdIo::new(stdin_write),
86 ));
87 });
88 }
89 if let (Some(stdout_read), Some(mut stdout_write)) =
90 (child.stdout.take(), request.stdout.take())
91 {
92 std::thread::spawn(move || {
93 let _ = block_on(futures::io::copy(
94 AllowStdIo::new(stdout_read),
95 &mut stdout_write,
96 ));
97 });
98 }
99 if let (Some(stderr_read), Some(mut stderr_write)) =
100 (child.stderr.take(), request.stderr.take())
101 {
102 std::thread::spawn(move || {
103 let _ = block_on(futures::io::copy(
104 AllowStdIo::new(stderr_read),
105 &mut stderr_write,
106 ));
107 });
108 }
109
110 std::thread::spawn(move || {
111 let exit_status = child.wait().unwrap();
112 let status = convert_exit_status(exit_status);
113 tracing::debug!(pid, ?status, "process exited");
114 send.send(status);
115 });
116 Ok(pipette_protocol::ExecuteResponse { pid, result: recv })
117}
118
119fn convert_exit_status(exit_status: std::process::ExitStatus) -> pipette_protocol::ExitStatus {
120 if let Some(code) = exit_status.code() {
121 return pipette_protocol::ExitStatus::Normal(code);
122 }
123
124 #[cfg(unix)]
125 if let Some(signal) = std::os::unix::process::ExitStatusExt::signal(&exit_status) {
126 return pipette_protocol::ExitStatus::Signal(signal);
127 }
128
129 pipette_protocol::ExitStatus::Unknown
130}