python.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package python
  2. import (
  3. _ "embed"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "strconv"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/google/uuid"
  13. "github.com/langgenius/dify-sandbox/internal/core/runner"
  14. "github.com/langgenius/dify-sandbox/internal/static"
  15. "github.com/langgenius/dify-sandbox/internal/utils/log"
  16. )
  17. type PythonRunner struct {
  18. runner.Runner
  19. runner.SeccompRunner
  20. }
  21. //go:embed prescript.py
  22. var python_sandbox_fs []byte
  23. //go:embed python.so
  24. var python_lib []byte
  25. func init() {
  26. log.Info("initializing python runner environment...")
  27. // remove /tmp/sandbox-python
  28. os.RemoveAll("/tmp/sandbox-python")
  29. os.Remove("/tmp/sandbox-python")
  30. err := os.MkdirAll("/tmp/sandbox-python", 0755)
  31. if err != nil {
  32. log.Panic("failed to create /tmp/sandbox-python")
  33. }
  34. err = os.WriteFile("/tmp/sandbox-python/python.so", python_lib, 0755)
  35. if err != nil {
  36. log.Panic("failed to write /tmp/sandbox-python/python.so")
  37. }
  38. log.Info("python runner environment initialized")
  39. }
  40. func (p *PythonRunner) Run(code string, timeout time.Duration, stdin []byte) (chan []byte, chan []byte, chan bool, error) {
  41. // create a tmp dir and copy the python script
  42. temp_code_name := strings.ReplaceAll(uuid.New().String(), "-", "_")
  43. temp_code_name = strings.ReplaceAll(temp_code_name, "/", ".")
  44. temp_code_path := fmt.Sprintf("/tmp/code/%s.py", temp_code_name)
  45. err := os.MkdirAll("/tmp/code", 0755)
  46. if err != nil {
  47. return nil, nil, nil, err
  48. }
  49. err = os.WriteFile(temp_code_path, []byte(code), 0755)
  50. if err != nil {
  51. return nil, nil, nil, err
  52. }
  53. stdout := make(chan []byte, 42)
  54. stderr := make(chan []byte, 42)
  55. write_out := func(data []byte) {
  56. stdout <- data
  57. }
  58. write_err := func(data []byte) {
  59. stderr <- data
  60. }
  61. done_chan := make(chan bool)
  62. err = p.WithTempDir([]string{
  63. temp_code_path,
  64. "/tmp/sandbox-python/python.so",
  65. }, func(root_path string) error {
  66. // create a new process
  67. cmd := exec.Command(
  68. static.GetDifySandboxGlobalConfigurations().PythonPath,
  69. "-c",
  70. string(python_sandbox_fs),
  71. temp_code_path,
  72. strconv.Itoa(static.SANDBOX_USER_UID),
  73. strconv.Itoa(static.SANDBOX_GROUP_ID),
  74. )
  75. cmd.Env = []string{}
  76. // create a pipe for the stdout
  77. stdout_reader, err := cmd.StdoutPipe()
  78. if err != nil {
  79. return err
  80. }
  81. // create a pipe for the stderr
  82. stderr_reader, err := cmd.StderrPipe()
  83. if err != nil {
  84. stdout_reader.Close()
  85. return err
  86. }
  87. // start the process
  88. err = cmd.Start()
  89. if err != nil {
  90. stdout_reader.Close()
  91. stderr_reader.Close()
  92. return err
  93. }
  94. // start a timer for the timeout
  95. timer := time.NewTimer(timeout)
  96. go func() {
  97. <-timer.C
  98. if cmd != nil && cmd.Process != nil {
  99. // write the error
  100. write_err([]byte("error: timeout\n"))
  101. // send a signal to the process
  102. cmd.Process.Kill()
  103. }
  104. }()
  105. wg := sync.WaitGroup{}
  106. wg.Add(2)
  107. // read the output
  108. go func() {
  109. defer wg.Done()
  110. for {
  111. buf := make([]byte, 1024)
  112. n, err := stdout_reader.Read(buf)
  113. // exit if EOF
  114. if err != nil {
  115. if err == io.EOF {
  116. break
  117. } else {
  118. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  119. break
  120. }
  121. }
  122. write_out(buf[:n])
  123. }
  124. }()
  125. // read the error
  126. go func() {
  127. buf := make([]byte, 1024)
  128. defer wg.Done()
  129. for {
  130. n, err := stderr_reader.Read(buf)
  131. // exit if EOF
  132. if err != nil {
  133. if err == io.EOF {
  134. break
  135. } else {
  136. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  137. break
  138. }
  139. }
  140. write_err(buf[:n])
  141. }
  142. }()
  143. // wait for the process to finish
  144. go func() {
  145. status, err := cmd.Process.Wait()
  146. if err != nil {
  147. log.Error("process finished with status: %v", status.String())
  148. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  149. } else if status.ExitCode() != 0 {
  150. exit_string := status.String()
  151. if strings.Contains(exit_string, "bad system call") {
  152. write_err([]byte("error: operation not permitted\n"))
  153. } else {
  154. write_err([]byte(fmt.Sprintf("error: %v\n", exit_string)))
  155. }
  156. }
  157. // wait for the stdout and stderr to finish
  158. wg.Wait()
  159. stderr_reader.Close()
  160. stdout_reader.Close()
  161. os.Remove(temp_code_path)
  162. os.RemoveAll(root_path)
  163. os.Remove(root_path)
  164. timer.Stop()
  165. done_chan <- true
  166. }()
  167. return nil
  168. })
  169. if err != nil {
  170. return nil, nil, nil, err
  171. }
  172. return stdout, stderr, done_chan, nil
  173. }