python.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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(
  41. code string, timeout time.Duration, stdin []byte, preload string,
  42. ) (chan []byte, chan []byte, chan bool, error) {
  43. // create a tmp dir and copy the python script
  44. temp_code_name := strings.ReplaceAll(uuid.New().String(), "-", "_")
  45. temp_code_name = strings.ReplaceAll(temp_code_name, "/", ".")
  46. temp_code_path := fmt.Sprintf("/tmp/code/%s.py", temp_code_name)
  47. err := os.MkdirAll("/tmp/code", 0755)
  48. if err != nil {
  49. return nil, nil, nil, err
  50. }
  51. err = os.WriteFile(temp_code_path, []byte(code), 0755)
  52. if err != nil {
  53. return nil, nil, nil, err
  54. }
  55. stdout := make(chan []byte, 42)
  56. stderr := make(chan []byte, 42)
  57. write_out := func(data []byte) {
  58. stdout <- data
  59. }
  60. write_err := func(data []byte) {
  61. stderr <- data
  62. }
  63. done_chan := make(chan bool)
  64. err = p.WithTempDir([]string{
  65. temp_code_path,
  66. "/tmp/sandbox-python/python.so",
  67. }, func(root_path string) error {
  68. python_sandbox_file := string(python_sandbox_fs)
  69. if preload != "" {
  70. python_sandbox_file = fmt.Sprintf("%s\n%s", preload, python_sandbox_file)
  71. }
  72. // create a new process
  73. cmd := exec.Command(
  74. static.GetDifySandboxGlobalConfigurations().PythonPath,
  75. "-c",
  76. python_sandbox_file,
  77. temp_code_path,
  78. strconv.Itoa(static.SANDBOX_USER_UID),
  79. strconv.Itoa(static.SANDBOX_GROUP_ID),
  80. )
  81. cmd.Env = []string{}
  82. // create a pipe for the stdout
  83. stdout_reader, err := cmd.StdoutPipe()
  84. if err != nil {
  85. return err
  86. }
  87. // create a pipe for the stderr
  88. stderr_reader, err := cmd.StderrPipe()
  89. if err != nil {
  90. stdout_reader.Close()
  91. return err
  92. }
  93. // start the process
  94. err = cmd.Start()
  95. if err != nil {
  96. stdout_reader.Close()
  97. stderr_reader.Close()
  98. return err
  99. }
  100. // start a timer for the timeout
  101. timer := time.NewTimer(timeout)
  102. go func() {
  103. <-timer.C
  104. if cmd != nil && cmd.Process != nil {
  105. // write the error
  106. write_err([]byte("error: timeout\n"))
  107. // send a signal to the process
  108. cmd.Process.Kill()
  109. }
  110. }()
  111. wg := sync.WaitGroup{}
  112. wg.Add(2)
  113. // read the output
  114. go func() {
  115. defer wg.Done()
  116. for {
  117. buf := make([]byte, 1024)
  118. n, err := stdout_reader.Read(buf)
  119. // exit if EOF
  120. if err != nil {
  121. if err == io.EOF {
  122. break
  123. } else {
  124. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  125. break
  126. }
  127. }
  128. write_out(buf[:n])
  129. }
  130. }()
  131. // read the error
  132. go func() {
  133. buf := make([]byte, 1024)
  134. defer wg.Done()
  135. for {
  136. n, err := stderr_reader.Read(buf)
  137. // exit if EOF
  138. if err != nil {
  139. if err == io.EOF {
  140. break
  141. } else {
  142. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  143. break
  144. }
  145. }
  146. write_err(buf[:n])
  147. }
  148. }()
  149. // wait for the process to finish
  150. go func() {
  151. status, err := cmd.Process.Wait()
  152. if err != nil {
  153. log.Error("process finished with status: %v", status.String())
  154. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  155. } else if status.ExitCode() != 0 {
  156. exit_string := status.String()
  157. if strings.Contains(exit_string, "bad system call") {
  158. write_err([]byte("error: operation not permitted\n"))
  159. } else {
  160. write_err([]byte(fmt.Sprintf("error: %v\n", exit_string)))
  161. }
  162. }
  163. // wait for the stdout and stderr to finish
  164. wg.Wait()
  165. stderr_reader.Close()
  166. stdout_reader.Close()
  167. os.Remove(temp_code_path)
  168. os.RemoveAll(root_path)
  169. os.Remove(root_path)
  170. timer.Stop()
  171. done_chan <- true
  172. }()
  173. return nil
  174. })
  175. if err != nil {
  176. return nil, nil, nil, err
  177. }
  178. return stdout, stderr, done_chan, nil
  179. }