diff --git a/internal/e/err.go b/internal/e/err.go index 44204e0..9129d57 100644 --- a/internal/e/err.go +++ b/internal/e/err.go @@ -1,5 +1,7 @@ package e +import "errors" + type Status int func (code Status) String() string { @@ -9,3 +11,7 @@ func (code Status) String() string { } return msgText[InternalError] } + +func (code Status) AsError() error { + return errors.New(code.String()) +} diff --git a/internal/service/runner/common.go b/internal/service/runner/common.go index 378186f..942332b 100644 --- a/internal/service/runner/common.go +++ b/internal/service/runner/common.go @@ -55,6 +55,20 @@ func (s *service) checkAndExecute(version uint, user string, lang string, script return e.Success } +func (s *service) check(version uint, user string, lang string) e.Status { + if !s.ProblemExists(version) { + s.log.Info("problem not exists", zap.Uint("version", version)) + return e.RunnerProblemNotExist + } + + if !s.userExists(user, fmt.Sprintf("%s.%s", user, lang)) { + s.log.Info("user program not exists", zap.String("user", user), zap.String("lang", lang)) + return e.RunnerUserNotExist + } + + return e.Success +} + func (s *service) ProblemExists(version uint) bool { problemPath := filepath.Join(ProblemDir, fmt.Sprintf("%d", version)) return file.Exist(problemPath) diff --git a/internal/service/runner/compile.go b/internal/service/runner/compile.go index 6b794d1..e787283 100644 --- a/internal/service/runner/compile.go +++ b/internal/service/runner/compile.go @@ -5,18 +5,112 @@ import ( "git.0x7f.app/WOJ/woj-server/internal/e" "git.0x7f.app/WOJ/woj-server/pkg/file" "git.0x7f.app/WOJ/woj-server/pkg/utils" + "go.uber.org/zap" + "io" "os" "path/filepath" + "time" ) +func (s *service) getProblemScript(version uint, lang string) (string, e.Status) { + config, err := s.ParseConfig(version, true) + if err != nil { + return "", e.RunnerProblemParseFailed + } + + var script string + for _, l := range config.Languages { + if l.Lang == lang { + if l.Type == "default" { + script = "/woj/framework/template/default/" + lang + ".Makefile" + } else { + script = "/woj/problem/judge/" + l.Script + } + break + } + } + + if script == "" { + return "", e.RunnerProblemParseFailed + } + return script, e.Success +} + func (s *service) Compile(version uint, user string, lang string) (JudgeStatus, e.Status) { - target := filepath.Join(UserDir, user, fmt.Sprintf("%s.out", user)) + // 1. ensure problem/user exists + status := s.check(version, user, lang) + if status != e.Success { + return JudgeStatus{Message: "check failed"}, status + } - _ = os.Remove(target) - status := s.checkAndExecute(version, user, lang, "problem_compile.sh", e.RunnerUserCompileFailed) + // 2. prepare judge environment + workDir := filepath.Join(UserDir, user) + judgeDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", version), "judge") - log := filepath.Join(UserDir, user, fmt.Sprintf("%s.compile.log", user)) - msg, err := file.FileRead(log) + sourceFile := filepath.Join(workDir, fmt.Sprintf("%s.%s", user, lang)) + boxSourceFile := filepath.Join("/woj/problem/user", fmt.Sprintf("%s.%s", user, lang)) + + targetFile := filepath.Join(workDir, fmt.Sprintf("%s.out", user)) + boxTargetFile := filepath.Join("/woj/problem/user", fmt.Sprintf("%s.out", user)) + + logFile := filepath.Join(workDir, fmt.Sprintf("%s.compile.log", user)) + log, err := os.Create(logFile) + if err != nil { + return JudgeStatus{Message: "create log file failed"}, e.RunnerUserCompileFailed + } + defer func(log *os.File) { + _ = log.Close() + }(log) + + // 3. compile + err = utils.NewMust(). + Do(func() error { + _ = os.Remove(targetFile) + return nil + }). + Do(func() error { return file.TouchErr(targetFile) }). + Do(func() error { + script, err := s.getProblemScript(version, lang) + if err != e.Success { + return err.AsError() + } + + args := []string{ + "-v", judgeDir + ":/woj/problem/judge:ro", + "-v", sourceFile + ":" + boxSourceFile + ":ro", + "-v", targetFile + ":" + boxTargetFile, + "-e", fmt.Sprintf("USER_PROG=%s", user), + "-e", fmt.Sprintf("LANG=%s", lang), + "git.0x7f.app/woj/ubuntu-full:latest", + "sh", "-c", fmt.Sprintf("cd /woj/problem/user && make -f %s compile", script), + } + + runArgs := &podmanArgs{ + executeArgs: executeArgs{ + args: args, + timeout: 60 * time.Second, + output: log, + }, + memory: "256m", + } + + return s.podmanRun(runArgs) + }). + Done() + + if err != nil { + s.log.Info("compile failed", + zap.Error(err), + zap.Uint("version", version), + zap.String("user", user), + zap.String("lang", lang), + ) + status = e.RunnerUserCompileFailed + } + + // 4. read log + _, _ = log.Seek(0, io.SeekStart) + msg, err := io.ReadAll(log) msg = utils.If(err == nil, msg, nil) msgText := string(msg) diff --git a/resource/runner/scripts/problem_compile.sh b/resource/runner/scripts/problem_compile.sh deleted file mode 100755 index ab06937..0000000 --- a/resource/runner/scripts/problem_compile.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd) -. "$WORKSPACE"/scripts/common.sh -. "$WORKSPACE"/scripts/docker_run.sh -. "$WORKSPACE"/scripts/problem.sh - -if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then - log_warn "Usage: $0 " - exit 1 -fi - -get_problem_info "$WORKSPACE" "$1" "$3" - -SRC_FILE="$WORKSPACE"/user/"$2"/"$2"."$3" -EXE_FILE="$WORKSPACE"/user/"$2"/"$2".out -export LOG_FILE="$WORKSPACE"/user/"$2"/"$2".compile.log - -rm -f "$EXE_FILE" && touch "$EXE_FILE" - -export TIMEOUT=${4:-60} -docker_run \ - -v "$WORKSPACE"/problem/"$1"/judge:/woj/problem/judge:ro \ - -v "$SRC_FILE":/woj/problem/user/"$2"."$3":ro \ - -v "$EXE_FILE":/woj/problem/user/"$2".out \ - -e USER_PROG="$2" \ - -e LANG="$3" \ - git.0x7f.app/woj/ubuntu-full \ - sh -c \ - "cd /woj/problem/user && make -f $Info_Script compile"