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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +34 -0
- data/LICENSE +21 -0
- data/README.md +82 -0
- data/docs/00_introduction.md +34 -0
- data/docs/01_getting_started.md +127 -0
- data/docs/02_buffers.md +95 -0
- data/docs/03_reference.md +410 -0
- data/ext/netcdf/extconf.rb +61 -0
- data/ext/netcdf/internal.h +64 -0
- data/ext/netcdf/nc_att.c +431 -0
- data/ext/netcdf/nc_dim.c +74 -0
- data/ext/netcdf/nc_file.c +349 -0
- data/ext/netcdf/nc_group.c +171 -0
- data/ext/netcdf/nc_io.c +1284 -0
- data/ext/netcdf/nc_types.c +278 -0
- data/ext/netcdf/nc_var.c +551 -0
- data/ext/netcdf/netcdf.c +289 -0
- data/lib/netcdf/version.rb +3 -0
- data/lib/netcdf.rb +29 -0
- data/netcdf.gemspec +30 -0
- metadata +66 -0
data/ext/netcdf/nc_io.c
ADDED
|
@@ -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
|
+
}
|