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 }