python.go 3.4 KB

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