diff options
Diffstat (limited to 'jni/ruby/nacl')
-rw-r--r-- | jni/ruby/nacl/GNUmakefile.in | 100 | ||||
-rw-r--r-- | jni/ruby/nacl/README.nacl | 51 | ||||
-rw-r--r-- | jni/ruby/nacl/create_nmf.rb | 70 | ||||
-rw-r--r-- | jni/ruby/nacl/dirent.h | 15 | ||||
-rw-r--r-- | jni/ruby/nacl/example.html | 150 | ||||
-rwxr-xr-x | jni/ruby/nacl/nacl-config.rb | 61 | ||||
-rw-r--r-- | jni/ruby/nacl/package.rb | 113 | ||||
-rw-r--r-- | jni/ruby/nacl/pepper_main.c | 732 | ||||
-rw-r--r-- | jni/ruby/nacl/resource.h | 8 | ||||
-rw-r--r-- | jni/ruby/nacl/select.h | 7 | ||||
-rw-r--r-- | jni/ruby/nacl/signal.h | 6 | ||||
-rw-r--r-- | jni/ruby/nacl/stat.h | 10 | ||||
-rw-r--r-- | jni/ruby/nacl/unistd.h | 9 | ||||
-rw-r--r-- | jni/ruby/nacl/utime.h | 11 |
14 files changed, 1343 insertions, 0 deletions
diff --git a/jni/ruby/nacl/GNUmakefile.in b/jni/ruby/nacl/GNUmakefile.in new file mode 100644 index 0000000..1fffcee --- /dev/null +++ b/jni/ruby/nacl/GNUmakefile.in @@ -0,0 +1,100 @@ +# Copyright 2012 Google Inc. All Rights Reserved. +# Author: yugui@google.com (Yugui Sonoda) + +include Makefile +-include uncommon.mk + +NACL_SDK_ROOT=@NACL_SDK_ROOT@ +NACL_TOOLCHAIN=@NACL_TOOLCHAIN@ +NACL_TOOLCHAIN_DIR=$(NACL_SDK_ROOT)/toolchain/$(NACL_TOOLCHAIN) +CXX=@CXX@ + +# Don't override CC/LD/etc if they are already set to absolute +# paths (this is the case when building in the naclports tree). +ifeq ($(dir $(CC)),./) +CC:=$(NACL_TOOLCHAIN_DIR)/bin/$(CC) +endif +ifeq ($(dir $(CXX)),./) +CXX:=$(NACL_TOOLCHAIN_DIR)/bin/$(CXX) +endif +ifeq ($(dir $(LD)),./) +LD:=$(NACL_TOOLCHAIN_DIR)/bin/$(LD) +endif +ifeq ($(dir $(NM)),./) +NM:=$(NACL_TOOLCHAIN_DIR)/bin/$(NM) +endif +ifeq ($(dir $(AR)),./) +AR:=$(NACL_TOOLCHAIN_DIR)/bin/$(AR) +endif +ifeq ($(dir $(AS)),./) +AS:=$(NACL_TOOLCHAIN_DIR)/bin/$(AS) +endif +ifeq ($(dir $(RANLIB)),./) +RANLIB:=$(NACL_TOOLCHAIN_DIR)/bin/$(RANLIB) +endif +ifeq ($(dir $(OBJDUMP)),./) +OBJDUMP:=$(NACL_TOOLCHAIN_DIR)/bin/$(OBJDUMP) +endif +ifeq ($(dir $(OBJCOPY)),./) +OBJCOPY:=$(NACL_TOOLCHAIN_DIR)/bin/$(OBJCOPY) +endif +PYTHON=@PYTHON@ + +PPROGRAM=pepper-$(PROGRAM) +PEPPER_LIBS=-lppapi -lnacl_io +PROGRAM_NMF=$(PROGRAM:$(EXEEXT)=.nmf) +PPROGRAM_NMF=$(PPROGRAM:$(EXEEXT)=.nmf) + +GNUmakefile: $(srcdir)/nacl/GNUmakefile.in +$(PPROGRAM): $(PROGRAM) pepper_main.$(OBJEXT) + $(Q)$(MAKE) $(MFLAGS) PROGRAM=$(PPROGRAM) MAINOBJ="pepper_main.$(OBJEXT)" LIBS="$(LIBS) $(PEPPER_LIBS)" CC="$(CXX)" program +$(PROGRAM_NMF) $(PPROGRAM_NMF): $(@:.nmf=$(EXEEXT)) nacl/create_nmf.rb + +.PHONY: pprogram package show_naclflags +.SUFFIXES: $(EXEEXT) .nmf +$(EXEEXT).nmf: + $(ECHO) generating manifest $@ + $(Q)$(MINIRUBY) $(srcdir)/nacl/create_nmf.rb --verbose=$(V) $(@:.nmf=$(EXEEXT)) $@ + +pepper_main.$(OBJEXT): $(srcdir)/nacl/pepper_main.c + @$(ECHO) compiling nacl/pepper_main.c + $(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c $(srcdir)/nacl/pepper_main.c + +.rbconfig.time: + @$(MAKE) .rbconfig.raw.time RBCONFIG=.rbconfig.raw.time + @sed \ + -e 's!CONFIG\["CC"\] = .*!CONFIG\["CC"\] = "$(CC)"!' \ + -e 's!CONFIG\["LD"\] = .*!CONFIG\["LD"\] = "$(LD)"!' \ + -e 's!CONFIG\["NM"\] = .*!CONFIG\["NM"\] = "$(NM)"!' \ + -e 's!CONFIG\["AR"\] = .*!CONFIG\["AR"\] = "$(AR)"!' \ + -e 's!CONFIG\["AS"\] = .*!CONFIG\["AS"\] = "$(AS)"!' \ + -e 's!CONFIG\["RANLIB"\] = .*!CONFIG\["RANLIB"\] = "$(RANLIB)"!' \ + -e 's!CONFIG\["OBJDUMP"\] = .*!CONFIG\["OBJDUMP"\] = "$(OBJDUMP)"!' \ + -e 's!CONFIG\["OBJCOPY"\] = .*!CONFIG\["OBJCOPY"\] = "$(OBJCOPY)"!' \ + -i.bak rbconfig.rb + @touch .rbconfig.time + +all: pprogram +main: $(PROGRAM_NMF) +pprogram: showflags $(PPROGRAM) $(PPROGRAM_NMF) +program: $(PROGRAM_NMF) +prog: pprogram + +package: pprogram install-lib install-ext-comm install-ext-arch + $(INSTALL_DATA) $(srcdir)/nacl/example.html $(prefix) + $(ECHO) generating manifest $@ + $(Q)$(MINIRUBY) $(srcdir)/nacl/package.rb $(prefix) + +showflags: show_naclflags + +show_naclflags: + @echo " NACL_SDK_ROOT = $(NACL_SDK_ROOT)" + @echo " NM = $(NM)" + @echo " AR = $(AR)" + @echo " AS = $(AS)" + @echo " RANLIB = $(RANLIB)" + @echo " OBJDUMP = $(OBJDUMP)" + @echo " OBJCOPY = $(OBJCOPY)" + +clean-local:: + -$(RM) $(PPROGRAM) pepper_main.$(OBJEXT) $(PROGRAM_NMF) $(PPRGORAM_NMF) diff --git a/jni/ruby/nacl/README.nacl b/jni/ruby/nacl/README.nacl new file mode 100644 index 0000000..77140e0 --- /dev/null +++ b/jni/ruby/nacl/README.nacl @@ -0,0 +1,51 @@ +=begin += Native Client port of Ruby + += How to build +== Prerequisites +You need to install the following things before building NaCl port of Ruby. +* Ruby 1.9.3 or later +* Python 2.6 or later +* NativeClient SDK pepper 37 or later +* GNU make + +== Steps +(1) Extract all files from the tarball: + $ tar xzf ruby-X.Y.Z.tar.gz +(2) Set NACL_SDK_ROOT environment variable to the path to the Native Client SDK you installed: + $ export NACL_SDK_ROOT=/home/yugui/src/nacl_sdk/pepper_37 +(3) Configure + $ ./configure --prefix=/tmp/nacl-ruby --host=x86_64-nacl --with-baseruby=/path/to/ruby-1.9.3-or-later +(4) Make + $ make + $ make package + +Now you have ruby.nexe. You can run it by sel_ldr in NaCl SDK. + +"make package" installs "pepper-ruby.nexe", an example Pepper application that +embeds Ruby", and libraries to $prefix. You can run it by the following steps: +(5) Publish the $prefix directory on a web server +(6) Visit the example.html on the web server by a browser that implements Pepper 18 or later. + -- e.g., Chrome 18 implements Pepper 18, Chrome 19 implements Pepper 19, ... + +=== Example Configurations +* x86_32 Native Client + $ ./configure --prefix=/tmp/nacl-ruby \ + --host=i686-nacl \ + --with-baseruby=/path/to/ruby-1.9.3-or-later +* arm Native Client + $ ./configure --prefix=/tmp/nacl-ruby \ + --host=arm-nacl \ + --with-newlib \ + --with-baseruby=/path/to/ruby-1.9.3-or-later +* Portable Native Client + $ ./configure --prefix=/tmp/nacl-ruby \ + --host=le32-nacl \ + --with-newlib \ + --with-static-linked-ext \ + --with-baseruby=/path/to/ruby-1.9.3-or-later + += Copyright +* Copyright 2012 Google Inc. All Rights Reserved. +* Author: yugui@google.com (Yugui Sonoda) +=end diff --git a/jni/ruby/nacl/create_nmf.rb b/jni/ruby/nacl/create_nmf.rb new file mode 100644 index 0000000..cdfe7ad --- /dev/null +++ b/jni/ruby/nacl/create_nmf.rb @@ -0,0 +1,70 @@ +#!/usr/bin/ruby +# Copyright:: Copyright 2012 Google Inc. +# License:: All Rights Reserved. +# Original Author:: Yugui Sonoda (mailto:yugui@google.com) +# +# Wrapper for create_nmf.py / generate_nmf.py + +require File.join(File.dirname(__FILE__), 'nacl-config') + +include NaClConfig +$verbosity = 0 + +def usage_and_exit + $stderr.puts "Usage: #{$PROGRAM_NAME} [--verbose=N] path/to/input.nexe path/to/output.nmf" + exit false +end + +def create_dynamically_linked(nmf, exe) + cmd = [ + PYTHON, CREATE_NMF, + '-o', nmf, + '-D', OBJDUMP, + '-L', HOST_LIB, + exe + ] + puts cmd.join(' ') if $verbosity > 0 + exec(*cmd) +end + +def create_statically_linked(nmf, exe) + File.open(nmf, "w") {|f| + f.write <<-EOS.gsub(/^ {6}/, '') + { + "program": { + "#{ARCH}": { + "url": "#{exe}" + } + } + } + EOS + } +end + +def main + while m = ARGV.first.match(/--([a-z-]+)(?:=(\S+))?/) + case m[1] + when 'verbose' + usage_and_exit unless m[2][/\A[0-9]+\z/] + $verbosity = m[2].to_i + when 'help' + usage_end_exit + end + ARGV.shift + end + + usage_and_exit if ARGV.size < 2 + + exe, nmf = ARGV[0], ARGV[1] + if newlib? + create_statically_linked(nmf, exe) + else + create_dynamically_linked(nmf, exe) + end +end + +if __FILE__ == $0 + main() +end + + diff --git a/jni/ruby/nacl/dirent.h b/jni/ruby/nacl/dirent.h new file mode 100644 index 0000000..31bdad3 --- /dev/null +++ b/jni/ruby/nacl/dirent.h @@ -0,0 +1,15 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * Author: yugui@google.com (Yugui Sonoda) + */ +#ifndef RUBY_NACL_DIRENT_H +#define RUBY_NACL_DIRENT_H + +/* NaCl SDK 0.3 has implementations of dir functions but no declaration in + * dirent.h */ +int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); +void rewinddir(DIR *dirp); +long telldir(DIR *dirp); +void seekdir(DIR *dirp, long offset); + +#endif diff --git a/jni/ruby/nacl/example.html b/jni/ruby/nacl/example.html new file mode 100644 index 0000000..3cc3329 --- /dev/null +++ b/jni/ruby/nacl/example.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<html> +<head> + <title>Ruby Example</title> + + <script type="text/javascript"> + RubyModule = null; // Global application object. + statusText = 'NO-STATUS'; + rubyReady = false; + + // Indicate load success. + function moduleDidLoad() { + RubyModule = document.getElementById('ruby'); + form = document.getElementById('source-form'); + form.style.display = "block"; + updateStatus('SUCCESS'); + } + + function evalSource() { + if (rubyReady) { + RubyModule.postMessage('eval:' + document.getElementById("source").value); + } else { + throw "Not yet ready"; + } + return false; + } + + function RubyError(message) { + this.message = message; + this.toString = function() { + return message; + } + } + + function FatalError(message) { + this.message = message; + } + + // The 'message' event handler. This handler is fired when the NaCl module + // posts a message to the browser by calling PPB_Messaging.PostMessage() + // (in C) or pp::Instance.PostMessage() (in C++). This implementation + // simply displays the content of the message in an alert panel. + function handleMessage(message_event) { + var raw = message_event.data; + var components; + if (raw.indexOf("error") == 0) { + components = raw.split(":", 2); + throw new RubyError(components[1]); + } else if (raw.indexOf("return") == 0) { + components = raw.split(":", 2); + document.getElementById("result").value = components[1]; + } else if (raw == "rubyReady") { + rubyReady = true; + } else { + throw new FatalError(raw); + } + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + if (RubyModule == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('status_field'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Module Ruby</h1> +<p> + <!-- Load the published .nexe. This includes the 'nacl' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the + instruction set architecture ('x86-32', 'x86-64', etc.); the value is a URL + for the desired NaCl module. + To load the debug versions of your .nexes, set the 'nacl' attribute to the + _dbg.nmf version of the manifest file. + + Note: Since this NaCl module does not use any real-estate in the browser, + it's width and height are set to 0. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener'); + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="ruby" + width="0" height="0" + src="ruby.nmf" + type="application/x-nacl" /> + <form id="source-form" action="#" method="post" style="display:none" + onsubmit="evalSource(); return false"> + <table> + <tbody> + <tr> + <th>Source</th> + <td> + <textarea rows="10" cols="80" id="source"></textarea> + <input type="submit" /> + </td> + </tr> + <tr> + <th>Result</th> + <td> + <textarea rows="10" cols="80" id="result"></textarea> + </td> + </tr> + </tbody> + </table> + </form> + </div> +</p> + +<h2>Status</h2> +<div id="status_field">NO-STATUS</div> +</body> +</html> diff --git a/jni/ruby/nacl/nacl-config.rb b/jni/ruby/nacl/nacl-config.rb new file mode 100755 index 0000000..6648130 --- /dev/null +++ b/jni/ruby/nacl/nacl-config.rb @@ -0,0 +1,61 @@ +#!/usr/bin/ruby +# +# Copyright:: Copyright 2012 Google Inc. +# License:: All Rights Reserved. +# Original Author:: Yugui Sonoda (mailto:yugui@google.com) +# +# Convenient functions/constants for native client specific configurations. +require 'rbconfig' + +module NaClConfig + config = RbConfig::CONFIG + + cpu_nick = config['host_alias'].sub(/-gnu$|-newlib$/, '').sub(/-nacl$/, '').sub(/i.86/, 'x86_32') + ARCH = cpu_nick.sub('x86_64', 'x86-64').sub('x86_32', 'x86-32') + HOST = ARCH.sub(/x86-../, 'x86_64') + '-nacl' + + lib_suffix = config['host_cpu'][/i.86/] ? '32' : '' + PYTHON = config['PYTHON'] + OBJDUMP = config['OBJDUMP'] + SDK_ROOT = config['NACL_SDK_ROOT'] + CREATE_NMF = [ + File.join(SDK_ROOT, 'build_tools', 'nacl_sdk_scons', 'site_tools', 'create_nmf.py'), + File.join(SDK_ROOT, 'tools', 'create_nmf.py') + ].find{|path| File.exist?(path) } or raise "No create_nmf found" + HOST_LIB = File.join(SDK_ROOT, 'toolchain', config['NACL_TOOLCHAIN'], HOST, "lib#{lib_suffix}") + + NACL_LIB = File.join(SDK_ROOT, 'lib', config['NACL_LIB_PATH'], 'Release') + + INSTALL_PROGRAM = config['INSTALL_PROGRAM'] + INSTALL_LIBRARY = config['INSTALL_DATA'] + + if cpu_nick == 'x86_64' or cpu_nick == 'x86_32' + SEL_LDR = File.join(SDK_ROOT, 'tools', "sel_ldr_#{cpu_nick}") + IRT_CORE = File.join(SDK_ROOT, 'tools', "irt_core_#{cpu_nick}.nexe") + raise "No sel_ldr found" if not File.executable?(SEL_LDR) + raise "No irt_core found" if not File.exists?(IRT_CORE) + end + RUNNABLE_LD = File.join(HOST_LIB, 'runnable-ld.so') + + module_function + + def newlib? + RbConfig::CONFIG['NACL_SDK_VARIANT'] == 'newlib' + end + + def self.config(name) + if NaClConfig::const_defined?(name.upcase) + NaClConfig::const_get(name.upcase) + elsif NaClConfig::respond_to?(name) and NaClConfig::method(name).arity == 0 + NaClConfig::send(name) + else + raise ArgumentError, "No such config: #{name}" + end + end +end + +if $0 == __FILE__ + ARGV.each do |arg| + puts NaClConfig::config(arg) + end +end diff --git a/jni/ruby/nacl/package.rb b/jni/ruby/nacl/package.rb new file mode 100644 index 0000000..9ba9588 --- /dev/null +++ b/jni/ruby/nacl/package.rb @@ -0,0 +1,113 @@ +#!/usr/bin/ruby +# Copyright:: Copyright 2012 Google Inc. +# License:: All Rights Reserved. +# Original Author:: Yugui Sonoda (mailto:yugui@google.com) +# +# Generates a runnable package of the pepper API example. + +require File.join(File.dirname(__FILE__), 'nacl-config') +require 'json' +require 'find' +require 'fileutils' + +include NaClConfig + +class Installation + include NaClConfig + + SRC_DIRS = [ + Dir.pwd, + HOST_LIB, + NACL_LIB, + ] + + def initialize(destdir) + @destdir = destdir + @manifest = { + "files" => {} + } + ruby_libs.each do |path| + raise "Collision of #{path}" if @manifest['files'].key? path + @manifest['files'][path] = { + ARCH => { + "url" => path + } + } + if path[/\.so$/] + alternate_path = path.gsub('/', "_") + raise "Collision of #{alternate_path}" if @manifest['files'].key? alternate_path + @manifest['files'][alternate_path] = { + ARCH => { + "url" => path + } + } + end + end + end + + def manifest + @manifest.dup + end + + def install_program(basename) + do_install_binary(basename, File.join(@destdir, "bin", ARCH)) + @manifest["program"] = { + ARCH => { + "url" => File.join("bin", ARCH, basename) + } + } + end + + def install_library(name, basename) + do_install_binary(basename, File.join(@destdir, "lib", ARCH)) + @manifest["files"][name] = { + ARCH => { + "url" => File.join("lib", ARCH, basename) + } + } + end + + private + def do_install_binary(basename, dest_dir) + full_path = nil + catch(:found) { + SRC_DIRS.each do |path| + full_path = File.join(path, basename) + if File.exist? full_path + throw :found + end + end + raise Errno::ENOENT, "No such file to install: %s" % basename + } + FileUtils.mkdir_p dest_dir + system("#{INSTALL_PROGRAM} #{full_path} #{dest_dir}") + end + + def ruby_libs + Find.find(RbConfig::CONFIG['rubylibdir']).select{|path| File.file?(path) }.map{|path| path.sub("#{@destdir}/", "") } + end +end + +def install(destdir) + inst = Installation.new(destdir) + manifest = JSON.parse(File.read("pepper-ruby.nmf")) + + program = File.basename(manifest['program'][ARCH]['url']) + inst.install_program(program) + + manifest['files'].each do |name, attr| + inst.install_library(name, File.basename(attr[ARCH]["url"])) + end + + File.open(File.join(destdir, "ruby.nmf"), "w") {|f| + f.puts JSON.pretty_generate(inst.manifest) + } +end + +def main + install(ARGV[0]) +end + +if __FILE__ == $0 + main() +end diff --git a/jni/ruby/nacl/pepper_main.c b/jni/ruby/nacl/pepper_main.c new file mode 100644 index 0000000..168e8f9 --- /dev/null +++ b/jni/ruby/nacl/pepper_main.c @@ -0,0 +1,732 @@ +/****************************************************************************** + Copyright 2012 Google Inc. All Rights Reserved. + Author: yugui@google.com (Yugui Sonoda) + ******************************************************************************/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <pthread.h> +#include <sys/stat.h> +#include <sys/mount.h> +#include <fcntl.h> +#include <pthread.h> +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_module.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb.h" +#include "ppapi/c/ppb_core.h" +#include "ppapi/c/ppb_file_ref.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/c/ppb_messaging.h" +#include "ppapi/c/ppb_url_loader.h" +#include "ppapi/c/ppb_url_request_info.h" +#include "ppapi/c/ppb_url_response_info.h" +#include "ppapi/c/ppb_var.h" +#include "ppapi/c/ppp.h" +#include "ppapi/c/ppp_instance.h" +#include "ppapi/c/ppp_messaging.h" +#include "nacl_io/nacl_io.h" + +#include "verconf.h" +#include "ruby/ruby.h" +#include "version.h" +#include "gc.h" + +#ifdef HAVE_STRUCT_PPB_CORE +typedef struct PPB_Core PPB_Core; +#endif +#ifdef HAVE_STRUCT_PPB_MESSAGING +typedef struct PPB_Messaging PPB_Messaging; +#endif +#ifdef HAVE_STRUCT_PPB_VAR +typedef struct PPB_Var PPB_Var; +#endif +#ifdef HAVE_STRUCT_PPB_URLLOADER +typedef struct PPB_URLLoader PPB_URLLoader; +#endif +#ifdef HAVE_STRUCT_PPB_URLREQUESTINFO +typedef struct PPB_URLRequestInfo PPB_URLRequestInfo; +#endif +#ifdef HAVE_STRUCT_PPB_URLRESPONSEINFO +typedef struct PPB_URLResponseInfo PPB_URLResponseInfo; +#endif +#ifdef HAVE_STRUCT_PPP_INSTANCE +typedef struct PPP_Instance PPP_Instance; +#endif + +static PP_Module module_id = 0; +static PPB_GetInterface get_browser_interface = NULL; +static PPB_Core* core_interface = NULL; +static PPB_Messaging* messaging_interface = NULL; +static PPB_Var* var_interface = NULL; +static PPB_URLLoader* loader_interface = NULL; +static PPB_URLRequestInfo* request_interface = NULL; +static PPB_URLResponseInfo* response_interface = NULL; +static PPB_FileRef* fileref_interface = NULL; +static struct st_table* instance_data = NULL; + +static VALUE instance_table = Qundef; + +static PP_Instance current_instance = 0; + +/****************************************************************************** + * State of instance + ******************************************************************************/ + +static void inst_mark(void *const ptr); +static void inst_free(void *const ptr); +static size_t inst_memsize(void *const ptr); +static const rb_data_type_t pepper_instance_data_type = { + "PepperInstance", + { inst_mark, inst_free, inst_memsize } +}; + +struct PepperInstance { + PP_Instance instance; + PP_Resource url_loader; + VALUE self; + void* async_call_args; + union { + int32_t as_int; + const char* as_str; + VALUE as_value; + } async_call_result; + char buf[1000]; + + pthread_t th; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +struct PepperInstance* +pruby_get_instance(PP_Instance instance) +{ + VALUE self = rb_hash_aref(instance_table, INT2FIX(instance)); + if (RTEST(self)) { + struct PepperInstance *inst; + TypedData_Get_Struct(self, struct PepperInstance, &pepper_instance_data_type, inst); + return inst; + } + else { + return NULL; + } +} + +#define GET_PEPPER_INSTANCE() (pruby_get_instance(current_instance)) + +struct PepperInstance* +pruby_register_instance(PP_Instance instance) +{ + VALUE obj; + struct PepperInstance *data; + obj = TypedData_Make_Struct(rb_cData, struct PepperInstance, &pepper_instance_data_type, data); + data->self = obj; + data->instance = instance; + data->url_loader = 0; + + pthread_mutex_init(&data->mutex, NULL); + pthread_cond_init(&data->cond, NULL); + + rb_hash_aset(instance_table, INT2FIX(instance), obj); + return data; +} + +int +pruby_unregister_instance(PP_Instance instance) +{ + VALUE inst = rb_hash_delete(instance_table, INT2FIX(instance)); + return RTEST(inst); +} + +static void +inst_mark(void *const ptr) +{ + RUBY_MARK_ENTER("PepperInstance"0); + if (ptr) { + const struct PepperInstance* inst = (struct PepperInstance*)ptr; + RUBY_MARK_UNLESS_NULL(inst->async_call_result.as_value); + } + RUBY_MARK_LEAVE("PepperInstance"0); +} + +static void +inst_free(void *const ptr) +{ + ruby_xfree(ptr); +} + +static size_t +inst_memsize(void *const ptr) +{ + if (ptr) { + const struct PepperInstance* inst = (struct PepperInstance*)ptr; + return sizeof(*inst); + } else { + return 0; + } +} + +void +pruby_async_return_int(void* data, int32_t result) +{ + /* PPAPI main thread */ + struct PepperInstance* const instance = (struct PepperInstance*)data; + instance->async_call_result.as_int = result; + if (pthread_cond_signal(&instance->cond)) { + perror("pepper-ruby:pthread_cond_signal"); + } +} + +void +pruby_async_return_str(void* data, const char *result) +{ + /* PPAPI main thread */ + struct PepperInstance* const instance = (struct PepperInstance*)data; + instance->async_call_result.as_str = result; + if (pthread_cond_signal(&instance->cond)) { + perror("pepper-ruby:pthread_cond_signal"); + } +} + +void +pruby_async_return_value(void* data, VALUE value) +{ + /* PPAPI main thread */ + struct PepperInstance* const instance = (struct PepperInstance*)data; + instance->async_call_result.as_value = value; + if (pthread_cond_signal(&instance->cond)) { + perror("pepper-ruby:pthread_cond_signal"); + } +} +/****************************************************************************** + * Conversion between Ruby's VALUE, Pepper's Var and C string + ******************************************************************************/ + +/** + * Creates a new string PP_Var from C string. The resulting object will be a + * refcounted string object. It will be AddRef()ed for the caller. When the + * caller is done with it, it should be Release()d. + * @param[in] str C string to be converted to PP_Var + * @return PP_Var containing string. + */ +static struct PP_Var +pruby_cstr_to_var(const char* str) +{ +#ifndef PPB_VAR_INTERFACE_1_1 + if (var_interface != NULL) + return var_interface->VarFromUtf8(module_id, str, strlen(str)); + return PP_MakeUndefined(); +#else + return var_interface->VarFromUtf8(str, strlen(str)); +#endif +} + +/** + * Returns a mutable C string contained in the @a var or NULL if @a var is not + * string. This makes a copy of the string in the @a var and adds a NULL + * terminator. Note that VarToUtf8() does not guarantee the NULL terminator on + * the returned string. See the comments for VarToUtf8() in ppapi/c/ppb_var.h + * for more info. The caller is responsible for freeing the returned memory. + * @param[in] var PP_Var containing string. + * @return a mutable C string representation of @a var. + * @note The caller is responsible for freeing the returned string. + */ +static char* +pruby_var_to_cstr(struct PP_Var var) +{ + uint32_t len = 0; + if (var_interface != NULL) { + const char* var_c_str = var_interface->VarToUtf8(var, &len); + if (len > 0) { + char* c_str = (char*)malloc(len + 1); + memcpy(c_str, var_c_str, len); + c_str[len] = '\0'; + return c_str; + } + } + return NULL; +} + +static struct PP_Var +pruby_str_to_var(volatile VALUE str) +{ + if (!RB_TYPE_P(str, T_STRING)) { + fprintf(stderr, "[BUG] Unexpected object type: %x\n", TYPE(str)); + exit(EXIT_FAILURE); + } +#ifndef PPB_VAR_INTERFACE_1_1 + if (var_interface != NULL) { + return var_interface->VarFromUtf8(module_id, RSTRING_PTR(str), RSTRING_LEN(str)); + } +#else + return var_interface->VarFromUtf8(RSTRING_PTR(str), RSTRING_LEN(str)); +#endif + return PP_MakeUndefined(); +} + +static struct PP_Var +pruby_obj_to_var(volatile VALUE obj) +{ + static const char* const error = + "throw 'Failed to convert the result to a JavaScript object';"; + int state; + obj = rb_protect(&rb_obj_as_string, obj, &state); + if (!state) { + return pruby_str_to_var(obj); + } + else { + return pruby_cstr_to_var(error); + } +} + +int +pruby_var_equal_to_cstr_p(struct PP_Var lhs, const char* rhs) +{ + uint32_t len = 0; + if (var_interface == NULL) { + return 0; + } + else { + const char* const cstr = var_interface->VarToUtf8(lhs, &len); + return strncmp(cstr, rhs, len) == 0; + } +} + +int +pruby_var_prefixed_p(struct PP_Var var, const char* prefix) +{ + uint32_t len = 0; + if (var_interface == NULL) { + return 0; + } + else { + const char* const cstr = var_interface->VarToUtf8(var, &len); + const size_t prefix_len = strlen(prefix); + return len >= prefix_len && memcmp(cstr, prefix, len) == 0; + } +} + + +/****************************************************************************** + * Messaging + ******************************************************************************/ + +/* Posts the given C string as a message. + * @param data pointer to a NULL-terminated string */ +void +pruby_post_cstr(void* data) +{ + /* PPAPI main thread */ + struct PepperInstance* const instance = (struct PepperInstance*)data; + const char* const msg = (const char*)instance->async_call_args; + messaging_interface->PostMessage(instance->instance, + pruby_cstr_to_var(msg)); +} + +/* Posts the given Ruby VALUE as a message. + * @param data a VALUE casted to void* */ +void +pruby_post_value(void* data) +{ + /* PPAPI main thread */ + struct PepperInstance* const instance = (struct PepperInstance*)data; + volatile VALUE value = (VALUE)instance->async_call_args; + messaging_interface->PostMessage(instance->instance, pruby_obj_to_var(value)); +} + + + +/****************************************************************************** + * Ruby initialization + ******************************************************************************/ + +static void +init_loadpath(void) +{ + ruby_incpush("lib/ruby/"RUBY_LIB_VERSION); + ruby_incpush("lib/ruby/"RUBY_LIB_VERSION"/"RUBY_PLATFORM); + ruby_incpush("."); +} + +static VALUE +init_libraries_internal(VALUE unused) +{ + extern void Init_enc(); + extern void Init_ext(); + + init_loadpath(); + Init_enc(); + Init_ext(); + return Qnil; +} + +static void* +init_libraries(void* data) +{ + int state; + struct PepperInstance* const instance = (struct PepperInstance*)data; + current_instance = instance->instance; + + if (pthread_mutex_lock(&instance->mutex)) { + perror("pepper-ruby:pthread_mutex_lock"); + return 0; + } + rb_protect(&init_libraries_internal, Qnil, &state); + pthread_mutex_unlock(&instance->mutex); + + if (state) { + volatile VALUE err = rb_errinfo(); + err = rb_obj_as_string(err); + } else { + instance->async_call_args = (void*)"rubyReady"; + core_interface->CallOnMainThread( + 0, PP_MakeCompletionCallback(pruby_post_cstr, instance), 0); + } + return NULL; +} + +static int +init_libraries_if_necessary(void) +{ + static int initialized = 0; + if (!initialized) { + struct PepperInstance* const instance = GET_PEPPER_INSTANCE(); + int err; + initialized = 1; + err = pthread_create(&instance->th, NULL, &init_libraries, instance); + if (err) { + fprintf(stderr, "pepper_ruby:pthread_create: %s\n", strerror(err)); + exit(EXIT_FAILURE); + } + pthread_detach(instance->th); + } + return 0; +} + +static int +reopen_fd(int fd, const char* path, int flags) { + int fd2 = open(path, flags); + if (fd2 < 0) { + perror("open fd"); + return -1; + } + if (dup2(fd2, fd) < 0) { + perror("dup2 fd"); + return -1; + } + if (close(fd2)) { + perror("close old fd"); + return -1; + } + return fd; +} + +static int +pruby_init(void) +{ + RUBY_INIT_STACK; + ruby_init(); + + instance_table = rb_hash_new(); + rb_gc_register_mark_object(instance_table); + + return 0; +} + + +/****************************************************************************** + * Ruby evaluation + ******************************************************************************/ + +static void* +pruby_eval(void* data) +{ + extern VALUE ruby_eval_string_from_file_protect(const char* src, const char* path, int* state); + struct PepperInstance* const instance = (struct PepperInstance*)data; + volatile VALUE src = (VALUE)instance->async_call_args; + volatile VALUE result = Qnil; + volatile int state; + + RUBY_INIT_STACK; + + if (pthread_mutex_lock(&instance->mutex)) { + perror("pepper-ruby:pthread_mutex_lock"); + return 0; + } + result = ruby_eval_string_from_file_protect( + RSTRING_PTR(src), "(pepper-ruby)", &state); + pthread_mutex_unlock(&instance->mutex); + + if (!state) { + instance->async_call_args = + rb_str_concat(rb_usascii_str_new_cstr("return:"), + rb_obj_as_string(result)); + core_interface->CallOnMainThread( + 0, PP_MakeCompletionCallback(pruby_post_value, instance), 0); + return NULL; + } + else { + rb_set_errinfo(Qnil); + instance->async_call_args = + rb_str_concat(rb_usascii_str_new_cstr("error:"), + rb_obj_as_string(result)); + core_interface->CallOnMainThread( + 0, PP_MakeCompletionCallback(pruby_post_value, instance), 0); + return NULL; + } +} + + +/****************************************************************************** + * Pepper Module callbacks + ******************************************************************************/ + +/** + * Called when the NaCl module is instantiated on the web page. The identifier + * of the new instance will be passed in as the first argument (this value is + * generated by the browser and is an opaque handle). This is called for each + * instantiation of the NaCl module, which is each time the <embed> tag for + * this module is encountered. + * + * If this function reports a failure (by returning @a PP_FALSE), the NaCl + * module will be deleted and DidDestroy will be called. + * @param[in] instance The identifier of the new instance representing this + * NaCl module. + * @param[in] argc The number of arguments contained in @a argn and @a argv. + * @param[in] argn An array of argument names. These argument names are + * supplied in the <embed> tag, for example: + * <embed id="nacl_module" dimensions="2"> + * will produce two arguments, one named "id" and one named "dimensions". + * @param[in] argv An array of argument values. These are the values of the + * arguments listed in the <embed> tag. In the above example, there will + * be two elements in this array, "nacl_module" and "2". The indices of + * these values match the indices of the corresponding names in @a argn. + * @return @a PP_TRUE on success. + */ +static PP_Bool +Instance_DidCreate(PP_Instance instance, + uint32_t argc, const char* argn[], const char* argv[]) +{ + struct PepperInstance* data = pruby_register_instance(instance); + current_instance = instance; + + nacl_io_init_ppapi(instance, get_browser_interface); + + if (mount("", "/dev2", "dev", 0, "")) { + perror("mount dev"); + return PP_FALSE; + } + if (reopen_fd(0, "/dev2/stdin", O_RDONLY) < 0) { + perror("reopen stdin"); + return PP_FALSE; + } + if (reopen_fd(1, "/dev2/stdout", O_WRONLY) < 0) { + perror("reopen stdout"); + return PP_FALSE; + } + if (reopen_fd(2, "/dev2/console1", O_WRONLY) < 0) { + perror("reopen stderr"); + return PP_FALSE; + } + + /* TODO(yugui) Unmount original /dev */ + + if (mount("/lib", "/lib", "httpfs", + 0, "allow_cross_origin_requests=false")) { + perror("mount httpfs"); + return PP_FALSE; + } + + return init_libraries_if_necessary() ? PP_FALSE : PP_TRUE; +} + +/** + * Called when the NaCl module is destroyed. This will always be called, + * even if DidCreate returned failure. This routine should deallocate any data + * associated with the instance. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + */ +static void Instance_DidDestroy(PP_Instance instance) { + struct PepperInstance* data = pruby_get_instance(instance); + core_interface->ReleaseResource(data->url_loader); + pruby_unregister_instance(instance); +} + +/** + * Called when the position, the size, or the clip rect of the element in the + * browser that corresponds to this NaCl module has changed. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] position The location on the page of this NaCl module. This is + * relative to the top left corner of the viewport, which changes as the + * page is scrolled. + * @param[in] clip The visible region of the NaCl module. This is relative to + * the top left of the plugin's coordinate system (not the page). If the + * plugin is invisible, @a clip will be (0, 0, 0, 0). + */ +#ifndef PPP_INSTANCE_INTERFACE_1_1 +static void +Instance_DidChangeView(PP_Instance instance, + const struct PP_Rect* position, + const struct PP_Rect* clip) +{ +} +#else +static void +Instance_DidChangeView(PP_Instance instance, PP_Resource view_resource) +{ +} +#endif + +/** + * Notification that the given NaCl module has gained or lost focus. + * Having focus means that keyboard events will be sent to the NaCl module + * represented by @a instance. A NaCl module's default condition is that it + * will not have focus. + * + * Note: clicks on NaCl modules will give focus only if you handle the + * click event. You signal if you handled it by returning @a true from + * HandleInputEvent. Otherwise the browser will bubble the event and give + * focus to the element on the page that actually did end up consuming it. + * If you're not getting focus, check to make sure you're returning true from + * the mouse click in HandleInputEvent. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] has_focus Indicates whether this NaCl module gained or lost + * event focus. + */ +static void +Instance_DidChangeFocus(PP_Instance instance, PP_Bool has_focus) +{ +} + +/** + * Handler that gets called after a full-frame module is instantiated based on + * registered MIME types. This function is not called on NaCl modules. This + * function is essentially a place-holder for the required function pointer in + * the PPP_Instance structure. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance. + * @return PP_FALSE. + */ +static PP_Bool +Instance_HandleDocumentLoad(PP_Instance instance, PP_Resource url_loader) +{ + /* NaCl modules do not need to handle the document load function. */ + return PP_FALSE; +} + + +/** + * Handler for messages coming in from the browser via postMessage. The + * @a var_message can contain anything: a JSON string; a string that encodes + * method names and arguments; etc. For example, you could use JSON.stringify + * in the browser to create a message that contains a method name and some + * parameters, something like this: + * var json_message = JSON.stringify({ "myMethod" : "3.14159" }); + * nacl_module.postMessage(json_message); + * On receipt of this message in @a var_message, you could parse the JSON to + * retrieve the method name, match it to a function call, and then call it with + * the parameter. + * @param[in] instance The instance ID. + * @param[in] message The contents, copied by value, of the message sent from + * browser via postMessage. + */ +void +Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message) +{ + char* const message = pruby_var_to_cstr(var_message); + size_t message_len = strlen(message); + current_instance = instance; + + if (strstr(message, "eval:") != NULL) { + volatile VALUE src; + struct PepperInstance* const instance_data = GET_PEPPER_INSTANCE(); + int err; +#define EVAL_PREFIX_LEN 5 + src = rb_str_new(message + EVAL_PREFIX_LEN, message_len - EVAL_PREFIX_LEN); + instance_data->async_call_args = (void*)src; + err = pthread_create(&instance_data->th, NULL, &pruby_eval, instance_data); + if (err) { + fprintf(stderr, "pepper_ruby:pthread_create: %s\n", strerror(err)); + exit(EXIT_FAILURE); + } + pthread_detach(instance_data->th); + } + free(message); +} + +/** + * Entry points for the module. + * Initialize instance interface and scriptable object class. + * @param[in] a_module_id Module ID + * @param[in] get_browser_interface Pointer to PPB_GetInterface + * @return PP_OK on success, any other value on failure. + */ +PP_EXPORT int32_t +PPP_InitializeModule(PP_Module a_module_id, PPB_GetInterface a_get_browser_interface) +{ + module_id = a_module_id; + get_browser_interface = a_get_browser_interface; + core_interface = (PPB_Core*)(get_browser_interface(PPB_CORE_INTERFACE)); + if (core_interface == NULL) return PP_ERROR_NOINTERFACE; + + var_interface = (PPB_Var*)(get_browser_interface(PPB_VAR_INTERFACE)); + if (var_interface == NULL) return PP_ERROR_NOINTERFACE; + + messaging_interface = (PPB_Messaging*)(get_browser_interface(PPB_MESSAGING_INTERFACE)); + if (messaging_interface == NULL) return PP_ERROR_NOINTERFACE; + + loader_interface = (PPB_URLLoader*)(get_browser_interface(PPB_URLLOADER_INTERFACE)); + if (loader_interface == NULL) return PP_ERROR_NOINTERFACE; + + request_interface = (PPB_URLRequestInfo*)(get_browser_interface(PPB_URLREQUESTINFO_INTERFACE)); + if (request_interface == NULL) return PP_ERROR_NOINTERFACE; + + response_interface = (PPB_URLResponseInfo*)(get_browser_interface(PPB_URLRESPONSEINFO_INTERFACE)); + if (response_interface == NULL) return PP_ERROR_NOINTERFACE; + + fileref_interface = (PPB_FileRef*)(get_browser_interface(PPB_FILEREF_INTERFACE)); + if (fileref_interface == NULL) return PP_ERROR_NOINTERFACE; + + return pruby_init() ? PP_ERROR_FAILED : PP_OK; +} + +/** + * Returns an interface pointer for the interface of the given name, or NULL + * if the interface is not supported. + * @param[in] interface_name name of the interface + * @return pointer to the interface + */ +PP_EXPORT const void* +PPP_GetInterface(const char* interface_name) +{ + if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) { + static PPP_Instance instance_interface = { + &Instance_DidCreate, + &Instance_DidDestroy, + &Instance_DidChangeView, + &Instance_DidChangeFocus, + &Instance_HandleDocumentLoad + }; + return &instance_interface; + } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { + static PPP_Messaging messaging_interface = { + &Messaging_HandleMessage + }; + return &messaging_interface; + } + return NULL; +} + +/** + * Called before the plugin module is unloaded. + */ +PP_EXPORT void +PPP_ShutdownModule(void) +{ + ruby_cleanup(0); +} diff --git a/jni/ruby/nacl/resource.h b/jni/ruby/nacl/resource.h new file mode 100644 index 0000000..57ca53b --- /dev/null +++ b/jni/ruby/nacl/resource.h @@ -0,0 +1,8 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * Author: yugui@google.com (Yugui Sonoda) + * */ +#ifndef RUBY_NACL_RESOURCE_H +#define RUBY_NACL_RESOURCE_H +int getrusage(int who, struct rusage *usage); +#endif diff --git a/jni/ruby/nacl/select.h b/jni/ruby/nacl/select.h new file mode 100644 index 0000000..921721a --- /dev/null +++ b/jni/ruby/nacl/select.h @@ -0,0 +1,7 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// Author: yugui@google.com (Yugui Sonoda) +#ifndef RUBY_NACL_SELECT_H +#define RUBY_NACL_SELECT_H +int select(int num_fds, fd_set *in_fds, fd_set *out_fds, + fd_set *ex_fds, struct timeval *timeout); +#endif diff --git a/jni/ruby/nacl/signal.h b/jni/ruby/nacl/signal.h new file mode 100644 index 0000000..54832de --- /dev/null +++ b/jni/ruby/nacl/signal.h @@ -0,0 +1,6 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// Author: yugui@google.com (Yugui Sonoda) +#ifndef RUBY_NACL_SIGNAL_H +#define RUBY_NACL_SIGNAL_H +int kill(pid_t pid, int signal); +#endif diff --git a/jni/ruby/nacl/stat.h b/jni/ruby/nacl/stat.h new file mode 100644 index 0000000..7be40ad --- /dev/null +++ b/jni/ruby/nacl/stat.h @@ -0,0 +1,10 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * Author: yugui@google.com (Yugui Sonoda) + * */ +#ifndef RUBY_NACL_STAT_H +#define RUBY_NACL_STAT_H +mode_t umask(mode_t mask); +struct stat; +int lstat(const char* path, struct stat* result); +#endif diff --git a/jni/ruby/nacl/unistd.h b/jni/ruby/nacl/unistd.h new file mode 100644 index 0000000..1c97390 --- /dev/null +++ b/jni/ruby/nacl/unistd.h @@ -0,0 +1,9 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// Author: yugui@google.com (Yugui Sonoda) +#ifndef RUBY_NACL_UNISTD_H +#define RUBY_NACL_UNISTD_H +int seteuid(pid_t pid); +int setegid(pid_t pid); +int truncate(const char* path, off_t new_size); +int ftruncate(int fd, off_t new_size); +#endif diff --git a/jni/ruby/nacl/utime.h b/jni/ruby/nacl/utime.h new file mode 100644 index 0000000..9691005 --- /dev/null +++ b/jni/ruby/nacl/utime.h @@ -0,0 +1,11 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * Author: yugui@google.com (Yugui Sonoda) + */ + +#ifndef RUBY_NACL_UTIME_H +#define RUBY_NACL_UTIME_H +#include <utime.h> +int utime(const char *filename, const struct utimbuf *times); +int utimes(const char *filename, const struct timeval times[2]); +#endif |