aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental
diff options
context:
space:
mode:
authorGravatar commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2014-04-09 18:59:44 +0000
committerGravatar commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2014-04-09 18:59:44 +0000
commit6d036c2e47819ce91ba4f09edeeccead60af4320 (patch)
tree7139d1d308d9b5252754cd1bfa74aa22c8f6cfce /experimental
parent0257ebe4ac7907d0293371cc6a50a0ec0a55f61c (diff)
Initial code for webtry, a web application for allowing users to try out Skia.
Currently running at http://108.170.220.126:8000/ BUG=skia: R=mtklein@google.com Author: jcgregorio@google.com Review URL: https://codereview.chromium.org/228693002 git-svn-id: http://skia.googlecode.com/svn/trunk@14114 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'experimental')
-rw-r--r--experimental/webtry/DESIGN121
-rw-r--r--experimental/webtry/README68
-rw-r--r--experimental/webtry/TODO11
-rw-r--r--experimental/webtry/main.cpp114
-rw-r--r--experimental/webtry/result.cpp28
-rw-r--r--experimental/webtry/seccomp_bpf.h45
-rw-r--r--experimental/webtry/server.py79
-rwxr-xr-xexperimental/webtry/setup/continue_install56
-rwxr-xr-xexperimental/webtry/setup/webtry_setup.sh32
-rw-r--r--experimental/webtry/sys/webtry_init159
-rw-r--r--experimental/webtry/sys/webtry_monit4
-rw-r--r--experimental/webtry/sys/webtry_schroot9
-rw-r--r--experimental/webtry/template.cpp23
-rw-r--r--experimental/webtry/templates/index.html (renamed from experimental/webtry/index.html)17
-rw-r--r--experimental/webtry/templates/template.cpp13
-rw-r--r--experimental/webtry/webtry.go226
16 files changed, 890 insertions, 115 deletions
diff --git a/experimental/webtry/DESIGN b/experimental/webtry/DESIGN
new file mode 100644
index 0000000000..ddc26f538c
--- /dev/null
+++ b/experimental/webtry/DESIGN
@@ -0,0 +1,121 @@
+Design
+======
+
+
+Overview
+--------
+Allows trying out Skia code in the browser.
+
+
+Security
+--------
+We're putting a C++ compiler on the web, and promising to run the results of
+user submitted code, so security is a large concern. Security is handled in a
+layered approach, using a combination of seccomp-bpf, chroot jail and rlimits.
+
+*seccomp-bpf* - Used to limit the types of system calls that the user code can
+make. Any attempts to make a system call that isn't allowed causes the
+application to terminate immediately.
+
+*chroot jail* - The code is run in a chroot jail, making the rest of the
+operating system files unreachable from the running code.
+
+*rlimits* - Used to limit the resources the running code can get access to,
+for example runtime is limited to 5s of CPU.
+
+User submitted code is also restricted in the following ways:
+ * Limited to 10K of code total.
+ * No preprocessor use is allowed (no lines can begin with \s*#).
+
+
+Architecture
+------------
+
+The server runs on GCE, and consists of a Go Web Server that calls out to the
+c++ compiler and executes code in a chroot jail. See the diagram below:
+
+                           
+   +–––––––––––––+         
+   |             |         
+   |  Browser    |         
+   |             |         
+   +––––––+––––––+         
+          |                
+   +––––––+––––––+         
+   |             |         
+   |             |         
+   | Web Server  |         
+   |             |         
+   |   (Go)      |         
+   |             |         
+   |             |         
+   +–––––––+–––––+         
+           |               
+   +–––––––+––––––––––+    
+   | chroot jail      |    
+   |  +––––––––––––––+|    
+   |  | seccomp      ||    
+   |  |  +––––––––––+||    
+   |  |  |User code |||    
+   |  |  |          |||    
+   |  |  +----------+||    
+   |  +––------------+|    
+   |                  |    
+   +––––––––––––––––––+    
+                           
+                           
+The user code is expanded into a simple template and linked against libskia
+and a couple other .o files that contain main() and the code that sets up the
+seccomp and rlimit restrictions. This code also sets up the SkCanvas that is
+handed to the user code. Any code the user submits is restricted to running in
+a single function that looks like this:
+
+
+ void draw(SkCanvas* canvas) {
+ // User code goes here.
+ }
+
+The user code is tracked by taking an MD5 hash of the code The template is
+expanded out into <hash>.cpp, which is compiled into <hash>.o, which is then
+linked together with all the other libs and object files to create an
+executable named <hash>. That executable is copied into a directory
+/home/webtry/inout, that is accessible to both the web server and the schroot
+jail. The application is then run in the schroot jail, writing its response,
+<hash>.png, out into the same directory, /home/webtry/inout/, where is it read
+by the web server and returned to the user.
+
+Startup and config
+------------------
+The server is started and stopped via:
+
+ sudo /etc/init.d/webtry [start|stop|restart]
+
+By sysv init only handles starting and stopping a program once, so we use
+Monit to monitor the application and restart it if it crashes. The config
+is in:
+
+ /etc/monit/conf.d/webtry
+
+The chroot jail is implemented using schroot, its configuration
+file is found in:
+
+ /etc/schroot/chroot.d/webtry
+
+The seccomp configuration is in main.cpp and only allows the following system
+calls:
+
+ exit_group
+ exit
+ fstat
+ read
+ write
+ close
+ mmap
+ munmap
+ brk
+
+Installation
+------------
+See the README file.
+
+
diff --git a/experimental/webtry/README b/experimental/webtry/README
index a36ac8a3fe..c7573110f6 100644
--- a/experimental/webtry/README
+++ b/experimental/webtry/README
@@ -3,14 +3,72 @@ WebTry
Allows trying out Skia code in the browser. Run a local webserver
and from the pages it serves try out Skia code and see the results
-immediately.
+immediately. To make sandboxing easier this must be built w/GPU off.
-Running
-=======
+Running Locally
+===============
+$ GYP_GENERATORS=ninja ./gyp_skia gyp/webtry.gyp gyp/most.gyp -Dskia_gpu=0
+$ ninja -C out/Debug webtry
$ cd experimental/webtry
-$ python server.py
+$ go build webtry.go
+$ ./webtry
-Then visit http://localhost:8765 in your browser.
+Then visit http://localhost:8000 in your browser.
+Only tested under linux, doubtful it will work on other platforms.
+
+Full Server Setup
+=================
+
+Create a GCE instance:
+
+gcutil --project=google.com:skia-buildbots addinstance skia-webtry-b \
+ --zone=us-central2-b --external_ip_address=108.170.220.126 \
+ --service_account=default \
+ --service_account_scopes="https://www.googleapis.com/auth/devstorage.full_control" \
+ --network=default --machine_type=n1-standard-1 --image=backports-debian-7-wheezy-v20140331 \
+ --persistent_boot_disk
+
+SSH into the instance:
+
+ gcutil --project=google.com:skia-buildbots ssh --ssh_user=default skia-webtry-b
+
+
+The following things only need to be done once
+----------------------------------------------
+
+1. sudo apt-get install git schroot debootstrap
+2. git clone https://skia.googlesource.com/skia
+3. Add the following to /etc/fstab and reboot:
+
+ none /dev/shm tmpfs rw,nosuid,nodev,noexec 0 0
+
+The above will allow ninja to run. See http://stackoverflow.com/questions/2009278/python-multiprocessing-permission-denied
+
+4. Add the following to the /etc/schroot/minimal/fstab:
+
+ /home/webtry/inout /inout none rw,bind 0 0
+
+5. Change /etc/monit/monitrc to:
+
+ set daemon 2
+
+then run the following so it applies:
+
+ sudo /etc/init.d/monit restart
+
+This means that monit will poll every two seconds that our application is up and running.
+
+Do the following the first time you setup a machine, and each time you want to update the code running on the server
+--------------------------------------------------------------------------------------------------------------------
+
+cd ~/skia/experimental/webtry/setup
+./webtry_setup.sh
+
+
+Do these steps only once, but only after running webtry_setup.sh the first time
+-------------------------------------------------------------------------------
+
+1. sudo debootstrap --variant=minbase wheezy /srv/chroot/webtry
diff --git a/experimental/webtry/TODO b/experimental/webtry/TODO
new file mode 100644
index 0000000000..81382cda5d
--- /dev/null
+++ b/experimental/webtry/TODO
@@ -0,0 +1,11 @@
+ - Add -port flag to webtry.go.
+ - Add flag for inout directory to webtry.go.
+ - In webtry.go add mutexes per hash, to avoid conflicts of writing the same file at the same time.
+ - Don't allow #macros in user code.
+ - Limit the size of the user code submitted.
+ - Add font support in the c++ template.
+ - Add in all the Sk header files that could be needed.
+ - Clean up web page appearance.
+ - Add inline links to doxygen.
+ - Set wait cursor when doing the compiling.
+ - Add monitoring and probing (nagios).
diff --git a/experimental/webtry/main.cpp b/experimental/webtry/main.cpp
new file mode 100644
index 0000000000..9e7df1484f
--- /dev/null
+++ b/experimental/webtry/main.cpp
@@ -0,0 +1,114 @@
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "SkCanvas.h"
+#include "SkCommandLineFlags.h"
+#include "SkData.h"
+#include "SkForceLinking.h"
+#include "SkGraphics.h"
+#include "SkImageEncoder.h"
+#include "SkImageInfo.h"
+#include "SkStream.h"
+#include "SkSurface.h"
+
+#include "seccomp_bpf.h"
+
+__SK_FORCE_IMAGE_DECODER_LINKING;
+
+DEFINE_string(out, "", "Filename of the PNG to write to.");
+
+static bool install_syscall_filter() {
+ struct sock_filter filter[] = {
+ /* Grab the system call number. */
+ EXAMINE_SYSCALL,
+ /* List allowed syscalls. */
+ ALLOW_SYSCALL(exit_group),
+ ALLOW_SYSCALL(exit),
+ ALLOW_SYSCALL(fstat),
+ ALLOW_SYSCALL(read),
+ ALLOW_SYSCALL(write),
+ ALLOW_SYSCALL(close),
+ ALLOW_SYSCALL(mmap),
+ ALLOW_SYSCALL(munmap),
+ ALLOW_SYSCALL(brk),
+ KILL_PROCESS,
+ };
+ struct sock_fprog prog = {
+ SK_ARRAY_COUNT(filter),
+ filter,
+ };
+
+ // Lock down the app so that it can't get new privs, such as setuid.
+ // Calling this is a requirement for an unpriviledged process to use mode
+ // 2 seccomp filters, ala SECCOMP_MODE_FILTER, otherwise we'd have to be
+ // root.
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ perror("prctl(NO_NEW_PRIVS)");
+ goto failed;
+ }
+ // Now call seccomp and restrict the system calls that can be made to only
+ // the ones in the provided filter list.
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
+ perror("prctl(SECCOMP)");
+ goto failed;
+ }
+ return true;
+
+failed:
+ if (errno == EINVAL) {
+ fprintf(stderr, "SECCOMP_FILTER is not available. :(\n");
+ }
+ return false;
+}
+
+static void setLimits() {
+ struct rlimit n;
+
+ // Limit to 5 seconds of CPU.
+ n.rlim_cur = 5;
+ n.rlim_max = 5;
+ if (setrlimit(RLIMIT_CPU, &n)) {
+ perror("setrlimit(RLIMIT_CPU)");
+ }
+
+ // Limit to 50M of Address space.
+ n.rlim_cur = 50000000;
+ n.rlim_max = 50000000;
+ if (setrlimit(RLIMIT_AS, &n)) {
+ perror("setrlimit(RLIMIT_CPU)");
+ }
+}
+
+extern void draw(SkCanvas* canvas);
+
+int main(int argc, char** argv) {
+ SkCommandLineFlags::Parse(argc, argv);
+ SkAutoGraphics init;
+
+ if (FLAGS_out.count() == 0) {
+ perror("The --out flag must have an argument.");
+ return 1;
+ }
+ SkFILEWStream stream(FLAGS_out[0]);
+
+ SkImageInfo info = SkImageInfo::MakeN32(300, 300, kPremul_SkAlphaType);
+ SkAutoTUnref<SkSurface> surface(SkSurface::NewRaster(info));
+ SkCanvas* canvas = surface->getCanvas();
+
+ setLimits();
+
+ if (!install_syscall_filter()) {
+ return 1;
+ }
+
+ draw(canvas);
+
+ // Write out the image as a PNG.
+ SkAutoTUnref<SkImage> image(surface->newImageSnapshot());
+ SkAutoTUnref<SkData> data(image->encode(SkImageEncoder::kPNG_Type, 100));
+ if (NULL == data.get()) {
+ printf("Failed to encode\n");
+ exit(1);
+ }
+ stream.write(data->data(), data->size());
+}
diff --git a/experimental/webtry/result.cpp b/experimental/webtry/result.cpp
new file mode 100644
index 0000000000..d06ef9c3da
--- /dev/null
+++ b/experimental/webtry/result.cpp
@@ -0,0 +1,28 @@
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkForceLinking.h"
+#include "SkGraphics.h"
+#include "SkImageEncoder.h"
+#include "SkImageInfo.h"
+#include "SkStream.h"
+#include "SkSurface.h"
+
+
+void draw(SkCanvas* canvas) {
+#line 1
+SkPaint p;
+#line 2
+p.setColor(SK_ColorRED);
+#line 3
+p.setAntiAlias(true);
+#line 4
+p.setStyle(SkPaint::kStroke_Style);
+#line 5
+p.setStrokeWidth(10);
+#line 6
+
+#line 7
+canvas->drawLine(20, 20, 100, 100, p);
+#line 8
+
+}
diff --git a/experimental/webtry/seccomp_bpf.h b/experimental/webtry/seccomp_bpf.h
new file mode 100644
index 0000000000..8bbe99b7f8
--- /dev/null
+++ b/experimental/webtry/seccomp_bpf.h
@@ -0,0 +1,45 @@
+/*
+ * seccomp example for x86 (32-bit and 64-bit) with BPF macros
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Authors:
+ * Will Drewry <wad@chromium.org>
+ * Kees Cook <keescook@chromium.org>
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * A stripped down version of the file found in this tutorial: http://outflux.net/teach-seccomp/.
+ */
+#ifndef _SECCOMP_BPF_H_
+#define _SECCOMP_BPF_H_
+
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/prctl.h>
+
+#include <linux/unistd.h>
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+
+#define syscall_nr (offsetof(struct seccomp_data, nr))
+
+#define EXAMINE_SYSCALL \
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)
+
+#define ALLOW_SYSCALL(name) \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
+
+#define KILL_PROCESS \
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
+
+#endif /* _SECCOMP_BPF_H_ */
diff --git a/experimental/webtry/server.py b/experimental/webtry/server.py
deleted file mode 100644
index 8367b3e46a..0000000000
--- a/experimental/webtry/server.py
+++ /dev/null
@@ -1,79 +0,0 @@
-import BaseHTTPServer
-import base64
-import json
-import logging
-import string
-import subprocess
-
-HOST_NAME = 'localhost'
-PORT_NUMBER = 8765
-
-def runCode(usercode):
- f = open('template.cpp', 'rb')
- template = string.Template(f.read())
- f.close()
-
- code = template.substitute(usercode=usercode)
-
- f = open('result.cpp', 'wb')
- f.write(code)
- f.close()
-
- msg = ""
- img = ""
- try:
- logging.info("compiling")
- msg = subprocess.check_output('ninja -C ../../out/Debug webtry'.split())
- try:
- logging.info("running")
- msg = subprocess.check_output('../../out/Debug/webtry'.split())
- f = open('foo.png', 'rb')
- img = base64.b64encode(f.read())
- f.close()
- except subprocess.CalledProcessError as e:
- logging.info(e)
- msg = e.output
- except subprocess.CalledProcessError as e:
- logging.info(e)
- msg = e.output
-
- retval = {
- 'message': msg
- }
- if img:
- retval['img'] = img
- return retval
-
-class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
- def do_POST(self):
- logging.info("POST")
- body = ""
- l = self.rfile.readline()
- while l.strip() != "EOF":
- body += l
- l = self.rfile.readline()
- self.send_response(200)
- self.send_header("Content-type", "application/json")
- self.end_headers()
- resp = runCode(body)
- self.wfile.write(json.dumps(resp))
- self.end_headers()
-
- def do_GET(self):
- """Respond to a GET request."""
- self.send_response(200)
- self.send_header("Content-type", "text/html")
- self.end_headers()
- f = open('index.html', 'rb')
- self.wfile.write(f.read())
- f.close()
-
-if __name__ == '__main__':
- server_class = BaseHTTPServer.HTTPServer
- httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
- logging.info("Server Start: %s:%s" % (HOST_NAME, PORT_NUMBER))
- try:
- httpd.serve_forever()
- except KeyboardInterrupt:
- pass
- httpd.server_close()
diff --git a/experimental/webtry/setup/continue_install b/experimental/webtry/setup/continue_install
new file mode 100755
index 0000000000..0db794b13d
--- /dev/null
+++ b/experimental/webtry/setup/continue_install
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+# Don't execute this script directly, instead it is copied into the webtry
+# user's directory and executed as the user webtry by the webtry_setup.sh
+# script.
+#
+# See the README file for detailed installation instructions.
+cd
+pwd
+
+# Install depot_tools.
+if [ -d depot_tools ]; then
+ (cd depot_tools && git pull);
+else
+ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git;
+fi
+export PATH=$PATH:$HOME/depot_tools
+
+# Install Go
+if [ -d go ]; then
+ echo Go already installed.
+else
+ wget https://go.googlecode.com/files/go1.2.1.linux-amd64.tar.gz
+ tar -xzf go1.2.1.linux-amd64.tar.gz
+fi
+export GOROOT=$HOME/go
+export PATH=$PATH:$GOROOT/bin
+
+mkdir /home/webtry/cache
+mkdir /home/webtry/inout
+chmod 777 /home/webtry/inout
+
+# Sometimes you need to test patches on the server, to do that uncomment
+# the following commented out lines and update the PATCH env variable to the
+# name of the codereview to use.
+
+# rm -rf skia
+
+# Checkout the skia code and dependencies.
+mkdir skia
+cd skia
+gclient config --name . https://skia.googlesource.com/skia.git
+gclient sync
+git checkout master
+
+# PATCH=issue196723021_100001.diff
+# rm $PATCH
+# wget https://codereview.chromium.org/download/$PATCH
+# git apply $PATCH
+
+GYP_GENERATORS=ninja ./gyp_skia gyp/webtry.gyp gyp/most.gyp -Dskia_gpu=0
+ninja -C out/Debug webtry
+
+cd experimental/webtry
+
+go build webtry.go
diff --git a/experimental/webtry/setup/webtry_setup.sh b/experimental/webtry/setup/webtry_setup.sh
new file mode 100755
index 0000000000..6658ca8c97
--- /dev/null
+++ b/experimental/webtry/setup/webtry_setup.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Script to setup a GCE instance to run the webtry server.
+# For full instructions see the README file.
+sudo apt-get install schroot debootstrap monit
+sudo apt-get install g++ libfreetype6 libfreetype6-dev libpng12-0 libpng12-dev libglu1-mesa-dev mesa-common-dev freeglut3-dev libgif-dev libfontconfig libfontconfig-dev
+
+echo "Adding the webtry user account"
+sudo adduser webtry
+
+sudo cp continue_install /home/webtry/continue_install
+sudo chmod 766 /home/webtry/continue_install
+sudo chown webtry:webtry /home/webtry/continue_install
+sudo su webtry -c /home/webtry/continue_install
+
+sudo mkdir -p /srv/chroot/webtry
+sudo cp /home/webtry/skia/experimental/webtry/sys/webtry_schroot /etc/schroot/chroot.d/webtry
+
+sudo mkdir /srv/chroot/webtry/etc
+sudo mkdir /srv/chroot/webtry/bin
+sudo cp /bin/sh /srv/chroot/webtry/bin/sh
+
+# Copy all the dependent libraries into the schroot.
+sudo cp --parents `ldd /home/webtry/skia/out/Debug/webtry | cut -d " " -f 3` /srv/chroot/webtry
+sudo cp --parents `ldd /bin/sh | cut -d " " -f 3` /srv/chroot/webtry
+
+sudo cp /home/webtry/skia/experimental/webtry/sys/webtry_init /etc/init.d/webtry
+sudo cp /home/webtry/skia/experimental/webtry/sys/webtry_monit /etc/monit/conf.d/webtry
+sudo chmod 744 /etc/init.d/webtry
+
+# Confirm that monit is happy.
+sudo monit -t
diff --git a/experimental/webtry/sys/webtry_init b/experimental/webtry/sys/webtry_init
new file mode 100644
index 0000000000..359e19e46c
--- /dev/null
+++ b/experimental/webtry/sys/webtry_init
@@ -0,0 +1,159 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: webtry
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Start webtry.
+# Description: Web server for trying Skia C++ code.
+### END INIT INFO
+
+# Author: Joe Gregorio <jcgregorio@google.com>
+#
+# Copied from /etc/init.d/skeleton and modified only the following
+# environment variables and updated the start-stop-daemon calls
+# in do_start() to add --make-pidfile, --background, and --chuid.
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="The Skia webtry application."
+NAME=webtry
+DAEMON=/home/webtry/skia/experimental/webtry/$NAME
+DAEMON_ARGS="--use_chroot"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON --make-pidfile --background --chuid webtry --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON --make-pidfile --background --chuid webtry --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/experimental/webtry/sys/webtry_monit b/experimental/webtry/sys/webtry_monit
new file mode 100644
index 0000000000..5fccf8a84e
--- /dev/null
+++ b/experimental/webtry/sys/webtry_monit
@@ -0,0 +1,4 @@
+check process webtry with pidfile /var/run/webtry.pid
+ start program = "/etc/init.d/webtry start"
+ stop program = "/etc/init.d/webtry stop"
+
diff --git a/experimental/webtry/sys/webtry_schroot b/experimental/webtry/sys/webtry_schroot
new file mode 100644
index 0000000000..c2bdfd320d
--- /dev/null
+++ b/experimental/webtry/sys/webtry_schroot
@@ -0,0 +1,9 @@
+[webtry]
+description=Chroot jail for webtry runs.
+type=directory
+directory=/srv/chroot/webtry
+users=default,webtry
+root-groups=default
+personality=linux
+preserve-environment=false
+profile=minimal
diff --git a/experimental/webtry/template.cpp b/experimental/webtry/template.cpp
deleted file mode 100644
index 20b9086cde..0000000000
--- a/experimental/webtry/template.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include "SkCanvas.h"
-#include "SkGraphics.h"
-#include "SkImageEncoder.h"
-#include "SkImageInfo.h"
-#include "SkForceLinking.h"
-
-int main() {
- SkForceLinking(false);
- SkGraphics::Init();
-
- SkImageInfo info = SkImageInfo::MakeN32(300, 300, kPremul_SkAlphaType);
- SkBitmap bitmap;
- bitmap.setConfig(info);
- bitmap.allocPixels();
- SkCanvas c(bitmap);
- c.drawColor(SK_ColorWHITE);
-
- $usercode
-
- if (!SkImageEncoder::EncodeFile("foo.png", bitmap, SkImageEncoder::kPNG_Type, 100)) {
- printf("Failed to encode\n");
- }
-}
diff --git a/experimental/webtry/index.html b/experimental/webtry/templates/index.html
index b3903e675f..c630f14ef5 100644
--- a/experimental/webtry/index.html
+++ b/experimental/webtry/templates/index.html
@@ -13,6 +13,9 @@
padding: 0;
color: green;
}
+ #output {
+ color: #333;
+ }
</style>
</head>
<body>
@@ -27,11 +30,8 @@ int main() {
SkGraphics::Init();
SkImageInfo info = SkImageInfo::MakeN32(300, 300, kPremul_SkAlphaType);
- SkBitmap bitmap;
- bitmap.setConfig(info);
- bitmap.allocPixels();
- SkCanvas c(bitmap);
- c.drawColor(SK_ColorWHITE);
+ SkAutoTUnref&lt;SkSurface> surface(SkSurface::NewRaster(info));
+ SkCanvas* canvas = surface->getCanvas();
<textarea name='code' id='code' rows='20' cols='80'>SkPaint p;
p.setColor(SK_ColorRED);
@@ -39,7 +39,7 @@ p.setAntiAlias(true);
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(10);
-c.drawLine(20, 20, 100, 100, p);
+canvas->drawLine(20, 20, 100, 100, p);
</textarea>
if (!SkImageEncoder::EncodeFile("foo.png", bitmap, SkImageEncoder::kPNG_Type, 100)) {
@@ -48,12 +48,13 @@ c.drawLine(20, 20, 100, 100, p);
}
</code></pre>
+ <input type='button' value='Run' id='run'>
+
<p>Image appears here:</p>
<img id='img' src=''/>
<pre><code id='output'></code></pre>
- <input type='button' value='Run' id='run'>
<script type='text/javascript' charset='utf-8'>
var run = document.getElementById('run');
var code = document.getElementById('code');
@@ -90,7 +91,7 @@ c.drawLine(20, 20, 100, 100, p);
req.addEventListener('error', codeError);
req.overrideMimeType('application/json');
req.open('POST', '.', true);
- req.send(code.value + '\r\nEOF\r\n');
+ req.send(code.value);
}
</script>
</body>
diff --git a/experimental/webtry/templates/template.cpp b/experimental/webtry/templates/template.cpp
new file mode 100644
index 0000000000..3543b0cb24
--- /dev/null
+++ b/experimental/webtry/templates/template.cpp
@@ -0,0 +1,13 @@
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkForceLinking.h"
+#include "SkGraphics.h"
+#include "SkImageEncoder.h"
+#include "SkImageInfo.h"
+#include "SkStream.h"
+#include "SkSurface.h"
+
+
+void draw(SkCanvas* canvas) {
+{{.UserCode}}
+}
diff --git a/experimental/webtry/webtry.go b/experimental/webtry/webtry.go
new file mode 100644
index 0000000000..d70102a1c8
--- /dev/null
+++ b/experimental/webtry/webtry.go
@@ -0,0 +1,226 @@
+package main
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/base64"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "text/template"
+)
+
+const (
+ RESULT_COMPILE = `c++ -DSK_GAMMA_SRGB -DSK_GAMMA_APPLY_TO_A8 -DSK_SCALAR_TO_FLOAT_EXCLUDED -DSK_ALLOW_STATIC_GLOBAL_INITIALIZERS=1 -DSK_SUPPORT_GPU=0 -DSK_SUPPORT_OPENCL=0 -DSK_FORCE_DISTANCEFIELD_FONTS=0 -DSK_SCALAR_IS_FLOAT -DSK_CAN_USE_FLOAT -DSK_SAMPLES_FOR_X -DSK_BUILD_FOR_UNIX -DSK_USE_POSIX_THREADS -DSK_SYSTEM_ZLIB=1 -DSK_DEBUG -DSK_DEVELOPER=1 -I../../src/core -I../../src/images -I../../tools/flags -I../../include/config -I../../include/core -I../../include/pathops -I../../include/pipe -I../../include/effects -I../../include/ports -I../../src/sfnt -I../../include/utils -I../../src/utils -I../../include/images -g -fno-exceptions -fstrict-aliasing -Wall -Wextra -Winit-self -Wpointer-arith -Wno-unused-parameter -Wno-c++11-extensions -Werror -m64 -fno-rtti -Wnon-virtual-dtor -c ../../../cache/%s.cpp -o ../../../cache/%s.o`
+ LINK = `c++ -m64 -lstdc++ -lm -o ../../../inout/%s -Wl,--start-group ../../../cache/%s.o obj/experimental/webtry/webtry.main.o obj/experimental/webtry/webtry.syscall_reporter.o obj/gyp/libflags.a libskia_images.a libskia_core.a libskia_effects.a obj/gyp/libjpeg.a obj/gyp/libwebp_dec.a obj/gyp/libwebp_demux.a obj/gyp/libwebp_dsp.a obj/gyp/libwebp_enc.a obj/gyp/libwebp_utils.a libskia_utils.a libskia_opts.a libskia_opts_ssse3.a libskia_ports.a libskia_sfnt.a -Wl,--end-group -lpng -lz -lgif -lpthread -lfontconfig -ldl -lfreetype`
+)
+
+var (
+ // codeTemplate is the cpp code template the user's code is copied into.
+ codeTemplate *template.Template = nil
+
+ // index is the main index.html page we serve.
+ index []byte
+)
+
+// flags
+var (
+ useChroot = flag.Bool("use_chroot", false, "Run the compiled code in the schroot jail.")
+)
+
+// lineNumbers adds #line numbering to the user's code.
+func LineNumbers(c string) string {
+ lines := strings.Split(c, "\n")
+ ret := []string{}
+ for i, line := range lines {
+ ret = append(ret, fmt.Sprintf("#line %d", i+1))
+ ret = append(ret, line)
+ }
+ return strings.Join(ret, "\n")
+}
+
+func init() {
+ // Change the current working directory to the directory of the executable.
+ var err error
+ cwd, err := filepath.Abs(filepath.Dir(os.Args[0]))
+ if err != nil {
+ log.Fatal(err)
+ }
+ os.Chdir(cwd)
+
+ codeTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/template.cpp"))
+ if err != nil {
+ panic(err)
+ }
+ index, err = ioutil.ReadFile(filepath.Join(cwd, "templates/index.html"))
+ if err != nil {
+ panic(err)
+ }
+}
+
+// userCode is used in template expansion.
+type userCode struct {
+ UserCode string
+}
+
+// expandToFile expands the template and writes the result to the file.
+func expandToFile(filename string, code string, t *template.Template) error {
+ f, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ return t.Execute(f, struct{ UserCode string }{UserCode: code})
+}
+
+// expandCode expands the template into a file and calculate the MD5 hash.
+func expandCode(code string) (string, error) {
+ h := md5.New()
+ h.Write([]byte(code))
+ hash := fmt.Sprintf("%x", h.Sum(nil))
+ // At this point we are running in skia/experimental/webtry, making cache a
+ // peer directory to skia.
+ // TODO(jcgregorio) Make all relative directories into flags.
+ err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, codeTemplate)
+ return hash, err
+}
+
+// response is serialized to JSON as a response to POSTs.
+type response struct {
+ Message string `json:"message"`
+ Img string `json:"img"`
+}
+
+// doCmd executes the given command line string in either the out/Debug
+// directory or the inout directory. Returns the stdout, and stderr in the case
+// of a non-zero exit code.
+func doCmd(commandLine string, moveToDebug bool) (string, error) {
+ log.Printf("Command: %q\n", commandLine)
+ programAndArgs := strings.SplitN(commandLine, " ", 2)
+ program := programAndArgs[0]
+ args := []string{}
+ if len(programAndArgs) > 1 {
+ args = strings.Split(programAndArgs[1], " ")
+ }
+ cmd := exec.Command(program, args...)
+ abs, err := filepath.Abs("../../out/Debug")
+ if err != nil {
+ return "", fmt.Errorf("Failed to find absolute path to Debug directory.")
+ }
+ if moveToDebug {
+ cmd.Dir = abs
+ } else if !*useChroot { // Don't set cmd.Dir when using chroot.
+ abs, err := filepath.Abs("../../../inout")
+ if err != nil {
+ return "", fmt.Errorf("Failed to find absolute path to inout directory.")
+ }
+ cmd.Dir = abs
+ }
+ log.Printf("Run in directory: %q\n", cmd.Dir)
+ var stdOut bytes.Buffer
+ cmd.Stdout = &stdOut
+ var stdErr bytes.Buffer
+ cmd.Stderr = &stdErr
+ cmd.Start()
+ err = cmd.Wait()
+ message := stdOut.String()
+ log.Printf("StdOut: %s\n", message)
+ if err != nil {
+ log.Printf("Exit status: %s\n", err.Error())
+ log.Printf("StdErr: %s\n", stdErr.String())
+ message += stdErr.String()
+ return message, fmt.Errorf("Failed to run command.")
+ }
+ return message, nil
+}
+
+// reportError formats an HTTP error response and also logs the detailed error message.
+func reportError(w http.ResponseWriter, r *http.Request, err error, message string) {
+ m := response{
+ Message: message,
+ }
+ log.Printf("Error: %s\n%s", message, err.Error())
+ resp, err := json.Marshal(m)
+ if err != nil {
+ http.Error(w, "Failed to serialize a response", 500)
+ return
+ }
+ w.Write(resp)
+}
+
+// mainHandler handles the GET and POST of the main page.
+func mainHandler(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" {
+ w.Write(index)
+ } else if r.Method == "POST" {
+ w.Header().Set("Content-Type", "application/json")
+ b, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ reportError(w, r, err, "Failed to read a request body.")
+ return
+ }
+ hash, err := expandCode(LineNumbers(string(b)))
+ if err != nil {
+ reportError(w, r, err, "Failed to write the code to compile.")
+ return
+ }
+ message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
+ if err != nil {
+ reportError(w, r, err, "Failed to compile the code:\n"+message)
+ return
+ }
+ linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true)
+ if err != nil {
+ reportError(w, r, err, "Failed to link the code:\n"+linkMessage)
+ return
+ }
+ message += linkMessage
+ cmd := hash + " --out " + hash + ".png"
+ if *useChroot {
+ cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd
+ } else {
+ abs, err := filepath.Abs("../../../inout")
+ if err != nil {
+ reportError(w, r, err, "Failed to find executable directory.")
+ return
+ }
+ cmd = abs + "/" + cmd
+ }
+
+ execMessage, err := doCmd(cmd, false)
+ if err != nil {
+ reportError(w, r, err, "Failed to run the code:\n"+execMessage)
+ return
+ }
+ png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
+ if err != nil {
+ reportError(w, r, err, "Failed to open the generated PNG.")
+ return
+ }
+
+ m := response{
+ Message: message,
+ Img: base64.StdEncoding.EncodeToString([]byte(png)),
+ }
+ resp, err := json.Marshal(m)
+ if err != nil {
+ reportError(w, r, err, "Failed to serialize a response.")
+ return
+ }
+ w.Write(resp)
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ http.HandleFunc("/", mainHandler)
+ log.Fatal(http.ListenAndServe(":8000", nil))
+}