nodejs.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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(
  86. code string, timeout time.Duration, stdin []byte, preload string,
  87. ) (chan []byte, chan []byte, chan bool, error) {
  88. // create a tmp dir and copy the nodejs script
  89. stdout := make(chan []byte)
  90. stderr := make(chan []byte)
  91. write_out := func(data []byte) {
  92. stdout <- data
  93. }
  94. write_err := func(data []byte) {
  95. stderr <- data
  96. }
  97. done_chan := make(chan bool)
  98. err := p.WithTempDir([]string{
  99. "/tmp/sandbox-nodejs-project/node_temp",
  100. "/tmp/sandbox-nodejs/nodejs.so",
  101. }, func(root_path string) error {
  102. node_sandbox_file := string(nodejs_sandbox_fs)
  103. if preload != "" {
  104. node_sandbox_file = fmt.Sprintf("%s\n%s", preload, node_sandbox_file)
  105. }
  106. // join nodejs_sandbox_fs and code
  107. code = node_sandbox_file + code
  108. // override root_path/tmp/sandbox-nodejs-project/prescript.js
  109. script_path := path.Join(root_path, "tmp/sandbox-nodejs-project/node_temp/node_temp/test.js")
  110. err := os.WriteFile(script_path, []byte(code), 0755)
  111. if err != nil {
  112. return err
  113. }
  114. // create a new process
  115. cmd := exec.Command(
  116. static.GetDifySandboxGlobalConfigurations().NodejsPath,
  117. script_path,
  118. strconv.Itoa(static.SANDBOX_USER_UID),
  119. strconv.Itoa(static.SANDBOX_GROUP_ID),
  120. )
  121. cmd.Env = []string{}
  122. // create a pipe for the stdout
  123. stdout_reader, err := cmd.StdoutPipe()
  124. if err != nil {
  125. return err
  126. }
  127. // create a pipe for the stderr
  128. stderr_reader, err := cmd.StderrPipe()
  129. if err != nil {
  130. stdout_reader.Close()
  131. return err
  132. }
  133. // start the process
  134. err = cmd.Start()
  135. if err != nil {
  136. stdout_reader.Close()
  137. stderr_reader.Close()
  138. return err
  139. }
  140. // start a timer for the timeout
  141. timer := time.NewTimer(timeout)
  142. go func() {
  143. <-timer.C
  144. if cmd != nil && cmd.Process != nil {
  145. // write the error
  146. write_err([]byte("error: timeout\n"))
  147. // send a signal to the process
  148. cmd.Process.Kill()
  149. }
  150. }()
  151. wg := sync.WaitGroup{}
  152. wg.Add(2)
  153. // read the output
  154. go func() {
  155. defer wg.Done()
  156. for {
  157. buf := make([]byte, 1024)
  158. n, err := stdout_reader.Read(buf)
  159. // exit if EOF
  160. if err != nil {
  161. if err == io.EOF {
  162. break
  163. } else {
  164. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  165. break
  166. }
  167. }
  168. write_out(buf[:n])
  169. }
  170. }()
  171. // read the error
  172. go func() {
  173. buf := make([]byte, 1024)
  174. defer wg.Done()
  175. for {
  176. n, err := stderr_reader.Read(buf)
  177. // exit if EOF
  178. if err != nil {
  179. if err == io.EOF {
  180. break
  181. } else {
  182. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  183. break
  184. }
  185. }
  186. write_err(buf[:n])
  187. }
  188. }()
  189. // wait for the process to finish
  190. go func() {
  191. status, err := cmd.Process.Wait()
  192. if err != nil {
  193. log.Error("process finished with status: %v", status.String())
  194. write_err([]byte(fmt.Sprintf("error: %v\n", err)))
  195. } else if status.ExitCode() != 0 {
  196. exit_string := status.String()
  197. if strings.Contains(exit_string, "bad system call") {
  198. write_err([]byte("error: operation not permitted\n"))
  199. } else {
  200. write_err([]byte(fmt.Sprintf("error: %v\n", status.String())))
  201. }
  202. }
  203. // wait for the stdout and stderr to finish
  204. wg.Wait()
  205. stderr_reader.Close()
  206. stdout_reader.Close()
  207. os.RemoveAll(root_path)
  208. os.Remove(root_path)
  209. timer.Stop()
  210. done_chan <- true
  211. }()
  212. return nil
  213. })
  214. if err != nil {
  215. return nil, nil, nil, err
  216. }
  217. return stdout, stderr, done_chan, nil
  218. }