python.go 3.7 KB

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