package runner import ( "encoding/json" "errors" "fmt" "git.0x7f.app/WOJ/woj-server/pkg/utils" "os" "path/filepath" ) var ( AllowedLanguages = map[string]struct { Interpreter string }{ "c": {""}, "cpp": {""}, "go": {""}, "python3": {"/usr/bin/python3"}, "pypy3": {"/usr/bin/pypy3"}, "rust": {""}, } ) type ConfigRuntime struct { 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 } var ( DefaultPrebuildRuntime = ConfigRuntime{ TimeLimit: 300000, MemoryLimit: 256, SoftMemoryLimit: 256, NProcLimit: 64, WriteFileLimit: 1024, } DefaultCompileRuntime = ConfigRuntime{ TimeLimit: 60000, MemoryLimit: 256, SoftMemoryLimit: 256, NProcLimit: 64, WriteFileLimit: 64, } DefaultCheckRuntime = ConfigRuntime{ TimeLimit: 60000, MemoryLimit: 128, SoftMemoryLimit: 128, NProcLimit: 64, WriteFileLimit: 16, } ) 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 } } 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") } if c.SoftMemoryLimit <= 0 { // default to memory limit c.SoftMemoryLimit = c.MemoryLimit } if c.NProcLimit <= 0 { c.NProcLimit = 0 } if c.WriteFileLimit <= 0 { // default to 1mb c.WriteFileLimit = 1 } return nil } type ConfigJudge struct { Type string `json:"Type"` Script string `json:"Script"` Cmp string `json:"Cmp"` } 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) } if c.Type == "custom" { if c.Script == "" { return fmt.Errorf("language %s has empty script", lang) } 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) } } if c.Type == "default" { if c.Cmp == "" { return fmt.Errorf("language %s has empty cmp", lang) } } return nil } type ConfigLanguage struct { Lang string `json:"Lang"` Judge ConfigJudge `json:"Judge,omitempty"` Runtime struct { Compile ConfigRuntime `json:"Compile"` Run ConfigRuntime `json:"Run"` Check ConfigRuntime `json:"Check,omitempty"` } `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 } } func (l *ConfigLanguage) JudgeInterpreter() string { return AllowedLanguages[l.Lang].Interpreter } type Config struct { Languages []ConfigLanguage `json:"Languages"` Prebuild ConfigRuntime `json:"Prebuild,omitempty"` Tasks []struct { Id int `json:"Id"` Points int32 `json:"Points"` } `json:"Tasks"` } func (c *Config) Validate(base string) error { // check per language config for _, lang := range c.Languages { if _, ok := AllowedLanguages[lang.Lang]; !ok { return fmt.Errorf("language %s is not allowed", lang.Lang) } 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 } } // check prebuild config if err := c.Prebuild.Validate(); err != nil { return err } // check tasks if len(c.Tasks) == 0 { return errors.New("no tasks") } ids := map[int]struct{}{} total := (1 + len(c.Tasks)) * len(c.Tasks) / 2 for _, task := range c.Tasks { 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 } 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) { base := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Run.Version)) 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 { config.Languages[idx].Runtime.Compile.InitWithDefault(&DefaultCompileRuntime) config.Languages[idx].Runtime.Check.InitWithDefault(&DefaultCheckRuntime) config.Languages[idx].Runtime.Run.FillDefault() } config.Prebuild.InitWithDefault(&DefaultPrebuildRuntime) if skipCheck { return config, nil } err = config.Validate(base) if err != nil { return &Config{}, err } return config, nil }