hakoniwa_code_runner/
executor.rs1use anyhow::Result;
2use chrono::prelude::*;
3use serde_variant::to_variant_name;
4use std::{fs, path::PathBuf, time::Duration};
5
6use crate::contrib;
7use hakoniwa::Sandbox;
8
9pub struct ExecutorFile {
10 name: String,
11 content: String,
12}
13
14impl ExecutorFile {
15 pub fn new(name: &str, content: &str) -> Self {
16 Self {
17 name: name.to_string(),
18 content: content.to_string(),
19 }
20 }
21}
22
23#[derive(Default, Debug)]
24pub struct ExecutorResult {
25 pub status: String,
26 pub reason: String,
27 pub exit_code: Option<i32>,
28 pub start_time: Option<DateTime<Utc>>,
29 pub real_time: Option<Duration>,
30 pub system_time: Option<Duration>,
31 pub user_time: Option<Duration>,
32 pub max_rss: Option<i64>,
33 pub stdout: String,
34 pub stderr: String,
35}
36
37impl ExecutorResult {
38 const MAX_CHARS_COUNT: usize = 4096;
39
40 fn complie_error(r: hakoniwa::ExecutorResult) -> Self {
41 let mut r = Self::from(r);
42 r.status = String::from("CE");
43 r
44 }
45
46 fn sandbox_setup_error(reason: &str) -> Self {
47 Self {
48 status: String::from("SE"),
49 reason: reason.to_string(),
50 ..Default::default()
51 }
52 }
53}
54
55impl From<hakoniwa::ExecutorResult> for ExecutorResult {
56 fn from(r: hakoniwa::ExecutorResult) -> Self {
57 Self {
58 status: to_variant_name(&r.status).unwrap().to_string(),
59 reason: r.reason,
60 exit_code: r.exit_code,
61 start_time: r.start_time,
62 real_time: r.real_time,
63 system_time: r.system_time,
64 user_time: r.user_time,
65 max_rss: r.max_rss,
66 stdout: contrib::str::truncate(&r.stdout, Self::MAX_CHARS_COUNT),
67 stderr: contrib::str::truncate(&r.stderr, Self::MAX_CHARS_COUNT),
68 }
69 }
70}
71
72#[derive(Default)]
73pub struct Executor {
74 pub(crate) id: String,
75 pub(crate) name: String,
76 work_dir: PathBuf,
77 compile: Vec<String>,
78 compilebox: Sandbox,
79 execute: Vec<String>,
80 executebox: Sandbox,
81}
82
83impl Executor {
84 const CONTAINER_WORK_DIR: &'static str = "/hako";
85
86 pub(crate) fn new(id: &str, name: &str) -> Self {
87 Self {
88 id: id.to_string(),
89 name: name.to_string(),
90 work_dir: contrib::tmpdir::random_name("hakoniwa-code-runner"),
91 ..Default::default()
92 }
93 }
94
95 pub(crate) fn with_compile_command<SC: AsRef<str>>(
96 &mut self,
97 command: &[SC],
98 sandbox: Sandbox,
99 ) {
100 self.compile = command.iter().map(|c| String::from(c.as_ref())).collect();
101 self.compilebox = sandbox;
102 }
103
104 pub(crate) fn with_execute_command<SC: AsRef<str>>(
105 &mut self,
106 command: &[SC],
107 sandbox: Sandbox,
108 ) {
109 self.execute = command.iter().map(|c| String::from(c.as_ref())).collect();
110 self.executebox = sandbox;
111 }
112
113 pub fn run(&self, files: &[ExecutorFile]) -> ExecutorResult {
114 match self._run(files) {
115 Ok(val) => val,
116 Err(err) => {
117 let err = err.to_string();
118 ExecutorResult::sandbox_setup_error(&err)
119 }
120 }
121 }
122
123 fn _run(&self, files: &[ExecutorFile]) -> Result<ExecutorResult> {
124 let _work_dir = contrib::tmpdir::new(&self.work_dir)?;
125 for f in files {
126 fs::write(self.work_dir.join(&f.name), &f.content)?;
127 }
128
129 if !self.compile.is_empty() {
130 let mut command = self.compilebox.command(&self.compile[0], &self.compile);
131 let result = command
132 .rw_bind(&self.work_dir, Self::CONTAINER_WORK_DIR)?
133 .current_dir(Self::CONTAINER_WORK_DIR)?
134 .run();
135 match result.exit_code {
136 Some(0) => {}
137 _ => return Ok(ExecutorResult::complie_error(result)),
138 }
139 }
140
141 let mut command = self.executebox.command(&self.execute[0], &self.execute);
142 let result = command
143 .rw_bind(&self.work_dir, Self::CONTAINER_WORK_DIR)?
144 .current_dir(Self::CONTAINER_WORK_DIR)?
145 .run();
146 Ok(ExecutorResult::from(result))
147 }
148}