netcdf 0.1.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.
@@ -0,0 +1,1284 @@
1
+ #include "internal.h"
2
+
3
+ /* ---------------------------------------------------------- */
4
+ /* Scalar I/O (var1) */
5
+ /* ---------------------------------------------------------- */
6
+
7
+ static VALUE
8
+ rb_nc_put_var1_text (VALUE self, VALUE rncid, VALUE rvarid, VALUE rindex, VALUE rvalue)
9
+ {
10
+ size_t idx[NC_MAX_VAR_DIMS];
11
+ int st;
12
+
13
+ StringValue(rvalue);
14
+ if ( RSTRING_LEN(rvalue) < 1 ) {
15
+ rb_raise(rb_eArgError, "value string must have at least 1 character");
16
+ }
17
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
18
+ st = nc_put_var1_text(NUM2INT(rncid), NUM2INT(rvarid), idx, RSTRING_PTR(rvalue));
19
+ if ( st != NC_NOERR ) {
20
+ nc_raise(st);
21
+ }
22
+ return Qnil;
23
+ }
24
+
25
+ static VALUE
26
+ rb_nc_get_var1_text (VALUE self, VALUE rncid, VALUE rvarid, VALUE rindex)
27
+ {
28
+ size_t idx[NC_MAX_VAR_DIMS];
29
+ char c;
30
+ int st;
31
+
32
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
33
+ st = nc_get_var1_text(NUM2INT(rncid), NUM2INT(rvarid), idx, &c);
34
+ if ( st != NC_NOERR ) {
35
+ nc_raise(st);
36
+ }
37
+ return rb_utf8_str_new(&c, 1);
38
+ }
39
+
40
+ #define DEFINE_VAR1_NUM(suffix, ctype, num2c, c2num) \
41
+ static VALUE \
42
+ rb_nc_put_var1_##suffix (VALUE self, VALUE rncid, VALUE rvarid, \
43
+ VALUE rindex, VALUE rvalue) \
44
+ { \
45
+ size_t idx[NC_MAX_VAR_DIMS]; \
46
+ ctype v; \
47
+ int st; \
48
+ \
49
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx); \
50
+ v = (ctype) num2c(rvalue); \
51
+ st = nc_put_var1_##suffix(NUM2INT(rncid), NUM2INT(rvarid), idx, &v); \
52
+ if ( st != NC_NOERR ) { \
53
+ nc_raise(st); \
54
+ } \
55
+ return Qnil; \
56
+ } \
57
+ \
58
+ static VALUE \
59
+ rb_nc_get_var1_##suffix (VALUE self, VALUE rncid, VALUE rvarid, \
60
+ VALUE rindex) \
61
+ { \
62
+ size_t idx[NC_MAX_VAR_DIMS]; \
63
+ ctype v; \
64
+ int st; \
65
+ \
66
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx); \
67
+ st = nc_get_var1_##suffix(NUM2INT(rncid), NUM2INT(rvarid), idx, &v); \
68
+ if ( st != NC_NOERR ) { \
69
+ nc_raise(st); \
70
+ } \
71
+ return c2num(v); \
72
+ }
73
+
74
+ DEFINE_VAR1_NUM(schar, signed char, num_to_schar, INT2NUM)
75
+ DEFINE_VAR1_NUM(uchar, unsigned char, num_to_uchar, UINT2NUM)
76
+ DEFINE_VAR1_NUM(short, short, num_to_short, INT2NUM)
77
+ DEFINE_VAR1_NUM(ushort, unsigned short, num_to_ushort, UINT2NUM)
78
+ DEFINE_VAR1_NUM(int, int, NUM2INT, INT2NUM)
79
+ DEFINE_VAR1_NUM(uint, unsigned int, num_to_uint, UINT2NUM)
80
+ DEFINE_VAR1_NUM(longlong, long long, NUM2LL, LL2NUM)
81
+ DEFINE_VAR1_NUM(ulonglong, unsigned long long, num_to_ulonglong, ULL2NUM)
82
+ DEFINE_VAR1_NUM(float, float, NUM2DBL, DBL2NUM)
83
+ DEFINE_VAR1_NUM(double, double, NUM2DBL, DBL2NUM)
84
+
85
+ static VALUE
86
+ rb_nc_put_var1_string (VALUE self, VALUE rncid, VALUE rvarid, VALUE rindex, VALUE rvalue)
87
+ {
88
+ size_t idx[NC_MAX_VAR_DIMS];
89
+ const char *p;
90
+ int st;
91
+
92
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
93
+ StringValue(rvalue);
94
+ p = StringValueCStr(rvalue);
95
+ st = nc_put_var1_string(NUM2INT(rncid), NUM2INT(rvarid), idx, &p);
96
+ if ( st != NC_NOERR ) {
97
+ nc_raise(st);
98
+ }
99
+ return Qnil;
100
+ }
101
+
102
+ static VALUE
103
+ rb_nc_get_var1_string (VALUE self, VALUE rncid, VALUE rvarid, VALUE rindex)
104
+ {
105
+ size_t idx[NC_MAX_VAR_DIMS];
106
+ VALUE out;
107
+ char *p;
108
+ int st;
109
+
110
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
111
+ p = NULL;
112
+ st = nc_get_var1_string(NUM2INT(rncid), NUM2INT(rvarid), idx, &p);
113
+ if ( st != NC_NOERR ) {
114
+ nc_raise(st);
115
+ }
116
+ out = rb_utf8_str_new_cstr(p ? p : "");
117
+ nc_free_string(1, &p);
118
+ return out;
119
+ }
120
+
121
+ /* NC_OPAQUE single-element I/O: String of opaque_size bytes per element. */
122
+ static VALUE
123
+ rb_nc_put_var1_opaque (VALUE rncid, VALUE rvarid, VALUE rindex, VALUE rvalue, size_t opaque_size)
124
+ {
125
+ size_t idx[NC_MAX_VAR_DIMS];
126
+ int st;
127
+
128
+ StringValue(rvalue);
129
+ if ( (size_t) RSTRING_LEN(rvalue) != opaque_size ) {
130
+ rb_raise(rb_eArgError, "NC_OPAQUE element must be %zu bytes (got %ld)",
131
+ opaque_size, RSTRING_LEN(rvalue));
132
+ }
133
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
134
+ st = nc_put_var1(NUM2INT(rncid), NUM2INT(rvarid), idx, RSTRING_PTR(rvalue));
135
+ if ( st != NC_NOERR ) {
136
+ nc_raise(st);
137
+ }
138
+ return Qnil;
139
+ }
140
+
141
+ static VALUE
142
+ rb_nc_get_var1_opaque (VALUE rncid, VALUE rvarid, VALUE rindex, size_t opaque_size)
143
+ {
144
+ size_t idx[NC_MAX_VAR_DIMS];
145
+ VALUE out;
146
+ int st;
147
+
148
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
149
+ out = rb_str_buf_new((long) opaque_size);
150
+ rb_str_set_len(out, (long) opaque_size);
151
+ st = nc_get_var1(NUM2INT(rncid), NUM2INT(rvarid), idx, RSTRING_PTR(out));
152
+ if ( st != NC_NOERR ) {
153
+ nc_raise(st);
154
+ }
155
+ return out;
156
+ }
157
+
158
+ /* Enum scalar I/O: use untyped nc_put_var1 / nc_get_var1 with a buffer
159
+ sized for the enum's base nc_type. */
160
+ static VALUE
161
+ rb_nc_put_var1_enum (VALUE rncid, VALUE rvarid, VALUE rindex, VALUE rvalue, nc_type base)
162
+ {
163
+ size_t idx[NC_MAX_VAR_DIMS];
164
+ long long buf;
165
+ int st;
166
+
167
+ buf = 0;
168
+ switch ( base ) {
169
+ case NC_BYTE: *(signed char *) &buf = num_to_schar(rvalue); break;
170
+ case NC_UBYTE: *(unsigned char *) &buf = num_to_uchar(rvalue); break;
171
+ case NC_SHORT: *(short *) &buf = num_to_short(rvalue); break;
172
+ case NC_USHORT: *(unsigned short *) &buf = num_to_ushort(rvalue); break;
173
+ case NC_INT: *(int *) &buf = NUM2INT(rvalue); break;
174
+ case NC_UINT: *(unsigned int *) &buf = num_to_uint(rvalue); break;
175
+ case NC_INT64: *(long long *) &buf = NUM2LL(rvalue); break;
176
+ case NC_UINT64: *(unsigned long long *) &buf = num_to_ulonglong(rvalue); break;
177
+ default:
178
+ rb_raise(rb_eArgError, "unsupported enum base type %d", base);
179
+ }
180
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
181
+ st = nc_put_var1(NUM2INT(rncid), NUM2INT(rvarid), idx, &buf);
182
+ if ( st != NC_NOERR ) {
183
+ nc_raise(st);
184
+ }
185
+ return Qnil;
186
+ }
187
+
188
+ static VALUE
189
+ rb_nc_get_var1_enum (VALUE rncid, VALUE rvarid, VALUE rindex, nc_type base)
190
+ {
191
+ size_t idx[NC_MAX_VAR_DIMS];
192
+ long long buf;
193
+ int st;
194
+
195
+ buf = 0;
196
+ indices_to_c(NUM2INT(rncid), NUM2INT(rvarid), rindex, idx);
197
+ st = nc_get_var1(NUM2INT(rncid), NUM2INT(rvarid), idx, &buf);
198
+ if ( st != NC_NOERR ) {
199
+ nc_raise(st);
200
+ }
201
+ switch ( base ) {
202
+ case NC_BYTE: return INT2NUM(*(const signed char *) &buf);
203
+ case NC_UBYTE: return UINT2NUM(*(const unsigned char *) &buf);
204
+ case NC_SHORT: return INT2NUM(*(const short *) &buf);
205
+ case NC_USHORT: return UINT2NUM(*(const unsigned short *) &buf);
206
+ case NC_INT: return INT2NUM(*(const int *) &buf);
207
+ case NC_UINT: return UINT2NUM(*(const unsigned int *) &buf);
208
+ case NC_INT64: return LL2NUM(*(const long long *) &buf);
209
+ case NC_UINT64: return ULL2NUM(*(const unsigned long long *) &buf);
210
+ default:
211
+ rb_raise(rb_eArgError, "unsupported enum base type %d", base);
212
+ }
213
+ }
214
+
215
+ /* Resolve a user-defined type's class + size + base type. Returns -1 when
216
+ the variable's type is a primitive, otherwise the class code. */
217
+ static int
218
+ resolve_user_type (int ncid, nc_type xt, nc_type *base_out, size_t *size_out)
219
+ {
220
+ nc_type base;
221
+ size_t size;
222
+ int klass;
223
+
224
+ if ( xt < NC_FIRSTUSERTYPEID ) {
225
+ return -1;
226
+ }
227
+ NC_CHECK(nc_inq_user_type(ncid, xt, NULL, &size, &base, NULL, &klass));
228
+ if ( base_out ) {
229
+ *base_out = base;
230
+ }
231
+ if ( size_out ) {
232
+ *size_out = size;
233
+ }
234
+ return klass;
235
+ }
236
+
237
+ /* Generic var1 — dispatches by the variable's nc_type. */
238
+ static VALUE
239
+ rb_nc_put_var1 (VALUE self, VALUE rncid, VALUE rvarid, VALUE rindex, VALUE rvalue)
240
+ {
241
+ nc_type xt, base;
242
+ size_t size;
243
+ int klass;
244
+
245
+ NC_CHECK(nc_inq_vartype(NUM2INT(rncid), NUM2INT(rvarid), &xt));
246
+ /* User-defined type handling */
247
+ if ( xt >= NC_FIRSTUSERTYPEID ) {
248
+ klass = resolve_user_type(NUM2INT(rncid), xt, &base, &size);
249
+ switch ( klass ) {
250
+ case NC_OPAQUE: return rb_nc_put_var1_opaque(rncid, rvarid, rindex, rvalue, size);
251
+ case NC_ENUM: return rb_nc_put_var1_enum(rncid, rvarid, rindex, rvalue, base);
252
+ case NC_VLEN: rb_raise(rb_eNotImpError, "NC_VLEN scalar I/O not yet supported");
253
+ case NC_COMPOUND: rb_raise(rb_eNotImpError, "NC_COMPOUND scalar I/O not yet supported");
254
+ default: rb_raise(rb_eNotImpError, "unknown user-defined type class %d", klass);
255
+ }
256
+ }
257
+ switch ( xt ) {
258
+ case NC_CHAR: return rb_nc_put_var1_text(self, rncid, rvarid, rindex, rvalue);
259
+ case NC_BYTE: return rb_nc_put_var1_schar(self, rncid, rvarid, rindex, rvalue);
260
+ case NC_UBYTE: return rb_nc_put_var1_uchar(self, rncid, rvarid, rindex, rvalue);
261
+ case NC_SHORT: return rb_nc_put_var1_short(self, rncid, rvarid, rindex, rvalue);
262
+ case NC_USHORT: return rb_nc_put_var1_ushort(self, rncid, rvarid, rindex, rvalue);
263
+ case NC_INT: return rb_nc_put_var1_int(self, rncid, rvarid, rindex, rvalue);
264
+ case NC_UINT: return rb_nc_put_var1_uint(self, rncid, rvarid, rindex, rvalue);
265
+ case NC_INT64: return rb_nc_put_var1_longlong(self, rncid, rvarid, rindex, rvalue);
266
+ case NC_UINT64: return rb_nc_put_var1_ulonglong(self, rncid, rvarid, rindex, rvalue);
267
+ case NC_FLOAT: return rb_nc_put_var1_float(self, rncid, rvarid, rindex, rvalue);
268
+ case NC_DOUBLE: return rb_nc_put_var1_double(self, rncid, rvarid, rindex, rvalue);
269
+ case NC_STRING: return rb_nc_put_var1_string(self, rncid, rvarid, rindex, rvalue);
270
+ default: rb_raise(rb_eNotImpError, "unsupported nc_type: %d", xt);
271
+ }
272
+ }
273
+
274
+ static VALUE
275
+ rb_nc_get_var1 (VALUE self, VALUE rncid, VALUE rvarid, VALUE rindex)
276
+ {
277
+ nc_type xt, base;
278
+ size_t size;
279
+ int klass;
280
+
281
+ NC_CHECK(nc_inq_vartype(NUM2INT(rncid), NUM2INT(rvarid), &xt));
282
+ if ( xt >= NC_FIRSTUSERTYPEID ) {
283
+ klass = resolve_user_type(NUM2INT(rncid), xt, &base, &size);
284
+ switch ( klass ) {
285
+ case NC_OPAQUE: return rb_nc_get_var1_opaque(rncid, rvarid, rindex, size);
286
+ case NC_ENUM: return rb_nc_get_var1_enum(rncid, rvarid, rindex, base);
287
+ case NC_VLEN: rb_raise(rb_eNotImpError, "NC_VLEN scalar I/O not yet supported");
288
+ case NC_COMPOUND: rb_raise(rb_eNotImpError, "NC_COMPOUND scalar I/O not yet supported");
289
+ default: rb_raise(rb_eNotImpError, "unknown user-defined type class %d", klass);
290
+ }
291
+ }
292
+ switch ( xt ) {
293
+ case NC_CHAR: return rb_nc_get_var1_text(self, rncid, rvarid, rindex);
294
+ case NC_BYTE: return rb_nc_get_var1_schar(self, rncid, rvarid, rindex);
295
+ case NC_UBYTE: return rb_nc_get_var1_uchar(self, rncid, rvarid, rindex);
296
+ case NC_SHORT: return rb_nc_get_var1_short(self, rncid, rvarid, rindex);
297
+ case NC_USHORT: return rb_nc_get_var1_ushort(self, rncid, rvarid, rindex);
298
+ case NC_INT: return rb_nc_get_var1_int(self, rncid, rvarid, rindex);
299
+ case NC_UINT: return rb_nc_get_var1_uint(self, rncid, rvarid, rindex);
300
+ case NC_INT64: return rb_nc_get_var1_longlong(self, rncid, rvarid, rindex);
301
+ case NC_UINT64: return rb_nc_get_var1_ulonglong(self, rncid, rvarid, rindex);
302
+ case NC_FLOAT: return rb_nc_get_var1_float(self, rncid, rvarid, rindex);
303
+ case NC_DOUBLE: return rb_nc_get_var1_double(self, rncid, rvarid, rindex);
304
+ case NC_STRING: return rb_nc_get_var1_string(self, rncid, rvarid, rindex);
305
+ default: rb_raise(rb_eNotImpError, "unsupported nc_type: %d", xt);
306
+ }
307
+ }
308
+
309
+ /* ---------------------------------------------------------- */
310
+ /* Array I/O (vara / vars / varm) */
311
+ /* ---------------------------------------------------------- */
312
+
313
+ typedef enum {
314
+ OP_PUT_GENERIC,
315
+ OP_GET_GENERIC,
316
+ OP_PUT_TEXT, OP_GET_TEXT,
317
+ OP_PUT_SCHAR, OP_GET_SCHAR,
318
+ OP_PUT_UCHAR, OP_GET_UCHAR,
319
+ OP_PUT_SHORT, OP_GET_SHORT,
320
+ OP_PUT_USHORT, OP_GET_USHORT,
321
+ OP_PUT_INT, OP_GET_INT,
322
+ OP_PUT_UINT, OP_GET_UINT,
323
+ OP_PUT_LONGLONG, OP_GET_LONGLONG,
324
+ OP_PUT_ULONGLONG, OP_GET_ULONGLONG,
325
+ OP_PUT_FLOAT, OP_GET_FLOAT,
326
+ OP_PUT_DOUBLE, OP_GET_DOUBLE,
327
+ } vara_op_t;
328
+
329
+ typedef struct {
330
+ int ncid;
331
+ int varid;
332
+ const size_t *start;
333
+ const size_t *count;
334
+ const ptrdiff_t *stride; /* NULL -> vara, non-NULL -> vars/varm */
335
+ const ptrdiff_t *imap; /* NULL -> vara/vars, non-NULL -> varm */
336
+ void *data;
337
+ vara_op_t op;
338
+ int status;
339
+ } vara_args_t;
340
+
341
+ #define DO_TYPED(family, suffix) \
342
+ do { \
343
+ if ( a->imap ) { \
344
+ a->status = nc_##family##_varm_##suffix(a->ncid, a->varid, \
345
+ a->start, a->count, \
346
+ a->stride, a->imap, \
347
+ a->data); \
348
+ } \
349
+ else if ( a->stride ) { \
350
+ a->status = nc_##family##_vars_##suffix(a->ncid, a->varid, \
351
+ a->start, a->count, \
352
+ a->stride, a->data); \
353
+ } \
354
+ else { \
355
+ a->status = nc_##family##_vara_##suffix(a->ncid, a->varid, \
356
+ a->start, a->count, \
357
+ a->data); \
358
+ } \
359
+ } while ( 0 ); break
360
+
361
+ #define DO_GENERIC(family) \
362
+ do { \
363
+ if ( a->imap ) { \
364
+ a->status = nc_##family##_varm(a->ncid, a->varid, \
365
+ a->start, a->count, \
366
+ a->stride, a->imap, a->data); \
367
+ } \
368
+ else if ( a->stride ) { \
369
+ a->status = nc_##family##_vars(a->ncid, a->varid, \
370
+ a->start, a->count, \
371
+ a->stride, a->data); \
372
+ } \
373
+ else { \
374
+ a->status = nc_##family##_vara(a->ncid, a->varid, \
375
+ a->start, a->count, a->data); \
376
+ } \
377
+ } while ( 0 ); break
378
+
379
+ static void *
380
+ nogvl_io (void *p)
381
+ {
382
+ vara_args_t *a;
383
+
384
+ a = (vara_args_t *) p;
385
+ switch ( a->op ) {
386
+ case OP_PUT_GENERIC: DO_GENERIC(put);
387
+ case OP_GET_GENERIC: DO_GENERIC(get);
388
+ case OP_PUT_TEXT: DO_TYPED(put, text);
389
+ case OP_GET_TEXT: DO_TYPED(get, text);
390
+ case OP_PUT_SCHAR: DO_TYPED(put, schar);
391
+ case OP_GET_SCHAR: DO_TYPED(get, schar);
392
+ case OP_PUT_UCHAR: DO_TYPED(put, uchar);
393
+ case OP_GET_UCHAR: DO_TYPED(get, uchar);
394
+ case OP_PUT_SHORT: DO_TYPED(put, short);
395
+ case OP_GET_SHORT: DO_TYPED(get, short);
396
+ case OP_PUT_USHORT: DO_TYPED(put, ushort);
397
+ case OP_GET_USHORT: DO_TYPED(get, ushort);
398
+ case OP_PUT_INT: DO_TYPED(put, int);
399
+ case OP_GET_INT: DO_TYPED(get, int);
400
+ case OP_PUT_UINT: DO_TYPED(put, uint);
401
+ case OP_GET_UINT: DO_TYPED(get, uint);
402
+ case OP_PUT_LONGLONG: DO_TYPED(put, longlong);
403
+ case OP_GET_LONGLONG: DO_TYPED(get, longlong);
404
+ case OP_PUT_ULONGLONG: DO_TYPED(put, ulonglong);
405
+ case OP_GET_ULONGLONG: DO_TYPED(get, ulonglong);
406
+ case OP_PUT_FLOAT: DO_TYPED(put, float);
407
+ case OP_GET_FLOAT: DO_TYPED(get, float);
408
+ case OP_PUT_DOUBLE: DO_TYPED(put, double);
409
+ case OP_GET_DOUBLE: DO_TYPED(get, double);
410
+ }
411
+ return NULL;
412
+ }
413
+ #undef DO_TYPED
414
+ #undef DO_GENERIC
415
+
416
+ /* ---------------------------------------------------------- */
417
+ /* NC_STRING — separate char**-based dispatch */
418
+ /* ---------------------------------------------------------- */
419
+
420
+ typedef struct {
421
+ int ncid;
422
+ int varid;
423
+ const size_t *start;
424
+ const size_t *count;
425
+ const ptrdiff_t *stride;
426
+ const ptrdiff_t *imap;
427
+ char **ptrs;
428
+ int status;
429
+ bool is_put;
430
+ } string_args_t;
431
+
432
+ static void *
433
+ nogvl_string_io (void *p)
434
+ {
435
+ string_args_t *a;
436
+
437
+ a = (string_args_t *) p;
438
+ if ( a->is_put ) {
439
+ if ( a->imap ) {
440
+ a->status = nc_put_varm_string(a->ncid, a->varid, a->start, a->count,
441
+ a->stride, a->imap, (const char **) a->ptrs);
442
+ }
443
+ else if ( a->stride ) {
444
+ a->status = nc_put_vars_string(a->ncid, a->varid, a->start, a->count,
445
+ a->stride, (const char **) a->ptrs);
446
+ }
447
+ else {
448
+ a->status = nc_put_vara_string(a->ncid, a->varid, a->start, a->count,
449
+ (const char **) a->ptrs);
450
+ }
451
+ }
452
+ else {
453
+ if ( a->imap ) {
454
+ a->status = nc_get_varm_string(a->ncid, a->varid, a->start, a->count,
455
+ a->stride, a->imap, a->ptrs);
456
+ }
457
+ else if ( a->stride ) {
458
+ a->status = nc_get_vars_string(a->ncid, a->varid, a->start, a->count,
459
+ a->stride, a->ptrs);
460
+ }
461
+ else {
462
+ a->status = nc_get_vara_string(a->ncid, a->varid, a->start, a->count,
463
+ a->ptrs);
464
+ }
465
+ }
466
+ return NULL;
467
+ }
468
+
469
+ /* Recursively shape a flat Ruby Array<String> into nested form based on
470
+ count[0..ndim-1]. `offset` is the running cursor through the flat array. */
471
+ static VALUE
472
+ build_nested (VALUE flat, size_t *offset, const size_t *count, int ndim)
473
+ {
474
+ VALUE out;
475
+ size_t n, i;
476
+
477
+ n = count[0];
478
+ out = rb_ary_new_capa((long) n);
479
+ if ( ndim == 1 ) {
480
+ for (i=0; i<n; i++) {
481
+ rb_ary_push(out, RARRAY_AREF(flat, (long) (*offset)++));
482
+ }
483
+ }
484
+ else {
485
+ for (i=0; i<n; i++) {
486
+ rb_ary_push(out, build_nested(flat, offset, count + 1, ndim - 1));
487
+ }
488
+ }
489
+ return out;
490
+ }
491
+
492
+ /* Map a single-char MemoryView format to a typed op (or -1 for unknown),
493
+ * reporting through *width the element size the format implies.
494
+ *
495
+ * `ruby/memory_view.h` specifies the vocabulary: `format` is a sequence
496
+ * of pack-template specifiers, and `item_size` should equal
497
+ * rb_memory_view_item_size_from_format(format). So there is one
498
+ * spelling, not a choice of conventions -- `c`/`C` for the 1-byte
499
+ * integers, `s`/`S` for the 2-byte ones, `i`/`I`/`q`/`Q`/`f`/`d` for
500
+ * the rest. It is what the producers around us publish. (`l`/`L` are
501
+ * in that vocabulary too, and mean 32 bits here; red-arrow spells Int32
502
+ * that way. We read them as Ruby does.) So are `e`/`E` and `g`/`G`,
503
+ * the floats that carry their own byte order -- little- and big-endian
504
+ * single and double -- which the string parser below holds to the
505
+ * host's.
506
+ *
507
+ * `b`/`B`/`h`/`H` are PEP 3118's spellings for the 1- and 2-byte
508
+ * integers and are outside Ruby's vocabulary, where they would denote
509
+ * bit- and hex-strings. We accept them anyway. CArray 3.0.0, the
510
+ * released version, publishes them -- 3.0.1 switches to Ruby's
511
+ * spelling, but a buffer from the version people have installed today
512
+ * arrives spelled PEP's way -- and so does a buffer that crossed a
513
+ * language boundary without being translated. That is a thin
514
+ * compatibility layer around the canonical set, not a second
515
+ * convention: nothing binary-numeric in Ruby's vocabulary collides with
516
+ * them, so accepting them cannot cause a misread.
517
+ *
518
+ * The item_size check at the call site is what makes this safe. A
519
+ * format that crossed from PEP-land untranslated can disagree with the
520
+ * width it implies here -- numpy spells int64 `l` and float16 `e` --
521
+ * and comparing against item_size catches that without having to guess
522
+ * which vocabulary the producer meant.
523
+ *
524
+ * Formats Ruby has no word for and netCDF has no type for (`?`,
525
+ * `Zf`/`Zd`, `T{...}`) fall through as unsupported. */
526
+ static int
527
+ vara_op_for_format (char fmt, bool is_put, ssize_t *width)
528
+ {
529
+ switch ( fmt ) {
530
+ case 'c': case 'b': *width = 1; return is_put ? OP_PUT_SCHAR : OP_GET_SCHAR;
531
+ case 'C': case 'B': *width = 1; return is_put ? OP_PUT_UCHAR : OP_GET_UCHAR;
532
+ case 's': case 'h': *width = 2; return is_put ? OP_PUT_SHORT : OP_GET_SHORT;
533
+ case 'S': case 'H': *width = 2; return is_put ? OP_PUT_USHORT : OP_GET_USHORT;
534
+ case 'i': case 'l': *width = 4; return is_put ? OP_PUT_INT : OP_GET_INT;
535
+ case 'I': case 'L': *width = 4; return is_put ? OP_PUT_UINT : OP_GET_UINT;
536
+ case 'q': *width = 8; return is_put ? OP_PUT_LONGLONG : OP_GET_LONGLONG;
537
+ case 'Q': *width = 8; return is_put ? OP_PUT_ULONGLONG : OP_GET_ULONGLONG;
538
+ case 'f': case 'e': case 'g': *width = 4; return is_put ? OP_PUT_FLOAT : OP_GET_FLOAT;
539
+ case 'd': case 'E': case 'G': *width = 8; return is_put ? OP_PUT_DOUBLE : OP_GET_DOUBLE;
540
+ default: *width = 0; return -1;
541
+ }
542
+ }
543
+
544
+ /* The netCDF type a typed op moves, for holding a buffer's format to the
545
+ * type libnetcdf will move regardless (an enum's base type). */
546
+ static nc_type
547
+ nc_type_of_op (vara_op_t op)
548
+ {
549
+ switch ( op ) {
550
+ case OP_PUT_SCHAR: case OP_GET_SCHAR: return NC_BYTE;
551
+ case OP_PUT_UCHAR: case OP_GET_UCHAR: return NC_UBYTE;
552
+ case OP_PUT_SHORT: case OP_GET_SHORT: return NC_SHORT;
553
+ case OP_PUT_USHORT: case OP_GET_USHORT: return NC_USHORT;
554
+ case OP_PUT_INT: case OP_GET_INT: return NC_INT;
555
+ case OP_PUT_UINT: case OP_GET_UINT: return NC_UINT;
556
+ case OP_PUT_LONGLONG: case OP_GET_LONGLONG: return NC_INT64;
557
+ case OP_PUT_ULONGLONG: case OP_GET_ULONGLONG: return NC_UINT64;
558
+ case OP_PUT_FLOAT: case OP_GET_FLOAT: return NC_FLOAT;
559
+ case OP_PUT_DOUBLE: case OP_GET_DOUBLE: return NC_DOUBLE;
560
+ default: return NC_NAT;
561
+ }
562
+ }
563
+
564
+ /* True if a byte-order marker denotes the host's own byte order (or says
565
+ * nothing about byte order at all).
566
+ *
567
+ * libnetcdf reads and writes the buffer as native-endian memory, so a
568
+ * marker naming the opposite order has to be refused rather than
569
+ * silently misread; byte-swapping is the producer's job. */
570
+ static bool
571
+ is_native_endian_marker (char marker)
572
+ {
573
+ switch ( marker ) {
574
+ case '=': case '@': case '|': case '^':
575
+ return true; /* native, or not applicable */
576
+ #ifdef WORDS_BIGENDIAN
577
+ case '>': case '!': return true;
578
+ case '<': return false;
579
+ #else
580
+ case '<': return true;
581
+ case '>': case '!': return false;
582
+ #endif
583
+ default:
584
+ return false;
585
+ }
586
+ }
587
+
588
+ /* Resolve a whole format string to a typed op, or -1 with *error_msg set.
589
+ *
590
+ * `format` is a sequence of specifiers, and the vocabulary lets one carry
591
+ * a byte order. Ruby spells it as a suffix on an integer (`q<`, `s>`)
592
+ * and as a letter of its own on a float (`e`/`E` little-endian, `g`/`G`
593
+ * big); a suffix on any other specifier is not Ruby's, and is refused
594
+ * as unsupported. PEP 3118 spells it as a prefix on the whole string
595
+ * (`<d`), which arrives here for the same reason PEP's `b`/`B` do -- a
596
+ * buffer that crossed a language boundary untranslated. All of these
597
+ * are read, and one naming the foreign order is refused: without this
598
+ * the order is simply not looked at, and a byte-swapped buffer
599
+ * transfers as if it were native.
600
+ *
601
+ * Only a single numeric element is accepted. Repeat counts, records
602
+ * (`T{...}`) and mixed formats have no netCDF counterpart, and reading
603
+ * just the first specifier of one would transfer the buffer as an array
604
+ * of whatever it happens to start with. */
605
+ static int
606
+ vara_op_for_format_string (const char *format, bool is_put, ssize_t *width,
607
+ VALUE *error_msg)
608
+ {
609
+ const char *p;
610
+ char spec;
611
+ int op;
612
+
613
+ p = format;
614
+ if ( *p == '<' || *p == '>' || *p == '=' || *p == '!' ||
615
+ *p == '@' || *p == '|' || *p == '^' ) {
616
+ if ( ! is_native_endian_marker(*p) ) {
617
+ goto not_native;
618
+ }
619
+ p++;
620
+ }
621
+
622
+ spec = *p;
623
+ op = vara_op_for_format(spec, is_put, width);
624
+ if ( op < 0 ) {
625
+ *error_msg = rb_sprintf("unsupported buffer format: %s", format);
626
+ return -1;
627
+ }
628
+ p++;
629
+
630
+ if ( spec == 'e' || spec == 'E' || spec == 'g' || spec == 'G' ) {
631
+ if ( ! is_native_endian_marker(( spec == 'e' || spec == 'E' ) ? '<' : '>') ) {
632
+ goto not_native;
633
+ }
634
+ }
635
+ else if ( *p == '<' || *p == '>' ) {
636
+ if ( strchr("sSiIlLqQ", spec) == NULL ) {
637
+ *error_msg = rb_sprintf("unsupported buffer format: %s", format);
638
+ return -1;
639
+ }
640
+ if ( ! is_native_endian_marker(*p) ) {
641
+ goto not_native;
642
+ }
643
+ p++;
644
+ }
645
+
646
+ if ( *p != '\0' ) {
647
+ *error_msg = rb_sprintf("buffer format \"%s\" does not describe a "
648
+ "single numeric element", format);
649
+ return -1;
650
+ }
651
+
652
+ return op;
653
+
654
+ not_native:
655
+ *error_msg = rb_sprintf("buffer format \"%s\" is not native-endian", format);
656
+ return -1;
657
+ }
658
+
659
+ /* ---------------------------------------------------------- */
660
+ /* Core dispatcher */
661
+ /* ---------------------------------------------------------- */
662
+
663
+ /* Sizes a transfer of `count`: *items is the number of elements moved
664
+ * (the product of count) and *extent the number the buffer must span.
665
+ * Without an imap the two are equal. With one, libnetcdf addresses
666
+ * element sum(index[i] * imap[i]) of the buffer, so it must reach the
667
+ * largest such offset -- which a gapped imap puts well past the product.
668
+ *
669
+ * Returns NULL, or why the transfer cannot be sized: a negative imap
670
+ * entry would address memory before the buffer, and a size that
671
+ * overflows ssize_t would wrap to one any buffer passes -- count is not
672
+ * bounded by libnetcdf on a put along an unlimited dimension. */
673
+ static const char *
674
+ transfer_size (int ndims, const size_t *count, const ptrdiff_t *imap,
675
+ ssize_t *items_out, ssize_t *extent_out)
676
+ {
677
+ ssize_t items, last, term;
678
+ int i;
679
+
680
+ if ( imap ) {
681
+ for (i=0; i<ndims; i++) {
682
+ if ( imap[i] < 0 ) {
683
+ return "imap entries must not be negative";
684
+ }
685
+ }
686
+ }
687
+ for (i=0; i<ndims; i++) {
688
+ if ( count[i] == 0 ) {
689
+ *items_out = *extent_out = 0;
690
+ return NULL;
691
+ }
692
+ }
693
+
694
+ items = 1;
695
+ for (i=0; i<ndims; i++) {
696
+ if ( count[i] > (size_t) SSIZE_MAX ||
697
+ __builtin_mul_overflow(items, (ssize_t) count[i], &items) ) {
698
+ return "count is too large";
699
+ }
700
+ }
701
+ *items_out = items;
702
+ if ( imap == NULL ) {
703
+ *extent_out = items;
704
+ return NULL;
705
+ }
706
+
707
+ last = 0;
708
+ for (i=0; i<ndims; i++) {
709
+ if ( __builtin_mul_overflow((ssize_t) (count[i] - 1), (ssize_t) imap[i], &term) ||
710
+ __builtin_add_overflow(last, term, &last) ) {
711
+ return "imap reaches too far";
712
+ }
713
+ }
714
+ if ( __builtin_add_overflow(last, (ssize_t) 1, extent_out) ) {
715
+ return "imap reaches too far";
716
+ }
717
+ return NULL;
718
+ }
719
+
720
+ typedef struct {
721
+ void *(*fn)(void *);
722
+ void *arg;
723
+ bool ran;
724
+ } nogvl_call_t;
725
+
726
+ static void *
727
+ nogvl_call (void *p)
728
+ {
729
+ nogvl_call_t *c;
730
+
731
+ c = (nogvl_call_t *) p;
732
+ c->ran = true;
733
+ return c->fn(c->arg);
734
+ }
735
+
736
+ /* Runs fn(arg) without the GVL and returns whether it ran.
737
+ *
738
+ * rb_thread_call_without_gvl checks for interrupts as it returns and
739
+ * raises right there -- a Ctrl-C, or a Thread#raise, that arrived during
740
+ * the call -- which would skip var_io_dispatch's cleanup: a MemoryView
741
+ * left unreleased stays in Ruby's table of exported objects, which marks
742
+ * it, so its buffer is never collected; strings libnetcdf allocated
743
+ * leak. The _gvl2 variant leaves a pending interrupt pending, for the
744
+ * caller to raise with rb_thread_check_ints() once it has cleaned up.
745
+ * It also does not call fn at all when an interrupt is already pending,
746
+ * and the caller then has to raise it or try again. */
747
+ static bool
748
+ call_without_gvl (void *(*fn)(void *), void *arg)
749
+ {
750
+ nogvl_call_t c;
751
+
752
+ c.fn = fn;
753
+ c.arg = arg;
754
+ c.ran = false;
755
+ rb_thread_call_without_gvl2(nogvl_call, &c, RUBY_UBF_IO, NULL);
756
+ return c.ran;
757
+ }
758
+
759
+ static VALUE
760
+ var_io_dispatch (VALUE rncid, VALUE rvarid,
761
+ VALUE rstart, VALUE rcount,
762
+ VALUE rstride, VALUE rimap,
763
+ VALUE rbuf, bool is_put)
764
+ {
765
+ size_t start[NC_MAX_VAR_DIMS], count[NC_MAX_VAR_DIMS];
766
+ ptrdiff_t stride_buf[NC_MAX_VAR_DIMS], imap_buf[NC_MAX_VAR_DIMS];
767
+ const ptrdiff_t *stride, *imap;
768
+ int ncid, varid, ndims;
769
+ int saved_status;
770
+ VALUE error_class, error_msg;
771
+ rb_memory_view_t view;
772
+ bool view_held;
773
+ char **str_ptrs;
774
+ VALUE vstr_ptrs; /* ALLOCV holder for str_ptrs */
775
+ VALUE vtext; /* ALLOCV holder for their text on a put */
776
+ size_t str_to_free; /* strings libnetcdf allocated on a get */
777
+ VALUE str_result;
778
+ bool ran; /* false if an interrupt kept libnetcdf from running */
779
+ vara_args_t args;
780
+ string_args_t sa;
781
+ nc_type var_xtype, base;
782
+ int user_class, st, flags, fmt_op;
783
+ size_t user_size, n_elem, text_len, len, offset, i;
784
+ bool force_generic; /* set for enum to bypass typed dispatch */
785
+ bool contiguous;
786
+ ssize_t expected_items, extent_items, fmt_width;
787
+ ssize_t effective_item_size, expected_bytes;
788
+ const char *why;
789
+ char *text;
790
+ VALUE flat, rstr;
791
+
792
+ ncid = NUM2INT(rncid);
793
+ varid = NUM2INT(rvarid);
794
+
795
+ NC_CHECK(nc_inq_varndims(ncid, varid, &ndims));
796
+ ary_to_sizet(rstart, ndims, "start", start);
797
+ ary_to_sizet(rcount, ndims, "count", count);
798
+ stride = ary_to_ptrdiff(rstride, ndims, "stride", stride_buf) ? stride_buf : NULL;
799
+ imap = ary_to_ptrdiff(rimap, ndims, "imap", imap_buf) ? imap_buf : NULL;
800
+
801
+ saved_status = NC_NOERR;
802
+ error_class = Qnil;
803
+ error_msg = Qnil;
804
+ /* Zero-initialize the view to guard against MemoryView providers
805
+ that forget to set `view.item_desc.components` /
806
+ `view.item_desc.length`. rb_memory_view_release unconditionally
807
+ xfree()s `components`, so an uninitialized garbage pointer
808
+ there aborts the process — and the crash looks like it comes
809
+ from this gem, not from the producer. red-arrow <= 24.0.0 is a
810
+ confirmed case (primitive_array_get and buffer_get in
811
+ ext/arrow/memory-view.cpp); it is fixed from 25.0.0, which
812
+ memsets the view itself. We cannot rely on users having
813
+ upgraded, and the same class of bug can recur in any future
814
+ producer, so this stays permanently as cheap defensive
815
+ insurance. */
816
+ memset(&view, 0, sizeof(view));
817
+ view_held = false;
818
+ str_ptrs = NULL;
819
+ vstr_ptrs = 0;
820
+ vtext = 0;
821
+ str_to_free = 0;
822
+ str_result = Qnil;
823
+ ran = true;
824
+
825
+ args.ncid = ncid;
826
+ args.varid = varid;
827
+ args.start = start;
828
+ args.count = count;
829
+ args.stride = stride;
830
+ args.imap = imap;
831
+ args.data = NULL;
832
+ args.op = OP_PUT_GENERIC;
833
+ args.status = NC_NOERR;
834
+
835
+ st = nc_inq_vartype(ncid, varid, &var_xtype);
836
+ if ( st != NC_NOERR ) {
837
+ saved_status = st;
838
+ goto cleanup;
839
+ }
840
+
841
+ /* For user-defined types, determine the class so we can route opaque/
842
+ enum/vlen correctly. Enum I/O delegates to the base type; opaque is
843
+ handled as a fixed-size byte buffer via the generic path; vlen is
844
+ still unsupported in this dispatcher. */
845
+ user_class = 0;
846
+ user_size = 0;
847
+ force_generic = false;
848
+ if ( var_xtype >= NC_FIRSTUSERTYPEID ) {
849
+ st = nc_inq_user_type(ncid, var_xtype, NULL, &user_size, &base,
850
+ NULL, &user_class);
851
+ if ( st != NC_NOERR ) {
852
+ saved_status = st;
853
+ goto cleanup;
854
+ }
855
+ if ( user_class == NC_ENUM ) {
856
+ /* Enum vars: bytewise the same as their base type, but libnetcdf
857
+ rejects the typed nc_*_vara_int family for them. Substitute
858
+ the base type so byte-size logic uses the right element width,
859
+ and force the untyped nc_*_vara path. */
860
+ var_xtype = base;
861
+ user_class = 0;
862
+ force_generic = true;
863
+ }
864
+ else if ( user_class == NC_COMPOUND ) {
865
+ error_class = rb_eNotImpError;
866
+ error_msg = rb_str_new_cstr("NC_COMPOUND variables are not supported");
867
+ goto cleanup;
868
+ }
869
+ else if ( user_class == NC_VLEN ) {
870
+ error_class = rb_eNotImpError;
871
+ error_msg = rb_str_new_cstr("NC_VLEN variables are not supported");
872
+ goto cleanup;
873
+ }
874
+ /* NC_OPAQUE falls through; size is in user_size. */
875
+ }
876
+
877
+ why = transfer_size(ndims, count, imap, &expected_items, &extent_items);
878
+ if ( why ) {
879
+ error_class = rb_eArgError;
880
+ error_msg = rb_str_new_cstr(why);
881
+ goto cleanup;
882
+ }
883
+
884
+ /* Path 0: NC_STRING — char**, separate from MemoryView. */
885
+ if ( var_xtype == NC_STRING ) {
886
+ /* The Ruby Array handed in or out is not laid out in memory, so
887
+ there is nothing for an imap to map onto. */
888
+ if ( imap ) {
889
+ error_class = rb_eNotImpError;
890
+ error_msg = rb_str_new_cstr("imap is not supported for NC_STRING variables");
891
+ goto cleanup;
892
+ }
893
+ n_elem = (size_t) expected_items;
894
+
895
+ sa.ncid = ncid;
896
+ sa.varid = varid;
897
+ sa.start = start;
898
+ sa.count = count;
899
+ sa.stride = stride;
900
+ sa.imap = imap;
901
+ sa.status = NC_NOERR;
902
+
903
+ if ( is_put ) {
904
+ if ( RB_TYPE_P(rbuf, T_STRING) ) {
905
+ if ( n_elem != 1 ) {
906
+ error_class = rb_eArgError;
907
+ error_msg = rb_sprintf("NC_STRING: single String given but %zu strings expected", n_elem);
908
+ goto cleanup;
909
+ }
910
+ flat = rb_ary_new_from_args(1, rbuf);
911
+ }
912
+ else if ( RB_TYPE_P(rbuf, T_ARRAY) ) {
913
+ flat = rb_funcall(rbuf, rb_intern("flatten"), 0);
914
+ }
915
+ else {
916
+ error_class = rb_eTypeError;
917
+ error_msg = rb_str_new_cstr("NC_STRING buffer must be a String or Array of Strings");
918
+ goto cleanup;
919
+ }
920
+ if ( (size_t) RARRAY_LEN(flat) != n_elem ) {
921
+ error_class = rb_eArgError;
922
+ error_msg = rb_sprintf("NC_STRING: expected %zu strings, got %ld after flatten",
923
+ n_elem, RARRAY_LEN(flat));
924
+ goto cleanup;
925
+ }
926
+ /* Copy the text out of the Strings before letting go of the
927
+ GVL. Pointers into them would be left to another thread
928
+ that can modify a String -- reallocating its bytes -- or to
929
+ a GC compaction that moves an embedded one. */
930
+ text_len = 0;
931
+ for (i=0; i<n_elem; i++) {
932
+ rstr = RARRAY_AREF(flat, (long) i);
933
+ if ( ! RB_TYPE_P(rstr, T_STRING) ) {
934
+ error_class = rb_eTypeError;
935
+ error_msg = rb_str_new_cstr("NC_STRING elements must all be Strings");
936
+ goto cleanup;
937
+ }
938
+ StringValueCStr(rstr); /* raises on an embedded NUL */
939
+ text_len += (size_t) RSTRING_LEN(rstr) + 1;
940
+ }
941
+ str_ptrs = ALLOCV_N(char *, vstr_ptrs, n_elem > 0 ? n_elem : 1);
942
+ text = ALLOCV_N(char, vtext, text_len > 0 ? text_len : 1);
943
+ for (i=0; i<n_elem; i++) {
944
+ rstr = RARRAY_AREF(flat, (long) i);
945
+ len = (size_t) RSTRING_LEN(rstr);
946
+ memcpy(text, RSTRING_PTR(rstr), len);
947
+ text[len] = '\0';
948
+ str_ptrs[i] = text;
949
+ text += len + 1;
950
+ }
951
+ sa.ptrs = str_ptrs;
952
+ sa.is_put = true;
953
+ ran = call_without_gvl(nogvl_string_io, &sa);
954
+ saved_status = sa.status;
955
+ str_result = rbuf;
956
+ }
957
+ else {
958
+ /* The strings come back as new Ruby objects; a buffer passed
959
+ for them would be left untouched. */
960
+ if ( ! NIL_P(rbuf) ) {
961
+ error_class = rb_eArgError;
962
+ error_msg = rb_str_new_cstr("NC_STRING variables take no buffer on a get");
963
+ goto cleanup;
964
+ }
965
+ str_ptrs = ALLOCV_N(char *, vstr_ptrs, n_elem > 0 ? n_elem : 1);
966
+ for (i=0; i<n_elem; i++) {
967
+ str_ptrs[i] = NULL;
968
+ }
969
+ sa.ptrs = str_ptrs;
970
+ sa.is_put = false;
971
+ ran = call_without_gvl(nogvl_string_io, &sa);
972
+ if ( ran ) {
973
+ str_to_free = n_elem;
974
+ }
975
+ saved_status = sa.status;
976
+ if ( ran && saved_status == NC_NOERR ) {
977
+ flat = rb_ary_new_capa((long) n_elem);
978
+ for (i=0; i<n_elem; i++) {
979
+ rb_ary_push(flat, rb_utf8_str_new_cstr(str_ptrs[i] ? str_ptrs[i] : ""));
980
+ }
981
+
982
+ if ( ndims == 0 ) {
983
+ str_result = rb_ary_entry(flat, 0);
984
+ }
985
+ else if ( ndims == 1 ) {
986
+ str_result = flat;
987
+ }
988
+ else {
989
+ offset = 0;
990
+ str_result = build_nested(flat, &offset, count, ndims);
991
+ }
992
+ }
993
+ }
994
+ goto cleanup;
995
+ }
996
+
997
+ /* Only NC_STRING, above, is read into an object this gem makes; the
998
+ rest go into memory the caller provides, on a get as on a put. */
999
+ if ( NIL_P(rbuf) ) {
1000
+ error_class = rb_eArgError;
1001
+ error_msg = rb_str_new_cstr(is_put
1002
+ ? "buffer is required for put operations"
1003
+ : "buffer is required for get operations (except on NC_STRING variables)");
1004
+ goto cleanup;
1005
+ }
1006
+
1007
+ /* Path B: MemoryView provider buffer. Ask for what is read below: the
1008
+ format, to pick the typed call, and the strides, to see whether the
1009
+ memory is row-major. Taking strides is the weaker request -- a
1010
+ producer with contiguous memory can always answer it -- and a
1011
+ strided one, such as a transposed numpy array, then hands over its
1012
+ view to be refused below as not row-major, where it would refuse a
1013
+ request without strides and leave the reason unknown. */
1014
+ flags = RUBY_MEMORY_VIEW_FORMAT | RUBY_MEMORY_VIEW_STRIDES;
1015
+ if ( ! is_put ) {
1016
+ flags |= RUBY_MEMORY_VIEW_WRITABLE;
1017
+ }
1018
+ if ( ! rb_memory_view_get(rbuf, &view, flags) ) {
1019
+ error_class = rb_eTypeError;
1020
+ error_msg = rb_sprintf("buffer must provide a%s MemoryView",
1021
+ is_put ? "" : " writable");
1022
+ goto cleanup;
1023
+ }
1024
+ view_held = true;
1025
+
1026
+ /* Check contiguity for any rank, including 1-D. A producer may hand us
1027
+ a strided view (a slice whose stride != item_size), so consumer-side
1028
+ verification is necessary to prevent silent data corruption.
1029
+ NULL strides mean contiguous by definition (a byte-array view from
1030
+ rb_memory_view_init_as_byte_array, such as Fiddle::Pointer's, has
1031
+ NULL shape and strides). ruby/memory_view.h lets a 1-D view leave
1032
+ shape NULL and still give strides, so that case is checked by hand
1033
+ -- rb_memory_view_is_row_major_contiguous would dereference the
1034
+ shape -- and a view of more dimensions without a shape cannot be
1035
+ laid out at all. */
1036
+ if ( view.ndim >= 1 && view.strides != NULL ) {
1037
+ contiguous = ( view.shape != NULL )
1038
+ ? rb_memory_view_is_row_major_contiguous(&view)
1039
+ : ( view.ndim == 1 && view.strides[0] == view.item_size );
1040
+ if ( ! contiguous ) {
1041
+ error_class = rb_eArgError;
1042
+ error_msg = rb_str_new_cstr("buffer must be row-major (C-contiguous)");
1043
+ goto cleanup;
1044
+ }
1045
+ }
1046
+
1047
+ /* rb_memory_view_get passes RUBY_MEMORY_VIEW_WRITABLE to the producer
1048
+ but does not hold it to the request, so a producer that ignores it
1049
+ hands back a read-only view. libnetcdf would write into it. */
1050
+ if ( ! is_put && view.readonly ) {
1051
+ error_class = rb_eTypeError;
1052
+ error_msg = rb_str_new_cstr("buffer is read-only (its producer ignored the request "
1053
+ "for a writable MemoryView)");
1054
+ goto cleanup;
1055
+ }
1056
+
1057
+ /* A buffer that names its element type is held to it: the format must
1058
+ be one we read, and agree with item_size. Opaque data has no
1059
+ element type for a format to name, so an opaque buffer's is not
1060
+ looked at. */
1061
+ fmt_op = -1;
1062
+ if ( view.format != NULL && user_class != NC_OPAQUE ) {
1063
+ fmt_width = 0;
1064
+ fmt_op = vara_op_for_format_string(view.format, is_put, &fmt_width, &error_msg);
1065
+ if ( fmt_op < 0 ) {
1066
+ error_class = rb_eArgError;
1067
+ goto cleanup;
1068
+ }
1069
+ /* The format tells us how to interpret the elements; item_size
1070
+ tells us how wide they actually are. If the two disagree the
1071
+ producer means something we are not reading -- PEP's 64-bit
1072
+ `l`, say, against Ruby's 32-bit one -- so refuse rather than
1073
+ misread the buffer. */
1074
+ if ( view.item_size != fmt_width ) {
1075
+ error_class = rb_eArgError;
1076
+ error_msg = rb_sprintf("buffer format \"%s\" implies %"PRIdSIZE"-byte elements "
1077
+ "but item_size is %"PRIdSIZE,
1078
+ view.format, fmt_width, view.item_size);
1079
+ goto cleanup;
1080
+ }
1081
+ }
1082
+
1083
+ /* The per-element byte size libnetcdf will actually move. Opaque,
1084
+ enum and format-less buffers go through the untyped call, which
1085
+ transfers elements of the variable's own type (the opaque's declared
1086
+ size, the enum's base type) regardless of how the buffer chose to
1087
+ break itself up. Only a formatted buffer on the typed path is
1088
+ transferred at its own item_size. */
1089
+ if ( user_class == NC_OPAQUE ) {
1090
+ effective_item_size = (ssize_t) user_size;
1091
+ }
1092
+ else if ( force_generic || view.format == NULL ) {
1093
+ effective_item_size = nc_xtype_size(var_xtype);
1094
+ }
1095
+ else {
1096
+ effective_item_size = view.item_size;
1097
+ }
1098
+ if ( __builtin_mul_overflow(extent_items, effective_item_size, &expected_bytes) ) {
1099
+ error_class = rb_eArgError;
1100
+ error_msg = rb_str_new_cstr("count is too large");
1101
+ goto cleanup;
1102
+ }
1103
+ if ( view.byte_size < expected_bytes ) {
1104
+ error_class = rb_eArgError;
1105
+ error_msg = rb_sprintf("buffer too small (have %"PRIdSIZE" bytes, need %"PRIdSIZE")",
1106
+ view.byte_size, expected_bytes);
1107
+ goto cleanup;
1108
+ }
1109
+
1110
+ if ( user_class == NC_OPAQUE ) {
1111
+ /* Opaque: pass the bytes straight through, libnetcdf knows the
1112
+ element size. */
1113
+ args.op = is_put ? OP_PUT_GENERIC : OP_GET_GENERIC;
1114
+ }
1115
+ else if ( force_generic ) {
1116
+ /* Enum: libnetcdf moves elements of the base type whatever the
1117
+ buffer holds, so a format naming another type would be
1118
+ reinterpreted, not converted. */
1119
+ if ( fmt_op >= 0 && nc_type_of_op((vara_op_t) fmt_op) != var_xtype ) {
1120
+ error_class = rb_eArgError;
1121
+ error_msg = rb_sprintf("buffer format \"%s\" does not match the enum's base type",
1122
+ view.format);
1123
+ goto cleanup;
1124
+ }
1125
+ args.op = is_put ? OP_PUT_GENERIC : OP_GET_GENERIC;
1126
+ }
1127
+ else if ( var_xtype == NC_CHAR ) {
1128
+ if ( view.item_size != 1 ) {
1129
+ error_class = rb_eArgError;
1130
+ error_msg = rb_sprintf("NC_CHAR variable requires a 1-byte-element buffer (item_size=%"PRIdSIZE")",
1131
+ view.item_size);
1132
+ goto cleanup;
1133
+ }
1134
+ args.op = is_put ? OP_PUT_TEXT : OP_GET_TEXT;
1135
+ }
1136
+ else if ( fmt_op < 0 ) {
1137
+ args.op = is_put ? OP_PUT_GENERIC : OP_GET_GENERIC;
1138
+ }
1139
+ else {
1140
+ args.op = (vara_op_t) fmt_op;
1141
+ }
1142
+ args.data = view.data;
1143
+ ran = call_without_gvl(nogvl_io, &args);
1144
+ saved_status = args.status;
1145
+
1146
+ cleanup:
1147
+ /* A producer whose release_func refuses keeps its buffer exported --
1148
+ pinned for good -- so say so rather than return as if released. */
1149
+ if ( view_held && ! rb_memory_view_release(&view) && NIL_P(error_msg) ) {
1150
+ error_class = rb_eRuntimeError;
1151
+ error_msg = rb_str_new_cstr("buffer's producer refused to release its MemoryView");
1152
+ }
1153
+ if ( str_to_free ) {
1154
+ nc_free_string(str_to_free, str_ptrs);
1155
+ }
1156
+ if ( str_ptrs ) {
1157
+ ALLOCV_END(vstr_ptrs);
1158
+ }
1159
+ if ( vtext ) {
1160
+ ALLOCV_END(vtext);
1161
+ }
1162
+
1163
+ /* With nothing held any more, raise an interrupt that arrived while
1164
+ libnetcdf ran, or kept it from running. One that raises nothing
1165
+ (a trap handler, a timer) leaves the transfer to be done over. */
1166
+ rb_thread_check_ints();
1167
+ if ( ! ran ) {
1168
+ return var_io_dispatch(rncid, rvarid, rstart, rcount,
1169
+ rstride, rimap, rbuf, is_put);
1170
+ }
1171
+
1172
+ if ( ! NIL_P(error_msg) ) {
1173
+ rb_raise(NIL_P(error_class) ? rb_eRuntimeError : error_class,
1174
+ "%s", StringValueCStr(error_msg));
1175
+ }
1176
+ if ( saved_status != NC_NOERR ) {
1177
+ nc_raise(saved_status);
1178
+ }
1179
+ return NIL_P(str_result) ? rbuf : str_result;
1180
+ }
1181
+
1182
+ static VALUE
1183
+ rb_nc_put_vara (VALUE self, VALUE rncid, VALUE rvarid, VALUE rstart, VALUE rcount, VALUE rbuf)
1184
+ {
1185
+ return var_io_dispatch(rncid, rvarid, rstart, rcount, Qnil, Qnil, rbuf, true);
1186
+ }
1187
+
1188
+ static VALUE
1189
+ rb_nc_get_vara (int argc, VALUE *argv, VALUE self)
1190
+ {
1191
+ VALUE rncid, rvarid, rstart, rcount, rbuf;
1192
+
1193
+ rb_scan_args(argc, argv, "41", &rncid, &rvarid, &rstart, &rcount, &rbuf);
1194
+ return var_io_dispatch(rncid, rvarid, rstart, rcount, Qnil, Qnil, rbuf, false);
1195
+ }
1196
+
1197
+ static VALUE
1198
+ rb_nc_put_vars (VALUE self, VALUE rncid, VALUE rvarid, VALUE rstart, VALUE rcount,
1199
+ VALUE rstride, VALUE rbuf)
1200
+ {
1201
+ return var_io_dispatch(rncid, rvarid, rstart, rcount, rstride, Qnil, rbuf, true);
1202
+ }
1203
+
1204
+ static VALUE
1205
+ rb_nc_get_vars (int argc, VALUE *argv, VALUE self)
1206
+ {
1207
+ VALUE rncid, rvarid, rstart, rcount, rstride, rbuf;
1208
+
1209
+ rb_scan_args(argc, argv, "51", &rncid, &rvarid, &rstart, &rcount, &rstride, &rbuf);
1210
+ return var_io_dispatch(rncid, rvarid, rstart, rcount, rstride, Qnil, rbuf, false);
1211
+ }
1212
+
1213
+ static VALUE
1214
+ rb_nc_put_varm (VALUE self, VALUE rncid, VALUE rvarid, VALUE rstart, VALUE rcount,
1215
+ VALUE rstride, VALUE rimap, VALUE rbuf)
1216
+ {
1217
+ return var_io_dispatch(rncid, rvarid, rstart, rcount, rstride, rimap, rbuf, true);
1218
+ }
1219
+
1220
+ static VALUE
1221
+ rb_nc_get_varm (int argc, VALUE *argv, VALUE self)
1222
+ {
1223
+ VALUE rncid, rvarid, rstart, rcount, rstride, rimap, rbuf;
1224
+
1225
+ rb_scan_args(argc, argv, "61", &rncid, &rvarid, &rstart, &rcount, &rstride, &rimap, &rbuf);
1226
+ return var_io_dispatch(rncid, rvarid, rstart, rcount, rstride, rimap, rbuf, false);
1227
+ }
1228
+
1229
+ /* Whole-variable I/O: builds start = [0...] and count = [dimlens...] for the
1230
+ variable, then defers to the array dispatcher. */
1231
+ static VALUE
1232
+ rb_nc_var_io (VALUE rncid, VALUE rvarid, VALUE rbuf, bool is_put)
1233
+ {
1234
+ int dimids[NC_MAX_VAR_DIMS];
1235
+ VALUE rstart, rcount;
1236
+ size_t len;
1237
+ int ncid, varid, ndims, i;
1238
+
1239
+ ncid = NUM2INT(rncid);
1240
+ varid = NUM2INT(rvarid);
1241
+
1242
+ NC_CHECK(nc_inq_varndims(ncid, varid, &ndims));
1243
+ NC_CHECK(nc_inq_vardimid(ncid, varid, dimids));
1244
+
1245
+ rstart = rb_ary_new_capa(ndims);
1246
+ rcount = rb_ary_new_capa(ndims);
1247
+ for (i=0; i<ndims; i++) {
1248
+ NC_CHECK(nc_inq_dimlen(ncid, dimids[i], &len));
1249
+ rb_ary_push(rstart, INT2FIX(0));
1250
+ rb_ary_push(rcount, SIZET2NUM(len));
1251
+ }
1252
+ return var_io_dispatch(rncid, rvarid, rstart, rcount, Qnil, Qnil, rbuf, is_put);
1253
+ }
1254
+
1255
+ static VALUE
1256
+ rb_nc_put_var (VALUE self, VALUE rncid, VALUE rvarid, VALUE rbuf)
1257
+ {
1258
+ return rb_nc_var_io(rncid, rvarid, rbuf, true);
1259
+ }
1260
+
1261
+ static VALUE
1262
+ rb_nc_get_var (int argc, VALUE *argv, VALUE self)
1263
+ {
1264
+ VALUE rncid, rvarid, rbuf;
1265
+
1266
+ rb_scan_args(argc, argv, "21", &rncid, &rvarid, &rbuf);
1267
+ return rb_nc_var_io(rncid, rvarid, rbuf, false);
1268
+ }
1269
+
1270
+ void
1271
+ init_nc_io (void)
1272
+ {
1273
+ rb_define_module_function(mNetCDF, "nc_put_var1", rb_nc_put_var1, 4);
1274
+ rb_define_module_function(mNetCDF, "nc_get_var1", rb_nc_get_var1, 3);
1275
+
1276
+ rb_define_module_function(mNetCDF, "nc_put_var", rb_nc_put_var, 3);
1277
+ rb_define_module_function(mNetCDF, "nc_get_var", rb_nc_get_var, -1);
1278
+ rb_define_module_function(mNetCDF, "nc_put_vara", rb_nc_put_vara, 5);
1279
+ rb_define_module_function(mNetCDF, "nc_get_vara", rb_nc_get_vara, -1);
1280
+ rb_define_module_function(mNetCDF, "nc_put_vars", rb_nc_put_vars, 6);
1281
+ rb_define_module_function(mNetCDF, "nc_get_vars", rb_nc_get_vars, -1);
1282
+ rb_define_module_function(mNetCDF, "nc_put_varm", rb_nc_put_varm, 7);
1283
+ rb_define_module_function(mNetCDF, "nc_get_varm", rb_nc_get_varm, -1);
1284
+ }