use tracing::{debug, instrument};
use crate::config::language::DEFAULT_SANDBOX_PATH;
use crate::config::{Config, Language};
use crate::isolate::{IsolateAction, IsolateBox, IsolateCommand, resolve_command, run_with_output};
use crate::runner::CompileError;
use crate::types::{ExecutionResult, ResourceLimits};
#[derive(Debug, Clone)]
pub struct CompileResult {
pub success: bool,
pub execution: ExecutionResult,
pub output: String,
}
impl CompileResult {
pub fn is_success(&self) -> bool {
self.success && self.execution.exit_code == Some(0)
}
}
fn default_compile_limits() -> ResourceLimits {
ResourceLimits {
time_limit: Some(30.0), wall_time_limit: Some(60.0), memory_limit: Some(524288), max_processes: Some(10), max_output: Some(65536), ..Default::default()
}
}
#[instrument(skip(sandbox, config, source))]
pub async fn compile(
sandbox: &IsolateBox,
config: &Config,
language: &Language,
source: &[u8],
limits: Option<&ResourceLimits>,
) -> Result<CompileResult, CompileError> {
let compile_config = language
.compile
.as_ref()
.ok_or_else(|| CompileError::NotCompiled(language.name.clone()))?;
let source_name = &compile_config.source_name;
sandbox
.write_file(source_name, source)
.await
.map_err(CompileError::Isolate)?;
debug!(source_name, "wrote source file");
let base_limits = default_compile_limits();
let lang_limits = compile_config.limits.as_ref();
let effective_limits = match (limits, lang_limits) {
(Some(user), Some(lang)) => base_limits.with_overrides(lang).with_overrides(user),
(Some(user), None) => base_limits.with_overrides(user),
(None, Some(lang)) => base_limits.with_overrides(lang),
(None, None) => base_limits,
};
let mut expanded_cmd = Language::expand_command(
&compile_config.command,
source_name,
&compile_config.output_name,
);
resolve_command(&mut expanded_cmd).map_err(CompileError::Isolate)?;
let mut command = IsolateCommand::new(config.isolate_binary(), sandbox.id())
.action(IsolateAction::Run)
.cgroup(config.cgroup)
.limits(effective_limits)
.working_dir("/box")
.env("PATH", DEFAULT_SANDBOX_PATH)
.mounts(config.sandbox_mounts.iter().cloned())
.command(expanded_cmd);
for (key, value) in &compile_config.env {
command = command.env(key, value);
}
let (result, mut output) = run_with_output(sandbox, command)
.await
.map_err(CompileError::Isolate)?;
let success = result.exit_code == Some(0);
debug!(
success,
exit_code = ?result.exit_code,
status = ?result.status,
message = ?result.message,
"compilation complete"
);
if output.is_empty()
&& let Some(ref msg) = result.message
{
output = msg.clone();
}
Ok(CompileResult {
success,
execution: result,
output,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_compile_limits() {
let limits = default_compile_limits();
assert_eq!(limits.time_limit, Some(30.0));
assert_eq!(limits.memory_limit, Some(524288));
}
}