1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Imports Ruby's port to NativeClient (a.k.a NaCl).

Patch by Google Inc. [ruby-core:45073].

* configure.in (RUBY_NACL): New M4 func to configure variables for
  NaCl.
  (RUBY_NACL_CHECK_PEPPER_TYPES): New M4 func to check the old names
  of Pepper interface types.
  (BTESTRUBY): New variable to specify which ruby should be run on
  "make btest". NaCl can run the built binary by sel_ldr, but it need
  rbconfig.rb. So this variable is distinguished from $MINIRUBY.
  
* thread_pthread.c: Disabled some features on NaCl.

* io.c: ditto.

* process.c: ditto.

* signal.c: ditto.

* file.c: ditto.

* missing/flock.c: ditto.

* nacl/pepper_main.c: An example implementation of Pepper application
  that embeds Ruby.

* nacl/example.html: An example of web page that uses the Pepper
  application.

* nacl/nacl-config.rb: Detects variants of NaCl SDK.

* nacl/GNUmakefile.in: Makefile template for NaCl specific build
  process.

* nacl/package.rb: script for packaging a NaCl-Ruby embedding
  application. 

* nacl/reate_nmf.rb: Wrapper script of create_nmf.py

* dln.c (dln_load): Added a hack to call on NaCl.

* util.c (ruby_getcwd): Path to the current directort is not available
  on NaCl.



git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@35672 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
yugui 2012-05-17 02:48:59 +00:00
parent 0a7aada5d1
commit 76bc2d1ed7
37 changed files with 1855 additions and 36 deletions

60
nacl/GNUmakefile.in Normal file
View file

@ -0,0 +1,60 @@
# 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)
PATH+=:$(NACL_TOOLCHAIN_DIR)/bin
PYTHON=@PYTHON@
PPROGRAM=pepper-$(PROGRAM)
PEPPER_LIBS=-lppapi
PROGRAM_NMF=$(PROGRAM:.nexe=.nmf)
PPROGRAM_NMF=$(PPROGRAM:.nexe=.nmf)
GNUmakefile: $(srcdir)/nacl/GNUmakefile.in
$(PPROGRAM): $(PROGRAM) pepper_main.$(OBJEXT)
$(Q)$(MAKE) $(MFLAGS) PROGRAM=$(PPROGRAM) MAINOBJ="pepper_main.$(OBJEXT)" LIBS="$(LIBS) $(PEPPER_LIBS)" program
$(PROGRAM_NMF) $(PPROGRAM_NMF): $(@:.nmf=.nexe) nacl/create_nmf.rb
.PHONY: pprogram package show_naclflags
.SUFFIXES: .nexe .nmf
.nexe.nmf:
$(ECHO) generating manifest $@
$(Q)$(MINIRUBY) $(srcdir)/nacl/create_nmf.rb --verbose=$(V) $(@:.nmf=.nexe) $@
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
ruby.$(OBJEXT):
@$(ECHO) compiling $<
$(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@.tmp -c $<
$(Q) $(OBJCOPY) --weaken-symbol=rb_load_file $@.tmp $@
@-$(RM) $@.tmp
file.$(OBJEXT):
@$(ECHO) compiling $<
$(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@.tmp -c $<
$(Q) $(OBJCOPY) --weaken-symbol=rb_file_load_ok $@.tmp $@
@-$(RM) $@.tmp
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
$(ECHO) generating manifest $@
$(Q)$(MINIRUBY) $(srcdir)/nacl/package.rb $(prefix)
showflags: show_naclflags
show_naclflags:
@echo " PATH = $(PATH)"
@echo " NACL_SDK_ROOT = $(NACL_SDK_ROOT)"
clean-local::
-$(RM) $(PPROGRAM) pepper_main.$(OBJEXT) $(PROGRAM_NMF) $(PPRGORAM_NMF)

34
nacl/README.nacl Normal file
View file

@ -0,0 +1,34 @@
=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 18 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 vairanble to the path to the Native Client SDK you installed:
$ export NACL_SDK_ROOT=/home/yugui/src/nacl_sdk/pepper_16
(3) Configure
$ ./configure --prefix=/tmp/nacl-ruby --host=x86_64-nacl --with-baseruby=/path/to/ruby-1.9.3
(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, ...
= Copyright
* Copyright 2012 Google Inc. All Rights Reserved.
* Author: yugui@google.com (Yugui Sonoda)
=end

70
nacl/create_nmf.rb Normal file
View file

@ -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

15
nacl/dirent.h Normal file
View file

@ -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

150
nacl/example.html Normal file
View file

@ -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>

7
nacl/ioctl.h Normal file
View file

@ -0,0 +1,7 @@
// Copyright 2012 Google Inc. All Rights Reserved.
// Author: yugui@google.com (Yugui Sonoda)
#ifndef RUBY_NACL_IOCTL_H
#define RUBY_NACL_IOCTL_H
int ioctl(int fd, int request, ...);
struct flock{};
#endif

61
nacl/nacl-config.rb Normal file
View file

@ -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$/, '')
ARCH = cpu_nick.sub('x86_64', 'x86-64').sub(/i.86/, '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) }
HOST_LIB = File.join(SDK_ROOT, 'toolchain', config['NACL_TOOLCHAIN'], HOST, "lib#{lib_suffix}")
INSTALL_PROGRAM = config['INSTALL_PROGRAM']
INSTALL_LIBRARY = config['INSTALL_DATA']
SEL_LDR = [
File.join(SDK_ROOT, 'toolchain', config['NACL_TOOLCHAIN'], 'bin', "sel_ldr_#{cpu_nick}"),
File.join(SDK_ROOT, 'tools', "sel_ldr_#{cpu_nick}")
].find{|path| File.executable?(path)}
IRT_CORE = [
File.join(SDK_ROOT, 'toolchain', config['NACL_TOOLCHAIN'], 'bin', "irt_core_#{cpu_nick}.nexe"),
File.join(SDK_ROOT, 'tools', "irt_core_#{cpu_nick}.nexe")
].find{|path| File.executable?(path)}
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

109
nacl/package.rb Normal file
View file

@ -0,0 +1,109 @@
#!/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 ]
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

924
nacl/pepper_main.c Normal file
View file

@ -0,0 +1,924 @@
/******************************************************************************
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 <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 "ruby/ruby.h"
#include "vm_core.h"
#include "eval_intern.h"
#include "gc.h"
#include "node.h"
RUBY_GLOBAL_SETUP
#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_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)
{
#ifdef PPB_VAR_INTERFACE_1_0
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);
}
#ifdef PPB_VAR_INTERFACE_1_0
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;
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
obj = rb_obj_as_string(obj);
}
POP_TAG();
switch (state) {
case 0:
return pruby_str_to_var(obj);
case TAG_RAISE:
rb_set_errinfo(Qnil);
return pruby_cstr_to_var(error);
default:
fprintf(stderr, "Fatal error white converting the result to a string\n");
exit(EXIT_FAILURE);
}
}
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)
{
volatile VALUE path;
VALUE load_path = GET_VM()->load_path;
path = rb_usascii_str_new_cstr("lib/ruby/2.0.0");
rb_ary_push(load_path, path);
path = rb_usascii_str_new_cstr("lib/ruby/2.0.0/x86_64-nacl");
rb_ary_push(load_path, path);
path = rb_usascii_str_new_cstr(".");
rb_ary_push(load_path, path);
}
static void*
init_libraries(void* data)
{
extern void Init_enc();
extern void Init_ext();
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;
}
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
init_loadpath();
Init_enc();
Init_ext();
}
POP_TAG();
pthread_mutex_unlock(&instance->mutex);
if (state) {
volatile VALUE err = rb_errinfo();
err = rb_obj_as_string(err);
} else {
instance->async_call_args = "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
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)
{
struct PepperInstance* const instance = (struct PepperInstance*)data;
volatile VALUE src = (VALUE)instance->async_call_args;
volatile VALUE iseq, result = Qnil;
volatile VALUE filename;
NODE* tree;
volatile int state;
rb_thread_t *th;
rb_env_t *env;
RUBY_INIT_STACK;
PUSH_TAG();
if (pthread_mutex_lock(&instance->mutex)) {
perror("pepper-ruby:pthread_mutex_lock");
return 0;
}
if ((state = EXEC_TAG()) == 0) {
th = GET_THREAD();
SAVE_ROOT_JMPBUF(th, {
th->mild_compile_error++;
tree = rb_compile_string("(pepper-ruby)", src, 1);
th->mild_compile_error--;
if (RTEST(rb_errinfo())) {
rb_exc_raise(rb_errinfo());
}
{
VALUE toplevel_binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
rb_binding_t *bind;
GetBindingPtr(toplevel_binding, bind);
GetEnvPtr(bind->env, env);
}
filename = rb_usascii_str_new("(pepper-ruby)", strlen("(pepper-ruby)"));
th->parse_in_eval--;
th->base_block = &env->block;
iseq = rb_iseq_new_main(tree, filename, filename);
th->parse_in_eval++;
th->base_block = 0;
result = rb_iseq_eval_main(iseq);
});
}
POP_TAG();
pthread_mutex_unlock(&instance->mutex);
switch (state) {
case 0:
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;
case TAG_RAISE:
result = rb_errinfo();
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;
default:
fprintf(stderr, "Fatal error\n");
exit(EXIT_FAILURE);
}
}
/******************************************************************************
* 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;
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).
*/
#ifdef PPP_INSTANCE_INTERFACE_1_0
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 get_browser_interface)
{
module_id = a_module_id;
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()
{
ruby_cleanup(0);
}
/******************************************************************************
* Overwrites rb_file_load_ok
******************************************************************************/
static void
load_ok_internal(void* data, int32_t unused)
{
/* PPAPI main thread */
struct PepperInstance* const instance = (struct PepperInstance*)data;
const char *const path = (const char*)instance->async_call_args;
PP_Resource req;
int result;
instance->url_loader = loader_interface->Create(instance->instance);
req = request_interface->Create(instance->instance);
request_interface->SetProperty(
req, PP_URLREQUESTPROPERTY_METHOD, pruby_cstr_to_var("HEAD"));
request_interface->SetProperty(
req, PP_URLREQUESTPROPERTY_URL, pruby_cstr_to_var(path));
result = loader_interface->Open(
instance->url_loader, req,
PP_MakeCompletionCallback(pruby_async_return_int, instance));
if (result != PP_OK_COMPLETIONPENDING) {
pruby_async_return_int(instance, result);
}
}
static void
pruby_file_fetch_check_response(void* data, int32_t unused)
{
/* PPAPI main thread */
PP_Resource res;
struct PepperInstance* const instance = (struct PepperInstance*)data;
res = loader_interface->GetResponseInfo(instance->url_loader);
if (res) {
struct PP_Var status =
response_interface->GetProperty(res, PP_URLRESPONSEPROPERTY_STATUSCODE);
if (status.type == PP_VARTYPE_INT32) {
pruby_async_return_int(instance, status.value.as_int / 100 == 2 ? PP_OK : PP_ERROR_FAILED);
return;
}
else {
messaging_interface->PostMessage(
instance->instance, pruby_cstr_to_var("Unexpected type: ResponseInfoInterface::GetProperty"));
}
}
else {
messaging_interface->PostMessage(
instance->instance, pruby_cstr_to_var("Failed to open URL: URLLoaderInterface::GetResponseInfo"));
}
pruby_async_return_int(instance, PP_ERROR_FAILED);
}
int
rb_file_load_ok(const char *path)
{
struct PepperInstance* const instance = GET_PEPPER_INSTANCE();
if (path[0] == '.' && path[1] == '/') path += 2;
instance->async_call_args = (void*)path;
core_interface->CallOnMainThread(
0, PP_MakeCompletionCallback(load_ok_internal, instance), 0);
if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
perror("pepper-ruby:pthread_cond_wait");
return 0;
}
if (instance->async_call_result.as_int != PP_OK) {
fprintf(stderr, "Failed to open URL: %d: %s\n",
instance->async_call_result.as_int, path);
return 0;
}
core_interface->CallOnMainThread(
0, PP_MakeCompletionCallback(pruby_file_fetch_check_response, instance), 0);
if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
perror("pepper-ruby:pthread_cond_wait");
return 0;
}
return instance->async_call_result.as_int == PP_OK;
}
/******************************************************************************
* Overwrites rb_load_file
******************************************************************************/
static void
load_file_internal(void* data, int32_t unused)
{
/* PPAPI main thread */
struct PepperInstance* const instance = (struct PepperInstance*)data;
const char *const path = (const char*)instance->async_call_args;
PP_Resource req;
int result;
instance->url_loader = loader_interface->Create(instance->instance);
req = request_interface->Create(instance->instance);
request_interface->SetProperty(
req, PP_URLREQUESTPROPERTY_METHOD, pruby_cstr_to_var("GET"));
request_interface->SetProperty(
req, PP_URLREQUESTPROPERTY_URL, pruby_cstr_to_var(path));
result = loader_interface->Open(
instance->url_loader, req,
PP_MakeCompletionCallback(pruby_async_return_int, instance));
if (result != PP_OK_COMPLETIONPENDING) {
pruby_async_return_int(instance, result);
}
}
static void
load_file_read_contents_callback(void *data, int result)
{
struct PepperInstance* const instance = (struct PepperInstance*)data;
if (result > 0) {
rb_str_buf_cat(instance->async_call_result.as_value,
instance->buf, result);
loader_interface->ReadResponseBody(
instance->url_loader, instance->buf, 1000, PP_MakeCompletionCallback(load_file_read_contents_callback, instance));
}
else if (result == 0) {
pruby_async_return_value(data, instance->async_call_result.as_value);
}
else {
pruby_async_return_value(data, INT2FIX(result));
}
}
static void
load_file_read_contents(void *data, int result)
{
struct PepperInstance* const instance = (struct PepperInstance*)data;
instance->async_call_result.as_value = rb_str_new(0, 0);
loader_interface->ReadResponseBody(
instance->url_loader, instance->buf, 1000, PP_MakeCompletionCallback(load_file_read_contents_callback, instance));
}
void*
rb_load_file(const char *path)
{
const char *real_path;
struct PepperInstance* instance;
if (path[0] != '.' || path[1] != '/') path += 2;
instance = GET_PEPPER_INSTANCE();
instance->async_call_args = (void*)path;
core_interface->CallOnMainThread(
0, PP_MakeCompletionCallback(load_file_internal, instance), 0);
if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
perror("pepper-ruby:pthread_cond_wait");
return 0;
}
if (instance->async_call_result.as_int != PP_OK) {
fprintf(stderr, "Failed to open URL: %d: %s\n",
instance->async_call_result.as_int, path);
return 0;
}
core_interface->CallOnMainThread(
0, PP_MakeCompletionCallback(pruby_file_fetch_check_response, instance), 0);
if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
perror("pepper-ruby:pthread_cond_wait");
return 0;
}
if (instance->async_call_result.as_int != PP_OK) return 0;
core_interface->CallOnMainThread(
0, PP_MakeCompletionCallback(load_file_read_contents, instance), 0);
if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
perror("pepper-ruby:pthread_cond_wait");
return 0;
}
if (FIXNUM_P(instance->async_call_result.as_value)) {
return 0;
}
else if (RB_TYPE_P(instance->async_call_result.as_value, T_STRING)) {
VALUE str = instance->async_call_result.as_value;
return rb_compile_cstr(path, RSTRING_PTR(str), RSTRING_LEN(str), 0);
}
else {
return 0;
}
}

8
nacl/resource.h Normal file
View file

@ -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

7
nacl/select.h Normal file
View file

@ -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

6
nacl/signal.h Normal file
View file

@ -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

10
nacl/stat.h Normal file
View file

@ -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

9
nacl/unistd.h Normal file
View file

@ -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

11
nacl/utime.h Normal file
View file

@ -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