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.

142 lines
3.8 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. "code.gitea.io/gitea/modules/log"
  12. )
  13. var (
  14. // ErrExecTimeout represent a timeout error
  15. ErrExecTimeout = errors.New("Process execution timeout")
  16. // DefaultTimeout is the timeout used by Exec* family
  17. // of function when timeout parameter is omitted or
  18. // passed as -1
  19. // NOTE: could be custom in config file for default.
  20. DefaultTimeout = 60 * time.Second
  21. )
  22. // Process represents a working process inherit from Gogs.
  23. type Process struct {
  24. Pid int64 // Process ID, not system one.
  25. Description string
  26. Start time.Time
  27. Cmd *exec.Cmd
  28. }
  29. // List of existing processes.
  30. var (
  31. curPid int64 = 1
  32. Processes []*Process
  33. )
  34. // Add adds a existing process and returns its PID.
  35. func Add(desc string, cmd *exec.Cmd) int64 {
  36. pid := curPid
  37. Processes = append(Processes, &Process{
  38. Pid: pid,
  39. Description: desc,
  40. Start: time.Now(),
  41. Cmd: cmd,
  42. })
  43. curPid++
  44. return pid
  45. }
  46. // ExecDirEnv runs a command in given path and environment variables, and waits for its completion
  47. // up to the given timeout (or DefaultTimeout if -1 is given).
  48. // Returns its complete stdout and stderr
  49. // outputs and an error, if any (including timeout)
  50. func ExecDirEnv(timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) {
  51. if timeout == -1 {
  52. timeout = DefaultTimeout
  53. }
  54. bufOut := new(bytes.Buffer)
  55. bufErr := new(bytes.Buffer)
  56. cmd := exec.Command(cmdName, args...)
  57. cmd.Dir = dir
  58. cmd.Env = env
  59. cmd.Stdout = bufOut
  60. cmd.Stderr = bufErr
  61. if err := cmd.Start(); err != nil {
  62. return "", err.Error(), err
  63. }
  64. pid := Add(desc, cmd)
  65. done := make(chan error)
  66. go func() {
  67. done <- cmd.Wait()
  68. }()
  69. var err error
  70. select {
  71. case <-time.After(timeout):
  72. if errKill := Kill(pid); errKill != nil {
  73. log.Error(4, "Exec(%d:%s): %v", pid, desc, errKill)
  74. }
  75. <-done
  76. return "", ErrExecTimeout.Error(), ErrExecTimeout
  77. case err = <-done:
  78. }
  79. Remove(pid)
  80. return bufOut.String(), bufErr.String(), err
  81. }
  82. // ExecDir works exactly like ExecDirEnv except no environment variable is provided.
  83. func ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
  84. return ExecDirEnv(timeout, dir, desc, nil, cmdName, args...)
  85. }
  86. // ExecTimeout runs a command and waits for its completion
  87. // up to the given timeout (or DefaultTimeout if -1 is given).
  88. // Returns its complete stdout and stderr
  89. // outputs and an error, if any (including timeout)
  90. func ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) {
  91. return ExecDir(timeout, "", desc, cmdName, args...)
  92. }
  93. // Exec runs a command and waits for its completion
  94. // up to DefaultTimeout. Returns its complete stdout and stderr
  95. // outputs and an error, if any (including timeout)
  96. func Exec(desc, cmdName string, args ...string) (string, string, error) {
  97. return ExecDir(-1, "", desc, cmdName, args...)
  98. }
  99. // Remove removes a process from list.
  100. func Remove(pid int64) {
  101. for i, proc := range Processes {
  102. if proc.Pid == pid {
  103. Processes = append(Processes[:i], Processes[i+1:]...)
  104. return
  105. }
  106. }
  107. }
  108. // Kill kills and removes a process from list.
  109. func Kill(pid int64) error {
  110. for i, proc := range Processes {
  111. if proc.Pid == pid {
  112. if proc.Cmd != nil && proc.Cmd.Process != nil &&
  113. proc.Cmd.ProcessState != nil && !proc.Cmd.ProcessState.Exited() {
  114. if err := proc.Cmd.Process.Kill(); err != nil {
  115. return fmt.Errorf("fail to kill process(%d/%s): %v", proc.Pid, proc.Description, err)
  116. }
  117. }
  118. Processes = append(Processes[:i], Processes[i+1:]...)
  119. return nil
  120. }
  121. }
  122. return nil
  123. }