python.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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 init() {
  27. log.Info("initializing python runner environment...")
  28. // remove /tmp/sandbox-python
  29. os.RemoveAll("/tmp/sandbox-python")
  30. os.Remove("/tmp/sandbox-python")
  31. err := os.MkdirAll("/tmp/sandbox-python", 0755)
  32. if err != nil {
  33. log.Panic("failed to create /tmp/sandbox-python")
  34. }
  35. err = os.WriteFile("/tmp/sandbox-python/python.so", python_lib, 0755)
  36. if err != nil {
  37. log.Panic("failed to write /tmp/sandbox-python/python.so")
  38. }
  39. log.Info("python runner environment initialized")
  40. }
  41. func (p *PythonRunner) Run(code string, timeout time.Duration, stdin []byte) (chan []byte, chan []byte, chan bool, error) {
  42. // create a tmp dir and copy the python script
  43. temp_code_name := strings.ReplaceAll(uuid.New().String(), "-", "_")
  44. temp_code_name = strings.ReplaceAll(temp_code_name, "/", ".")
  45. temp_code_path := fmt.Sprintf("/tmp/code/%s.py", temp_code_name)
  46. err := os.MkdirAll("/tmp/code", 0755)
  47. if err != nil {
  48. return nil, nil, nil, err
  49. }
  50. err = os.WriteFile(temp_code_path, []byte(code), 0755)
  51. if err != nil {
  52. return nil, nil, nil, err
  53. }
  54. stdout := make(chan []byte)
  55. stderr := make(chan []byte)
  56. done_chan := make(chan bool)
  57. err = p.WithTempDir([]string{
  58. temp_code_path,
  59. "/tmp/sandbox-python/python.so",
  60. }, func(root_path string) error {
  61. // create a new process
  62. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  63. cmd := exec.CommandContext(ctx,
  64. static.GetDifySandboxGlobalConfigurations().PythonPath,
  65. "-c",
  66. string(python_sandbox_fs),
  67. temp_code_path,
  68. strconv.Itoa(static.SANDBOX_USER_UID),
  69. strconv.Itoa(static.SANDBOX_GROUP_ID),
  70. )
  71. cmd.Env = []string{}
  72. // create a pipe for the stdout
  73. stdout_reader, err := cmd.StdoutPipe()
  74. if err != nil {
  75. cancel()
  76. return err
  77. }
  78. // create a pipe for the stderr
  79. stderr_reader, err := cmd.StderrPipe()
  80. if err != nil {
  81. cancel()
  82. return err
  83. }
  84. // start the process
  85. err = cmd.Start()
  86. if err != nil {
  87. cancel()
  88. return err
  89. }
  90. wg := sync.WaitGroup{}
  91. wg.Add(2)
  92. // read the output
  93. go func() {
  94. buf := make([]byte, 1024)
  95. defer wg.Done()
  96. for {
  97. n, err := stdout_reader.Read(buf)
  98. // exit if EOF
  99. if err != nil {
  100. if err == io.EOF {
  101. break
  102. } else {
  103. stderr <- []byte(fmt.Sprintf("error: %v\n", err))
  104. break
  105. }
  106. }
  107. stdout <- buf[:n]
  108. }
  109. }()
  110. // read the error
  111. go func() {
  112. buf := make([]byte, 1024)
  113. defer wg.Done()
  114. for {
  115. n, err := stderr_reader.Read(buf)
  116. // exit if EOF
  117. if err != nil {
  118. if err == io.EOF {
  119. break
  120. } else {
  121. stderr <- []byte(fmt.Sprintf("error: %v\n", err))
  122. break
  123. }
  124. }
  125. stderr <- buf[:n]
  126. }
  127. }()
  128. // wait for the process to finish
  129. go func() {
  130. status, err := cmd.Process.Wait()
  131. if err != nil {
  132. log.Error("process finished with status: %v", status.String())
  133. stderr <- []byte(fmt.Sprintf("error: %v\n", err))
  134. } else if status.ExitCode() != 0 {
  135. exit_string := status.String()
  136. if strings.Contains(exit_string, "bad system call (core dumped)") {
  137. stderr <- []byte("error: operation not permitted\n")
  138. } else {
  139. stderr <- []byte(fmt.Sprintf("exit code: %v\n", status.ExitCode()))
  140. }
  141. }
  142. // wait for the stdout and stderr to finish
  143. wg.Wait()
  144. stderr_reader.Close()
  145. stdout_reader.Close()
  146. os.Remove(temp_code_path)
  147. os.RemoveAll(root_path)
  148. os.Remove(root_path)
  149. cancel()
  150. done_chan <- true
  151. }()
  152. return nil
  153. })
  154. if err != nil {
  155. return nil, nil, nil, err
  156. }
  157. return stdout, stderr, done_chan, nil
  158. }