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 | |
29 | template<size_t N> |
30 | class BLWinU16String { |
31 | public: |
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. |
117 | static BL_INLINE bool blFileIsOpen(const BLFileCore* self) noexcept { |
118 | return static_cast<const BLFile*>(self)->isOpen(); |
119 | } |
120 | |
121 | BLResult blFileInit(BLFileCore* self) noexcept { |
122 | self->handle = -1; |
123 | return BL_SUCCESS; |
124 | } |
125 | |
126 | BLResult blFileReset(BLFileCore* self) noexcept { |
127 | return blFileClose(self); |
128 | } |
129 | |
130 | #ifdef _WIN32 |
131 | |
132 | static const constexpr DWORD BL_FILE_BUFFER_RW_SIZE = 32 * 1024 * 1024; // 32 MB. |
133 | |
134 | BLResult 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 | |
227 | BLResult 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 | |
243 | BLResult 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 | |
275 | BLResult 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 | |
312 | BLResult 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 | |
344 | BLResult 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 | |
366 | BLResult 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 | |
396 | BLResult 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 | |
436 | BLResult 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 | |
453 | BLResult 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 | |
486 | BLResult 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 | |
510 | BLResult 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 | |
534 | BLResult 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 | |
562 | BLResult 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 | |
586 | BLResult 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 | |
618 | BLResult 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 | |
644 | BLResult 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 | |
667 | BLResult 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 | |
694 | class BLMemoryMappedFileArrayImpl : public BLArrayImpl { |
695 | public: |
696 | BLFileMapping fileMapping; |
697 | }; |
698 | |
699 | void 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 | |
706 | static 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 | |
754 | BLResult 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 | |
800 | BLResult 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 | |