1
1
use std:: {
2
- fs:: { File , Permissions } ,
2
+ fs:: { self , File , Permissions } ,
3
3
io:: Write ,
4
- os:: unix:: fs:: PermissionsExt ,
4
+ os:: unix:: fs:: PermissionsExt , path :: Path ,
5
5
} ;
6
6
7
7
use anyhow:: { Result , anyhow} ;
8
8
use axum:: Json ;
9
- use serde:: Deserialize ;
9
+ use serde:: { Deserialize , Serialize } ;
10
10
use tempdir:: TempDir ;
11
11
12
12
use crate :: {
13
13
error:: AppError ,
14
- run_command:: { run_command, CommandOptions , CommandOutput } ,
14
+ run_command:: { run_command, CommandOptions } ,
15
15
types:: { Executable , Language } ,
16
16
} ;
17
17
use base64:: { prelude:: BASE64_STANDARD , Engine } ;
18
18
19
19
#[ derive( Deserialize ) ]
20
20
pub struct ExecuteRequest {
21
21
pub executable : Executable ,
22
- pub options : CommandOptions ,
22
+ pub options : ExecuteOptions ,
23
23
}
24
24
25
- pub fn execute ( payload : ExecuteRequest ) -> Result < CommandOutput > {
25
+ #[ derive( Deserialize ) ]
26
+ pub struct ExecuteOptions {
27
+ pub stdin : String ,
28
+ pub timeout_ms : u32 ,
29
+
30
+ /// Alphanumeric string if you want file I/O to be supported, such as "cowdating".
31
+ ///
32
+ /// Will create the files `file_io_name`.in and read `file_io_name`.out.
33
+ pub file_io_name : Option < String > ,
34
+ }
35
+
36
+ #[ derive( Serialize ) ]
37
+ pub enum Verdict {
38
+ #[ serde( rename = "accepted" ) ]
39
+ Accepted ,
40
+ #[ serde( rename = "wrong_answer" ) ]
41
+ #[ allow( dead_code) ]
42
+ WrongAnswer ,
43
+ #[ serde( rename = "time_limit_exceeded" ) ]
44
+ TimeLimitExceeded ,
45
+ #[ serde( rename = "runtime_error" ) ]
46
+ RuntimeError ,
47
+ }
48
+
49
+ #[ derive( Serialize ) ]
50
+ pub struct ExecuteResponse {
51
+ pub stdout : String ,
52
+
53
+ /// Only if `file_io_name`.out exists.
54
+ pub file_output : Option < String > ,
55
+
56
+ pub stderr : String ,
57
+ pub wall_time : String , // time format is 0:00.00
58
+ pub memory_usage : String ,
59
+
60
+ /// The underlying raw wait status. Note that this is different from an exit status.
61
+ pub exit_code : i32 ,
62
+ pub exit_signal : Option < String > ,
63
+
64
+ pub verdict : Verdict ,
65
+ }
66
+
67
+ pub fn execute ( payload : ExecuteRequest ) -> Result < ExecuteResponse > {
26
68
let tmp_dir = TempDir :: new ( "execute" ) ?;
27
69
28
- match payload. executable {
70
+ if let Some ( ref name) = payload. options . file_io_name {
71
+ if !name. chars ( ) . all ( |c| c. is_ascii_alphanumeric ( ) ) {
72
+ return Err ( anyhow ! ( "Invalid file I/O name. It must be alphanumeric, like \" cowdating\" ." ) )
73
+ }
74
+ let mut stdin_file = File :: create ( tmp_dir. path ( ) . join ( name) . with_extension ( "in" ) ) ?;
75
+ stdin_file. write_all ( payload. options . stdin . as_ref ( ) ) ?;
76
+ }
77
+
78
+ let command_options = CommandOptions { stdin : payload. options . stdin , timeout_ms : payload. options . timeout_ms } ;
79
+
80
+ let command_output = match payload. executable {
29
81
Executable :: Binary { value } => {
30
82
let mut executable_file = File :: create ( tmp_dir. path ( ) . join ( "program" ) ) ?;
31
83
executable_file. write_all ( BASE64_STANDARD . decode ( value) ?. as_ref ( ) ) ?;
32
84
executable_file. set_permissions ( Permissions :: from_mode ( 0o755 ) ) ?;
33
85
drop ( executable_file) ;
34
86
35
- run_command ( "./program" , tmp_dir. path ( ) , payload . options )
87
+ run_command ( "./program" , tmp_dir. path ( ) , command_options )
36
88
}
37
89
Executable :: JavaClass { class_name, value } => {
38
90
let mut class_file = File :: create (
@@ -47,7 +99,7 @@ pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
47
99
run_command (
48
100
format ! ( "java {}" , class_name) . as_ref ( ) ,
49
101
tmp_dir. path ( ) ,
50
- payload . options ,
102
+ command_options ,
51
103
)
52
104
}
53
105
Executable :: Script {
@@ -63,13 +115,41 @@ pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
63
115
executable_file. set_permissions ( Permissions :: from_mode ( 0o755 ) ) ?;
64
116
drop ( executable_file) ;
65
117
66
- run_command ( "python3.12 program.py" , tmp_dir. path ( ) , payload . options )
118
+ run_command ( "python3.12 program.py" , tmp_dir. path ( ) , command_options )
67
119
}
68
- }
120
+ } ?;
121
+
122
+ let verdict = match command_output. exit_code {
123
+ 124 => Verdict :: TimeLimitExceeded ,
124
+ 0 => Verdict :: Accepted ,
125
+ _ => Verdict :: RuntimeError ,
126
+ } ;
127
+
128
+ let file_output = if let Some ( name) = payload. options . file_io_name {
129
+ let output_file_path = tmp_dir. path ( ) . join ( name) . with_extension ( "out" ) ;
130
+ if Path :: exists ( & output_file_path) {
131
+ Some ( String :: from_utf8_lossy ( & fs:: read ( output_file_path) ?) . into_owned ( ) )
132
+ } else {
133
+ None
134
+ }
135
+ } else {
136
+ None
137
+ } ;
138
+
139
+ Ok ( ExecuteResponse {
140
+ stdout : command_output. stdout ,
141
+ file_output,
142
+ stderr : command_output. stderr ,
143
+ wall_time : command_output. wall_time ,
144
+ memory_usage : command_output. memory_usage ,
145
+ exit_code : command_output. exit_code ,
146
+ exit_signal : command_output. exit_signal ,
147
+ verdict,
148
+ } )
69
149
}
70
150
71
151
pub async fn execute_handler (
72
152
Json ( payload) : Json < ExecuteRequest > ,
73
- ) -> Result < Json < CommandOutput > , AppError > {
153
+ ) -> Result < Json < ExecuteResponse > , AppError > {
74
154
Ok ( Json ( execute ( payload) ?) )
75
155
}
0 commit comments