nodejs.go 5.4 KB

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