Lely core libraries 2.3.4
frbuf.c
Go to the documentation of this file.
1
24#if !_WIN32
25// This needs to be defined before any files are included to make fstat64(),
26// lseek64(), mmap64() and pread64() available.
27#define _LARGEFILE64_SOURCE 1
28#endif
29
30#include "util.h"
31
32#if !LELY_NO_STDIO
33
34#include <lely/util/errnum.h>
35#include <lely/util/frbuf.h>
36
37#include <assert.h>
38#include <stdlib.h>
39
40#if !_WIN32
41#include <stdio.h>
42#if _POSIX_C_SOURCE >= 200112L
43#include <fcntl.h>
44#if _POSIX_MAPPED_FILES >= 200112L
45#include <sys/mman.h>
46#endif
47#include <sys/stat.h>
48#endif
49#endif
50
52struct __frbuf {
53#if _WIN32
55 HANDLE hFile;
57 HANDLE hFileMappingObject;
59 LPVOID lpBaseAddress;
60#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
62 int fd;
64 void *addr;
66 size_t len;
67#else
69 FILE *stream;
71 void *map;
72#endif
73};
74
75void *
76__frbuf_alloc(void)
77{
78 void *ptr = malloc(sizeof(struct __frbuf));
79 if (!ptr)
80 set_errc(errno2c(errno));
81 return ptr;
82}
83
84void
85__frbuf_free(void *ptr)
86{
87 free(ptr);
88}
89
90struct __frbuf *
91__frbuf_init(struct __frbuf *buf, const char *filename)
92{
93 assert(buf);
94 assert(filename);
95
96#if _WIN32
97 buf->hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
98 OPEN_EXISTING,
99 FILE_ATTRIBUTE_READONLY | FILE_FLAG_NO_BUFFERING, NULL);
100 if (buf->hFile == INVALID_HANDLE_VALUE)
101 return NULL;
102
103 buf->hFileMappingObject = INVALID_HANDLE_VALUE;
104 buf->lpBaseAddress = NULL;
105#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
106 buf->fd = open(filename, O_RDONLY | O_CLOEXEC);
107 if (buf->fd == -1)
108 return NULL;
109
110 buf->addr = MAP_FAILED;
111 buf->len = 0;
112#else
113 buf->stream = fopen(filename, "rb");
114 if (!buf->stream)
115 return NULL;
116
117 buf->map = NULL;
118#endif
119
120 return buf;
121}
122
123void
124__frbuf_fini(struct __frbuf *buf)
125{
126 frbuf_unmap(buf);
127
128#if _WIN32
129 CloseHandle(buf->hFile);
130#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
131 close(buf->fd);
132#else
133 fclose(buf->stream);
134#endif
135}
136
137frbuf_t *
138frbuf_create(const char *filename)
139{
140 int errc = 0;
141
142 frbuf_t *buf = __frbuf_alloc();
143 if (!buf) {
144 errc = get_errc();
145 goto error_alloc_buf;
146 }
147
148 if (!__frbuf_init(buf, filename)) {
149 errc = get_errc();
150 goto error_init_buf;
151 }
152
153 return buf;
154
155error_init_buf:
156 __frbuf_free(buf);
157error_alloc_buf:
158 set_errc(errc);
159 return NULL;
160}
161
162void
164{
165 if (buf) {
166 __frbuf_fini(buf);
167 __frbuf_free(buf);
168 }
169}
170
171intmax_t
173{
174 assert(buf);
175
176#if _WIN32
177 LARGE_INTEGER FileSize;
178 if (!GetFileSizeEx(buf->hFile, &FileSize))
179 return -1;
180 return FileSize.QuadPart;
181#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
182#ifdef __linux__
183 struct stat64 stat;
184 if (fstat64(buf->fd, &stat) == -1)
185 return -1;
186#else
187 struct stat stat;
188 if (fstat(buf->fd, &stat) == -1)
189 return -1;
190#endif
191 return stat.st_size;
192#else
193 long offset = ftell(buf->stream);
194 if (offset == -1) {
195 set_errc(errno2c(errno));
196 return -1;
197 }
198
199 // WARNING: This is not guaranteed to work, but there exists no standard
200 // C alternative.
201 if (fseek(buf->stream, 0, SEEK_END)) {
202 set_errc(errno2c(errno));
203 return -1;
204 }
205
206 long size = ftell(buf->stream);
207
208 int errc = get_errc();
209 fseek(buf->stream, offset, SEEK_SET);
210 set_errc(errc);
211
212 return size;
213#endif
214}
215
216intmax_t
218{
219 assert(buf);
220
221#if _WIN32
222 LARGE_INTEGER li = { .QuadPart = 0 };
223 if (!SetFilePointerEx(buf->hFile, li, &li, FILE_CURRENT))
224 return -1;
225 return li.QuadPart;
226#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
227#ifdef __linux__
228 return lseek64(buf->fd, 0, SEEK_CUR);
229#else
230 return lseek(buf->fd, 0, SEEK_CUR);
231#endif
232#else
233 long pos = ftell(buf->stream);
234 if (pos == -1)
235 set_errc(errno2c(errno));
236 return pos;
237#endif
238}
239
240intmax_t
241frbuf_set_pos(frbuf_t *buf, intmax_t pos)
242{
243 assert(buf);
244
245 if (pos < 0) {
247 return -1;
248 }
249
250#if _WIN32
251 LARGE_INTEGER li = { .QuadPart = pos };
252 if (!SetFilePointerEx(buf->hFile, li, &li, FILE_BEGIN))
253 return -1;
254 return li.QuadPart;
255#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
256#ifdef __linux__
257 return lseek64(buf->fd, pos, SEEK_SET);
258#else
259 return lseek(buf->fd, pos, SEEK_SET);
260#endif
261#else
262 if (pos > LONG_MAX) {
264 return -1;
265 }
266 if (fseek(buf->stream, pos, SEEK_SET)) {
267 set_errc(errno2c(errno));
268 return -1;
269 }
270 return frbuf_get_pos(buf);
271#endif
272}
273
274ssize_t
275frbuf_read(frbuf_t *buf, void *ptr, size_t size)
276{
277 assert(buf);
278 assert(ptr || !size);
279
280 if (!size)
281 return 0;
282
283#if _WIN32
284 DWORD nNumberOfBytesRead;
285 if (!ReadFile(buf->hFile, ptr, size, &nNumberOfBytesRead, NULL))
286 return -1;
287 return nNumberOfBytesRead;
288#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
289 ssize_t result;
290 do
291 result = read(buf->fd, ptr, size);
292 while (result == -1 && errno == EINTR);
293 return result;
294#else
295 size_t result = fread(ptr, 1, size, buf->stream);
296 if (result != size && ferror(buf->stream)) {
297 set_errc(errno2c(errno));
298 if (!result)
299 return -1;
300 }
301 return result;
302#endif
303}
304
305ssize_t
306frbuf_pread(frbuf_t *buf, void *ptr, size_t size, intmax_t pos)
307{
308 assert(buf);
309 assert(ptr || !size);
310
311 if (!size)
312 return 0;
313
314 if (pos < 0) {
316 return -1;
317 }
318
319#if _WIN32
320 ssize_t result = 0;
321 DWORD dwErrCode = GetLastError();
322
323 intmax_t oldpos = frbuf_get_pos(buf);
324 if (oldpos == -1) {
325 result = -1;
326 dwErrCode = GetLastError();
327 goto error_get_pos;
328 }
329
330 DWORD nNumberOfBytesRead;
331 OVERLAPPED Overlapped = { 0 };
332 ULARGE_INTEGER uli = { .QuadPart = pos };
333 Overlapped.Offset = uli.LowPart;
334 Overlapped.OffsetHigh = uli.HighPart;
335 // clang-format off
336 if (!ReadFile(buf->hFile, ptr, size, &nNumberOfBytesRead,
337 &Overlapped)) {
338 // clang-format on
339 result = -1;
340 dwErrCode = GetLastError();
341 goto error_ReadFile;
342 }
343
344 result = nNumberOfBytesRead;
345
346error_ReadFile:
347 if (frbuf_set_pos(buf, oldpos) == -1 && !dwErrCode)
348 dwErrCode = GetLastError();
349error_get_pos:
350 SetLastError(dwErrCode);
351 return result;
352#elif _POSIX_C_SOURCE >= 200112L && !defined(__NEWLIB__)
353 ssize_t result;
354#ifdef __linux__
355 do
356 result = pread64(buf->fd, ptr, size, pos);
357#else
358 do
359 result = pread(buf->fd, ptr, size, pos);
360#endif
361 while (result == -1 && errno == EINTR);
362 return result;
363#else
364 ssize_t result = 0;
365 int errc = get_errc();
366
367 intmax_t oldpos = frbuf_get_pos(buf);
368 if (oldpos == -1) {
369 result = -1;
370 errc = get_errc();
371 goto error_get_pos;
372 }
373
374 if (frbuf_set_pos(buf, pos) != pos) {
375 result = -1;
376 errc = get_errc();
377 goto error_set_pos;
378 }
379
380 result = frbuf_read(buf, ptr, size);
381 if (result == -1 || (size_t)result != size)
382 errc = get_errc();
383
384error_set_pos:
385 if (frbuf_set_pos(buf, oldpos) == -1 && !errc)
386 errc = get_errc();
387error_get_pos:
388 set_errc(errc);
389 return result;
390#endif
391}
392
393const void *
394frbuf_map(frbuf_t *buf, intmax_t pos, size_t *psize)
395{
396 frbuf_unmap(buf);
397
398 intmax_t size = frbuf_get_size(buf);
399 if (size < 0)
400 return NULL;
401 if (pos < 0) {
403 return NULL;
404 }
405 if (pos > (intmax_t)size) {
407 return NULL;
408 }
409 size -= pos;
410
411 if (psize && *psize)
412 size = MIN((uintmax_t)size, *psize);
413
414#if _WIN32
415 DWORD dwErrCode = 0;
416
417 SYSTEM_INFO SystemInfo;
418 // cppcheck-suppress uninitvar
419 GetSystemInfo(&SystemInfo);
420 DWORD off = pos % SystemInfo.dwAllocationGranularity;
421 if ((uintmax_t)size > (uintmax_t)(SIZE_MAX - off)) {
422 dwErrCode = ERROR_INVALID_PARAMETER;
423 goto error_size;
424 }
425
426 ULARGE_INTEGER MaximumSize = { .QuadPart = pos + size };
427 buf->hFileMappingObject = CreateFileMapping(buf->hFile, NULL,
428 PAGE_READONLY, MaximumSize.HighPart,
429 MaximumSize.LowPart, NULL);
430 if (buf->hFileMappingObject == INVALID_HANDLE_VALUE) {
431 dwErrCode = GetLastError();
432 goto error_CreateFileMapping;
433 }
434
435 ULARGE_INTEGER FileOffset = { .QuadPart = pos - off };
436 buf->lpBaseAddress = MapViewOfFile(buf->hFileMappingObject,
437 FILE_MAP_READ, FileOffset.HighPart, FileOffset.LowPart,
438 (SIZE_T)(off + size));
439 if (!buf->lpBaseAddress) {
440 dwErrCode = GetLastError();
441 goto error_MapViewOfFile;
442 }
443
444 if (psize)
445 *psize = (size_t)size;
446
447 return (char *)buf->lpBaseAddress + off;
448
449error_MapViewOfFile:
450 CloseHandle(buf->hFileMappingObject);
451 buf->hFileMappingObject = INVALID_HANDLE_VALUE;
452error_CreateFileMapping:
453error_size:
454 SetLastError(dwErrCode);
455 return NULL;
456#elif _POSIX_MAPPED_FILES >= 200112L
457 long page_size = sysconf(_SC_PAGE_SIZE);
458 if (page_size <= 0)
459 return NULL;
460 intmax_t off = pos % page_size;
461 if ((uintmax_t)size > (uintmax_t)(SIZE_MAX - off)) {
462 errno = EOVERFLOW;
463 return NULL;
464 }
465
466#ifdef __linux__
467 buf->addr = mmap64(NULL, off + size, PROT_READ, MAP_SHARED, buf->fd,
468 pos - off);
469#else
470 // TODO: Check if `pos - off` does not overflow the range of off_t.
471 buf->addr = mmap(NULL, off + size, PROT_READ, MAP_SHARED, buf->fd,
472 pos - off);
473#endif
474 if (buf->addr == MAP_FAILED)
475 return NULL;
476 buf->len = off + size;
477
478 if (psize)
479 *psize = size;
480
481 return (char *)buf->addr + off;
482#else
483 int errc = get_errc();
484
485 buf->map = malloc(size);
486 if (!buf->map) {
487 errc = errno2c(errno);
488 goto error_malloc_map;
489 }
490
491 ssize_t result = frbuf_pread(buf, buf->map, size, pos);
492 if (result == -1) {
493 errc = get_errc();
494 goto error_pread;
495 }
496 size = result;
497
498 if (psize)
499 *psize = size;
500
501 return buf->map;
502
503error_pread:
504 free(buf->map);
505 buf->map = NULL;
506error_malloc_map:
507 set_errc(errc);
508 return NULL;
509#endif
510}
511
512int
514{
515 assert(buf);
516
517 int result = 0;
518
519#if _WIN32
520 if (buf->hFileMappingObject != INVALID_HANDLE_VALUE) {
521 DWORD dwErrCode = GetLastError();
522 if (!UnmapViewOfFile(buf->lpBaseAddress)) {
523 result = -1;
524 dwErrCode = GetLastError();
525 }
526 if (!CloseHandle(buf->hFileMappingObject) && !result) {
527 result = -1;
528 dwErrCode = GetLastError();
529 }
530 SetLastError(dwErrCode);
531
532 buf->hFileMappingObject = INVALID_HANDLE_VALUE;
533 buf->lpBaseAddress = NULL;
534 }
535#elif _POSIX_MAPPED_FILES >= 200112L
536 if (buf->addr != MAP_FAILED) {
537 result = munmap(buf->addr, buf->len);
538
539 buf->addr = MAP_FAILED;
540 buf->len = 0;
541 }
542#else
543 if (buf->map) {
544 free(buf->map);
545
546 buf->map = NULL;
547 }
548#endif
549
550 return result;
551}
552
553#endif // !LELY_NO_STDIO
This header file is part of the utilities library; it contains the native and platform-independent er...
@ ERRNUM_INVAL
Invalid argument.
Definition: errnum.h:132
@ ERRNUM_OVERFLOW
Value too large to be stored in data type.
Definition: errnum.h:204
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
Definition: errnum.c:932
void set_errc(int errc)
Sets the current (thread-specific) native error code to errc.
Definition: errnum.c:944
int errno2c(int errnum)
Transforms a standard C error number to a native error code.
Definition: errnum.c:46
void set_errnum(errnum_t errnum)
Sets the current (thread-specific) platform-independent error number to errnum.
Definition: errnum.h:424
int frbuf_unmap(frbuf_t *buf)
Unmaps the current memory map of a read file buffer, if it exists.
Definition: frbuf.c:513
intmax_t frbuf_get_size(frbuf_t *buf)
Returns the size (in bytes) of the a read file buffer, or -1 on error.
Definition: frbuf.c:172
const void * frbuf_map(frbuf_t *buf, intmax_t pos, size_t *psize)
Maps (part of) the contents of a read file buffer to memory.
Definition: frbuf.c:394
intmax_t frbuf_set_pos(frbuf_t *buf, intmax_t pos)
Sets the current offset (in bytes) of a read file buffer with respect to the beginning of the file.
Definition: frbuf.c:241
void frbuf_destroy(frbuf_t *buf)
Destroys a read file buffer.
Definition: frbuf.c:163
intmax_t frbuf_get_pos(frbuf_t *buf)
Returns the current offset (in bytes) of a read file buffer with respect to the beginning of the file...
Definition: frbuf.c:217
ssize_t frbuf_pread(frbuf_t *buf, void *ptr, size_t size, intmax_t pos)
Reads bytes from the specified position in a read file buffer.
Definition: frbuf.c:306
frbuf_t * frbuf_create(const char *filename)
Creates a new read file buffer.
Definition: frbuf.c:138
ssize_t frbuf_read(frbuf_t *buf, void *ptr, size_t size)
Reads bytes from the current position in a read file buffer.
Definition: frbuf.c:275
This header file is part of the utilities library; it contains the read file buffer declarations.
#define MIN(a, b)
Returns the minimum of a and b.
Definition: util.h:57
This is the internal header file of the utilities library.
This header file is part of the C11 and POSIX compatibility library; it includes <stdio....
This header file is part of the C11 and POSIX compatibility library; it includes <stdlib....
An read file buffer struct.
Definition: frbuf.c:52
int fd
The file descriptor.
Definition: frbuf.c:62
void * addr
The base address of the current file mapping.
Definition: frbuf.c:64
size_t len
The length (in bytes) of the mapping at addr.
Definition: frbuf.c:66