init
Signed-off-by: Joseph Umana <joseph@josephumana.com>
This commit is contained in:
commit
da34a7b62e
9 changed files with 378 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.out
|
||||||
|
gojrt
|
||||||
|
.cache/
|
||||||
|
compile_commands.json
|
2
Makefile
Normal file
2
Makefile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
all:
|
||||||
|
gcc main.c structs.h serialization.c serialization.h -o gojrt -Wall -std=c23 -g
|
12
README.md
Normal file
12
README.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# GOJ-Runtime
|
||||||
|
A lightweight, simple competitive programmer runtime and grader.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
[x] Time limits
|
||||||
|
[x] Memory limits
|
||||||
|
[x] Result Serialization (results)
|
||||||
|
[] Grading
|
||||||
|
[] Multiple Test Cases
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
`# gojrt <compile_command> <run_command> <runtime_seconds> <memory_limit>`
|
180
main.c
Normal file
180
main.c
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
#include "structs.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define COMPILE_TIMEOUT 120
|
||||||
|
#define COMPILE_MEMORYLIMIT 1000000 // 1GB
|
||||||
|
|
||||||
|
#define RUNNER_UID 1000
|
||||||
|
#define RUNNER_GID 1000
|
||||||
|
|
||||||
|
#ifndef RUNNER_UID
|
||||||
|
#error "RUNNER_UID must be set"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef RUNNER_GID
|
||||||
|
#error "RUNNER_GID must be set"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TODO: Better Panics
|
||||||
|
// Give an OE
|
||||||
|
_Noreturn void panic(const char* msg, ...) {
|
||||||
|
va_list vargs;
|
||||||
|
va_start(vargs, msg);
|
||||||
|
|
||||||
|
fprintf(stderr, "panic: ");
|
||||||
|
vfprintf(stderr, msg, vargs);
|
||||||
|
|
||||||
|
va_end(vargs);
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void execCommand(char* command) {
|
||||||
|
setgid(RUNNER_GID);
|
||||||
|
setuid(RUNNER_UID);
|
||||||
|
char* args[] = {"sh", "-c", command, NULL};
|
||||||
|
execvp("sh", args);
|
||||||
|
|
||||||
|
// If exec returns, we're beyond cooked so panic
|
||||||
|
panic("Failed to exec in process! Errno: %d", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getExecStats(pid_t pid, RunResult* result) {
|
||||||
|
puts("getExecStats");
|
||||||
|
struct rusage usage;
|
||||||
|
time_t start = time(NULL);
|
||||||
|
|
||||||
|
if (wait4(pid, &result->exitCode, 0, &usage) <= 0) {
|
||||||
|
panic("wait4 non-zero! Errno: %d", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
result->exitCode = WEXITSTATUS(result->exitCode);
|
||||||
|
|
||||||
|
result->maxRss = usage.ru_maxrss;
|
||||||
|
|
||||||
|
time_t end = time(NULL);
|
||||||
|
|
||||||
|
time_t executionTime = end - start;
|
||||||
|
|
||||||
|
result->runtime = executionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setExecFlags(RunResult* result, time_t runtimeSeconds, size_t memoryLimit) {
|
||||||
|
printf("%d\n", result->exitCode);
|
||||||
|
if (result->exitCode == 69) {
|
||||||
|
result->flags |= Nice;
|
||||||
|
strcpy(result->feedback, "Nice!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool errorFlag = false;
|
||||||
|
|
||||||
|
if (result->exitCode != 0) {
|
||||||
|
result->flags |= ProgramError;
|
||||||
|
errorFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result->runtime > runtimeSeconds) {
|
||||||
|
result->flags |= TimeLimitExceeded;
|
||||||
|
errorFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result->maxRss > memoryLimit) {
|
||||||
|
result->flags |= MemoryLimitExceeded;
|
||||||
|
errorFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorFlag) {
|
||||||
|
result->flags |= Err;
|
||||||
|
} else {
|
||||||
|
result->flags |= Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getRunSummary() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runs commands with given constraints
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* executeCommand will run the command string with <em>runtimeSeconds</em>
|
||||||
|
* as a time limit and <em>memoryLimit
|
||||||
|
* </em> as max RSS in KB. If time
|
||||||
|
* is exceeded, the function will immediately return error. However,
|
||||||
|
* a program that exceeds the memory limit will continue to run but, it's max
|
||||||
|
* RSS usage will be checked and will fail if it exceeds the memory limit
|
||||||
|
*
|
||||||
|
* Command strings are also passed in as an argument to "shell -c" allowing
|
||||||
|
* shell syntax to be used. This can be particularly useful for multi-step
|
||||||
|
* compilation.
|
||||||
|
*
|
||||||
|
* @param[in] command The shell commmand/syntax to run
|
||||||
|
* @param[in] runtimeSeconds Timeout in seconds
|
||||||
|
* @param[in] memoryLimit Max RSS limit in KB
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
RunResult executeCommand(char* command, time_t runtimeSeconds, size_t memoryLimit) {
|
||||||
|
int process = fork();
|
||||||
|
|
||||||
|
RunResult result;
|
||||||
|
|
||||||
|
// stop undefined behavior
|
||||||
|
// from thinking a string is present
|
||||||
|
result.feedback[0] = '\0';
|
||||||
|
|
||||||
|
if (process > 0) {
|
||||||
|
getExecStats(process, &result);
|
||||||
|
} else if (process == 0) {
|
||||||
|
execCommand(command);
|
||||||
|
} else {
|
||||||
|
panic("Failed to spawn process");
|
||||||
|
}
|
||||||
|
|
||||||
|
setExecFlags(&result, runtimeSeconds, memoryLimit);
|
||||||
|
|
||||||
|
result.flags |= ExecutionFinal;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (getuid() != 0) {
|
||||||
|
panic("Must run as root");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc < 5) {
|
||||||
|
panic("Not enough args");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t runMemoryLimit = atol(argv[3]);
|
||||||
|
time_t runRuntimeSeconds = atol(argv[4]);
|
||||||
|
|
||||||
|
if (runMemoryLimit <= 0 || runRuntimeSeconds <= 0) {
|
||||||
|
panic("invalid args");
|
||||||
|
}
|
||||||
|
|
||||||
|
RunResult compileResult = executeCommand(argv[1], COMPILE_TIMEOUT, COMPILE_MEMORYLIMIT);
|
||||||
|
RunResult runResult = executeCommand(argv[2], runRuntimeSeconds, runMemoryLimit);
|
||||||
|
|
||||||
|
SerializationResult serialResult = serializeResult(&runResult);
|
||||||
|
|
||||||
|
write(STDOUT_FILENO, serialResult.data, serialResult.size);
|
||||||
|
|
||||||
|
free(serialResult.data);
|
||||||
|
}
|
60
serialization.c
Normal file
60
serialization.c
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "structs.h"
|
||||||
|
|
||||||
|
#define WATERMARK {0x69, 0x6E, 0x79, 0x72, 0x74, 0x62, 0x75}
|
||||||
|
#define VERSION 0x00
|
||||||
|
|
||||||
|
// Number of chars required to represent any 64 bit int/uint + NUL
|
||||||
|
#define INT_BUFFER_SIZE 21
|
||||||
|
|
||||||
|
#define SUM_BYTES(x) totalBytes += x
|
||||||
|
|
||||||
|
#define CONCAT_BYTES(dst, src, size) memcpy(dst, src, size); \
|
||||||
|
dst += size;
|
||||||
|
|
||||||
|
// len_fb | fb | runtime
|
||||||
|
|
||||||
|
// WARNING: This approach assumes uniform endiness and architechture
|
||||||
|
// TODO: Make it more portable?
|
||||||
|
// TODO: this is horrible. i need a better way. maybe a serialization lib or c++
|
||||||
|
// rewrite?
|
||||||
|
|
||||||
|
uint64_t calcSize(RunResult* result) {
|
||||||
|
uint64_t totalBytes = 0;
|
||||||
|
|
||||||
|
size_t feedbackLen = strlen(result->feedback);
|
||||||
|
totalBytes += sizeof(feedbackLen);
|
||||||
|
totalBytes += feedbackLen;
|
||||||
|
|
||||||
|
totalBytes += sizeof(result->runtime);
|
||||||
|
totalBytes += sizeof(result->maxRss);
|
||||||
|
totalBytes += sizeof(result->flags);
|
||||||
|
totalBytes += sizeof(result->exitCode);
|
||||||
|
return totalBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializationResult serializeResult(RunResult* result) {
|
||||||
|
uint64_t size = calcSize(result);
|
||||||
|
uint8_t* buf = malloc(size);
|
||||||
|
uint8_t* bufCursor = buf;
|
||||||
|
|
||||||
|
size_t feedbackLen = strlen(result->feedback);
|
||||||
|
CONCAT_BYTES(bufCursor, &feedbackLen, sizeof(feedbackLen));
|
||||||
|
CONCAT_BYTES(bufCursor, result->feedback, feedbackLen);
|
||||||
|
CONCAT_BYTES(bufCursor, &result->runtime, sizeof(result->runtime));
|
||||||
|
CONCAT_BYTES(bufCursor, &result->maxRss, sizeof(result->maxRss));
|
||||||
|
CONCAT_BYTES(bufCursor, &result->flags, sizeof(result->flags));
|
||||||
|
CONCAT_BYTES(bufCursor, &result->exitCode, sizeof(result->exitCode));
|
||||||
|
|
||||||
|
SerializationResult serialResult = {
|
||||||
|
.data = buf,
|
||||||
|
.size = size
|
||||||
|
};
|
||||||
|
|
||||||
|
return serialResult;
|
||||||
|
}
|
4
serialization.h
Normal file
4
serialization.h
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
#include "structs.h"
|
||||||
|
|
||||||
|
SerializationResult serializeResult(RunResult* result);
|
70
structs.h
Normal file
70
structs.h
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum ResultFlags {
|
||||||
|
/// Execution Result ///
|
||||||
|
// No other error occured, output may be graded
|
||||||
|
// OK
|
||||||
|
Ok = 1 << 0,
|
||||||
|
// At least one error occured
|
||||||
|
// See "Error" for all of them
|
||||||
|
// ERR
|
||||||
|
Err = 1 << 1,
|
||||||
|
|
||||||
|
/// Grader Result ///
|
||||||
|
// The program gave correct output with no errors
|
||||||
|
// AC
|
||||||
|
Accepted = 1 << 2,
|
||||||
|
// The program got Err OR Wrong Answer
|
||||||
|
// RJ
|
||||||
|
Rejected = 1 << 3,
|
||||||
|
|
||||||
|
/// Errors ///
|
||||||
|
// Set if memory limit is exceeded
|
||||||
|
// MLE
|
||||||
|
MemoryLimitExceeded = 1 << 4,
|
||||||
|
// Set if timelimit triggered termination
|
||||||
|
// TLE
|
||||||
|
TimeLimitExceeded = 1 << 5,
|
||||||
|
// Code gave the wrong result
|
||||||
|
// WA
|
||||||
|
WrongAnswer = 1 << 6,
|
||||||
|
// Given if program tampers with the runtime (unused)
|
||||||
|
// TD
|
||||||
|
TamperingDetected = 1 << 7,
|
||||||
|
// Set if program retuns non-zero exit code
|
||||||
|
// PE
|
||||||
|
ProgramError = 1 << 8,
|
||||||
|
// Occurs if a non-listed error occurs
|
||||||
|
// (Details given in comment)
|
||||||
|
// OE
|
||||||
|
OtherError = 1 << 9,
|
||||||
|
|
||||||
|
/// Status ///
|
||||||
|
// Set if the run result is final (all flags set)
|
||||||
|
Final = 1 << 10,
|
||||||
|
// If all Execution flags are set
|
||||||
|
ExecutionFinal = 1 << 11,
|
||||||
|
|
||||||
|
/// Misc. ///
|
||||||
|
// Nice!
|
||||||
|
// NIC
|
||||||
|
Nice = 1 << 31,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
// Specifies extra feedback
|
||||||
|
char feedback[256];
|
||||||
|
time_t runtime;
|
||||||
|
int maxRss;
|
||||||
|
int flags;
|
||||||
|
int exitCode;
|
||||||
|
} typedef RunResult;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* data;
|
||||||
|
uint64_t size;
|
||||||
|
} SerializationResult;
|
||||||
|
|
4
testprogs/nonzero.c
Normal file
4
testprogs/nonzero.c
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
int main(void) {
|
||||||
|
return 69;
|
||||||
|
}
|
||||||
|
|
42
tools/decodeFlags.py
Executable file
42
tools/decodeFlags.py
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
flag_dict = {
|
||||||
|
0: ['OK', 'Ok'],
|
||||||
|
1: ['ERR', 'Err'],
|
||||||
|
2: ['AC', 'Accepted'],
|
||||||
|
3: ['RJ', 'Rejected'],
|
||||||
|
4: ['MLE', 'MemoryLimitExceeded'],
|
||||||
|
5: ['TLE', 'TimeLimitExceeded'],
|
||||||
|
6: ['WA', 'WrongAnswer'],
|
||||||
|
7: ['TD', 'TamperingDetected'],
|
||||||
|
8: ['PE', 'ProgramError'],
|
||||||
|
9: ['OE', 'OtherError'],
|
||||||
|
10: [None, 'Final'],
|
||||||
|
11: [None, 'ExecutionFinal'],
|
||||||
|
31: ['NIC', 'Nice'],
|
||||||
|
}
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='GOJ Flag Decoder',
|
||||||
|
description='Decoder for GOJ status flags'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('flags', help='the flags you would like to decode (as a 32 bit signed int)')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
flags = int(args.flags)
|
||||||
|
|
||||||
|
for flag in range(0, 32):
|
||||||
|
if (flags & 2 ** flag) == 2 ** flag:
|
||||||
|
desc_list = flag_dict.get(flag)
|
||||||
|
|
||||||
|
if desc_list is None:
|
||||||
|
print(f'{flag}: Unknown')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if desc_list[0] is None:
|
||||||
|
print(f'{flag}: {desc_list[1]}')
|
||||||
|
else:
|
||||||
|
print(f'{flag}: {desc_list[0]} ({desc_list[1]})')
|
Loading…
Add table
Reference in a new issue