package runner import ( "fmt" "git.0x7f.app/WOJ/woj-server/internal/e" "git.0x7f.app/WOJ/woj-server/pkg/file" "git.0x7f.app/WOJ/woj-server/pkg/utils" "github.com/opencontainers/runtime-spec/specs-go" "go.uber.org/zap" "os" "path/filepath" "strings" "time" ) func (s *service) SandboxArgsBuilder(meta *JudgeMeta, id int) string { var args []string args = append(args, fmt.Sprintf("--memory_limit=%d", meta.Cfg.Lang.Runtime.Run.MemoryLimit)) args = append(args, fmt.Sprintf("--nproc_limit=%d", meta.Cfg.Lang.Runtime.Run.NProcLimit)) args = append(args, fmt.Sprintf("--time_limit=%d", meta.Cfg.Lang.Runtime.Run.TimeLimit)) args = append(args, fmt.Sprintf("--sandbox_template=%s", meta.Cfg.Lang.Lang)) args = append(args, fmt.Sprintf("--sandbox_action=%s", "ret")) args = append(args, fmt.Sprintf("--uid=%d", 1000)) args = append(args, fmt.Sprintf("--gid=%d", 1000)) args = append(args, fmt.Sprintf("--file_input=/woj/problem/data/input/%d.input", id)) args = append(args, fmt.Sprintf("--file_output=/woj/user/%d.out.usr", id)) args = append(args, fmt.Sprintf("--file_info=/woj/user/%d.info", id)) if meta.Cfg.Lang.JudgeInterpreter() != "" { sourceFile := fmt.Sprintf("%s.%s", meta.Run.User, meta.Run.Lang) args = append(args, fmt.Sprintf("--program=%s", meta.Cfg.Lang.JudgeInterpreter())) args = append(args, fmt.Sprintf("--program_arg=/woj/user/%s", sourceFile)) } else { args = append(args, fmt.Sprintf("--program=/woj/user/%s.out", meta.Run.User)) } s.log.Debug("[run] sandbox args", zap.Strings("args", args)) return strings.Join(args, " ") } func (s *service) ProblemRun(meta *JudgeMeta) { workDir := filepath.Join(UserDir, meta.Run.User) dataDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Run.Version), "data", "input") runtimeArgs := RuntimeArgs{ Image: ContainerImageRun, // sh, woj_launcher:program, woj_launcher:killer, woj_launcher:stat Pid: int64(meta.Cfg.Lang.Runtime.Run.NProcLimit + 4), Memory: uint64(meta.Cfg.Lang.Runtime.Run.MemoryLimit * 1024 * 1024), // woj-sandbox killer will add 1 more second, here we add 1 also Timeout: time.Duration((meta.Cfg.Lang.Runtime.Run.TimeLimit+1000)/1000+1+1) * time.Second, } ids := make([]int, 0) for _, task := range meta.Cfg.All.Tasks { f := func(id int) func() error { return func() error { testCase := filepath.Join(dataDir, fmt.Sprintf("%d.input", id)) ansFile := filepath.Join(workDir, fmt.Sprintf("%d.out.usr", id)) ifoFile := filepath.Join(workDir, fmt.Sprintf("%d.info", id)) targetFile := fmt.Sprintf("%s.out", meta.Run.User) targetPath := filepath.Join(workDir, targetFile) sourceFile := fmt.Sprintf("%s.%s", meta.Run.User, meta.Run.Lang) sourcePath := filepath.Join(workDir, sourceFile) args := &RunArgs{ Program: ProgramArgs{ Args: []string{ "sh", "-c", "cd /woj/user && /woj/framework/scripts/woj_launcher " + s.SandboxArgsBuilder(meta, id), }, }, Runtime: runtimeArgs, } args.Runtime.Mount = []specs.Mount{ { Source: testCase, Destination: fmt.Sprintf("/woj/problem/data/input/%d.input", id), Type: "bind", Options: []string{"rbind", "ro"}, }, { Source: utils.If(meta.Cfg.Lang.JudgeInterpreter() != "", sourcePath, targetPath), Destination: fmt.Sprintf("/woj/user/%s", utils.If(meta.Cfg.Lang.JudgeInterpreter() != "", sourceFile, targetFile)), Type: "bind", Options: []string{"rbind", "ro"}, }, { Source: ansFile, Destination: fmt.Sprintf("/woj/user/%d.out.usr", id), Type: "bind", Options: []string{"rbind"}, }, { Source: ifoFile, Destination: fmt.Sprintf("/woj/user/%d.info", id), Type: "bind", Options: []string{"rbind"}, }, } err := utils.NewMust(). DoAny(func() error { return os.Remove(ansFile) }). DoAny(func() error { return os.Remove(ifoFile) }). Do(func() error { return file.TouchErr(ansFile) }). Do(func() error { return file.TouchErr(ifoFile) }). Do(func() error { return s.ContainerRun(args) }). Done() if err != nil { s.log.Info("[run] run failed", zap.Error(err), zap.Any("meta", *meta)) } return err } }(task.Id) id := s.pool.AddTask(f) ids = append(ids, id) } for _, id := range ids { _ = s.pool.WaitForTask(id) } } func (s *service) ProblemJudge(meta *JudgeMeta) { workDir := filepath.Join(UserDir, meta.Run.User) dataDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Run.Version), "data") judgeDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Run.Version), "judge") script := meta.Cfg.Lang.JudgeScript() runtimeArgs := RuntimeArgs{ Image: ContainerImageFull, Pid: int64(meta.Cfg.Lang.Runtime.Check.NProcLimit + 2), // bash + make Memory: uint64(meta.Cfg.Lang.Runtime.Check.MemoryLimit * 1024 * 1024), Timeout: time.Duration((meta.Cfg.Lang.Runtime.Check.TimeLimit+1000)/1000) * time.Second, } ids := make([]int, 0) for _, task := range meta.Cfg.All.Tasks { f := func(id int) func() error { return func() error { ansFile := filepath.Join(workDir, fmt.Sprintf("%d.out.usr", id)) jdgFile := filepath.Join(workDir, fmt.Sprintf("%d.judge", id)) args := &RunArgs{ Program: ProgramArgs{ Args: []string{ "sh", "-c", fmt.Sprintf("cd /woj/user && make -f %s judge", script), }, Env: []string{ fmt.Sprintf("TEST_NUM=%d", id), fmt.Sprintf("CMP=%s", meta.Cfg.Lang.Judge.Cmp), }, }, Runtime: runtimeArgs, } args.Runtime.Mount = []specs.Mount{ { Source: judgeDir, Destination: "/woj/problem/judge", Type: "bind", Options: []string{"rbind", "ro"}, }, { Source: dataDir, Destination: "/woj/problem/data", Type: "bind", Options: []string{"rbind", "ro"}, }, { Source: ansFile, Destination: fmt.Sprintf("/woj/user/%d.out.usr", id), Type: "bind", Options: []string{"rbind"}, }, { Source: jdgFile, Destination: fmt.Sprintf("/woj/user/%d.judge", id), Type: "bind", Options: []string{"rbind"}, }, } err := utils.NewMust(). DoAny(func() error { return os.Remove(jdgFile) }). Do(func() error { return file.TouchErr(jdgFile) }). Do(func() error { return s.ContainerRun(args) }). Done() if err != nil { s.log.Info("[judge] judge failed", zap.Error(err), zap.Any("meta", *meta)) } return err } }(task.Id) id := s.pool.AddTask(f) ids = append(ids, id) } for _, id := range ids { _ = s.pool.WaitForTask(id) } } func (s *service) RunAndJudge(meta *JudgeMeta) (*JudgeStatus, int32, e.Status) { // 1. run user program s.ProblemRun(meta) // 2. run judge s.ProblemJudge(meta) // 3. check result result, pts := s.CheckResults(meta) return result, pts, e.Success }