mirror of
https://github.com/criyle/go-judge.git
synced 2025-09-26 22:39:12 +08:00
195 lines
4.1 KiB
Go
195 lines
4.1 KiB
Go
package macsandbox
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/criyle/go-judge/env/pool"
|
|
"github.com/criyle/go-judge/envexec"
|
|
"github.com/criyle/go-sandbox/pkg/forkexec"
|
|
"github.com/criyle/go-sandbox/pkg/rlimit"
|
|
"github.com/criyle/go-sandbox/runner"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const outputLimit = 256 << 20 // 256M
|
|
|
|
var _ pool.Environment = &environment{}
|
|
|
|
type environment struct {
|
|
profile string
|
|
wdPath string
|
|
wd *os.File
|
|
}
|
|
|
|
func (e *environment) Execve(c context.Context, param envexec.ExecveParam) (envexec.Process, error) {
|
|
sTime := time.Now()
|
|
rLimits := rlimit.RLimits{
|
|
CPU: uint64(param.Limit.Time.Truncate(time.Second)/time.Second) + 1,
|
|
Data: param.Limit.Memory.Byte(),
|
|
FileSize: outputLimit,
|
|
Stack: param.Limit.Stack.Byte(),
|
|
}
|
|
|
|
ch := &forkexec.Runner{
|
|
Args: param.Args,
|
|
Env: param.Env,
|
|
Files: param.Files,
|
|
WorkDir: e.wdPath,
|
|
RLimits: rLimits.PrepareRLimit(),
|
|
SandboxProfile: e.profile,
|
|
}
|
|
|
|
pid, err := ch.Start()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := &process{
|
|
pid: pid,
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
go func() {
|
|
defer close(p.done)
|
|
|
|
mTime := time.Now()
|
|
|
|
// handle cancel
|
|
ctx, cancel := context.WithCancel(c)
|
|
defer cancel()
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
killAll(pid)
|
|
}()
|
|
|
|
// collect potential zombies
|
|
defer func() {
|
|
killAll(pid)
|
|
collectZombie(pid)
|
|
}()
|
|
|
|
var (
|
|
wstatus syscall.WaitStatus
|
|
rusage syscall.Rusage
|
|
)
|
|
for {
|
|
_, err = syscall.Wait4(pid, &wstatus, 0, &rusage)
|
|
if err == syscall.EINTR {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
p.result.Error = err.Error()
|
|
p.result.Status = runner.StatusRunnerError
|
|
return
|
|
}
|
|
fTime := time.Now()
|
|
p.result = runner.Result{
|
|
Status: runner.StatusNormal,
|
|
Time: time.Duration(rusage.Utime.Nano()),
|
|
Memory: runner.Size(rusage.Maxrss), // seems MacOS uses bytes instead of kb
|
|
SetUpTime: mTime.Sub(sTime),
|
|
RunningTime: fTime.Sub(mTime),
|
|
}
|
|
if p.result.Time > param.Limit.Time {
|
|
p.result.Status = runner.StatusTimeLimitExceeded
|
|
}
|
|
if p.result.Memory > param.Limit.Memory {
|
|
p.result.Status = runner.StatusMemoryLimitExceeded
|
|
}
|
|
|
|
switch {
|
|
case wstatus.Exited():
|
|
if status := wstatus.ExitStatus(); status != 0 {
|
|
p.result.Status = runner.StatusNonzeroExitStatus
|
|
p.result.ExitStatus = status
|
|
return
|
|
}
|
|
return
|
|
|
|
case wstatus.Signaled():
|
|
sig := wstatus.Signal()
|
|
switch sig {
|
|
case unix.SIGXCPU, unix.SIGKILL:
|
|
p.result.Status = runner.StatusTimeLimitExceeded
|
|
case unix.SIGXFSZ:
|
|
p.result.Status = runner.StatusOutputLimitExceeded
|
|
case unix.SIGSYS:
|
|
p.result.Status = runner.StatusDisallowedSyscall
|
|
default:
|
|
p.result.Status = runner.StatusSignalled
|
|
}
|
|
p.result.ExitStatus = int(sig)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func (e *environment) WorkDir() *os.File {
|
|
return e.wd
|
|
}
|
|
|
|
func (e *environment) Open(p string, flags int, perm os.FileMode) (*os.File, error) {
|
|
return os.OpenFile(filepath.Join(e.wdPath, p), flags, perm)
|
|
}
|
|
|
|
func (e *environment) MkdirAll(p string, perm os.FileMode) error {
|
|
return os.MkdirAll(filepath.Join(e.wdPath, p), perm)
|
|
}
|
|
|
|
func (e *environment) Symlink(oldName, newName string) error {
|
|
return os.Symlink(oldName, filepath.Join(e.wdPath, newName))
|
|
}
|
|
|
|
func (e *environment) Destroy() error {
|
|
e.wd.Close()
|
|
return os.RemoveAll(e.wdPath)
|
|
}
|
|
|
|
func (e *environment) Reset() error {
|
|
return removeContents(e.wdPath)
|
|
}
|
|
|
|
// removeContents delete content of a directory
|
|
func removeContents(dir string) error {
|
|
d, err := os.Open(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer d.Close()
|
|
|
|
names, err := d.Readdirnames(-1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, name := range names {
|
|
err = os.RemoveAll(filepath.Join(dir, name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func killAll(pid int) {
|
|
syscall.Kill(pid, syscall.SIGKILL)
|
|
}
|
|
|
|
// collect died child processes
|
|
func collectZombie(pgid int) {
|
|
var wstatus syscall.WaitStatus
|
|
for {
|
|
if _, err := syscall.Wait4(-pgid, &wstatus, syscall.WNOHANG, nil); err != syscall.EINTR && err != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|