Coverage Report

Created: 2026-07-10 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/c-blosc2/blosc/blosc2-stdio.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
#if defined(__linux__)
12
  /* Must be defined before anything else is included */
13
  #define _GNU_SOURCE
14
#endif
15
16
#include "blosc2/blosc2-stdio.h"
17
#include "blosc2.h"
18
19
#include <stdlib.h>
20
#include <stdio.h>
21
#include <stdint.h>
22
#include <limits.h>
23
#include <errno.h>
24
#include <inttypes.h>
25
#include <string.h>
26
27
#if defined(_WIN32)
28
  #include <memoryapi.h>
29
  // See https://github.com/Blosc/python-blosc2/issues/359
30
  #define fseek _fseeki64
31
  #define ftell _ftelli64
32
#else
33
  #include <sys/mman.h>
34
#endif
35
36
37
0
static bool checked_mul_int64_nonneg(int64_t a, int64_t b, int64_t* out) {
38
0
  if (a < 0 || b < 0) {
39
0
    return false;
40
0
  }
41
0
  if (a == 0 || b == 0) {
42
0
    *out = 0;
43
0
    return true;
44
0
  }
45
0
  if (a > INT64_MAX / b) {
46
0
    return false;
47
0
  }
48
0
  *out = a * b;
49
0
  return true;
50
0
}
51
52
0
static bool checked_add_int64_nonneg(int64_t a, int64_t b, int64_t* out) {
53
0
  if (a < 0 || b < 0) {
54
0
    return false;
55
0
  }
56
0
  if (a > INT64_MAX - b) {
57
0
    return false;
58
0
  }
59
0
  *out = a + b;
60
0
  return true;
61
0
}
62
63
0
static bool checked_size_t_to_int64(size_t value, int64_t* out) {
64
0
  if (value > (size_t)INT64_MAX) {
65
0
    return false;
66
0
  }
67
0
  *out = (int64_t)value;
68
0
  return true;
69
0
}
70
71
72
2
void *blosc2_stdio_open(const char *urlpath, const char *mode, void *params) {
73
2
  BLOSC_UNUSED_PARAM(params);
74
2
  if (urlpath == NULL || mode == NULL) {
75
2
    BLOSC_TRACE_ERROR("Invalid arguments for stdio open.");
76
2
    return NULL;
77
2
  }
78
0
  FILE *file = fopen(urlpath, mode);
79
0
  if (file == NULL) {
80
0
    BLOSC_TRACE_ERROR("Cannot open the file %s with mode %s.", urlpath, mode);
81
0
    return NULL;
82
0
  }
83
0
  blosc2_stdio_file *my_fp = malloc(sizeof(blosc2_stdio_file));
84
0
  if (my_fp == NULL) {
85
0
    BLOSC_TRACE_ERROR("Cannot allocate memory for stdio file wrapper.");
86
0
    fclose(file);
87
0
    return NULL;
88
0
  }
89
0
  my_fp->file = file;
90
0
  return my_fp;
91
0
}
92
93
0
int blosc2_stdio_close(void *stream) {
94
0
  if (stream == NULL) {
95
0
    BLOSC_TRACE_ERROR("Invalid stream for stdio close.");
96
0
    return -1;
97
0
  }
98
0
  blosc2_stdio_file *my_fp = (blosc2_stdio_file *) stream;
99
0
  if (my_fp->file == NULL) {
100
0
    BLOSC_TRACE_ERROR("Invalid stream for stdio close.");
101
0
    return -1;
102
0
  }
103
0
  int err = fclose(my_fp->file);
104
0
  free(my_fp);
105
0
  return err;
106
0
}
107
108
0
int64_t blosc2_stdio_size(void *stream) {
109
0
  if (stream == NULL) {
110
0
    BLOSC_TRACE_ERROR("Invalid stream for stdio size.");
111
0
    return -1;
112
0
  }
113
0
  blosc2_stdio_file *my_fp = (blosc2_stdio_file *) stream;
114
0
  if (my_fp->file == NULL) {
115
0
    BLOSC_TRACE_ERROR("Invalid stream for stdio size.");
116
0
    return -1;
117
0
  }
118
119
0
  int64_t current = ftell(my_fp->file);
120
0
  if (current < 0) {
121
0
    BLOSC_TRACE_ERROR("ftell failed while determining current position (error: %s).", strerror(errno));
122
0
    return -1;
123
0
  }
124
125
0
  if (fseek(my_fp->file, 0, SEEK_END) != 0) {
126
0
    BLOSC_TRACE_ERROR("fseek to file end failed while getting size (error: %s).", strerror(errno));
127
0
    fseek(my_fp->file, current, SEEK_SET);
128
0
    return -1;
129
0
  }
130
0
  int64_t size = ftell(my_fp->file);
131
0
  if (size < 0) {
132
0
    BLOSC_TRACE_ERROR("ftell failed while getting file size (error: %s).", strerror(errno));
133
0
    fseek(my_fp->file, current, SEEK_SET);
134
0
    return -1;
135
0
  }
136
137
0
  if (fseek(my_fp->file, current, SEEK_SET) != 0) {
138
0
    BLOSC_TRACE_ERROR("fseek restore failed after getting file size (error: %s).", strerror(errno));
139
0
    return -1;
140
0
  }
141
142
0
  return size;
143
0
}
144
145
0
int64_t blosc2_stdio_write(const void *ptr, int64_t size, int64_t nitems, int64_t position, void *stream) {
146
0
  if (stream == NULL || ptr == NULL || size < 0 || nitems < 0 || position < 0) {
147
0
    BLOSC_TRACE_ERROR("Invalid arguments for stdio write.");
148
0
    return 0;
149
0
  }
150
0
  blosc2_stdio_file *my_fp = (blosc2_stdio_file *) stream;
151
0
  if (my_fp->file == NULL) {
152
0
    BLOSC_TRACE_ERROR("Invalid arguments for stdio write.");
153
0
    return 0;
154
0
  }
155
156
0
  int64_t n_bytes_i64;
157
0
  if (!checked_mul_int64_nonneg(size, nitems, &n_bytes_i64)) {
158
0
    BLOSC_TRACE_ERROR("stdio write size overflow (size=%" PRId64 ", nitems=%" PRId64 ").", size, nitems);
159
0
    return 0;
160
0
  }
161
0
  if ((uint64_t)n_bytes_i64 > SIZE_MAX) {
162
0
    BLOSC_TRACE_ERROR("stdio write size does not fit in size_t (%" PRId64 ").", n_bytes_i64);
163
0
    return 0;
164
0
  }
165
166
0
#if !defined(_WIN32)
167
  /* POSIX fseek takes long; reject offsets that would silently truncate (e.g. on 32-bit). */
168
0
  if (position > (int64_t)LONG_MAX) {
169
0
    BLOSC_TRACE_ERROR("stdio write position %" PRId64 " exceeds LONG_MAX for fseek.", position);
170
0
    return 0;
171
0
  }
172
0
#endif
173
174
0
  int rc = fseek(my_fp->file, position, SEEK_SET);
175
0
  if (rc != 0) {
176
0
    BLOSC_TRACE_ERROR("fseek failed at position %" PRId64 " (error: %s).", position, strerror(errno));
177
0
    return 0;
178
0
  }
179
180
0
  size_t nitems_ = fwrite(ptr, (size_t) size, (size_t) nitems, my_fp->file);
181
0
  if ((int64_t)nitems_ != nitems) {
182
0
    BLOSC_TRACE_ERROR("Short write at position %" PRId64 ": requested %" PRId64 " items of size %" PRId64
183
0
                      ", wrote %zu (error: %s).", position, nitems, size, nitems_, strerror(errno));
184
0
  }
185
0
  return (int64_t) nitems_;
186
0
}
187
188
0
int64_t blosc2_stdio_read(void **ptr, int64_t size, int64_t nitems, int64_t position, void *stream) {
189
0
  if (stream == NULL || ptr == NULL || size < 0 || nitems < 0 || position < 0) {
190
0
    BLOSC_TRACE_ERROR("Invalid arguments for stdio read.");
191
0
    return 0;
192
0
  }
193
0
  blosc2_stdio_file *my_fp = (blosc2_stdio_file *) stream;
194
0
  if (my_fp->file == NULL) {
195
0
    BLOSC_TRACE_ERROR("Invalid arguments for stdio read.");
196
0
    return 0;
197
0
  }
198
199
0
  int64_t n_bytes_i64;
200
0
  if (!checked_mul_int64_nonneg(size, nitems, &n_bytes_i64)) {
201
0
    BLOSC_TRACE_ERROR("stdio read size overflow (size=%" PRId64 ", nitems=%" PRId64 ").", size, nitems);
202
0
    return 0;
203
0
  }
204
0
  if ((uint64_t)n_bytes_i64 > SIZE_MAX) {
205
0
    BLOSC_TRACE_ERROR("stdio read size does not fit in size_t (%" PRId64 ").", n_bytes_i64);
206
0
    return 0;
207
0
  }
208
  /* For allocation-necessary backends, *ptr must point at a real buffer
209
     when the caller asked for any bytes. Leave *ptr untouched on failure. */
210
0
  if (n_bytes_i64 > 0 && *ptr == NULL) {
211
0
    BLOSC_TRACE_ERROR("stdio read called with NULL buffer for %" PRId64 " bytes.", n_bytes_i64);
212
0
    return 0;
213
0
  }
214
215
0
#if !defined(_WIN32)
216
  /* POSIX fseek takes long; reject offsets that would silently truncate (e.g. on 32-bit). */
217
0
  if (position > (int64_t)LONG_MAX) {
218
0
    BLOSC_TRACE_ERROR("stdio read position %" PRId64 " exceeds LONG_MAX for fseek.", position);
219
0
    return 0;
220
0
  }
221
0
#endif
222
223
0
  int rc = fseek(my_fp->file, position, SEEK_SET);
224
0
  if (rc != 0) {
225
0
    BLOSC_TRACE_ERROR("fseek failed at position %" PRId64 " (error: %s).", position, strerror(errno));
226
0
    return 0;
227
0
  }
228
229
0
  void* data_ptr = *ptr;
230
0
  size_t nitems_ = fread(data_ptr, (size_t) size, (size_t) nitems, my_fp->file);
231
0
  if ((int64_t)nitems_ != nitems) {
232
0
    BLOSC_TRACE_ERROR("Short read at position %" PRId64 ": requested %" PRId64 " items of size %" PRId64
233
0
                      ", read %zu (error: %s).", position, nitems, size, nitems_, strerror(errno));
234
0
  }
235
0
  return (int64_t) nitems_;
236
0
}
237
238
0
int blosc2_stdio_truncate(void *stream, int64_t size) {
239
0
  if (stream == NULL || size < 0) {
240
0
    BLOSC_TRACE_ERROR("Invalid arguments for stdio truncate.");
241
0
    return -1;
242
0
  }
243
0
  blosc2_stdio_file *my_fp = (blosc2_stdio_file *) stream;
244
0
  if (my_fp->file == NULL) {
245
0
    BLOSC_TRACE_ERROR("Invalid arguments for stdio truncate.");
246
0
    return -1;
247
0
  }
248
0
  int rc;
249
#if defined(_MSC_VER)
250
  rc = _chsize_s(_fileno(my_fp->file), size);
251
#else
252
0
  off_t size_off_t = (off_t)size;
253
0
  if ((int64_t)size_off_t != size) {
254
0
    BLOSC_TRACE_ERROR("stdio truncate size does not fit in off_t (%" PRId64 ").", size);
255
0
    return -1;
256
0
  }
257
0
  rc = ftruncate(fileno(my_fp->file), size_off_t);
258
0
#endif
259
0
  return rc;
260
0
}
261
262
4.73k
int blosc2_stdio_destroy(void* params) {
263
4.73k
  BLOSC_UNUSED_PARAM(params);
264
4.73k
  return 0;
265
4.73k
}
266
267
#if defined(_WIN32)
268
void _print_last_error() {
269
    DWORD last_error = GetLastError();
270
    if(last_error == 0) {
271
        return;
272
    }
273
274
    LPSTR msg = NULL;
275
    FormatMessage(
276
      FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
277
      NULL,
278
      last_error,
279
      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
280
      (LPSTR)&msg,
281
      0,
282
      NULL
283
    );
284
285
    printf("Message for the error %lu:\n%s\n", last_error, msg);
286
    LocalFree(msg);
287
}
288
#endif
289
290
0
void *blosc2_stdio_mmap_open(const char *urlpath, const char *mode, void* params) {
291
0
  BLOSC_UNUSED_PARAM(mode);
292
293
0
  if (urlpath == NULL || params == NULL) {
294
0
    BLOSC_TRACE_ERROR("Invalid arguments for memory-mapped open.");
295
0
    return NULL;
296
0
  }
297
298
0
  blosc2_stdio_mmap *mmap_file = (blosc2_stdio_mmap *) params;
299
0
  if (mmap_file->addr != NULL) {
300
0
    if (mmap_file->urlpath == NULL) {
301
0
      BLOSC_TRACE_ERROR("Memory-mapped file has invalid state: urlpath is NULL.");
302
0
      return NULL;
303
0
    }
304
0
    if (strcmp(mmap_file->urlpath, urlpath) != 0) {
305
0
      BLOSC_TRACE_ERROR(
306
0
        "The memory-mapped file is already opened with the path %s and hence cannot be reopened with the path %s. This "
307
0
        "happens if you try to open a sframe (sparse frame); please note that memory-mapped files are not supported "
308
0
        "for sframes.",
309
0
        mmap_file->urlpath,
310
0
        urlpath
311
0
      );
312
0
      return NULL;
313
0
    }
314
315
    /* A memory-mapped file is only opened once */
316
0
    return mmap_file;
317
0
  }
318
319
0
  if (mmap_file->mode == NULL) {
320
0
    BLOSC_TRACE_ERROR("Memory-mapped mode is NULL.");
321
0
    return NULL;
322
0
  }
323
324
  // Keep the original path to ensure that all future file openings are with the same path
325
0
  size_t urlpath_len = strlen(urlpath);
326
0
  mmap_file->urlpath = malloc(urlpath_len + 1);
327
0
  if (mmap_file->urlpath == NULL) {
328
0
    BLOSC_TRACE_ERROR("Cannot allocate memory for the path of the memory-mapped file.");
329
0
    return NULL;
330
0
  }
331
0
  memcpy(mmap_file->urlpath, urlpath, urlpath_len + 1);
332
333
  /* mmap_file->mode mapping is similar to Numpy's memmap
334
  (https://github.com/numpy/numpy/blob/main/numpy/_core/memmap.py) and CPython
335
  (https://github.com/python/cpython/blob/main/Modules/mmapmodule.c) */
336
#if defined(_WIN32)
337
  char* open_mode;
338
  bool use_initial_mapping_size;
339
  if (strcmp(mmap_file->mode, "r") == 0) {
340
    mmap_file->access_flags = PAGE_READONLY;
341
    mmap_file->map_flags = FILE_MAP_READ;
342
    mmap_file->is_memory_only = false;
343
    open_mode = "rb";
344
    use_initial_mapping_size = false;
345
  } else if (strcmp(mmap_file->mode, "r+") == 0) {
346
    mmap_file->access_flags = PAGE_READWRITE;
347
    mmap_file->map_flags = FILE_MAP_WRITE;
348
    mmap_file->is_memory_only = false;
349
    open_mode = "rb+";
350
    use_initial_mapping_size = true;
351
  } else if (strcmp(mmap_file->mode, "w+") == 0) {
352
    mmap_file->access_flags = PAGE_READWRITE;
353
    mmap_file->map_flags = FILE_MAP_WRITE;
354
    mmap_file->is_memory_only = false;
355
    open_mode = "wb+";
356
    use_initial_mapping_size = true;
357
  } else if (strcmp(mmap_file->mode, "c") == 0) {
358
    mmap_file->access_flags = PAGE_WRITECOPY;
359
    mmap_file->map_flags = FILE_MAP_COPY;
360
    mmap_file->is_memory_only = true;
361
    open_mode = "rb";
362
    use_initial_mapping_size = false;
363
  } else {
364
    BLOSC_TRACE_ERROR("Mode %s not supported for memory-mapped files.", mmap_file->mode);
365
    free(mmap_file->urlpath);
366
    mmap_file->urlpath = NULL;
367
    return NULL;
368
  }
369
#else
370
0
  char* open_mode;
371
0
  bool use_initial_mapping_size;
372
0
  if (strcmp(mmap_file->mode, "r") == 0) {
373
0
    mmap_file->access_flags = PROT_READ;
374
0
    mmap_file->map_flags = MAP_SHARED;
375
0
    mmap_file->is_memory_only = false;
376
0
    open_mode = "rb";
377
0
    use_initial_mapping_size = false;
378
0
  } else if (strcmp(mmap_file->mode, "r+") == 0) {
379
0
    mmap_file->access_flags = PROT_READ | PROT_WRITE;
380
0
    mmap_file->map_flags = MAP_SHARED;
381
0
    mmap_file->is_memory_only = false;
382
0
    open_mode = "rb+";
383
0
    use_initial_mapping_size = true;
384
0
  } else if (strcmp(mmap_file->mode, "w+") == 0) {
385
0
    mmap_file->access_flags = PROT_READ | PROT_WRITE;
386
0
    mmap_file->map_flags = MAP_SHARED;
387
0
    mmap_file->is_memory_only = false;
388
0
    open_mode = "wb+";
389
0
    use_initial_mapping_size = true;
390
0
  } else if (strcmp(mmap_file->mode, "c") == 0) {
391
0
    mmap_file->access_flags = PROT_READ | PROT_WRITE;
392
0
    mmap_file->map_flags = MAP_PRIVATE;
393
0
    mmap_file->is_memory_only = true;
394
0
    open_mode = "rb";
395
0
    use_initial_mapping_size = true;
396
0
  } else {
397
0
    BLOSC_TRACE_ERROR("Mode %s not supported for memory-mapped files.", mmap_file->mode);
398
0
    free(mmap_file->urlpath);
399
0
    mmap_file->urlpath = NULL;
400
0
    return NULL;
401
0
  }
402
0
#endif
403
404
0
  mmap_file->file = fopen(urlpath, open_mode);
405
0
  if (mmap_file->file == NULL) {
406
0
    BLOSC_TRACE_ERROR("Cannot open the file %s with mode %s.", urlpath, open_mode);
407
0
    free(mmap_file->urlpath);
408
0
    mmap_file->urlpath = NULL;
409
0
    return NULL;
410
0
  }
411
412
  /* Retrieve the size of the file */
413
0
  if (fseek(mmap_file->file, 0, SEEK_END) != 0) {
414
0
    BLOSC_TRACE_ERROR("Cannot seek to the end of %s (error: %s).", urlpath, strerror(errno));
415
0
    fclose(mmap_file->file);
416
0
    mmap_file->file = NULL;
417
0
    free(mmap_file->urlpath);
418
0
    mmap_file->urlpath = NULL;
419
0
    return NULL;
420
0
  }
421
0
  int64_t file_size_i64 = ftell(mmap_file->file);
422
0
  if (file_size_i64 < 0) {
423
0
    BLOSC_TRACE_ERROR("Cannot retrieve file size for %s.", urlpath);
424
0
    fclose(mmap_file->file);
425
0
    mmap_file->file = NULL;
426
0
    free(mmap_file->urlpath);
427
0
    mmap_file->urlpath = NULL;
428
0
    return NULL;
429
0
  }
430
0
  if ((uint64_t)file_size_i64 > SIZE_MAX) {
431
0
    BLOSC_TRACE_ERROR("File size for %s exceeds size_t range.", urlpath);
432
0
    fclose(mmap_file->file);
433
0
    mmap_file->file = NULL;
434
0
    free(mmap_file->urlpath);
435
0
    mmap_file->urlpath = NULL;
436
0
    return NULL;
437
0
  }
438
0
  mmap_file->file_size = (size_t)file_size_i64;
439
0
  if (fseek(mmap_file->file, 0, SEEK_SET) != 0) {
440
0
    BLOSC_TRACE_ERROR("Cannot seek to the beginning of %s (error: %s).", urlpath, strerror(errno));
441
0
    fclose(mmap_file->file);
442
0
    mmap_file->file = NULL;
443
0
    free(mmap_file->urlpath);
444
0
    mmap_file->urlpath = NULL;
445
0
    return NULL;
446
0
  }
447
448
  /* The size of the mapping must be > 0 so we are using a large enough buffer for writing
449
  (which will be increased later if needed) */
450
0
  if (use_initial_mapping_size) {
451
0
    mmap_file->mapping_size = mmap_file->initial_mapping_size;
452
0
  }
453
0
  else {
454
0
    mmap_file->mapping_size = mmap_file->file_size;
455
0
  }
456
457
0
  if (mmap_file->file_size > mmap_file->mapping_size) {
458
0
    mmap_file->mapping_size = mmap_file->file_size;
459
0
  }
460
0
  if (mmap_file->mapping_size == 0) {
461
0
    mmap_file->mapping_size = 1;
462
0
  }
463
464
#if defined(_WIN32)
465
  mmap_file->fd = _fileno(mmap_file->file);
466
467
  /* Windows automatically expands the file size to the memory mapped file
468
  (https://learn.microsoft.com/en-us/windows/win32/memory/creating-a-file-mapping-object).
469
  In general, the size of the file is directly connected to the size of the mapping and cannot change. We cut the
470
  file size to the target size in the end after we close the mapping */
471
  HANDLE file_handle = (HANDLE) _get_osfhandle(mmap_file->fd);
472
  uint64_t mapping_size64 = (uint64_t)mmap_file->mapping_size;
473
  DWORD size_hi = (DWORD)(mapping_size64 >> 32);
474
  DWORD size_lo = (DWORD)(mapping_size64 & 0xFFFFFFFFu);
475
  mmap_file->mmap_handle = CreateFileMapping(file_handle, NULL, mmap_file->access_flags, size_hi, size_lo, NULL);
476
  if (mmap_file->mmap_handle == NULL) {
477
    _print_last_error();
478
    BLOSC_TRACE_ERROR("Creating the memory mapping failed for the file %s.", urlpath);
479
    fclose(mmap_file->file);
480
    mmap_file->file = NULL;
481
    free(mmap_file->urlpath);
482
    mmap_file->urlpath = NULL;
483
    return NULL;
484
  }
485
486
  DWORD offset = 0;
487
  mmap_file->addr = (char*) MapViewOfFile(
488
    mmap_file->mmap_handle, mmap_file->map_flags, offset, offset, mmap_file->mapping_size);
489
  if (mmap_file->addr == NULL) {
490
    _print_last_error();
491
    BLOSC_TRACE_ERROR("Memory mapping failed for the file %s.", urlpath);
492
493
    if (!CloseHandle(mmap_file->mmap_handle)) {
494
      _print_last_error();
495
      BLOSC_TRACE_ERROR("Cannot close the handle to the memory-mapped file.");
496
    }
497
498
    mmap_file->mmap_handle = INVALID_HANDLE_VALUE;
499
    fclose(mmap_file->file);
500
    mmap_file->file = NULL;
501
    free(mmap_file->urlpath);
502
    mmap_file->urlpath = NULL;
503
504
    return NULL;
505
  }
506
#else
507
0
  mmap_file->fd = fileno(mmap_file->file);
508
509
  /* Offset where the mapping should start */
510
0
  int64_t offset = 0;
511
0
  mmap_file->addr = mmap(
512
0
    NULL, mmap_file->mapping_size, mmap_file->access_flags, mmap_file->map_flags, mmap_file->fd, offset);
513
0
  if (mmap_file->addr == MAP_FAILED) {
514
0
    BLOSC_TRACE_ERROR("Memory mapping failed for file %s (error: %s).", urlpath, strerror(errno));
515
0
    mmap_file->addr = NULL;
516
0
    fclose(mmap_file->file);
517
0
    mmap_file->file = NULL;
518
0
    free(mmap_file->urlpath);
519
0
    mmap_file->urlpath = NULL;
520
0
    return NULL;
521
0
  }
522
0
#endif
523
524
0
  BLOSC_INFO(
525
0
    "Opened memory-mapped file %s in mode %s with an mapping size of %zu bytes.",
526
0
    mmap_file->urlpath,
527
0
    mmap_file->mode,
528
0
    mmap_file->mapping_size
529
0
  );
530
531
  /* The mmap_file->mode parameter is only available during the opening call and cannot be used in any of the other
532
     I/O functions since this string is managed by the caller (e.g., from Python) and the memory of the string may not
533
     be available anymore at a later point. */
534
0
  mmap_file->mode = NULL;
535
536
0
  return mmap_file;
537
0
}
538
539
0
int blosc2_stdio_mmap_close(void *stream) {
540
0
  BLOSC_UNUSED_PARAM(stream);
541
0
  return 0;
542
0
}
543
544
0
int64_t blosc2_stdio_mmap_size(void *stream) {
545
0
  blosc2_stdio_mmap *mmap_file = (blosc2_stdio_mmap *) stream;
546
0
  if (mmap_file->file_size > (size_t)INT64_MAX) {
547
0
    BLOSC_TRACE_ERROR("mmap file size exceeds int64_t return range (%zu).", mmap_file->file_size);
548
0
    return INT64_MAX;
549
0
  }
550
0
  return (int64_t)mmap_file->file_size;
551
0
}
552
553
0
int64_t blosc2_stdio_mmap_write(const void *ptr, int64_t size, int64_t nitems, int64_t position, void *stream) {
554
0
  blosc2_stdio_mmap *mmap_file = (blosc2_stdio_mmap *) stream;
555
556
0
  if (ptr == NULL || size < 0 || nitems < 0 || position < 0) {
557
0
    BLOSC_TRACE_ERROR("Invalid arguments for mmap write.");
558
0
    return 0;
559
0
  }
560
561
0
  int64_t n_bytes_i64;
562
0
  if (!checked_mul_int64_nonneg(size, nitems, &n_bytes_i64)) {
563
0
    BLOSC_TRACE_ERROR("mmap write size overflow (size=%" PRId64 ", nitems=%" PRId64 ").", size, nitems);
564
0
    return 0;
565
0
  }
566
0
  if ((uint64_t)n_bytes_i64 > SIZE_MAX) {
567
0
    BLOSC_TRACE_ERROR("mmap write size does not fit in size_t (%" PRId64 ").", n_bytes_i64);
568
0
    return 0;
569
0
  }
570
0
  size_t n_bytes = (size_t)n_bytes_i64;
571
0
  if (n_bytes == 0) {
572
0
    return 0;
573
0
  }
574
575
0
  int64_t position_end_i64;
576
0
  if (!checked_add_int64_nonneg(position, n_bytes_i64, &position_end_i64)) {
577
0
    BLOSC_TRACE_ERROR("mmap write position overflow (position=%" PRId64 ", nbytes=%" PRId64 ").", position, n_bytes_i64);
578
0
    return 0;
579
0
  }
580
0
  if ((uint64_t)position_end_i64 > SIZE_MAX) {
581
0
    BLOSC_TRACE_ERROR("mmap write end position does not fit in size_t (%" PRId64 ").", position_end_i64);
582
0
    return 0;
583
0
  }
584
0
  size_t position_size = (size_t)position;
585
0
  size_t position_end = (size_t)position_end_i64;
586
0
  size_t new_size = position_end > mmap_file->file_size ? position_end : mmap_file->file_size;
587
588
#if defined(_WIN32)
589
  if (mmap_file->file_size < new_size) {
590
    mmap_file->file_size = new_size;
591
  }
592
593
  if (mmap_file->mapping_size < mmap_file->file_size) {
594
    size_t remap_size;
595
    if (mmap_file->file_size > SIZE_MAX / 2) {
596
      BLOSC_TRACE_WARNING("mmap remap growth fallback: cannot double mapping_size near SIZE_MAX; using file_size (%zu).", mmap_file->file_size);
597
      remap_size = mmap_file->file_size;
598
    }
599
    else {
600
      remap_size = mmap_file->file_size * 2;
601
    }
602
    if (remap_size > (size_t)INT64_MAX) {
603
      BLOSC_TRACE_ERROR("mmap mapping size exceeds supported OS range (%zu).", remap_size);
604
      return 0;
605
    }
606
    mmap_file->mapping_size = remap_size;
607
608
    /* We need to remap the file completely and cannot pass the previous used address on Windows */
609
    if (!UnmapViewOfFile(mmap_file->addr)) {
610
      _print_last_error();
611
      BLOSC_TRACE_ERROR("Cannot unmap the memory-mapped file.");
612
      return 0;
613
    }
614
    if (!CloseHandle(mmap_file->mmap_handle)) {
615
      _print_last_error();
616
      BLOSC_TRACE_ERROR("Cannot close the handle to the memory-mapped file.");
617
      return 0;
618
    }
619
620
    HANDLE file_handle = (HANDLE) _get_osfhandle(mmap_file->fd);
621
    uint64_t mapping_size64 = (uint64_t)mmap_file->mapping_size;
622
    DWORD size_hi = (DWORD)(mapping_size64 >> 32);
623
    DWORD size_lo = (DWORD)(mapping_size64 & 0xFFFFFFFFu);
624
    mmap_file->mmap_handle = CreateFileMapping(file_handle, NULL, mmap_file->access_flags, size_hi, size_lo, NULL);
625
    if (mmap_file->mmap_handle == NULL) {
626
      _print_last_error();
627
      BLOSC_TRACE_ERROR("Cannot remapt the memory-mapped file.");
628
      return 0;
629
    }
630
631
    DWORD offset = 0;
632
    char* new_address = (char*) MapViewOfFile(
633
      mmap_file->mmap_handle, mmap_file->map_flags, offset, offset, mmap_file->mapping_size);
634
    if (new_address == NULL) {
635
      _print_last_error();
636
      BLOSC_TRACE_ERROR("Cannot remapt the memory-mapped file");
637
638
      if (!CloseHandle(mmap_file->mmap_handle)) {
639
        _print_last_error();
640
        BLOSC_TRACE_ERROR("Cannot close the handle to the memory-mapped file.");
641
      }
642
643
      return 0;
644
    }
645
646
    mmap_file->addr = new_address;
647
  }
648
#else
649
0
  if (mmap_file->file_size < new_size) {
650
0
    mmap_file->file_size = new_size;
651
652
0
    if (!mmap_file->is_memory_only) {
653
0
      int64_t ftruncate_size;
654
0
      if (!checked_size_t_to_int64(new_size, &ftruncate_size)) {
655
0
        BLOSC_TRACE_ERROR("Cannot extend the file size to %zu bytes: value exceeds int64_t.", new_size);
656
0
        return 0;
657
0
      }
658
0
      int rc = ftruncate(mmap_file->fd, ftruncate_size);
659
0
      if (rc < 0) {
660
0
        BLOSC_TRACE_ERROR("Cannot extend the file size to %zu bytes (error: %s).", new_size, strerror(errno));
661
0
        return 0;
662
0
      }
663
0
    }
664
0
  }
665
666
0
  if (mmap_file->mapping_size < mmap_file->file_size) {
667
0
    size_t new_mapping_size;
668
0
    if (mmap_file->file_size > SIZE_MAX / 2) {
669
0
      BLOSC_TRACE_WARNING("mmap remap growth fallback: cannot double mapping_size near SIZE_MAX; using file_size (%zu).", mmap_file->file_size);
670
0
      new_mapping_size = mmap_file->file_size;
671
0
    }
672
0
    else {
673
0
      new_mapping_size = mmap_file->file_size * 2;
674
0
    }
675
676
0
#if defined(__linux__)
677
    /* mremap is the best option as it also ensures that the old data is still available in c mode. Unfortunately, it
678
    is no POSIX standard and only available on Linux */
679
0
  size_t old_mapping_size = mmap_file->mapping_size;
680
0
    char* new_address = mremap(mmap_file->addr, old_mapping_size, new_mapping_size, MREMAP_MAYMOVE);
681
#else
682
    if (mmap_file->is_memory_only) {
683
      BLOSC_TRACE_ERROR("Remapping a memory-mapping in c mode is only possible on Linux."
684
      "Please specify either a different mode or set initial_mapping_size to a large enough number.");
685
      return 0;
686
    }
687
    /* Extend the current mapping with the help of MAP_FIXED */
688
    int64_t offset = 0;
689
    char* new_address = mmap(
690
      mmap_file->addr,
691
      new_mapping_size,
692
      mmap_file->access_flags,
693
      mmap_file->map_flags | MAP_FIXED,
694
      mmap_file->fd,
695
      offset
696
    );
697
#endif
698
699
0
    if (new_address == MAP_FAILED) {
700
0
      BLOSC_TRACE_ERROR("Cannot remap the memory-mapped file (error: %s).", strerror(errno));
701
0
      return 0;
702
0
    }
703
704
0
    mmap_file->mapping_size = new_mapping_size;
705
0
    mmap_file->addr = new_address;
706
0
  }
707
0
#endif
708
709
0
  memcpy(mmap_file->addr + position_size, ptr, n_bytes);
710
0
  return nitems;
711
0
}
712
713
0
int64_t blosc2_stdio_mmap_read(void **ptr, int64_t size, int64_t nitems, int64_t position, void *stream) {
714
0
  blosc2_stdio_mmap *mmap_file = (blosc2_stdio_mmap *) stream;
715
716
0
  if (ptr == NULL) {
717
0
    BLOSC_TRACE_ERROR("Invalid pointer argument for mmap read.");
718
0
    return 0;
719
0
  }
720
721
0
  if (size < 0 || nitems < 0 || position < 0) {
722
0
    BLOSC_TRACE_ERROR("Invalid arguments for mmap read.");
723
0
    *ptr = NULL;
724
0
    return 0;
725
0
  }
726
727
0
  int64_t n_bytes_i64;
728
0
  if (!checked_mul_int64_nonneg(size, nitems, &n_bytes_i64)) {
729
0
    BLOSC_TRACE_ERROR("mmap read size overflow (size=%" PRId64 ", nitems=%" PRId64 ").", size, nitems);
730
0
    *ptr = NULL;
731
0
    return 0;
732
0
  }
733
0
  int64_t position_end_i64;
734
0
  if (!checked_add_int64_nonneg(position, n_bytes_i64, &position_end_i64)) {
735
0
    BLOSC_TRACE_ERROR("mmap read position overflow (position=%" PRId64 ", nbytes=%" PRId64 ").", position, n_bytes_i64);
736
0
    *ptr = NULL;
737
0
    return 0;
738
0
  }
739
0
  if ((uint64_t)position_end_i64 > SIZE_MAX) {
740
0
    BLOSC_TRACE_ERROR("mmap read end position does not fit in size_t (%" PRId64 ").", position_end_i64);
741
0
    *ptr = NULL;
742
0
    return 0;
743
0
  }
744
745
0
  size_t position_size = (size_t)position;
746
0
  size_t position_end = (size_t)position_end_i64;
747
0
  if (position_end > mmap_file->file_size) {
748
0
    BLOSC_TRACE_ERROR("Cannot read beyond the end of the memory-mapped file.");
749
0
    *ptr = NULL;
750
0
    return 0;
751
0
  }
752
753
0
  *ptr = mmap_file->addr + position_size;
754
755
0
  return nitems;
756
0
}
757
758
0
int blosc2_stdio_mmap_truncate(void *stream, int64_t size) {
759
0
  blosc2_stdio_mmap *mmap_file = (blosc2_stdio_mmap *) stream;
760
761
0
  if (size < 0) {
762
0
    BLOSC_TRACE_ERROR("Cannot truncate mmap file to negative size (%" PRId64 ").", size);
763
0
    return -1;
764
0
  }
765
766
0
  if ((uint64_t)size > SIZE_MAX) {
767
0
    BLOSC_TRACE_ERROR("Cannot truncate mmap file to size beyond size_t range (%" PRId64 ").", size);
768
0
    return -1;
769
0
  }
770
771
0
  size_t target_size = (size_t)size;
772
773
0
  if (mmap_file->file_size == target_size) {
774
0
    return 0;
775
0
  }
776
777
0
  mmap_file->file_size = target_size;
778
779
  /* No file operations in c mode */
780
0
  if (mmap_file->is_memory_only) {
781
0
    return 0;
782
0
  }
783
784
#if defined(_WIN32)
785
  /* On Windows, we can truncate the file only at the end after we released the mapping */
786
  return 0;
787
#else
788
0
  return ftruncate(mmap_file->fd, size);
789
0
#endif
790
0
}
791
792
0
int blosc2_stdio_mmap_destroy(void* params) {
793
0
  if (params == NULL) {
794
0
    BLOSC_TRACE_ERROR("Invalid arguments for memory-mapped destroy.");
795
0
    return -1;
796
0
  }
797
0
  blosc2_stdio_mmap *mmap_file = (blosc2_stdio_mmap *) params;
798
0
  int err = 0;
799
800
#if defined(_WIN32)
801
  if (mmap_file->addr == NULL || mmap_file->mmap_handle == INVALID_HANDLE_VALUE || mmap_file->file == NULL) {
802
    BLOSC_TRACE_ERROR("Invalid memory-mapped state during destroy.");
803
    err = -1;
804
    goto cleanup;
805
  }
806
807
  if (mmap_file->access_flags == PAGE_READWRITE) {
808
    /* Ensure modified pages are written to disk */
809
    if (!FlushViewOfFile(mmap_file->addr, mmap_file->file_size)) {
810
      _print_last_error();
811
      BLOSC_TRACE_ERROR("Cannot flush the memory-mapped view to disk.");
812
      err = -1;
813
    }
814
    HANDLE file_handle = (HANDLE) _get_osfhandle(mmap_file->fd);
815
    if (!FlushFileBuffers(file_handle)) {
816
      _print_last_error();
817
      BLOSC_TRACE_ERROR("Cannot flush the memory-mapped file to disk.");
818
      err = -1;
819
    }
820
  }
821
822
  if (!UnmapViewOfFile(mmap_file->addr)) {
823
    _print_last_error();
824
    BLOSC_TRACE_ERROR("Cannot unmap the memory-mapped file.");
825
    err = -1;
826
  }
827
  if (!CloseHandle(mmap_file->mmap_handle)) {
828
    _print_last_error();
829
    BLOSC_TRACE_ERROR("Cannot close the handle to the memory-mapped file.");
830
    err = -1;
831
  }
832
  int64_t file_size_i64;
833
  if (!checked_size_t_to_int64(mmap_file->file_size, &file_size_i64)) {
834
    BLOSC_TRACE_ERROR("Cannot extend the file size to %zu bytes: value exceeds int64_t.", mmap_file->file_size);
835
    err = -1;
836
  }
837
  else {
838
    int rc = _chsize_s(mmap_file->fd, (long long)file_size_i64);
839
    if (rc != 0) {
840
      BLOSC_TRACE_ERROR(
841
        "Cannot extend the file size to %zu bytes (error: %s).", mmap_file->file_size, strerror(errno));
842
      err = -1;
843
    }
844
  }
845
#else
846
0
  if (mmap_file->addr == NULL || mmap_file->file == NULL) {
847
0
    BLOSC_TRACE_ERROR("Invalid memory-mapped state during destroy.");
848
0
    err = -1;
849
0
    goto cleanup;
850
0
  }
851
852
0
  if ((mmap_file->access_flags & PROT_WRITE) && !mmap_file->is_memory_only) {
853
    /* Ensure modified pages are written to disk */
854
    /* This is important since not every munmap implementation flushes modified pages to disk
855
    (e.g.: https://nfs.sourceforge.net/#faq_d8) */
856
0
    int rc = msync(mmap_file->addr, mmap_file->file_size, MS_SYNC);
857
0
    if (rc < 0) {
858
0
      BLOSC_TRACE_ERROR("Cannot sync the memory-mapped file to disk (error: %s).", strerror(errno));
859
0
      err = -1;
860
0
    }
861
0
  }
862
863
0
  if (munmap(mmap_file->addr, mmap_file->mapping_size) < 0) {
864
0
    BLOSC_TRACE_ERROR("Cannot unmap the memory-mapped file (error: %s).", strerror(errno));
865
0
    err = -1;
866
0
  }
867
0
#endif
868
0
cleanup:
869
  /* Also closes the HANDLE on Windows */
870
0
  if (mmap_file->file != NULL && fclose(mmap_file->file) < 0) {
871
0
    BLOSC_TRACE_ERROR("Could not close the memory-mapped file.");
872
0
    err = -1;
873
0
  }
874
0
  mmap_file->file = NULL;
875
0
  mmap_file->addr = NULL;
876
877
0
  free(mmap_file->urlpath);
878
0
  mmap_file->urlpath = NULL;
879
0
  if (mmap_file->needs_free) {
880
0
    free(mmap_file);
881
0
  }
882
883
0
  return err;
884
0
}