nodejs.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package nodejs
  2. import (
  3. "context"
  4. "embed"
  5. "fmt"
  6. "io"
  7. "os"
  8. "os/exec"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "time"
  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 NodeJsRunner struct {
  19. runner.Runner
  20. runner.SeccompRunner
  21. }
  22. //go:embed prescript.js
  23. var nodejs_sandbox_fs []byte
  24. //go:embed nodejs.so
  25. var nodejs_lib []byte
  26. //go:embed dependens
  27. var nodejs_dependens embed.FS // it's a directory
  28. func init() {
  29. log.Info("initializing nodejs runner environment...")
  30. os.RemoveAll("/tmp/sandbox-nodejs")
  31. os.Remove("/tmp/sandbox-nodejs")
  32. err := os.MkdirAll("/tmp/sandbox-nodejs", 0755)
  33. if err != nil {
  34. log.Panic("failed to create /tmp/sandbox-nodejs")
  35. }
  36. err = os.WriteFile("/tmp/sandbox-nodejs/nodejs.so", nodejs_lib, 0755)
  37. if err != nil {
  38. log.Panic("failed to write /tmp/sandbox-nodejs/nodejs.so")
  39. }
  40. // remove /tmp/sandbox-nodejs-project
  41. os.RemoveAll("/tmp/sandbox-nodejs-project")
  42. os.Remove("/tmp/sandbox-nodejs-project")
  43. // copy the nodejs project into /tmp/sandbox-nodejs-project
  44. err = os.MkdirAll("/tmp/sandbox-nodejs-project", 0755)
  45. if err != nil {
  46. log.Panic("failed to create /tmp/sandbox-nodejs-project")
  47. }
  48. // copy the nodejs project into /tmp/sandbox-nodejs-project
  49. var recursively_copy func(src string, dst string) error
  50. recursively_copy = func(src string, dst string) error {
  51. entries, err := nodejs_dependens.ReadDir(src)
  52. if err != nil {
  53. return err
  54. }
  55. for _, entry := range entries {
  56. src_path := src + "/" + entry.Name()
  57. dst_path := dst + "/" + entry.Name()
  58. if entry.IsDir() {
  59. err = os.Mkdir(dst_path, 0755)
  60. if err != nil {
  61. return err
  62. }
  63. err = recursively_copy(src_path, dst_path)
  64. if err != nil {
  65. return err
  66. }
  67. } else {
  68. data, err := nodejs_dependens.ReadFile(src_path)
  69. if err != nil {
  70. return err
  71. }
  72. err = os.WriteFile(dst_path, data, 0755)
  73. if err != nil {
  74. return err
  75. }
  76. }
  77. }
  78. return nil
  79. }
  80. err = recursively_copy("dependens", "/tmp/sandbox-nodejs-project")
  81. if err != nil {
  82. log.Panic("failed to copy nodejs project")
  83. }
  84. log.Info("nodejs runner environment initialized")
  85. }
  86. func (p *NodeJsRunner) Run(code string, timeout time.Duration, stdin []byte) (chan []byte, chan []byte, chan bool, error) {
  87. // create a tmp dir and copy the nodejs script
  88. stdout := make(chan []byte)
  89. stderr := make(chan []byte)
  90. done_chan := make(chan bool)
  91. err := p.WithTempDir([]string{
  92. "/tmp/sandbox-nodejs-project/node_temp",
  93. "/tmp/sandbox-nodejs/nodejs.so",
  94. }, func(root_path string) error {
  95. // join nodejs_sandbox_fs and code
  96. code = string(nodejs_sandbox_fs) + code
  97. // override root_path/tmp/sandbox-nodejs-project/prescript.js
  98. script_path := path.Join(root_path, "tmp/sandbox-nodejs-project/node_temp/node_temp/test.js")
  99. err := os.WriteFile(script_path, []byte(code), 0755)
  100. if err != nil {
  101. return err
  102. }
  103. // create a new process
  104. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  105. cmd := exec.CommandContext(ctx,
  106. static.GetDifySandboxGlobalConfigurations().NodejsPath,
  107. script_path,
  108. strconv.Itoa(static.SANDBOX_USER_UID),
  109. strconv.Itoa(static.SANDBOX_GROUP_ID),
  110. )
  111. cmd.Env = []string{}
  112. // create a pipe for the stdout
  113. stdout_reader, err := cmd.StdoutPipe()
  114. if err != nil {
  115. cancel()
  116. return err
  117. }
  118. // create a pipe for the stderr
  119. stderr_reader, err := cmd.StderrPipe()
  120. if err != nil {
  121. cancel()
  122. return err
  123. }
  124. // start the process
  125. err = cmd.Start()
  126. if err != nil {
  127. cancel()
  128. return err
  129. }
  130. wg := sync.WaitGroup{}
  131. wg.Add(2)
  132. // read the output
  133. go func() {
  134. defer wg.Done()
  135. for {
  136. buf := make([]byte, 1024)
  137. n, err := stdout_reader.Read(buf)
  138. // exit if EOF
  139. if err != nil {
  140. if err == io.EOF {
  141. break
  142. } else {
  143. stderr <- []byte(fmt.Sprintf("error: %v\n", err))
  144. break
  145. }
  146. }
  147. stdout <- buf[:n]
  148. }
  149. }()
  150. // read the error
  151. go func() {
  152. buf := make([]byte, 1024)
  153. defer wg.Done()
  154. for {
  155. n, err := stderr_reader.Read(buf)
  156. // exit if EOF
  157. if err != nil {
  158. if err == io.EOF {
  159. break
  160. } else {
  161. stderr <- []byte(fmt.Sprintf("error: %v\n", err))
  162. break
  163. }
  164. }
  165. stderr <- buf[:n]
  166. }
  167. }()
  168. // wait for the process to finish
  169. go func() {
  170. status, err := cmd.Process.Wait()
  171. if err != nil {
  172. log.Error("process finished with status: %v", status.String())
  173. stderr <- []byte(fmt.Sprintf("error: %v\n", err))
  174. } else if status.ExitCode() != 0 {
  175. exit_string := status.String()
  176. if strings.Contains(exit_string, "bad system call (core dumped)") {
  177. stderr <- []byte("error: operation not permitted\n")
  178. } else {
  179. stderr <- []byte(fmt.Sprintf("exit code: %v\n", status.ExitCode()))
  180. }
  181. }
  182. // wait for the stdout and stderr to finish
  183. wg.Wait()
  184. stderr_reader.Close()
  185. stdout_reader.Close()
  186. os.RemoveAll(root_path)
  187. os.Remove(root_path)
  188. cancel()
  189. done_chan <- true
  190. }()
  191. return nil
  192. })
  193. if err != nil {
  194. return nil, nil, nil, err
  195. }
  196. return stdout, stderr, done_chan, nil
  197. }