From afe76a4edecd54ba03bd1aa0d91f49cd5aa212f9 Mon Sep 17 00:00:00 2001 From: Nathan Wang Date: Tue, 16 Jul 2024 22:18:48 -0400 Subject: [PATCH] Return verdict; add support for file io --- src/compile_and_execute.rs | 8 ++-- src/execute.rs | 84 +++++++++++++++++++++++++++++++++----- src/run_command.rs | 7 ---- 3 files changed, 78 insertions(+), 21 deletions(-) diff --git a/src/compile_and_execute.rs b/src/compile_and_execute.rs index 17d48ba..d695a57 100644 --- a/src/compile_and_execute.rs +++ b/src/compile_and_execute.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{ compile::{compile, CompileRequest}, error::AppError, - execute::{execute, ExecuteRequest}, - run_command::{CommandOptions, CommandOutput}, + execute::{execute, ExecuteOptions, ExecuteRequest, ExecuteResponse}, + run_command::CommandOutput, }; /// Payload for POST /compile-and-execute @@ -15,7 +15,7 @@ use crate::{ #[derive(Deserialize)] pub struct CompileAndExecuteRequest { pub compile: CompileRequest, - pub execute: CommandOptions, + pub execute: ExecuteOptions, } /// Response for POST /compile-and-execute @@ -23,7 +23,7 @@ pub struct CompileAndExecuteRequest { pub struct CompileAndExecuteResponse { pub compile: CommandOutput, /// None if the program failed to compile. - pub execute: Option, + pub execute: Option, } pub async fn compile_and_execute_handler( diff --git a/src/execute.rs b/src/execute.rs index aa6f45e..af5f859 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -6,12 +6,12 @@ use std::{ use anyhow::{Result, anyhow}; use axum::Json; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tempdir::TempDir; use crate::{ error::AppError, - run_command::{run_command, CommandOptions, CommandOutput}, + run_command::{run_command, CommandOptions}, types::{Executable, Language}, }; use base64::{prelude::BASE64_STANDARD, Engine}; @@ -19,20 +19,68 @@ use base64::{prelude::BASE64_STANDARD, Engine}; #[derive(Deserialize)] pub struct ExecuteRequest { pub executable: Executable, - pub options: CommandOptions, + pub options: ExecuteOptions, } -pub fn execute(payload: ExecuteRequest) -> Result { +#[derive(Deserialize)] +pub struct ExecuteOptions { + pub stdin: String, + pub timeout_ms: u32, + + /// Alphanumeric string if you want file I/O to be supported, such as "cowdating". + /// + /// Will create the files `file_io_name`.in and read `file_io_name`.out. + pub file_io_name: Option, +} + +#[derive(Serialize)] +pub enum Verdict { + #[serde(rename = "accepted")] + Accepted, + #[serde(rename = "wrong_answer")] + #[allow(dead_code)] + WrongAnswer, + #[serde(rename = "time_limit_exceeded")] + TimeLimitExceeded, + #[serde(rename = "runtime_error")] + RuntimeError, +} + +#[derive(Serialize)] +pub struct ExecuteResponse { + pub stdout: String, + pub stderr: String, + pub wall_time: String, // time format is 0:00.00 + pub memory_usage: String, + + /// The underlying raw wait status. Note that this is different from an exit status. + pub exit_code: i32, + pub exit_signal: Option, + + pub verdict: Verdict, +} + +pub fn execute(payload: ExecuteRequest) -> Result { let tmp_dir = TempDir::new("execute")?; - match payload.executable { + if let Some(name) = payload.options.file_io_name { + if !name.chars().all(|c| c.is_ascii_alphanumeric()) { + return Err(anyhow!("Invalid file I/O name. It must be alphanumeric, like \"cowdating\".")) + } + let mut stdin_file = File::create(tmp_dir.path().join(name).with_extension("in"))?; + stdin_file.write_all(payload.options.stdin.as_ref())?; + } + + let command_options = CommandOptions { stdin: payload.options.stdin, timeout_ms: payload.options.timeout_ms }; + + let command_output = match payload.executable { Executable::Binary { value } => { let mut executable_file = File::create(tmp_dir.path().join("program"))?; executable_file.write_all(BASE64_STANDARD.decode(value)?.as_ref())?; executable_file.set_permissions(Permissions::from_mode(0o755))?; drop(executable_file); - run_command("./program", tmp_dir.path(), payload.options) + run_command("./program", tmp_dir.path(), command_options) } Executable::JavaClass { class_name, value } => { let mut class_file = File::create( @@ -47,7 +95,7 @@ pub fn execute(payload: ExecuteRequest) -> Result { run_command( format!("java {}", class_name).as_ref(), tmp_dir.path(), - payload.options, + command_options, ) } Executable::Script { @@ -63,13 +111,29 @@ pub fn execute(payload: ExecuteRequest) -> Result { executable_file.set_permissions(Permissions::from_mode(0o755))?; drop(executable_file); - run_command("python3.12 program.py", tmp_dir.path(), payload.options) + run_command("python3.12 program.py", tmp_dir.path(), command_options) } - } + }?; + + let verdict = match command_output.exit_code { + 124 => Verdict::TimeLimitExceeded, + 0 => Verdict::Accepted, + _ => Verdict::RuntimeError, + }; + + Ok(ExecuteResponse { + stdout: command_output.stdout, + stderr: command_output.stderr, + wall_time: command_output.wall_time, + memory_usage: command_output.memory_usage, + exit_code: command_output.exit_code, + exit_signal: command_output.exit_signal, + verdict, + }) } pub async fn execute_handler( Json(payload): Json, -) -> Result, AppError> { +) -> Result, AppError> { Ok(Json(execute(payload)?)) } diff --git a/src/run_command.rs b/src/run_command.rs index 2ea068d..90b979a 100644 --- a/src/run_command.rs +++ b/src/run_command.rs @@ -26,14 +26,7 @@ pub struct CommandOutput { /// The underlying raw wait status. Note that this is different from an exit status. pub exit_code: i32, - pub exit_signal: Option, - // /** - // * When executing, if `fileIOName` is given, this is - // * set to whatever is written in `[fileIOName].out` - // * or null if there's no such file. - // */ - // pub file_output: Option, } struct TimingOutput {