bootsnap 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 021403c5730eaf97a0e8e8a1b31f4b5fd0a24af3
4
+ data.tar.gz: 63951a0464d36619d7695cfeceb2a6e806db6a9e
5
+ SHA512:
6
+ metadata.gz: 0f152d961214408b1c75e5f7288f411c70fc0eba8697e2b7646b767fa0cc7238c58ad2b68e5e63b9b2fac37f26d73b2093a9ad77bb8a77e57cb1495e3fa37e73
7
+ data.tar.gz: 6b0b6812411caefaf659bbd1375009a2b3b55a37428ce6d7a29115c40b30a3047b7bdb41710519ba4b6db9b4d889c1473ca87bba121243f21ab7b63e507a0b53
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ *.gem
15
+ *.db
16
+ mkmf.log
17
+ .rubocop-*
@@ -0,0 +1,7 @@
1
+ inherit_from:
2
+ - http://shopify.github.io/ruby-style-guide/rubocop.yml
3
+
4
+ AllCops:
5
+ Exclude:
6
+ - 'vendor/**/*'
7
+
@@ -0,0 +1,21 @@
1
+ # Contributing to Bootsnap
2
+
3
+ We love receiving pull requests!
4
+
5
+ ## Standards
6
+
7
+ * PR should explain what the feature does, and why the change exists.
8
+ * PR should include any carrier specific documentation explaining how it works.
9
+ * Code _must_ be tested, including both unit and remote tests where applicable.
10
+ * Be consistent. Write clean code that follows [Ruby community standards](https://github.com/bbatsov/ruby-style-guide).
11
+ * Code should be generic and reusable.
12
+
13
+ If you're stuck, ask questions!
14
+
15
+ ## How to contribute
16
+
17
+ 1. Fork it ( https://github.com/Shopify/bootsnap/fork )
18
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
19
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
20
+ 4. Push to the branch (`git push origin my-new-feature`)
21
+ 5. Create a new Pull Request
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bootsnap.gemspec
4
+ gemspec
@@ -0,0 +1,49 @@
1
+ # Bootsnap
2
+
3
+ > Beta version
4
+
5
+ Bootsnap is a library that overrides `Kernel#require`, `Kernel#load`, `Module#autoload` and in the case that `ActiveSupport` is used, it will also override a number of `ActiveSupport` methods.
6
+
7
+ Bootsnap creates 2 kinds of caches, a stable, long lived cache out of Ruby and Gem directories. These are assumed to never change and so we can cache more aggresively. Application code is expected to change frequently, so it is cached with little aggression (short lived bursts that should last only as long as the app takes to boot). This is the “volatile” cache.
8
+
9
+ Below is a diagram explaining how the overrides work.
10
+
11
+ ![Flowchart explaining Bootsnap](https://cloud.githubusercontent.com/assets/3074765/24532120/eed94e64-158b-11e7-9137-438d759b2ac8.png)
12
+
13
+ In this diagram, you might notice that we refer to cache and autoload_path_cache as the main points of override.
14
+
15
+ # How it works
16
+
17
+ Caching paths is the main function of bootsnap. There are 2 types of caches:
18
+
19
+ - Stable: For Gems and Rubies since these are highly unlikely to change
20
+ - Volatile: For everything else, like your app code, since this is likely to change
21
+
22
+ This path is shown in the flowchart below. In a number of instances, scan is mentioned.
23
+
24
+ ![How path searching works](https://cloud.githubusercontent.com/assets/3074765/24532143/18278cd6-158c-11e7-8250-78d831df70db.png)
25
+
26
+ # Usage
27
+
28
+ Add `bootsnap` to your `Gemfile`:
29
+
30
+ ```ruby
31
+ gem 'bootsnap'
32
+ ```
33
+
34
+ Next, add this to your boot setup after `require 'bundler/setup'` but before the end.
35
+
36
+ ```ruby
37
+ require 'bootsnap'
38
+ Bootsnap.setup(
39
+ cache_dir: 'tmp/cache', ## Path to your cache
40
+ development_mode: ENV['MY_ENV'] == 'development',
41
+ load_path_cache: true, ## Should we optimize the LOAD_PATH with a cache?
42
+ autoload_paths_cache: true, ## Should we optimize the AUTOLOAD_PATH with a cache?
43
+ disable_trace: false, ## Sets `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
44
+ compile_cache_iseq: true, ## Should compile Ruby code into iSeq cache?
45
+ compile_cache_yaml: true ## Should compile YAML into a cache?
46
+ )
47
+ ```
48
+
49
+ **Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap', 'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This will help optimize boot time.
@@ -0,0 +1,11 @@
1
+ require 'rake/extensiontask'
2
+
3
+ gemspec = Gem::Specification.load('bootsnap.gemspec')
4
+ Rake::ExtensionTask.new do |ext|
5
+ ext.name = 'bootsnap'
6
+ ext.ext_dir = 'ext/bootsnap'
7
+ ext.lib_dir = 'lib/bootsnap'
8
+ ext.gem_spec = gemspec
9
+ end
10
+
11
+ task(default: :compile)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "bootsnap"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ if [ $# -eq 0 ]; then
4
+ ruby -I"test" -e 'Dir.glob("./test/**/*_test.rb").each { |f| require f }' -- "$@"
5
+ else
6
+ path=$1
7
+ ruby -I"test" -e "require '${path#test/}'" -- "$@"
8
+ fi
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bootsnap/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bootsnap"
8
+ spec.version = Bootsnap::VERSION
9
+ spec.authors = ["Burke Libbey"]
10
+ spec.email = ["burke.libbey@shopify.com"]
11
+
12
+ spec.license = "MIT"
13
+
14
+ spec.summary = "wip"
15
+ spec.description = "wip."
16
+ spec.homepage = "https://github.com/Shopify/bootsnap"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+ spec.extensions = ['ext/bootsnap/extconf.rb']
25
+
26
+ spec.add_development_dependency "bundler", '~> 0'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rake-compiler', '~> 0'
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ spec.add_development_dependency "mocha", "~> 1.2"
31
+
32
+ spec.add_runtime_dependency "msgpack", "~> 1.0"
33
+ spec.add_runtime_dependency "snappy", "~> 0.0.15"
34
+ end
data/dev.yml ADDED
@@ -0,0 +1,5 @@
1
+ up:
2
+ - ruby: 2.3.3
3
+ - bundler
4
+ commands:
5
+ test: 'rake compile && bin/testunit'
@@ -0,0 +1,542 @@
1
+ #include "bootsnap.h"
2
+ #include <sys/types.h>
3
+ #include <sys/xattr.h>
4
+ #include <sys/stat.h>
5
+ #include <errno.h>
6
+ #include <unistd.h>
7
+ #include <fcntl.h>
8
+ #include <stdbool.h>
9
+ #include <utime.h>
10
+
11
+ #ifdef __APPLE__
12
+ #define _ENOATTR ENOATTR
13
+ #else
14
+ #define _ENOATTR ENODATA
15
+ #endif
16
+
17
+ /*
18
+ * TODO:
19
+ * - test on linux or reject on non-darwin
20
+ * - source files over 4GB will likely break things (meh)
21
+ */
22
+
23
+ static VALUE rb_mBootsnap;
24
+ static VALUE rb_mBootsnap_CompileCache;
25
+ static VALUE rb_mBootsnap_CompileCache_Native;
26
+ static VALUE rb_eBootsnap_CompileCache_Uncompilable;
27
+ static uint32_t current_ruby_revision;
28
+ static uint32_t current_compile_option_crc32 = 0;
29
+ static ID uncompilable;
30
+
31
+ struct stats {
32
+ uint64_t hit;
33
+ uint64_t unwritable;
34
+ uint64_t uncompilable;
35
+ uint64_t miss;
36
+ uint64_t fail;
37
+ uint64_t retry;
38
+ };
39
+ static struct stats stats = {
40
+ .hit = 0,
41
+ .unwritable = 0,
42
+ .uncompilable = 0,
43
+ .miss = 0,
44
+ .fail = 0,
45
+ .retry = 0,
46
+ };
47
+
48
+ struct xattr_key {
49
+ uint8_t version;
50
+ uint32_t compile_option;
51
+ uint32_t data_size;
52
+ uint32_t ruby_revision;
53
+ uint64_t mtime;
54
+ } __attribute__((packed));
55
+
56
+ struct i2o_data {
57
+ VALUE handler;
58
+ VALUE input_data;
59
+ };
60
+
61
+ struct i2s_data {
62
+ VALUE handler;
63
+ VALUE input_data;
64
+ VALUE pathval;
65
+ };
66
+
67
+ struct s2o_data {
68
+ VALUE handler;
69
+ VALUE storage_data;
70
+ };
71
+
72
+ static const uint8_t current_version = 10;
73
+ static const char * xattr_key_name = "user.aotcc.key";
74
+ static const char * xattr_data_name = "user.aotcc.value";
75
+ static const size_t xattr_key_size = sizeof (struct xattr_key);
76
+
77
+ #ifdef __APPLE__
78
+ #define GETXATTR_TRAILER ,0,0
79
+ #define SETXATTR_TRAILER ,0
80
+ #define REMOVEXATTR_TRAILER ,0
81
+ #else
82
+ #define GETXATTR_TRAILER
83
+ #define SETXATTR_TRAILER
84
+ #define REMOVEXATTR_TRAILER
85
+ #endif
86
+
87
+ /* forward declarations */
88
+ static int bs_fetch_data(int fd, size_t size, VALUE handler, VALUE * storage_data, int * exception_tag);
89
+ static int bs_update_key(int fd, uint32_t data_size, uint64_t current_mtime);
90
+ static int bs_open(const char * path, bool * writable);
91
+ static int bs_get_cache(int fd, struct xattr_key * key);
92
+ static size_t bs_read_contents(int fd, size_t size, char ** contents);
93
+ static int bs_close_and_unclobber_times(int * fd, const char * path, time_t atime, time_t mtime);
94
+ static VALUE bs_fetch(VALUE self, VALUE pathval, VALUE handler);
95
+ static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32val);
96
+ static VALUE prot_exception_for_errno(VALUE err);
97
+ static VALUE prot_input_to_output(VALUE arg);
98
+ static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
99
+ static VALUE prot_input_to_storage(VALUE arg);
100
+ static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
101
+ static VALUE prot_storage_to_output(VALUE arg);
102
+ static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
103
+ static int logging_enabled();
104
+ static VALUE bs_stats(VALUE self);
105
+
106
+ void
107
+ Init_bootsnap(void)
108
+ {
109
+ rb_mBootsnap = rb_define_module("Bootsnap");
110
+ rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
111
+ rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
112
+ rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
113
+ current_ruby_revision = FIX2INT(rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")));
114
+
115
+ uncompilable = rb_intern("__bootsnap_uncompilable__");
116
+
117
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_fetch, 2);
118
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "stats", bs_stats, 0);
119
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
120
+ }
121
+
122
+ static VALUE
123
+ bs_stats(VALUE self)
124
+ {
125
+ VALUE ret = rb_hash_new();
126
+ rb_hash_aset(ret, ID2SYM(rb_intern("hit")), INT2NUM(stats.hit));
127
+ rb_hash_aset(ret, ID2SYM(rb_intern("miss")), INT2NUM(stats.miss));
128
+ rb_hash_aset(ret, ID2SYM(rb_intern("unwritable")), INT2NUM(stats.unwritable));
129
+ rb_hash_aset(ret, ID2SYM(rb_intern("uncompilable")), INT2NUM(stats.uncompilable));
130
+ rb_hash_aset(ret, ID2SYM(rb_intern("fail")), INT2NUM(stats.fail));
131
+ rb_hash_aset(ret, ID2SYM(rb_intern("retry")), INT2NUM(stats.retry));
132
+ return ret;
133
+ }
134
+
135
+ static VALUE
136
+ bs_compile_option_crc32_set(VALUE self, VALUE crc32val)
137
+ {
138
+ Check_Type(crc32val, T_FIXNUM);
139
+ current_compile_option_crc32 = FIX2UINT(crc32val);
140
+ return Qnil;
141
+ }
142
+
143
+ #define CHECK_C(ret, func) \
144
+ do { if ((int)(ret) == -1) FAIL((func), errno); } while(0);
145
+
146
+ #define FAIL(func, err) \
147
+ do { \
148
+ int state; \
149
+ exception = rb_protect(prot_exception_for_errno, INT2FIX(err), &state); \
150
+ if (state) exception = rb_eStandardError; \
151
+ goto fail; \
152
+ } while(0);
153
+
154
+ #define CHECK_RB0() \
155
+ do { if (exception_tag != 0) goto raise; } while (0);
156
+
157
+ #define CHECK_RB(body) \
158
+ do { (body); CHECK_RB0(); } while (0);
159
+
160
+ #define SUCCEED(final) \
161
+ do { \
162
+ output_data = final; \
163
+ goto cleanup; \
164
+ } while(0);
165
+
166
+ static VALUE
167
+ bs_fetch(VALUE self, VALUE pathval, VALUE handler)
168
+ {
169
+ const char * path;
170
+
171
+ VALUE exception;
172
+ int exception_tag;
173
+
174
+ int fd, ret, retry;
175
+ bool valid_cache;
176
+ bool writable;
177
+ uint32_t data_size;
178
+ struct xattr_key cache_key;
179
+ struct stat statbuf;
180
+ char * contents;
181
+
182
+ VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
183
+ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
184
+ VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
185
+
186
+ /* don't leak memory */
187
+ #define return error!
188
+ #define rb_raise error!
189
+
190
+ retry = 0;
191
+ begin:
192
+ output_data = Qnil;
193
+ contents = 0;
194
+
195
+ /* Blow up if we can't turn our argument into a char* */
196
+ Check_Type(pathval, T_STRING);
197
+ path = RSTRING_PTR(pathval);
198
+
199
+ /* open the file, get its mtime and read the cache key xattr */
200
+ CHECK_C(fd = bs_open(path, &writable), "open");
201
+ CHECK_C( fstat(fd, &statbuf), "fstat");
202
+ CHECK_C(valid_cache = bs_get_cache(fd, &cache_key), "fgetxattr");
203
+
204
+ /* `valid_cache` is true if the cache key isn't trivially invalid, e.g. built
205
+ * with a different RUBY_REVISION */
206
+ if (valid_cache && cache_key.mtime == (uint64_t)statbuf.st_mtime) {
207
+ /* if the mtimes match, assume the cache is valid. fetch the cached data. */
208
+ ret = bs_fetch_data(fd, (size_t)cache_key.data_size, handler, &output_data, &exception_tag);
209
+ if (ret == -1 && errno == _ENOATTR) {
210
+ /* the key was present, but the data was missing. remove the key, and
211
+ * start over */
212
+ CHECK_C(fremovexattr(fd, xattr_key_name REMOVEXATTR_TRAILER), "fremovexattr");
213
+ goto retry;
214
+ }
215
+ CHECK_RB0();
216
+ CHECK_C(ret, "fgetxattr/fetch-data");
217
+ if (!NIL_P(output_data)) {
218
+ stats.hit++;
219
+ SUCCEED(output_data); /* this is the fast-path to shoot for */
220
+ }
221
+ valid_cache = false; /* invalid cache; we'll want to regenerate it */
222
+ }
223
+
224
+ /* read the contents of the file and crc32 it to compare with the cache key */
225
+ CHECK_C(bs_read_contents(fd, statbuf.st_size, &contents), "read") /* contents must be xfree'd */
226
+
227
+ /* we need to pass this char* to ruby-land */
228
+ input_data = rb_str_new_static(contents, statbuf.st_size);
229
+
230
+ /* if we didn't have write permission to the file, bail now -- everything
231
+ * that follows is about generating and writing the cache. Let's just convert
232
+ * the input format to the output format and return */
233
+ if (!writable) {
234
+ stats.unwritable++;
235
+ CHECK_RB(bs_input_to_output(handler, input_data, &output_data, &exception_tag));
236
+ SUCCEED(output_data);
237
+ }
238
+
239
+ /* Now, we know we have write permission, and can update the xattrs.
240
+ * Additionally, we know the cache is currently missing or absent, and needs
241
+ * to be updated. */
242
+ stats.miss++;
243
+
244
+ /* First, convert the input format to the storage format by calling into the
245
+ * handler. */
246
+ CHECK_RB(exception_tag = bs_input_to_storage(handler, input_data, pathval, &storage_data));
247
+ if (storage_data == uncompilable) {
248
+ /* The handler can raise Bootsnap::CompileCache::Uncompilable. When it does this,
249
+ * we just call the input_to_output handler method, bypassing the storage format. */
250
+ CHECK_RB(bs_input_to_output(handler, input_data, &output_data, &exception_tag));
251
+ stats.uncompilable++;
252
+ SUCCEED(output_data);
253
+ }
254
+
255
+ /* we can only really write strings to xattrs */
256
+ if (!RB_TYPE_P(storage_data, T_STRING)) {
257
+ goto invalid_type_storage_data;
258
+ }
259
+
260
+ /* xattrs can't exceed 64MB */
261
+ if (RB_TYPE_P(storage_data, T_STRING) && RSTRING_LEN(storage_data) > 64 * 1024 * 1024) {
262
+ if (logging_enabled()) {
263
+ fprintf(stderr, "[OPT_AOT_LOG] warning: compiled artifact is over 64MB, which is too large to store in an xattr.%s\n", path);
264
+ }
265
+ CHECK_RB(bs_input_to_output(handler, input_data, &output_data, &exception_tag));
266
+ SUCCEED(output_data);
267
+ }
268
+
269
+ data_size = (uint32_t)RSTRING_LEN(storage_data);
270
+
271
+ /* update the cache, but don't leave it in an invalid state even briefly: remove the key first. */
272
+ fremovexattr(fd, xattr_key_name REMOVEXATTR_TRAILER);
273
+ CHECK_C(fsetxattr(fd, xattr_data_name, RSTRING_PTR(storage_data), (size_t)data_size, 0 SETXATTR_TRAILER), "fsetxattr");
274
+ CHECK_C(bs_update_key(fd, data_size, statbuf.st_mtime), "fsetxattr");
275
+
276
+ /* updating xattrs bumps mtime, so we set them back after */
277
+ CHECK_C(bs_close_and_unclobber_times(&fd, path, statbuf.st_atime, statbuf.st_mtime), "close/utime");
278
+
279
+ /* convert the data we just stored into the output format */
280
+ CHECK_RB(exception_tag = bs_storage_to_output(handler, storage_data, &output_data));
281
+
282
+ /* if the storage data was broken, remove the cache and run input_to_output */
283
+ if (output_data == Qnil) {
284
+ /* deletion here is best effort; no need to fail if it does */
285
+ fremovexattr(fd, xattr_key_name REMOVEXATTR_TRAILER);
286
+ fremovexattr(fd, xattr_data_name REMOVEXATTR_TRAILER);
287
+ CHECK_RB(bs_input_to_output(handler, input_data, &output_data, &exception_tag));
288
+ }
289
+
290
+ SUCCEED(output_data);
291
+
292
+ #undef return
293
+ #undef rb_raise
294
+ #define CLEANUP \
295
+ if (contents != 0) xfree(contents); \
296
+ if (fd > 0) close(fd);
297
+
298
+ __builtin_unreachable();
299
+ cleanup:
300
+ CLEANUP;
301
+ return output_data;
302
+ fail:
303
+ CLEANUP;
304
+ stats.fail++;
305
+ rb_exc_raise(exception);
306
+ __builtin_unreachable();
307
+ invalid_type_storage_data:
308
+ CLEANUP;
309
+ stats.fail++;
310
+ Check_Type(storage_data, T_STRING);
311
+ __builtin_unreachable();
312
+ retry:
313
+ CLEANUP;
314
+ stats.retry++;
315
+ if (retry == 1) {
316
+ rb_raise(rb_eRuntimeError, "internal error in bootsnap");
317
+ __builtin_unreachable();
318
+ }
319
+ retry = 1;
320
+ goto begin;
321
+ raise:
322
+ CLEANUP;
323
+ stats.fail++;
324
+ rb_jump_tag(exception_tag);
325
+ __builtin_unreachable();
326
+ }
327
+
328
+ static int
329
+ bs_fetch_data(int fd, size_t size, VALUE handler, VALUE * output_data, int * exception_tag)
330
+ {
331
+ int ret;
332
+ ssize_t nbytes;
333
+ void * xattr_data;
334
+ VALUE storage_data;
335
+
336
+ *output_data = Qnil;
337
+ *exception_tag = 0;
338
+
339
+ xattr_data = ALLOC_N(uint8_t, size);
340
+ nbytes = fgetxattr(fd, xattr_data_name, xattr_data, size GETXATTR_TRAILER);
341
+ if (nbytes == -1) {
342
+ ret = -1;
343
+ goto done;
344
+ }
345
+ if (nbytes != (ssize_t)size) {
346
+ errno = EIO; /* lies but whatever */
347
+ ret = -1;
348
+ goto done;
349
+ }
350
+ storage_data = rb_str_new_static(xattr_data, nbytes);
351
+ ret = bs_storage_to_output(handler, storage_data, output_data);
352
+ if (ret != 0) {
353
+ *exception_tag = ret;
354
+ errno = 0;
355
+ }
356
+ done:
357
+ xfree(xattr_data);
358
+ return ret;
359
+ }
360
+
361
+ static int
362
+ bs_update_key(int fd, uint32_t data_size, uint64_t current_mtime)
363
+ {
364
+ struct xattr_key xattr_key;
365
+
366
+ xattr_key = (struct xattr_key){
367
+ .version = current_version,
368
+ .data_size = data_size,
369
+ .compile_option = current_compile_option_crc32,
370
+ .ruby_revision = current_ruby_revision,
371
+ .mtime = current_mtime,
372
+ };
373
+
374
+ return fsetxattr(fd, xattr_key_name, &xattr_key, (size_t)xattr_key_size, 0 SETXATTR_TRAILER);
375
+ }
376
+
377
+ /*
378
+ * Open the file O_RDWR if possible, or O_RDONLY if that throws EACCES.
379
+ * Set +writable+ to indicate which mode was used.
380
+ */
381
+ static int
382
+ bs_open(const char * path, bool * writable)
383
+ {
384
+ int fd;
385
+
386
+ *writable = true;
387
+ fd = open(path, O_RDWR);
388
+ if (fd == -1 && errno == EACCES) {
389
+ *writable = false;
390
+ if (logging_enabled()) {
391
+ fprintf(stderr, "[OPT_AOT_LOG] warning: unable to cache because no write permission to %s\n", path);
392
+ }
393
+ fd = open(path, O_RDONLY);
394
+ }
395
+ return fd;
396
+ }
397
+
398
+ /*
399
+ * Fetch the cache key from the relevant xattr into +key+.
400
+ * Returns:
401
+ * 0: invalid/no cache
402
+ * 1: valid cache
403
+ * -1: fgetxattr failed, errno is set
404
+ */
405
+ static int
406
+ bs_get_cache(int fd, struct xattr_key * key)
407
+ {
408
+ ssize_t nbytes;
409
+
410
+ nbytes = fgetxattr(fd, xattr_key_name, (void *)key, xattr_key_size GETXATTR_TRAILER);
411
+ if (nbytes == -1 && errno != _ENOATTR) {
412
+ return -1;
413
+ }
414
+
415
+ return (nbytes == (ssize_t)xattr_key_size && \
416
+ key->version == current_version && \
417
+ key->compile_option == current_compile_option_crc32 && \
418
+ key->ruby_revision == current_ruby_revision);
419
+ }
420
+
421
+ /*
422
+ * Read an entire file into a char*
423
+ * contents must be freed with xfree() when done.
424
+ */
425
+ static size_t
426
+ bs_read_contents(int fd, size_t size, char ** contents)
427
+ {
428
+ *contents = ALLOC_N(char, size);
429
+ return read(fd, *contents, size);
430
+ }
431
+
432
+ static int
433
+ bs_close_and_unclobber_times(int * fd, const char * path, time_t atime, time_t mtime)
434
+ {
435
+ struct utimbuf times = {
436
+ .actime = atime,
437
+ .modtime = mtime,
438
+ };
439
+ if (close(*fd) == -1) {
440
+ return -1;
441
+ }
442
+ *fd = 0;
443
+ return utime(path, &times);
444
+ }
445
+
446
+ static VALUE
447
+ prot_exception_for_errno(VALUE err)
448
+ {
449
+ if (err != INT2FIX(0)) {
450
+ VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
451
+ VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
452
+ VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
453
+ return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
454
+ }
455
+ return rb_eStandardError;
456
+ }
457
+
458
+ static VALUE
459
+ prot_input_to_output(VALUE arg)
460
+ {
461
+ struct i2o_data * data = (struct i2o_data *)arg;
462
+ return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
463
+ }
464
+
465
+ static void
466
+ bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
467
+ {
468
+ struct i2o_data i2o_data = {
469
+ .handler = handler,
470
+ .input_data = input_data,
471
+ };
472
+ *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
473
+ }
474
+
475
+ static VALUE
476
+ try_input_to_storage(VALUE arg)
477
+ {
478
+ struct i2s_data * data = (struct i2s_data *)arg;
479
+ return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
480
+ }
481
+
482
+ static VALUE
483
+ rescue_input_to_storage(VALUE arg)
484
+ {
485
+ return uncompilable;
486
+ }
487
+
488
+ static VALUE
489
+ prot_input_to_storage(VALUE arg)
490
+ {
491
+ struct i2s_data * data = (struct i2s_data *)arg;
492
+ return rb_rescue2(
493
+ try_input_to_storage, (VALUE)data,
494
+ rescue_input_to_storage, Qnil,
495
+ rb_eBootsnap_CompileCache_Uncompilable, 0);
496
+ }
497
+
498
+ static int
499
+ bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
500
+ {
501
+ int state;
502
+ struct i2s_data i2s_data = {
503
+ .handler = handler,
504
+ .input_data = input_data,
505
+ .pathval = pathval,
506
+ };
507
+ *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
508
+ return state;
509
+ }
510
+
511
+ static VALUE
512
+ prot_storage_to_output(VALUE arg)
513
+ {
514
+ struct s2o_data * data = (struct s2o_data *)arg;
515
+ return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
516
+ }
517
+
518
+ static int
519
+ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
520
+ {
521
+ int state;
522
+ struct s2o_data s2o_data = {
523
+ .handler = handler,
524
+ .storage_data = storage_data,
525
+ };
526
+ *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
527
+ return state;
528
+ }
529
+
530
+ /* default no if empty, yes if present, no if "0" */
531
+ static int
532
+ logging_enabled()
533
+ {
534
+ char * log = getenv("OPT_AOT_LOG");
535
+ if (log == 0) {
536
+ return 0;
537
+ } else if (log[0] == '0') {
538
+ return 0;
539
+ } else {
540
+ return 1;
541
+ }
542
+ }