github.com/criyle/go-sandbox@v0.10.3/container/host_exec_linux.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/criyle/go-sandbox/pkg/rlimit"
     9  	"github.com/criyle/go-sandbox/pkg/seccomp"
    10  	"github.com/criyle/go-sandbox/pkg/unixsocket"
    11  	"github.com/criyle/go-sandbox/runner"
    12  )
    13  
    14  // ExecveParam is parameters to run process inside container
    15  type ExecveParam struct {
    16  	// Args holds command line arguments
    17  	Args []string
    18  
    19  	// Env specifies the environment of the process
    20  	Env []string
    21  
    22  	// Files specifies file descriptors for the child process
    23  	Files []uintptr
    24  
    25  	// ExecFile specifies file descriptor for executable file using fexecve
    26  	ExecFile uintptr
    27  
    28  	// RLimits specifies POSIX Resource limit through setrlimit
    29  	RLimits []rlimit.RLimit
    30  
    31  	// Seccomp specifies seccomp filter
    32  	Seccomp seccomp.Filter
    33  
    34  	// CTTY specifies whether to set controlling TTY
    35  	CTTY bool
    36  
    37  	// SyncFunc calls with pid just before execve (for attach the process to cgroups)
    38  	SyncFunc func(pid int) error
    39  }
    40  
    41  // Execve runs process inside container. It accepts context cancelation as time limit exceeded.
    42  func (c *container) Execve(ctx context.Context, param ExecveParam) runner.Result {
    43  	c.mu.Lock()
    44  	defer c.mu.Unlock()
    45  
    46  	sTime := time.Now()
    47  
    48  	// if execve with fd, put fd at the first parameter
    49  	var files []int
    50  	if param.ExecFile > 0 {
    51  		files = append(files, int(param.ExecFile))
    52  	}
    53  	files = append(files, uintptrSliceToInt(param.Files)...)
    54  	msg := unixsocket.Msg{
    55  		Fds: files,
    56  	}
    57  	execCmd := &execCmd{
    58  		Argv:    param.Args,
    59  		Env:     param.Env,
    60  		RLimits: param.RLimits,
    61  		Seccomp: param.Seccomp,
    62  		FdExec:  param.ExecFile > 0,
    63  		CTTY:    param.CTTY,
    64  	}
    65  	cm := cmd{
    66  		Cmd:     cmdExecve,
    67  		ExecCmd: execCmd,
    68  	}
    69  	if err := c.sendCmd(cm, msg); err != nil {
    70  		return errResult("execve: sendCmd %v", err)
    71  	}
    72  	// sync function
    73  	rep, msg, err := c.recvReply()
    74  	if err != nil {
    75  		return errResult("execve: recvReply %v", err)
    76  	}
    77  	// if sync function did not involved
    78  	if rep.Error != nil {
    79  		return errResult("execve: %v", rep.Error)
    80  	}
    81  	// if pid not received
    82  	if msg.Cred == nil {
    83  		// tell kill function to exit and sync
    84  		c.execveSyncKill()
    85  		// tell err exec function to exit and sync
    86  		c.execveSyncKill()
    87  		return errResult("execve: no pid received")
    88  	}
    89  	if param.SyncFunc != nil {
    90  		if err := param.SyncFunc(int(msg.Cred.Pid)); err != nil {
    91  			// tell sync function to exit and recv error
    92  			c.execveSyncKill()
    93  			// tell kill function to exit and sync
    94  			c.execveSyncKill()
    95  			return errResult("execve: syncfunc failed %v", err)
    96  		}
    97  	}
    98  	// send to syncFunc ack ok
    99  	if err := c.sendCmd(cmd{Cmd: cmdOk}, unixsocket.Msg{}); err != nil {
   100  		return errResult("execve: ack failed %v", err)
   101  	}
   102  
   103  	// wait for done
   104  	return c.waitForDone(ctx, sTime)
   105  }
   106  
   107  func (c *container) waitForDone(ctx context.Context, sTime time.Time) runner.Result {
   108  	mTime := time.Now()
   109  	select {
   110  	case <-c.done: // socket error
   111  		return convertReplyResult(reply{}, sTime, mTime, c.err)
   112  
   113  	case <-ctx.Done(): // cancel
   114  		c.sendCmd(cmd{Cmd: cmdKill}, unixsocket.Msg{}) // kill
   115  		reply, _, _ := c.recvReply()
   116  		_, _, err := c.recvReply()
   117  		return convertReplyResult(reply, sTime, mTime, err)
   118  
   119  	case ret := <-c.recvCh: // result
   120  		c.sendCmd(cmd{Cmd: cmdKill}, unixsocket.Msg{}) // kill
   121  		_, _, err := c.recvReply()
   122  		return convertReplyResult(ret.Reply, sTime, mTime, err)
   123  	}
   124  }
   125  
   126  func convertReplyResult(reply reply, sTime, mTime time.Time, err error) runner.Result {
   127  	// handle potential error
   128  	if err != nil {
   129  		return runner.Result{
   130  			Status: runner.StatusRunnerError,
   131  			Error:  err.Error(),
   132  		}
   133  	}
   134  	if reply.Error != nil {
   135  		return runner.Result{
   136  			Status: runner.StatusRunnerError,
   137  			Error:  reply.Error.Error(),
   138  		}
   139  	}
   140  	if reply.ExecReply == nil {
   141  		return runner.Result{
   142  			Status: runner.StatusRunnerError,
   143  			Error:  "execve: no reply received",
   144  		}
   145  	}
   146  	// emit result after all communication finish
   147  	return runner.Result{
   148  		Status:      reply.ExecReply.Status,
   149  		ExitStatus:  reply.ExecReply.ExitStatus,
   150  		Time:        reply.ExecReply.Time,
   151  		Memory:      reply.ExecReply.Memory,
   152  		SetUpTime:   mTime.Sub(sTime),
   153  		RunningTime: time.Since(mTime),
   154  	}
   155  }
   156  
   157  // execveSyncKill will send kill and recv reply
   158  func (c *container) execveSyncKill() {
   159  	c.sendCmd(cmd{Cmd: cmdKill}, unixsocket.Msg{})
   160  	c.recvReply()
   161  }
   162  
   163  func errResult(f string, v ...interface{}) runner.Result {
   164  	return runner.Result{
   165  		Status: runner.StatusRunnerError,
   166  		Error:  fmt.Sprintf(f, v...),
   167  	}
   168  }