hakoniwa_code_runner/
executor.rs

1use 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}