Coverage Report

Created: 2026-09-14 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/c-blosc2/blosc/schunk.c
Line
Count
Source
1
/*********************************************************************
2
  Blosc - Blocked Shuffling and Compression Library
3
4
  Copyright (c) 2021  Blosc Development Team <blosc@blosc.org>
5
  https://blosc.org
6
  License: BSD 3-Clause (see LICENSE.txt)
7
8
  See LICENSE.txt for details about copyright and rights to use.
9
**********************************************************************/
10
11
#include "frame.h"
12
#include "stune.h"
13
#include "blosc-private.h"
14
#include "context.h"
15
#include "blosc2/tuners-registry.h"
16
#include "blosc2.h"
17
18
#if defined(_WIN32)
19
#include <windows.h>
20
#include <direct.h>
21
#include <malloc.h>
22
#define mkdir(D, M) _mkdir(D)
23
#else
24
#include <unistd.h>
25
#endif  /* _WIN32 */
26
27
#include <sys/stat.h>
28
29
#include <inttypes.h>
30
#include <limits.h>
31
#include <stdio.h>
32
#include <stdint.h>
33
#include <stdlib.h>
34
#include <string.h>
35
36
/* If C11 is supported, use it's built-in aligned allocation. */
37
#if __STDC_VERSION__ >= 201112L
38
  #include <stdalign.h>
39
#endif
40
41
static int schunk_get_chunk_flags2(blosc2_schunk *schunk, int64_t nchunk, uint8_t *chunk_flags2);
42
43
44
25.1k
static int validate_nchunk(blosc2_schunk *schunk, int64_t nchunk, bool allow_end, const char *func_name) {
45
25.1k
  if (nchunk < 0) {
46
0
    BLOSC_TRACE_ERROR("nchunk ('%" PRId64 "') is negative in %s.", nchunk, func_name);
47
0
    return BLOSC2_ERROR_INVALID_PARAM;
48
0
  }
49
50
25.1k
  if (allow_end) {
51
0
    if (nchunk > schunk->nchunks) {
52
0
      BLOSC_TRACE_ERROR("nchunk ('%" PRId64 "') is out of range [0, %" PRId64 "] in %s.",
53
0
                        nchunk, schunk->nchunks, func_name);
54
0
      return BLOSC2_ERROR_INVALID_PARAM;
55
0
    }
56
0
  }
57
25.1k
  else {
58
25.1k
    if (nchunk >= schunk->nchunks) {
59
0
      BLOSC_TRACE_ERROR("nchunk ('%" PRId64 "') exceeds the number of chunks "
60
0
                        "('%" PRId64 "') in %s.", nchunk, schunk->nchunks, func_name);
61
0
      return BLOSC2_ERROR_INVALID_PARAM;
62
0
    }
63
25.1k
  }
64
65
25.1k
  return BLOSC2_ERROR_SUCCESS;
66
25.1k
}
67
68
69
/* Get the cparams associated with a super-chunk */
70
9.25k
int blosc2_schunk_get_cparams(blosc2_schunk *schunk, blosc2_cparams **cparams) {
71
9.25k
  *cparams = calloc(1, sizeof(blosc2_cparams));
72
9.25k
  (*cparams)->schunk = schunk;
73
64.7k
  for (int i = 0; i < BLOSC2_MAX_FILTERS; i++) {
74
55.5k
    (*cparams)->filters[i] = schunk->filters[i];
75
55.5k
    (*cparams)->filters_meta[i] = schunk->filters_meta[i];
76
55.5k
  }
77
9.25k
  (*cparams)->compcode = schunk->compcode;
78
9.25k
  (*cparams)->compcode_meta = schunk->compcode_meta;
79
9.25k
  (*cparams)->clevel = schunk->clevel;
80
9.25k
  (*cparams)->typesize = schunk->typesize;
81
9.25k
  (*cparams)->blocksize = schunk->blocksize;
82
9.25k
  (*cparams)->splitmode = schunk->splitmode;
83
9.25k
  (*cparams)->use_dict = schunk->use_dict;
84
9.25k
  if (schunk->cctx == NULL) {
85
9.25k
    (*cparams)->nthreads = blosc2_get_nthreads();
86
9.25k
  }
87
0
  else {
88
0
    (*cparams)->nthreads = (int16_t)schunk->cctx->nthreads;
89
0
  }
90
9.25k
  return 0;
91
9.25k
}
92
93
94
/* Get the dparams associated with a super-chunk */
95
9.24k
int blosc2_schunk_get_dparams(blosc2_schunk *schunk, blosc2_dparams **dparams) {
96
9.24k
  *dparams = calloc(1, sizeof(blosc2_dparams));
97
9.24k
  (*dparams)->schunk = schunk;
98
9.24k
  if (schunk->dctx == NULL) {
99
9.24k
    (*dparams)->nthreads = blosc2_get_nthreads();
100
9.24k
  }
101
0
  else {
102
0
    (*dparams)->nthreads = schunk->dctx->nthreads;
103
0
  }
104
9.24k
  return 0;
105
9.24k
}
106
107
108
3.10k
int update_schunk_properties(struct blosc2_schunk* schunk) {
109
3.10k
  blosc2_cparams* cparams = schunk->storage->cparams;
110
3.10k
  blosc2_dparams* dparams = schunk->storage->dparams;
111
112
21.7k
  for (int i = 0; i < BLOSC2_MAX_FILTERS; i++) {
113
18.6k
    schunk->filters[i] = cparams->filters[i];
114
18.6k
    schunk->filters_meta[i] = cparams->filters_meta[i];
115
18.6k
  }
116
3.10k
  schunk->compcode = cparams->compcode;
117
3.10k
  schunk->compcode_meta = cparams->compcode_meta;
118
3.10k
  schunk->clevel = cparams->clevel;
119
3.10k
  schunk->splitmode = cparams->splitmode;
120
3.10k
  schunk->use_dict = (uint8_t)cparams->use_dict;
121
3.10k
  schunk->typesize = cparams->typesize;
122
3.10k
  schunk->blocksize = cparams->blocksize;
123
3.10k
  schunk->chunksize = -1;
124
3.10k
  schunk->flags2 = 0;
125
3.10k
  schunk->tuner_params = cparams->tuner_params;
126
3.10k
  schunk->tuner_id = cparams->tuner_id;
127
3.10k
  if (cparams->tuner_id == BLOSC_BTUNE) {
128
0
    cparams->use_dict = 0;
129
0
  }
130
  /* The compression context */
131
3.10k
  if (schunk->cctx != NULL) {
132
0
    blosc2_free_ctx(schunk->cctx);
133
0
  }
134
3.10k
  cparams->schunk = schunk;
135
3.10k
  schunk->cctx = blosc2_create_cctx(*cparams);
136
3.10k
  if (schunk->cctx == NULL) {
137
0
    BLOSC_TRACE_ERROR("Could not create compression ctx");
138
0
    return BLOSC2_ERROR_NULL_POINTER;
139
0
  }
140
141
  /* The decompression context */
142
3.10k
  if (schunk->dctx != NULL) {
143
0
    blosc2_free_ctx(schunk->dctx);
144
0
  }
145
3.10k
  dparams->schunk = schunk;
146
3.10k
  schunk->dctx = blosc2_create_dctx(*dparams);
147
3.10k
  if (schunk->dctx == NULL) {
148
0
    BLOSC_TRACE_ERROR("Could not create decompression ctx");
149
0
    return BLOSC2_ERROR_NULL_POINTER;
150
0
  }
151
152
3.10k
  return BLOSC2_ERROR_SUCCESS;
153
3.10k
}
154
155
156
0
static bool file_exists (char *filename) {
157
0
  struct stat   buffer;
158
0
  return (stat (filename, &buffer) == 0);
159
0
}
160
161
162
/* Create a new super-chunk */
163
3.10k
blosc2_schunk* blosc2_schunk_new(blosc2_storage *storage) {
164
3.10k
  blosc2_schunk* schunk = calloc(1, sizeof(blosc2_schunk));
165
3.10k
  schunk->version = 0;     /* pre-first version */
166
3.10k
  schunk->view = false;  /* not a view by default */
167
168
  // Get the storage with proper defaults
169
3.10k
  schunk->storage = get_new_storage(storage, &BLOSC2_CPARAMS_DEFAULTS, &BLOSC2_DPARAMS_DEFAULTS, &BLOSC2_IO_DEFAULTS);
170
  // Update the (local variable) storage
171
3.10k
  storage = schunk->storage;
172
173
3.10k
  char* tradeoff = getenv("BTUNE_TRADEOFF");
174
3.10k
  if (tradeoff != NULL) {
175
    // If BTUNE_TRADEOFF passed, automatically use btune
176
0
    storage->cparams->tuner_id = BLOSC_BTUNE;
177
0
  }
178
179
  // ...and update internal properties
180
3.10k
  if (update_schunk_properties(schunk) < 0) {
181
0
    BLOSC_TRACE_ERROR("Error when updating schunk properties");
182
0
    return NULL;
183
0
  }
184
185
3.10k
  if (!storage->contiguous && storage->urlpath != NULL){
186
0
    char* urlpath;
187
0
    char last_char = storage->urlpath[strlen(storage->urlpath) - 1];
188
0
    urlpath = malloc(strlen(storage->urlpath) + 1);
189
0
    strcpy(urlpath, storage->urlpath);
190
0
    if (last_char == '\\' || last_char == '/') {
191
0
      urlpath[strlen(storage->urlpath) - 1] = '\0';
192
0
    }
193
    // Create directory
194
0
    if (mkdir(urlpath, 0777) == -1) {
195
0
      BLOSC_TRACE_ERROR("Error during the creation of the directory, maybe it already exists.");
196
0
      return NULL;
197
0
    }
198
    // We want a sparse (directory) frame as storage
199
0
    blosc2_frame_s* frame = frame_new(urlpath);
200
0
    free(urlpath);
201
0
    if (frame == NULL) {
202
0
      BLOSC_TRACE_ERROR("Error creating sparse frame.");
203
0
      return NULL;
204
0
    }
205
0
    frame->sframe = true;
206
0
    frame_set_locking(frame, schunk->storage->io);
207
    // Initialize frame (basically, encode the header)
208
0
    frame->schunk = schunk;
209
0
    int64_t frame_len = frame_from_schunk(schunk, frame);
210
0
    if (frame_len < 0) {
211
0
      BLOSC_TRACE_ERROR("Error during the conversion of schunk to frame.");
212
0
      return NULL;
213
0
    }
214
0
    schunk->frame = (blosc2_frame*)frame;
215
0
  }
216
3.10k
  if (storage->contiguous){
217
    // We want a contiguous frame as storage
218
0
    if (storage->urlpath != NULL) {
219
0
      if (file_exists(storage->urlpath)) {
220
0
        BLOSC_TRACE_ERROR("You are trying to overwrite an existing frame.  Remove it first!");
221
0
        return NULL;
222
0
      }
223
0
    }
224
0
    blosc2_frame_s* frame = frame_new(storage->urlpath);
225
0
    if (frame == NULL) {
226
0
      BLOSC_TRACE_ERROR("Error creating contiguous frame.");
227
0
      return NULL;
228
0
    }
229
0
    frame->sframe = false;
230
0
    frame_set_locking(frame, schunk->storage->io);
231
    // Initialize frame (basically, encode the header)
232
0
    frame->schunk = schunk;
233
0
    int64_t frame_len = frame_from_schunk(schunk, frame);
234
0
    if (frame_len < 0) {
235
0
      BLOSC_TRACE_ERROR("Error during the conversion of schunk to frame.");
236
0
      return NULL;
237
0
    }
238
0
    schunk->frame = (blosc2_frame*)frame;
239
0
  }
240
241
3.10k
  return schunk;
242
3.10k
}
243
244
245
/* Create a copy of a super-chunk */
246
0
blosc2_schunk* blosc2_schunk_copy(blosc2_schunk *schunk, blosc2_storage *storage) {
247
0
  if (schunk == NULL) {
248
0
    BLOSC_TRACE_ERROR("Can not copy a NULL `schunk`.");
249
0
    return NULL;
250
0
  }
251
252
  // Check if cparams are equals
253
0
  bool cparams_equal = true;
254
0
  blosc2_cparams cparams = {0};
255
0
  if (storage->cparams == NULL) {
256
    // When cparams are not specified, just use the same of schunk
257
0
    cparams.typesize = schunk->cctx->typesize;
258
0
    cparams.clevel = schunk->cctx->clevel;
259
0
    cparams.compcode = schunk->cctx->compcode;
260
0
    cparams.compcode_meta = schunk->cctx->compcode_meta;
261
0
    cparams.splitmode = schunk->cctx->splitmode;
262
0
    cparams.use_dict = schunk->cctx->use_dict;
263
0
    cparams.blocksize = schunk->cctx->blocksize;
264
0
    memcpy(cparams.filters, schunk->cctx->filters, BLOSC2_MAX_FILTERS);
265
0
    memcpy(cparams.filters_meta, schunk->cctx->filters_meta, BLOSC2_MAX_FILTERS);
266
0
    storage->cparams = &cparams;
267
0
  }
268
0
  else {
269
0
    cparams = *storage->cparams;
270
0
  }
271
0
  if (cparams.blocksize == 0) {
272
    // TODO: blocksize should be read from schunk->blocksize
273
    // For this, it should be updated during the first append
274
    // (or change API to make this a property during schunk creation).
275
0
    cparams.blocksize = schunk->cctx->blocksize;
276
0
  }
277
278
0
  if (cparams.typesize != schunk->cctx->typesize ||
279
0
      cparams.clevel != schunk->cctx->clevel ||
280
0
      cparams.compcode != schunk->cctx->compcode ||
281
0
      cparams.use_dict != schunk->cctx->use_dict ||
282
0
      cparams.blocksize != schunk->cctx->blocksize ||
283
      // In case of prefilters or postfilters, force their execution.
284
0
      schunk->cctx->prefilter != NULL ||
285
0
      schunk->dctx->postfilter != NULL) {
286
0
    cparams_equal = false;
287
0
  }
288
0
  for (int i = 0; i < BLOSC2_MAX_FILTERS; ++i) {
289
0
    if (cparams.filters[i] != schunk->cctx->filters[i] ||
290
0
        cparams.filters_meta[i] != schunk->cctx->filters_meta[i]) {
291
0
      cparams_equal = false;
292
0
    }
293
0
  }
294
295
  // Create new schunk
296
0
  blosc2_schunk *new_schunk = blosc2_schunk_new(storage);
297
0
  if (new_schunk == NULL) {
298
0
    BLOSC_TRACE_ERROR("Can not create a new schunk");
299
0
    return NULL;
300
0
  }
301
  // Set the chunksize for the schunk, as it cannot be derived from storage
302
0
  new_schunk->chunksize = schunk->chunksize;
303
0
  new_schunk->flags2 = schunk->flags2;
304
305
  // Copy metalayers
306
0
  for (int nmeta = 0; nmeta < schunk->nmetalayers; ++nmeta) {
307
0
    blosc2_metalayer *meta = schunk->metalayers[nmeta];
308
0
    if (blosc2_meta_add(new_schunk, meta->name, meta->content, meta->content_len) < 0) {
309
0
      BLOSC_TRACE_ERROR("Can not add %s `metalayer`.", meta->name);
310
0
      return NULL;
311
0
    }
312
0
  }
313
314
  // Copy chunks
315
0
  bool uses_vlblocks = (schunk->flags2 & BLOSC2_VL_BLOCKS) != 0;
316
317
0
  if (cparams_equal || uses_vlblocks) {
318
0
    for (int nchunk = 0; nchunk < schunk->nchunks; ++nchunk) {
319
0
      uint8_t *chunk;
320
0
      bool needs_free;
321
0
      int rc = blosc2_schunk_get_chunk(schunk, nchunk, &chunk, &needs_free);
322
0
      if (rc < 0) {
323
0
        BLOSC_TRACE_ERROR("Can not get the `chunk` %d.", nchunk);
324
0
        return NULL;
325
0
      }
326
0
      rc = blosc2_schunk_append_chunk(new_schunk, chunk, !needs_free);
327
0
      if (rc < 0) {
328
0
        BLOSC_TRACE_ERROR("Can not append the `chunk` into super-chunk.");
329
0
        return NULL;
330
0
      }
331
0
    }
332
0
  } else {
333
0
    int32_t chunksize = schunk->chunksize == -1 ? 0 : schunk->chunksize;
334
0
    uint8_t *buffer = malloc(chunksize);
335
0
    for (int nchunk = 0; nchunk < schunk->nchunks; ++nchunk) {
336
0
      if (blosc2_schunk_decompress_chunk(schunk, nchunk, buffer, schunk->chunksize) < 0) {
337
0
        BLOSC_TRACE_ERROR("Can not decompress the `chunk` %d.", nchunk);
338
0
        return NULL;
339
0
      }
340
0
      if (blosc2_schunk_append_buffer(new_schunk, buffer, schunk->chunksize) < 0) {
341
0
        BLOSC_TRACE_ERROR("Can not append the `buffer` into super-chunk.");
342
0
        return NULL;
343
0
      }
344
0
    }
345
0
    free(buffer);
346
0
  }
347
348
  // Copy vlmetalayers
349
0
  for (int nmeta = 0; nmeta < schunk->nvlmetalayers; ++nmeta) {
350
0
    uint8_t *content = NULL;
351
0
    int32_t content_len;
352
0
    char* name = schunk->vlmetalayers[nmeta]->name;
353
0
    if (blosc2_vlmeta_get(schunk, name, &content, &content_len) < 0) {
354
      // Passing the (previously uninitialized) content pointer forward ended
355
      // in a bogus free that aborted the process; bail out instead.
356
0
      BLOSC_TRACE_ERROR("Can not get %s `vlmetalayer`.", name);
357
0
      return NULL;
358
0
    }
359
0
    if (blosc2_vlmeta_add(new_schunk, name, content, content_len, NULL) < 0) {
360
0
      BLOSC_TRACE_ERROR("Can not add %s `vlmetalayer`.", name);
361
0
      free(content);
362
0
      return NULL;
363
0
    }
364
0
    free(content);
365
0
  }
366
0
  return new_schunk;
367
0
}
368
369
370
/* Open an existing super-chunk that is on-disk (no copy is made). */
371
0
blosc2_schunk* blosc2_schunk_open_udio(const char* urlpath, const blosc2_io *udio) {
372
0
  return blosc2_schunk_open_offset_udio(urlpath, 0, udio);
373
0
}
374
375
// frame_from_file_offset()'s bootstrap read (path stat + header + trailer)
376
// happens before any lock is taken, so it can race a concurrent writer
377
// growing or rewriting the frame (e.g. a stat() size snapshot made stale by
378
// a header already advertising the writer's new, larger frame length) and
379
// fail outright instead of returning a frame to refresh under lock.  Under
380
// the locking contract such a failure is expected to be transient, so
381
// retry a bounded number of times with a short backoff before giving up;
382
// without locking requested this is the pre-existing single-attempt
383
// behavior (a concurrent writer without locking is already out of the SWMR
384
// contract).  Used for every bootstrap/re-read of the frame header in
385
// blosc2_schunk_open_offset_udio() below -- a compressed-size change on an
386
// in-place update can rewrite the frame layout just like growth does, so
387
// this race is not limited to append/resize.
388
static blosc2_frame_s* frame_from_file_offset_retrying(
389
0
    const char* urlpath, const blosc2_io *udio, int64_t offset, int max_attempts) {
390
0
  blosc2_frame_s* frame = NULL;
391
0
  for (int attempt = 0; attempt < max_attempts; attempt++) {
392
0
    frame = frame_from_file_offset(urlpath, udio, offset);
393
0
    if (frame != NULL || attempt + 1 == max_attempts) {
394
0
      break;
395
0
    }
396
#if defined(_WIN32)
397
    Sleep(1);
398
#else
399
0
    usleep(1000);
400
0
#endif
401
0
  }
402
0
  return frame;
403
0
}
404
405
0
blosc2_schunk* blosc2_schunk_open_offset_udio(const char* urlpath, int64_t offset, const blosc2_io *udio) {
406
0
  if (urlpath == NULL) {
407
0
    BLOSC_TRACE_ERROR("You need to supply a urlpath.");
408
0
    return NULL;
409
0
  }
410
411
0
  bool retry_on_race = frame_locking_requested(udio);
412
0
  const int max_attempts = retry_on_race ? 50 : 1;
413
0
  blosc2_frame_s* frame = frame_from_file_offset_retrying(urlpath, udio, offset, max_attempts);
414
0
  if (frame == NULL) {
415
0
    blosc2_io_cb *io_cb = blosc2_get_io_cb(udio->id);
416
0
    if (io_cb == NULL) {
417
0
        BLOSC_TRACE_ERROR("Error getting the input/output API");
418
0
        return NULL;
419
0
    }
420
0
    int rc = io_cb->destroy(udio->params);
421
0
    if (rc < 0) {
422
0
      BLOSC_TRACE_ERROR("Cannot destroy the input/output object.");
423
0
    }
424
0
    return NULL;
425
0
  }
426
  // Guard the initial read of the frame against concurrent writers
427
0
  if (frame_lock(frame, false) < 0) {
428
0
    frame_free(frame);
429
0
    return NULL;
430
0
  }
431
0
  if (frame->force_refresh) {
432
    // The bootstrap read in frame_from_file_offset() happened before the lock
433
    // acquisition above, so a writer may have mutated the frame in between
434
    // (the generation counter cannot tell a first acquisition from a raced
435
    // one, so this also triggers on every open of a previously-mutated frame;
436
    // one extra header read at open time is a fair price).  Re-read the frame
437
    // now, taking the fresh handle's own shared lock *before* releasing the
438
    // current one so that no writer can interleave.  Same transient race as
439
    // the bootstrap read above, so the same bounded retry applies.
440
0
    blosc2_frame_s* fresh = frame_from_file_offset_retrying(urlpath, udio, offset, max_attempts);
441
0
    int lock_rc = (fresh == NULL) ? BLOSC2_ERROR_FILE_READ : frame_lock(fresh, false);
442
0
    frame_free(frame);  // closing its lock fd releases the old shared lock
443
0
    if (fresh == NULL) {
444
0
      return NULL;
445
0
    }
446
0
    if (lock_rc < 0) {
447
0
      frame_free(fresh);
448
0
      return NULL;
449
0
    }
450
    // The re-read ran under a continuously-held shared lock, so it is
451
    // current: the seq mismatch of a first acquisition is not a mutation
452
0
    fresh->force_refresh = false;
453
0
    frame = fresh;
454
0
  }
455
0
  blosc2_schunk* schunk = frame_to_schunk(frame, false, udio);
456
0
  if (schunk == NULL) {
457
0
    BLOSC_TRACE_ERROR("Error converting frame to super-chunk");
458
    // frame_to_schunk() has already freed the frame on failure; closing its
459
    // lock fd released the OS lock, so no frame_unlock() here
460
0
    return NULL;
461
0
  }
462
0
  frame_unlock(frame);
463
464
  // Set the storage with proper defaults
465
0
  size_t pathlen = strlen(urlpath);
466
0
  schunk->storage->urlpath = malloc(pathlen + 1);
467
0
  strcpy(schunk->storage->urlpath, urlpath);
468
0
  schunk->storage->contiguous = !frame->sframe;
469
470
0
  return schunk;
471
0
}
472
473
0
blosc2_schunk* blosc2_schunk_open(const char* urlpath) {
474
0
  return blosc2_schunk_open_udio(urlpath, &BLOSC2_IO_DEFAULTS);
475
0
}
476
477
0
blosc2_schunk* blosc2_schunk_open_offset(const char* urlpath, int64_t offset) {
478
0
  return blosc2_schunk_open_offset_udio(urlpath, offset, &BLOSC2_IO_DEFAULTS);
479
0
}
480
481
0
int64_t blosc2_schunk_to_buffer(blosc2_schunk* schunk, uint8_t** dest, bool* needs_free) {
482
0
  blosc2_frame_s* frame;
483
0
  int64_t cframe_len;
484
485
  // Initialize defaults in case of errors
486
0
  *dest = NULL;
487
0
  *needs_free = false;
488
489
0
  if ((schunk->storage->contiguous == true) && (schunk->storage->urlpath == NULL)) {
490
0
    frame =  (blosc2_frame_s*)(schunk->frame);
491
0
    *dest = frame->cframe;
492
0
    cframe_len = frame->len;
493
0
    *needs_free = false;
494
0
  }
495
0
  else {
496
    // Copy to a contiguous storage
497
0
    blosc2_storage frame_storage = {.contiguous=true};
498
0
    blosc2_schunk* schunk_copy = blosc2_schunk_copy(schunk, &frame_storage);
499
0
    if (schunk_copy == NULL) {
500
0
      BLOSC_TRACE_ERROR("Error during the conversion of schunk to buffer.");
501
0
      return BLOSC2_ERROR_SCHUNK_COPY;
502
0
    }
503
0
    frame = (blosc2_frame_s*)(schunk_copy->frame);
504
0
    *dest = frame->cframe;
505
0
    cframe_len = frame->len;
506
0
    *needs_free = true;
507
0
    frame->avoid_cframe_free = true;
508
0
    blosc2_schunk_free(schunk_copy);
509
0
  }
510
511
0
  return cframe_len;
512
513
0
}
514
515
516
/* Write an in-memory frame out to a file. */
517
0
int64_t frame_to_file(blosc2_frame_s* frame, const char* urlpath) {
518
0
  blosc2_io_cb *io_cb = blosc2_get_io_cb(frame->schunk->storage->io->id);
519
0
  if (io_cb == NULL) {
520
0
    BLOSC_TRACE_ERROR("Error getting the input/output API");
521
0
    return BLOSC2_ERROR_PLUGIN_IO;
522
0
  }
523
0
  void* fp = io_cb->open(urlpath, "wb", frame->schunk->storage->io->params);
524
0
  if (fp == NULL) {
525
0
    BLOSC_TRACE_ERROR("Cannot open %s for writing.", urlpath);
526
0
    return BLOSC2_ERROR_FILE_OPEN;
527
0
  }
528
0
  int64_t io_pos = 0;
529
0
  int64_t nitems = io_cb->write(frame->cframe, frame->len, 1, io_pos, fp);
530
0
  io_cb->close(fp);
531
0
  if (nitems != 1) {
532
0
    BLOSC_TRACE_ERROR("Cannot write the frame to %s.", urlpath);
533
0
    return BLOSC2_ERROR_FILE_WRITE;
534
0
  }
535
0
  return nitems * frame->len;
536
0
}
537
538
539
/* Append an in-memory frame to a file. */
540
0
int64_t append_frame_to_file(blosc2_frame_s* frame, const char* urlpath) {
541
0
    blosc2_io_cb *io_cb = blosc2_get_io_cb(frame->schunk->storage->io->id);
542
0
    if (io_cb == NULL) {
543
0
        BLOSC_TRACE_ERROR("Error getting the input/output API");
544
0
        return BLOSC2_ERROR_PLUGIN_IO;
545
0
    }
546
    /* "rb+" rather than "ab": the write below passes an explicit position, and
547
       POSIX ignores it on an O_APPEND descriptor (every write lands at EOF)
548
       while Windows honours it.  Without O_APPEND the position means the same
549
       thing on both.  "rb+" will not create, so create the file first when it
550
       is not there yet -- which is what "ab" used to cover.  Creating with "ab"
551
       rather than "wb+": should the "rb+" below have failed for some reason
552
       other than a missing file, "ab" leaves its contents alone where "wb+"
553
       would truncate them away.
554
555
       Note this gives up O_APPEND's atomic land-at-EOF: two processes appending
556
       to the same file concurrently can now write at the same offset.  They
557
       could not use the result before either -- the offset returned here comes
558
       from a size() that the other process invalidates just the same -- and
559
       Windows never had the atomicity to begin with, since it honours the
560
       explicit position even on an append handle. */
561
0
    void* fp = io_cb->open(urlpath, "rb+", frame->schunk->storage->io->params);
562
0
    if (fp == NULL) {
563
0
        fp = io_cb->open(urlpath, "ab", frame->schunk->storage->io->params);
564
0
        if (fp != NULL) {
565
0
            io_cb->close(fp);
566
0
            fp = io_cb->open(urlpath, "rb+", frame->schunk->storage->io->params);
567
0
        }
568
0
    }
569
0
    if (fp == NULL) {
570
0
        BLOSC_TRACE_ERROR("Cannot open %s for appending.", urlpath);
571
0
        return BLOSC2_ERROR_FILE_OPEN;
572
0
    }
573
574
0
    int64_t io_pos = io_cb->size(fp);
575
0
    if (io_pos < 0) {
576
0
        io_cb->close(fp);
577
0
        BLOSC_TRACE_ERROR("Cannot determine the size of %s.", urlpath);
578
0
        return BLOSC2_ERROR_FILE_READ;
579
0
    }
580
0
    int64_t nitems = io_cb->write(frame->cframe, frame->len, 1, io_pos, fp);
581
0
    io_cb->close(fp);
582
0
    if (nitems != 1) {
583
0
        BLOSC_TRACE_ERROR("Cannot append the frame to %s.", urlpath);
584
0
        return BLOSC2_ERROR_FILE_WRITE;
585
0
    }
586
0
    return io_pos;
587
0
}
588
589
590
/* Write super-chunk out to a file. */
591
0
int64_t blosc2_schunk_to_file(blosc2_schunk* schunk, const char* urlpath) {
592
0
  if (urlpath == NULL) {
593
0
    BLOSC_TRACE_ERROR("urlpath cannot be NULL");
594
0
    return BLOSC2_ERROR_INVALID_PARAM;
595
0
  }
596
597
  // Accelerated path for in-memory frames
598
0
  if (schunk->storage->contiguous && schunk->storage->urlpath == NULL) {
599
0
    int64_t len = frame_to_file((blosc2_frame_s*)(schunk->frame), urlpath);
600
0
    if (len <= 0) {
601
0
      BLOSC_TRACE_ERROR("Error writing to file");
602
0
      return len;
603
0
    }
604
0
    return len;
605
0
  }
606
607
  // Copy to a contiguous file
608
0
  blosc2_storage frame_storage = {.contiguous=true, .urlpath=(char*)urlpath};
609
0
  blosc2_schunk* schunk_copy = blosc2_schunk_copy(schunk, &frame_storage);
610
0
  if (schunk_copy == NULL) {
611
0
    BLOSC_TRACE_ERROR("Error during the conversion of schunk to buffer.");
612
0
    return BLOSC2_ERROR_SCHUNK_COPY;
613
0
  }
614
0
  blosc2_frame_s* frame = (blosc2_frame_s*)(schunk_copy->frame);
615
0
  int64_t frame_len = frame->len;
616
0
  blosc2_schunk_free(schunk_copy);
617
0
  return frame_len;
618
0
}
619
620
621
/* Append a super-chunk to a file. */
622
0
int64_t blosc2_schunk_append_file(blosc2_schunk* schunk, const char* urlpath) {
623
0
    if (urlpath == NULL) {
624
0
        BLOSC_TRACE_ERROR("urlpath cannot be NULL");
625
0
        return BLOSC2_ERROR_INVALID_PARAM;
626
0
    }
627
628
    // Accelerated path for in-memory frames
629
0
    if (schunk->storage->contiguous && schunk->storage->urlpath == NULL) {
630
0
        int64_t offset = append_frame_to_file((blosc2_frame_s*)(schunk->frame), urlpath);
631
0
        if (offset <= 0) {
632
0
            BLOSC_TRACE_ERROR("Error writing to file");
633
0
            return offset;
634
0
        }
635
0
        return offset;
636
0
    }
637
638
    // Copy to a contiguous file
639
0
    blosc2_storage frame_storage = {.contiguous=true, .urlpath=NULL};
640
0
    blosc2_schunk* schunk_copy = blosc2_schunk_copy(schunk, &frame_storage);
641
0
    if (schunk_copy == NULL) {
642
0
        BLOSC_TRACE_ERROR("Error during the conversion of schunk to buffer.");
643
0
        return BLOSC2_ERROR_SCHUNK_COPY;
644
0
    }
645
0
    blosc2_frame_s* frame = (blosc2_frame_s*)(schunk_copy->frame);
646
0
    int64_t offset = append_frame_to_file(frame, urlpath);
647
0
    blosc2_schunk_free(schunk_copy);
648
0
    return offset;
649
0
}
650
651
652
/* Free all memory from a super-chunk. */
653
12.3k
void schunk_free_metalayers(blosc2_schunk *schunk) {
654
12.3k
  for (int i = 0; i < schunk->nmetalayers; i++) {
655
16
    if (schunk->metalayers[i] != NULL) {
656
6
      free(schunk->metalayers[i]->name);
657
6
      free(schunk->metalayers[i]->content);
658
6
      free(schunk->metalayers[i]);
659
6
      schunk->metalayers[i] = NULL;
660
6
    }
661
16
  }
662
12.3k
  schunk->nmetalayers = 0;
663
12.3k
}
664
665
666
12.3k
void schunk_free_vlmetalayers(blosc2_schunk *schunk) {
667
43.3k
  for (int i = 0; i < schunk->nvlmetalayers; i++) {
668
30.9k
    if (schunk->vlmetalayers[i] != NULL) {
669
333
      free(schunk->vlmetalayers[i]->name);
670
333
      free(schunk->vlmetalayers[i]->content);
671
333
      free(schunk->vlmetalayers[i]);
672
333
      schunk->vlmetalayers[i] = NULL;
673
333
    }
674
30.9k
  }
675
12.3k
  schunk->nvlmetalayers = 0;
676
12.3k
}
677
678
679
12.3k
int blosc2_schunk_free(blosc2_schunk *schunk) {
680
12.3k
  int err = 0;
681
682
  // If it is a view, the data belongs to original array and should not be freed
683
12.3k
  if (schunk->data != NULL && !schunk->view) {
684
52.8k
    for (int i = 0; i < schunk->nchunks; i++) {
685
49.7k
      free(schunk->data[i]);
686
49.7k
    }
687
3.09k
    free(schunk->data);
688
3.09k
  }
689
12.3k
  if (schunk->cctx != NULL)
690
12.3k
    blosc2_free_ctx(schunk->cctx);
691
12.3k
  if (schunk->dctx != NULL)
692
12.3k
    blosc2_free_ctx(schunk->dctx);
693
12.3k
  if (schunk->blockshape != NULL)
694
0
    free(schunk->blockshape);
695
696
12.3k
  schunk_free_metalayers(schunk);
697
698
12.3k
  if (schunk->storage != NULL) {
699
12.3k
    blosc2_io_cb *io_cb = blosc2_get_io_cb(schunk->storage->io->id);
700
12.3k
    if (io_cb != NULL) {
701
12.3k
      int rc = io_cb->destroy(schunk->storage->io->params);
702
12.3k
      if (rc < 0) {
703
0
        BLOSC_TRACE_ERROR("Could not free the I/O resources.");
704
0
        err = 1;
705
0
      }
706
12.3k
    }
707
708
12.3k
    if (schunk->storage->urlpath != NULL) {
709
0
      free(schunk->storage->urlpath);
710
0
    }
711
12.3k
    free(schunk->storage->cparams);
712
12.3k
    free(schunk->storage->dparams);
713
12.3k
    free(schunk->storage->io);
714
12.3k
    free(schunk->storage);
715
12.3k
  }
716
717
  // If it is a view, the frame belongs to original array and should not be freed
718
12.3k
  if (schunk->frame != NULL && !schunk->view) {
719
9.27k
    frame_free((blosc2_frame_s *) schunk->frame);
720
9.27k
  }
721
722
12.3k
  schunk_free_vlmetalayers(schunk);
723
724
12.3k
  free(schunk);
725
726
12.3k
  return err;
727
12.3k
}
728
729
730
/* Create a super-chunk out of a contiguous frame buffer */
731
9.34k
blosc2_schunk* blosc2_schunk_from_buffer(uint8_t *cframe, int64_t len, bool copy) {
732
  // Check that the buffer actually comes from a cframe
733
9.34k
  if (cframe == NULL || len < FRAME_HEADER_MINLEN) {
734
0
    return NULL;
735
0
  }
736
9.34k
  char *magic_number = (char *)cframe + FRAME_HEADER_MAGIC;
737
9.34k
  if (memcmp(magic_number, "b2frame", sizeof("b2frame")) != 0) {
738
52
    return NULL;
739
52
  }
740
9.29k
  blosc2_frame_s* frame = frame_from_cframe(cframe, len, false);
741
9.29k
  if (frame == NULL) {
742
11
    return NULL;
743
11
  }
744
9.27k
  blosc2_schunk* schunk = frame_to_schunk(frame, copy, &BLOSC2_IO_DEFAULTS);
745
9.27k
  if (schunk && copy) {
746
    // Super-chunk has its own copy of frame
747
0
    frame_free(frame);
748
0
  }
749
9.27k
  return schunk;
750
9.29k
}
751
752
753
/* Set whether freeing this super-chunk should avoid freeing its contiguous frame buffer */
754
0
void blosc2_schunk_avoid_cframe_free(blosc2_schunk *schunk, bool avoid_cframe_free) {
755
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
756
0
  if (frame) {
757
0
    frame_avoid_cframe_free(frame, avoid_cframe_free);
758
0
  }
759
0
}
760
761
762
/* Hold the exclusive frame lock across several operations: the operations
763
   between blosc2_schunk_lock() and blosc2_schunk_unlock() nest on the
764
   already-held lock (via the frame's lock depth counter) instead of
765
   re-acquiring it, so the whole bracket is atomic against other handles.
766
   No-ops (success) when locking is not enabled on the handle. */
767
0
int blosc2_schunk_lock(blosc2_schunk *schunk) {
768
0
  return frame_lock((blosc2_frame_s*)schunk->frame, true);
769
0
}
770
771
772
0
int blosc2_schunk_unlock(blosc2_schunk *schunk) {
773
0
  return frame_unlock((blosc2_frame_s*)schunk->frame);
774
0
}
775
776
777
/* Re-sync the cached counters (nchunks, nbytes, cbytes) and metalayers of a
778
   disk-based super-chunk when another handle changed it behind our back.
779
   A no-op for in-memory super-chunks. */
780
0
int blosc2_schunk_refresh(blosc2_schunk *schunk) {
781
0
  if (schunk == NULL) {
782
0
    return BLOSC2_ERROR_NULL_POINTER;
783
0
  }
784
0
  if (schunk->frame == NULL) {
785
0
    return 0;
786
0
  }
787
0
  return frame_check_stale((blosc2_frame_s *) schunk->frame);
788
0
}
789
790
791
/* Fill an empty frame with special values (fast path). */
792
int64_t blosc2_schunk_fill_special(blosc2_schunk* schunk, int64_t nitems, int special_value,
793
0
                               int32_t chunksize) {
794
0
  if (nitems == 0) {
795
0
    return 0;
796
0
  }
797
798
0
  int32_t typesize = schunk->typesize;
799
0
  if (nitems < 0 || chunksize <= 0 || typesize <= 0) {
800
0
    BLOSC_TRACE_ERROR("Invalid special fill parameters.");
801
0
    return BLOSC2_ERROR_INVALID_PARAM;
802
0
  }
803
804
0
  if ((schunk->nbytes > 0) || (schunk->cbytes > 0)) {
805
0
    BLOSC_TRACE_ERROR("Filling with special values only works on empty super-chunks");
806
0
    return BLOSC2_ERROR_FRAME_SPECIAL;
807
0
  }
808
809
  // Compute the number of chunks and the length of the offsets chunk
810
0
  int32_t chunkitems = chunksize / typesize;
811
0
  if (chunkitems <= 0) {
812
0
    BLOSC_TRACE_ERROR("chunksize must be >= typesize for special fill.");
813
0
    return BLOSC2_ERROR_INVALID_PARAM;
814
0
  }
815
0
  int64_t nchunks = nitems / chunkitems;
816
0
  if (nchunks > INT_MAX) {
817
0
    BLOSC_TRACE_ERROR("nitems is too large.  Try increasing the chunksize.");
818
0
    return BLOSC2_ERROR_SCHUNK_SPECIAL;
819
0
  }
820
0
  if (nitems > INT64_MAX / typesize) {
821
0
    BLOSC_TRACE_ERROR("nitems is too large for nbytes accounting.");
822
0
    return BLOSC2_ERROR_SCHUNK_SPECIAL;
823
0
  }
824
0
  int32_t leftover_items = (int32_t)(nitems % chunkitems);
825
826
0
  if (schunk->frame == NULL) {
827
    // Build the special chunks
828
0
    int32_t leftover_size = leftover_items * typesize;
829
0
    void* chunk = malloc(BLOSC_EXTENDED_HEADER_LENGTH);
830
0
    void* chunk2 = malloc(BLOSC_EXTENDED_HEADER_LENGTH);
831
0
    blosc2_cparams* cparams;
832
0
    blosc2_schunk_get_cparams(schunk, &cparams);
833
0
    int csize, csize2;
834
0
    switch (special_value) {
835
0
      case BLOSC2_SPECIAL_ZERO:
836
0
        csize = blosc2_chunk_zeros(*cparams, chunksize, chunk, BLOSC_EXTENDED_HEADER_LENGTH);
837
0
        csize2 = blosc2_chunk_zeros(*cparams, leftover_size, chunk2, BLOSC_EXTENDED_HEADER_LENGTH);
838
0
        break;
839
0
      case BLOSC2_SPECIAL_UNINIT:
840
0
        csize = blosc2_chunk_uninit(*cparams, chunksize, chunk, BLOSC_EXTENDED_HEADER_LENGTH);
841
0
        csize2 = blosc2_chunk_uninit(*cparams, leftover_size, chunk2, BLOSC_EXTENDED_HEADER_LENGTH);
842
0
        break;
843
0
      case BLOSC2_SPECIAL_NAN:
844
0
        csize = blosc2_chunk_nans(*cparams, chunksize, chunk, BLOSC_EXTENDED_HEADER_LENGTH);
845
0
        csize2 = blosc2_chunk_nans(*cparams, leftover_size, chunk2, BLOSC_EXTENDED_HEADER_LENGTH);
846
0
        break;
847
0
      default:
848
0
        BLOSC_TRACE_ERROR("Only zeros, NaNs or non-initialized values are supported.");
849
0
        return BLOSC2_ERROR_SCHUNK_SPECIAL;
850
0
    }
851
0
    free(cparams);
852
0
    if (csize < 0 || csize2 < 0) {
853
0
      BLOSC_TRACE_ERROR("Error creating special chunks.");
854
0
      return BLOSC2_ERROR_SCHUNK_SPECIAL;
855
0
    }
856
857
0
    for (int nchunk = 0; nchunk < nchunks; nchunk++) {
858
0
      int64_t nchunk_ = blosc2_schunk_append_chunk(schunk, chunk, true);
859
0
      if (nchunk_ != nchunk + 1) {
860
0
        BLOSC_TRACE_ERROR("Error appending special chunks.");
861
0
        return BLOSC2_ERROR_SCHUNK_SPECIAL;
862
0
      }
863
0
    }
864
865
0
    if (leftover_items) {
866
0
      int64_t nchunk_ = blosc2_schunk_append_chunk(schunk, chunk2, true);
867
0
      if (nchunk_ != nchunks + 1) {
868
0
        BLOSC_TRACE_ERROR("Error appending last special chunk.");
869
0
        return BLOSC2_ERROR_SCHUNK_SPECIAL;
870
0
      }
871
0
    }
872
0
    free(chunk);
873
0
    free(chunk2);
874
0
  }
875
0
  else {
876
    /* Fill an empty frame with special values (fast path). */
877
0
    blosc2_frame_s *frame = (blosc2_frame_s *) schunk->frame;
878
0
    int64_t total_chunks = nchunks + (leftover_items ? 1 : 0);
879
0
    if (!blosc2_nchunks_to_offsets_nbytes(total_chunks, NULL)) {
880
0
      BLOSC_TRACE_ERROR("Too many chunks for frame offsets representation.");
881
0
      return BLOSC2_ERROR_FRAME_SPECIAL;
882
0
    }
883
884
0
    int64_t old_nchunks = schunk->nchunks;
885
0
    int64_t old_nbytes = schunk->nbytes;
886
0
    int32_t old_chunksize = schunk->chunksize;
887
888
    /* Update counters (necessary for the frame_fill_special() logic) */
889
0
    if (leftover_items) {
890
0
      nchunks += 1;
891
0
    }
892
0
    schunk->chunksize = chunksize;
893
0
    schunk->nchunks = nchunks;
894
0
    schunk->nbytes = nitems * typesize;
895
0
    int rc = frame_lock(frame, true);
896
0
    if (rc < 0) {
897
0
      schunk->chunksize = old_chunksize;
898
0
      schunk->nchunks = old_nchunks;
899
0
      schunk->nbytes = old_nbytes;
900
0
      return rc;
901
0
    }
902
0
    int64_t frame_len = frame_fill_special(frame, nitems, special_value, chunksize, schunk);
903
0
    frame_unlock(frame);
904
0
    if (frame_len < 0) {
905
0
      schunk->chunksize = old_chunksize;
906
0
      schunk->nchunks = old_nchunks;
907
0
      schunk->nbytes = old_nbytes;
908
0
      BLOSC_TRACE_ERROR("Error creating special frame.");
909
0
      return frame_len;
910
0
    }
911
0
  }
912
913
0
  return schunk->nchunks;
914
0
}
915
916
46.6k
static int schunk_get_chunk_nbytes(blosc2_schunk *schunk, int64_t nchunk, int32_t *chunk_nbytes) {
917
46.6k
  int rc;
918
46.6k
  if (schunk->frame == NULL) {
919
46.6k
    return blosc2_cbuffer_sizes(schunk->data[nchunk], chunk_nbytes, NULL, NULL);
920
46.6k
  }
921
922
46.6k
  bool needs_free;
923
0
  uint8_t *chunk;
924
0
  rc = frame_get_lazychunk((blosc2_frame_s *)schunk->frame, nchunk, &chunk, &needs_free);
925
0
  if (rc < 0) {
926
0
    return rc;
927
0
  }
928
0
  rc = blosc2_cbuffer_sizes(chunk, chunk_nbytes, NULL, NULL);
929
0
  if (needs_free) {
930
0
    free(chunk);
931
0
  }
932
0
  return rc;
933
0
}
934
935
/* The flags2 byte lives in the extended header, at offset 0x1e, so it only
936
   exists when the chunk actually carries one.  Whether it does is recorded in
937
   the flags byte, the same way read_chunk_header() decides it -- not in the
938
   chunk size: a Blosc1-style chunk with 16 or more bytes of payload is 32 bytes
939
   or longer, yet offset 0x1e is compressed data there, and reading it as flags2
940
   can mark a schunk as using variable-length blocks out of thin air.  The size
941
   check stays as the bound that keeps a short chunk from being over-read. */
942
96.3k
static uint8_t get_chunk_flags2(const uint8_t *chunk, int32_t chunk_cbytes) {
943
96.3k
  uint8_t flags = chunk[BLOSC2_CHUNK_FLAGS];
944
96.3k
  bool extended_header = (flags & BLOSC_DOSHUFFLE) && (flags & BLOSC_DOBITSHUFFLE);
945
96.3k
  if (!extended_header || chunk_cbytes < BLOSC_EXTENDED_HEADER_LENGTH) {
946
0
    return 0;
947
0
  }
948
96.3k
  return chunk[BLOSC2_CHUNK_BLOSC2_FLAGS2];
949
96.3k
}
950
951
46.6k
static int schunk_get_chunk_flags2(blosc2_schunk *schunk, int64_t nchunk, uint8_t *chunk_flags2) {
952
46.6k
  if (schunk->frame == NULL) {
953
46.6k
    int32_t chunk_cbytes;
954
46.6k
    int rc = blosc2_cbuffer_sizes(schunk->data[nchunk], NULL, &chunk_cbytes, NULL);
955
46.6k
    if (rc < 0) {
956
0
      return rc;
957
0
    }
958
46.6k
    *chunk_flags2 = get_chunk_flags2(schunk->data[nchunk], chunk_cbytes);
959
46.6k
    return 0;
960
46.6k
  }
961
962
46.6k
  bool needs_free;
963
0
  uint8_t *chunk;
964
0
  int rc = frame_get_chunk((blosc2_frame_s *)schunk->frame, nchunk, &chunk, &needs_free);
965
0
  if (rc < 0) {
966
0
    return rc;
967
0
  }
968
0
  *chunk_flags2 = get_chunk_flags2(chunk, rc);
969
0
  if (needs_free) {
970
0
    free(chunk);
971
0
  }
972
0
  return 0;
973
0
}
974
975
/* Append an existing chunk into a super-chunk. */
976
49.7k
static int64_t schunk_append_chunk_unlocked(blosc2_schunk *schunk, uint8_t *chunk, bool copy) {
977
49.7k
  int32_t chunk_nbytes;
978
49.7k
  int32_t chunk_cbytes;
979
49.7k
  int64_t nchunks = schunk->nchunks;
980
981
49.7k
  int rc = blosc2_cbuffer_sizes(chunk, &chunk_nbytes, &chunk_cbytes, NULL);
982
49.7k
  if (rc < 0) {
983
0
    return rc;
984
0
  }
985
49.7k
  uint8_t flags2 = get_chunk_flags2(chunk, chunk_cbytes);
986
49.7k
  bool chunk_vlblocks = (flags2 & BLOSC2_VL_BLOCKS) != 0;
987
49.7k
  if (nchunks > 0) {
988
46.6k
    uint8_t first_flags2;
989
46.6k
    rc = schunk_get_chunk_flags2(schunk, 0, &first_flags2);
990
46.6k
    if (rc < 0) {
991
0
      return rc;
992
0
    }
993
46.6k
    if (((first_flags2 & BLOSC2_VL_BLOCKS) != 0) != chunk_vlblocks) {
994
0
      BLOSC_TRACE_ERROR("schunks cannot mix regular chunks and VL-block chunks.");
995
0
      return BLOSC2_ERROR_CHUNK_APPEND;
996
0
    }
997
46.6k
  }
998
3.09k
  else {
999
3.09k
    schunk->flags2 = flags2;
1000
3.09k
  }
1001
1002
49.7k
  int32_t chunksize = schunk->chunksize;
1003
49.7k
  bool variable_chunksize = (chunksize == 0);
1004
49.7k
  if (chunksize == -1) {
1005
3.09k
    schunk->chunksize = chunk_nbytes;  // The super-chunk is initialized now
1006
3.09k
    chunksize = schunk->chunksize;
1007
3.09k
  }
1008
49.7k
  if (!variable_chunksize && nchunks > 0) {
1009
46.6k
    int32_t last_nbytes;
1010
46.6k
    rc = schunk_get_chunk_nbytes(schunk, nchunks - 1, &last_nbytes);
1011
46.6k
    if (rc < 0) {
1012
0
      return rc;
1013
0
    }
1014
46.6k
    if (last_nbytes < chunksize || chunk_nbytes > chunksize) {
1015
0
      variable_chunksize = true;
1016
0
      schunk->chunksize = 0;
1017
0
    }
1018
46.6k
  }
1019
49.7k
  if (!variable_chunksize && chunksize > 0 && chunk_nbytes > chunksize) {
1020
0
    BLOSC_TRACE_ERROR("Appending chunks that have different lengths in the same schunk "
1021
0
                      "is not supported yet: %d > %d.", chunk_nbytes, chunksize);
1022
0
    return BLOSC2_ERROR_CHUNK_APPEND;
1023
0
  }
1024
1025
  /* Update counters */
1026
49.7k
  schunk->current_nchunk = nchunks;
1027
49.7k
  schunk->nchunks = nchunks + 1;
1028
49.7k
  schunk->nbytes += chunk_nbytes;
1029
49.7k
  if (schunk->frame == NULL) {
1030
49.7k
    schunk->cbytes += chunk_cbytes;
1031
49.7k
  } else {
1032
    // A frame
1033
0
    int special_value = (chunk[BLOSC2_CHUNK_BLOSC2_FLAGS] >> 4) & BLOSC2_SPECIAL_MASK;
1034
0
    switch (special_value) {
1035
0
      case BLOSC2_SPECIAL_ZERO:
1036
0
      case BLOSC2_SPECIAL_NAN:
1037
0
      case BLOSC2_SPECIAL_UNINIT:
1038
0
        schunk->cbytes += 0;
1039
0
        break;
1040
0
      default:
1041
0
        schunk->cbytes += chunk_cbytes;
1042
0
    }
1043
0
  }
1044
1045
49.7k
  if (copy) {
1046
    // Make a copy of the chunk
1047
0
    uint8_t *chunk_copy = malloc(chunk_cbytes);
1048
0
    memcpy(chunk_copy, chunk, chunk_cbytes);
1049
0
    chunk = chunk_copy;
1050
0
  }
1051
1052
  // Update super-chunk or frame
1053
49.7k
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1054
49.7k
  if (frame == NULL) {
1055
49.7k
    if (!copy && (chunk_cbytes < chunk_nbytes)) {
1056
      // We still want to do a shrink of the chunk
1057
34.1k
      chunk = realloc(chunk, chunk_cbytes);
1058
34.1k
    }
1059
1060
    /* Make space for appending the copy of the chunk and do it */
1061
49.7k
    if ((nchunks + 1) * sizeof(void *) > schunk->data_len) {
1062
      // Extend the data pointer by one memory page (4k)
1063
3.13k
      schunk->data_len += 4096;  // must be a multiple of sizeof(void*)
1064
3.13k
      schunk->data = realloc(schunk->data, schunk->data_len);
1065
3.13k
    }
1066
49.7k
    schunk->data[nchunks] = chunk;
1067
49.7k
  }
1068
0
  else {
1069
0
    if (frame_append_chunk(frame, chunk, schunk) == NULL) {
1070
0
      BLOSC_TRACE_ERROR("Problems appending a chunk.");
1071
0
      return BLOSC2_ERROR_CHUNK_APPEND;
1072
0
    }
1073
0
  }
1074
49.7k
  return schunk->nchunks;
1075
49.7k
}
1076
1077
1078
/* Append an existing @p chunk to a super-chunk. */
1079
49.7k
int64_t blosc2_schunk_append_chunk(blosc2_schunk *schunk, uint8_t *chunk, bool copy) {
1080
49.7k
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1081
49.7k
  int rc = frame_lock(frame, true);
1082
49.7k
  if (rc < 0) {
1083
0
    return rc;
1084
0
  }
1085
  // Sync the cached counters before the delta below is applied to them, so a
1086
  // handle whose lock acquisition just flagged staleness does not persist
1087
  // wrong nbytes/cbytes/nchunks (see plans/todo-locking-swmr.md item 1).
1088
49.7k
  rc = frame_check_stale(frame);
1089
49.7k
  if (rc < 0) {
1090
0
    frame_unlock(frame);
1091
0
    return rc;
1092
0
  }
1093
49.7k
  int64_t nchunks = schunk_append_chunk_unlocked(schunk, chunk, copy);
1094
49.7k
  frame_unlock(frame);
1095
49.7k
  return nchunks;
1096
49.7k
}
1097
1098
1099
/* Insert an existing @p chunk in a specified position on a super-chunk */
1100
0
static int64_t schunk_insert_chunk_unlocked(blosc2_schunk *schunk, int64_t nchunk, uint8_t *chunk, bool copy) {
1101
0
  int rc = validate_nchunk(schunk, nchunk, true, "blosc2_schunk_insert_chunk");
1102
0
  if (rc < 0) {
1103
0
    return rc;
1104
0
  }
1105
1106
0
  int32_t chunk_nbytes;
1107
0
  int32_t chunk_cbytes;
1108
0
  int64_t nchunks = schunk->nchunks;
1109
1110
0
  rc = blosc2_cbuffer_sizes(chunk, &chunk_nbytes, &chunk_cbytes, NULL);
1111
0
  if (rc < 0) {
1112
0
    return rc;
1113
0
  }
1114
0
  uint8_t flags2 = get_chunk_flags2(chunk, chunk_cbytes);
1115
0
  bool chunk_vlblocks = (flags2 & BLOSC2_VL_BLOCKS) != 0;
1116
0
  if (nchunks > 0) {
1117
0
    uint8_t first_flags2;
1118
0
    rc = schunk_get_chunk_flags2(schunk, 0, &first_flags2);
1119
0
    if (rc < 0) {
1120
0
      return rc;
1121
0
    }
1122
0
    if (((first_flags2 & BLOSC2_VL_BLOCKS) != 0) != chunk_vlblocks) {
1123
0
      BLOSC_TRACE_ERROR("schunks cannot mix regular chunks and VL-block chunks.");
1124
0
      return BLOSC2_ERROR_CHUNK_INSERT;
1125
0
    }
1126
0
  }
1127
0
  else {
1128
0
    schunk->flags2 = flags2;
1129
0
  }
1130
1131
0
  int32_t chunksize = schunk->chunksize;
1132
0
  bool variable_chunksize = (chunksize == 0);
1133
0
  if (chunksize == -1) {
1134
0
    schunk->chunksize = chunk_nbytes;  // The super-chunk is initialized now
1135
0
    chunksize = schunk->chunksize;
1136
0
  }
1137
1138
0
  if (!variable_chunksize && nchunks > 0) {
1139
0
    int32_t last_nbytes;
1140
0
    rc = schunk_get_chunk_nbytes(schunk, nchunks - 1, &last_nbytes);
1141
0
    if (rc < 0) {
1142
0
      return rc;
1143
0
    }
1144
0
    if (last_nbytes < chunksize || chunk_nbytes > chunksize ||
1145
0
        (nchunk != nchunks && chunk_nbytes != chunksize)) {
1146
0
      variable_chunksize = true;
1147
0
      schunk->chunksize = 0;
1148
0
    }
1149
0
  }
1150
0
  if (!variable_chunksize && chunksize > 0 && chunk_nbytes > chunksize) {
1151
0
    BLOSC_TRACE_ERROR("Inserting chunks that have different lengths in the same schunk "
1152
0
                      "is not supported yet: %d > %d.", chunk_nbytes, chunksize);
1153
0
    return BLOSC2_ERROR_CHUNK_INSERT;
1154
0
  }
1155
1156
  /* Update counters */
1157
0
  schunk->current_nchunk = nchunk;
1158
0
  schunk->nchunks = nchunks + 1;
1159
0
  schunk->nbytes += chunk_nbytes;
1160
0
  if (schunk->frame == NULL) {
1161
0
    schunk->cbytes += chunk_cbytes;
1162
0
  } else {
1163
    // A frame
1164
0
    int special_value = (chunk[BLOSC2_CHUNK_BLOSC2_FLAGS] >> 4) & BLOSC2_SPECIAL_MASK;
1165
0
    switch (special_value) {
1166
0
      case BLOSC2_SPECIAL_ZERO:
1167
0
      case BLOSC2_SPECIAL_NAN:
1168
0
      case BLOSC2_SPECIAL_UNINIT:
1169
0
        schunk->cbytes += 0;
1170
0
        break;
1171
0
      default:
1172
0
        schunk->cbytes += chunk_cbytes;
1173
0
    }
1174
0
  }
1175
1176
0
  if (copy) {
1177
    // Make a copy of the chunk
1178
0
    uint8_t *chunk_copy = malloc(chunk_cbytes);
1179
0
    memcpy(chunk_copy, chunk, chunk_cbytes);
1180
0
    chunk = chunk_copy;
1181
0
  }
1182
1183
  // Update super-chunk or frame
1184
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1185
0
  if (frame == NULL) {
1186
0
    if (!copy && (chunk_cbytes < chunk_nbytes)) {
1187
      // We still want to do a shrink of the chunk
1188
0
      chunk = realloc(chunk, chunk_cbytes);
1189
0
    }
1190
1191
    // Make space for appending the copy of the chunk and do it
1192
0
    if ((nchunks + 1) * sizeof(void *) > schunk->data_len) {
1193
      // Extend the data pointer by one memory page (4k)
1194
0
      schunk->data_len += 4096;  // must be a multiple of sizeof(void*)
1195
0
      schunk->data = realloc(schunk->data, schunk->data_len);
1196
0
    }
1197
1198
    // Reorder the offsets and insert the new chunk
1199
0
    for (int64_t i = nchunks; i > nchunk; --i) {
1200
0
      schunk->data[i] = schunk->data[i-1];
1201
0
    }
1202
0
    schunk->data[nchunk] = chunk;
1203
0
  }
1204
1205
0
  else {
1206
0
    if (frame_insert_chunk(frame, nchunk, chunk, schunk) == NULL) {
1207
0
      BLOSC_TRACE_ERROR("Problems inserting a chunk in a frame.");
1208
0
      return BLOSC2_ERROR_CHUNK_INSERT;
1209
0
    }
1210
0
  }
1211
0
  return schunk->nchunks;
1212
0
}
1213
1214
1215
/* Insert an existing @p chunk in a specified position on a super-chunk. */
1216
0
int64_t blosc2_schunk_insert_chunk(blosc2_schunk *schunk, int64_t nchunk, uint8_t *chunk, bool copy) {
1217
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1218
0
  int rc = frame_lock(frame, true);
1219
0
  if (rc < 0) {
1220
0
    return rc;
1221
0
  }
1222
  // See blosc2_schunk_append_chunk() for why this must happen before the
1223
  // counter deltas below are applied.
1224
0
  rc = frame_check_stale(frame);
1225
0
  if (rc < 0) {
1226
0
    frame_unlock(frame);
1227
0
    return rc;
1228
0
  }
1229
0
  int64_t nchunks = schunk_insert_chunk_unlocked(schunk, nchunk, chunk, copy);
1230
0
  frame_unlock(frame);
1231
0
  return nchunks;
1232
0
}
1233
1234
1235
0
static int64_t schunk_update_chunk_unlocked(blosc2_schunk *schunk, int64_t nchunk, uint8_t *chunk, bool copy) {
1236
0
  int rc = validate_nchunk(schunk, nchunk, false, "blosc2_schunk_update_chunk");
1237
0
  if (rc < 0) {
1238
0
    return rc;
1239
0
  }
1240
1241
0
  int32_t chunk_nbytes;
1242
0
  int32_t chunk_cbytes;
1243
1244
0
  rc = blosc2_cbuffer_sizes(chunk, &chunk_nbytes, &chunk_cbytes, NULL);
1245
0
  if (rc < 0) {
1246
0
    return rc;
1247
0
  }
1248
0
  uint8_t flags2 = get_chunk_flags2(chunk, chunk_cbytes);
1249
0
  bool chunk_vlblocks = (flags2 & BLOSC2_VL_BLOCKS) != 0;
1250
0
  if (schunk->nchunks > 1 || (schunk->nchunks == 1 && nchunk != 0)) {
1251
0
    int64_t ref_nchunk = (nchunk == 0) ? 1 : 0;
1252
0
    uint8_t ref_flags2;
1253
0
    rc = schunk_get_chunk_flags2(schunk, ref_nchunk, &ref_flags2);
1254
0
    if (rc < 0) {
1255
0
      return rc;
1256
0
    }
1257
0
    if (((ref_flags2 & BLOSC2_VL_BLOCKS) != 0) != chunk_vlblocks) {
1258
0
      BLOSC_TRACE_ERROR("schunks cannot mix regular chunks and VL-block chunks.");
1259
0
      return BLOSC2_ERROR_CHUNK_UPDATE;
1260
0
    }
1261
0
  }
1262
0
  else {
1263
0
    schunk->flags2 = flags2;
1264
0
  }
1265
1266
0
  int32_t chunksize = schunk->chunksize;
1267
0
  bool variable_chunksize = (chunksize == 0);
1268
0
  if (chunksize == -1) {
1269
0
    schunk->chunksize = chunk_nbytes;  // The super-chunk is initialized now
1270
0
    chunksize = schunk->chunksize;
1271
0
  }
1272
1273
0
  if (!variable_chunksize && (chunk_nbytes > chunksize ||
1274
0
      (nchunk != schunk->nchunks - 1 && chunk_nbytes != chunksize))) {
1275
0
    variable_chunksize = true;
1276
0
    schunk->chunksize = 0;
1277
0
  }
1278
0
  if (!variable_chunksize && chunksize > 0 && chunk_nbytes > chunksize) {
1279
0
    BLOSC_TRACE_ERROR("Updating chunks having different lengths in the same schunk "
1280
0
                      "is not supported yet: %d > %d.", chunk_nbytes, chunksize);
1281
0
    return BLOSC2_ERROR_CHUNK_UPDATE;
1282
0
  }
1283
1284
0
  bool needs_free;
1285
0
  uint8_t *chunk_old;
1286
0
  int err = blosc2_schunk_get_chunk(schunk, nchunk, &chunk_old, &needs_free);
1287
0
  if (err < 0) {
1288
0
    BLOSC_TRACE_ERROR("%" PRId64 " chunk can not be obtained from schunk.", nchunk);
1289
0
    return -1;
1290
0
  }
1291
0
  int32_t chunk_nbytes_old = 0;
1292
0
  int32_t chunk_cbytes_old = 0;
1293
0
  schunk->current_nchunk = nchunk;
1294
1295
0
  if (chunk_old != 0) {
1296
0
    rc = blosc2_cbuffer_sizes(chunk_old, &chunk_nbytes_old, &chunk_cbytes_old, NULL);
1297
0
    if (rc < 0) {
1298
0
      return rc;
1299
0
    }
1300
0
    if (chunk_cbytes_old == BLOSC2_MAX_OVERHEAD) {
1301
0
        chunk_cbytes_old = 0;
1302
0
    }
1303
0
  }
1304
0
  if (needs_free) {
1305
0
    free(chunk_old);
1306
0
  }
1307
1308
0
  if (copy) {
1309
    // Make a copy of the chunk
1310
0
    uint8_t *chunk_copy = malloc(chunk_cbytes);
1311
0
    memcpy(chunk_copy, chunk, chunk_cbytes);
1312
0
    chunk = chunk_copy;
1313
0
  }
1314
1315
0
  blosc2_frame_s* frame = (blosc2_frame_s*)(schunk->frame);
1316
0
  if (schunk->frame == NULL) {
1317
    /* Update counters */
1318
0
    schunk->nbytes += chunk_nbytes;
1319
0
    schunk->nbytes -= chunk_nbytes_old;
1320
0
    schunk->cbytes += chunk_cbytes;
1321
0
    schunk->cbytes -= chunk_cbytes_old;
1322
0
  } else {
1323
    // A frame
1324
0
    int special_value = (chunk[BLOSC2_CHUNK_BLOSC2_FLAGS] >> 4) & BLOSC2_SPECIAL_MASK;
1325
0
    schunk->nbytes += chunk_nbytes;
1326
0
    schunk->nbytes -= chunk_nbytes_old;
1327
0
    switch (special_value) {
1328
0
      case BLOSC2_SPECIAL_ZERO:
1329
0
      case BLOSC2_SPECIAL_NAN:
1330
0
      case BLOSC2_SPECIAL_UNINIT:
1331
0
        schunk->cbytes -= chunk_cbytes_old;
1332
0
        break;
1333
0
      default:
1334
0
        schunk->cbytes += chunk_cbytes;
1335
0
        schunk->cbytes -= chunk_cbytes_old;
1336
0
    }
1337
0
  }
1338
1339
  // Update super-chunk or frame
1340
0
  if (schunk->frame == NULL) {
1341
0
    if (!copy && (chunk_cbytes < chunk_nbytes)) {
1342
      // We still want to do a shrink of the chunk
1343
0
      chunk = realloc(chunk, chunk_cbytes);
1344
0
    }
1345
1346
    // Free old chunk and add reference to new chunk
1347
0
    if (schunk->data[nchunk] != 0) {
1348
0
      free(schunk->data[nchunk]);
1349
0
    }
1350
0
    schunk->data[nchunk] = chunk;
1351
0
  }
1352
0
  else {
1353
0
    if (frame_update_chunk(frame, nchunk, chunk, schunk) == NULL) {
1354
0
        BLOSC_TRACE_ERROR("Problems updating a chunk in a frame.");
1355
0
        return BLOSC2_ERROR_CHUNK_UPDATE;
1356
0
    }
1357
0
  }
1358
1359
0
  return schunk->nchunks;
1360
0
}
1361
1362
/* Update the chunk at a specified position of a super-chunk. */
1363
0
int64_t blosc2_schunk_update_chunk(blosc2_schunk *schunk, int64_t nchunk, uint8_t *chunk, bool copy) {
1364
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1365
0
  int rc = frame_lock(frame, true);
1366
0
  if (rc < 0) {
1367
0
    return rc;
1368
0
  }
1369
0
  int64_t nchunks = schunk_update_chunk_unlocked(schunk, nchunk, chunk, copy);
1370
0
  frame_unlock(frame);
1371
0
  return nchunks;
1372
0
}
1373
1374
1375
0
static int64_t schunk_delete_chunk_unlocked(blosc2_schunk *schunk, int64_t nchunk) {
1376
0
  int rc = validate_nchunk(schunk, nchunk, false, "blosc2_schunk_delete_chunk");
1377
0
  if (rc < 0) {
1378
0
    return rc;
1379
0
  }
1380
1381
0
  bool needs_free;
1382
0
  uint8_t *chunk_old;
1383
0
  int err = blosc2_schunk_get_chunk(schunk, nchunk, &chunk_old, &needs_free);
1384
0
  if (err < 0) {
1385
0
    BLOSC_TRACE_ERROR("%" PRId64 "chunk can not be obtained from schunk.", nchunk);
1386
0
    return -1;
1387
0
  }
1388
0
  int32_t chunk_nbytes_old = 0;
1389
0
  int32_t chunk_cbytes_old = 0;
1390
0
  schunk->current_nchunk = nchunk;
1391
1392
0
  if (chunk_old != 0) {
1393
0
    rc = blosc2_cbuffer_sizes(chunk_old, &chunk_nbytes_old, &chunk_cbytes_old, NULL);
1394
0
    if (rc < 0) {
1395
0
      return rc;
1396
0
    }
1397
0
    if (chunk_cbytes_old == BLOSC2_MAX_OVERHEAD) {
1398
0
      chunk_cbytes_old = 0;
1399
0
    }
1400
0
  }
1401
0
  if (needs_free) {
1402
0
    free(chunk_old);
1403
0
  }
1404
1405
0
  blosc2_frame_s* frame = (blosc2_frame_s*)(schunk->frame);
1406
0
  schunk->nchunks -= 1;
1407
0
  if (schunk->nchunks == 0) {
1408
0
    schunk->flags2 = 0;
1409
0
  }
1410
0
  if (schunk->frame == NULL) {
1411
    /* Update counters */
1412
0
    schunk->nbytes -= chunk_nbytes_old;
1413
0
    schunk->cbytes -= chunk_cbytes_old;
1414
0
  } else {
1415
    // A frame
1416
0
    schunk->nbytes -= chunk_nbytes_old;
1417
0
    if (frame->sframe) {
1418
0
      schunk->cbytes -= chunk_cbytes_old;
1419
0
    }
1420
0
  }
1421
1422
  // Update super-chunk or frame
1423
0
  if (schunk->frame == NULL) {
1424
    // Free old chunk
1425
0
    if (schunk->data[nchunk] != 0) {
1426
0
      free(schunk->data[nchunk]);
1427
0
    }
1428
    // Reorder the offsets and insert the new chunk
1429
0
    for (int64_t i = nchunk; i < schunk->nchunks; i++) {
1430
0
      schunk->data[i] = schunk->data[i + 1];
1431
0
    }
1432
0
    schunk->data[schunk->nchunks] = NULL;
1433
1434
0
  }
1435
0
  else {
1436
0
    if (frame_delete_chunk(frame, nchunk, schunk) == NULL) {
1437
0
      BLOSC_TRACE_ERROR("Problems deleting a chunk in a frame.");
1438
0
      return BLOSC2_ERROR_CHUNK_UPDATE;
1439
0
    }
1440
0
  }
1441
0
  return schunk->nchunks;
1442
0
}
1443
1444
1445
/* Delete the chunk at a specified position of a super-chunk. */
1446
0
int64_t blosc2_schunk_delete_chunk(blosc2_schunk *schunk, int64_t nchunk) {
1447
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1448
0
  int rc = frame_lock(frame, true);
1449
0
  if (rc < 0) {
1450
0
    return rc;
1451
0
  }
1452
0
  int64_t nchunks = schunk_delete_chunk_unlocked(schunk, nchunk);
1453
0
  frame_unlock(frame);
1454
0
  return nchunks;
1455
0
}
1456
1457
1458
/* Append a data buffer to a super-chunk. */
1459
49.7k
int64_t blosc2_schunk_append_buffer(blosc2_schunk *schunk, const void *src, int32_t nbytes) {
1460
49.7k
  uint8_t* chunk = malloc(nbytes + BLOSC2_MAX_OVERHEAD);
1461
49.7k
  schunk->current_nchunk = schunk->nchunks;
1462
  /* Compress the src buffer using super-chunk context */
1463
49.7k
  int cbytes = blosc2_compress_ctx(schunk->cctx, src, nbytes, chunk,
1464
49.7k
                                   nbytes + BLOSC2_MAX_OVERHEAD);
1465
49.7k
  if (cbytes < 0) {
1466
0
    free(chunk);
1467
0
    return cbytes;
1468
0
  }
1469
  // We don't need a copy of the chunk, as it will be shrunk if necessary
1470
49.7k
  int64_t nchunks = blosc2_schunk_append_chunk(schunk, chunk, false);
1471
49.7k
  if (nchunks < 0) {
1472
0
    BLOSC_TRACE_ERROR("Error appending a buffer in super-chunk");
1473
0
    return nchunks;
1474
0
  }
1475
1476
49.7k
  return nchunks;
1477
49.7k
}
1478
1479
1480
/* Decompress and return a chunk that is part of a super-chunk. */
1481
int blosc2_schunk_decompress_chunk(blosc2_schunk *schunk, int64_t nchunk,
1482
25.1k
                                   void *dest, int32_t nbytes) {
1483
25.1k
  int rc = validate_nchunk(schunk, nchunk, false, "blosc2_schunk_decompress_chunk");
1484
25.1k
  if (rc < 0) {
1485
0
    return rc;
1486
0
  }
1487
1488
25.1k
  int32_t chunk_nbytes;
1489
25.1k
  int32_t chunk_cbytes;
1490
25.1k
  int chunksize;
1491
25.1k
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1492
1493
25.1k
  schunk->current_nchunk = nchunk;
1494
25.1k
  if (frame == NULL) {
1495
15.3k
    uint8_t* src = schunk->data[nchunk];
1496
15.3k
    if (src == 0) {
1497
0
      return 0;
1498
0
    }
1499
1500
15.3k
    rc = blosc2_cbuffer_sizes(src, &chunk_nbytes, &chunk_cbytes, NULL);
1501
15.3k
    if (rc < 0) {
1502
0
      return rc;
1503
0
    }
1504
1505
15.3k
    if (nbytes < chunk_nbytes) {
1506
490
      BLOSC_TRACE_ERROR("Buffer size is too small for the decompressed buffer "
1507
490
                        "('%d' bytes, but '%d' are needed).", nbytes, chunk_nbytes);
1508
490
      return BLOSC2_ERROR_INVALID_PARAM;
1509
490
    }
1510
1511
14.8k
    chunksize = blosc2_decompress_ctx(schunk->dctx, src, chunk_cbytes, dest, nbytes);
1512
14.8k
    if (chunksize < 0 || chunksize != chunk_nbytes) {
1513
0
      BLOSC_TRACE_ERROR("Error in decompressing chunk.");
1514
0
      if (chunksize < 0)
1515
0
        return chunksize;
1516
0
      return BLOSC2_ERROR_FAILURE;
1517
0
    }
1518
14.8k
  } else {
1519
9.79k
    rc = frame_lock(frame, false);
1520
9.79k
    if (rc < 0) {
1521
0
      return rc;
1522
0
    }
1523
9.79k
    chunksize = frame_decompress_chunk(schunk->dctx, frame, nchunk, dest, nbytes);
1524
9.79k
    frame_unlock(frame);
1525
9.79k
    if (chunksize < 0) {
1526
335
      return chunksize;
1527
335
    }
1528
9.79k
  }
1529
24.3k
  return chunksize;
1530
25.1k
}
1531
1532
1533
/* Return a compressed chunk that is part of a super-chunk in the `chunk` parameter.
1534
 * If the super-chunk is backed by a frame that is disk-based, a buffer is allocated for the
1535
 * (compressed) chunk, and hence a free is needed.  You can check if the chunk requires a free
1536
 * with the `needs_free` parameter.
1537
 * If the chunk does not need a free, it means that a pointer to the location in the super-chunk
1538
 * (or the backing in-memory frame) is returned in the `chunk` parameter.
1539
 *
1540
 * The size of the (compressed) chunk is returned.  If some problem is detected, a negative code
1541
 * is returned instead.
1542
*/
1543
0
int blosc2_schunk_get_chunk(blosc2_schunk *schunk, int64_t nchunk, uint8_t **chunk, bool *needs_free) {
1544
0
  int rc = validate_nchunk(schunk, nchunk, false, "blosc2_schunk_get_chunk");
1545
0
  if (rc < 0) {
1546
0
    return rc;
1547
0
  }
1548
1549
0
  if (ctx_uses_parallel_backend(schunk->dctx)) {
1550
0
    blosc2_pthread_mutex_lock(&schunk->dctx->nchunk_mutex);
1551
0
    schunk->current_nchunk = nchunk;
1552
0
    blosc2_pthread_mutex_unlock(&schunk->dctx->nchunk_mutex);
1553
0
  }
1554
0
  else {
1555
0
    schunk->current_nchunk = nchunk;
1556
0
  }
1557
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1558
0
  if (frame != NULL) {
1559
0
    rc = frame_lock(frame, false);
1560
0
    if (rc < 0) {
1561
0
      return rc;
1562
0
    }
1563
0
    rc = frame_get_chunk(frame, nchunk, chunk, needs_free);
1564
0
    frame_unlock(frame);
1565
0
    return rc;
1566
0
  }
1567
1568
0
  *chunk = schunk->data[nchunk];
1569
0
  if (*chunk == 0) {
1570
0
    *needs_free = 0;
1571
0
    return 0;
1572
0
  }
1573
1574
0
  *needs_free = false;
1575
0
  int32_t chunk_cbytes;
1576
0
  rc = blosc2_cbuffer_sizes(*chunk, NULL, &chunk_cbytes, NULL);
1577
0
  if (rc < 0) {
1578
0
    return rc;
1579
0
  }
1580
0
  return (int)chunk_cbytes;
1581
0
}
1582
1583
1584
/* Return a compressed chunk that is part of a super-chunk in the `chunk` parameter.
1585
 * If the super-chunk is backed by a frame that is disk-based, a buffer is allocated for the
1586
 * (compressed) chunk, and hence a free is needed.  You can check if the chunk requires a free
1587
 * with the `needs_free` parameter.
1588
 * If the chunk does not need a free, it means that a pointer to the location in the super-chunk
1589
 * (or the backing in-memory frame) is returned in the `chunk` parameter.
1590
 *
1591
 * The size of the (compressed) chunk is returned.  If some problem is detected, a negative code
1592
 * is returned instead.
1593
*/
1594
0
int blosc2_schunk_get_lazychunk(blosc2_schunk *schunk, int64_t nchunk, uint8_t **chunk, bool *needs_free) {
1595
0
  int rc = validate_nchunk(schunk, nchunk, false, "blosc2_schunk_get_lazychunk");
1596
0
  if (rc < 0) {
1597
0
    return rc;
1598
0
  }
1599
1600
0
  if (ctx_uses_parallel_backend(schunk->dctx)) {
1601
0
    blosc2_pthread_mutex_lock(&schunk->dctx->nchunk_mutex);
1602
0
    schunk->current_nchunk = nchunk;
1603
0
    blosc2_pthread_mutex_unlock(&schunk->dctx->nchunk_mutex);
1604
0
  }
1605
0
  else {
1606
0
    schunk->current_nchunk = nchunk;
1607
0
  }
1608
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
1609
0
  if (schunk->frame != NULL) {
1610
0
    rc = frame_lock(frame, false);
1611
0
    if (rc < 0) {
1612
0
      return rc;
1613
0
    }
1614
0
    rc = frame_get_lazychunk(frame, nchunk, chunk, needs_free);
1615
0
    frame_unlock(frame);
1616
0
    return rc;
1617
0
  }
1618
1619
0
  *chunk = schunk->data[nchunk];
1620
0
  if (*chunk == 0) {
1621
0
    *needs_free = 0;
1622
0
    return 0;
1623
0
  }
1624
1625
0
  *needs_free = false;
1626
0
  int32_t chunk_cbytes;
1627
0
  rc = blosc2_cbuffer_sizes(*chunk, NULL, &chunk_cbytes, NULL);
1628
0
  if (rc < 0) {
1629
0
    return rc;
1630
0
  }
1631
0
  return (int)chunk_cbytes;
1632
0
}
1633
1634
1635
int blosc2_schunk_get_vlblock(blosc2_schunk *schunk, int64_t nchunk, int32_t nblock,
1636
0
                              uint8_t **dest, int32_t *destsize) {
1637
0
  if (schunk == NULL || dest == NULL || destsize == NULL) {
1638
0
    BLOSC_TRACE_ERROR("schunk, dest, and destsize must not be NULL.");
1639
0
    return BLOSC2_ERROR_INVALID_PARAM;
1640
0
  }
1641
1642
0
  uint8_t *chunk = NULL;
1643
0
  bool needs_free = false;
1644
0
  int cbytes = blosc2_schunk_get_chunk(schunk, nchunk, &chunk, &needs_free);
1645
0
  if (cbytes < 0) {
1646
0
    return cbytes;
1647
0
  }
1648
0
  if (chunk == NULL || cbytes == 0) {
1649
0
    BLOSC_TRACE_ERROR("Chunk %" PRId64 " is not initialized.", nchunk);
1650
0
    return BLOSC2_ERROR_INVALID_PARAM;
1651
0
  }
1652
1653
0
  int result = blosc2_vldecompress_block_ctx(schunk->dctx, chunk, cbytes,
1654
0
                                             nblock, dest, destsize);
1655
0
  if (needs_free) {
1656
0
    free(chunk);
1657
0
  }
1658
0
  return result;
1659
0
}
1660
1661
1662
0
int blosc2_schunk_get_slice_buffer(blosc2_schunk *schunk, int64_t start, int64_t stop, void *buffer) {
1663
0
  int64_t byte_start = start * schunk->typesize;
1664
0
  int64_t byte_stop = stop * schunk->typesize;
1665
0
  int64_t nchunk_start = byte_start / schunk->chunksize;
1666
0
  int32_t chunk_start = (int32_t) (byte_start % schunk->chunksize); // 0 indexed
1667
0
  int32_t chunk_stop; // 0 indexed
1668
0
  if (byte_stop >= (nchunk_start + 1) * schunk->chunksize) {
1669
0
    chunk_stop = schunk->chunksize;
1670
0
  }
1671
0
  else {
1672
0
    chunk_stop = (int32_t) (byte_stop % schunk->chunksize);
1673
0
  }
1674
1675
0
  uint8_t *dst_ptr = (uint8_t *) buffer;
1676
0
  bool needs_free;
1677
0
  uint8_t *chunk;
1678
0
  int32_t cbytes;
1679
0
  int64_t nchunk = nchunk_start;
1680
0
  int64_t nbytes_read = 0;
1681
0
  int32_t nbytes;
1682
0
  int32_t chunksize = schunk->chunksize;
1683
1684
0
  while (nbytes_read < ((stop - start) * schunk->typesize)) {
1685
0
    cbytes = blosc2_schunk_get_lazychunk(schunk, nchunk, &chunk, &needs_free);
1686
0
    if (cbytes < 0) {
1687
0
      BLOSC_TRACE_ERROR("Cannot get lazychunk ('%" PRId64 "').", nchunk);
1688
0
      return BLOSC2_ERROR_FAILURE;
1689
0
    }
1690
0
    int32_t blocksize = sw32_(chunk + BLOSC2_CHUNK_BLOCKSIZE);
1691
1692
0
    int32_t nblock_start = (int32_t) (chunk_start / blocksize);
1693
0
    int32_t nblock_stop = (int32_t) ((chunk_stop - 1) / blocksize);
1694
0
    if (nchunk == (schunk->nchunks - 1) && schunk->nbytes % schunk->chunksize != 0) {
1695
0
      chunksize = schunk->nbytes % schunk->chunksize;
1696
0
    }
1697
0
    int32_t nblocks = chunksize / blocksize;
1698
0
    if (chunksize % blocksize != 0) {
1699
0
      nblocks++;
1700
0
    }
1701
1702
0
    if (chunk_start == 0 && chunk_stop == chunksize) {
1703
      // Avoid memcpy
1704
0
      nbytes = blosc2_decompress_ctx(schunk->dctx, chunk, cbytes, dst_ptr, chunksize);
1705
0
      if (nbytes < 0) {
1706
0
        BLOSC_TRACE_ERROR("Cannot decompress chunk ('%" PRId64 "').", nchunk);
1707
0
        if (needs_free) {
1708
0
          free(chunk);
1709
0
        }
1710
0
        return BLOSC2_ERROR_FAILURE;
1711
0
      }
1712
0
    }
1713
0
    else {
1714
      // After extensive timing I have not been able to see lots of situations where
1715
      // a maskout read is better than a getitem one.  Disabling for now.
1716
      // if (nblock_start != nblock_stop) {
1717
0
      if (false) {
1718
0
        uint8_t *data = malloc(chunksize);
1719
        /* We have more than 1 block to read, so use a masked read */
1720
0
        bool *block_maskout = calloc(nblocks, 1);
1721
0
        for (int32_t nblock = 0; nblock < nblocks; nblock++) {
1722
0
          if ((nblock < nblock_start) || (nblock > nblock_stop)) {
1723
0
            block_maskout[nblock] = true;
1724
0
          }
1725
0
        }
1726
0
        if (blosc2_set_maskout(schunk->dctx, block_maskout, nblocks) < 0) {
1727
0
          BLOSC_TRACE_ERROR("Cannot set maskout");
1728
0
          return BLOSC2_ERROR_FAILURE;
1729
0
        }
1730
1731
0
        nbytes = blosc2_decompress_ctx(schunk->dctx, chunk, cbytes, data, chunksize);
1732
0
        if (nbytes < 0) {
1733
0
          BLOSC_TRACE_ERROR("Cannot decompress chunk ('%" PRId64 "').", nchunk);
1734
0
          return BLOSC2_ERROR_FAILURE;
1735
0
        }
1736
0
        nbytes = chunk_stop - chunk_start;
1737
0
        memcpy(dst_ptr, &data[chunk_start], nbytes);
1738
0
        free(block_maskout);
1739
0
        free(data);
1740
0
      }
1741
0
      else {
1742
        /* Less than 1 block to read; use a getitem call.  Counting in bytes
1743
           keeps this right for typesizes above BLOSC_MAX_TYPESIZE, which chunks
1744
           record as 1. */
1745
0
        int32_t nbytes_wanted = chunk_stop - chunk_start;
1746
0
        nbytes = blosc2_getitem_bytes_ctx(schunk->dctx, chunk, cbytes, chunk_start,
1747
0
                                          nbytes_wanted, dst_ptr, nbytes_wanted);
1748
0
        if (nbytes < 0) {
1749
0
          BLOSC_TRACE_ERROR("Cannot get item from ('%" PRId64 "') chunk.", nchunk);
1750
0
          if (needs_free) {
1751
0
            free(chunk);
1752
0
          }
1753
0
          return BLOSC2_ERROR_FAILURE;
1754
0
        }
1755
0
        if (nbytes != nbytes_wanted) {
1756
0
          BLOSC_TRACE_ERROR("Short read (%d out of %d bytes) in ('%" PRId64 "') chunk.",
1757
0
                            nbytes, nbytes_wanted, nchunk);
1758
0
          if (needs_free) {
1759
0
            free(chunk);
1760
0
          }
1761
0
          return BLOSC2_ERROR_FAILURE;
1762
0
        }
1763
0
      }
1764
0
    }
1765
1766
0
    dst_ptr += nbytes;
1767
0
    nbytes_read += nbytes;
1768
0
    nchunk++;
1769
1770
0
    if (needs_free) {
1771
0
      free(chunk);
1772
0
    }
1773
0
    chunk_start = 0;
1774
0
    if (byte_stop >= (nchunk + 1) * chunksize) {
1775
0
      chunk_stop = chunksize;
1776
0
    }
1777
0
    else {
1778
0
      chunk_stop = (int32_t)(byte_stop % chunksize);
1779
0
    }
1780
0
  }
1781
1782
0
  return BLOSC2_ERROR_SUCCESS;
1783
0
}
1784
1785
1786
typedef struct {
1787
  int64_t coord;
1788
  int64_t out_index;
1789
} sparse_coord_entry;
1790
1791
typedef struct {
1792
  int64_t nchunk;
1793
  uint8_t *chunk;
1794
  int cbytes;
1795
  bool needs_free;
1796
} sparse_chunk_entry;
1797
1798
typedef struct {
1799
  int64_t first;
1800
  int64_t count;
1801
  int64_t chunk_index;
1802
  int32_t nblock;
1803
} sparse_block_task;
1804
1805
typedef struct {
1806
  const sparse_coord_entry *entries;
1807
  const sparse_block_task *tasks;
1808
  const sparse_chunk_entry *chunks;
1809
  int64_t ntasks;
1810
  uint8_t *buffer;
1811
  int32_t typesize;
1812
  int32_t blocksize;
1813
  int64_t chunk_nitems;
1814
  int64_t next_task;
1815
  int error;
1816
  blosc2_pthread_mutex_t mutex;
1817
} sparse_work;
1818
1819
typedef struct {
1820
  sparse_work *work;
1821
  blosc2_context *dctx;
1822
  uint8_t *block;
1823
} sparse_worker;
1824
1825
0
static int sparse_coord_entry_cmp(const void *a, const void *b) {
1826
0
  const sparse_coord_entry *ea = (const sparse_coord_entry *)a;
1827
0
  const sparse_coord_entry *eb = (const sparse_coord_entry *)b;
1828
0
  if (ea->coord < eb->coord) return -1;
1829
0
  if (ea->coord > eb->coord) return 1;
1830
0
  if (ea->out_index < eb->out_index) return -1;
1831
0
  if (ea->out_index > eb->out_index) return 1;
1832
0
  return 0;
1833
0
}
1834
1835
0
static void sparse_work_set_error(sparse_work *work, int error) {
1836
0
  blosc2_pthread_mutex_lock(&work->mutex);
1837
0
  if (work->error == 0) {
1838
0
    work->error = error;
1839
0
  }
1840
0
  blosc2_pthread_mutex_unlock(&work->mutex);
1841
0
}
1842
1843
0
static void sparse_worker_func(void *arg) {
1844
0
  sparse_worker *worker = (sparse_worker *)arg;
1845
0
  sparse_work *work = worker->work;
1846
1847
0
  while (true) {
1848
0
    blosc2_pthread_mutex_lock(&work->mutex);
1849
0
    if (work->error < 0 || work->next_task >= work->ntasks) {
1850
0
      blosc2_pthread_mutex_unlock(&work->mutex);
1851
0
      break;
1852
0
    }
1853
0
    int64_t task_index = work->next_task++;
1854
0
    blosc2_pthread_mutex_unlock(&work->mutex);
1855
1856
0
    const sparse_block_task *task = &work->tasks[task_index];
1857
0
    const sparse_chunk_entry *chunk_entry = &work->chunks[task->chunk_index];
1858
0
    int nbytes = blosc2_decompress_block_ctx(worker->dctx, chunk_entry->chunk, chunk_entry->cbytes,
1859
0
                                             task->nblock, worker->block, work->blocksize);
1860
0
    if (nbytes < 0) {
1861
0
      sparse_work_set_error(work, nbytes);
1862
0
      break;
1863
0
    }
1864
1865
0
    for (int64_t i = task->first; i < task->first + task->count; ++i) {
1866
0
      const sparse_coord_entry *entry = &work->entries[i];
1867
0
      int64_t item_in_chunk = entry->coord % work->chunk_nitems;
1868
0
      int64_t byte_in_chunk = item_in_chunk * work->typesize;
1869
0
      int32_t block_offset = (int32_t)(byte_in_chunk % work->blocksize);
1870
0
      if (block_offset < 0 || block_offset > nbytes - work->typesize) {
1871
0
        sparse_work_set_error(work, BLOSC2_ERROR_DATA);
1872
0
        return;
1873
0
      }
1874
0
      memcpy(work->buffer + entry->out_index * work->typesize,
1875
0
             worker->block + block_offset,
1876
0
             (size_t)work->typesize);
1877
0
    }
1878
0
  }
1879
1880
0
  return;
1881
0
}
1882
1883
0
static int schunk_get_sparse_getitem(blosc2_schunk *schunk, int64_t ncoords, const int64_t* coords, void *buffer) {
1884
0
  int64_t nitems = schunk->nbytes / schunk->typesize;
1885
0
  int64_t chunk_nitems = schunk->chunksize / schunk->typesize;
1886
0
  uint8_t *dst_ptr = (uint8_t *)buffer;
1887
1888
0
  for (int64_t i = 0; i < ncoords; ++i) {
1889
0
    int64_t coord = coords[i];
1890
0
    if (coord < 0 || coord >= nitems) {
1891
0
      BLOSC_TRACE_ERROR("Coordinate out of bounds.");
1892
0
      return BLOSC2_ERROR_INVALID_PARAM;
1893
0
    }
1894
1895
0
    int64_t nchunk = coord / chunk_nitems;
1896
    // count in bytes, so that a typesize above BLOSC_MAX_TYPESIZE (which chunks
1897
    // record as 1) needs no special casing here
1898
0
    int32_t start = (int32_t)((coord % chunk_nitems) * schunk->typesize);
1899
0
    uint8_t *chunk = NULL;
1900
0
    bool needs_free = false;
1901
0
    int cbytes = blosc2_schunk_get_lazychunk(schunk, nchunk, &chunk, &needs_free);
1902
0
    if (cbytes <= 0) {
1903
0
      BLOSC_TRACE_ERROR("Cannot get lazychunk ('%" PRId64 "').", nchunk);
1904
0
      return BLOSC2_ERROR_FAILURE;
1905
0
    }
1906
1907
0
    int nbytes = blosc2_getitem_bytes_ctx(schunk->dctx, chunk, cbytes, start,
1908
0
                                          schunk->typesize, dst_ptr, schunk->typesize);
1909
0
    if (needs_free) {
1910
0
      free(chunk);
1911
0
    }
1912
0
    if (nbytes != schunk->typesize) {
1913
0
      BLOSC_TRACE_ERROR("Cannot get item from ('%" PRId64 "') chunk.", nchunk);
1914
0
      return BLOSC2_ERROR_FAILURE;
1915
0
    }
1916
0
    dst_ptr += schunk->typesize;
1917
0
  }
1918
1919
0
  return BLOSC2_ERROR_SUCCESS;
1920
0
}
1921
1922
0
int blosc2_schunk_get_sparse_buffer(blosc2_schunk *schunk, int64_t ncoords, const int64_t* coords, void *buffer) {
1923
0
  if (schunk == NULL) {
1924
0
    BLOSC_TRACE_ERROR("schunk must not be NULL.");
1925
0
    return BLOSC2_ERROR_INVALID_PARAM;
1926
0
  }
1927
0
  if (ncoords < 0) {
1928
0
    BLOSC_TRACE_ERROR("ncoords must be non-negative.");
1929
0
    return BLOSC2_ERROR_INVALID_PARAM;
1930
0
  }
1931
0
  if (ncoords == 0) {
1932
0
    return BLOSC2_ERROR_SUCCESS;
1933
0
  }
1934
0
  if (coords == NULL || buffer == NULL) {
1935
0
    BLOSC_TRACE_ERROR("coords and buffer must not be NULL when ncoords > 0.");
1936
0
    return BLOSC2_ERROR_INVALID_PARAM;
1937
0
  }
1938
0
  if (schunk->typesize <= 0) {
1939
0
    BLOSC_TRACE_ERROR("schunk must have a positive typesize.");
1940
0
    return BLOSC2_ERROR_INVALID_PARAM;
1941
0
  }
1942
0
  if (schunk->chunksize <= 0) {
1943
0
    BLOSC_TRACE_ERROR("blosc2_schunk_get_sparse_buffer does not support variable-length chunks yet.");
1944
0
    return BLOSC2_ERROR_INVALID_PARAM;
1945
0
  }
1946
0
  if (schunk->blocksize <= 0 || (schunk->flags2 & BLOSC2_VL_BLOCKS)) {
1947
0
    BLOSC_TRACE_ERROR("blosc2_schunk_get_sparse_buffer does not support variable-length blocks yet.");
1948
0
    return BLOSC2_ERROR_INVALID_PARAM;
1949
0
  }
1950
0
  if ((schunk->chunksize % schunk->typesize) != 0 || (schunk->blocksize % schunk->typesize) != 0) {
1951
0
    BLOSC_TRACE_ERROR("chunksize and blocksize must be multiples of typesize.");
1952
0
    return BLOSC2_ERROR_INVALID_PARAM;
1953
0
  }
1954
1955
0
  for (int i = 0; i < BLOSC2_MAX_FILTERS; ++i) {
1956
0
    if (schunk->filters[i] == BLOSC_DELTA) {
1957
0
      return schunk_get_sparse_getitem(schunk, ncoords, coords, buffer);
1958
0
    }
1959
0
  }
1960
0
  if (schunk->dctx != NULL && schunk->dctx->postfilter != NULL) {
1961
0
    return schunk_get_sparse_getitem(schunk, ncoords, coords, buffer);
1962
0
  }
1963
0
  if (ncoords <= 1) {
1964
0
    return schunk_get_sparse_getitem(schunk, ncoords, coords, buffer);
1965
0
  }
1966
1967
0
  int rc = BLOSC2_ERROR_SUCCESS;
1968
0
  int64_t nitems = schunk->nbytes / schunk->typesize;
1969
0
  int64_t chunk_nitems = schunk->chunksize / schunk->typesize;
1970
0
  sparse_coord_entry *entries = NULL;
1971
0
  sparse_block_task *tasks = NULL;
1972
0
  sparse_chunk_entry *chunks = NULL;
1973
0
  sparse_worker *workers = NULL;
1974
0
  int16_t nthreads = 1;
1975
0
  int64_t ntasks = 0;
1976
0
  int64_t nchunks = 0;
1977
0
  sparse_work work;
1978
0
  memset(&work, 0, sizeof(work));
1979
1980
0
  if (chunk_nitems <= 0 || chunk_nitems > INT32_MAX) {
1981
0
    BLOSC_TRACE_ERROR("Invalid chunk item count.");
1982
0
    return BLOSC2_ERROR_INVALID_PARAM;
1983
0
  }
1984
1985
0
  entries = calloc((size_t)ncoords, sizeof(sparse_coord_entry));
1986
0
  if (entries == NULL) {
1987
0
    rc = BLOSC2_ERROR_MEMORY_ALLOC;
1988
0
    goto cleanup;
1989
0
  }
1990
1991
0
  for (int64_t i = 0; i < ncoords; ++i) {
1992
0
    int64_t coord = coords[i];
1993
0
    if (coord < 0 || coord >= nitems) {
1994
0
      BLOSC_TRACE_ERROR("Coordinate out of bounds.");
1995
0
      rc = BLOSC2_ERROR_INVALID_PARAM;
1996
0
      goto cleanup;
1997
0
    }
1998
0
    entries[i].coord = coord;
1999
0
    entries[i].out_index = i;
2000
0
  }
2001
2002
0
  qsort(entries, (size_t)ncoords, sizeof(sparse_coord_entry), sparse_coord_entry_cmp);
2003
2004
  /* First pass over sorted entries: count touched chunks and selected block tasks. */
2005
0
  for (int64_t i = 0; i < ncoords;) {
2006
0
    int64_t nchunk = entries[i].coord / chunk_nitems;
2007
0
    ++nchunks;
2008
0
    while (i < ncoords && entries[i].coord / chunk_nitems == nchunk) {
2009
0
      int64_t item_in_chunk = entries[i].coord % chunk_nitems;
2010
0
      int64_t byte_in_chunk = item_in_chunk * schunk->typesize;
2011
0
      int32_t nblock = (int32_t)(byte_in_chunk / schunk->blocksize);
2012
0
      while (i < ncoords &&
2013
0
             entries[i].coord / chunk_nitems == nchunk &&
2014
0
             (int32_t)(((entries[i].coord % chunk_nitems) * schunk->typesize) / schunk->blocksize) == nblock) {
2015
0
        ++i;
2016
0
      }
2017
0
      ++ntasks;
2018
0
    }
2019
0
  }
2020
2021
0
  tasks = calloc((size_t)ntasks, sizeof(sparse_block_task));
2022
0
  chunks = calloc((size_t)nchunks, sizeof(sparse_chunk_entry));
2023
0
  if (tasks == NULL || chunks == NULL) {
2024
0
    rc = BLOSC2_ERROR_MEMORY_ALLOC;
2025
0
    goto cleanup;
2026
0
  }
2027
2028
  /* Second pass: fetch touched chunks and build one task per selected block. */
2029
0
  int64_t chunk_index = 0;
2030
0
  int64_t task_index = 0;
2031
0
  for (int64_t i = 0; i < ncoords;) {
2032
0
    int64_t nchunk = entries[i].coord / chunk_nitems;
2033
0
    uint8_t *chunk = NULL;
2034
0
    bool needs_free = false;
2035
0
    int cbytes = blosc2_schunk_get_lazychunk(schunk, nchunk, &chunk, &needs_free);
2036
0
    if (cbytes <= 0) {
2037
0
      BLOSC_TRACE_ERROR("Cannot get lazychunk ('%" PRId64 "').", nchunk);
2038
0
      rc = BLOSC2_ERROR_FAILURE;
2039
0
      goto cleanup;
2040
0
    }
2041
0
    chunks[chunk_index].nchunk = nchunk;
2042
0
    chunks[chunk_index].chunk = chunk;
2043
0
    chunks[chunk_index].cbytes = cbytes;
2044
0
    chunks[chunk_index].needs_free = needs_free;
2045
2046
0
    while (i < ncoords && entries[i].coord / chunk_nitems == nchunk) {
2047
0
      int64_t item_in_chunk = entries[i].coord % chunk_nitems;
2048
0
      int64_t byte_in_chunk = item_in_chunk * schunk->typesize;
2049
0
      int32_t nblock = (int32_t)(byte_in_chunk / schunk->blocksize);
2050
0
      int64_t first = i;
2051
0
      while (i < ncoords &&
2052
0
             entries[i].coord / chunk_nitems == nchunk &&
2053
0
             (int32_t)(((entries[i].coord % chunk_nitems) * schunk->typesize) / schunk->blocksize) == nblock) {
2054
0
        ++i;
2055
0
      }
2056
0
      tasks[task_index].first = first;
2057
0
      tasks[task_index].count = i - first;
2058
0
      tasks[task_index].chunk_index = chunk_index;
2059
0
      tasks[task_index].nblock = nblock;
2060
0
      ++task_index;
2061
0
    }
2062
0
    ++chunk_index;
2063
0
  }
2064
2065
0
  nthreads = schunk->dctx != NULL ? schunk->dctx->nthreads : blosc2_get_nthreads();
2066
0
  if (nthreads < 1) {
2067
0
    nthreads = 1;
2068
0
  }
2069
0
  if ((int64_t)nthreads > ntasks) {
2070
0
    nthreads = (int16_t)ntasks;
2071
0
  }
2072
2073
0
  workers = calloc((size_t)nthreads, sizeof(sparse_worker));
2074
0
  if (workers == NULL) {
2075
0
    rc = BLOSC2_ERROR_MEMORY_ALLOC;
2076
0
    goto cleanup;
2077
0
  }
2078
2079
0
  work.entries = entries;
2080
0
  work.tasks = tasks;
2081
0
  work.chunks = chunks;
2082
0
  work.ntasks = ntasks;
2083
0
  work.buffer = (uint8_t *)buffer;
2084
0
  work.typesize = schunk->typesize;
2085
0
  work.blocksize = schunk->blocksize;
2086
0
  work.chunk_nitems = chunk_nitems;
2087
0
  work.next_task = 0;
2088
0
  work.error = 0;
2089
0
  blosc2_pthread_mutex_init(&work.mutex, NULL);
2090
2091
0
  for (int16_t i = 0; i < nthreads; ++i) {
2092
0
    blosc2_dparams dparams = BLOSC2_DPARAMS_DEFAULTS;
2093
0
    dparams.nthreads = 1;
2094
0
    dparams.schunk = schunk;
2095
0
    workers[i].work = &work;
2096
0
    workers[i].dctx = blosc2_create_dctx(dparams);
2097
0
    workers[i].block = malloc((size_t)schunk->blocksize);
2098
0
    if (workers[i].dctx == NULL || workers[i].block == NULL) {
2099
0
      rc = BLOSC2_ERROR_MEMORY_ALLOC;
2100
0
      goto cleanup_work_mutex;
2101
0
    }
2102
0
  }
2103
2104
0
  if (nthreads == 1) {
2105
0
    sparse_worker_func(&workers[0]);
2106
0
  }
2107
0
  else {
2108
0
    int err = blosc2_run_parallel(nthreads, sparse_worker_func, sizeof(sparse_worker), workers);
2109
0
    if (err < 0) {
2110
0
      sparse_work_set_error(&work, err);
2111
0
    }
2112
0
  }
2113
2114
0
  if (work.error < 0) {
2115
0
    rc = work.error;
2116
0
  }
2117
2118
0
cleanup_work_mutex:
2119
0
  blosc2_pthread_mutex_destroy(&work.mutex);
2120
0
cleanup:
2121
0
  if (workers != NULL) {
2122
0
    int16_t nworkers = nthreads > 0 ? nthreads : 1;
2123
0
    for (int16_t i = 0; i < nworkers; ++i) {
2124
0
      if (workers[i].dctx != NULL) {
2125
0
        blosc2_free_ctx(workers[i].dctx);
2126
0
      }
2127
0
      free(workers[i].block);
2128
0
    }
2129
0
  }
2130
0
  if (chunks != NULL) {
2131
0
    for (int64_t i = 0; i < nchunks; ++i) {
2132
0
      if (chunks[i].needs_free) {
2133
0
        free(chunks[i].chunk);
2134
0
      }
2135
0
    }
2136
0
  }
2137
0
  free(workers);
2138
0
  free(chunks);
2139
0
  free(tasks);
2140
0
  free(entries);
2141
2142
0
  return rc;
2143
0
}
2144
2145
2146
0
int blosc2_schunk_set_slice_buffer(blosc2_schunk *schunk, int64_t start, int64_t stop, void *buffer) {
2147
0
  int64_t byte_start = start * schunk->typesize;
2148
0
  int64_t byte_stop = stop * schunk->typesize;
2149
0
  int64_t nchunk_start = byte_start / schunk->chunksize;
2150
0
  int32_t chunk_start = (int32_t) (byte_start % schunk->chunksize);
2151
0
  int32_t chunk_stop;
2152
0
  if (byte_stop >= (nchunk_start + 1) * schunk->chunksize) {
2153
0
    chunk_stop = schunk->chunksize;
2154
0
  }
2155
0
  else {
2156
0
    chunk_stop = (int32_t) (byte_stop % schunk->chunksize);
2157
0
  }
2158
2159
0
  uint8_t *src_ptr = (uint8_t *) buffer;
2160
0
  int64_t nchunk = nchunk_start;
2161
0
  int64_t nbytes_written = 0;
2162
0
  int32_t nbytes;
2163
0
  uint8_t *data = malloc(schunk->chunksize);
2164
0
  int64_t nchunks;
2165
0
  int32_t chunksize = schunk->chunksize;
2166
2167
0
  while (nbytes_written < ((stop - start) * schunk->typesize)) {
2168
0
    if (chunk_start == 0 &&
2169
0
        (chunk_stop == schunk->chunksize || chunk_stop == schunk->nbytes % schunk->chunksize)) {
2170
0
      if (chunk_stop == schunk->nbytes % schunk->chunksize) {
2171
0
        chunksize = chunk_stop;
2172
0
      }
2173
0
      uint8_t *chunk = malloc(chunksize + BLOSC2_MAX_OVERHEAD);
2174
0
      if (blosc2_compress_ctx(schunk->cctx, src_ptr, chunksize, chunk, chunksize + BLOSC2_MAX_OVERHEAD) < 0) {
2175
0
        BLOSC_TRACE_ERROR("Cannot compress data of chunk ('%" PRId64 "').", nchunk);
2176
0
        return BLOSC2_ERROR_FAILURE;
2177
0
      }
2178
0
      nchunks = blosc2_schunk_update_chunk(schunk, nchunk, chunk, false);
2179
0
      if (nchunks != schunk->nchunks) {
2180
0
        BLOSC_TRACE_ERROR("Cannot update chunk ('%" PRId64 "').", nchunk);
2181
0
        return BLOSC2_ERROR_CHUNK_UPDATE;
2182
0
      }
2183
0
    }
2184
0
    else {
2185
0
      nbytes = blosc2_schunk_decompress_chunk(schunk, nchunk, data, schunk->chunksize);
2186
0
      if (nbytes < 0) {
2187
0
        BLOSC_TRACE_ERROR("Cannot decompress chunk ('%" PRId64 "').", nchunk);
2188
0
        return BLOSC2_ERROR_FAILURE;
2189
0
      }
2190
0
      memcpy(&data[chunk_start], src_ptr, chunk_stop - chunk_start);
2191
0
      uint8_t *chunk = malloc(nbytes + BLOSC2_MAX_OVERHEAD);
2192
0
      if (blosc2_compress_ctx(schunk->cctx, data, nbytes, chunk, nbytes + BLOSC2_MAX_OVERHEAD) < 0) {
2193
0
        BLOSC_TRACE_ERROR("Cannot compress data of chunk ('%" PRId64 "').", nchunk);
2194
0
        return BLOSC2_ERROR_FAILURE;
2195
0
      }
2196
0
      nchunks = blosc2_schunk_update_chunk(schunk, nchunk, chunk, false);
2197
0
      if (nchunks != schunk->nchunks) {
2198
0
        BLOSC_TRACE_ERROR("Cannot update chunk ('%" PRId64 "').", nchunk);
2199
0
        return BLOSC2_ERROR_CHUNK_UPDATE;
2200
0
      }
2201
0
    }
2202
0
    nchunk++;
2203
0
    nbytes_written += chunk_stop - chunk_start;
2204
0
    src_ptr += chunk_stop - chunk_start;
2205
0
    chunk_start = 0;
2206
0
    if (byte_stop >= (nchunk + 1) * schunk->chunksize) {
2207
0
      chunk_stop = schunk->chunksize;
2208
0
    }
2209
0
    else {
2210
0
      chunk_stop = (int32_t) (byte_stop % schunk->chunksize);
2211
0
    }
2212
0
  }
2213
0
  free(data);
2214
2215
0
  return BLOSC2_ERROR_SUCCESS;
2216
0
}
2217
2218
2219
0
int64_t schunk_get_slice_nchunks(blosc2_schunk *schunk, int64_t start, int64_t stop, int64_t **chunks_idx) {
2220
0
  BLOSC_ERROR_NULL(schunk, BLOSC2_ERROR_NULL_POINTER);
2221
0
  if (schunk->nchunks == 0){
2222
0
    *chunks_idx = NULL;
2223
0
    return 0;
2224
0
  }
2225
0
  int64_t byte_start = start * schunk->typesize;
2226
0
  int64_t byte_stop = stop * schunk->typesize;
2227
0
  int64_t nchunk_start = byte_start / schunk->chunksize;
2228
0
  int64_t nchunk_stop = byte_stop / schunk->chunksize;
2229
0
  if (byte_stop % schunk->chunksize != 0) {
2230
0
    nchunk_stop++;
2231
0
  }
2232
0
  int64_t nchunk = nchunk_start;
2233
0
  int64_t nchunks = nchunk_stop - nchunk_start;
2234
0
  *chunks_idx = malloc(nchunks * sizeof(int64_t));
2235
0
  int64_t *ptr = *chunks_idx;
2236
0
  for (int64_t i = 0; i < nchunks; ++i) {
2237
0
    ptr[i] = nchunk;
2238
0
    nchunk++;
2239
0
  }
2240
0
  return nchunks;
2241
0
}
2242
2243
/* Reorder the chunk offsets of an existing super-chunk. */
2244
0
int blosc2_schunk_reorder_offsets(blosc2_schunk *schunk, int64_t *offsets_order) {
2245
  // Check that the offsets order are correct
2246
0
  bool *index_check = (bool *) calloc(schunk->nchunks, sizeof(bool));
2247
0
  for (int i = 0; i < schunk->nchunks; ++i) {
2248
0
    int64_t index = offsets_order[i];
2249
0
    if (index < 0 || index >= schunk->nchunks) {
2250
0
      BLOSC_TRACE_ERROR("Index is out of range (negative or >= number of chunks).");
2251
0
      free(index_check);
2252
0
      return BLOSC2_ERROR_DATA;
2253
0
    }
2254
0
    if (index_check[index] == false) {
2255
0
      index_check[index] = true;
2256
0
    } else {
2257
0
      BLOSC_TRACE_ERROR("Index is yet used.");
2258
0
      free(index_check);
2259
0
      return BLOSC2_ERROR_DATA;
2260
0
    }
2261
0
  }
2262
0
  free(index_check);
2263
2264
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
2265
0
  if (frame != NULL) {
2266
0
    int rc = frame_lock(frame, true);
2267
0
    if (rc < 0) {
2268
0
      return rc;
2269
0
    }
2270
0
    rc = frame_reorder_offsets(frame, offsets_order, schunk);
2271
0
    frame_unlock(frame);
2272
0
    return rc;
2273
0
  }
2274
0
  uint8_t **offsets = schunk->data;
2275
2276
  // Make a copy of the chunk offsets and reorder it
2277
0
  uint8_t **offsets_copy = malloc(schunk->data_len);
2278
0
  memcpy(offsets_copy, offsets, schunk->data_len);
2279
2280
0
  for (int i = 0; i < schunk->nchunks; ++i) {
2281
0
    offsets[i] = offsets_copy[offsets_order[i]];
2282
0
  }
2283
0
  free(offsets_copy);
2284
2285
0
  return 0;
2286
0
}
2287
2288
2289
// Get the length (in bytes) of the internal frame of the super-chunk
2290
0
int64_t blosc2_schunk_frame_len(blosc2_schunk* schunk) {
2291
0
  int64_t len;
2292
0
  blosc2_frame_s* frame_s = (blosc2_frame_s*)(schunk->frame);
2293
0
  if (frame_s != NULL) {
2294
0
    len = frame_s->len;
2295
0
  }
2296
0
  else {
2297
    // No attached frame, but we can still come with an estimate
2298
0
    len = (int64_t) (schunk->cbytes + schunk->nchunks * sizeof(int64_t));
2299
0
  }
2300
2301
0
  return len;
2302
0
}
2303
2304
2305
/**
2306
 * @brief Flush metalayers content into a possible attached frame.
2307
 *
2308
 * @param schunk The super-chunk to which the flush should be applied.
2309
 *
2310
 * @return If successful, a 0 is returned. Else, return a negative value.
2311
 */
2312
// Initially, this was a public function, but as it is really meant to be used only
2313
// in the schunk_add_metalayer(), I decided to convert it into private and call it
2314
// implicitly instead of requiring the user to do so.  The only drawback is that
2315
// each add operation requires a complete frame re-build, but as users should need
2316
// very few metalayers, this overhead should be negligible in practice.
2317
0
int metalayer_flush(blosc2_schunk* schunk) {
2318
0
  int rc = BLOSC2_ERROR_SUCCESS;
2319
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
2320
0
  if (frame == NULL) {
2321
0
    return rc;
2322
0
  }
2323
0
  rc = frame_lock(frame, true);
2324
0
  if (rc < 0) {
2325
0
    return rc;
2326
0
  }
2327
0
  rc = frame_update_header(frame, schunk, true);
2328
0
  if (rc < 0) {
2329
0
    BLOSC_TRACE_ERROR("Unable to update metalayers into frame.");
2330
0
    frame_unlock(frame);
2331
0
    return rc;
2332
0
  }
2333
0
  rc = frame_update_trailer(frame, schunk);
2334
0
  if (rc < 0) {
2335
0
    BLOSC_TRACE_ERROR("Unable to update trailer into frame.");
2336
0
    frame_unlock(frame);
2337
0
    return rc;
2338
0
  }
2339
0
  frame_unlock(frame);
2340
0
  return rc;
2341
0
}
2342
2343
2344
/* Add content into a new metalayer.
2345
 *
2346
 * If successful, return the index of the new metalayer.  Else, return a negative value.
2347
 */
2348
0
int blosc2_meta_add(blosc2_schunk *schunk, const char *name, uint8_t *content, int32_t content_len) {
2349
0
  if (schunk == NULL || name == NULL) {
2350
0
    BLOSC_TRACE_ERROR("Invalid parameters.");
2351
0
    return BLOSC2_ERROR_INVALID_PARAM;
2352
0
  }
2353
0
  int nmetalayer = blosc2_meta_exists(schunk, name);
2354
0
  if (nmetalayer >= 0) {
2355
0
    BLOSC_TRACE_ERROR("Metalayer \"%s\" already exists.", name);
2356
0
    return BLOSC2_ERROR_INVALID_PARAM;
2357
0
  }
2358
0
  if (content_len < 0) {
2359
0
    BLOSC_TRACE_ERROR("Metalayer content length must not be negative.");
2360
0
    return BLOSC2_ERROR_INVALID_PARAM;
2361
0
  }
2362
0
  if (content_len > 0 && content == NULL) {
2363
0
    BLOSC_TRACE_ERROR("Metalayer content pointer must not be NULL when content length is positive.");
2364
0
    return BLOSC2_ERROR_INVALID_PARAM;
2365
0
  }
2366
2367
  // Add the metalayer
2368
0
  blosc2_metalayer *metalayer = malloc(sizeof(blosc2_metalayer));
2369
0
  BLOSC_ERROR_NULL(metalayer, BLOSC2_ERROR_MEMORY_ALLOC);
2370
2371
0
  char* name_ = malloc(strlen(name) + 1);
2372
0
  if (name_ == NULL) {
2373
0
    free(metalayer);
2374
0
    BLOSC_TRACE_ERROR("Unable to allocate metalayer name buffer.");
2375
0
    return BLOSC2_ERROR_MEMORY_ALLOC;
2376
0
  }
2377
0
  strcpy(name_, name);
2378
0
  metalayer->name = name_;
2379
2380
0
  uint8_t* content_buf = malloc((size_t)content_len);
2381
0
  if (content_buf == NULL && content_len > 0) {
2382
0
    free(name_);
2383
0
    free(metalayer);
2384
0
    BLOSC_TRACE_ERROR("Unable to allocate metalayer content buffer.");
2385
0
    return BLOSC2_ERROR_MEMORY_ALLOC;
2386
0
  }
2387
0
  if (content_len > 0) {
2388
0
    memcpy(content_buf, content, content_len);
2389
0
  }
2390
0
  metalayer->content = content_buf;
2391
0
  metalayer->content_len = content_len;
2392
0
  schunk->metalayers[schunk->nmetalayers] = metalayer;
2393
0
  schunk->nmetalayers += 1;
2394
2395
0
  int rc = metalayer_flush(schunk);
2396
0
  if (rc < 0) {
2397
0
    return rc;
2398
0
  }
2399
0
  schunk->change_tick++;
2400
2401
0
  return schunk->nmetalayers - 1;
2402
0
}
2403
2404
2405
/* Update the content of an existing metalayer.
2406
 *
2407
 * If successful, return the index of the new metalayer.  Else, return a negative value.
2408
 */
2409
0
int blosc2_meta_update(blosc2_schunk *schunk, const char *name, uint8_t *content, int32_t content_len) {
2410
0
  if (schunk == NULL || name == NULL) {
2411
0
    BLOSC_TRACE_ERROR("Invalid parameters.");
2412
0
    return BLOSC2_ERROR_INVALID_PARAM;
2413
0
  }
2414
0
  int nmetalayer = blosc2_meta_exists(schunk, name);
2415
0
  if (nmetalayer < 0) {
2416
0
    BLOSC_TRACE_ERROR("Metalayer \"%s\" not found.", name);
2417
0
    return nmetalayer;
2418
0
  }
2419
0
  if (content_len < 0) {
2420
0
    BLOSC_TRACE_ERROR("Metalayer content length must not be negative.");
2421
0
    return BLOSC2_ERROR_INVALID_PARAM;
2422
0
  }
2423
0
  if (content_len > 0 && content == NULL) {
2424
0
    BLOSC_TRACE_ERROR("Metalayer content pointer must not be NULL when content length is positive.");
2425
0
    return BLOSC2_ERROR_INVALID_PARAM;
2426
0
  }
2427
2428
0
  blosc2_metalayer *metalayer = schunk->metalayers[nmetalayer];
2429
0
  if (content_len > metalayer->content_len) {
2430
0
    BLOSC_TRACE_ERROR("`content_len` cannot exceed the existing size of %d bytes.", metalayer->content_len);
2431
0
    return nmetalayer;
2432
0
  }
2433
2434
  // Update the contents of the metalayer
2435
0
  if (content_len > 0) {
2436
0
    memcpy(metalayer->content, content, content_len);
2437
0
  }
2438
2439
  // Update the metalayers in frame (as size has not changed, we don't need to update the trailer)
2440
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
2441
0
  if (frame != NULL) {
2442
0
    int rc = frame_update_header(frame, schunk, false);
2443
0
    if (rc < 0) {
2444
0
      BLOSC_TRACE_ERROR("Unable to update meta info from frame.");
2445
0
      return rc;
2446
0
    }
2447
0
  }
2448
0
  schunk->change_tick++;
2449
2450
0
  return nmetalayer;
2451
0
}
2452
2453
2454
/* Find whether the schunk has a variable-length metalayer or not.
2455
 *
2456
 * If successful, return the index of the variable-length metalayer.  Else, return a negative value.
2457
 */
2458
0
int blosc2_vlmeta_exists(blosc2_schunk *schunk, const char *name) {
2459
0
  if (strlen(name) > BLOSC2_METALAYER_NAME_MAXLEN) {
2460
0
    BLOSC_TRACE_ERROR("Variable-length metalayer names cannot be larger than %d chars.", BLOSC2_METALAYER_NAME_MAXLEN);
2461
0
    return BLOSC2_ERROR_INVALID_PARAM;
2462
0
  }
2463
  // Re-sync the cached vlmetalayers if another handle rewrote the frame, so
2464
  // that a vlmetalayer added or deleted elsewhere is (not) found.  The other
2465
  // vlmeta entry points (add/get/update/delete) all pass through here, so
2466
  // this is the single sync point for the vlmetalayers.
2467
0
  if (schunk->frame != NULL) {
2468
0
    int rc = frame_check_stale((blosc2_frame_s*)schunk->frame);
2469
0
    if (rc < 0) {
2470
0
      return rc;
2471
0
    }
2472
0
  }
2473
2474
0
  for (int nvlmetalayer = 0; nvlmetalayer < schunk->nvlmetalayers; nvlmetalayer++) {
2475
0
    if (strcmp(name, schunk->vlmetalayers[nvlmetalayer]->name) == 0) {
2476
0
      return nvlmetalayer;
2477
0
    }
2478
0
  }
2479
0
  return BLOSC2_ERROR_NOT_FOUND;
2480
0
}
2481
2482
0
int vlmetalayer_flush(blosc2_schunk* schunk) {
2483
0
  int rc = BLOSC2_ERROR_SUCCESS;
2484
0
  blosc2_frame_s* frame = (blosc2_frame_s*)schunk->frame;
2485
0
  if (frame == NULL) {
2486
0
    return rc;
2487
0
  }
2488
0
  rc = frame_lock(frame, true);
2489
0
  if (rc < 0) {
2490
0
    return rc;
2491
0
  }
2492
0
  rc = frame_update_header(frame, schunk, false);
2493
0
  if (rc < 0) {
2494
0
    BLOSC_TRACE_ERROR("Unable to update metalayers into frame.");
2495
0
    frame_unlock(frame);
2496
0
    return rc;
2497
0
  }
2498
0
  rc = frame_update_trailer(frame, schunk);
2499
0
  if (rc < 0) {
2500
0
    BLOSC_TRACE_ERROR("Unable to update trailer into frame.");
2501
0
    frame_unlock(frame);
2502
0
    return rc;
2503
0
  }
2504
0
  frame_unlock(frame);
2505
0
  return rc;
2506
0
}
2507
2508
/* Add content into a new variable-length metalayer.
2509
 *
2510
 * If successful, return the index of the new variable-length metalayer.  Else, return a negative value.
2511
 */
2512
int blosc2_vlmeta_add(blosc2_schunk *schunk, const char *name, uint8_t *content, int32_t content_len,
2513
0
                      blosc2_cparams *cparams) {
2514
0
  if (schunk == NULL || name == NULL) {
2515
0
    BLOSC_TRACE_ERROR("Invalid parameters.");
2516
0
    return BLOSC2_ERROR_INVALID_PARAM;
2517
0
  }
2518
0
  int nvlmetalayer = blosc2_vlmeta_exists(schunk, name);
2519
0
  if (nvlmetalayer >= 0) {
2520
0
    BLOSC_TRACE_ERROR("Variable-length metalayer \"%s\" already exists.", name);
2521
0
    return BLOSC2_ERROR_INVALID_PARAM;
2522
0
  }
2523
0
  if (content_len < 0) {
2524
0
    BLOSC_TRACE_ERROR("Variable-length metalayer content length must not be negative.");
2525
0
    return BLOSC2_ERROR_INVALID_PARAM;
2526
0
  }
2527
0
  if (content_len > 0 && content == NULL) {
2528
0
    BLOSC_TRACE_ERROR("Variable-length metalayer content pointer must not be NULL when content length is positive.");
2529
0
    return BLOSC2_ERROR_INVALID_PARAM;
2530
0
  }
2531
2532
  // Add the vlmetalayer
2533
0
  blosc2_metalayer *vlmetalayer = malloc(sizeof(blosc2_metalayer));
2534
0
  BLOSC_ERROR_NULL(vlmetalayer, BLOSC2_ERROR_MEMORY_ALLOC);
2535
0
  vlmetalayer->name = strdup(name);
2536
0
  if (vlmetalayer->name == NULL) {
2537
0
    free(vlmetalayer);
2538
0
    BLOSC_TRACE_ERROR("Unable to allocate variable-length metalayer name buffer.");
2539
0
    return BLOSC2_ERROR_MEMORY_ALLOC;
2540
0
  }
2541
2542
0
  int64_t destsize = (int64_t)content_len + BLOSC2_MAX_OVERHEAD;
2543
0
  if (destsize > INT32_MAX) {
2544
0
    free(vlmetalayer->name);
2545
0
    free(vlmetalayer);
2546
0
    BLOSC_TRACE_ERROR("Variable-length metalayer size is too large.");
2547
0
    return BLOSC2_ERROR_INVALID_PARAM;
2548
0
  }
2549
0
  size_t destsize_sz = (size_t)destsize;
2550
0
  uint8_t* content_buf = malloc(destsize_sz);
2551
0
  if (content_buf == NULL) {
2552
0
    free(vlmetalayer->name);
2553
0
    free(vlmetalayer);
2554
0
    BLOSC_TRACE_ERROR("Unable to allocate variable-length metalayer buffer.");
2555
0
    return BLOSC2_ERROR_MEMORY_ALLOC;
2556
0
  }
2557
2558
0
  blosc2_context *cctx;
2559
0
  if (cparams != NULL) {
2560
0
    cctx = blosc2_create_cctx(*cparams);
2561
0
  } else {
2562
0
    cctx = blosc2_create_cctx(BLOSC2_CPARAMS_DEFAULTS);
2563
0
  }
2564
0
  if (cctx == NULL) {
2565
0
    free(content_buf);
2566
0
    free(vlmetalayer->name);
2567
0
    free(vlmetalayer);
2568
0
    BLOSC_TRACE_ERROR("Error while creating the compression context");
2569
0
    return BLOSC2_ERROR_NULL_POINTER;
2570
0
  }
2571
2572
0
  int csize = blosc2_compress_ctx(cctx, content, content_len, content_buf, (int32_t)destsize);
2573
0
  if (csize < 0) {
2574
0
    blosc2_free_ctx(cctx);
2575
0
    free(content_buf);
2576
0
    free(vlmetalayer->name);
2577
0
    free(vlmetalayer);
2578
0
    BLOSC_TRACE_ERROR("Can not compress the `%s` variable-length metalayer.", name);
2579
0
    return csize;
2580
0
  }
2581
0
  blosc2_free_ctx(cctx);
2582
2583
0
  uint8_t *compressed_buf = realloc(content_buf, csize);
2584
0
  if (compressed_buf == NULL && csize > 0) {
2585
0
    compressed_buf = content_buf;
2586
0
  }
2587
0
  vlmetalayer->content = compressed_buf;
2588
0
  vlmetalayer->content_len = csize;
2589
0
  schunk->vlmetalayers[schunk->nvlmetalayers] = vlmetalayer;
2590
0
  schunk->nvlmetalayers += 1;
2591
2592
  // Propagate to frames
2593
0
  int rc = vlmetalayer_flush(schunk);
2594
0
  if (rc < 0) {
2595
0
    schunk->nvlmetalayers -= 1;
2596
0
    schunk->vlmetalayers[schunk->nvlmetalayers] = NULL;
2597
0
    free(vlmetalayer->content);
2598
0
    free(vlmetalayer->name);
2599
0
    free(vlmetalayer);
2600
0
    BLOSC_TRACE_ERROR("Can not propagate de `%s` variable-length metalayer to a frame.", name);
2601
0
    return rc;
2602
0
  }
2603
0
  schunk->change_tick++;
2604
2605
0
  return schunk->nvlmetalayers - 1;
2606
0
}
2607
2608
2609
int blosc2_vlmeta_get(blosc2_schunk *schunk, const char *name, uint8_t **content,
2610
0
                      int32_t *content_len) {
2611
0
  if (schunk == NULL || name == NULL || content == NULL || content_len == NULL) {
2612
0
    BLOSC_TRACE_ERROR("Invalid parameters.");
2613
0
    return BLOSC2_ERROR_INVALID_PARAM;
2614
0
  }
2615
  // blosc2_vlmeta_exists() re-syncs the cached vlmetalayers if another handle
2616
  // rewrote the frame
2617
0
  int nvlmetalayer = blosc2_vlmeta_exists(schunk, name);
2618
0
  if (nvlmetalayer < 0) {
2619
0
    BLOSC_TRACE_ERROR("User metalayer \"%s\" not found.", name);
2620
0
    return nvlmetalayer;
2621
0
  }
2622
0
  blosc2_metalayer *meta = schunk->vlmetalayers[nvlmetalayer];
2623
0
  int32_t nbytes, cbytes;
2624
0
  blosc2_cbuffer_sizes(meta->content, &nbytes, &cbytes, NULL);
2625
0
  if (cbytes != meta->content_len) {
2626
0
    BLOSC_TRACE_ERROR("User metalayer \"%s\" is corrupted.", meta->name);
2627
0
    return BLOSC2_ERROR_DATA;
2628
0
  }
2629
0
  if (nbytes < 0) {
2630
0
    BLOSC_TRACE_ERROR("User metalayer \"%s\" has corrupted decompressed size %d.", meta->name, nbytes);
2631
0
    return BLOSC2_ERROR_DATA;
2632
0
  }
2633
0
  *content_len = nbytes;
2634
0
  if (nbytes == 0) {
2635
0
    *content = NULL;
2636
0
  } else {
2637
0
    *content = malloc((size_t) nbytes);
2638
0
    if (*content == NULL) {
2639
0
      BLOSC_TRACE_ERROR("Unable to allocate variable-length metalayer content buffer.");
2640
0
      *content_len = 0;
2641
0
      return BLOSC2_ERROR_MEMORY_ALLOC;
2642
0
    }
2643
0
  }
2644
0
  blosc2_context *dctx = blosc2_create_dctx(*schunk->storage->dparams);
2645
0
  if (dctx == NULL) {
2646
0
    if (*content != NULL) {
2647
0
      free(*content);
2648
0
      *content = NULL;
2649
0
    }
2650
0
    *content_len = 0;
2651
0
    BLOSC_TRACE_ERROR("Error while creating the decompression context");
2652
0
    return BLOSC2_ERROR_NULL_POINTER;
2653
0
  }
2654
0
  int nbytes_ = blosc2_decompress_ctx(dctx, meta->content, meta->content_len, *content, nbytes);
2655
0
  blosc2_free_ctx(dctx);
2656
0
  if (nbytes_ != nbytes) {
2657
0
    if (*content != NULL) {
2658
0
      free(*content);
2659
0
      *content = NULL;
2660
0
    }
2661
0
    *content_len = 0;
2662
0
    BLOSC_TRACE_ERROR("User metalayer \"%s\" is corrupted.", meta->name);
2663
0
    return BLOSC2_ERROR_READ_BUFFER;
2664
0
  }
2665
0
  return nvlmetalayer;
2666
0
}
2667
2668
int blosc2_vlmeta_update(blosc2_schunk *schunk, const char *name, uint8_t *content, int32_t content_len,
2669
0
                         blosc2_cparams *cparams) {
2670
0
  if (schunk == NULL || name == NULL) {
2671
0
    BLOSC_TRACE_ERROR("Invalid parameters.");
2672
0
    return BLOSC2_ERROR_INVALID_PARAM;
2673
0
  }
2674
0
  int nvlmetalayer = blosc2_vlmeta_exists(schunk, name);
2675
0
  if (nvlmetalayer < 0) {
2676
0
    BLOSC_TRACE_ERROR("User vlmetalayer \"%s\" not found.", name);
2677
0
    return nvlmetalayer;
2678
0
  }
2679
0
  if (content_len < 0) {
2680
0
    BLOSC_TRACE_ERROR("Variable-length metalayer content length must not be negative.");
2681
0
    return BLOSC2_ERROR_INVALID_PARAM;
2682
0
  }
2683
0
  if (content_len > 0 && content == NULL) {
2684
0
    BLOSC_TRACE_ERROR("Variable-length metalayer content pointer must not be NULL when content length is positive.");
2685
0
    return BLOSC2_ERROR_INVALID_PARAM;
2686
0
  }
2687
2688
0
  blosc2_metalayer *vlmetalayer = schunk->vlmetalayers[nvlmetalayer];
2689
0
  int64_t destsize = (int64_t)content_len + BLOSC2_MAX_OVERHEAD;
2690
0
  if (destsize > INT32_MAX) {
2691
0
    BLOSC_TRACE_ERROR("Variable-length metalayer size is too large.");
2692
0
    return BLOSC2_ERROR_INVALID_PARAM;
2693
0
  }
2694
0
  size_t destsize_sz = (size_t)destsize;
2695
0
  uint8_t* content_buf = malloc(destsize_sz);
2696
0
  if (content_buf == NULL && content_len > 0) {
2697
0
    BLOSC_TRACE_ERROR("Unable to allocate variable-length metalayer buffer.");
2698
0
    return BLOSC2_ERROR_MEMORY_ALLOC;
2699
0
  }
2700
2701
0
  blosc2_context *cctx;
2702
0
  if (cparams != NULL) {
2703
0
    cctx = blosc2_create_cctx(*cparams);
2704
0
  } else {
2705
0
    cctx = blosc2_create_cctx(BLOSC2_CPARAMS_DEFAULTS);
2706
0
  }
2707
0
  if (cctx == NULL) {
2708
0
    free(content_buf);
2709
0
    BLOSC_TRACE_ERROR("Error while creating the compression context");
2710
0
    return BLOSC2_ERROR_NULL_POINTER;
2711
0
  }
2712
2713
0
  int csize = blosc2_compress_ctx(cctx, content, content_len, content_buf, (int32_t)destsize);
2714
0
  if (csize < 0) {
2715
0
    blosc2_free_ctx(cctx);
2716
0
    free(content_buf);
2717
0
    BLOSC_TRACE_ERROR("Can not compress the `%s` variable-length metalayer.", name);
2718
0
    return csize;
2719
0
  }
2720
0
  blosc2_free_ctx(cctx);
2721
2722
0
  uint8_t* compressed_buf = realloc(content_buf, csize);
2723
0
  if (compressed_buf == NULL && csize > 0) {
2724
0
    compressed_buf = content_buf;
2725
0
  }
2726
0
  uint8_t* old_content = vlmetalayer->content;
2727
0
  vlmetalayer->content = compressed_buf;
2728
0
  vlmetalayer->content_len = csize;
2729
0
  free(old_content);
2730
2731
  // Propagate to frames
2732
0
  int rc = vlmetalayer_flush(schunk);
2733
0
  if (rc < 0) {
2734
0
    BLOSC_TRACE_ERROR("Can not propagate de `%s` variable-length metalayer to a frame.", name);
2735
0
    return rc;
2736
0
  }
2737
0
  schunk->change_tick++;
2738
2739
0
  return nvlmetalayer;
2740
0
}
2741
2742
0
int blosc2_vlmeta_delete(blosc2_schunk *schunk, const char *name) {
2743
0
  int nvlmetalayer = blosc2_vlmeta_exists(schunk, name);
2744
0
  if (nvlmetalayer < 0) {
2745
0
    BLOSC_TRACE_ERROR("User vlmetalayer \"%s\" not found.", name);
2746
0
    return nvlmetalayer;
2747
0
  }
2748
2749
0
  blosc2_metalayer *vlmetalayer = schunk->vlmetalayers[nvlmetalayer];
2750
0
  for (int i = nvlmetalayer; i < (schunk->nvlmetalayers - 1); i++) {
2751
0
    schunk->vlmetalayers[i] = schunk->vlmetalayers[i + 1];
2752
0
  }
2753
0
  schunk->vlmetalayers[schunk->nvlmetalayers - 1] = NULL;
2754
0
  free(vlmetalayer->name);
2755
0
  free(vlmetalayer->content);
2756
0
  free(vlmetalayer);
2757
0
  schunk->nvlmetalayers--;
2758
2759
  // Propagate to frames
2760
0
  int rc = vlmetalayer_flush(schunk);
2761
0
  if (rc < 0) {
2762
0
    BLOSC_TRACE_ERROR("Can not propagate de `%s` variable-length metalayer to a frame.", name);
2763
0
    return rc;
2764
0
  }
2765
0
  schunk->change_tick++;
2766
2767
0
  return schunk->nvlmetalayers;
2768
0
}
2769
2770
2771
0
int blosc2_vlmeta_get_names(blosc2_schunk *schunk, char **names) {
2772
0
  int16_t nvlmetalayers = schunk->nvlmetalayers;
2773
2774
0
  for (int i = 0; i < nvlmetalayers; ++i) {
2775
0
    names[i] = schunk->vlmetalayers[i]->name;
2776
0
  }
2777
2778
0
  return nvlmetalayers;
2779
0
}