@@ -6,34 +6,147 @@ import (
66 "os/exec"
77 "path/filepath"
88 "strings"
9+ "syscall"
910)
1011
1112type sandboxWorkspace struct {
12- dir string
13- sourcePath string
14- binaryPath string
13+ dir string
14+ sourcePath string
15+ binaryPath string
16+ compileCommand []string
17+ runCommand []string
18+ extraEnv []string
19+ useChroot bool
20+ runtimeMounts []string
21+ mountedTargets []string
1522}
1623
17- func prepareWorkspace (code string ) (sandboxWorkspace , error ) {
18- dir , err := os .MkdirTemp ("" , "codesandbox-c- *" )
24+ func prepareWorkspace (language , code string ) (sandboxWorkspace , error ) {
25+ dir , err := os .MkdirTemp ("" , "codesandbox-*" )
1926 if err != nil {
2027 return sandboxWorkspace {}, fmt .Errorf ("failed to create sandbox dir: %w" , err )
2128 }
22- ws := sandboxWorkspace {dir : dir }
23- ws .sourcePath = filepath .Join (dir , "main.c" )
24- ws .binaryPath = filepath .Join (dir , "program" )
29+ ws , err := buildWorkspacePlan (dir , language )
30+ if err != nil {
31+ _ = os .RemoveAll (dir )
32+ return sandboxWorkspace {}, err
33+ }
2534 if err := os .WriteFile (ws .sourcePath , []byte (code ), 0o600 ); err != nil {
35+ _ = os .RemoveAll (dir )
2636 return sandboxWorkspace {}, fmt .Errorf ("failed to write source: %w" , err )
2737 }
2838 return ws , nil
2939}
3040
41+ func buildWorkspacePlan (dir , language string ) (sandboxWorkspace , error ) {
42+ normalized := normalizeLanguage (language )
43+ ws := sandboxWorkspace {dir : dir , binaryPath : filepath .Join (dir , "program" )}
44+
45+ switch normalized {
46+ case "c" :
47+ ws .sourcePath = filepath .Join (dir , "main.c" )
48+ ws .compileCommand = []string {"gcc" , "-O2" , "-pipe" , "-std=c11" , "-static" , "-s" , "-o" , ws .binaryPath , ws .sourcePath }
49+ ws .runCommand = []string {"/program" }
50+ ws .useChroot = true
51+ case "cpp" :
52+ ws .sourcePath = filepath .Join (dir , "main.cpp" )
53+ ws .compileCommand = []string {"g++" , "-O2" , "-pipe" , "-std=c++17" , "-static" , "-s" , "-o" , ws .binaryPath , ws .sourcePath }
54+ ws .runCommand = []string {"/program" }
55+ ws .useChroot = true
56+ case "java" :
57+ javaBin , javaLibDir , javaHome , err := resolveJavaRuntime ()
58+ if err != nil {
59+ return sandboxWorkspace {}, err
60+ }
61+ ws .sourcePath = filepath .Join (dir , "Main.java" )
62+ ws .compileCommand = []string {"javac" , ws .sourcePath }
63+ ws .runCommand = []string {javaBin , "-Xms8m" , "-Xmx24m" , "-cp" , "/" , "Main" }
64+ ws .extraEnv = []string {"LD_LIBRARY_PATH=" + javaLibDir , "JAVA_HOME=" + javaHome }
65+ ws .useChroot = true
66+ ws .runtimeMounts = defaultRuntimeMounts ()
67+ case "python3" :
68+ ws .sourcePath = filepath .Join (dir , "main.py" )
69+ ws .runCommand = []string {"/usr/bin/python3" , "/main.py" }
70+ ws .useChroot = true
71+ ws .runtimeMounts = defaultRuntimeMounts ()
72+ default :
73+ return sandboxWorkspace {}, fmt .Errorf ("unsupported language: %s" , language )
74+ }
75+
76+ return ws , nil
77+ }
78+
79+ func normalizeLanguage (language string ) string {
80+ switch strings .ToLower (strings .TrimSpace (language )) {
81+ case "python" , "py" , "python3" :
82+ return "python3"
83+ case "c++" , "cpp" :
84+ return "cpp"
85+ default :
86+ return strings .ToLower (strings .TrimSpace (language ))
87+ }
88+ }
89+
90+ func resolveJavaRuntime () (javaBin string , javaLibDir string , javaHome string , err error ) {
91+ javaPath , lookErr := exec .LookPath ("java" )
92+ if lookErr != nil {
93+ return "" , "" , "" , fmt .Errorf ("java runtime not found: %w" , lookErr )
94+ }
95+
96+ resolvedJava , evalErr := filepath .EvalSymlinks (javaPath )
97+ if evalErr != nil {
98+ resolvedJava = javaPath
99+ }
100+
101+ javaBin = resolvedJava
102+ javaHome = filepath .Clean (filepath .Join (filepath .Dir (resolvedJava ), ".." ))
103+ javaLibDir = filepath .Join (javaHome , "lib" )
104+ return javaBin , javaLibDir , javaHome , nil
105+ }
106+
107+ func defaultRuntimeMounts () []string {
108+ return []string {"/usr" , "/lib" , "/lib64" , "/bin" , "/etc" }
109+ }
110+
111+ func prepareRuntimeMounts (ws * sandboxWorkspace ) error {
112+ if ! ws .useChroot || len (ws .runtimeMounts ) == 0 {
113+ return nil
114+ }
115+
116+ for _ , source := range ws .runtimeMounts {
117+ target := filepath .Join (ws .dir , strings .TrimPrefix (source , "/" ))
118+ if err := os .MkdirAll (target , 0o755 ); err != nil {
119+ cleanupRuntimeMounts (ws )
120+ return fmt .Errorf ("failed to prepare mount target %s: %w" , target , err )
121+ }
122+ if err := syscall .Mount (source , target , "" , syscall .MS_BIND | syscall .MS_REC , "" ); err != nil {
123+ cleanupRuntimeMounts (ws )
124+ return fmt .Errorf ("failed to bind mount %s -> %s: %w" , source , target , err )
125+ }
126+ ws .mountedTargets = append (ws .mountedTargets , target )
127+ }
128+
129+ return nil
130+ }
131+
132+ func cleanupRuntimeMounts (ws * sandboxWorkspace ) {
133+ for i := len (ws .mountedTargets ) - 1 ; i >= 0 ; i -- {
134+ target := ws .mountedTargets [i ]
135+ _ = syscall .Unmount (target , syscall .MNT_DETACH )
136+ }
137+ ws .mountedTargets = nil
138+ }
139+
31140func compileWorkspace (ws sandboxWorkspace ) (string , bool ) {
32- cmd := exec .Command ("gcc" , "-O2" , "-pipe" , "-std=c11" , "-static" , "-s" , "-o" , ws .binaryPath , ws .sourcePath )
33- var stderr strings.Builder
34- cmd .Stderr = & stderr
35- if err := cmd .Run (); err != nil {
36- return stderr .String (), false
141+ if len (ws .compileCommand ) == 0 {
142+ return "" , true
143+ }
144+
145+ cmd := exec .Command (ws .compileCommand [0 ], ws .compileCommand [1 :]... )
146+ cmd .Dir = ws .dir
147+ out , err := cmd .CombinedOutput ()
148+ if err != nil {
149+ return string (out ), false
37150 }
38151 return "" , true
39152}
0 commit comments