qr_reader-native 0.0.1

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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,1014 @@
1
+ /* sp_iobuffer.c -- IO::Buffer, CRuby 4.x semantics (see sp_iobuffer.h for
2
+ the memory model). Bound through packages/io/io/buffer.rb; that file is
3
+ spliced into any program referencing `IO::Buffer`, so this object is
4
+ pulled from libspinel_rt.a only when the class is actually used.
5
+
6
+ Conversion edges follow CRuby's NUM2* family: 8/16-bit stores wrap any
7
+ 64-bit value; 32-bit stores range-check ([-2^31, 2^32-1] unsigned,
8
+ [-2^31, 2^31-1] signed); 64-bit stores accept the full two's-complement
9
+ range plus Bignums up to the unsigned bound. Messages mirror CRuby's,
10
+ including the "integer ... to" (fixnum) / "bignum ... into" wording. */
11
+ #include "sp_iobuffer.h"
12
+ #include "sp_io.h" /* the IO integration: sp_File, the readiness parks */
13
+ #include <stdlib.h>
14
+ #include <string.h>
15
+ #include <stdio.h>
16
+ #include <math.h>
17
+ #include <unistd.h>
18
+ #include <errno.h>
19
+ #include <fcntl.h>
20
+ #include <sys/mman.h>
21
+ #include <sys/stat.h>
22
+
23
+ extern const char *sp_sprintf(const char *fmt, ...);
24
+ extern SP_NORETURN void sp_raise_cls(const char *cls, const char *msg);
25
+
26
+ /* sp_bigint.c helpers (sp_bigint.h is mruby-shim territory; the u64 pair is
27
+ added beside the other serialization helpers there) */
28
+ extern sp_Bigint *sp_bigint_new_u64(uint64_t v);
29
+ extern int sp_bigint_mag_u64(sp_Bigint *b, uint64_t *out); /* 1 iff |b| < 2^64 */
30
+ extern int sp_bigint_sign(sp_Bigint *b);
31
+ extern double sp_bigint_to_double(sp_Bigint *b);
32
+
33
+ /* ---- raising ---- */
34
+
35
+ static SP_NORETURN void iob_arg(const char *msg) { sp_raise_cls("ArgumentError", msg); }
36
+
37
+ /* The lock does NOT gate data access: reads and writes (and slice/dup/
38
+ copy/each/...) all run inside locked { }. Only ownership-shaped
39
+ operations refuse -- free / resize / transfer / a second locked { } --
40
+ each with its own message (matched against CRuby empirically; note the
41
+ lock also survives an exception out of the block). */
42
+ static void iob_readable(sp_IOBuffer *b) { (void)b; }
43
+ static void iob_writable(sp_IOBuffer *b) {
44
+ if (b->flags & SP_IOB_READONLY) sp_raise_cls("IO::Buffer::AccessError", "Buffer is not writable!");
45
+ }
46
+
47
+ /* the value's Ruby spelling for TypeError messages (nil/true/false read as
48
+ themselves, like CRuby's) */
49
+ static const char *iob_val_name(sp_RbVal v) {
50
+ switch (v.tag) {
51
+ case SP_TAG_NIL: return "nil";
52
+ case SP_TAG_BOOL: return v.v.i ? "true" : "false";
53
+ case SP_TAG_INT: case SP_TAG_BIGINT: return "Integer";
54
+ case SP_TAG_FLT: return "Float";
55
+ case SP_TAG_STR: return "String";
56
+ case SP_TAG_SYM: return "Symbol";
57
+ case SP_TAG_CLASS: return "Class";
58
+ case SP_TAG_OBJ:
59
+ switch (v.cls_id) {
60
+ case SP_BUILTIN_INT_ARRAY: case SP_BUILTIN_STR_ARRAY:
61
+ case SP_BUILTIN_FLT_ARRAY: case SP_BUILTIN_PTR_ARRAY:
62
+ case SP_BUILTIN_SYM_ARRAY: case SP_BUILTIN_POLY_ARRAY:
63
+ return "Array";
64
+ case SP_BUILTIN_STR_INT_HASH: case SP_BUILTIN_STR_STR_HASH:
65
+ case SP_BUILTIN_INT_STR_HASH: case SP_BUILTIN_SYM_INT_HASH:
66
+ case SP_BUILTIN_SYM_STR_HASH: case SP_BUILTIN_STR_POLY_HASH:
67
+ case SP_BUILTIN_SYM_POLY_HASH: case SP_BUILTIN_POLY_POLY_HASH:
68
+ return "Hash";
69
+ case SP_BUILTIN_RANGE: return "Range";
70
+ case SP_BUILTIN_TIME: return "Time";
71
+ case SP_BUILTIN_PROC: return "Proc";
72
+ case SP_BUILTIN_IO: return "IO";
73
+ default: return "Object";
74
+ }
75
+ default: return "Object";
76
+ }
77
+ }
78
+
79
+ /* ---- base pointer + bounds ---- */
80
+
81
+ static uint8_t *iob_base(sp_IOBuffer *b) {
82
+ if (b->source) return b->source->data ? b->source->data + b->off : NULL;
83
+ return b->data;
84
+ }
85
+
86
+ /* A slice whose root was freed, transferred, or shrunk below the view's
87
+ range must not be read through: CRuby raises InvalidatedError there, and
88
+ the stale base would be NULL or past the live allocation. */
89
+ static int iob_slice_invalid(sp_IOBuffer *b) {
90
+ return b->source && (!b->source->data || b->off > b->source->size - b->size);
91
+ }
92
+ static uint8_t *iob_ptr(sp_IOBuffer *b) {
93
+ if (iob_slice_invalid(b))
94
+ sp_raise_cls("IO::Buffer::InvalidatedError", "Buffer has been invalidated!");
95
+ return iob_base(b);
96
+ }
97
+
98
+ /* Overflow-safe: `off + len > size` wraps for large operands, so compare
99
+ against the remaining room instead (both are non-negative here). */
100
+ static void iob_range(sp_IOBuffer *b, int64_t off, int64_t len) {
101
+ if (off < 0) iob_arg("Offset can't be negative!");
102
+ if (len < 0) iob_arg("Length can't be negative!");
103
+ if (off > b->size || len > b->size - off) iob_arg("Specified offset+length is bigger than the buffer size!");
104
+ }
105
+
106
+ /* ---- the type table ---- */
107
+
108
+ typedef struct { const char *name; int8_t width; int8_t be; int8_t sign; int8_t flt; } IobType;
109
+ static const IobType iob_types[SP_IOB_TY__COUNT] = {
110
+ [SP_IOB_TY_U8] = {"U8", 1, 0, 0, 0}, [SP_IOB_TY_S8] = {"S8", 1, 0, 1, 0},
111
+ [SP_IOB_TY_u16] = {"u16", 2, 0, 0, 0}, [SP_IOB_TY_s16] = {"s16", 2, 0, 1, 0},
112
+ [SP_IOB_TY_U16] = {"U16", 2, 1, 0, 0}, [SP_IOB_TY_S16] = {"S16", 2, 1, 1, 0},
113
+ [SP_IOB_TY_u32] = {"u32", 4, 0, 0, 0}, [SP_IOB_TY_s32] = {"s32", 4, 0, 1, 0},
114
+ [SP_IOB_TY_U32] = {"U32", 4, 1, 0, 0}, [SP_IOB_TY_S32] = {"S32", 4, 1, 1, 0},
115
+ [SP_IOB_TY_u64] = {"u64", 8, 0, 0, 0}, [SP_IOB_TY_s64] = {"s64", 8, 0, 1, 0},
116
+ [SP_IOB_TY_U64] = {"U64", 8, 1, 0, 0}, [SP_IOB_TY_S64] = {"S64", 8, 1, 1, 0},
117
+ [SP_IOB_TY_f32] = {"f32", 4, 0, 1, 1}, [SP_IOB_TY_f64] = {"f64", 8, 0, 1, 1},
118
+ [SP_IOB_TY_F32] = {"F32", 4, 1, 1, 1}, [SP_IOB_TY_F64] = {"F64", 8, 1, 1, 1},
119
+ };
120
+
121
+ int sp_IOBuffer_type_of(const char *name) {
122
+ if (!name) return -1;
123
+ for (int i = 0; i < SP_IOB_TY__COUNT; i++)
124
+ if (strcmp(iob_types[i].name, name) == 0) return i;
125
+ return -1;
126
+ }
127
+
128
+ static int iob_decode_type(sp_RbVal type) {
129
+ if (type.tag == SP_TAG_SYM && sp_sym_name_fn) {
130
+ int t = sp_IOBuffer_type_of(sp_sym_name_fn((sp_sym)type.v.i));
131
+ if (t >= 0) return t;
132
+ }
133
+ iob_arg("Invalid type name!");
134
+ return -1;
135
+ }
136
+
137
+ static uint8_t *iob_value_ptr(sp_IOBuffer *b, int64_t off, int width) {
138
+ if (off < 0) iob_arg("Offset can't be negative!");
139
+ if (b->size < width || off > b->size - width)
140
+ iob_arg(sp_sprintf("Type extends beyond end of buffer! (offset=%lld > size=%lld)",
141
+ (long long)off, (long long)b->size));
142
+ return iob_ptr(b) + off;
143
+ }
144
+
145
+ /* ---- raw loads/stores (byte-assembled: host-endian agnostic) ---- */
146
+
147
+ static uint64_t iob_load(const uint8_t *p, int width, int be) {
148
+ uint64_t v = 0;
149
+ if (be) for (int i = 0; i < width; i++) v = (v << 8) | p[i];
150
+ else for (int i = 0; i < width; i++) v |= (uint64_t)p[i] << (8 * i);
151
+ return v;
152
+ }
153
+ static void iob_store(uint8_t *p, int width, int be, uint64_t v) {
154
+ if (be) for (int i = 0; i < width; i++) p[i] = (uint8_t)(v >> (8 * (width - 1 - i)));
155
+ else for (int i = 0; i < width; i++) p[i] = (uint8_t)(v >> (8 * i));
156
+ }
157
+
158
+ /* ---- value conversion (CRuby NUM2* semantics + messages) ---- */
159
+
160
+ static int64_t iob_float_to_i64(double f) {
161
+ if (isnan(f)) sp_raise_cls("FloatDomainError", "NaN");
162
+ if (isinf(f)) sp_raise_cls("FloatDomainError", f > 0 ? "Infinity" : "-Infinity");
163
+ f = trunc(f);
164
+ if (f < -9223372036854775808.0 || f >= 9223372036854775808.0)
165
+ sp_raise_cls("RangeError", "float out of range of integer");
166
+ return (int64_t)f;
167
+ }
168
+
169
+ /* Convert `val` to the store word for integer type `ty`. */
170
+ static uint64_t iob_int_operand(int ty, sp_RbVal val) {
171
+ const IobType *t = &iob_types[ty];
172
+ const char *cname =
173
+ t->width == 8 ? (t->sign ? "long long" : "unsigned long long")
174
+ : t->width == 4 ? (t->sign ? "int" : "unsigned int")
175
+ : (t->sign ? "long" : "unsigned long");
176
+ int64_t iv;
177
+ if (val.tag == SP_TAG_INT) iv = val.v.i;
178
+ else if (val.tag == SP_TAG_FLT) iv = iob_float_to_i64(val.v.f);
179
+ else if (val.tag == SP_TAG_BIGINT) {
180
+ sp_Bigint *bg = (sp_Bigint *)val.v.p;
181
+ uint64_t mag;
182
+ int fits = sp_bigint_mag_u64(bg, &mag);
183
+ int neg = sp_bigint_sign(bg) < 0;
184
+ if (t->width == 8 && !t->sign) {
185
+ /* u64: [-2^63, 2^64-1] */
186
+ if (!neg) {
187
+ if (!fits) sp_raise_cls("RangeError", sp_sprintf("bignum too big to convert into '%s'", cname));
188
+ return mag;
189
+ }
190
+ if (!fits || mag > (uint64_t)1 << 63)
191
+ sp_raise_cls("RangeError", sp_sprintf("bignum out of range of %s", cname));
192
+ return (uint64_t)0 - mag;
193
+ }
194
+ if (t->width <= 2) {
195
+ /* 8/16-bit: any value representable in 64 bits wraps */
196
+ if (!neg) {
197
+ if (!fits) sp_raise_cls("RangeError", sp_sprintf("bignum too big to convert into '%s'", cname));
198
+ return mag;
199
+ }
200
+ if (!fits || mag > (uint64_t)1 << 63)
201
+ sp_raise_cls("RangeError", sp_sprintf("bignum too small to convert into '%s'", cname));
202
+ return (uint64_t)0 - mag;
203
+ }
204
+ /* s64: a Bignum is out of range except at the negative bound (any heap
205
+ Bignum a 64-bit build makes has |v| >= 2^63; smaller ones stay sp_int). */
206
+ if (t->width == 8 && t->sign) {
207
+ if (fits && !neg && mag <= (uint64_t)INT64_MAX) return mag;
208
+ if (fits && neg && mag <= (uint64_t)1 << 63) return (uint64_t)0 - mag;
209
+ }
210
+ /* 32-bit: the type's own width decides, not how the value is held. On a
211
+ 32-bit build sp_int is 32 bits wide and every value past 2^31-1 is a
212
+ heap Bignum, 0xCAFEBABE included, which is most of the range a U32 is
213
+ asked for (#4647). A magnitude that fits the field is stored. */
214
+ if (t->width == 4 && fits) {
215
+ uint64_t hi = t->sign ? ((uint64_t)1 << 31) - 1 : ((uint64_t)1 << 32) - 1;
216
+ if (!neg && mag <= hi) return mag;
217
+ if (neg && mag <= (uint64_t)1 << 31) return (uint64_t)0 - mag;
218
+ }
219
+ sp_raise_cls("RangeError", sp_sprintf(neg ? "bignum too small to convert into '%s'"
220
+ : "bignum too big to convert into '%s'", cname));
221
+ return 0;
222
+ }
223
+ else {
224
+ sp_raise_cls("TypeError", sp_sprintf("no implicit conversion of %s into Integer", iob_val_name(val)));
225
+ return 0;
226
+ }
227
+ if (t->width == 4) {
228
+ int64_t lo = -((int64_t)1 << 31);
229
+ int64_t hi = t->sign ? ((int64_t)1 << 31) - 1 : ((int64_t)1 << 32) - 1;
230
+ if (iv < lo) sp_raise_cls("RangeError", sp_sprintf("integer %lld too small to convert to '%s'", (long long)iv, cname));
231
+ if (iv > hi) sp_raise_cls("RangeError", sp_sprintf("integer %lld too big to convert to '%s'", (long long)iv, cname));
232
+ }
233
+ return (uint64_t)iv;
234
+ }
235
+
236
+ static double iob_float_operand(sp_RbVal val) {
237
+ if (val.tag == SP_TAG_FLT) return val.v.f;
238
+ if (val.tag == SP_TAG_INT) return (double)val.v.i;
239
+ if (val.tag == SP_TAG_BIGINT) return sp_bigint_to_double((sp_Bigint *)val.v.p);
240
+ if (val.tag == SP_TAG_OBJ && val.cls_id == SP_BUILTIN_RATIONAL && val.v.p) {
241
+ sp_Rational *r = (sp_Rational *)val.v.p;
242
+ return (double)r->num / (double)r->den;
243
+ }
244
+ sp_raise_cls("TypeError", sp_sprintf("can't convert %s into Float", iob_val_name(val)));
245
+ return 0;
246
+ }
247
+
248
+ /* ---- boxed load/store cores shared by the generic and typed entries ---- */
249
+
250
+ static sp_RbVal iob_get_core(sp_IOBuffer *b, int ty, int64_t off) {
251
+ const IobType *t = &iob_types[ty];
252
+ iob_readable(b);
253
+ const uint8_t *p = iob_value_ptr(b, off, t->width);
254
+ if (t->flt) {
255
+ uint64_t raw = iob_load(p, t->width, t->be);
256
+ if (t->width == 4) { union { uint32_t u; float f; } u; u.u = (uint32_t)raw; return sp_box_float(u.f); }
257
+ union { uint64_t u; double f; } u; u.u = raw; return sp_box_float(u.f);
258
+ }
259
+ uint64_t raw = iob_load(p, t->width, t->be);
260
+ /* A loaded value goes out as a machine int when it fits sp_int and is not
261
+ SP_INT_NIL (the nullable-int sentinel, INTPTR_MIN), and as a Bignum
262
+ otherwise. That is by the WIDTH OF sp_int, not of the field: on a 32-bit
263
+ build a U32 above 2**31-1 and an S32 of exactly INT32_MIN are Bignums
264
+ the same way a u64 above 2**63-1 and an s64 of INT64_MIN are on a 64-bit
265
+ one (#4647). */
266
+ if (t->sign) {
267
+ /* sign-extend from width */
268
+ int shift = 64 - 8 * t->width;
269
+ int64_t sv = (int64_t)(raw << shift) >> shift;
270
+ if (sv < (int64_t)INTPTR_MIN || sv > (int64_t)INTPTR_MAX || (sp_int)sv == SP_INT_NIL) {
271
+ SP_GC_ROOT(b);
272
+ return sp_box_bigint(sp_bigint_new_int(sv));
273
+ }
274
+ return sp_box_int((sp_int)sv);
275
+ }
276
+ if (raw > (uint64_t)INTPTR_MAX) {
277
+ SP_GC_ROOT(b);
278
+ return sp_box_bigint(sp_bigint_new_u64(raw));
279
+ }
280
+ return sp_box_int((sp_int)raw);
281
+ }
282
+
283
+ static sp_int iob_set_core(sp_IOBuffer *b, int ty, int64_t off, sp_RbVal val) {
284
+ const IobType *t = &iob_types[ty];
285
+ iob_writable(b);
286
+ uint8_t *p = iob_value_ptr(b, off, t->width);
287
+ if (t->flt) {
288
+ double f = iob_float_operand(val);
289
+ uint64_t raw;
290
+ if (t->width == 4) { union { uint32_t u; float f; } u; u.f = (float)f; raw = u.u; }
291
+ else { union { uint64_t u; double f; } u; u.f = f; raw = u.u; }
292
+ iob_store(p, t->width, t->be, raw);
293
+ }
294
+ else {
295
+ iob_store(p, t->width, t->be, iob_int_operand(ty, val));
296
+ }
297
+ return t->width;
298
+ }
299
+
300
+ /* ---- constructors ---- */
301
+
302
+ static void iob_scan(void *p) {
303
+ sp_IOBuffer *b = (sp_IOBuffer *)p;
304
+ if (b->source) sp_gc_mark(b->source);
305
+ }
306
+ /* Give the allocation back: munmap for a mapped file, free otherwise. */
307
+ static void iob_release(sp_IOBuffer *b) {
308
+ if (b->map_base) { munmap(b->map_base, b->map_len); b->map_base = NULL; b->map_len = 0; }
309
+ else free(b->data);
310
+ b->data = NULL;
311
+ }
312
+ void sp_IOBuffer_fin(void *p) {
313
+ iob_release((sp_IOBuffer *)p);
314
+ }
315
+
316
+ static sp_IOBuffer *iob_alloc_obj(sp_int cls_id) {
317
+ sp_IOBuffer *b = (sp_IOBuffer *)sp_gc_alloc(sizeof(sp_IOBuffer), sp_IOBuffer_fin, iob_scan);
318
+ memset(b, 0, sizeof *b);
319
+ b->cls_id = cls_id;
320
+ return b;
321
+ }
322
+
323
+ sp_int sp_IOBuffer_page_size(void) {
324
+ long ps = sysconf(_SC_PAGESIZE);
325
+ return ps > 0 ? (sp_int)ps : 4096;
326
+ }
327
+
328
+ static void iob_allocate(sp_IOBuffer *b, int64_t size, uint32_t flags, int flags_given) {
329
+ if (size < 0) iob_arg("Size can't be negative!");
330
+ if (!flags_given)
331
+ flags = size == 0 ? 0
332
+ : size >= (int64_t)sp_IOBuffer_page_size() ? SP_IOB_MAPPED : SP_IOB_INTERNAL;
333
+ else if (size > 0 && !(flags & (SP_IOB_INTERNAL | SP_IOB_MAPPED)))
334
+ sp_raise_cls("IO::Buffer::AllocationError", "Could not allocate buffer!");
335
+ b->flags = flags;
336
+ b->size = size;
337
+ if (size > 0) {
338
+ b->data = (uint8_t *)calloc(1, (size_t)size);
339
+ if (!b->data) sp_oom_die();
340
+ }
341
+ }
342
+
343
+ sp_IOBuffer *sp_IOBuffer_new(sp_int cls_id) {
344
+ sp_IOBuffer *b = iob_alloc_obj(cls_id);
345
+ iob_allocate(b, 65536, 0, 0);
346
+ return b;
347
+ }
348
+ sp_IOBuffer *sp_IOBuffer_new_i(sp_int cls_id, sp_int size) {
349
+ sp_IOBuffer *b = iob_alloc_obj(cls_id);
350
+ iob_allocate(b, size, 0, 0);
351
+ return b;
352
+ }
353
+ sp_IOBuffer *sp_IOBuffer_new_if(sp_int cls_id, sp_int size, sp_int flags) {
354
+ sp_IOBuffer *b = iob_alloc_obj(cls_id);
355
+ iob_allocate(b, size, (uint32_t)flags, 1);
356
+ return b;
357
+ }
358
+
359
+ sp_IOBuffer *sp_IOBuffer_become_for(sp_IOBuffer *b, const char *s) {
360
+ size_t n = sp_str_byte_len(s);
361
+ uint8_t *d = (uint8_t *)malloc(n ? n : 1); /* non-NULL even for "": for("") is not a null buffer */
362
+ if (!d) sp_oom_die();
363
+ memcpy(d, s, n);
364
+ iob_release(b);
365
+ b->data = d;
366
+ b->source = NULL;
367
+ b->off = 0;
368
+ b->size = (int64_t)n;
369
+ b->flags = SP_IOB_EXTERNAL | SP_IOB_READONLY | SP_IOB_SLICE;
370
+ return b;
371
+ }
372
+
373
+ /* ---- generic + typed accessors ---- */
374
+
375
+ sp_RbVal sp_IOBuffer_get_value(sp_IOBuffer *b, sp_RbVal type, sp_int off) {
376
+ return iob_get_core(b, iob_decode_type(type), off);
377
+ }
378
+ sp_int sp_IOBuffer_set_value(sp_IOBuffer *b, sp_RbVal type, sp_int off, sp_RbVal val) {
379
+ return iob_set_core(b, iob_decode_type(type), off, val);
380
+ }
381
+
382
+ sp_int sp_IOBuffer_get_i(sp_IOBuffer *b, sp_int ty, sp_int off) {
383
+ const IobType *t = &iob_types[ty];
384
+ iob_readable(b);
385
+ const uint8_t *p = iob_value_ptr(b, off, t->width);
386
+ uint64_t raw = iob_load(p, t->width, t->be);
387
+ if (t->sign) { int shift = 64 - 8 * t->width; return (int64_t)(raw << shift) >> shift; }
388
+ return (sp_int)raw;
389
+ }
390
+ sp_RbVal sp_IOBuffer_get_x(sp_IOBuffer *b, sp_int ty, sp_int off) {
391
+ return iob_get_core(b, (int)ty, off);
392
+ }
393
+ double sp_IOBuffer_get_f(sp_IOBuffer *b, sp_int ty, sp_int off) {
394
+ const IobType *t = &iob_types[ty];
395
+ iob_readable(b);
396
+ const uint8_t *p = iob_value_ptr(b, off, t->width);
397
+ uint64_t raw = iob_load(p, t->width, t->be);
398
+ if (t->width == 4) { union { uint32_t u; float f; } u; u.u = (uint32_t)raw; return u.f; }
399
+ union { uint64_t u; double f; } u; u.u = raw; return u.f;
400
+ }
401
+ sp_int sp_IOBuffer_set_i(sp_IOBuffer *b, sp_int ty, sp_int off, sp_int v) {
402
+ return iob_set_core(b, ty, off, sp_box_int(v));
403
+ }
404
+ sp_int sp_IOBuffer_set_f(sp_IOBuffer *b, sp_int ty, sp_int off, double v) {
405
+ return iob_set_core(b, ty, off, sp_box_float(v));
406
+ }
407
+ /* The typed lowering with a BOXED value: the generic path minus the symbol
408
+ decode. A boxed value cannot go through set_i -- an integer type takes a
409
+ Float (truncating, as CRuby does) and u64/s64 take a Bignum -- so it
410
+ reaches iob_set_core unexamined, exactly as set_value leaves it. */
411
+ sp_int sp_IOBuffer_set_v(sp_IOBuffer *b, sp_int ty, sp_int off, sp_RbVal v) {
412
+ return iob_set_core(b, ty, off, v);
413
+ }
414
+
415
+ /* ---- strings ---- */
416
+
417
+ /* The bare-offset arm gets its own message only when the LENGTH was
418
+ defaulted (CRuby: get_string(9) names the offset, get_string(9, 0) the
419
+ offset+length). */
420
+ static const char *iob_get_string(sp_IOBuffer *b, int64_t off, int64_t len, int len_given) {
421
+ SP_GC_ROOT(b);
422
+ iob_readable(b);
423
+ if (off < 0) iob_arg("Offset can't be negative!");
424
+ if (!len_given) {
425
+ if (off > b->size) iob_arg("The given offset is bigger than the buffer size!");
426
+ len = b->size - off;
427
+ }
428
+ if (len < 0) iob_arg("Length can't be negative!");
429
+ if (off > b->size || len > b->size - off) iob_arg("Specified offset+length is bigger than the buffer size!");
430
+ char *r = sp_str_alloc_raw((size_t)len + 1);
431
+ if (len > 0) memcpy(r, iob_ptr(b) + off, (size_t)len);
432
+ r[len] = '\0';
433
+ sp_str_set_len(r, (size_t)len);
434
+ sp_str_mark_binary(r);
435
+ return r;
436
+ }
437
+ const char *sp_IOBuffer_get_string0(sp_IOBuffer *b) { return iob_get_string(b, 0, 0, 0); }
438
+ const char *sp_IOBuffer_get_string1(sp_IOBuffer *b, sp_int off) { return iob_get_string(b, off, 0, 0); }
439
+ const char *sp_IOBuffer_get_string2(sp_IOBuffer *b, sp_int off, sp_int len) { return iob_get_string(b, off, len, 1); }
440
+
441
+ static sp_int iob_set_string(sp_IOBuffer *b, const char *s, int64_t off, int64_t len, int64_t soff, int len_given) {
442
+ iob_writable(b);
443
+ int64_t slen = (int64_t)sp_str_byte_len(s);
444
+ if (off < 0) iob_arg("Offset can't be negative!");
445
+ if (soff < 0) iob_arg("Source offset can't be negative!");
446
+ if (soff > slen) iob_arg("The given source offset is bigger than the source itself!");
447
+ if (!len_given) len = slen - soff;
448
+ if (len < 0) iob_arg("Length can't be negative!");
449
+ /* destination range before source range, as CRuby orders them */
450
+ if (off > b->size || len > b->size - off) iob_arg("Specified offset+length is bigger than the buffer size!");
451
+ if (len > slen - soff) iob_arg("The computed source range exceeds the size of the source buffer!");
452
+ if (len > 0) memmove(iob_ptr(b) + off, s + soff, (size_t)len);
453
+ return len;
454
+ }
455
+ sp_int sp_IOBuffer_set_string1(sp_IOBuffer *b, const char *s) { return iob_set_string(b, s, 0, 0, 0, 0); }
456
+ sp_int sp_IOBuffer_set_string2(sp_IOBuffer *b, const char *s, sp_int off) { return iob_set_string(b, s, off, 0, 0, 0); }
457
+ sp_int sp_IOBuffer_set_string3(sp_IOBuffer *b, const char *s, sp_int off, sp_int len) { return iob_set_string(b, s, off, len, 0, 1); }
458
+ sp_int sp_IOBuffer_set_string4(sp_IOBuffer *b, const char *s, sp_int off, sp_int len, sp_int soff) { return iob_set_string(b, s, off, len, soff, 1); }
459
+
460
+ /* ---- whole-buffer operations ---- */
461
+
462
+ sp_int sp_IOBuffer_size(sp_IOBuffer *b) { return b->size; }
463
+
464
+ sp_IOBuffer *sp_IOBuffer_resize(sp_IOBuffer *b, sp_int size) {
465
+ if (b->flags & SP_IOB_LOCKED)
466
+ sp_raise_cls("IO::Buffer::LockedError", "Cannot resize locked buffer!");
467
+ if (b->flags & SP_IOB_EXTERNAL) sp_raise_cls("IO::Buffer::AccessError", "Cannot resize external buffer!");
468
+ /* a file mapping has the file's length; growing it would detach the
469
+ view from the file it was made for, so it is refused as EXTERNAL is */
470
+ if (b->map_base) sp_raise_cls("IO::Buffer::AccessError", "Cannot resize mapped file buffer!");
471
+ if (size < 0) iob_arg("Size can't be negative!");
472
+ if (b->source) {
473
+ /* a slice detaches into its own (internal) allocation */
474
+ uint8_t *base = iob_ptr(b);
475
+ uint8_t *d = NULL;
476
+ if (size > 0) {
477
+ d = (uint8_t *)calloc(1, (size_t)size);
478
+ if (!d) sp_oom_die();
479
+ int64_t keep = b->size < size ? b->size : size;
480
+ if (base && keep > 0) memcpy(d, base, (size_t)keep);
481
+ }
482
+ b->source = NULL;
483
+ b->off = 0;
484
+ b->data = d;
485
+ b->size = size;
486
+ b->flags = SP_IOB_INTERNAL | (b->flags & SP_IOB_READONLY);
487
+ return b;
488
+ }
489
+ if (size == 0) {
490
+ free(b->data);
491
+ b->data = NULL;
492
+ b->size = 0;
493
+ return b;
494
+ }
495
+ uint8_t *d = (uint8_t *)realloc(b->data, (size_t)size);
496
+ if (!d) sp_oom_die();
497
+ if (size > b->size) memset(d + b->size, 0, (size_t)(size - b->size));
498
+ b->data = d;
499
+ b->size = size;
500
+ if (!(b->flags & (SP_IOB_INTERNAL | SP_IOB_MAPPED))) b->flags |= SP_IOB_INTERNAL;
501
+ return b;
502
+ }
503
+
504
+ static sp_IOBuffer *iob_clear(sp_IOBuffer *b, int64_t v, int64_t off, int64_t len) {
505
+ iob_writable(b);
506
+ iob_range(b, off, len);
507
+ if (len > 0) memset(iob_ptr(b) + off, (int)(uint8_t)v, (size_t)len);
508
+ return b;
509
+ }
510
+ sp_IOBuffer *sp_IOBuffer_clear0(sp_IOBuffer *b) { return iob_clear(b, 0, 0, b->size); }
511
+ sp_IOBuffer *sp_IOBuffer_clear1(sp_IOBuffer *b, sp_int v) { return iob_clear(b, v, 0, b->size); }
512
+ sp_IOBuffer *sp_IOBuffer_clear2(sp_IOBuffer *b, sp_int v, sp_int off) {
513
+ return iob_clear(b, v, off, off <= b->size ? b->size - off : 0);
514
+ }
515
+ sp_IOBuffer *sp_IOBuffer_clear3(sp_IOBuffer *b, sp_int v, sp_int off, sp_int len) { return iob_clear(b, v, off, len); }
516
+
517
+ static sp_IOBuffer *iob_other(sp_IOBuffer *b, sp_RbVal v) {
518
+ if (v.tag == SP_TAG_OBJ && v.v.p && v.cls_id == (int)b->cls_id) return (sp_IOBuffer *)v.v.p;
519
+ sp_raise_cls("TypeError", sp_sprintf("wrong argument type %s (expected IO::Buffer)", iob_val_name(v)));
520
+ return NULL;
521
+ }
522
+
523
+ static sp_int iob_copy(sp_IOBuffer *b, sp_RbVal srcv, int64_t off, int64_t len, int64_t soff, int len_given) {
524
+ sp_IOBuffer *src = iob_other(b, srcv);
525
+ iob_writable(b);
526
+ iob_readable(src);
527
+ if (off < 0) iob_arg("Offset can't be negative!");
528
+ if (soff < 0) iob_arg("Source offset can't be negative!");
529
+ if (soff > src->size) iob_arg("The given source offset is bigger than the source itself!");
530
+ if (!len_given) len = src->size - soff;
531
+ if (len < 0) iob_arg("Length can't be negative!");
532
+ /* destination range before source range, as CRuby orders them */
533
+ if (off > b->size || len > b->size - off) iob_arg("Specified offset+length is bigger than the buffer size!");
534
+ if (len > src->size - soff)
535
+ iob_arg("The computed source range exceeds the size of the source buffer!");
536
+ if (len > 0) memmove(iob_ptr(b) + off, iob_ptr(src) + soff, (size_t)len);
537
+ return len;
538
+ }
539
+ sp_int sp_IOBuffer_copy1(sp_IOBuffer *b, sp_RbVal src) { return iob_copy(b, src, 0, 0, 0, 0); }
540
+ sp_int sp_IOBuffer_copy2(sp_IOBuffer *b, sp_RbVal src, sp_int off) { return iob_copy(b, src, off, 0, 0, 0); }
541
+ sp_int sp_IOBuffer_copy3(sp_IOBuffer *b, sp_RbVal src, sp_int off, sp_int len) { return iob_copy(b, src, off, len, 0, 1); }
542
+ sp_int sp_IOBuffer_copy4(sp_IOBuffer *b, sp_RbVal src, sp_int off, sp_int len, sp_int soff) { return iob_copy(b, src, off, len, soff, 1); }
543
+
544
+ static sp_IOBuffer *iob_slice(sp_IOBuffer *b, int64_t off, int64_t len) {
545
+ SP_GC_ROOT(b);
546
+ iob_readable(b);
547
+ if (iob_slice_invalid(b))
548
+ sp_raise_cls("IO::Buffer::InvalidatedError", "Buffer has been invalidated!");
549
+ iob_range(b, off, len);
550
+ sp_IOBuffer *s = iob_alloc_obj(b->cls_id);
551
+ s->source = b->source ? b->source : b;
552
+ s->off = (b->source ? b->off : 0) + off;
553
+ s->size = len;
554
+ s->flags = SP_IOB_SLICE | (b->flags & SP_IOB_READONLY);
555
+ return s;
556
+ }
557
+ sp_IOBuffer *sp_IOBuffer_slice0(sp_IOBuffer *b) { return iob_slice(b, 0, b->size); }
558
+ sp_IOBuffer *sp_IOBuffer_slice1(sp_IOBuffer *b, sp_int off) {
559
+ return iob_slice(b, off, off <= b->size ? b->size - off : 0);
560
+ }
561
+ sp_IOBuffer *sp_IOBuffer_slice2(sp_IOBuffer *b, sp_int off, sp_int len) { return iob_slice(b, off, len); }
562
+
563
+ sp_IOBuffer *sp_IOBuffer_transfer(sp_IOBuffer *b) {
564
+ SP_GC_ROOT(b);
565
+ if (b->flags & SP_IOB_LOCKED)
566
+ sp_raise_cls("IO::Buffer::LockedError", "Cannot transfer ownership of locked buffer!");
567
+ sp_IOBuffer *t = iob_alloc_obj(b->cls_id);
568
+ t->data = b->data;
569
+ t->source = b->source;
570
+ t->off = b->off;
571
+ t->size = b->size;
572
+ t->flags = b->flags;
573
+ t->map_base = b->map_base;
574
+ t->map_len = b->map_len;
575
+ b->data = NULL;
576
+ b->source = NULL;
577
+ b->off = 0;
578
+ b->size = 0;
579
+ b->map_base = NULL;
580
+ b->map_len = 0;
581
+ /* the drained buffer keeps its flags (CRuby inspects as "NULL INTERNAL") */
582
+ return t;
583
+ }
584
+
585
+ sp_IOBuffer *sp_IOBuffer_free_m(sp_IOBuffer *b) {
586
+ if (b->flags & SP_IOB_LOCKED)
587
+ sp_raise_cls("IO::Buffer::LockedError", "Buffer is locked!");
588
+ iob_release(b);
589
+ b->source = NULL;
590
+ b->off = 0;
591
+ b->size = 0;
592
+ b->flags = 0;
593
+ return b;
594
+ }
595
+
596
+ sp_IOBuffer *sp_IOBuffer_dup_m(sp_IOBuffer *b) {
597
+ SP_GC_ROOT(b);
598
+ iob_readable(b);
599
+ sp_IOBuffer *d = iob_alloc_obj(b->cls_id);
600
+ uint8_t *base = b->source ? iob_ptr(b) : b->data;
601
+ if (base) {
602
+ d->data = (uint8_t *)malloc(b->size > 0 ? (size_t)b->size : 1);
603
+ if (!d->data) sp_oom_die();
604
+ if (b->size > 0) memcpy(d->data, base, (size_t)b->size);
605
+ d->size = b->size;
606
+ d->flags = SP_IOB_INTERNAL;
607
+ }
608
+ return d;
609
+ }
610
+
611
+ /* ---- comparison ---- */
612
+
613
+ static sp_int iob_cmp_core(sp_IOBuffer *b, sp_IOBuffer *o) {
614
+ iob_readable(b);
615
+ iob_readable(o);
616
+ int64_t n = b->size < o->size ? b->size : o->size;
617
+ if (n > 0) {
618
+ int c = memcmp(iob_ptr(b), iob_ptr(o), (size_t)n);
619
+ if (c) return c < 0 ? -1 : 1;
620
+ }
621
+ return b->size < o->size ? -1 : b->size > o->size ? 1 : 0;
622
+ }
623
+ sp_int sp_IOBuffer_cmp(sp_IOBuffer *b, sp_RbVal other) { return iob_cmp_core(b, iob_other(b, other)); }
624
+ sp_bool sp_IOBuffer_eq(sp_IOBuffer *b, sp_RbVal other) {
625
+ sp_IOBuffer *o = iob_other(b, other);
626
+ if (b->size != o->size) return 0;
627
+ return iob_cmp_core(b, o) == 0;
628
+ }
629
+
630
+ /* ---- rendering ---- */
631
+
632
+ /* One hexdump line into `out` (which has room): "0x%08x " + width slots of
633
+ "%02x " (or three spaces past the end) + the printable-ASCII column. */
634
+ static size_t iob_hexline(char *out, const uint8_t *base, int64_t addr, int64_t end, int64_t width) {
635
+ char *w = out;
636
+ w += sprintf(w, "0x%08llx ", (unsigned long long)addr);
637
+ for (int64_t i = 0; i < width; i++) {
638
+ if (addr + i < end) w += sprintf(w, " %02x", base[addr + i]);
639
+ else { memcpy(w, " ", 3); w += 3; }
640
+ }
641
+ *w++ = ' ';
642
+ for (int64_t i = 0; i < width && addr + i < end; i++) {
643
+ uint8_t c = base[addr + i];
644
+ *w++ = (c >= 0x20 && c < 0x7f) ? (char)c : '.';
645
+ }
646
+ *w = '\0';
647
+ return (size_t)(w - out);
648
+ }
649
+
650
+ static const char *iob_hexdump(sp_IOBuffer *b, int64_t off, int64_t len, int64_t width, int len_given) {
651
+ SP_GC_ROOT(b);
652
+ iob_readable(b);
653
+ if (b->source && iob_slice_invalid(b))
654
+ sp_raise_cls("IO::Buffer::InvalidatedError", "Buffer has been invalidated!");
655
+ if (!iob_base(b)) return NULL;
656
+ if (off < 0) iob_arg("Offset can't be negative!");
657
+ if (width < 1) iob_arg("Width must be at least 1!");
658
+ if (!len_given) len = b->size - off;
659
+ if (len < 0) iob_arg("Length can't be negative!");
660
+ if (off > b->size) off = b->size;
661
+ if (len > b->size - off) len = b->size - off;
662
+ int64_t end = off + len;
663
+ /* ceiling division without the additive form: len + width - 1 itself
664
+ overflows for a near-INT64_MAX width */
665
+ int64_t nlines = len / width + (len % width != 0);
666
+ /* per line: the address prefix ("0x" + up to 16 hex digits + two spaces),
667
+ width * 3 hex slots, a separator, width ASCII chars, the newline and
668
+ NUL -- and `width` is caller data, so every derived size is checked
669
+ before it reaches malloc (a huge width made the product wrap and the
670
+ sprintf write past a too-small block). */
671
+ uint64_t line_max, total;
672
+ if (sp_ckd_mul_u64((uint64_t)width, (uint64_t)4, &line_max) ||
673
+ sp_ckd_add_u64(line_max, (uint64_t)40, &line_max) ||
674
+ sp_ckd_mul_u64((uint64_t)(nlines > 0 ? nlines : 1), line_max, &total) ||
675
+ total > (uint64_t)1 << 40)
676
+ sp_oom_die();
677
+ char *tmp = (char *)malloc((size_t)total);
678
+ if (!tmp) sp_oom_die();
679
+ size_t used = 0;
680
+ const uint8_t *base = iob_base(b);
681
+ for (int64_t a = off; a < end; a += width) {
682
+ if (a > off) tmp[used++] = '\n';
683
+ used += iob_hexline(tmp + used, base, a, end, width);
684
+ }
685
+ char *r = sp_str_alloc_raw(used + 1);
686
+ memcpy(r, tmp, used);
687
+ r[used] = '\0';
688
+ sp_str_set_len(r, used);
689
+ free(tmp);
690
+ return r;
691
+ }
692
+ const char *sp_IOBuffer_hexdump0(sp_IOBuffer *b) { return iob_hexdump(b, 0, 0, 16, 0); }
693
+ const char *sp_IOBuffer_hexdump1(sp_IOBuffer *b, sp_int off) { return iob_hexdump(b, off, 0, 16, 0); }
694
+ const char *sp_IOBuffer_hexdump2(sp_IOBuffer *b, sp_int off, sp_int len) { return iob_hexdump(b, off, len, 16, 1); }
695
+ const char *sp_IOBuffer_hexdump3(sp_IOBuffer *b, sp_int off, sp_int len, sp_int width) { return iob_hexdump(b, off, len, width, 1); }
696
+
697
+ static size_t iob_header(sp_IOBuffer *b, char *out, size_t cap) {
698
+ uint8_t *base = iob_base(b);
699
+ size_t n = (size_t)snprintf(out, cap, "#<IO::Buffer 0x%016llx+%lld",
700
+ (unsigned long long)(uintptr_t)base, (long long)b->size);
701
+ static const struct { uint32_t bit; const char *name; } FLAGS[] = {
702
+ {0, "NULL"}, {SP_IOB_EXTERNAL, "EXTERNAL"}, {SP_IOB_INTERNAL, "INTERNAL"},
703
+ {SP_IOB_MAPPED, "MAPPED"}, {SP_IOB_SHARED, "SHARED"}, {SP_IOB_LOCKED, "LOCKED"},
704
+ {SP_IOB_PRIVATE, "PRIVATE"}, {SP_IOB_READONLY, "READONLY"}, {SP_IOB_SLICE, "SLICE"},
705
+ };
706
+ for (size_t i = 0; i < sizeof FLAGS / sizeof FLAGS[0]; i++) {
707
+ int on = i == 0 ? base == NULL : (b->flags & FLAGS[i].bit) != 0;
708
+ if (on) n += (size_t)snprintf(out + n, cap - n, " %s", FLAGS[i].name);
709
+ }
710
+ n += (size_t)snprintf(out + n, cap - n, ">");
711
+ return n;
712
+ }
713
+
714
+ const char *sp_IOBuffer_to_s(sp_IOBuffer *b) {
715
+ SP_GC_ROOT(b);
716
+ char hd[256];
717
+ size_t n = iob_header(b, hd, sizeof hd);
718
+ char *r = sp_str_alloc_raw(n + 1);
719
+ memcpy(r, hd, n + 1);
720
+ sp_str_set_len(r, n);
721
+ return r;
722
+ }
723
+
724
+ const char *sp_IOBuffer_inspect(sp_IOBuffer *b) {
725
+ SP_GC_ROOT(b);
726
+ char hd[256];
727
+ size_t n = iob_header(b, hd, sizeof hd);
728
+ int64_t shown = b->size < 256 ? b->size : 256;
729
+ if (!iob_base(b) || iob_slice_invalid(b) || shown == 0) {
730
+ char *r0 = sp_str_alloc_raw(n + 1);
731
+ memcpy(r0, hd, n + 1);
732
+ sp_str_set_len(r0, n);
733
+ return r0;
734
+ }
735
+ const char *dump = iob_hexdump(b, 0, shown, 16, 1);
736
+ SP_GC_ROOT_STR(dump);
737
+ size_t dn = sp_str_byte_len(dump);
738
+ char tail[64];
739
+ tail[0] = '\0';
740
+ if (b->size > shown)
741
+ snprintf(tail, sizeof tail, "\n(and %lld more bytes not printed)", (long long)(b->size - shown));
742
+ size_t tn = strlen(tail);
743
+ char *r = sp_str_alloc_raw(n + 1 + dn + tn + 1);
744
+ memcpy(r, hd, n);
745
+ r[n] = '\n';
746
+ memcpy(r + n + 1, dump, dn);
747
+ memcpy(r + n + 1 + dn, tail, tn + 1);
748
+ sp_str_set_len(r, n + 1 + dn + tn);
749
+ return r;
750
+ }
751
+
752
+ /* ---- predicates ---- */
753
+
754
+ /* a slice's own base pointer is never the null buffer, even when its root
755
+ was freed (CRuby: null? false, valid? false there) */
756
+ sp_bool sp_IOBuffer_null_p(sp_IOBuffer *b) { return b->source ? 0 : b->data == NULL; }
757
+ sp_bool sp_IOBuffer_empty_p(sp_IOBuffer *b) { return b->size == 0; }
758
+ sp_bool sp_IOBuffer_valid_p(sp_IOBuffer *b) { return !iob_slice_invalid(b); }
759
+ sp_bool sp_IOBuffer_external_p(sp_IOBuffer *b) { return (b->flags & SP_IOB_EXTERNAL) != 0; }
760
+ sp_bool sp_IOBuffer_internal_p(sp_IOBuffer *b) { return (b->flags & SP_IOB_INTERNAL) != 0; }
761
+ sp_bool sp_IOBuffer_mapped_p(sp_IOBuffer *b) { return (b->flags & SP_IOB_MAPPED) != 0; }
762
+ sp_bool sp_IOBuffer_shared_p(sp_IOBuffer *b) { return (b->flags & SP_IOB_SHARED) != 0; }
763
+ sp_bool sp_IOBuffer_locked_p(sp_IOBuffer *b) { return (b->flags & SP_IOB_LOCKED) != 0; }
764
+ sp_bool sp_IOBuffer_readonly_p(sp_IOBuffer *b) { return (b->flags & SP_IOB_READONLY) != 0; }
765
+ sp_bool sp_IOBuffer_private_p(sp_IOBuffer *b) { return (b->flags & SP_IOB_PRIVATE) != 0; }
766
+
767
+ /* ---- bitwise ---- */
768
+
769
+ typedef uint8_t (*iob_bop)(uint8_t, uint8_t);
770
+ static uint8_t bop_and(uint8_t a, uint8_t c) { return a & c; }
771
+ static uint8_t bop_or(uint8_t a, uint8_t c) { return a | c; }
772
+ static uint8_t bop_xor(uint8_t a, uint8_t c) { return a ^ c; }
773
+
774
+ /* The mask (second operand) TILES: the result is self-sized, and the mask
775
+ repeats over it. An empty mask has nothing to repeat: MaskError. */
776
+ static sp_IOBuffer *iob_bitop(sp_IOBuffer *b, sp_RbVal otherv, iob_bop op) {
777
+ sp_IOBuffer *o = iob_other(b, otherv);
778
+ SP_GC_ROOT(b);
779
+ SP_GC_ROOT(o);
780
+ iob_readable(b);
781
+ iob_readable(o);
782
+ if (o->size == 0) sp_raise_cls("IO::Buffer::MaskError", "Zero-length mask given!");
783
+ int64_t n = b->size;
784
+ sp_IOBuffer *r = iob_alloc_obj(b->cls_id);
785
+ if (n > 0) {
786
+ r->data = (uint8_t *)malloc((size_t)n);
787
+ if (!r->data) sp_oom_die();
788
+ const uint8_t *pa = iob_ptr(b), *pb = iob_ptr(o);
789
+ for (int64_t i = 0; i < n; i++) r->data[i] = op(pa[i], pb[i % o->size]);
790
+ r->size = n;
791
+ r->flags = SP_IOB_INTERNAL;
792
+ }
793
+ return r;
794
+ }
795
+ sp_IOBuffer *sp_IOBuffer_and(sp_IOBuffer *b, sp_RbVal other) { return iob_bitop(b, other, bop_and); }
796
+ sp_IOBuffer *sp_IOBuffer_or(sp_IOBuffer *b, sp_RbVal other) { return iob_bitop(b, other, bop_or); }
797
+ sp_IOBuffer *sp_IOBuffer_xor(sp_IOBuffer *b, sp_RbVal other) { return iob_bitop(b, other, bop_xor); }
798
+ sp_IOBuffer *sp_IOBuffer_not(sp_IOBuffer *b) {
799
+ SP_GC_ROOT(b);
800
+ iob_readable(b);
801
+ sp_IOBuffer *r = iob_alloc_obj(b->cls_id);
802
+ if (b->size > 0 && (b->source ? iob_ptr(b) : b->data)) {
803
+ r->data = (uint8_t *)malloc((size_t)b->size);
804
+ if (!r->data) sp_oom_die();
805
+ const uint8_t *p = iob_base(b);
806
+ for (int64_t i = 0; i < b->size; i++) r->data[i] = (uint8_t)~p[i];
807
+ r->size = b->size;
808
+ r->flags = SP_IOB_INTERNAL;
809
+ }
810
+ return r;
811
+ }
812
+
813
+ static sp_IOBuffer *iob_bitop_ip(sp_IOBuffer *b, sp_RbVal otherv, iob_bop op) {
814
+ sp_IOBuffer *o = iob_other(b, otherv);
815
+ iob_writable(b);
816
+ iob_readable(o);
817
+ if (o->size == 0) sp_raise_cls("IO::Buffer::MaskError", "Zero-length mask given!");
818
+ uint8_t *pa = iob_ptr(b);
819
+ const uint8_t *pb = iob_ptr(o);
820
+ for (int64_t i = 0; i < b->size; i++) pa[i] = op(pa[i], pb[i % o->size]);
821
+ return b;
822
+ }
823
+ sp_IOBuffer *sp_IOBuffer_and_ip(sp_IOBuffer *b, sp_RbVal other) { return iob_bitop_ip(b, other, bop_and); }
824
+ sp_IOBuffer *sp_IOBuffer_or_ip(sp_IOBuffer *b, sp_RbVal other) { return iob_bitop_ip(b, other, bop_or); }
825
+ sp_IOBuffer *sp_IOBuffer_xor_ip(sp_IOBuffer *b, sp_RbVal other) { return iob_bitop_ip(b, other, bop_xor); }
826
+ sp_IOBuffer *sp_IOBuffer_not_ip(sp_IOBuffer *b) {
827
+ iob_writable(b);
828
+ uint8_t *p = b->size > 0 ? iob_ptr(b) : NULL;
829
+ for (int64_t i = 0; i < b->size; i++) p[i] = (uint8_t)~p[i];
830
+ return b;
831
+ }
832
+
833
+ /* ---- locked { } ---- */
834
+
835
+ sp_IOBuffer *sp_IOBuffer_lock(sp_IOBuffer *b) {
836
+ if (b->flags & SP_IOB_LOCKED) sp_raise_cls("IO::Buffer::LockedError", "Buffer already locked!");
837
+ b->flags |= SP_IOB_LOCKED;
838
+ return b;
839
+ }
840
+ sp_IOBuffer *sp_IOBuffer_unlock(sp_IOBuffer *b) {
841
+ b->flags &= ~SP_IOB_LOCKED;
842
+ return b;
843
+ }
844
+
845
+ /* ---- FFI ----
846
+ An IO::Buffer passed to an ffi_func pointer argument hands C its base
847
+ address, as CRuby's rb_io_buffer_get_bytes_for_reading / _for_writing do:
848
+ NULL for a null buffer (freed, or zero-sized), InvalidatedError for a
849
+ slice whose source was freed or shrunk under it, and AccessError for a
850
+ read-only buffer in a slot other than :buffer_in. A nil (a buffer-typed
851
+ local that holds none) is NULL, as it is for :ptr. */
852
+ void *sp_IOBuffer_ffi_base(sp_IOBuffer *b, sp_int writing) {
853
+ if (!b) return NULL;
854
+ if (writing) iob_writable(b);
855
+ uint8_t *p = iob_ptr(b); /* an invalidated slice raises first */
856
+ return b->size > 0 ? p : NULL; /* a zero-size view is a null buffer too */
857
+ }
858
+ /* Held across a `blocking: true` call, during which other threads run: the
859
+ buffer, and a slice's source, are locked so that their free or resize
860
+ raises LockedError instead of releasing memory C is still using. Answers
861
+ what it locked (a buffer the program already holds locked stays so), for
862
+ sp_IOBuffer_ffi_release to undo exactly that. */
863
+ sp_int sp_IOBuffer_ffi_hold(sp_IOBuffer *b) {
864
+ sp_int m = 0;
865
+ if (!b) return 0;
866
+ if (!(b->flags & SP_IOB_LOCKED)) { b->flags |= SP_IOB_LOCKED; m |= 1; }
867
+ if (b->source && !(b->source->flags & SP_IOB_LOCKED)) { b->source->flags |= SP_IOB_LOCKED; m |= 2; }
868
+ return m;
869
+ }
870
+ void sp_IOBuffer_ffi_release(sp_IOBuffer *b, sp_int m) {
871
+ if (!b) return;
872
+ if (m & 1) b->flags &= ~SP_IOB_LOCKED;
873
+ if ((m & 2) && b->source) b->source->flags &= ~SP_IOB_LOCKED;
874
+ }
875
+ /* the same for a boxed argument, which may not be a buffer at all */
876
+ sp_int sp_IOBuffer_ffi_hold_v(sp_RbVal v, sp_int cls_id) {
877
+ return v.tag == SP_TAG_OBJ && v.cls_id == cls_id ? sp_IOBuffer_ffi_hold((sp_IOBuffer *)v.v.p) : 0;
878
+ }
879
+ void sp_IOBuffer_ffi_release_v(sp_RbVal v, sp_int cls_id, sp_int m) {
880
+ if (v.tag == SP_TAG_OBJ && v.cls_id == cls_id) sp_IOBuffer_ffi_release((sp_IOBuffer *)v.v.p, m);
881
+ }
882
+ /* a boxed pointer argument: an IO::Buffer (class `cls_id`) gives its base,
883
+ anything else its raw pointer as before */
884
+ void *sp_IOBuffer_ffi_ptr(sp_RbVal v, sp_int cls_id, sp_int writing) {
885
+ if (v.tag == SP_TAG_OBJ && v.cls_id == cls_id && v.v.p)
886
+ return sp_IOBuffer_ffi_base((sp_IOBuffer *)v.v.p, writing);
887
+ return v.v.p;
888
+ }
889
+
890
+ /* ---- IO integration (#4474) ----
891
+ Spinel's IO is a stdio FILE* (sp_File), so a raw descriptor read has to
892
+ stay coherent with the stdio buffer: bytes stdio already holds for the
893
+ stream are served first (the readpartial / read_nonblock discipline), and
894
+ a raw write flushes what stdio still holds so the bytes do not reorder.
895
+ A read that can block parks the green thread on readiness first, as every
896
+ IO read does, and a write on a pipe or socket parks on writability; the
897
+ Ruby side holds the buffer's lock across the call so a concurrent resize
898
+ or free cannot move the bytes a parked syscall is about to touch. One
899
+ syscall per call, answering its count, 0 at EOF, or -errno, as CRuby. */
900
+ static sp_File *iob_io(sp_RbVal io) {
901
+ if (io.tag == SP_TAG_OBJ && io.cls_id == SP_BUILTIN_IO && io.v.p) {
902
+ sp_File *f = (sp_File *)io.v.p;
903
+ if (!f->fp || f->closed) sp_raise_cls("IOError", "closed stream");
904
+ return f;
905
+ }
906
+ sp_raise_cls("TypeError", sp_sprintf("wrong argument type %s (expected IO)", iob_val_name(io)));
907
+ }
908
+ /* the span [offset, offset+length) of the buffer, length < 0 meaning nil */
909
+ static uint8_t *iob_io_span(sp_IOBuffer *b, sp_int *length, sp_int offset, int writing) {
910
+ if (writing) iob_writable(b);
911
+ if (*length < 0) *length = b->size - offset;
912
+ iob_range(b, offset, *length);
913
+ uint8_t *base = iob_ptr(b);
914
+ if (!base) sp_raise_cls("IO::Buffer::AccessError", "Buffer is not allocated!");
915
+ return base + offset;
916
+ }
917
+ sp_int sp_IOBuffer_read_io(sp_IOBuffer *b, sp_RbVal io, sp_int length, sp_int offset) {
918
+ SP_GC_ROOT(b); SP_GC_ROOT_RBVAL(io);
919
+ sp_File *f = iob_io(io);
920
+ uint8_t *at = iob_io_span(b, &length, offset, 1);
921
+ if (length == 0) return 0;
922
+ /* what stdio already holds is data the stream has delivered: take it
923
+ first, or a raw read would step over it */
924
+ size_t pend = sp_io_stdio_buffered(f->fp);
925
+ if (pend > 0) {
926
+ size_t want = (size_t)length < pend ? (size_t)length : pend;
927
+ return (sp_int)fread(at, 1, want, f->fp);
928
+ }
929
+ sp_io_wait_readable(f);
930
+ at = iob_io_span(b, &length, offset, 1); /* the park can raise, never move a locked buffer */
931
+ ssize_t n;
932
+ do { n = read(fileno(f->fp), at, (size_t)length); } while (n < 0 && errno == EINTR);
933
+ return n < 0 ? -(sp_int)errno : (sp_int)n;
934
+ }
935
+ sp_int sp_IOBuffer_write_io(sp_IOBuffer *b, sp_RbVal io, sp_int length, sp_int offset) {
936
+ SP_GC_ROOT(b); SP_GC_ROOT_RBVAL(io);
937
+ sp_File *f = iob_io(io);
938
+ uint8_t *at = iob_io_span(b, &length, offset, 0);
939
+ if (length == 0) return 0;
940
+ /* a buffered write still in stdio would land after this one */
941
+ if (fflush(f->fp) != 0) return -(sp_int)errno;
942
+ int fd = fileno(f->fp);
943
+ for (;;) {
944
+ ssize_t n = write(fd, at, (size_t)length);
945
+ if (n >= 0) return (sp_int)n;
946
+ if (errno == EINTR) continue;
947
+ if (errno == EAGAIN || errno == EWOULDBLOCK) { sp_io_wait_writable(f); continue; }
948
+ return -(sp_int)errno;
949
+ }
950
+ }
951
+ sp_int sp_IOBuffer_pread_io(sp_IOBuffer *b, sp_RbVal io, sp_int from, sp_int length, sp_int offset) {
952
+ SP_GC_ROOT(b); SP_GC_ROOT_RBVAL(io);
953
+ sp_File *f = iob_io(io);
954
+ if (from < 0) iob_arg("Position can't be negative!");
955
+ uint8_t *at = iob_io_span(b, &length, offset, 1);
956
+ if (length == 0) return 0;
957
+ ssize_t n;
958
+ do { n = pread(fileno(f->fp), at, (size_t)length, (off_t)from); } while (n < 0 && errno == EINTR);
959
+ return n < 0 ? -(sp_int)errno : (sp_int)n;
960
+ }
961
+ sp_int sp_IOBuffer_pwrite_io(sp_IOBuffer *b, sp_RbVal io, sp_int from, sp_int length, sp_int offset) {
962
+ SP_GC_ROOT(b); SP_GC_ROOT_RBVAL(io);
963
+ sp_File *f = iob_io(io);
964
+ if (from < 0) iob_arg("Position can't be negative!");
965
+ uint8_t *at = iob_io_span(b, &length, offset, 0);
966
+ if (length == 0) return 0;
967
+ if (fflush(f->fp) != 0) return -(sp_int)errno;
968
+ ssize_t n;
969
+ do { n = pwrite(fileno(f->fp), at, (size_t)length, (off_t)from); } while (n < 0 && errno == EINTR);
970
+ return n < 0 ? -(sp_int)errno : (sp_int)n;
971
+ }
972
+
973
+ /* IO::Buffer.map(file, size = nil, offset = 0, flags = READONLY): a view of
974
+ the file's bytes through mmap. The mapping is page-aligned below the
975
+ requested offset and `data` points at the offset within it; PRIVATE maps
976
+ copy-on-write, SHARED (the default) writes through, READONLY maps
977
+ PROT_READ. The finalizer munmaps; a slice keeps its source alive as for
978
+ any buffer; resize is refused (see sp_IOBuffer_resize). */
979
+ sp_IOBuffer *sp_IOBuffer_become_map(sp_IOBuffer *b, sp_RbVal io, sp_int size, sp_int offset, sp_int flags) {
980
+ SP_GC_ROOT(b); SP_GC_ROOT_RBVAL(io);
981
+ sp_File *f = iob_io(io);
982
+ if (offset < 0) iob_arg("Offset can't be negative!");
983
+ int fd = fileno(f->fp);
984
+ fflush(f->fp);
985
+ if (size < 0) {
986
+ struct stat st;
987
+ if (fstat(fd, &st) != 0) sp_raise_cls("SystemCallError", sp_sprintf("%s @ IO::Buffer.map", strerror(errno)));
988
+ size = (sp_int)st.st_size - offset;
989
+ if (size < 0) size = 0;
990
+ }
991
+ uint32_t fl = (uint32_t)flags;
992
+ int prot = (fl & SP_IOB_READONLY) ? PROT_READ : (PROT_READ | PROT_WRITE);
993
+ int mflags = (fl & SP_IOB_PRIVATE) ? MAP_PRIVATE : MAP_SHARED;
994
+ iob_release(b);
995
+ b->source = NULL; b->off = 0;
996
+ b->size = size;
997
+ b->flags = SP_IOB_MAPPED | (fl & (SP_IOB_READONLY | SP_IOB_PRIVATE)) | ((fl & SP_IOB_PRIVATE) ? 0 : SP_IOB_SHARED);
998
+ if (size == 0) { b->data = NULL; return b; }
999
+ long ps = sysconf(_SC_PAGESIZE);
1000
+ if (ps <= 0) ps = 4096;
1001
+ off_t page = (off_t)(offset - offset % ps);
1002
+ size_t len = (size_t)(size + (offset - page));
1003
+ void *m = mmap(NULL, len, prot, mflags, fd, page);
1004
+ if (m == MAP_FAILED) {
1005
+ int e = errno;
1006
+ b->size = 0; b->flags = 0;
1007
+ sp_raise_cls(e == EACCES ? "Errno::EACCES" : e == EINVAL ? "Errno::EINVAL" : "SystemCallError",
1008
+ sp_sprintf("%s @ IO::Buffer.map", strerror(e)));
1009
+ }
1010
+ b->map_base = m;
1011
+ b->map_len = len;
1012
+ b->data = (uint8_t *)m + (offset - page);
1013
+ return b;
1014
+ }