github.com/sdibtacm/sandbox@v0.0.0-20200320120712-60470cf803dc/exec/exec.go (about)

     1  //+build linux
     2  
     3  package exec
     4  
     5  import "C"
     6  import (
     7  	"context"
     8  	"errors"
     9  	"github.com/boxjan/golib/logs"
    10  	"github.com/sdibtacm/sandbox/exec/log"
    11  	"github.com/sdibtacm/sandbox/exec/scmpFilter"
    12  	"io"
    13  	"os"
    14  	"os/signal"
    15  	"syscall"
    16  	"time"
    17  	"unsafe"
    18  )
    19  
    20  // skipStdinCopyError optionally specifies a function which reports
    21  // whether the provided stdin copy error should be ignored.
    22  // It is non-nil everywhere but Plan 9, which lacks EPIPE. See exec_posix.go.
    23  var skipStdinCopyError func(error) bool
    24  
    25  func init() {
    26  	skipStdinCopyError = func(err error) bool {
    27  		// Ignore EPIPE errors copying to stdin if the program
    28  		// completed successfully otherwise.
    29  		// See Issue 9173.
    30  		pe, ok := err.(*os.PathError)
    31  		return ok &&
    32  			pe.Op == "write" && pe.Path == "|1" &&
    33  			pe.Err == syscall.EPIPE
    34  	}
    35  }
    36  
    37  type Cmd struct {
    38  	Path   string   // run command path
    39  	Args   []string // run command args
    40  	Envs   []string
    41  	Chroot string
    42  	Chdir  string
    43  
    44  	Stdin  io.Reader
    45  	Stdout io.Writer
    46  	Stderr io.Writer
    47  
    48  	ResourceLimit    Resource
    49  	resourceMaxStats Resource
    50  	resourceStats    Resource
    51  	Sys              *SysAttr
    52  	Syscall          *SyscallLimit
    53  	Process          *Process
    54  	ProcessState     *ProcessState
    55  
    56  	finished        bool // when Wait was called
    57  	ctx             context.Context
    58  	ctxCancel       context.CancelFunc
    59  	closeAfterStart []io.Closer
    60  	closeAfterWait  []io.Closer
    61  	childFiles      []*os.File
    62  	goroutine       []func() error
    63  	errch           chan error // one send per goroutine
    64  	sigchan         chan os.Signal
    65  	waitDone        chan struct{}
    66  	pt              *ptrace
    67  
    68  	startTimestamp time.Time
    69  	endTimestamp   time.Time
    70  }
    71  
    72  type Result struct {
    73  	CpuTime    uint
    74  	ClockTime  uint
    75  	MemoryUsed uint64
    76  	ExitStatus syscall.WaitStatus
    77  	ExitCode   int
    78  	Exceed     int
    79  	HelpStr    string
    80  }
    81  
    82  type Resource struct {
    83  	CpuTime   uint
    84  	ClockTime uint
    85  	Memory    uint64
    86  	Output    uint64
    87  	Thread    uint
    88  }
    89  
    90  type SyscallLimit struct {
    91  	Level  int
    92  	Action int
    93  	Helper string
    94  }
    95  
    96  func SetLogger(logger *logs.Logger) {
    97  	log.SetLog(logger)
    98  }
    99  
   100  func findExecutable(name string) (string, error) {
   101  	if err := executable(name); err == nil {
   102  		return name, nil
   103  	}
   104  	if lp, err := LookPath(name); err != nil {
   105  		return "", err
   106  	} else {
   107  		return lp, nil
   108  	}
   109  }
   110  
   111  func Command(name string, args ...string) *Cmd {
   112  	return CommandNotSameProgramName(name, name, args...)
   113  }
   114  
   115  func CommandNotSameProgramName(command string, name string, args ...string) *Cmd {
   116  	cmd := &Cmd{}
   117  	cmd.Path = command
   118  	cmd.Args = append([]string{name}, args...)
   119  	return cmd
   120  }
   121  
   122  func (c *Cmd) envv() []string {
   123  	if c.Envs != nil {
   124  		return c.Envs
   125  	}
   126  	return os.Environ()
   127  }
   128  
   129  func (c *Cmd) argv() []string {
   130  	if len(c.Args) > 0 {
   131  		return c.Args
   132  	}
   133  	return []string{c.Path}
   134  }
   135  
   136  // interfaceEqual protects against panics from doing equality tests on
   137  // two interfaces with non-comparable underlying types.
   138  func interfaceEqual(a, b interface{}) bool {
   139  	defer func() {
   140  		recover()
   141  	}()
   142  	return a == b
   143  }
   144  
   145  func (c *Cmd) stdin() (f *os.File, err error) {
   146  	if c.Stdin == nil {
   147  		f, err = os.Open(os.DevNull)
   148  		if err != nil {
   149  			return
   150  		}
   151  		c.closeAfterStart = append(c.closeAfterStart, f)
   152  		return
   153  	}
   154  
   155  	if f, ok := c.Stdin.(*os.File); ok {
   156  		return f, nil
   157  	}
   158  
   159  	pr, pw, err := os.Pipe()
   160  	if err != nil {
   161  		return
   162  	}
   163  
   164  	c.closeAfterStart = append(c.closeAfterStart, pr)
   165  	c.closeAfterWait = append(c.closeAfterWait, pw)
   166  	c.goroutine = append(c.goroutine, func() error {
   167  		_, err := io.Copy(pw, c.Stdin)
   168  		if skip := skipStdinCopyError; skip != nil && skip(err) {
   169  			err = nil
   170  		}
   171  		if err1 := pw.Close(); err == nil {
   172  			err = err1
   173  		}
   174  		return err
   175  	})
   176  	return pr, nil
   177  }
   178  
   179  func (c *Cmd) stdout() (f *os.File, err error) {
   180  	return c.writerDescriptor(c.Stdout)
   181  }
   182  
   183  func (c *Cmd) stderr() (f *os.File, err error) {
   184  	if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
   185  		return c.childFiles[1], nil
   186  	}
   187  	return c.writerDescriptor(c.Stderr)
   188  }
   189  
   190  func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
   191  	if w == nil {
   192  		f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
   193  		if err != nil {
   194  			return
   195  		}
   196  		c.closeAfterStart = append(c.closeAfterStart, f)
   197  		return
   198  	}
   199  
   200  	if f, ok := w.(*os.File); ok {
   201  		return f, nil
   202  	}
   203  
   204  	pr, pw, err := os.Pipe()
   205  	if err != nil {
   206  		return
   207  	}
   208  
   209  	c.closeAfterStart = append(c.closeAfterStart, pw)
   210  	c.closeAfterWait = append(c.closeAfterWait, pr)
   211  	c.goroutine = append(c.goroutine, func() error {
   212  		_, err := io.Copy(w, pr)
   213  		pr.Close() // in case io.Copy stopped due to write error
   214  		return err
   215  	})
   216  	return pw, nil
   217  }
   218  
   219  func (c *Cmd) closeDescriptors(closers []io.Closer) {
   220  	for _, fd := range closers {
   221  		fd.Close()
   222  	}
   223  }
   224  
   225  func (c *Cmd) Start() error {
   226  	execPath, err := findExecutable(c.Path)
   227  	if err != nil {
   228  		log.GetLog().Warning("{} can not exec", c.Path)
   229  		return err
   230  	}
   231  	c.Path = execPath
   232  
   233  	if c.Process != nil {
   234  		return errors.New("exec: already started")
   235  	}
   236  
   237  	c.childFiles = make([]*os.File, 0, 3)
   238  	type F func(*Cmd) (*os.File, error)
   239  	for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
   240  		fd, err := setupFd(c)
   241  		if err != nil {
   242  			log.GetLog().Error("fail to get os.File struct with error: {}", err)
   243  			c.closeDescriptors(c.closeAfterStart)
   244  			c.closeDescriptors(c.closeAfterWait)
   245  			return err
   246  		}
   247  		c.childFiles = append(c.childFiles, fd)
   248  	}
   249  
   250  	// set cpu time and clock time
   251  	if c.Sys.RlimitList[RLIMIT_CPU] == RLIMIT_UNRESOURCE && c.ResourceLimit.CpuTime != TIME_UNRESOURCE {
   252  		log.GetLog().Debug("cpu time limit will be set {}s", uint64(c.ResourceLimit.CpuTime/1000+1))
   253  		c.Sys.RlimitList[RLIMIT_CPU] = uint64(c.ResourceLimit.CpuTime/1000 + 1)
   254  		if c.ResourceLimit.ClockTime == TIME_UNRESOURCE {
   255  			log.GetLog().Info("Have set cpu time, but not set clock time, clock time will be set {}ms, to keep safe", c.ResourceLimit.CpuTime*10)
   256  			if uint64(c.ResourceLimit.CpuTime*10) > uint64(MAX_TIME) {
   257  				c.ResourceLimit.ClockTime = MAX_TIME
   258  			} else {
   259  				c.ResourceLimit.ClockTime = c.ResourceLimit.CpuTime * 10
   260  			}
   261  		}
   262  		if c.ResourceLimit.ClockTime < c.ResourceLimit.CpuTime {
   263  			log.GetLog().Info("clock time limit is small than cpu time, will change clock time to", c.ResourceLimit.CpuTime+1)
   264  			if uint64(c.ResourceLimit.CpuTime+1) > uint64(MAX_TIME) {
   265  				c.ResourceLimit.ClockTime = MAX_TIME
   266  			} else {
   267  				c.ResourceLimit.ClockTime = c.ResourceLimit.CpuTime
   268  			}
   269  		}
   270  	}
   271  
   272  	if c.Sys.RlimitList[RLIMIT_FSIZE] == RLIMIT_UNRESOURCE && c.ResourceLimit.Output != BYTE_UNRESOURCE {
   273  		c.Sys.RlimitList[RLIMIT_FSIZE] = c.ResourceLimit.Output + 1
   274  	}
   275  
   276  	log.GetLog().Debug("will start process")
   277  	c.Process, err = c.startProcess()
   278  	if err != nil {
   279  		log.GetLog().Error("start process fail with error: {}", err)
   280  		c.closeDescriptors(c.closeAfterStart)
   281  		c.closeDescriptors(c.closeAfterWait)
   282  		return err
   283  	}
   284  
   285  	go c.SentSig()
   286  	c.startTimestamp = time.Now()
   287  	if c.ResourceLimit.ClockTime != TIME_UNRESOURCE {
   288  		c.ctx, c.ctxCancel = context.WithTimeout(context.Background(), time.Duration(c.ResourceLimit.ClockTime)*time.Millisecond)
   289  	}
   290  	c.closeDescriptors(c.closeAfterStart)
   291  
   292  	go c.limiter()
   293  	// Don't allocate the channel unless there are goroutines to fire.
   294  	if len(c.goroutine) > 0 {
   295  		c.errch = make(chan error, len(c.goroutine))
   296  		for _, fn := range c.goroutine {
   297  			go func(fn func() error) {
   298  				c.errch <- fn()
   299  			}(fn)
   300  		}
   301  	}
   302  
   303  	if c.ctx != nil {
   304  		c.waitDone = make(chan struct{})
   305  		go func() {
   306  			select {
   307  			case <-c.ctx.Done():
   308  				_ = c.Process.KillGroup()
   309  			case <-c.waitDone:
   310  			}
   311  		}()
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  func (c *Cmd) NowUsed() Resource {
   318  	return c.resourceStats
   319  }
   320  
   321  func (c *Cmd) Wait() error {
   322  	if c.Process == nil {
   323  		return errors.New("exec: not started")
   324  	}
   325  	if c.finished {
   326  		return errors.New("exec: Wait was already called")
   327  	}
   328  	c.finished = true
   329  
   330  	state, err, pt := c.wait()
   331  	c.endTimestamp = time.Now()
   332  	if c.ctxCancel != nil {
   333  		c.ctxCancel()
   334  	}
   335  
   336  	if err != nil {
   337  		return err
   338  	}
   339  	if c.waitDone != nil {
   340  		close(c.waitDone)
   341  	}
   342  	c.ProcessState = state
   343  	c.pt = pt
   344  
   345  	var copyError error
   346  	for range c.goroutine {
   347  		if err := <-c.errch; err != nil && copyError == nil {
   348  			copyError = err
   349  		}
   350  	}
   351  
   352  	c.closeDescriptors(c.closeAfterWait)
   353  
   354  	if err != nil {
   355  		return err
   356  	}
   357  
   358  	return copyError
   359  }
   360  
   361  func (c *Cmd) Result() *Result {
   362  
   363  	r := &Result{
   364  		CpuTime:    uint(c.ProcessState.rusage.Utime.Nano()+c.ProcessState.rusage.Stime.Nano()) / 1e6,
   365  		ClockTime:  uint(c.endTimestamp.UnixNano()-c.startTimestamp.UnixNano()) / 1e6,
   366  		MemoryUsed: c.resourceStats.Memory,
   367  		ExitStatus: c.ProcessState.status,
   368  		ExitCode:   c.ProcessState.ExitCode(),
   369  	}
   370  
   371  	return r
   372  }
   373  
   374  func (c *Cmd) Run() error {
   375  	if err := c.Start(); err != nil {
   376  		return err
   377  	}
   378  	return c.Wait()
   379  }
   380  
   381  func (c *Cmd) startProcess() (*Process, error) {
   382  
   383  	path0, err := syscall.BytePtrFromString(c.Path)
   384  	if err != nil {
   385  		return nil, err
   386  	}
   387  	argsp, err := syscall.SlicePtrFromStrings(c.Args)
   388  	if err != nil {
   389  		return nil, err
   390  	}
   391  	if len(c.Envs) == 0 {
   392  		c.Envs = os.Environ()
   393  	}
   394  	envsp, err := syscall.SlicePtrFromStrings(c.Envs)
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  	var chroot *byte
   399  	if c.Chroot != "" {
   400  		chroot, err = syscall.BytePtrFromString(c.Chroot)
   401  		if err != nil {
   402  			return nil, err
   403  		}
   404  	}
   405  	var chdir *byte
   406  	if c.Chdir != "" {
   407  		chdir, err = syscall.BytePtrFromString(c.Chdir)
   408  		if err != nil {
   409  			return nil, err
   410  		}
   411  	}
   412  	var attr *SysAttr
   413  	if c.Sys == nil {
   414  		attr = &SysAttr{}
   415  	} else {
   416  		attr = c.Sys
   417  	}
   418  
   419  	if len(c.childFiles) != 0 {
   420  		attr.Files = make([]uintptr, 0, len(c.childFiles))
   421  		for _, f := range c.childFiles {
   422  			attr.Files = append(attr.Files, f.Fd())
   423  		}
   424  	}
   425  
   426  	if c.Syscall != nil && c.Syscall.Helper != "" && c.Syscall.Level != 0 {
   427  		scmpHelper := &scmpFilter.ScmpFilterLoadHelper{ExecvePathPointer: unsafe.Pointer(path0), Action: scmpFilter.ScmpAction(c.Syscall.Action)}
   428  		if c.Syscall.Helper != "" {
   429  			scmpHelper.LrunScmpFilter = c.Syscall.Helper
   430  			scmpHelper.Level = -1
   431  		} else {
   432  			scmpHelper.Level = c.Syscall.Level
   433  		}
   434  		filter, err := scmpFilter.GetScmpFilter(scmpHelper)
   435  		if err != nil {
   436  			return nil, err
   437  		}
   438  		attr.Bpf = filter.BPF
   439  		attr.Ptrace = filter.SetPrivs
   440  	}
   441  
   442  	pid, err := forkExec(path0, argsp, envsp, chroot, chdir, attr)
   443  	if err != nil {
   444  		log.GetLog().Error("exec fail with error: {}", err.Error())
   445  		return nil, errors.New(err.Error())
   446  	}
   447  	return newProcess(pid, 0), nil
   448  }
   449  
   450  func (c *Cmd) SentSig() {
   451  	c.sigchan = make(chan os.Signal)
   452  	signal.Notify(c.sigchan, syscall.SIGINT, syscall.SIGTERM)
   453  
   454  	for sig, ok := <-c.sigchan; (!c.Process.Done()) && ok; {
   455  		err := c.Process.Signal(sig)
   456  		if err != nil {
   457  			log.GetLog().Warning("sent sig meet error: {}", err)
   458  		}
   459  	}
   460  	signal.Stop(c.sigchan)
   461  	close(c.sigchan)
   462  	c.sigchan = nil
   463  }