wasval 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c40e62ec75efb8c895a87bdf907ffe4e339d1954e5e4ea7368e2652732e3156
4
- data.tar.gz: 964d7ad7932d2fe91a8e27a87a188ac54a0de84eb008407da778bab3c13f6d3a
3
+ metadata.gz: 51ff6fbf16b70ddb40f11ff08c901eb580276c667d3477aec4cf30de524abe81
4
+ data.tar.gz: d859cac0bad0f52c865105abb4749799453f618e7f3eb66b45043de172085f81
5
5
  SHA512:
6
- metadata.gz: ab1cbfd5146a97b4c06852070840a20060b1101ee6d28a27f98f0c5fdfaa68979c198c03c88abefa0ec7fa9d6483defe8be58002f2a8d2cdc04820ebc900baf9
7
- data.tar.gz: 9523a2fd9f67374216d3f3d5a5f4d1e8072e821a2563b777522f21ef20d07c8004b3ffeedf0aeec6dad7d19809f2a20140b8d0ec775808f583e8142211f17627
6
+ metadata.gz: c5e2c5b5c54127f9d8b4e517b3d68a9764e7a758934f0055317d8e52de355e33a985edbbb2ce1e61271955b1537672a6264b2f7375b5e57430e79d16ba307444
7
+ data.tar.gz: fd4aee2c209d178e182bb5655c895d72c2e809cf3db46ed3618900dac4becbdc5a9d7b82c245d8a6c4b9bef56e33cd3d47ac1496f74a3fc431c7e1c58a49def1
data/README.md CHANGED
@@ -50,6 +50,36 @@ You can customize the destination via the `WASVAL_RUBY_WASM_PATH` environment va
50
50
  Wasval::Install::RubyWasm.new(dest: "/path/to/ruby.wasm").download
51
51
  ```
52
52
 
53
+ ### Bundling gems into the binary (`include_gems`)
54
+
55
+ By default, the installed `ruby.wasm` binary does not include the standard library or gems, so `require` calls inside the sandbox will fail. To bundle the standard library (and any gems installed in the `usr` directory of the tarball) into the binary, use the `include_gems: true` option:
56
+
57
+ ```ruby
58
+ Wasval::Install::RubyWasm.new(
59
+ include_gems: true,
60
+ pack_output: "/path/to/ruby-packed.wasm",
61
+ serialized_dest: "/path/to/ruby.cwasm"
62
+ ).install
63
+ ```
64
+
65
+ This extracts the `usr` directory from the downloaded tarball, packs it into the binary via `rbwasm pack`, and serializes the result. The packed binary is written to `pack_output` (defaults to `~/.wasval/ruby-packed.wasm`), and the serialized module is written to `serialized_dest`.
66
+
67
+ If you have a pre-existing `usr` directory (e.g. with additional gems installed), pass its path via `include_gems:`:
68
+
69
+ ```ruby
70
+ Wasval::Install::RubyWasm.new(
71
+ include_gems: true,
72
+ include_gems: "/path/to/usr"
73
+ ).install
74
+ ```
75
+
76
+ After installing with `include_gems: true`, point `WASVAL_RUBY_CWASM_PATH` to the serialized binary and `require` will work inside the sandbox:
77
+
78
+ ```ruby
79
+ result = Wasval.execute("require 'json'; JSON.parse('1')")
80
+ result.status # => :success
81
+ ```
82
+
53
83
  ## Usage
54
84
 
55
85
  Set the `WASVAL_RUBY_WASM_PATH` environment variable to point to the `ruby.wasm` binary, then execute Ruby code:
@@ -67,7 +97,7 @@ result.success? # => true
67
97
  ```ruby
68
98
  Wasval.configure do |config|
69
99
  config.timeout = 10 # seconds (default: 5)
70
- config.memory_limit = 256 # MB (default: 128)
100
+ config.memory_limit = 32 # MB (default: 16)
71
101
  end
72
102
  ```
73
103
 
data/lib/wasval/config.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Wasval
4
4
  class Config
5
5
  DEFAULT_TIMEOUT = 5
6
- DEFAULT_MEMORY_LIMIT = 128
6
+ DEFAULT_MEMORY_LIMIT = 16
7
7
 
8
8
  attr_accessor :timeout, :memory_limit
9
9
 
@@ -19,6 +19,9 @@ module Wasval
19
19
  else
20
20
  raise ArgumentError.new "Please specify 'WASVAL_RUBY_WASM_PATH' or 'WASVAL_RUBY_CWASM_PATH' env"
21
21
  end
22
+
23
+ @engine.start_epoch_interval(1000)
24
+ @baseline_memory_mb = measure_startup_memory_mb
22
25
  end
23
26
 
24
27
  def execute(code:, timeout:, memory_limit:)
@@ -42,15 +45,13 @@ module Wasval
42
45
 
43
46
  store = Wasmtime::Store.new(@engine,
44
47
  wasi_p1_config: wasi_config,
45
- limits: { memory_size: memory_limit * 1024 * 1024 }
48
+ limits: { memory_size: (@baseline_memory_mb + memory_limit) * 1024 * 1024 }
46
49
  )
47
50
  store.set_epoch_deadline(timeout)
48
51
 
49
52
  linker = Wasmtime::Linker.new(@engine)
50
53
  Wasmtime::WASI::P1.add_to_linker_sync(linker)
51
54
 
52
- @engine.start_epoch_interval(1000)
53
-
54
55
  begin
55
56
  linker.instantiate(store, @mod).invoke("_start")
56
57
  classify_output(stdout_buf, stderr_buf, store)
@@ -77,6 +78,32 @@ module Wasval
77
78
 
78
79
  private
79
80
 
81
+ def measure_startup_memory_mb
82
+ stdout_buf = +""
83
+ stderr_buf = +""
84
+ wasi_config = Wasmtime::WasiConfig.new
85
+ .set_argv(["ruby", "-"])
86
+ .set_stdin_string("")
87
+ .set_stdout_buffer(stdout_buf, 1024)
88
+ .set_stderr_buffer(stderr_buf, 1024)
89
+
90
+ probe_store = Wasmtime::Store.new(@engine,
91
+ wasi_p1_config: wasi_config,
92
+ limits: { memory_size: 512 * 1024 * 1024 }
93
+ )
94
+ probe_store.set_epoch_deadline(30)
95
+
96
+ linker = Wasmtime::Linker.new(@engine)
97
+ Wasmtime::WASI::P1.add_to_linker_sync(linker)
98
+
99
+ begin
100
+ linker.instantiate(probe_store, @mod).invoke("_start")
101
+ rescue Wasmtime::WasiExit
102
+ end
103
+
104
+ (probe_store.max_linear_memory_consumed.to_f / (1024 * 1024)).ceil
105
+ end
106
+
80
107
  def wrapped_code(code)
81
108
  <<~RUBY
82
109
  begin
@@ -17,14 +17,19 @@ module Wasval
17
17
  DEFAULT_INSTALL_DIR = File.expand_path("~/.wasval")
18
18
  DEFAULT_BINARY_NAME = "ruby.wasm"
19
19
  DEFAULT_SERIALIZED_BINARY_NAME = "ruby.cwasm"
20
+ DEFAULT_PACKED_BINARY_NAME = "ruby-packed.wasm"
21
+ DEFAULT_USR_DIR_NAME = "usr"
20
22
 
21
- attr_reader :dest, :serialized_dest, :ruby_version, :profile
23
+ attr_reader :dest, :serialized_dest, :ruby_version, :profile, :pack_dirs, :pack_output, :include_gems
22
24
 
23
- def initialize(dest: nil, serialized_dest: nil, ruby_version: nil, profile: :full)
25
+ def initialize(dest: nil, serialized_dest: nil, ruby_version: nil, profile: :full, pack_dirs: nil, pack_output: nil, include_gems: nil)
24
26
  @ruby_version = ruby_version || ENV["WASVAL_RUBY_VERSION"] || default_ruby_version
25
27
  @profile = profile.to_s
26
28
  @dest = dest || ENV["WASVAL_RUBY_WASM_PATH"] || File.join(DEFAULT_INSTALL_DIR, DEFAULT_BINARY_NAME)
27
29
  @serialized_dest = serialized_dest || ENV["WASVAL_RUBY_CWASM_PATH"] || File.join(File.dirname(@dest), DEFAULT_SERIALIZED_BINARY_NAME)
30
+ @pack_dirs = pack_dirs
31
+ @pack_output = pack_output || File.join(File.dirname(@dest), DEFAULT_PACKED_BINARY_NAME)
32
+ @include_gems = include_gems
28
33
  end
29
34
 
30
35
  def download
@@ -38,18 +43,49 @@ module Wasval
38
43
  end
39
44
 
40
45
  def install
41
- download
46
+ FileUtils.mkdir_p(File.dirname(dest))
47
+ Dir.mktmpdir do |tmpdir|
48
+ tarball_path = File.join(tmpdir, tarball_name)
49
+ download_file(download_url, tarball_path)
50
+ extract_binary(tarball_path)
51
+ if pack_dirs || include_gems
52
+ dirs = pack_dirs ? pack_dirs.dup : []
53
+ if include_gems
54
+ if include_gems == true
55
+ actual_usr_dir = File.join(tmpdir, DEFAULT_USR_DIR_NAME)
56
+ extract_usr_dir(tarball_path, actual_usr_dir)
57
+ else
58
+ actual_usr_dir = include_gems
59
+ end
60
+ dirs << actual_usr_dir
61
+ end
62
+ pack(*dirs, output: pack_output) if dirs.any?
63
+ end
64
+ end
42
65
  serialize
43
66
  end
44
67
 
45
68
  def serialize
46
69
  engine = Wasmtime::Engine.new(epoch_interruption: true)
47
- mod = Wasmtime::Module.from_file(engine, dest)
70
+ source = (pack_dirs || include_gems) ? pack_output : dest
71
+ mod = Wasmtime::Module.from_file(engine, source)
48
72
  FileUtils.mkdir_p(File.dirname(serialized_dest))
49
73
  File.binwrite(serialized_dest, mod.serialize)
50
74
  serialized_dest
51
75
  end
52
76
 
77
+ def pack(*dirs, output:)
78
+ args = [dest]
79
+ dirs.each do |dir|
80
+ dir_arg = dir.include?("::") ? dir : "#{dir}::/#{File.basename(dir)}"
81
+ args.push("--dir", dir_arg)
82
+ end
83
+ args.push("-o", output)
84
+
85
+ system("rbwasm", "pack", *args, exception: true)
86
+ output
87
+ end
88
+
53
89
  def installed?
54
90
  File.exist?(dest)
55
91
  end
@@ -88,6 +124,16 @@ module Wasval
88
124
  end
89
125
  end
90
126
 
127
+ def extract_usr_dir(tarball_path, dest_usr_dir)
128
+ Dir.mktmpdir do |extract_dir|
129
+ system("tar", "-xzf", tarball_path, "-C", extract_dir, exception: true)
130
+ src_usr = Dir.glob("#{extract_dir}/**/usr").min_by(&:length)
131
+ raise "usr directory not found in tarball" unless src_usr && File.directory?(src_usr)
132
+ FileUtils.cp_r(src_usr, File.dirname(dest_usr_dir))
133
+ end
134
+ dest_usr_dir
135
+ end
136
+
91
137
  def extract_binary(tarball_path)
92
138
  Zlib::GzipReader.open(tarball_path) do |gz|
93
139
  Gem::Package::TarReader.new(gz) do |tar|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wasval
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wasval
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuji Yaginuma
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
72
  - !ruby/object:Gem::Version
73
73
  version: '0'
74
74
  requirements: []
75
- rubygems_version: 4.0.3
75
+ rubygems_version: 4.0.6
76
76
  specification_version: 4
77
77
  summary: Execute 'eval' in a WASM runtime
78
78
  test_files: []