/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 | } |