From bfedda0ba29f075e4c5e7c5b3e7d1089d98b5f27 Mon Sep 17 00:00:00 2001 From: criyle Date: Wed, 12 Feb 2020 03:01:37 -0500 Subject: [PATCH] adapted to context --- README.md | 14 ++++- cmd/runprog/main.go | 111 +++++++++++++++++++++++------------- daemon/container_execve.go | 26 +++++---- daemon/deamon.go | 9 +-- daemon/master_cmd_execve.go | 42 ++++++++------ ptracer/tracer_track.go | 45 ++++++++------- runner/ptrace/handle.go | 2 +- runner/ptrace/run.go | 17 +++++- runner/runner.go | 8 ++- runner/unshare/run.go | 41 ++++++------- types/limit.go | 16 ++++++ types/result.go | 36 ++++++++++++ types/status.go | 45 +++++++++------ types/types.go | 21 ------- 14 files changed, 273 insertions(+), 160 deletions(-) create mode 100644 types/limit.go create mode 100644 types/result.go delete mode 100644 types/types.go diff --git a/README.md b/README.md index ac78c58..a175330 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,12 @@ Default file access syscall check: - Unauthorized Access - Disallowed Syscall - Runtime Error - - Signaled + - Signalled - `SIGXCPU` / `SIGKILL` are treated as TimeLimitExceeded by rlimit or caller kill - `SIGXFSZ` is treated as OutputLimitExceeded by rlimit - `SIGSYS` is treaded as Disallowed Syscall by seccomp - Potential Runtime error are: `SIGSEGV` (segment fault) - - Nonzero Exit Code + - Nonzero Exit Status - Program Runner Error ### Result Structure @@ -80,6 +80,16 @@ type Result struct { } ``` +### Runner Interface + +Configured runner to run the program. `Context` is used to cancel (control time limit exceeded event; should not be nil). + +``` go +type Runner interface { + Run(context.Context) <-chan types.Result +} +``` + ### Pre-forked Container Protocol 1. Pre-fork container daemons to run programs inside diff --git a/cmd/runprog/main.go b/cmd/runprog/main.go index 1dcd246..25354d4 100644 --- a/cmd/runprog/main.go +++ b/cmd/runprog/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "io/ioutil" @@ -108,7 +109,7 @@ func main() { rt, err := start() if rt == nil { rt = &types.Result{ - Status: types.StatusFatal, + Status: types.StatusRunnerError, } } if err == nil && rt.Status != types.StatusNormal { @@ -120,15 +121,15 @@ func main() { debug(err) c, ok := err.(types.Status) if !ok { - c = types.StatusFatal + c = types.StatusRunnerError } // Handle fatal error from trace - fmt.Fprintf(f, "%d %d %d %d\n", int(c), rt.UserTime, rt.UserMem, rt.ExitStatus) - if c == types.StatusFatal { + fmt.Fprintf(f, "%d %d %d %d\n", getStatus(c), int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus) + if c == types.StatusRunnerError { os.Exit(1) } } else { - fmt.Fprintf(f, "%d %d %d %d\n", 0, rt.UserTime, rt.UserMem, rt.ExitStatus) + fmt.Fprintf(f, "%d %d %d %d\n", 0, int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus) } } @@ -137,8 +138,8 @@ type daemonRunner struct { *daemon.ExecveParam } -func (r *daemonRunner) Start(done <-chan struct{}) (<-chan types.Result, error) { - return r.Master.Execve(done, r.ExecveParam) +func (r *daemonRunner) Run(c context.Context) <-chan types.Result { + return r.Master.Execve(c, r.ExecveParam) } func start() (*types.Result, error) { @@ -223,6 +224,11 @@ func start() (*types.Result, error) { actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow) } + limit := types.Limit{ + TimeLimit: time.Duration(timeLimit) * time.Second, + MemoryLimit: types.Size(memoryLimit << 20), + } + if runt == "daemon" { root, err := ioutil.TempDir("", "dm") if err != nil { @@ -273,16 +279,13 @@ func start() (*types.Result, error) { return nil, fmt.Errorf("cannot make rootfs mounts") } runner = &unshare.Runner{ - Args: args, - Env: []string{pathEnv}, - ExecFile: execFile, - WorkDir: "/w", - Files: fds, - RLimits: rlims.PrepareRLimit(), - Limit: types.Limit{ - TimeLimit: timeLimit * 1e3, - MemoryLimit: memoryLimit << 10, - }, + Args: args, + Env: []string{pathEnv}, + ExecFile: execFile, + WorkDir: "/w", + Files: fds, + RLimits: rlims.PrepareRLimit(), + Limit: limit, Seccomp: filter, Root: root, Mounts: mounts, @@ -302,15 +305,12 @@ func start() (*types.Result, error) { return nil, fmt.Errorf("failed to create seccomp filter %v", err) } runner = &ptrace.Runner{ - Args: args, - Env: []string{pathEnv}, - ExecFile: execFile, - WorkDir: workPath, - RLimits: rlims.PrepareRLimit(), - Limit: types.Limit{ - TimeLimit: timeLimit * 1e3, - MemoryLimit: memoryLimit << 10, - }, + Args: args, + Env: []string{pathEnv}, + ExecFile: execFile, + WorkDir: workPath, + RLimits: rlims.PrepareRLimit(), + Limit: limit, Files: fds, Seccomp: filter, ShowDetails: showDetails, @@ -328,22 +328,17 @@ func start() (*types.Result, error) { // Run tracer sTime := time.Now() - done := make(chan struct{}) - s, err := runner.Start(done) + c, cancel := context.WithTimeout(context.Background(), time.Duration(int64(realTimeLimit)*int64(time.Second))) + defer cancel() + + s := runner.Run(c) rTime := time.Now() - if err != nil { - return nil, fmt.Errorf("failed to execve: %v", err) - } - tC := time.After(time.Duration(int64(realTimeLimit) * int64(time.Second))) + select { case <-sig: - close(done) - rt = <-s - rt.Status = types.StatusFatal - - case <-tC: - close(done) + cancel() rt = <-s + rt.Status = types.StatusRunnerError case rt = <-s: } @@ -366,8 +361,9 @@ func start() (*types.Result, error) { return nil, fmt.Errorf("cgroup memory: %v", err) } debug("cgroup: cpu: ", cpu, " memory: ", memory) - rt.UserTime = cpu / uint64(time.Millisecond) - rt.UserMem = memory >> 10 + rt.Time = time.Duration(cpu) + rt.Memory = types.Size(memory) + debug("cgroup:", rt) } return &rt, nil } @@ -377,3 +373,38 @@ func debug(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) } } + +type Status int + +// UOJ run_program constants +const ( + StatusNormal Status = iota // 0 + StatusInvalid // 1 + StatusRE // 2 + StatusMLE // 3 + StatusTLE // 4 + StatusOLE // 5 + StatusBan // 6 + StatusFatal // 7 +) + +func getStatus(s types.Status) int { + switch s { + case types.StatusNormal: + return int(StatusNormal) + case types.StatusInvalid: + return int(StatusInvalid) + case types.StatusTimeLimitExceeded: + return int(StatusTLE) + case types.StatusMemoryLimitExceeded: + return int(StatusMLE) + case types.StatusOutputLimitExceeded: + return int(StatusOLE) + case types.StatusDisallowedSyscall: + return int(StatusBan) + case types.StatusSignalled, types.StatusNonzeroExitStatus: + return int(StatusRE) + default: + return int(StatusFatal) + } +} diff --git a/daemon/container_execve.go b/daemon/container_execve.go index d7fd0e9..eed1c8f 100644 --- a/daemon/container_execve.go +++ b/daemon/container_execve.go @@ -3,6 +3,7 @@ package daemon import ( "fmt" "syscall" + "time" "github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/unixsocket" @@ -114,9 +115,9 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error if err != nil { c.sendErrorReply("execve: wait4 %v", err) } else { - var status types.Status - userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms - userMem := uint64(rusage.Maxrss) // kb + status := types.StatusNormal + userTime := time.Duration(rusage.Utime.Nano()) // ns + userMem := types.Size(rusage.Maxrss << 10) // bytes switch { case wstatus.Exited(): exitStatus := wstatus.ExitStatus() @@ -124,8 +125,8 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error ExecReply: &ExecReply{ Status: status, ExitStatus: exitStatus, - UserTime: userTime, - UserMem: userMem, + Time: userTime, + Memory: userMem, }, }, nil) @@ -133,19 +134,20 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error switch wstatus.Signal() { // kill signal treats as TLE case syscall.SIGXCPU, syscall.SIGKILL: - status = types.StatusTLE + status = types.StatusTimeLimitExceeded case syscall.SIGXFSZ: - status = types.StatusOLE + status = types.StatusOutputLimitExceeded case syscall.SIGSYS: - status = types.StatusBan + status = types.StatusDisallowedSyscall default: - status = types.StatusRE + status = types.StatusSignalled } c.sendReply(&Reply{ ExecReply: &ExecReply{ - Status: status, - UserTime: userTime, - UserMem: userMem, + ExitStatus: int(wstatus.Signal()), + Status: status, + Time: userTime, + Memory: userMem, }, }, nil) diff --git a/daemon/deamon.go b/daemon/deamon.go index 06edfaf..da58a5f 100644 --- a/daemon/deamon.go +++ b/daemon/deamon.go @@ -36,6 +36,7 @@ Any socket related error will cause the daemon exit (with all process inside con import ( "os" "syscall" + "time" "github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/types" @@ -90,10 +91,10 @@ type ErrorReply struct { // ExecReply stores execve result type ExecReply struct { - ExitStatus int // waitpid exit status - Status types.Status // return status - UserTime uint64 // waitpid user CPU (ms) - UserMem uint64 // waitpid user memory (kb) + ExitStatus int // waitpid exit status + Status types.Status // return status + Time time.Duration // waitpid user CPU (ns) + Memory types.Size // waitpid user memory (byte) } func (e *ErrorReply) Error() string { diff --git a/daemon/master_cmd_execve.go b/daemon/master_cmd_execve.go index 26e6dae..84a2c64 100644 --- a/daemon/master_cmd_execve.go +++ b/daemon/master_cmd_execve.go @@ -1,6 +1,7 @@ package daemon import ( + "context" "fmt" "time" @@ -26,12 +27,23 @@ type ExecveParam struct { } // Execve runs process inside container -// accepts done for cancelation -func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.Result, error) { +// accepts context for cancelation +func (m *Master) Execve(c context.Context, param *ExecveParam) <-chan types.Result { m.mu.Lock() sTime := time.Now() + // make sure goroutine not leaked (blocked) even if result is not consumed + result := make(chan types.Result, 1) + + errResult := func(f string, v ...interface{}) <-chan types.Result { + result <- types.Result{ + Status: types.StatusRunnerError, + Error: fmt.Sprintf(f, v...), + } + return result + } + // if execve with fd, put fd at the first parameter var files []int if param.ExecFile > 0 { @@ -53,20 +65,20 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types. } if err := m.sendCmd(&cmd, msg); err != nil { m.mu.Unlock() - return nil, fmt.Errorf("execve: sendCmd %v", err) + return errResult("execve: sendCmd %v", err) } // sync function reply, msg, err := m.recvReply() if err != nil { m.mu.Unlock() - return nil, fmt.Errorf("execve: recvReply %v", err) + return errResult("execve: recvReply %v", err) } // if sync function did not involved if reply.Error != nil || msg == nil || msg.Cred == nil { // tell kill function to exit and sync m.execveSyncKill() m.mu.Unlock() - return nil, fmt.Errorf("execve: no pid received or error %v", reply.Error) + return errResult("execve: no pid received or error %v", reply.Error) } if param.SyncFunc != nil { if err := param.SyncFunc(int(msg.Cred.Pid)); err != nil { @@ -75,19 +87,17 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types. // tell kill function to exit and sync m.execveSyncKill() m.mu.Unlock() - return nil, fmt.Errorf("execve: syncfunc failed %v", err) + return errResult("execve: syncfunc failed %v", err) } } // send to syncFunc ack ok if err := m.sendCmd(&Cmd{Cmd: cmdOk}, nil); err != nil { m.mu.Unlock() - return nil, fmt.Errorf("execve: ack failed %v", err) + return errResult("execve: ack failed %v", err) } mTime := time.Now() - // make sure goroutine not leaked (blocked) even if result is not consumed - result := make(chan types.Result, 1) waitDone := make(chan struct{}) // Wait @@ -102,21 +112,21 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types. // handle potential error if err != nil { result <- types.Result{ - Status: types.StatusFatal, + Status: types.StatusRunnerError, Error: err.Error(), } return } if reply2.Error != nil { result <- types.Result{ - Status: types.StatusFatal, + Status: types.StatusRunnerError, Error: reply2.Error.Error(), } return } if reply2.ExecReply == nil { result <- types.Result{ - Status: types.StatusFatal, + Status: types.StatusRunnerError, Error: "execve: no reply received", } return @@ -125,8 +135,8 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types. result <- types.Result{ Status: reply2.ExecReply.Status, ExitStatus: reply2.ExecReply.ExitStatus, - UserTime: reply2.ExecReply.UserTime, - UserMem: reply2.ExecReply.UserMem, + Time: reply2.ExecReply.Time, + Memory: reply2.ExecReply.Memory, SetUpTime: mTime.Sub(sTime), RunningTime: time.Since(mTime), } @@ -135,13 +145,13 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types. // Kill (if wait is done, a kill message need to be send to collect zombies) go func() { select { - case <-done: + case <-c.Done(): case <-waitDone: } m.sendCmd(&Cmd{Cmd: cmdKill}, nil) }() - return result, nil + return result } // execveSyncKill will send kill and recv reply diff --git a/ptracer/tracer_track.go b/ptracer/tracer_track.go index e1d3a28..234d2a5 100644 --- a/ptracer/tracer_track.go +++ b/ptracer/tracer_track.go @@ -55,7 +55,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t t.Handler.Debug("tracer started: ", pgid, err) if err != nil { t.Handler.Debug("start tracee failed: ", err) - result.Status = types.StatusRE + result.Status = types.StatusRunnerError return result, err } @@ -77,11 +77,11 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t // also ensure processes was well terminated defer func() { if tle { - err = types.StatusTLE + err = types.StatusTimeLimitExceeded } if err2 := recover(); err2 != nil { t.Handler.Debug(err2) - err = types.StatusFatal + err = types.StatusRunnerError } // kill all tracee upon return killAll(pgid) @@ -101,27 +101,27 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t } if err != nil { t.Handler.Debug("wait4 failed: ", err) - return result, types.StatusFatal + return result, types.StatusRunnerError } t.Handler.Debug("------ ", pid, " ------") status := types.StatusNormal if pid == pgid { // update resource usage and check against limits - userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms - userMem := uint64(rusage.Maxrss) // kb + userTime := time.Duration(rusage.Utime.Nano()) // ns + userMem := types.Size(rusage.Maxrss << 10) // bytes // check tle / mle if userTime > t.Limit.TimeLimit { - status = types.StatusTLE + status = types.StatusTimeLimitExceeded } if userMem > t.Limit.MemoryLimit { - status = types.StatusMLE + status = types.StatusMemoryLimitExceeded } result = types.Result{ - Status: status, - UserTime: userTime, - UserMem: userMem, + Status: status, + Time: userTime, + Memory: userMem, } if status != types.StatusNormal { return result, status @@ -138,8 +138,8 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t result.ExitStatus = wstatus.ExitStatus() return result, nil } - result.Status = types.StatusFatal - return result, types.StatusFatal + result.Status = types.StatusRunnerError + return result, types.StatusRunnerError } case wstatus.Signaled(): @@ -149,15 +149,16 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t delete(traced, pid) switch sig { case unix.SIGXCPU, unix.SIGKILL: - status = types.StatusTLE + status = types.StatusTimeLimitExceeded case unix.SIGXFSZ: - status = types.StatusOLE + status = types.StatusOutputLimitExceeded case unix.SIGSYS: - status = types.StatusBan + status = types.StatusDisallowedSyscall default: - status = types.StatusRE + status = types.StatusSignalled } result.Status = status + result.ExitStatus = int(sig) return result, status } unix.PtraceCont(pid, int(sig)) @@ -170,7 +171,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t // Ptrace set option valid if the tracee is stopped err = setPtraceOption(pid) if err != nil { - result.Status = types.StatusFatal + result.Status = types.StatusRunnerError return result, err } } @@ -183,7 +184,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t // give the customized handle for syscall err := t.handleTrap(pid) if err != nil { - result.Status = types.StatusBan + result.Status = types.StatusDisallowedSyscall return result, err } } else { @@ -212,9 +213,9 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t // check if cpu rlimit hit switch stopSig { case unix.SIGXCPU: - status = types.StatusTLE + status = types.StatusTimeLimitExceeded case unix.SIGXFSZ: - status = types.StatusOLE + status = types.StatusOutputLimitExceeded } if status != types.StatusNormal { result.Status = status @@ -266,7 +267,7 @@ func (t *Tracer) handleTrap(pid int) error { return ctx.skipSyscall() case TraceKill: - return types.StatusBan + return types.StatusDisallowedSyscall } } diff --git a/runner/ptrace/handle.go b/runner/ptrace/handle.go index b83e640..1e6b81f 100644 --- a/runner/ptrace/handle.go +++ b/runner/ptrace/handle.go @@ -127,7 +127,7 @@ func (h *tracerHandler) GetSyscallName(ctx *ptracer.Context) (string, error) { func (h *tracerHandler) HandlerDisallow(name string) error { if !h.Unsafe { - return types.StatusBan + return types.StatusDisallowedSyscall } return nil } diff --git a/runner/ptrace/run.go b/runner/ptrace/run.go index 0e5c707..cb7b4fe 100644 --- a/runner/ptrace/run.go +++ b/runner/ptrace/run.go @@ -1,13 +1,15 @@ package ptrace import ( + "context" + "github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/ptracer" "github.com/criyle/go-sandbox/types" ) -// Start starts the tracing process -func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) { +// Run starts the tracing process +func (r *Runner) Run(c context.Context) <-chan types.Result { ch := &forkexec.Runner{ Args: r.Args, Env: r.Env, @@ -31,5 +33,14 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) { Runner: ch, Limit: r.Limit, } - return tracer.Trace(done) + rt, err := tracer.Trace(c.Done()) + if err != nil { + ch := make(chan types.Result, 1) + ch <- types.Result{ + Status: types.StatusRunnerError, + Error: err.Error(), + } + rt = ch + } + return rt } diff --git a/runner/runner.go b/runner/runner.go index 852c67e..f8ab08a 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -1,8 +1,12 @@ package runner -import "github.com/criyle/go-sandbox/types" +import ( + "context" + + "github.com/criyle/go-sandbox/types" +) // Runner interface defines method to start running type Runner interface { - Start(<-chan struct{}) (<-chan types.Result, error) + Run(context.Context) <-chan types.Result } diff --git a/runner/unshare/run.go b/runner/unshare/run.go index 697047a..0bfe47d 100644 --- a/runner/unshare/run.go +++ b/runner/unshare/run.go @@ -1,6 +1,7 @@ package unshare import ( + "context" "fmt" "os" "time" @@ -16,9 +17,8 @@ const ( UnshareFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP ) -// Start starts the unshared process -func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) { - var err error +// Run starts the unshared process +func (r *Runner) Run(c context.Context) <-chan types.Result { ch := &forkexec.Runner{ Args: r.Args, Env: r.Env, @@ -44,8 +44,8 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) { // run go func() { defer close(finish) - ret, err2 := r.Trace(done, start, ch) - err = err2 + ret, err2 := r.Trace(c.Done(), start, ch) + ret.Error = err2.Error() result <- ret }() @@ -53,7 +53,7 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) { case <-start: case <-finish: } - return result, err + return result } // Trace tracks child processes @@ -72,7 +72,7 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{}, pgid, err := runner.Start() r.println("Starts: ", pgid, err) if err != nil { - result.Status = types.StatusRE + result.Status = types.StatusRunnerError return result, err } @@ -92,7 +92,7 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{}, defer func() { if tle { - err = types.StatusTLE + err = types.StatusTimeLimitExceeded } // kill all tracee upon return killAll(pgid) @@ -107,24 +107,24 @@ loop: _, err := unix.Wait4(pgid, &wstatus, 0, &rusage) r.println("wait4: ", wstatus) if err != nil { - return result, types.StatusFatal + return result, types.StatusRunnerError } // update resource usage and check against limits - userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms - userMem := uint64(rusage.Maxrss) // kb + userTime := time.Duration(rusage.Utime.Nano()) // ns + userMem := types.Size(rusage.Maxrss << 10) // bytes // kb // check tle / mle if userTime > r.Limit.TimeLimit { - status = types.StatusTLE + status = types.StatusTimeLimitExceeded } if userMem > r.Limit.MemoryLimit { - status = types.StatusMLE + status = types.StatusMemoryLimitExceeded } result = types.Result{ - Status: status, - UserTime: userTime, - UserMem: userMem, + Status: status, + Time: userTime, + Memory: userMem, } if status != types.StatusNormal { return result, status @@ -138,15 +138,16 @@ loop: sig := wstatus.Signal() switch sig { case unix.SIGXCPU, unix.SIGKILL: - status = types.StatusTLE + status = types.StatusTimeLimitExceeded case unix.SIGXFSZ: - status = types.StatusOLE + status = types.StatusOutputLimitExceeded case unix.SIGSYS: - status = types.StatusBan + status = types.StatusDisallowedSyscall default: - status = types.StatusRE + status = types.StatusSignalled } result.Status = status + result.ExitStatus = int(sig) break loop } } diff --git a/types/limit.go b/types/limit.go new file mode 100644 index 0000000..428148e --- /dev/null +++ b/types/limit.go @@ -0,0 +1,16 @@ +package types + +import ( + "fmt" + "time" +) + +// Limit represents the resource limit for traced process +type Limit struct { + TimeLimit time.Duration // user CPU time limit (in ns) + MemoryLimit Size // user memory limit (in bytes) +} + +func (l Limit) String() string { + return fmt.Sprintf("Limit[Time=%v, Memory=%v]", l.TimeLimit, l.MemoryLimit) +} diff --git a/types/result.go b/types/result.go new file mode 100644 index 0000000..1ed7e26 --- /dev/null +++ b/types/result.go @@ -0,0 +1,36 @@ +package types + +import ( + "fmt" + "time" +) + +// Result is the program runner result +type Result struct { + Status // result status + ExitStatus int // exit status (signal number if signalled) + Error string // potential detailed error message (for program runner error) + + Time time.Duration // used user CPU time (underlying type int64 in ns) + Memory Size // used user memory (underlying type uint64 in bytes) + + // metrics for the program runner + SetUpTime time.Duration + RunningTime time.Duration +} + +func (r Result) String() string { + switch r.Status { + case StatusNormal: + return fmt.Sprintf("Result[%v %v][%v %v]", r.Time, r.Memory, r.SetUpTime, r.RunningTime) + + case StatusSignalled: + return fmt.Sprintf("Result[Signalled(%d)][%v %v][%v %v]", r.ExitStatus, r.Time, r.Memory, r.SetUpTime, r.RunningTime) + + case StatusRunnerError: + return fmt.Sprintf("Result[RunnerFailed(%s)][%v %v][%v %v]", r.Error, r.Time, r.Memory, r.SetUpTime, r.RunningTime) + + default: + return fmt.Sprintf("Result[%v(%s)][%v %v][%v %v]", r.Status, r.Error, r.Time, r.Memory, r.SetUpTime, r.RunningTime) + } +} diff --git a/types/status.go b/types/status.go index 6ee42b3..ecd0111 100644 --- a/types/status.go +++ b/types/status.go @@ -3,28 +3,39 @@ package types // Status is the result Status type Status int -// Different end condition +// Result Status for program runner const ( - StatusNormal Status = iota // 0 - StatusInvalid // 1 - StatusRE // 2 - StatusMLE // 3 - StatusTLE // 4 - StatusOLE // 5 - StatusBan // 6 - StatusFatal // 7 + StatusInvalid Status = iota // 0 not initialized + // Normal + StatusNormal // 1 normal + + // Resource Limit Exceeded + StatusTimeLimitExceeded // 2 tle + StatusMemoryLimitExceeded // 3 mle + StatusOutputLimitExceeded // 4 ole + + // Unauthorized Access + StatusDisallowedSyscall // 5 ban + + // Runtime Error + StatusSignalled // 6 signalled + StatusNonzeroExitStatus // 7 nonzero exit status + + // Programmer Runner Error + StatusRunnerError // 8 runner error ) var ( statusString = []string{ + "Invalid", "", - "invalid", - "runtime error", - "memory limit exceeded", - "time limit exceeded", - "output limit exceeded", - "syscall banned", - "runner failed", + "Time Limit Exceeded", + "Memory Limit Exceeded", + "Output Limit Exceeded", + "Disallowed Syscall", + "Signalled", + "Nonzero Exit Status", + "Runner Error", } ) @@ -33,7 +44,7 @@ func (t Status) String() string { if i >= 0 && i < len(statusString) { return statusString[i] } - return "invalid" + return statusString[0] } func (t Status) Error() string { diff --git a/types/types.go b/types/types.go deleted file mode 100644 index e491b3d..0000000 --- a/types/types.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import "time" - -// Result is the result returned by strat trace -type Result struct { - Status // the final status for the process - ExitStatus int // exit Status - Error string // potential detailed error message - UserTime uint64 // used user CPU time (in ms) - UserMem uint64 // used user memory (in kb) - // collects time usage for the runner - SetUpTime time.Duration - RunningTime time.Duration -} - -// Limit represents the resource limit for traced process -type Limit struct { - TimeLimit uint64 // user CPU time limit (in ms) - MemoryLimit uint64 // user memory limit (in kB) -}