2022-10-22 17:38:39 +08:00
|
|
|
package runner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-01-27 23:07:14 +08:00
|
|
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
2022-10-22 17:38:39 +08:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
)
|
|
|
|
|
2024-01-30 20:56:07 +08:00
|
|
|
var (
|
|
|
|
AllowedLanguages = map[string]struct {
|
|
|
|
Interpreter string
|
|
|
|
}{
|
|
|
|
"c": {""},
|
|
|
|
"cpp": {""},
|
2024-04-27 22:02:14 +08:00
|
|
|
"go": {""},
|
2024-01-30 20:56:07 +08:00
|
|
|
"python3": {"/usr/bin/python3"},
|
|
|
|
"pypy3": {"/usr/bin/pypy3"},
|
2024-04-27 22:02:14 +08:00
|
|
|
"rust": {""},
|
2024-01-30 20:56:07 +08:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
type ConfigRuntime struct {
|
2024-04-27 22:02:14 +08:00
|
|
|
TimeLimit int `json:"TimeLimit"` // in ms
|
|
|
|
MemoryLimit int `json:"MemoryLimit"` // in mb
|
|
|
|
SoftMemoryLimit int `json:"SoftMemoryLimit"` // in mb
|
|
|
|
NProcLimit int `json:"NProcLimit"`
|
|
|
|
WriteFileLimit int `json:"WriteFileLimit"` // in mb
|
2024-01-06 19:21:37 +08:00
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
var (
|
|
|
|
DefaultPrebuildRuntime = ConfigRuntime{
|
2024-04-27 22:02:14 +08:00
|
|
|
TimeLimit: 300000,
|
|
|
|
MemoryLimit: 256,
|
|
|
|
SoftMemoryLimit: 256,
|
|
|
|
NProcLimit: 64,
|
|
|
|
WriteFileLimit: 1024,
|
2024-01-27 23:07:14 +08:00
|
|
|
}
|
|
|
|
DefaultCompileRuntime = ConfigRuntime{
|
2024-04-27 22:02:14 +08:00
|
|
|
TimeLimit: 60000,
|
|
|
|
MemoryLimit: 256,
|
|
|
|
SoftMemoryLimit: 256,
|
|
|
|
NProcLimit: 64,
|
|
|
|
WriteFileLimit: 64,
|
2024-01-27 23:07:14 +08:00
|
|
|
}
|
|
|
|
DefaultCheckRuntime = ConfigRuntime{
|
2024-04-27 22:02:14 +08:00
|
|
|
TimeLimit: 60000,
|
|
|
|
MemoryLimit: 128,
|
|
|
|
SoftMemoryLimit: 128,
|
|
|
|
NProcLimit: 64,
|
|
|
|
WriteFileLimit: 16,
|
2024-01-27 23:07:14 +08:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-04-28 14:39:43 +08:00
|
|
|
func (c *ConfigRuntime) InitWithDefault(fallback *ConfigRuntime) {
|
|
|
|
if c.Validate() != nil {
|
|
|
|
*c = *fallback
|
|
|
|
} else {
|
|
|
|
c.FillDefault()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ConfigRuntime) FillDefault() {
|
|
|
|
if c.SoftMemoryLimit <= 0 {
|
|
|
|
// default to memory limit
|
|
|
|
c.SoftMemoryLimit = c.MemoryLimit
|
|
|
|
}
|
|
|
|
if c.NProcLimit <= 0 {
|
|
|
|
// default to not limited
|
|
|
|
c.NProcLimit = 0
|
|
|
|
}
|
|
|
|
if c.WriteFileLimit <= 0 {
|
|
|
|
// default to 1mb
|
|
|
|
c.WriteFileLimit = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
func (c *ConfigRuntime) Validate() error {
|
|
|
|
if c.TimeLimit <= 0 {
|
|
|
|
return errors.New("time limit <= 0")
|
|
|
|
}
|
|
|
|
if c.MemoryLimit <= 0 {
|
|
|
|
return errors.New("memory limit <= 0")
|
|
|
|
}
|
2024-04-27 22:02:14 +08:00
|
|
|
if c.SoftMemoryLimit <= 0 {
|
|
|
|
// default to memory limit
|
|
|
|
c.SoftMemoryLimit = c.MemoryLimit
|
|
|
|
}
|
2024-01-27 23:07:14 +08:00
|
|
|
if c.NProcLimit <= 0 {
|
2024-04-27 22:02:14 +08:00
|
|
|
c.NProcLimit = 0
|
2024-01-27 23:07:14 +08:00
|
|
|
}
|
2024-04-27 21:25:21 +08:00
|
|
|
if c.WriteFileLimit <= 0 {
|
2024-04-27 22:02:14 +08:00
|
|
|
// default to 1mb
|
|
|
|
c.WriteFileLimit = 1
|
2024-04-27 21:25:21 +08:00
|
|
|
}
|
2024-01-27 23:07:14 +08:00
|
|
|
return nil
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
type ConfigJudge struct {
|
|
|
|
Type string `json:"Type"`
|
|
|
|
Script string `json:"Script"`
|
|
|
|
Cmp string `json:"Cmp"`
|
|
|
|
}
|
2022-10-22 17:38:39 +08:00
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
func (c *ConfigJudge) Validate(base string, lang string) error {
|
|
|
|
if c.Type != "custom" && c.Type != "default" {
|
|
|
|
return fmt.Errorf("language %s has invalid type %s", lang, c.Type)
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
if c.Type == "custom" {
|
|
|
|
if c.Script == "" {
|
|
|
|
return fmt.Errorf("language %s has empty script", lang)
|
|
|
|
}
|
2022-10-22 17:38:39 +08:00
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
file := filepath.Join(base, "judge", c.Script)
|
|
|
|
_, err := os.Stat(file)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("language %s has invalid script %s", lang, c.Script)
|
|
|
|
}
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
if c.Type == "default" {
|
|
|
|
if c.Cmp == "" {
|
|
|
|
return fmt.Errorf("language %s has empty cmp", lang)
|
|
|
|
}
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
return nil
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
type ConfigLanguage struct {
|
|
|
|
Lang string `json:"Lang"`
|
2024-01-28 18:15:07 +08:00
|
|
|
Judge ConfigJudge `json:"Judge,omitempty"`
|
2024-01-27 23:07:14 +08:00
|
|
|
Runtime struct {
|
2024-01-28 18:15:07 +08:00
|
|
|
Compile ConfigRuntime `json:"Compile"`
|
|
|
|
Run ConfigRuntime `json:"Run"`
|
|
|
|
Check ConfigRuntime `json:"Check,omitempty"`
|
2024-01-27 23:07:14 +08:00
|
|
|
} `json:"Runtime"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *ConfigLanguage) JudgeScript() string {
|
|
|
|
if l.Judge.Type == "default" {
|
|
|
|
return "/woj/framework/template/default/" + l.Lang + ".Makefile"
|
|
|
|
} else {
|
|
|
|
return "/woj/problem/judge/" + l.Judge.Script
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
2024-01-27 23:07:14 +08:00
|
|
|
}
|
|
|
|
|
2024-01-30 20:56:07 +08:00
|
|
|
func (l *ConfigLanguage) JudgeInterpreter() string {
|
|
|
|
return AllowedLanguages[l.Lang].Interpreter
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
type Config struct {
|
|
|
|
Languages []ConfigLanguage `json:"Languages"`
|
2024-01-28 18:15:07 +08:00
|
|
|
Prebuild ConfigRuntime `json:"Prebuild,omitempty"`
|
2024-01-27 23:07:14 +08:00
|
|
|
Tasks []struct {
|
|
|
|
Id int `json:"Id"`
|
|
|
|
Points int32 `json:"Points"`
|
|
|
|
} `json:"Tasks"`
|
|
|
|
}
|
2022-10-22 17:38:39 +08:00
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
func (c *Config) Validate(base string) error {
|
2024-01-28 18:15:07 +08:00
|
|
|
// check per language config
|
2024-01-27 23:07:14 +08:00
|
|
|
for _, lang := range c.Languages {
|
2024-01-30 20:56:07 +08:00
|
|
|
if _, ok := AllowedLanguages[lang.Lang]; !ok {
|
2022-10-22 17:38:39 +08:00
|
|
|
return fmt.Errorf("language %s is not allowed", lang.Lang)
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
err := utils.NewMust().
|
|
|
|
// check judge config
|
|
|
|
Do(func() error { return lang.Judge.Validate(base, lang.Lang) }).
|
|
|
|
// check runtime limits
|
|
|
|
Do(func() error { return lang.Runtime.Compile.Validate() }).
|
|
|
|
Do(func() error { return lang.Runtime.Run.Validate() }).
|
|
|
|
Do(func() error { return lang.Runtime.Check.Validate() }).
|
|
|
|
Done()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
2024-01-28 18:15:07 +08:00
|
|
|
}
|
2022-10-22 17:38:39 +08:00
|
|
|
|
2024-01-28 18:15:07 +08:00
|
|
|
// check prebuild config
|
|
|
|
if err := c.Prebuild.Validate(); err != nil {
|
|
|
|
return err
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-01-28 18:15:07 +08:00
|
|
|
// check tasks
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
if len(c.Tasks) == 0 {
|
2022-10-22 17:38:39 +08:00
|
|
|
return errors.New("no tasks")
|
|
|
|
}
|
2024-01-27 23:07:14 +08:00
|
|
|
|
2022-10-22 17:38:39 +08:00
|
|
|
ids := map[int]struct{}{}
|
2024-01-27 23:07:14 +08:00
|
|
|
total := (1 + len(c.Tasks)) * len(c.Tasks) / 2
|
|
|
|
for _, task := range c.Tasks {
|
2022-10-22 17:38:39 +08:00
|
|
|
if task.Id <= 0 {
|
|
|
|
return fmt.Errorf("task %d has non-positive id", task.Id)
|
|
|
|
}
|
|
|
|
|
|
|
|
if task.Points < 0 {
|
|
|
|
return fmt.Errorf("task %d has negative points", task.Id)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := ids[task.Id]; ok {
|
|
|
|
return fmt.Errorf("task %d has duplicate id", task.Id)
|
|
|
|
}
|
|
|
|
|
|
|
|
total -= task.Id
|
|
|
|
ids[task.Id] = struct{}{}
|
|
|
|
}
|
|
|
|
if total != 0 {
|
|
|
|
return errors.New("task ids are not continuous")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2024-01-27 23:07:14 +08:00
|
|
|
|
|
|
|
func (c *Config) FilterLanguage(lang string) (*ConfigLanguage, bool) {
|
|
|
|
for idx := range c.Languages {
|
|
|
|
if c.Languages[idx].Lang == lang {
|
|
|
|
return &c.Languages[idx], true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &ConfigLanguage{}, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *service) ParseConfig(meta *JudgeMeta, skipCheck bool) (*Config, error) {
|
2024-01-29 21:15:39 +08:00
|
|
|
base := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Run.Version))
|
2024-01-27 23:07:14 +08:00
|
|
|
file := filepath.Join(base, "config.json")
|
|
|
|
|
|
|
|
data, err := os.ReadFile(file)
|
|
|
|
if err != nil {
|
|
|
|
return &Config{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// unmarshal
|
|
|
|
config := &Config{}
|
|
|
|
err = json.Unmarshal(data, config)
|
|
|
|
if err != nil {
|
|
|
|
return &Config{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// fill default
|
|
|
|
for idx := range config.Languages {
|
2024-04-28 14:39:43 +08:00
|
|
|
config.Languages[idx].Runtime.Compile.InitWithDefault(&DefaultCompileRuntime)
|
|
|
|
config.Languages[idx].Runtime.Check.InitWithDefault(&DefaultCheckRuntime)
|
|
|
|
config.Languages[idx].Runtime.Run.FillDefault()
|
2024-01-28 18:15:07 +08:00
|
|
|
}
|
2024-04-28 14:39:43 +08:00
|
|
|
config.Prebuild.InitWithDefault(&DefaultPrebuildRuntime)
|
2024-01-27 23:07:14 +08:00
|
|
|
|
|
|
|
if skipCheck {
|
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err = config.Validate(base)
|
|
|
|
if err != nil {
|
|
|
|
return &Config{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
}
|