bootsnap 1.18.6 → 1.26.0
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 +4 -4
- data/CHANGELOG.md +101 -7
- data/LICENSE.txt +2 -1
- data/README.md +57 -10
- data/ext/bootsnap/bootsnap.c +297 -154
- data/ext/bootsnap/extconf.rb +2 -1
- data/lib/bootsnap/cli/worker_pool.rb +3 -3
- data/lib/bootsnap/cli.rb +4 -31
- data/lib/bootsnap/compile_cache/iseq.rb +183 -77
- data/lib/bootsnap/compile_cache/ruby_bug_22023_canary.rb +1 -0
- data/lib/bootsnap/compile_cache/yaml.rb +98 -46
- data/lib/bootsnap/compile_cache.rb +5 -10
- data/lib/bootsnap/load_path_cache/cache.rb +11 -14
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +9 -4
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +1 -1
- data/lib/bootsnap/load_path_cache/path.rb +34 -27
- data/lib/bootsnap/load_path_cache/path_scanner.rb +78 -32
- data/lib/bootsnap/load_path_cache/store.rb +2 -2
- data/lib/bootsnap/rake.rb +14 -0
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +51 -10
- metadata +11 -11
- data/ext/bootsnap/bootsnap.h +0 -6
- data/lib/bootsnap/compile_cache/json.rb +0 -89
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* here.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
#include "bootsnap.h"
|
|
15
14
|
#include "ruby.h"
|
|
16
15
|
#include <stdint.h>
|
|
17
16
|
#include <stdbool.h>
|
|
@@ -20,10 +19,15 @@
|
|
|
20
19
|
#include <fcntl.h>
|
|
21
20
|
#include <unistd.h>
|
|
22
21
|
#include <sys/stat.h>
|
|
22
|
+
#include <dirent.h>
|
|
23
|
+
|
|
24
|
+
#ifndef RBIMPL_ATTR_NORETURN
|
|
25
|
+
#define RBIMPL_ATTR_NORETURN()
|
|
26
|
+
#endif
|
|
23
27
|
|
|
24
28
|
#ifdef __APPLE__
|
|
25
29
|
// The symbol is present, however not in the headers
|
|
26
|
-
// See: https://github.com/
|
|
30
|
+
// See: https://github.com/rails/bootsnap/issues/470
|
|
27
31
|
extern int fdatasync(int);
|
|
28
32
|
#endif
|
|
29
33
|
|
|
@@ -36,7 +40,7 @@
|
|
|
36
40
|
#define MAX_CACHEPATH_SIZE 1000
|
|
37
41
|
#define MAX_CACHEDIR_SIZE 981
|
|
38
42
|
|
|
39
|
-
#define KEY_SIZE
|
|
43
|
+
#define KEY_SIZE 32
|
|
40
44
|
|
|
41
45
|
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
|
42
46
|
|
|
@@ -45,34 +49,27 @@
|
|
|
45
49
|
#endif
|
|
46
50
|
|
|
47
51
|
/*
|
|
48
|
-
* An instance of this key is written as the first
|
|
52
|
+
* An instance of this key is written as the first `KEY_SIZE` bytes of each cache file.
|
|
49
53
|
* The mtime and size members track whether the file contents have changed, and
|
|
50
|
-
* the
|
|
51
|
-
* changes to the environment that could invalidate compile results without
|
|
54
|
+
* the ruby_version_digest (bootsnap_cache_version + RUBY_DESCRIPTION) and compile_option
|
|
55
|
+
* members track changes to the environment that could invalidate compile results without
|
|
52
56
|
* file contents having changed. The data_size member is not truly part of the
|
|
53
57
|
* "key". Really, this could be called a "header" with the first six members
|
|
54
58
|
* being an embedded "key" struct and an additional data_size member.
|
|
55
59
|
*
|
|
56
60
|
* The data_size indicates the remaining number of bytes in the cache file
|
|
57
61
|
* after the header (the size of the cached artifact).
|
|
58
|
-
*
|
|
59
|
-
* After data_size, the struct is padded to 64 bytes.
|
|
60
62
|
*/
|
|
61
63
|
struct bs_cache_key {
|
|
62
|
-
|
|
63
|
-
uint32_t ruby_platform;
|
|
64
|
-
uint32_t compile_option;
|
|
65
|
-
uint32_t ruby_revision;
|
|
66
|
-
uint64_t size;
|
|
64
|
+
uint64_t ruby_version_digest;
|
|
67
65
|
uint64_t mtime;
|
|
68
|
-
uint64_t data_size; //
|
|
69
66
|
uint64_t digest;
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
uint32_t size;
|
|
68
|
+
uint32_t data_size;
|
|
72
69
|
} __attribute__((packed));
|
|
73
70
|
|
|
74
71
|
/*
|
|
75
|
-
* If the struct padding isn't correct to pad the key to
|
|
72
|
+
* If the struct padding isn't correct to pad the key to `KEY_SIZE` bytes, refuse to
|
|
76
73
|
* compile.
|
|
77
74
|
*/
|
|
78
75
|
#define STATIC_ASSERT(X) STATIC_ASSERT2(X,__LINE__)
|
|
@@ -82,21 +79,20 @@ struct bs_cache_key {
|
|
|
82
79
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
|
83
80
|
|
|
84
81
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
|
85
|
-
static const uint32_t
|
|
86
|
-
|
|
87
|
-
/*
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
static const uint32_t bootsnap_cache_version = 8;
|
|
83
|
+
|
|
84
|
+
/* Invalidates cache when switching ruby version, platform or ABI */
|
|
85
|
+
static uint64_t base_ruby_version_digest = 0;
|
|
86
|
+
|
|
87
|
+
// `base_ruby_version_digest` combined with RubyVM::InstructionSequence.compile_option
|
|
88
|
+
// Invalidates cache when RubyVM::InstructionSequence.compile_option changes
|
|
89
|
+
static uint64_t current_ruby_version_digest = 0;
|
|
90
|
+
|
|
94
91
|
/* Current umask */
|
|
95
92
|
static mode_t current_umask;
|
|
96
93
|
|
|
97
94
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
|
98
95
|
static VALUE rb_mBootsnap;
|
|
99
|
-
static VALUE rb_mBootsnap_CompileCache;
|
|
100
96
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
|
101
97
|
static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
|
102
98
|
static ID instrumentation_method;
|
|
@@ -106,13 +102,15 @@ static bool readonly = false;
|
|
|
106
102
|
static bool revalidation = false;
|
|
107
103
|
static bool perm_issue = false;
|
|
108
104
|
|
|
105
|
+
static ID id_storage_to_output, id_input_to_output, id_input_to_storage;
|
|
106
|
+
|
|
109
107
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
|
110
108
|
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
|
111
109
|
static VALUE bs_readonly_set(VALUE self, VALUE enabled);
|
|
112
110
|
static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
|
|
113
111
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
|
114
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
|
115
|
-
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
|
112
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args);
|
|
113
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler);
|
|
116
114
|
|
|
117
115
|
/* Helpers */
|
|
118
116
|
enum cache_status {
|
|
@@ -120,7 +118,7 @@ enum cache_status {
|
|
|
120
118
|
hit,
|
|
121
119
|
stale,
|
|
122
120
|
};
|
|
123
|
-
static void bs_cache_path(
|
|
121
|
+
static void bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
|
124
122
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
|
125
123
|
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
|
126
124
|
static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
|
|
@@ -131,8 +129,7 @@ static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handle
|
|
|
131
129
|
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
|
132
130
|
static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
|
133
131
|
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
|
134
|
-
static
|
|
135
|
-
static uint32_t get_ruby_platform(void);
|
|
132
|
+
static uint64_t get_ruby_version_digest(void);
|
|
136
133
|
|
|
137
134
|
/*
|
|
138
135
|
* Helper functions to call ruby methods on handler object without crashing on
|
|
@@ -140,7 +137,7 @@ static uint32_t get_ruby_platform(void);
|
|
|
140
137
|
*/
|
|
141
138
|
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
|
142
139
|
static VALUE prot_input_to_output(VALUE arg);
|
|
143
|
-
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
|
140
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * output_data, int * exception_tag);
|
|
144
141
|
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
|
145
142
|
struct s2o_data;
|
|
146
143
|
struct i2o_data;
|
|
@@ -152,6 +149,114 @@ bs_rb_get_path(VALUE self, VALUE fname)
|
|
|
152
149
|
return rb_get_path(fname);
|
|
153
150
|
}
|
|
154
151
|
|
|
152
|
+
#ifdef HAVE_FSTATAT
|
|
153
|
+
|
|
154
|
+
RBIMPL_ATTR_NORETURN()
|
|
155
|
+
static void
|
|
156
|
+
bs_syserr_fail_path(const char *func_name, int n, VALUE path)
|
|
157
|
+
{
|
|
158
|
+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s", func_name, RSTRING_PTR(path)));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
RBIMPL_ATTR_NORETURN()
|
|
162
|
+
static void
|
|
163
|
+
bs_syserr_fail_dir_entry(const char *func_name, int n, VALUE dir, const char *d_name)
|
|
164
|
+
{
|
|
165
|
+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s/%s", func_name, RSTRING_PTR(dir), d_name));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static VALUE
|
|
169
|
+
bs_rb_scan_dir(VALUE self, VALUE abspath)
|
|
170
|
+
{
|
|
171
|
+
Check_Type(abspath, T_STRING);
|
|
172
|
+
|
|
173
|
+
VALUE dirs = rb_ary_new();
|
|
174
|
+
VALUE requirables = rb_ary_new();
|
|
175
|
+
VALUE result = rb_ary_new_from_args(2, requirables, dirs);
|
|
176
|
+
|
|
177
|
+
DIR *dirp = opendir(RSTRING_PTR(abspath));
|
|
178
|
+
if (dirp == NULL) {
|
|
179
|
+
if (errno == ENOTDIR || errno == ENOENT) {
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
bs_syserr_fail_path("opendir", errno, abspath);
|
|
184
|
+
return Qundef;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
struct dirent *entry;
|
|
188
|
+
struct stat st;
|
|
189
|
+
int dfd = -1;
|
|
190
|
+
|
|
191
|
+
while (1) {
|
|
192
|
+
errno = 0;
|
|
193
|
+
|
|
194
|
+
entry = readdir(dirp);
|
|
195
|
+
if (entry == NULL) break;
|
|
196
|
+
|
|
197
|
+
if (entry->d_name[0] == '.') continue;
|
|
198
|
+
|
|
199
|
+
if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
|
|
200
|
+
// Note: the original implementation of LoadPathCache did follow symlink.
|
|
201
|
+
// So this is replicated here, but I'm not sure it's a good idea.
|
|
202
|
+
if (dfd < 0) {
|
|
203
|
+
dfd = dirfd(dirp);
|
|
204
|
+
if (dfd < 0) {
|
|
205
|
+
int err = errno;
|
|
206
|
+
closedir(dirp);
|
|
207
|
+
bs_syserr_fail_path("dirfd", err, abspath);
|
|
208
|
+
return Qundef;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (fstatat(dfd, entry->d_name, &st, 0)) {
|
|
213
|
+
if (errno == ENOENT) continue; // Broken symlink
|
|
214
|
+
|
|
215
|
+
int err = errno;
|
|
216
|
+
closedir(dirp);
|
|
217
|
+
bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
|
|
218
|
+
return Qundef;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (S_ISREG(st.st_mode)) {
|
|
222
|
+
entry->d_type = DT_REG;
|
|
223
|
+
} else if (S_ISDIR(st.st_mode)) {
|
|
224
|
+
entry->d_type = DT_DIR;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (entry->d_type == DT_DIR) {
|
|
229
|
+
rb_ary_push(dirs, rb_utf8_str_new_cstr(entry->d_name));
|
|
230
|
+
continue;
|
|
231
|
+
} else if (entry->d_type == DT_REG) {
|
|
232
|
+
size_t len = strlen(entry->d_name);
|
|
233
|
+
bool is_requirable = (
|
|
234
|
+
// Comparing 4B allows compiler to optimize this into a single 32b integer comparison.
|
|
235
|
+
(len > 3 && memcmp(entry->d_name + (len - 3), ".rb", 4) == 0)
|
|
236
|
+
|| (len > DLEXT_MAXLEN && memcmp(entry->d_name + (len - DLEXT_MAXLEN), DLEXT, DLEXT_MAXLEN + 1) == 0)
|
|
237
|
+
#ifdef DLEXT2
|
|
238
|
+
|| (len > DLEXT2_MAXLEN && memcmp(entry->d_name + (len - DLEXT2_MAXLEN), DLEXT2, DLEXT2_MAXLEN + 1) == 0)
|
|
239
|
+
#endif
|
|
240
|
+
);
|
|
241
|
+
if (is_requirable) {
|
|
242
|
+
rb_ary_push(requirables, rb_utf8_str_new(entry->d_name, len));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (errno) {
|
|
248
|
+
int err = errno;
|
|
249
|
+
closedir(dirp);
|
|
250
|
+
bs_syserr_fail_path("readdir", err, abspath);
|
|
251
|
+
} else if (closedir(dirp)) {
|
|
252
|
+
bs_syserr_fail_path("closedir", errno, abspath);
|
|
253
|
+
return Qundef;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
#endif
|
|
259
|
+
|
|
155
260
|
/*
|
|
156
261
|
* Ruby C extensions are initialized by calling Init_<extname>.
|
|
157
262
|
*
|
|
@@ -164,15 +269,25 @@ Init_bootsnap(void)
|
|
|
164
269
|
{
|
|
165
270
|
rb_mBootsnap = rb_define_module("Bootsnap");
|
|
166
271
|
|
|
272
|
+
id_storage_to_output = rb_intern("storage_to_output");
|
|
273
|
+
id_input_to_output = rb_intern("input_to_output");
|
|
274
|
+
id_input_to_storage = rb_intern("input_to_storage");
|
|
275
|
+
|
|
167
276
|
rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
|
|
168
277
|
|
|
169
|
-
|
|
278
|
+
#ifdef HAVE_FSTATAT
|
|
279
|
+
VALUE rb_mBootsnap_LoadPathCache = rb_define_module_under(rb_mBootsnap, "LoadPathCache");
|
|
280
|
+
VALUE rb_mBootsnap_LoadPathCache_Native = rb_define_module_under(rb_mBootsnap_LoadPathCache, "Native");
|
|
281
|
+
|
|
282
|
+
rb_define_singleton_method(rb_mBootsnap_LoadPathCache_Native, "scan_dir", bs_rb_scan_dir, 1);
|
|
283
|
+
#endif
|
|
284
|
+
|
|
285
|
+
VALUE rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
|
|
170
286
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
|
171
287
|
rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
|
|
172
288
|
rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
|
|
173
289
|
|
|
174
|
-
|
|
175
|
-
current_ruby_platform = get_ruby_platform();
|
|
290
|
+
base_ruby_version_digest = get_ruby_version_digest();
|
|
176
291
|
|
|
177
292
|
instrumentation_method = rb_intern("_instrument");
|
|
178
293
|
|
|
@@ -184,8 +299,8 @@ Init_bootsnap(void)
|
|
|
184
299
|
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
|
185
300
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
|
186
301
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
|
|
187
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
|
188
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile,
|
|
302
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 5);
|
|
303
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 4);
|
|
189
304
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
|
190
305
|
|
|
191
306
|
current_umask = umask(0777);
|
|
@@ -203,7 +318,13 @@ static inline void
|
|
|
203
318
|
bs_instrumentation(VALUE event, VALUE path)
|
|
204
319
|
{
|
|
205
320
|
if (RB_UNLIKELY(instrumentation_enabled)) {
|
|
321
|
+
// If the funcall raises and rescues an error we will lose the errinfo
|
|
322
|
+
// that exists now so we save it before and restore it after.
|
|
323
|
+
VALUE saved_errinfo = rb_errinfo();
|
|
206
324
|
rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
|
|
325
|
+
// If the ruby code in instrumentation_method raised we won't reach this point.
|
|
326
|
+
// If it didn't raise it _might_ have rescued so we should restore errinfo.
|
|
327
|
+
rb_set_errinfo(saved_errinfo);
|
|
207
328
|
}
|
|
208
329
|
}
|
|
209
330
|
|
|
@@ -221,28 +342,10 @@ bs_revalidation_set(VALUE self, VALUE enabled)
|
|
|
221
342
|
return enabled;
|
|
222
343
|
}
|
|
223
344
|
|
|
224
|
-
/*
|
|
225
|
-
* Bootsnap's ruby code registers a hook that notifies us via this function
|
|
226
|
-
* when compile_option changes. These changes invalidate all existing caches.
|
|
227
|
-
*
|
|
228
|
-
* Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
|
|
229
|
-
* can be represented by a uint.
|
|
230
|
-
*/
|
|
231
|
-
static VALUE
|
|
232
|
-
bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
233
|
-
{
|
|
234
|
-
if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) {
|
|
235
|
-
Check_Type(crc32_v, T_FIXNUM);
|
|
236
|
-
}
|
|
237
|
-
current_compile_option_crc32 = NUM2UINT(crc32_v);
|
|
238
|
-
return Qnil;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
345
|
static uint64_t
|
|
242
|
-
fnv1a_64_iter(uint64_t h, const
|
|
346
|
+
fnv1a_64_iter(uint64_t h, const unsigned char *s, size_t len)
|
|
243
347
|
{
|
|
244
|
-
unsigned char *
|
|
245
|
-
unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
|
|
348
|
+
const unsigned char *str_end = s + len;
|
|
246
349
|
|
|
247
350
|
while (s < str_end) {
|
|
248
351
|
h ^= (uint64_t)*s++;
|
|
@@ -253,45 +356,43 @@ fnv1a_64_iter(uint64_t h, const VALUE str)
|
|
|
253
356
|
}
|
|
254
357
|
|
|
255
358
|
static uint64_t
|
|
256
|
-
|
|
359
|
+
fnv1a_64_iter_str(uint64_t h, const VALUE str)
|
|
360
|
+
{
|
|
361
|
+
Check_Type(str, T_STRING);
|
|
362
|
+
return fnv1a_64_iter(h, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
static uint64_t
|
|
366
|
+
fnv1a_64_str(const VALUE str)
|
|
257
367
|
{
|
|
258
368
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
|
259
|
-
return
|
|
369
|
+
return fnv1a_64_iter_str(h, str);
|
|
260
370
|
}
|
|
261
371
|
|
|
262
372
|
/*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
373
|
+
* Bootsnap's ruby code registers a hook that notifies us via this function
|
|
374
|
+
* when compile_option changes. These changes invalidate all existing caches.
|
|
375
|
+
*
|
|
376
|
+
* Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
|
|
377
|
+
* can be represented by a uint.
|
|
265
378
|
*/
|
|
266
|
-
static
|
|
267
|
-
|
|
379
|
+
static VALUE
|
|
380
|
+
bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
268
381
|
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
|
|
272
|
-
if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
|
|
273
|
-
return FIX2INT(ruby_revision);
|
|
274
|
-
} else {
|
|
275
|
-
uint64_t hash;
|
|
276
|
-
|
|
277
|
-
hash = fnv1a_64(ruby_revision);
|
|
278
|
-
return (uint32_t)(hash >> 32);
|
|
382
|
+
if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) {
|
|
383
|
+
Check_Type(crc32_v, T_FIXNUM);
|
|
279
384
|
}
|
|
385
|
+
uint32_t crc32 = (uint32_t)NUM2UINT(crc32_v);
|
|
386
|
+
current_ruby_version_digest = fnv1a_64_iter(base_ruby_version_digest, (unsigned char *)&crc32, sizeof(crc32));
|
|
387
|
+
return Qnil;
|
|
280
388
|
}
|
|
281
389
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
* (or OS version), we need to invalidate the cache.
|
|
285
|
-
*/
|
|
286
|
-
static uint32_t
|
|
287
|
-
get_ruby_platform(void)
|
|
390
|
+
static uint64_t
|
|
391
|
+
get_ruby_version_digest(void)
|
|
288
392
|
{
|
|
289
|
-
uint64_t hash;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
|
293
|
-
hash = fnv1a_64(ruby_platform);
|
|
294
|
-
return (uint32_t)(hash >> 32);
|
|
393
|
+
uint64_t hash = fnv1a_64_str(rb_const_get(rb_mBootsnap, rb_intern("RUBY_CACHE_KEY")));
|
|
394
|
+
hash = fnv1a_64_iter(hash, (unsigned char *)&bootsnap_cache_version, sizeof(bootsnap_cache_version));
|
|
395
|
+
return hash;
|
|
295
396
|
}
|
|
296
397
|
|
|
297
398
|
/*
|
|
@@ -300,15 +401,35 @@ get_ruby_platform(void)
|
|
|
300
401
|
* be stored.
|
|
301
402
|
*
|
|
302
403
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
|
404
|
+
*
|
|
405
|
+
* `path_v` must already have been through FilePathValue(): the callers read it
|
|
406
|
+
* with RSTRING_PTR() too, so the conversion has to happen in the frame that
|
|
407
|
+
* owns the variable.
|
|
303
408
|
*/
|
|
304
409
|
static void
|
|
305
|
-
bs_cache_path(
|
|
410
|
+
bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
|
306
411
|
{
|
|
307
|
-
|
|
412
|
+
Check_Type(cachedir_v, T_STRING);
|
|
413
|
+
Check_Type(path_v, T_STRING);
|
|
414
|
+
|
|
415
|
+
long namespace_len = 0;
|
|
416
|
+
if (!NIL_P(namespace_v)) {
|
|
417
|
+
Check_Type(namespace_v, T_STRING);
|
|
418
|
+
namespace_len = RSTRING_LEN(namespace_v);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (RSTRING_LEN(cachedir_v) + namespace_len > MAX_CACHEDIR_SIZE) {
|
|
422
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const char * cachedir = RSTRING_PTR(cachedir_v);
|
|
426
|
+
const char * namespace = NIL_P(namespace_v) ? "" : RSTRING_PTR(namespace_v);
|
|
427
|
+
|
|
428
|
+
uint64_t hash = fnv1a_64_str(path_v);
|
|
308
429
|
uint8_t first_byte = (hash >> (64 - 8));
|
|
309
430
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
|
310
431
|
|
|
311
|
-
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
|
432
|
+
sprintf(*cache_path, "%s%s/%02"PRIx8"/%014"PRIx64, cachedir, namespace, first_byte, remainder);
|
|
312
433
|
}
|
|
313
434
|
|
|
314
435
|
/*
|
|
@@ -321,10 +442,8 @@ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_C
|
|
|
321
442
|
*/
|
|
322
443
|
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
|
|
323
444
|
struct bs_cache_key *k2) {
|
|
324
|
-
if (k1->
|
|
325
|
-
k1->
|
|
326
|
-
k1->compile_option == k2->compile_option &&
|
|
327
|
-
k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) {
|
|
445
|
+
if (k1->ruby_version_digest == k2->ruby_version_digest &&
|
|
446
|
+
k1->size == k2->size) {
|
|
328
447
|
if (k1->mtime == k2->mtime) {
|
|
329
448
|
return hit;
|
|
330
449
|
}
|
|
@@ -343,11 +462,34 @@ static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
|
|
|
343
462
|
return current_key->digest == cached_key->digest;
|
|
344
463
|
}
|
|
345
464
|
|
|
465
|
+
static ssize_t
|
|
466
|
+
resumable_write(int fd, const void *buf, size_t count)
|
|
467
|
+
{
|
|
468
|
+
size_t total_written = 0;
|
|
469
|
+
const char *ptr = buf;
|
|
470
|
+
|
|
471
|
+
while (count > 0) {
|
|
472
|
+
ssize_t res = write(fd, ptr, count);
|
|
473
|
+
|
|
474
|
+
if (res < 0) {
|
|
475
|
+
if (errno == EINTR) {
|
|
476
|
+
continue; // Interrupted by signal, try again
|
|
477
|
+
}
|
|
478
|
+
return -1; // Real error
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
ptr += res;
|
|
482
|
+
count -= res;
|
|
483
|
+
total_written += res;
|
|
484
|
+
}
|
|
485
|
+
return total_written;
|
|
486
|
+
}
|
|
487
|
+
|
|
346
488
|
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
|
|
347
489
|
{
|
|
348
490
|
old_key->mtime = current_key->mtime;
|
|
349
491
|
lseek(cache_fd, 0, SEEK_SET);
|
|
350
|
-
ssize_t nwrite =
|
|
492
|
+
ssize_t nwrite = resumable_write(cache_fd, old_key, KEY_SIZE);
|
|
351
493
|
if (nwrite < 0) {
|
|
352
494
|
*errno_provenance = "update_cache_key:write";
|
|
353
495
|
return -1;
|
|
@@ -368,10 +510,9 @@ static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_ke
|
|
|
368
510
|
*/
|
|
369
511
|
static void bs_cache_key_digest(struct bs_cache_key *key,
|
|
370
512
|
const VALUE input_data) {
|
|
371
|
-
if (key->
|
|
513
|
+
if (key->digest)
|
|
372
514
|
return;
|
|
373
|
-
key->digest =
|
|
374
|
-
key->digest_set = 1;
|
|
515
|
+
key->digest = fnv1a_64_str(input_data);
|
|
375
516
|
}
|
|
376
517
|
|
|
377
518
|
/*
|
|
@@ -380,25 +521,16 @@ static void bs_cache_key_digest(struct bs_cache_key *key,
|
|
|
380
521
|
* conversions on the ruby VALUE arguments before passing them along.
|
|
381
522
|
*/
|
|
382
523
|
static VALUE
|
|
383
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
|
524
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args)
|
|
384
525
|
{
|
|
385
526
|
FilePathValue(path_v);
|
|
386
527
|
|
|
387
|
-
Check_Type(cachedir_v, T_STRING);
|
|
388
|
-
Check_Type(path_v, T_STRING);
|
|
389
|
-
|
|
390
|
-
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
391
|
-
rb_raise(rb_eArgError, "cachedir too long");
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
char * cachedir = RSTRING_PTR(cachedir_v);
|
|
395
|
-
char * path = RSTRING_PTR(path_v);
|
|
396
528
|
char cache_path[MAX_CACHEPATH_SIZE];
|
|
397
529
|
|
|
398
530
|
/* generate cache path to cache_path */
|
|
399
|
-
bs_cache_path(
|
|
531
|
+
bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
|
|
400
532
|
|
|
401
|
-
return bs_fetch(
|
|
533
|
+
return bs_fetch(RSTRING_PTR(path_v), path_v, cache_path, handler, args);
|
|
402
534
|
}
|
|
403
535
|
|
|
404
536
|
/*
|
|
@@ -407,25 +539,15 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
|
|
|
407
539
|
* and doesn't return the content.
|
|
408
540
|
*/
|
|
409
541
|
static VALUE
|
|
410
|
-
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
542
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler)
|
|
411
543
|
{
|
|
412
544
|
FilePathValue(path_v);
|
|
413
545
|
|
|
414
|
-
Check_Type(cachedir_v, T_STRING);
|
|
415
|
-
Check_Type(path_v, T_STRING);
|
|
416
|
-
|
|
417
|
-
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
418
|
-
rb_raise(rb_eArgError, "cachedir too long");
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
char * cachedir = RSTRING_PTR(cachedir_v);
|
|
422
|
-
char * path = RSTRING_PTR(path_v);
|
|
423
546
|
char cache_path[MAX_CACHEPATH_SIZE];
|
|
424
|
-
|
|
425
547
|
/* generate cache path to cache_path */
|
|
426
|
-
bs_cache_path(
|
|
548
|
+
bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
|
|
427
549
|
|
|
428
|
-
return bs_precompile(
|
|
550
|
+
return bs_precompile(RSTRING_PTR(path_v), path_v, cache_path, handler);
|
|
429
551
|
}
|
|
430
552
|
|
|
431
553
|
static int bs_open_noatime(const char *path, int flags) {
|
|
@@ -471,13 +593,19 @@ open_current_file(const char * path, struct bs_cache_key * key, const char ** er
|
|
|
471
593
|
return -1;
|
|
472
594
|
}
|
|
473
595
|
|
|
474
|
-
key->
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
596
|
+
key->ruby_version_digest = current_ruby_version_digest;
|
|
597
|
+
|
|
598
|
+
// We're limited to file of 4GiB or less. Hopefully that's enough for everyone.
|
|
599
|
+
if (statbuf.st_size > (uint32_t)-1) {
|
|
600
|
+
*errno_provenance = "bs_fetch:open_current_file:file_too_big";
|
|
601
|
+
close(fd);
|
|
602
|
+
errno = EFBIG;
|
|
603
|
+
return -1;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
key->size = (uint32_t)statbuf.st_size;
|
|
607
|
+
key->mtime = (uint64_t)statbuf.st_mtime;
|
|
608
|
+
key->digest = 0;
|
|
481
609
|
|
|
482
610
|
return fd;
|
|
483
611
|
}
|
|
@@ -547,10 +675,10 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
|
547
675
|
|
|
548
676
|
/*
|
|
549
677
|
* The cache file is laid out like:
|
|
550
|
-
* 0
|
|
551
|
-
*
|
|
678
|
+
* 0...`KEY_SIZE` : bs_cache_key
|
|
679
|
+
* `KEY_SIZE`..-1 : cached artifact
|
|
552
680
|
*
|
|
553
|
-
* This function takes a file descriptor whose position is pre-set to
|
|
681
|
+
* This function takes a file descriptor whose position is pre-set to `KEY_SIZE`, and
|
|
554
682
|
* the data_size (corresponding to the remaining number of bytes) listed in the
|
|
555
683
|
* cache header.
|
|
556
684
|
*
|
|
@@ -626,6 +754,17 @@ mkpath(char * file_path, mode_t mode)
|
|
|
626
754
|
return 0;
|
|
627
755
|
}
|
|
628
756
|
|
|
757
|
+
static int
|
|
758
|
+
bs_fchmod(int fd, const char *path, mode_t mode)
|
|
759
|
+
{
|
|
760
|
+
#ifdef _WIN32
|
|
761
|
+
setmode(fd, O_BINARY);
|
|
762
|
+
return chmod(path, mode);
|
|
763
|
+
#else
|
|
764
|
+
return fchmod(fd, mode);
|
|
765
|
+
#endif
|
|
766
|
+
}
|
|
767
|
+
|
|
629
768
|
/*
|
|
630
769
|
* Write a cache header/key and a compiled artifact to a given cache path by
|
|
631
770
|
* writing to a tmpfile and then renaming the tmpfile over top of the final
|
|
@@ -639,13 +778,18 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
|
|
|
639
778
|
int fd, ret, attempt;
|
|
640
779
|
ssize_t nwrite;
|
|
641
780
|
|
|
781
|
+
uint64_t data_size = RSTRING_LEN(data);
|
|
782
|
+
if (data_size > (uint32_t)-1) {
|
|
783
|
+
return 0; // Don't cache.
|
|
784
|
+
}
|
|
785
|
+
|
|
642
786
|
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
|
643
787
|
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
|
644
788
|
strcat(tmp_path, ".tmp.XXXXXX");
|
|
645
789
|
|
|
646
790
|
// mkstemp modifies the template to be the actual created path
|
|
647
791
|
fd = mkstemp(tmp_path);
|
|
648
|
-
if (fd
|
|
792
|
+
if (fd >= 0) break;
|
|
649
793
|
|
|
650
794
|
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
|
651
795
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
|
@@ -657,45 +801,42 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
|
|
|
657
801
|
return -1;
|
|
658
802
|
}
|
|
659
803
|
|
|
660
|
-
if (
|
|
804
|
+
if (bs_fchmod(fd, tmp_path, 0644 & ~current_umask) < 0) {
|
|
805
|
+
close(fd);
|
|
661
806
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
|
662
807
|
return -1;
|
|
663
808
|
}
|
|
664
809
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
#endif
|
|
668
|
-
|
|
669
|
-
key->data_size = RSTRING_LEN(data);
|
|
670
|
-
nwrite = write(fd, key, KEY_SIZE);
|
|
810
|
+
key->data_size = (uint32_t)data_size;
|
|
811
|
+
nwrite = resumable_write(fd, key, KEY_SIZE);
|
|
671
812
|
if (nwrite < 0) {
|
|
813
|
+
close(fd);
|
|
672
814
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
|
673
815
|
return -1;
|
|
674
816
|
}
|
|
675
817
|
if (nwrite != KEY_SIZE) {
|
|
818
|
+
close(fd);
|
|
676
819
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
|
677
820
|
errno = EIO; /* Lies but whatever */
|
|
678
821
|
return -1;
|
|
679
822
|
}
|
|
680
823
|
|
|
681
|
-
nwrite =
|
|
824
|
+
nwrite = resumable_write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
|
682
825
|
if (nwrite < 0) return -1;
|
|
683
826
|
if (nwrite != RSTRING_LEN(data)) {
|
|
827
|
+
close(fd);
|
|
684
828
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
|
685
829
|
errno = EIO; /* Lies but whatever */
|
|
686
830
|
return -1;
|
|
687
831
|
}
|
|
688
832
|
|
|
689
833
|
close(fd);
|
|
834
|
+
|
|
690
835
|
ret = rename(tmp_path, path);
|
|
691
836
|
if (ret < 0) {
|
|
692
837
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
|
693
838
|
return -1;
|
|
694
839
|
}
|
|
695
|
-
ret = chmod(path, 0664 & ~current_umask);
|
|
696
|
-
if (ret < 0) {
|
|
697
|
-
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
|
698
|
-
}
|
|
699
840
|
return ret;
|
|
700
841
|
}
|
|
701
842
|
|
|
@@ -845,7 +986,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
845
986
|
exception_message = path_v;
|
|
846
987
|
goto fail_errno;
|
|
847
988
|
}
|
|
848
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
989
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
849
990
|
if (exception_tag != 0) goto raise;
|
|
850
991
|
goto succeed;
|
|
851
992
|
} else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
|
@@ -871,7 +1012,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
871
1012
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
|
872
1013
|
* to cache anything; just return input_to_output(input_data) */
|
|
873
1014
|
if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
|
874
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1015
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
875
1016
|
if (exception_tag != 0) goto raise;
|
|
876
1017
|
goto succeed;
|
|
877
1018
|
}
|
|
@@ -891,7 +1032,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
891
1032
|
|
|
892
1033
|
if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
|
893
1034
|
/* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
|
|
894
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1035
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
895
1036
|
if (exception_tag != 0) goto raise;
|
|
896
1037
|
} else if (NIL_P(output_data)) {
|
|
897
1038
|
/* If output_data is nil, delete the cache entry and generate the output
|
|
@@ -905,7 +1046,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
905
1046
|
goto fail_errno;
|
|
906
1047
|
}
|
|
907
1048
|
}
|
|
908
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1049
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
909
1050
|
if (exception_tag != 0) goto raise;
|
|
910
1051
|
}
|
|
911
1052
|
|
|
@@ -1001,7 +1142,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
1001
1142
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
|
1002
1143
|
|
|
1003
1144
|
/* Read the contents of the source file into a buffer */
|
|
1004
|
-
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
|
|
1145
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
|
|
1005
1146
|
|
|
1006
1147
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
|
1007
1148
|
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
|
@@ -1063,6 +1204,7 @@ struct i2o_data {
|
|
|
1063
1204
|
VALUE handler;
|
|
1064
1205
|
VALUE args;
|
|
1065
1206
|
VALUE input_data;
|
|
1207
|
+
VALUE pathval;
|
|
1066
1208
|
};
|
|
1067
1209
|
|
|
1068
1210
|
struct i2s_data {
|
|
@@ -1075,7 +1217,7 @@ static VALUE
|
|
|
1075
1217
|
try_storage_to_output(VALUE arg)
|
|
1076
1218
|
{
|
|
1077
1219
|
struct s2o_data * data = (struct s2o_data *)arg;
|
|
1078
|
-
return rb_funcall(data->handler,
|
|
1220
|
+
return rb_funcall(data->handler, id_storage_to_output, 2, data->storage_data, data->args);
|
|
1079
1221
|
}
|
|
1080
1222
|
|
|
1081
1223
|
static int
|
|
@@ -1092,12 +1234,13 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
|
|
|
1092
1234
|
}
|
|
1093
1235
|
|
|
1094
1236
|
static void
|
|
1095
|
-
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
|
1237
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE path_v, VALUE * output_data, int * exception_tag)
|
|
1096
1238
|
{
|
|
1097
1239
|
struct i2o_data i2o_data = {
|
|
1098
1240
|
.handler = handler,
|
|
1099
1241
|
.args = args,
|
|
1100
1242
|
.input_data = input_data,
|
|
1243
|
+
.pathval = path_v,
|
|
1101
1244
|
};
|
|
1102
1245
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
|
1103
1246
|
}
|
|
@@ -1106,14 +1249,14 @@ static VALUE
|
|
|
1106
1249
|
prot_input_to_output(VALUE arg)
|
|
1107
1250
|
{
|
|
1108
1251
|
struct i2o_data * data = (struct i2o_data *)arg;
|
|
1109
|
-
return rb_funcall(data->handler,
|
|
1252
|
+
return rb_funcall(data->handler, id_input_to_output, 3, data->input_data, data->pathval, data->args);
|
|
1110
1253
|
}
|
|
1111
1254
|
|
|
1112
1255
|
static VALUE
|
|
1113
1256
|
try_input_to_storage(VALUE arg)
|
|
1114
1257
|
{
|
|
1115
1258
|
struct i2s_data * data = (struct i2s_data *)arg;
|
|
1116
|
-
return rb_funcall(data->handler,
|
|
1259
|
+
return rb_funcall(data->handler, id_input_to_storage, 2, data->input_data, data->pathval);
|
|
1117
1260
|
}
|
|
1118
1261
|
|
|
1119
1262
|
static int
|