mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
162 lines
3.7 KiB
Go
162 lines
3.7 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/criyle/go-sandbox/pkg/rlimit"
|
|
"github.com/criyle/go-sandbox/pkg/unixsocket"
|
|
"github.com/criyle/go-sandbox/types"
|
|
)
|
|
|
|
// ExecveParam is parameters to run process inside container
|
|
type ExecveParam struct {
|
|
Args []string
|
|
Env []string
|
|
Fds []uintptr
|
|
|
|
// fexecve fd
|
|
ExecFile uintptr
|
|
|
|
// POSIX Resource limit set by set rlimit
|
|
RLimits []rlimit.RLimit
|
|
|
|
// SyncFunc called with pid before execve (e.g. add to cgroup)
|
|
SyncFunc func(pid int) error
|
|
}
|
|
|
|
// Execve runs process inside container
|
|
// 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 {
|
|
files = append(files, int(param.ExecFile))
|
|
}
|
|
files = append(files, uintptrSliceToInt(param.Fds)...)
|
|
msg := &unixsocket.Msg{
|
|
Fds: files,
|
|
}
|
|
execCmd := &ExecCmd{
|
|
Argv: param.Args,
|
|
Env: param.Env,
|
|
RLimits: param.RLimits,
|
|
FdExec: param.ExecFile > 0,
|
|
}
|
|
cmd := Cmd{
|
|
Cmd: cmdExecve,
|
|
ExecCmd: execCmd,
|
|
}
|
|
if err := m.sendCmd(&cmd, msg); err != nil {
|
|
m.mu.Unlock()
|
|
return errResult("execve: sendCmd %v", err)
|
|
}
|
|
// sync function
|
|
reply, msg, err := m.recvReply()
|
|
if err != nil {
|
|
m.mu.Unlock()
|
|
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 errResult("execve: no pid received or error %v", reply.Error)
|
|
}
|
|
if param.SyncFunc != nil {
|
|
if err := param.SyncFunc(int(msg.Cred.Pid)); err != nil {
|
|
// tell sync function to exit and recv error
|
|
m.execveSyncKill()
|
|
// tell kill function to exit and sync
|
|
m.execveSyncKill()
|
|
m.mu.Unlock()
|
|
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 errResult("execve: ack failed %v", err)
|
|
}
|
|
|
|
mTime := time.Now()
|
|
|
|
waitDone := make(chan struct{})
|
|
|
|
// Wait
|
|
go func() {
|
|
reply2, _, err := m.recvReply()
|
|
close(waitDone)
|
|
// done signal (should recv after kill)
|
|
m.recvReply()
|
|
// unlock after last read / write
|
|
m.mu.Unlock()
|
|
|
|
// handle potential error
|
|
if err != nil {
|
|
result <- types.Result{
|
|
Status: types.StatusRunnerError,
|
|
Error: err.Error(),
|
|
}
|
|
return
|
|
}
|
|
if reply2.Error != nil {
|
|
result <- types.Result{
|
|
Status: types.StatusRunnerError,
|
|
Error: reply2.Error.Error(),
|
|
}
|
|
return
|
|
}
|
|
if reply2.ExecReply == nil {
|
|
result <- types.Result{
|
|
Status: types.StatusRunnerError,
|
|
Error: "execve: no reply received",
|
|
}
|
|
return
|
|
}
|
|
// emit result after all communication finish
|
|
result <- types.Result{
|
|
Status: reply2.ExecReply.Status,
|
|
ExitStatus: reply2.ExecReply.ExitStatus,
|
|
Time: reply2.ExecReply.Time,
|
|
Memory: reply2.ExecReply.Memory,
|
|
SetUpTime: mTime.Sub(sTime),
|
|
RunningTime: time.Since(mTime),
|
|
}
|
|
}()
|
|
|
|
// Kill (if wait is done, a kill message need to be send to collect zombies)
|
|
go func() {
|
|
select {
|
|
case <-c.Done():
|
|
case <-waitDone:
|
|
}
|
|
m.sendCmd(&Cmd{Cmd: cmdKill}, nil)
|
|
}()
|
|
|
|
return result
|
|
}
|
|
|
|
// execveSyncKill will send kill and recv reply
|
|
func (m *Master) execveSyncKill() {
|
|
m.sendCmd(&Cmd{Cmd: cmdKill}, nil)
|
|
m.recvReply()
|
|
}
|