1// [Blend2D]
2// 2D Vector Graphics Powered by a JIT Compiler.
3//
4// [License]
5// Zlib - See LICENSE.md file in the package.
6
7#include "./blapi-build_p.h"
8#include "./blarray_p.h"
9#include "./blfilesystem_p.h"
10#include "./blruntime_p.h"
11#include "./blsupport_p.h"
12#include "./blunicode_p.h"
13
14#ifndef _WIN32
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <unistd.h>
18 #include <sys/mman.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21#endif
22
23// ============================================================================
24// [BLWinU16String]
25// ============================================================================
26
27#ifdef _WIN32
28
29template<size_t N>
30class BLWinU16String {
31public:
32 uint16_t* _data;
33 size_t _size;
34 size_t _capacity;
35 uint16_t _embeddedData[N + 1];
36
37 BL_NONCOPYABLE(BLWinU16String);
38
39 BL_INLINE BLWinU16String() noexcept
40 : _data(_embeddedData),
41 _size(0),
42 _capacity(N) {}
43
44 BL_INLINE ~BLWinU16String() noexcept {
45 if (_data != _embeddedData)
46 free(_data);
47 }
48
49 BL_INLINE uint16_t* data() const noexcept { return _data; }
50 BL_INLINE wchar_t* dataAsWideChar() const noexcept { return reinterpret_cast<wchar_t*>(_data); }
51
52 BL_INLINE size_t size() const noexcept { return _size; }
53 BL_INLINE size_t capacity() const noexcept { return _capacity; }
54
55 BL_INLINE void nullTerminate() noexcept { _data[_size] = uint16_t(0); }
56
57 BL_NOINLINE BLResult fromUtf8(const char* src) noexcept {
58 size_t srcSize = strlen(src);
59 BLUnicodeConversionState conversionState;
60
61 BLResult result = blConvertUnicode(
62 _data, _capacity * 2u, BL_TEXT_ENCODING_UTF16, src, srcSize, BL_TEXT_ENCODING_UTF8, conversionState);
63
64 if (result == BL_SUCCESS) {
65 _size = conversionState.dstIndex / 2u;
66 nullTerminate();
67 return result;
68 }
69
70 if (result != BL_ERROR_NO_SPACE_LEFT) {
71 _size = 0;
72 nullTerminate();
73 return result;
74 }
75
76 size_t procUtf8Size = conversionState.srcIndex;
77 size_t procUtf16Size = conversionState.dstIndex / 2u;
78
79 BLUnicodeValidationState validationState;
80 BL_PROPAGATE(blValidateUtf8(src + procUtf8Size, srcSize - procUtf8Size, validationState));
81
82 size_t newSize = procUtf16Size + validationState.utf16Index;
83 uint16_t* newData = static_cast<uint16_t*>(malloc((newSize + 1) * sizeof(uint16_t)));
84
85 if (BL_UNLIKELY(!newData)) {
86 _size = 0;
87 nullTerminate();
88 return blTraceError(BL_ERROR_OUT_OF_MEMORY);
89 }
90
91 memcpy(newData, _data, procUtf16Size * sizeof(uint16_t));
92 blConvertUnicode(
93 newData + procUtf16Size, (newSize - procUtf16Size) * 2u, BL_TEXT_ENCODING_UTF16,
94 src + procUtf8Size, srcSize - procUtf8Size, BL_TEXT_ENCODING_UTF8, conversionState);
95 BL_ASSERT(newSize == procUtf16Size + conversionState.dstIndex * 2u);
96
97 if (_data != _embeddedData)
98 free(_data);
99
100 _data = newData;
101 _size = newSize;
102 _capacity = newSize;
103
104 nullTerminate();
105 return BL_SUCCESS;
106 }
107};
108
109#endif
110
111// ============================================================================
112// [BLFile]
113// ============================================================================
114
115// Just a helper to case to `BLFile` and call its `isOpen()` as this is the
116// only thing we need from C++ API here.
117static BL_INLINE bool blFileIsOpen(const BLFileCore* self) noexcept {
118 return static_cast<const BLFile*>(self)->isOpen();
119}
120
121BLResult blFileInit(BLFileCore* self) noexcept {
122 self->handle = -1;
123 return BL_SUCCESS;
124}
125
126BLResult blFileReset(BLFileCore* self) noexcept {
127 return blFileClose(self);
128}
129
130#ifdef _WIN32
131
132static const constexpr DWORD BL_FILE_BUFFER_RW_SIZE = 32 * 1024 * 1024; // 32 MB.
133
134BLResult blFileOpen(BLFileCore* self, const char* fileName, uint32_t openFlags) noexcept {
135 // Desired Access
136 // --------------
137 //
138 // The same flags as O_RDONLY|O_WRONLY|O_RDWR:
139
140 DWORD dwDesiredAccess = 0;
141 switch (openFlags & BL_FILE_OPEN_RW) {
142 case BL_FILE_OPEN_READ : dwDesiredAccess = GENERIC_READ ; break;
143 case BL_FILE_OPEN_WRITE: dwDesiredAccess = GENERIC_WRITE; break;
144 case BL_FILE_OPEN_RW : dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; break;
145 default:
146 return blTraceError(BL_ERROR_INVALID_VALUE);
147 }
148
149 // Creation Disposition
150 // --------------------
151 //
152 // Since WinAPI documentation is so brief here is a better explanation
153 // about various CreationDisposition modes, reformatted from SO:
154 //
155 // https://stackoverflow.com/questions/14469607/difference-between-open-always-and-create-always-in-createfile-of-windows-api
156 //
157 // +-------------------------+-------------+--------------------+
158 // | Creation Disposition | File Exists | File Doesn't Exist |
159 // +-------------------------+-------------+--------------------+
160 // | CREATE_ALWAYS | Truncate | Create New |
161 // | CREATE_NEW | Fail | Create New |
162 // | OPEN_ALWAYS | Open | Create New |
163 // | OPEN_EXISTING | Open | Fail |
164 // | TRUNCATE_EXISTING | Truncate | Fail |
165 // +-------------------------+-------------+--------------------+
166
167 uint32_t kExtFlags = BL_FILE_OPEN_CREATE |
168 BL_FILE_OPEN_CREATE_EXCLUSIVE |
169 BL_FILE_OPEN_TRUNCATE ;
170
171 if ((openFlags & kExtFlags) && (!(openFlags & BL_FILE_OPEN_WRITE)))
172 return blTraceError(BL_ERROR_INVALID_VALUE);
173
174 DWORD dwCreationDisposition = OPEN_EXISTING;
175 if (openFlags & BL_FILE_OPEN_CREATE_EXCLUSIVE)
176 dwCreationDisposition = CREATE_NEW;
177 else if ((openFlags & (BL_FILE_OPEN_CREATE | BL_FILE_OPEN_TRUNCATE)) == BL_FILE_OPEN_CREATE)
178 dwCreationDisposition = OPEN_ALWAYS;
179 else if ((openFlags & (BL_FILE_OPEN_CREATE | BL_FILE_OPEN_TRUNCATE)) == (BL_FILE_OPEN_CREATE | BL_FILE_OPEN_TRUNCATE))
180 dwCreationDisposition = CREATE_ALWAYS;
181 else if (openFlags & BL_FILE_OPEN_TRUNCATE)
182 dwCreationDisposition = TRUNCATE_EXISTING;
183
184 // Share Mode
185 // ----------
186
187 DWORD dwShareMode = 0;
188
189 auto isShared = [&](uint32_t access, uint32_t exclusive) noexcept -> bool {
190 return (openFlags & (access | exclusive)) == access;
191 };
192
193 if (isShared(BL_FILE_OPEN_READ, BL_FILE_OPEN_READ_EXCLUSIVE)) dwShareMode |= FILE_SHARE_READ;
194 if (isShared(BL_FILE_OPEN_WRITE, BL_FILE_OPEN_WRITE_EXCLUSIVE)) dwShareMode |= FILE_SHARE_WRITE;
195 if (isShared(BL_FILE_OPEN_DELETE, BL_FILE_OPEN_DELETE_EXCLUSIVE)) dwShareMode |= FILE_SHARE_DELETE;
196
197 // Other Flags
198 // -----------
199
200 DWORD dwFlagsAndAttributes = 0;
201 LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr;
202
203 // NOTE: Do not close the file before calling `CreateFileW()`. We should
204 // behave atomically, which means that we won't close the existing file
205 // if `CreateFileW()` fails...
206 BLWinU16String<1024> fileNameW;
207 BL_PROPAGATE(fileNameW.fromUtf8(fileName));
208
209 HANDLE handle = CreateFileW(
210 fileNameW.dataAsWideChar(),
211 dwDesiredAccess,
212 dwShareMode,
213 lpSecurityAttributes,
214 dwCreationDisposition,
215 dwFlagsAndAttributes,
216 nullptr);
217
218 if (handle == INVALID_HANDLE_VALUE)
219 return blTraceError(blResultFromWinError(GetLastError()));
220
221 blFileClose(self);
222 self->handle = intptr_t(handle);
223
224 return BL_SUCCESS;
225}
226
227BLResult blFileClose(BLFileCore* self) noexcept {
228 // Not sure what should happen if `CloseHandle()` fails, if the handle is
229 // invalid or the close can be called again? To ensure compatibility with
230 // POSIX implementation we just make it invalid.
231 if (blFileIsOpen(self)) {
232 HANDLE handle = (HANDLE)self->handle;
233 BOOL result = CloseHandle(handle);
234
235 self->handle = -1;
236 if (!result)
237 return blTraceError(blResultFromWinError(GetLastError()));
238 }
239
240 return BL_SUCCESS;
241}
242
243BLResult blFileSeek(BLFileCore* self, int64_t offset, uint32_t seekType, int64_t* positionOut) noexcept {
244 *positionOut = -1;
245
246 DWORD dwMoveMethod = 0;
247 switch (seekType) {
248 case BL_FILE_SEEK_SET: dwMoveMethod = FILE_BEGIN ; break;
249 case BL_FILE_SEEK_CUR: dwMoveMethod = FILE_CURRENT; break;
250 case BL_FILE_SEEK_END: dwMoveMethod = FILE_END ; break;
251
252 default:
253 return blTraceError(BL_ERROR_INVALID_VALUE);
254 }
255
256 if (!blFileIsOpen(self))
257 return blTraceError(BL_ERROR_INVALID_HANDLE);
258
259 LARGE_INTEGER to;
260 LARGE_INTEGER prev;
261
262 to.QuadPart = offset;
263 prev.QuadPart = 0;
264
265 HANDLE handle = (HANDLE)self->handle;
266 BOOL result = SetFilePointerEx(handle, to, &prev, dwMoveMethod);
267
268 if (!result)
269 return blTraceError(blResultFromWinError(GetLastError()));
270
271 *positionOut = prev.QuadPart;
272 return BL_SUCCESS;
273}
274
275BLResult blFileRead(BLFileCore* self, void* buffer, size_t n, size_t* bytesReadOut) noexcept {
276 *bytesReadOut = 0;
277 if (!blFileIsOpen(self))
278 return blTraceError(BL_ERROR_INVALID_HANDLE);
279
280 BOOL result = true;
281 HANDLE handle = (HANDLE)self->handle;
282
283 size_t remainingSize = n;
284 size_t bytesReadTotal = 0;
285
286 while (remainingSize) {
287 DWORD localSize = static_cast<DWORD>(blMin<size_t>(remainingSize, BL_FILE_BUFFER_RW_SIZE));
288 DWORD bytesRead = 0;
289
290 result = ReadFile(handle, buffer, localSize, &bytesRead, nullptr);
291 remainingSize -= localSize;
292 bytesReadTotal += bytesRead;
293
294 if (bytesRead < localSize || !result)
295 break;
296
297 buffer = blOffsetPtr(buffer, bytesRead);
298 }
299
300 *bytesReadOut = bytesReadTotal;
301 if (!result) {
302 DWORD e = GetLastError();
303 if (e == ERROR_HANDLE_EOF)
304 return BL_SUCCESS;
305 return blTraceError(blResultFromWinError(e));
306 }
307 else {
308 return BL_SUCCESS;
309 }
310}
311
312BLResult blFileWrite(BLFileCore* self, const void* buffer, size_t n, size_t* bytesWrittenOut) noexcept {
313 *bytesWrittenOut = 0;
314 if (!blFileIsOpen(self))
315 return blTraceError(BL_ERROR_INVALID_HANDLE);
316
317 HANDLE handle = (HANDLE)self->handle;
318 BOOL result = true;
319
320 size_t remainingSize = n;
321 size_t bytesWrittenTotal = 0;
322
323 while (remainingSize) {
324 DWORD localSize = static_cast<DWORD>(blMin<size_t>(remainingSize, BL_FILE_BUFFER_RW_SIZE));
325 DWORD bytesWritten = 0;
326
327 result = WriteFile(handle, buffer, localSize, &bytesWritten, nullptr);
328 remainingSize -= localSize;
329 bytesWrittenTotal += bytesWritten;
330
331 if (bytesWritten < localSize || !result)
332 break;
333
334 buffer = blOffsetPtr(buffer, bytesWritten);
335 }
336
337 *bytesWrittenOut = bytesWrittenTotal;
338 if (!result)
339 return blTraceError(blResultFromWinError(GetLastError()));
340
341 return BL_SUCCESS;
342}
343
344BLResult blFileTruncate(BLFileCore* self, int64_t maxSize) noexcept {
345 if (!blFileIsOpen(self))
346 return blTraceError(BL_ERROR_INVALID_HANDLE);
347
348 if (BL_UNLIKELY(maxSize < 0))
349 return blTraceError(BL_ERROR_INVALID_VALUE);
350
351 int64_t prev;
352 BL_PROPAGATE(blFileSeek(self, maxSize, BL_FILE_SEEK_SET, &prev));
353
354 HANDLE handle = (HANDLE)self->handle;
355 BOOL result = SetEndOfFile(handle);
356
357 if (prev < maxSize)
358 blFileSeek(self, prev, BL_FILE_SEEK_SET, &prev);
359
360 if (!result)
361 return blTraceError(blResultFromWinError(GetLastError()));
362 else
363 return BL_SUCCESS;
364}
365
366BLResult blFileGetSize(BLFileCore* self, uint64_t* fileSizeOut) noexcept {
367 *fileSizeOut = 0;
368 if (!blFileIsOpen(self))
369 return blTraceError(BL_ERROR_INVALID_HANDLE);
370
371 LARGE_INTEGER size;
372 BOOL result = GetFileSizeEx((HANDLE)self->handle, &size);
373
374 if (!result)
375 return blTraceError(blResultFromWinError(GetLastError()));
376
377 *fileSizeOut = uint64_t(size.QuadPart);
378 return BL_SUCCESS;
379}
380
381#else
382
383// These OSes use 64-bit offsets by default.
384#if defined(__APPLE__ ) || \
385 defined(__HAIKU__ ) || \
386 defined(__bsdi__ ) || \
387 defined(__DragonFly__) || \
388 defined(__FreeBSD__ ) || \
389 defined(__NetBSD__ ) || \
390 defined(__OpenBSD__ )
391 #define BL_FILE64_API(NAME) NAME
392#else
393 #define BL_FILE64_API(NAME) NAME##64
394#endif
395
396BLResult blFileOpen(BLFileCore* self, const char* fileName, uint32_t openFlags) noexcept {
397 int of = 0;
398
399 switch (openFlags & BL_FILE_OPEN_RW) {
400 case BL_FILE_OPEN_READ : of |= O_RDONLY; break;
401 case BL_FILE_OPEN_WRITE: of |= O_WRONLY; break;
402 case BL_FILE_OPEN_RW : of |= O_RDWR ; break;
403 default:
404 return blTraceError(BL_ERROR_INVALID_VALUE);
405 }
406
407 uint32_t kExtFlags = BL_FILE_OPEN_CREATE |
408 BL_FILE_OPEN_CREATE_EXCLUSIVE |
409 BL_FILE_OPEN_TRUNCATE ;
410
411 if ((openFlags & kExtFlags) && !(openFlags & BL_FILE_OPEN_WRITE))
412 return blTraceError(BL_ERROR_INVALID_VALUE);
413
414 if (openFlags & BL_FILE_OPEN_CREATE ) of |= O_CREAT;
415 if (openFlags & BL_FILE_OPEN_CREATE_EXCLUSIVE) of |= O_CREAT | O_EXCL;
416 if (openFlags & BL_FILE_OPEN_TRUNCATE ) of |= O_TRUNC;
417
418 mode_t om = S_IRUSR | S_IWUSR |
419 S_IRGRP | S_IWGRP |
420 S_IROTH | S_IWOTH ;
421
422 // NOTE: Do not close the file before calling `open()`. We should
423 // behave atomically, which means that we won't close the existing
424 // file if `open()` fails...
425 int fd = BL_FILE64_API(open)(fileName, of, om);
426 if (fd < 0) {
427 return blTraceError(blResultFromPosixError(errno));
428 }
429
430 blFileClose(self);
431 self->handle = intptr_t(fd);
432
433 return BL_SUCCESS;
434}
435
436BLResult blFileClose(BLFileCore* self) noexcept {
437 if (blFileIsOpen(self)) {
438 int fd = int(self->handle);
439 int result = close(fd);
440
441 // NOTE: Even when `close()` fails the handle cannot be used again as it
442 // could have already been reused. The failure is just to inform the user
443 // that something failed and that there may be data-loss.
444 self->handle = -1;
445
446 if (BL_UNLIKELY(result != 0))
447 return blTraceError(blResultFromPosixError(errno));
448 }
449
450 return BL_SUCCESS;
451}
452
453BLResult blFileSeek(BLFileCore* self, int64_t offset, uint32_t seekType, int64_t* positionOut) noexcept {
454 *positionOut = -1;
455
456 int whence = 0;
457 switch (seekType) {
458 case BL_FILE_SEEK_SET: whence = SEEK_SET; break;
459 case BL_FILE_SEEK_CUR: whence = SEEK_CUR; break;
460 case BL_FILE_SEEK_END: whence = SEEK_END; break;
461
462 default:
463 return blTraceError(BL_ERROR_INVALID_VALUE);
464 }
465
466 if (!blFileIsOpen(self))
467 return blTraceError(BL_ERROR_INVALID_HANDLE);
468
469 int fd = int(self->handle);
470 int64_t result = BL_FILE64_API(lseek)(fd, offset, whence);
471
472 if (result < 0) {
473 int e = errno;
474
475 // Returned when the file was not open for reading or writing.
476 if (e == EBADF)
477 return blTraceError(BL_ERROR_NOT_PERMITTED);
478
479 return blTraceError(blResultFromPosixError(errno));
480 }
481
482 *positionOut = result;
483 return BL_SUCCESS;
484}
485
486BLResult blFileRead(BLFileCore* self, void* buffer, size_t n, size_t* bytesReadOut) noexcept {
487 typedef std::make_signed<size_t>::type SignedSizeT;
488
489 *bytesReadOut = 0;
490 if (!blFileIsOpen(self))
491 return blTraceError(BL_ERROR_INVALID_HANDLE);
492
493 int fd = int(self->handle);
494 SignedSizeT result = read(fd, buffer, n);
495
496 if (result < 0) {
497 int e = errno;
498
499 // Returned when the file was not open for reading.
500 if (e == EBADF)
501 return blTraceError(BL_ERROR_NOT_PERMITTED);
502
503 return blTraceError(blResultFromPosixError(e));
504 }
505
506 *bytesReadOut = size_t(result);
507 return BL_SUCCESS;
508}
509
510BLResult blFileWrite(BLFileCore* self, const void* buffer, size_t n, size_t* bytesWrittenOut) noexcept {
511 typedef std::make_signed<size_t>::type SignedSizeT;
512
513 *bytesWrittenOut = 0;
514 if (!blFileIsOpen(self))
515 return blTraceError(BL_ERROR_INVALID_HANDLE);
516
517 int fd = int(self->handle);
518 SignedSizeT result = write(fd, buffer, n);
519
520 if (result < 0) {
521 int e = errno;
522
523 // These are the two errors that would be returned if the file was open for read-only.
524 if (e == EBADF || e == EINVAL)
525 return blTraceError(BL_ERROR_NOT_PERMITTED);
526
527 return blTraceError(blResultFromPosixError(e));
528 }
529
530 *bytesWrittenOut = size_t(result);
531 return BL_SUCCESS;
532}
533
534BLResult blFileTruncate(BLFileCore* self, int64_t maxSize) noexcept {
535 if (!blFileIsOpen(self))
536 return blTraceError(BL_ERROR_INVALID_HANDLE);
537
538 if (maxSize < 0)
539 return blTraceError(BL_ERROR_INVALID_VALUE);
540
541 int fd = int(self->handle);
542 int result = BL_FILE64_API(ftruncate)(fd, maxSize);
543
544 if (result != 0) {
545 int e = errno;
546
547 // These are the two errors that would be returned if the file was open for read-only.
548 if (e == EBADF || e == EINVAL)
549 return blTraceError(BL_ERROR_NOT_PERMITTED);
550
551 // File was smaller than `maxSize` - we don't consider this to be an error.
552 if (e == EFBIG)
553 return BL_SUCCESS;
554
555 return blTraceError(blResultFromPosixError(e));
556 }
557 else {
558 return BL_SUCCESS;
559 }
560}
561
562BLResult blFileGetSize(BLFileCore* self, uint64_t* fileSizeOut) noexcept {
563 *fileSizeOut = 0;
564 if (!blFileIsOpen(self))
565 return blTraceError(BL_ERROR_INVALID_HANDLE);
566
567 int fd = int(self->handle);
568 struct stat s;
569
570 if (fstat(fd, &s) != 0)
571 return blTraceError(blResultFromPosixError(errno));
572
573 *fileSizeOut = uint64_t(s.st_size);
574 return BL_SUCCESS;
575}
576
577#undef BL_FILE64_API
578#endif
579
580// ============================================================================
581// [BLFileMapping - API]
582// ============================================================================
583
584#if defined(_WIN32)
585
586BLResult BLFileMapping::map(BLFile& file, size_t size, uint32_t flags) noexcept {
587 BL_UNUSED(flags);
588
589 if (!file.isOpen())
590 return blTraceError(BL_ERROR_INVALID_VALUE);
591
592 DWORD dwProtect = PAGE_READONLY;
593 DWORD dwDesiredAccess = FILE_MAP_READ;
594
595 // Create a file mapping handle and map view of file into it.
596 HANDLE hFileMapping = CreateFileMappingW((HANDLE)file.handle, nullptr, dwProtect, 0, 0, nullptr);
597 if (hFileMapping == nullptr)
598 return blTraceError(blResultFromWinError(GetLastError()));
599
600 void* data = MapViewOfFile(hFileMapping, dwDesiredAccess, 0, 0, 0);
601 if (!data) {
602 BLResult result = blResultFromWinError(GetLastError());
603 CloseHandle(hFileMapping);
604 return blTraceError(result);
605 }
606
607 // Succeeded, now is the time to change the content of `BLFileMapping`.
608 unmap();
609
610 _file.handle = file.takeHandle();
611 _fileMappingHandle = hFileMapping;
612 _data = data;
613 _size = size;
614
615 return BL_SUCCESS;
616}
617
618BLResult BLFileMapping::unmap() noexcept {
619 if (empty())
620 return BL_SUCCESS;
621
622 BLResult result = BL_SUCCESS;
623 DWORD err = 0;
624
625 if (!UnmapViewOfFile(_data))
626 err = GetLastError();
627
628 if (!CloseHandle(_fileMappingHandle) && !err)
629 err = GetLastError();
630
631 if (err)
632 result = blTraceError(blResultFromWinError(err));
633
634 blFileClose(&_file);
635 _fileMappingHandle = INVALID_HANDLE_VALUE;
636 _data = nullptr;
637 _size = 0;
638
639 return result;
640}
641
642#else
643
644BLResult BLFileMapping::map(BLFile& file, size_t size, uint32_t flags) noexcept {
645 BL_UNUSED(flags);
646
647 if (!file.isOpen())
648 return blTraceError(BL_ERROR_INVALID_VALUE);
649
650 int mmapProt = PROT_READ;
651 int mmapFlags = MAP_SHARED;
652
653 // Create the mapping.
654 void* data = mmap(nullptr, size, mmapProt, mmapFlags, int(file.handle), 0);
655 if (data == (void *)-1)
656 return blTraceError(blResultFromPosixError(errno));
657
658 // Succeeded, now is the time to change the content of `BLFileMapping`.
659 unmap();
660
661 _file.handle = file.takeHandle();
662 _data = data;
663 _size = size;
664 return BL_SUCCESS;
665}
666
667BLResult BLFileMapping::unmap() noexcept {
668 if (empty())
669 return BL_SUCCESS;
670
671 BLResult result = BL_SUCCESS;
672 int unmapStatus = munmap(_data, _size);
673
674 // If error happened we must read `errno` now as a call to `close()` may
675 // trash it. We prefer the first error instead of the last one.
676 if (unmapStatus != 0)
677 result = blTraceError(blResultFromPosixError(errno));
678
679 BLResult result2 = blFileClose(&_file);
680 if (result == BL_SUCCESS)
681 result = result2;
682
683 _data = nullptr;
684 _size = 0;
685 return result;
686}
687
688#endif
689
690// ============================================================================
691// [BLFileSystem - Memory Mapped File]
692// ============================================================================
693
694class BLMemoryMappedFileArrayImpl : public BLArrayImpl {
695public:
696 BLFileMapping fileMapping;
697};
698
699void BL_CDECL blFileSystemDestroyMemoryMappedFile(void* impl_, void* destroyData) noexcept {
700 BL_UNUSED(destroyData);
701
702 BLMemoryMappedFileArrayImpl* impl = static_cast<BLMemoryMappedFileArrayImpl*>(impl_);
703 blCallDtor(impl->fileMapping);
704}
705
706static BLResult blFileSystemCreateMemoryMappedFile(BLArray<uint8_t>* dst, BLFile& file, size_t size) noexcept {
707 // This condition must be handled before.
708 BL_ASSERT(size != 0);
709
710 BLArrayImpl* oldI = dst->impl;
711 uint32_t implSize = sizeof(BLExternalImplPreface) + sizeof(BLMemoryMappedFileArrayImpl);
712 uint32_t implTraits = BL_IMPL_TRAIT_IMMUTABLE | BL_IMPL_TRAIT_EXTERNAL;
713
714 uint16_t memPoolData;
715 void* p = blRuntimeAllocImpl(implSize, &memPoolData);
716
717 if (BL_UNLIKELY(!p))
718 return blTraceError(BL_ERROR_OUT_OF_MEMORY);
719
720 BLExternalImplPreface* preface = static_cast<BLExternalImplPreface*>(p);
721 BLMemoryMappedFileArrayImpl* impl = blOffsetPtr<BLMemoryMappedFileArrayImpl>(p, sizeof(BLExternalImplPreface));
722
723 preface->destroyFunc = blFileSystemDestroyMemoryMappedFile;
724 preface->destroyData = nullptr;
725
726 impl->data = nullptr;
727 impl->size = size;
728 impl->capacity = size;
729 impl->itemSize = 1;
730 impl->dispatchType = 0;
731 impl->reserved[0] = 0;
732 impl->reserved[1] = 0;
733
734 blImplInit(impl, BL_IMPL_TYPE_ARRAY_U8, implTraits, memPoolData);
735 blCallCtor(impl->fileMapping);
736
737 BLResult result = impl->fileMapping.map(file, size);
738 if (result != BL_SUCCESS) {
739 // No need to call fileMapping destructor as it holds no data.
740 blRuntimeFreeImpl(p, implSize, memPoolData);
741 return result;
742 }
743
744 // Mapping succeeded.
745 impl->data = impl->fileMapping.data();
746 dst->impl = impl;
747 return blArrayImplRelease(oldI);
748}
749
750// ============================================================================
751// [BLFileSystem]
752// ============================================================================
753
754BLResult blFileSystemReadFile(const char* fileName, BLArrayCore* dst_, size_t maxSize, uint32_t readFlags) noexcept {
755 BLArray<uint8_t>& dst = dst_->dcast<BLArray<uint8_t>>();
756 dst.clear();
757
758 if (BL_UNLIKELY(dst.impl->implType != BL_IMPL_TYPE_ARRAY_U8))
759 return blTraceError(BL_ERROR_INVALID_STATE);
760
761 BLFile file;
762 BL_PROPAGATE(file.open(fileName, BL_FILE_OPEN_READ));
763
764 // TODO: This won't read `stat` files.
765 uint64_t size64;
766 BL_PROPAGATE(file.getSize(&size64));
767
768 if (size64 == 0)
769 return BL_SUCCESS;
770
771 if (maxSize)
772 size64 = blMin<uint64_t>(size64, maxSize);
773
774#if BL_TARGET_ARCH_BITS < 64
775 if (BL_UNLIKELY(size64 >= uint64_t(SIZE_MAX)))
776 return blTraceError(BL_ERROR_FILE_TOO_LARGE);
777#endif
778
779 size_t size = size_t(size64);
780
781 // Use memory mapped file if enabled.
782 if (readFlags & BL_FILE_READ_MMAP_ENABLED) {
783 bool isSmall = size < BL_FILE_SYSTEM_SMALL_FILE_SIZE_THRESHOLD;
784 if (!(readFlags & BL_FILE_READ_MMAP_AVOID_SMALL) || !isSmall) {
785 BLResult result = blFileSystemCreateMemoryMappedFile(&dst, file, size);
786 if (result == BL_SUCCESS)
787 return result;
788
789 if (readFlags & BL_FILE_READ_MMAP_NO_FALLBACK)
790 return result;
791 }
792 }
793
794
795 uint8_t* data;
796 BL_PROPAGATE(dst.modifyOp(BL_MODIFY_OP_ASSIGN_FIT, size, &data));
797 return file.read(data, size, &dst.impl->size);
798}
799
800BLResult blFileSystemWriteFile(const char* fileName, const void* data, size_t size, size_t* bytesWrittenOut) noexcept {
801 *bytesWrittenOut = 0;
802
803 BLFile file;
804 BL_PROPAGATE(file.open(fileName, BL_FILE_OPEN_WRITE | BL_FILE_OPEN_CREATE | BL_FILE_OPEN_TRUNCATE));
805 return size ? file.write(data, size , bytesWrittenOut) : BL_SUCCESS;
806}
807