mirror of
https://github.com/criyle/go-judge.git
synced 2025-09-26 22:39:12 +08:00
140 lines
3.3 KiB
Go
140 lines
3.3 KiB
Go
package envexec
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
|
|
"github.com/criyle/go-sandbox/runner"
|
|
)
|
|
|
|
func closePipes(ptc []pipeCollector) {
|
|
for _, p := range ptc {
|
|
p.buffer.Close()
|
|
os.Remove(p.buffer.Name())
|
|
}
|
|
}
|
|
|
|
// runSingle runs Cmd inside the given environment and cgroup
|
|
func runSingle(pc context.Context, c *Cmd, fds []*os.File, ptc []pipeCollector, newStoreFile NewStoreFile) (result Result, err error) {
|
|
m := c.Environment
|
|
resultFileError := func(err error, fe []FileError) {
|
|
result.Status = StatusFileError
|
|
result.Error = err.Error()
|
|
result.FileError = fe
|
|
closePipes(ptc)
|
|
closeFiles(fds...)
|
|
}
|
|
// copyin
|
|
if fe, err := runSingleCopyIn(m, c.CopyIn); err != nil {
|
|
resultFileError(err, fe)
|
|
return result, nil
|
|
}
|
|
// symlink
|
|
if fe, err := symlink(m, c.SymLinks); err != nil {
|
|
resultFileError(err, []FileError{*fe})
|
|
return result, nil
|
|
}
|
|
|
|
// run cmd and wait for result
|
|
rt := runSingleWait(pc, m, c, fds)
|
|
|
|
// collect result
|
|
files, fe, err := copyOutAndCollect(m, c, ptc, newStoreFile)
|
|
result = Result{
|
|
Status: convertStatus(rt.Status),
|
|
ExitStatus: rt.ExitStatus,
|
|
Error: rt.Error,
|
|
Time: rt.Time,
|
|
RunTime: rt.RunningTime,
|
|
Memory: rt.Memory,
|
|
ProcPeak: rt.ProcPeak,
|
|
Files: files,
|
|
FileError: fe,
|
|
}
|
|
// collect error (only if the process exits normally)
|
|
if rt.Status == runner.StatusNormal && err != nil && result.Error == "" {
|
|
switch err := err.(type) {
|
|
case runner.Status:
|
|
result.Status = convertStatus(err)
|
|
default:
|
|
result.Status = StatusFileError
|
|
}
|
|
result.Error = err.Error()
|
|
}
|
|
if result.Time > c.TimeLimit {
|
|
result.Status = StatusTimeLimitExceeded
|
|
}
|
|
if result.Memory > c.MemoryLimit {
|
|
result.Status = StatusMemoryLimitExceeded
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func runSingleCopyIn(m Environment, copyInFiles map[string]File) ([]FileError, error) {
|
|
if len(copyInFiles) == 0 {
|
|
return nil, nil
|
|
}
|
|
return copyIn(m, copyInFiles)
|
|
}
|
|
|
|
func runSingleWait(pc context.Context, m Environment, c *Cmd, fds []*os.File) RunnerResult {
|
|
// start the cmd (they will be canceled in other goroutines)
|
|
ctx, cancel := context.WithCancel(pc)
|
|
defer cancel()
|
|
|
|
process, err := runSingleExecve(ctx, m, c, fds)
|
|
if err != nil {
|
|
return runner.Result{
|
|
Status: runner.StatusRunnerError,
|
|
Error: err.Error(),
|
|
}
|
|
}
|
|
|
|
// starts waiter to periodically check cpu usage
|
|
c.Waiter(ctx, process)
|
|
// cancel the process as waiter exits
|
|
cancel()
|
|
|
|
return process.Result()
|
|
}
|
|
|
|
func runSingleExecve(ctx context.Context, m Environment, c *Cmd, fds []*os.File) (Process, error) {
|
|
defer closeFiles(fds...)
|
|
|
|
extraMemoryLimit := c.ExtraMemoryLimit
|
|
if extraMemoryLimit == 0 {
|
|
extraMemoryLimit = defaultExtraMemoryLimit
|
|
}
|
|
|
|
memoryLimit := c.MemoryLimit + extraMemoryLimit
|
|
|
|
var stackLimit Size
|
|
if c.StackLimit > 0 {
|
|
stackLimit = c.StackLimit
|
|
}
|
|
if stackLimit > memoryLimit {
|
|
stackLimit = memoryLimit
|
|
}
|
|
|
|
// set running parameters
|
|
execParam := ExecveParam{
|
|
Args: c.Args,
|
|
Env: c.Env,
|
|
Files: getFdArray(fds),
|
|
TTY: c.TTY,
|
|
Limit: Limit{
|
|
Time: c.TimeLimit,
|
|
Memory: memoryLimit,
|
|
Proc: c.ProcLimit,
|
|
Stack: stackLimit,
|
|
Output: c.OutputLimit,
|
|
Rate: c.CPURateLimit,
|
|
OpenFile: c.OpenFileLimit,
|
|
CPUSet: c.CPUSetLimit,
|
|
DataSegment: c.DataSegmentLimit,
|
|
AddressSpace: c.AddressSpaceLimit,
|
|
},
|
|
}
|
|
return m.Execve(ctx, execParam)
|
|
}
|