You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

127 lines
2.9 KiB

  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package process
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "os/exec"
  10. "time"
  11. "github.com/gogits/gogs/modules/log"
  12. )
  13. var (
  14. ErrExecTimeout = errors.New("Process execution timeout")
  15. )
  16. // Common timeout.
  17. var (
  18. // NOTE: could be custom in config file for default.
  19. DEFAULT = 60 * time.Second
  20. )
  21. // Process represents a working process inherit from Gogs.
  22. type Process struct {
  23. Pid int64 // Process ID, not system one.
  24. Description string
  25. Start time.Time
  26. Cmd *exec.Cmd
  27. }
  28. // List of existing processes.
  29. var (
  30. curPid int64 = 1
  31. Processes []*Process
  32. )
  33. // Add adds a existing process and returns its PID.
  34. func Add(desc string, cmd *exec.Cmd) int64 {
  35. pid := curPid
  36. Processes = append(Processes, &Process{
  37. Pid: pid,
  38. Description: desc,
  39. Start: time.Now(),
  40. Cmd: cmd,
  41. })
  42. curPid++
  43. return pid
  44. }
  45. // Exec starts executing a command in given path, it records its process and timeout.
  46. func ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
  47. if timeout == -1 {
  48. timeout = DEFAULT
  49. }
  50. bufOut := new(bytes.Buffer)
  51. bufErr := new(bytes.Buffer)
  52. cmd := exec.Command(cmdName, args...)
  53. cmd.Dir = dir
  54. cmd.Stdout = bufOut
  55. cmd.Stderr = bufErr
  56. if err := cmd.Start(); err != nil {
  57. return "", err.Error(), err
  58. }
  59. pid := Add(desc, cmd)
  60. done := make(chan error)
  61. go func() {
  62. done <- cmd.Wait()
  63. }()
  64. var err error
  65. select {
  66. case <-time.After(timeout):
  67. if errKill := Kill(pid); errKill != nil {
  68. log.Error(4, "Exec(%d:%s): %v", pid, desc, errKill)
  69. }
  70. <-done
  71. return "", ErrExecTimeout.Error(), ErrExecTimeout
  72. case err = <-done:
  73. }
  74. Remove(pid)
  75. return bufOut.String(), bufErr.String(), err
  76. }
  77. // Exec starts executing a command, it records its process and timeout.
  78. func ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) {
  79. return ExecDir(timeout, "", desc, cmdName, args...)
  80. }
  81. // Exec starts executing a command, it records its process and has default timeout.
  82. func Exec(desc, cmdName string, args ...string) (string, string, error) {
  83. return ExecDir(-1, "", desc, cmdName, args...)
  84. }
  85. // Remove removes a process from list.
  86. func Remove(pid int64) {
  87. for i, proc := range Processes {
  88. if proc.Pid == pid {
  89. Processes = append(Processes[:i], Processes[i+1:]...)
  90. return
  91. }
  92. }
  93. }
  94. // Kill kills and removes a process from list.
  95. func Kill(pid int64) error {
  96. for i, proc := range Processes {
  97. if proc.Pid == pid {
  98. if proc.Cmd != nil && proc.Cmd.Process != nil &&
  99. proc.Cmd.ProcessState != nil && !proc.Cmd.ProcessState.Exited() {
  100. if err := proc.Cmd.Process.Kill(); err != nil {
  101. return fmt.Errorf("fail to kill process(%d/%s): %v", proc.Pid, proc.Description, err)
  102. }
  103. }
  104. Processes = append(Processes[:i], Processes[i+1:]...)
  105. return nil
  106. }
  107. }
  108. return nil
  109. }