playground: add sandbox and nacl base image

Change-Id: If6c2893ef8df63a3005ec85075db2e9df0cbf993
diff --git a/README b/README
deleted file mode 100644
index 61c25fd..0000000
--- a/README
+++ /dev/null
@@ -1,4 +0,0 @@
-This subrepository holds the source for various packages and tools that support
-the Go playground: https://play.golang.org/
-
-To submit changes to this repository, see http://golang.org/doc/contribute.html.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bf7f902
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# playground
+
+This subrepository holds the source for various packages and tools that support
+the Go playground: https://play.golang.org/
+
+## building
+
+```
+# build the golang:nacl base image
+docker build -t golang:nacl gonacl/
+# build the sandbox server image
+docker build -t sandbox sandbox/
+```
+
+## running
+
+```
+# run the sandbox
+docker run -d -p 8080:8080 sandbox
+# get docker host ip, try boot2docker fallback on localhost.
+DOCKER_HOST_IP=$(boot2docker ip || echo localhost)
+# run go some code
+cat /path/to/code.go | go run ./sandbox/client.go | curl --data @- $DOCKER_HOST_IP:8080/compile
+```
+
+To submit changes to this repository, see http://golang.org/doc/contribute.html.
diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile
new file mode 100644
index 0000000..ee0a0a0
--- /dev/null
+++ b/sandbox/Dockerfile
@@ -0,0 +1,21 @@
+# Copyright 2014 The Go Authors.  All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+FROM golang:1.4
+
+# enable faketime
+RUN apt-get update && apt-get install -yq --no-install-recommends patch
+ADD enable-fake-time.patch /usr/src/go/
+RUN patch /usr/src/go/src/runtime/rt0_nacl_amd64p32.s /usr/src/go/enable-fake-time.patch
+
+# build go nacl tool chain
+RUN cd /usr/src/go/src && GOOS=nacl GOARCH=amd64p32 ./make.bash --no-clean
+RUN cd /usr/local/bin && curl -s -O https://storage.googleapis.com/gobuilder/sel_ldr_x86_64 && chmod +x sel_ldr_x86_64
+
+# add and compile sandbox daemon
+ADD . /go/src/sandbox/
+RUN go install sandbox
+
+EXPOSE 8080
+ENTRYPOINT ["/go/bin/sandbox"]
diff --git a/sandbox/client.go b/sandbox/client.go
new file mode 100644
index 0000000..eee62f9
--- /dev/null
+++ b/sandbox/client.go
@@ -0,0 +1,26 @@
+// +build ignore
+
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"log"
+	"os"
+)
+
+func main() {
+	body, err := ioutil.ReadAll(os.Stdin)
+	if err != nil {
+		log.Fatalf("error reading stdin: %v", err)
+	}
+	json.NewEncoder(os.Stdout).Encode(struct {
+		Body string
+	}{
+		Body: string(body),
+	})
+}
diff --git a/sandbox/enable-fake-time.patch b/sandbox/enable-fake-time.patch
new file mode 100644
index 0000000..4e389b9
--- /dev/null
+++ b/sandbox/enable-fake-time.patch
@@ -0,0 +1,11 @@
+--- rt0_nacl_amd64p32.s	2014-10-28 17:28:25.028188222 -0700
++++ rt0_nacl_amd64p32-faketime.s	2014-10-28 17:28:06.363674896 -0700
+@@ -25,6 +25,6 @@
+ 
+ TEXT main(SB),NOSPLIT,$0
+ 	// Uncomment for fake time like on Go Playground.
+-	//MOVQ	$1257894000000000000, AX
+-	//MOVQ	AX, runtime·faketime(SB)
++	MOVQ	$1257894000000000000, AX
++	MOVQ	AX, runtime·faketime(SB)
+ 	JMP	runtime·rt0_go(SB)
diff --git a/sandbox/play.go b/sandbox/play.go
new file mode 100644
index 0000000..8679945
--- /dev/null
+++ b/sandbox/play.go
@@ -0,0 +1,119 @@
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"time"
+	"unicode/utf8"
+)
+
+type Event struct {
+	Message string
+	Delay   time.Duration // time to wait before printing Message
+	Kind    string
+}
+
+// Decode takes an output string comprised of playback headers, and converts
+// it to a sequence of Events.
+// It sanitizes each Event's Message to ensure it is valid UTF-8.
+//
+// Playground programs precede all their writes with a header (described
+// below) that describes the time the write occurred (in playground time) and
+// the length of the data that will be written. If a non-header is
+// encountered where a header is expected, the output is scanned for the next
+// header and the intervening text string is added to the sequence an event
+// occurring at the same time as the preceding event.
+//
+// A playback header has this structure:
+// 	4 bytes: "\x00\x00PB", a magic header
+// 	8 bytes: big-endian int64, unix time in nanoseconds
+// 	4 bytes: big-endian int32, length of the next write
+//
+func Decode(output []byte) (seq []Event, err error) {
+	var (
+		magic     = []byte{0, 0, 'P', 'B'}
+		headerLen = 8 + 4
+		now       = time.Unix(1257894000, 0) // go epoch
+	)
+
+	add := func(t time.Time, b []byte) {
+		e := Event{
+			Message: string(sanitize(b)),
+			Delay:   t.Sub(now),
+		}
+		if e.Delay == 0 && len(seq) > 0 {
+			// Merge this event with previous event, to avoid
+			// sending a lot of events for a big output with no
+			// significant timing information.
+			seq[len(seq)-1].Message += e.Message
+		} else {
+			seq = append(seq, e)
+		}
+		now = t
+	}
+
+	for i := 0; i < len(output); {
+		if !bytes.HasPrefix(output[i:], magic) {
+			// Not a header; find next header.
+			j := bytes.Index(output[i:], magic)
+			if j < 0 {
+				// No more headers; bail.
+				add(now, output[i:])
+				break
+			}
+			add(now, output[i:i+j])
+			i += j
+		}
+		i += len(magic)
+
+		// Decode header.
+		if len(output)-i < headerLen {
+			return nil, errors.New("short header")
+		}
+		header := output[i : i+headerLen]
+		nanos := int64(binary.BigEndian.Uint64(header[0:]))
+		t := time.Unix(0, nanos)
+		if t.Before(now) {
+			// Force timestamps to be monotonic. (This could
+			// be an encoding error, which we ignore now but will
+			// will likely be picked up when decoding the length.)
+			t = now
+		}
+		n := int(binary.BigEndian.Uint32(header[8:]))
+		if n < 0 {
+			return nil, fmt.Errorf("bad length: %v", n)
+		}
+		i += headerLen
+
+		// Slurp output.
+		// Truncated output is OK (probably caused by sandbox limits).
+		end := i + n
+		if end > len(output) {
+			end = len(output)
+		}
+		add(t, output[i:end])
+		i += n
+	}
+	return
+}
+
+// sanitize scans b for invalid utf8 code points. If found, it reconstructs
+// the slice replacing the invalid codes with \uFFFD, properly encoded.
+func sanitize(b []byte) []byte {
+	if utf8.Valid(b) {
+		return b
+	}
+	var buf bytes.Buffer
+	for len(b) > 0 {
+		r, size := utf8.DecodeRune(b)
+		b = b[size:]
+		buf.WriteRune(r)
+	}
+	return buf.Bytes()
+}
diff --git a/sandbox/sandbox.go b/sandbox/sandbox.go
new file mode 100644
index 0000000..67e69af
--- /dev/null
+++ b/sandbox/sandbox.go
@@ -0,0 +1,95 @@
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command sandbox is an HTTP server that takes requests containing go
+// source files, and builds and executes them in a NaCl sanbox.
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"os/exec"
+	"path"
+)
+
+type Request struct {
+	Body string
+}
+
+type Response struct {
+	Errors string
+	Events []Event
+}
+
+func handleCompile(w http.ResponseWriter, r *http.Request) {
+	var codeRequest Request
+	if err := json.NewDecoder(r.Body).Decode(&codeRequest); err != nil {
+		http.Error(w, fmt.Sprintf("request error: %v", err), http.StatusBadRequest)
+		return
+	}
+	resp, err := compileAndRun(&codeRequest)
+	if err != nil {
+		http.Error(w, fmt.Sprintf("sandbox error: %v", err), http.StatusInternalServerError)
+		return
+	}
+	if err := json.NewEncoder(w).Encode(resp); err != nil {
+		http.Error(w, fmt.Sprintf("response error: %v", err), http.StatusInternalServerError)
+		return
+	}
+}
+
+func compileAndRun(req *Request) (*Response, error) {
+	tmpDir, err := ioutil.TempDir("", "sandbox")
+	if err != nil {
+		return nil, fmt.Errorf("error creating temp directory: %v", err)
+	}
+	defer os.RemoveAll(tmpDir)
+
+	in := path.Join(tmpDir, "main.go")
+	if err := ioutil.WriteFile(in, []byte(req.Body), 0400); err != nil {
+		return nil, fmt.Errorf("error creating temp file %q: %v", in, err)
+	}
+	exe := path.Join(tmpDir, "a.out")
+	cmd := exec.Command("go", "build", "-o", exe, in)
+	cmd.Env = []string{
+		"GOOS=nacl",
+		"GOARCH=amd64p32",
+	}
+	if out, err := cmd.CombinedOutput(); err != nil {
+		if _, ok := err.(*exec.ExitError); ok {
+			// build error
+			return &Response{
+				Errors: string(out),
+			}, nil
+		}
+		return nil, fmt.Errorf("error building go source: %v", err)
+	}
+	// TODO(proppy): restrict run time and memory use.
+	cmd = exec.Command("sel_ldr_x86_64", "-l", "/dev/null", "-S", "-e", exe)
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		if _, ok := err.(*exec.ExitError); !ok {
+			return nil, fmt.Errorf("error running sandbox: %v", err)
+		}
+	}
+	events, err := Decode(out)
+	if err != nil {
+		return nil, fmt.Errorf("error decoding events: %v", err)
+	}
+	return &Response{
+		Events: events,
+	}, nil
+}
+
+func main() {
+	http.HandleFunc("/compile", handleCompile)
+	http.HandleFunc("/_ah/health", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(w, "ok")
+	})
+	log.Fatal(http.ListenAndServe(":8080", nil))
+}