-
-
Save jvns/14b8f65537004a56013260d9219ef36f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"io/ioutil" | |
"net/http" | |
"os" | |
"os/exec" | |
"strings" | |
"syscall" | |
"time" | |
) | |
func bubblewrap(command ...string) *exec.Cmd { | |
bwrap_args := []string{ | |
"--setenv", "HOME", "/root", | |
"--setenv", "TERM", "xterm-256color", | |
"--setenv", "PATH", "/usr/local/sbin:/usr/local/bin:/usr/bin:/bin", | |
"--setenv", "BINARY_NAME", os.Getenv("BINARY_NAME"), | |
"--unshare-pid", "--unshare-net", "--unshare-uts", "--unshare-user", | |
"--ro-bind", "/usr", "/usr", | |
"--ro-bind", "/lib", "/lib", | |
"--ro-bind", "/tmp", "/tmp", | |
"--ro-bind-try", "/lib32", "/lib32", | |
"--ro-bind-try", "/lib64", "/lib64", | |
"--ro-bind-try", "/libx32", "/libx32", | |
"--ro-bind", "/bin", "/bin", | |
"--ro-bind", "/etc", "/etc", | |
"--ro-bind", "/root", "/root", | |
"--ro-bind", "/app", "/app", | |
"--proc", "/proc", "--dev", "/dev", | |
} | |
bwrap_args = append(bwrap_args, command...) | |
return exec.Command("bwrap", bwrap_args...) | |
} | |
func runWithTimeout(cmd *exec.Cmd, timeout time.Duration) ([]byte, error) { | |
ctx, cancel := context.WithTimeout(context.Background(), timeout) | |
defer cancel() | |
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} | |
outChan := make(chan []byte, 1) | |
errChan := make(chan error, 1) | |
go func() { | |
output, err := cmd.CombinedOutput() | |
if err != nil { | |
errChan <- fmt.Errorf("failed to run command: %v\n%v", err, string(output)) | |
return | |
} | |
outChan <- output | |
}() | |
select { | |
case <-ctx.Done(): | |
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) | |
return nil, errors.New("command timed out") | |
case err := <-errChan: | |
return nil, err | |
case out := <-outChan: | |
return out, nil | |
} | |
} | |
func run(rw http.ResponseWriter, request *http.Request) { | |
rw.Header().Set("Access-Control-Allow-Origin", "*") | |
if request.Method == "OPTIONS" { | |
rw.WriteHeader(http.StatusOK) | |
return | |
} | |
// get program from request params | |
program, err := ioutil.ReadAll(request.Body) | |
// get the line of the reponse that contains "line_number" | |
output, err := runProgram(program) | |
json := extractJSON(output) | |
if err != nil { | |
rw.WriteHeader(http.StatusInternalServerError) | |
rw.Write([]byte(err.Error())) | |
return | |
} | |
rw.WriteHeader(http.StatusOK) | |
rw.Write([]byte(json)) | |
} | |
func runProgram(program []byte) (string, error) { | |
// make temp file | |
sourceFile, err := ioutil.TempFile("", "temp_*.c") | |
defer os.Remove(sourceFile.Name()) | |
if err != nil { | |
return "", err | |
} | |
// write program to temp file | |
_, err = sourceFile.Write(program) | |
if err != nil { | |
return "", err | |
} | |
// close temp file | |
err = sourceFile.Close() | |
if err != nil { | |
return "", err | |
} | |
// compile program with clang -g | |
outputFile := sourceFile.Name()[0 : len(sourceFile.Name())-2] | |
defer os.Remove(outputFile) | |
cmd := exec.Command("clang", "-O0", "-g", "-o", outputFile, sourceFile.Name()) | |
output, err := runWithTimeout(cmd, 5*time.Second) | |
if err != nil { | |
return "", fmt.Errorf("failed to compile program: %v\n%s", err, output) | |
} | |
// set environment variable with output file name | |
err = os.Setenv("BINARY_NAME", outputFile) | |
if err != nil { | |
return "", fmt.Errorf("failed to set environment variable: %v", err) | |
} | |
// run lldb with script | |
cmd = bubblewrap("lldb", "-s", "stack_trace") | |
// print cmd so that someone could run it | |
fmt.Println(cmd.String()) | |
lldbOutput, err := runWithTimeout(cmd, 1*time.Second) | |
if err != nil { | |
return "", fmt.Errorf("failed to run lldb: %v\n%s", err, lldbOutput) | |
} | |
return string(lldbOutput), nil | |
} | |
func extractJSON(output string) string { | |
// find the line that contains "line_number" | |
lines := strings.Split(output, "\n") | |
for _, line := range lines { | |
if strings.Contains(line, "line_number") { | |
return line | |
} | |
} | |
return "" | |
} | |
func test() { | |
program, err := ioutil.ReadFile(os.Args[2]) | |
result, err := runProgram(program) | |
if err != nil { | |
fmt.Println("Error: ", err) | |
} | |
fmt.Println("Result: ", result) | |
} | |
func main() { | |
if len(os.Args) > 1 && os.Args[1] == "--test" { | |
test() | |
return | |
} | |
// start http server on port 8080 | |
// static files are in the "static" directory | |
http.HandleFunc("/run/", run) | |
http.ListenAndServe(":8080", nil) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment