libmdbx/src/osal.c
Леонид Юрьев (Leonid Yuriev) aa9d2387e5 mdbx: исправление me_dxb_mmap.current > me_dxb_mmap.limit и срабатывания соответствующей assert-проверки.
Устранение упущения приводящего к нелогичной ситуации `me_dxb_mmap.curren > me_dxb_mmap.limit` при "дребезге" размера БД.
В текущем понимании, последствий кроме срабатывания assert-проверки нет, а вероятность проявления близка к нулю.
2024-03-03 17:56:16 +03:00

3536 lines
113 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* https://en.wikipedia.org/wiki/Operating_system_abstraction_layer */
/*
* Copyright 2015-2023 Leonid Yuriev <leo@yuriev.ru>
* and other libmdbx authors: please see AUTHORS file.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include "internals.h"
#if defined(_WIN32) || defined(_WIN64)
#include <psapi.h>
#include <winioctl.h>
#if !MDBX_WITHOUT_MSVC_CRT && defined(_DEBUG)
#include <crtdbg.h>
#endif
static int waitstatus2errcode(DWORD result) {
switch (result) {
case WAIT_OBJECT_0:
return MDBX_SUCCESS;
case WAIT_FAILED:
return (int)GetLastError();
case WAIT_ABANDONED:
return ERROR_ABANDONED_WAIT_0;
case WAIT_IO_COMPLETION:
return ERROR_USER_APC;
case WAIT_TIMEOUT:
return ERROR_TIMEOUT;
default:
return ERROR_UNHANDLED_ERROR;
}
}
/* Map a result from an NTAPI call to WIN32 error code. */
static int ntstatus2errcode(NTSTATUS status) {
DWORD dummy;
OVERLAPPED ov;
memset(&ov, 0, sizeof(ov));
ov.Internal = status;
/* Zap: '_Param_(1)' could be '0' */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6387);
return GetOverlappedResult(NULL, &ov, &dummy, FALSE) ? MDBX_SUCCESS
: (int)GetLastError();
}
/* We use native NT APIs to setup the memory map, so that we can
* let the DB file grow incrementally instead of always preallocating
* the full size. These APIs are defined in <wdm.h> and <ntifs.h>
* but those headers are meant for driver-level development and
* conflict with the regular user-level headers, so we explicitly
* declare them here. Using these APIs also means we must link to
* ntdll.dll, which is not linked by default in user code. */
extern NTSTATUS NTAPI NtCreateSection(
OUT PHANDLE SectionHandle, IN ACCESS_MASK DesiredAccess,
IN OPTIONAL POBJECT_ATTRIBUTES ObjectAttributes,
IN OPTIONAL PLARGE_INTEGER MaximumSize, IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes, IN OPTIONAL HANDLE FileHandle);
typedef struct _SECTION_BASIC_INFORMATION {
ULONG Unknown;
ULONG SectionAttributes;
LARGE_INTEGER SectionSize;
} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION;
extern NTSTATUS NTAPI NtMapViewOfSection(
IN HANDLE SectionHandle, IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress,
IN ULONG_PTR ZeroBits, IN SIZE_T CommitSize,
IN OUT OPTIONAL PLARGE_INTEGER SectionOffset, IN OUT PSIZE_T ViewSize,
IN SECTION_INHERIT InheritDisposition, IN ULONG AllocationType,
IN ULONG Win32Protect);
extern NTSTATUS NTAPI NtUnmapViewOfSection(IN HANDLE ProcessHandle,
IN OPTIONAL PVOID BaseAddress);
/* Zap: Inconsistent annotation for 'NtClose'... */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(28251)
extern NTSTATUS NTAPI NtClose(HANDLE Handle);
extern NTSTATUS NTAPI NtAllocateVirtualMemory(
IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG_PTR ZeroBits,
IN OUT PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect);
extern NTSTATUS NTAPI NtFreeVirtualMemory(IN HANDLE ProcessHandle,
IN PVOID *BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG FreeType);
#ifndef WOF_CURRENT_VERSION
typedef struct _WOF_EXTERNAL_INFO {
DWORD Version;
DWORD Provider;
} WOF_EXTERNAL_INFO, *PWOF_EXTERNAL_INFO;
#endif /* WOF_CURRENT_VERSION */
#ifndef WIM_PROVIDER_CURRENT_VERSION
#define WIM_PROVIDER_HASH_SIZE 20
typedef struct _WIM_PROVIDER_EXTERNAL_INFO {
DWORD Version;
DWORD Flags;
LARGE_INTEGER DataSourceId;
BYTE ResourceHash[WIM_PROVIDER_HASH_SIZE];
} WIM_PROVIDER_EXTERNAL_INFO, *PWIM_PROVIDER_EXTERNAL_INFO;
#endif /* WIM_PROVIDER_CURRENT_VERSION */
#ifndef FILE_PROVIDER_CURRENT_VERSION
typedef struct _FILE_PROVIDER_EXTERNAL_INFO_V1 {
ULONG Version;
ULONG Algorithm;
ULONG Flags;
} FILE_PROVIDER_EXTERNAL_INFO_V1, *PFILE_PROVIDER_EXTERNAL_INFO_V1;
#endif /* FILE_PROVIDER_CURRENT_VERSION */
#ifndef STATUS_OBJECT_NOT_EXTERNALLY_BACKED
#define STATUS_OBJECT_NOT_EXTERNALLY_BACKED ((NTSTATUS)0xC000046DL)
#endif
#ifndef STATUS_INVALID_DEVICE_REQUEST
#define STATUS_INVALID_DEVICE_REQUEST ((NTSTATUS)0xC0000010L)
#endif
#ifndef STATUS_NOT_SUPPORTED
#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BBL)
#endif
#ifndef FILE_DEVICE_FILE_SYSTEM
#define FILE_DEVICE_FILE_SYSTEM 0x00000009
#endif
#ifndef FSCTL_GET_EXTERNAL_BACKING
#define FSCTL_GET_EXTERNAL_BACKING \
CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 196, METHOD_BUFFERED, FILE_ANY_ACCESS)
#endif
#ifndef ERROR_NOT_CAPABLE
#define ERROR_NOT_CAPABLE 775L
#endif
#endif /* _WIN32 || _WIN64 */
/*----------------------------------------------------------------------------*/
#if defined(__ANDROID_API__)
__extern_C void __assert2(const char *file, int line, const char *function,
const char *msg) __noreturn;
#define __assert_fail(assertion, file, line, function) \
__assert2(file, line, function, assertion)
#elif defined(__UCLIBC__)
__extern_C void __assert(const char *, const char *, unsigned int, const char *)
#ifdef __THROW
__THROW
#else
__nothrow
#endif /* __THROW */
MDBX_NORETURN;
#define __assert_fail(assertion, file, line, function) \
__assert(assertion, file, line, function)
#elif _POSIX_C_SOURCE > 200212 && \
/* workaround for avoid musl libc wrong prototype */ ( \
defined(__GLIBC__) || defined(__GNU_LIBRARY__))
/* Prototype should match libc runtime. ISO POSIX (2003) & LSB 1.x-3.x */
__extern_C void __assert_fail(const char *assertion, const char *file,
unsigned line, const char *function)
#ifdef __THROW
__THROW
#else
__nothrow
#endif /* __THROW */
MDBX_NORETURN;
#elif defined(__APPLE__) || defined(__MACH__)
__extern_C void __assert_rtn(const char *function, const char *file, int line,
const char *assertion) /* __nothrow */
#ifdef __dead2
__dead2
#else
MDBX_NORETURN
#endif /* __dead2 */
#ifdef __disable_tail_calls
__disable_tail_calls
#endif /* __disable_tail_calls */
;
#define __assert_fail(assertion, file, line, function) \
__assert_rtn(function, file, line, assertion)
#elif defined(__sun) || defined(__SVR4) || defined(__svr4__)
__extern_C void __assert_c99(const char *assection, const char *file, int line,
const char *function) MDBX_NORETURN;
#define __assert_fail(assertion, file, line, function) \
__assert_c99(assertion, file, line, function)
#elif defined(__OpenBSD__)
__extern_C __dead void __assert2(const char *file, int line,
const char *function,
const char *assertion) /* __nothrow */;
#define __assert_fail(assertion, file, line, function) \
__assert2(file, line, function, assertion)
#elif defined(__NetBSD__)
__extern_C __dead void __assert13(const char *file, int line,
const char *function,
const char *assertion) /* __nothrow */;
#define __assert_fail(assertion, file, line, function) \
__assert13(file, line, function, assertion)
#elif defined(__FreeBSD__) || defined(__BSD__) || defined(__bsdi__) || \
defined(__DragonFly__)
__extern_C void __assert(const char *function, const char *file, int line,
const char *assertion) /* __nothrow */
#ifdef __dead2
__dead2
#else
MDBX_NORETURN
#endif /* __dead2 */
#ifdef __disable_tail_calls
__disable_tail_calls
#endif /* __disable_tail_calls */
;
#define __assert_fail(assertion, file, line, function) \
__assert(function, file, line, assertion)
#endif /* __assert_fail */
__cold void mdbx_assert_fail(const MDBX_env *env, const char *msg,
const char *func, unsigned line) {
#if MDBX_DEBUG
if (env && env->me_assert_func)
env->me_assert_func(env, msg, func, line);
#else
(void)env;
assert_fail(msg, func, line);
}
MDBX_NORETURN __cold void assert_fail(const char *msg, const char *func,
unsigned line) {
#endif /* MDBX_DEBUG */
if (debug_logger)
debug_log(MDBX_LOG_FATAL, func, line, "assert: %s\n", msg);
else {
#if defined(_WIN32) || defined(_WIN64)
char *message = nullptr;
const int num = osal_asprintf(&message, "\r\nMDBX-ASSERTION: %s, %s:%u",
msg, func ? func : "unknown", line);
if (num < 1 || !message)
message = "<troubles with assertion-message preparation>";
OutputDebugStringA(message);
#else
__assert_fail(msg, "mdbx", line, func);
#endif
}
while (1) {
#if defined(_WIN32) || defined(_WIN64)
#if !MDBX_WITHOUT_MSVC_CRT && defined(_DEBUG)
_CrtDbgReport(_CRT_ASSERT, func ? func : "unknown", line, "libmdbx",
"assertion failed: %s", msg);
#else
if (IsDebuggerPresent())
DebugBreak();
#endif
FatalExit(STATUS_ASSERTION_FAILURE);
#else
abort();
#endif
}
}
__cold void mdbx_panic(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
char *message = nullptr;
const int num = osal_vasprintf(&message, fmt, ap);
va_end(ap);
const char *const const_message =
unlikely(num < 1 || !message)
? "<troubles with panic-message preparation>"
: message;
if (debug_logger)
debug_log(MDBX_LOG_FATAL, "panic", 0, "%s", const_message);
while (1) {
#if defined(_WIN32) || defined(_WIN64)
#if !MDBX_WITHOUT_MSVC_CRT && defined(_DEBUG)
_CrtDbgReport(_CRT_ASSERT, "mdbx.c", 0, "libmdbx", "panic: %s",
const_message);
#else
OutputDebugStringA("\r\nMDBX-PANIC: ");
OutputDebugStringA(const_message);
if (IsDebuggerPresent())
DebugBreak();
#endif
FatalExit(ERROR_UNHANDLED_ERROR);
#else
__assert_fail(const_message, "mdbx", 0, "panic");
abort();
#endif
}
}
/*----------------------------------------------------------------------------*/
#ifndef osal_vasprintf
MDBX_INTERNAL_FUNC int osal_vasprintf(char **strp, const char *fmt,
va_list ap) {
va_list ones;
va_copy(ones, ap);
const int needed = vsnprintf(nullptr, 0, fmt, ones);
va_end(ones);
if (unlikely(needed < 0 || needed >= INT_MAX)) {
*strp = nullptr;
return needed;
}
*strp = osal_malloc(needed + (size_t)1);
if (unlikely(*strp == nullptr)) {
#if defined(_WIN32) || defined(_WIN64)
SetLastError(MDBX_ENOMEM);
#else
errno = MDBX_ENOMEM;
#endif
return -1;
}
const int actual = vsnprintf(*strp, needed + (size_t)1, fmt, ap);
assert(actual == needed);
if (unlikely(actual < 0)) {
osal_free(*strp);
*strp = nullptr;
}
return actual;
}
#endif /* osal_vasprintf */
#ifndef osal_asprintf
MDBX_INTERNAL_FUNC int osal_asprintf(char **strp, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
const int rc = osal_vasprintf(strp, fmt, ap);
va_end(ap);
return rc;
}
#endif /* osal_asprintf */
#ifndef osal_memalign_alloc
MDBX_INTERNAL_FUNC int osal_memalign_alloc(size_t alignment, size_t bytes,
void **result) {
assert(is_powerof2(alignment) && alignment >= sizeof(void *));
#if defined(_WIN32) || defined(_WIN64)
(void)alignment;
*result = VirtualAlloc(NULL, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
return *result ? MDBX_SUCCESS : MDBX_ENOMEM /* ERROR_OUTOFMEMORY */;
#elif defined(_ISOC11_SOURCE)
*result = aligned_alloc(alignment, ceil_powerof2(bytes, alignment));
return *result ? MDBX_SUCCESS : errno;
#elif _POSIX_VERSION >= 200112L && \
(!defined(__ANDROID_API__) || __ANDROID_API__ >= 17)
*result = nullptr;
return posix_memalign(result, alignment, bytes);
#elif __GLIBC_PREREQ(2, 16) || __STDC_VERSION__ >= 201112L
*result = memalign(alignment, bytes);
return *result ? MDBX_SUCCESS : errno;
#else
#error FIXME
#endif
}
#endif /* osal_memalign_alloc */
#ifndef osal_memalign_free
MDBX_INTERNAL_FUNC void osal_memalign_free(void *ptr) {
#if defined(_WIN32) || defined(_WIN64)
VirtualFree(ptr, 0, MEM_RELEASE);
#else
osal_free(ptr);
#endif
}
#endif /* osal_memalign_free */
#ifndef osal_strdup
char *osal_strdup(const char *str) {
if (!str)
return NULL;
size_t bytes = strlen(str) + 1;
char *dup = osal_malloc(bytes);
if (dup)
memcpy(dup, str, bytes);
return dup;
}
#endif /* osal_strdup */
/*----------------------------------------------------------------------------*/
MDBX_INTERNAL_FUNC int osal_condpair_init(osal_condpair_t *condpair) {
int rc;
memset(condpair, 0, sizeof(osal_condpair_t));
#if defined(_WIN32) || defined(_WIN64)
if ((condpair->mutex = CreateMutexW(NULL, FALSE, NULL)) == NULL) {
rc = (int)GetLastError();
goto bailout_mutex;
}
if ((condpair->event[0] = CreateEventW(NULL, FALSE, FALSE, NULL)) == NULL) {
rc = (int)GetLastError();
goto bailout_event;
}
if ((condpair->event[1] = CreateEventW(NULL, FALSE, FALSE, NULL)) != NULL)
return MDBX_SUCCESS;
rc = (int)GetLastError();
(void)CloseHandle(condpair->event[0]);
bailout_event:
(void)CloseHandle(condpair->mutex);
#else
rc = pthread_mutex_init(&condpair->mutex, NULL);
if (unlikely(rc != 0))
goto bailout_mutex;
rc = pthread_cond_init(&condpair->cond[0], NULL);
if (unlikely(rc != 0))
goto bailout_cond;
rc = pthread_cond_init(&condpair->cond[1], NULL);
if (likely(rc == 0))
return MDBX_SUCCESS;
(void)pthread_cond_destroy(&condpair->cond[0]);
bailout_cond:
(void)pthread_mutex_destroy(&condpair->mutex);
#endif
bailout_mutex:
memset(condpair, 0, sizeof(osal_condpair_t));
return rc;
}
MDBX_INTERNAL_FUNC int osal_condpair_destroy(osal_condpair_t *condpair) {
#if defined(_WIN32) || defined(_WIN64)
int rc = CloseHandle(condpair->mutex) ? MDBX_SUCCESS : (int)GetLastError();
rc = CloseHandle(condpair->event[0]) ? rc : (int)GetLastError();
rc = CloseHandle(condpair->event[1]) ? rc : (int)GetLastError();
#else
int err, rc = pthread_mutex_destroy(&condpair->mutex);
rc = (err = pthread_cond_destroy(&condpair->cond[0])) ? err : rc;
rc = (err = pthread_cond_destroy(&condpair->cond[1])) ? err : rc;
#endif
memset(condpair, 0, sizeof(osal_condpair_t));
return rc;
}
MDBX_INTERNAL_FUNC int osal_condpair_lock(osal_condpair_t *condpair) {
#if defined(_WIN32) || defined(_WIN64)
DWORD code = WaitForSingleObject(condpair->mutex, INFINITE);
return waitstatus2errcode(code);
#else
return osal_pthread_mutex_lock(&condpair->mutex);
#endif
}
MDBX_INTERNAL_FUNC int osal_condpair_unlock(osal_condpair_t *condpair) {
#if defined(_WIN32) || defined(_WIN64)
return ReleaseMutex(condpair->mutex) ? MDBX_SUCCESS : (int)GetLastError();
#else
return pthread_mutex_unlock(&condpair->mutex);
#endif
}
MDBX_INTERNAL_FUNC int osal_condpair_signal(osal_condpair_t *condpair,
bool part) {
#if defined(_WIN32) || defined(_WIN64)
return SetEvent(condpair->event[part]) ? MDBX_SUCCESS : (int)GetLastError();
#else
return pthread_cond_signal(&condpair->cond[part]);
#endif
}
MDBX_INTERNAL_FUNC int osal_condpair_wait(osal_condpair_t *condpair,
bool part) {
#if defined(_WIN32) || defined(_WIN64)
DWORD code = SignalObjectAndWait(condpair->mutex, condpair->event[part],
INFINITE, FALSE);
if (code == WAIT_OBJECT_0) {
code = WaitForSingleObject(condpair->mutex, INFINITE);
if (code == WAIT_OBJECT_0)
return MDBX_SUCCESS;
}
return waitstatus2errcode(code);
#else
return pthread_cond_wait(&condpair->cond[part], &condpair->mutex);
#endif
}
/*----------------------------------------------------------------------------*/
MDBX_INTERNAL_FUNC int osal_fastmutex_init(osal_fastmutex_t *fastmutex) {
#if defined(_WIN32) || defined(_WIN64)
InitializeCriticalSection(fastmutex);
return MDBX_SUCCESS;
#elif MDBX_DEBUG
pthread_mutexattr_t ma;
int rc = pthread_mutexattr_init(&ma);
if (likely(!rc)) {
rc = pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK);
if (likely(!rc) || rc == ENOTSUP)
rc = pthread_mutex_init(fastmutex, &ma);
pthread_mutexattr_destroy(&ma);
}
return rc;
#else
return pthread_mutex_init(fastmutex, nullptr);
#endif
}
MDBX_INTERNAL_FUNC int osal_fastmutex_destroy(osal_fastmutex_t *fastmutex) {
#if defined(_WIN32) || defined(_WIN64)
DeleteCriticalSection(fastmutex);
return MDBX_SUCCESS;
#else
return pthread_mutex_destroy(fastmutex);
#endif
}
MDBX_INTERNAL_FUNC int osal_fastmutex_acquire(osal_fastmutex_t *fastmutex) {
#if defined(_WIN32) || defined(_WIN64)
__try {
EnterCriticalSection(fastmutex);
} __except (
(GetExceptionCode() ==
0xC0000194 /* STATUS_POSSIBLE_DEADLOCK / EXCEPTION_POSSIBLE_DEADLOCK */)
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH) {
return MDBX_EDEADLK;
}
return MDBX_SUCCESS;
#else
return osal_pthread_mutex_lock(fastmutex);
#endif
}
MDBX_INTERNAL_FUNC int osal_fastmutex_release(osal_fastmutex_t *fastmutex) {
#if defined(_WIN32) || defined(_WIN64)
LeaveCriticalSection(fastmutex);
return MDBX_SUCCESS;
#else
return pthread_mutex_unlock(fastmutex);
#endif
}
/*----------------------------------------------------------------------------*/
#if defined(_WIN32) || defined(_WIN64)
MDBX_INTERNAL_FUNC int osal_mb2w(const char *const src, wchar_t **const pdst) {
const size_t dst_wlen = MultiByteToWideChar(
CP_THREAD_ACP, MB_ERR_INVALID_CHARS, src, -1, nullptr, 0);
wchar_t *dst = *pdst;
int rc = ERROR_INVALID_NAME;
if (unlikely(dst_wlen < 2 || dst_wlen > /* MAX_PATH */ INT16_MAX))
goto bailout;
dst = osal_realloc(dst, dst_wlen * sizeof(wchar_t));
rc = MDBX_ENOMEM;
if (unlikely(!dst))
goto bailout;
*pdst = dst;
if (likely(dst_wlen == (size_t)MultiByteToWideChar(CP_THREAD_ACP,
MB_ERR_INVALID_CHARS, src,
-1, dst, (int)dst_wlen)))
return MDBX_SUCCESS;
rc = ERROR_INVALID_NAME;
bailout:
if (*pdst) {
osal_free(*pdst);
*pdst = nullptr;
}
return rc;
}
#endif /* Windows */
/*----------------------------------------------------------------------------*/
#if defined(_WIN32) || defined(_WIN64)
#define ior_alignment_mask (ior->pagesize - 1)
#define ior_WriteFile_flag 1
#define OSAL_IOV_MAX (4096 / sizeof(ior_sgv_element))
static void ior_put_event(osal_ioring_t *ior, HANDLE event) {
assert(event && event != INVALID_HANDLE_VALUE && event != ior);
assert(ior->event_stack < ior->allocated);
ior->event_pool[ior->event_stack] = event;
ior->event_stack += 1;
}
static HANDLE ior_get_event(osal_ioring_t *ior) {
assert(ior->event_stack <= ior->allocated);
if (ior->event_stack > 0) {
ior->event_stack -= 1;
assert(ior->event_pool[ior->event_stack] != 0);
return ior->event_pool[ior->event_stack];
}
return CreateEventW(nullptr, true, false, nullptr);
}
static void WINAPI ior_wocr(DWORD err, DWORD bytes, OVERLAPPED *ov) {
osal_ioring_t *ior = ov->hEvent;
ov->Internal = err;
ov->InternalHigh = bytes;
if (++ior->async_completed >= ior->async_waiting)
SetEvent(ior->async_done);
}
#elif MDBX_HAVE_PWRITEV
#if defined(_SC_IOV_MAX)
static size_t osal_iov_max;
#define OSAL_IOV_MAX osal_iov_max
#else
#define OSAL_IOV_MAX IOV_MAX
#endif
#else
#undef OSAL_IOV_MAX
#endif /* OSAL_IOV_MAX */
MDBX_INTERNAL_FUNC int osal_ioring_create(osal_ioring_t *ior
#if defined(_WIN32) || defined(_WIN64)
,
bool enable_direct,
mdbx_filehandle_t overlapped_fd
#endif /* Windows */
) {
memset(ior, 0, sizeof(osal_ioring_t));
#if defined(_WIN32) || defined(_WIN64)
ior->overlapped_fd = overlapped_fd;
ior->direct = enable_direct && overlapped_fd;
const unsigned pagesize = (unsigned)osal_syspagesize();
ior->pagesize = pagesize;
ior->pagesize_ln2 = (uint8_t)log2n_powerof2(pagesize);
ior->async_done = ior_get_event(ior);
if (!ior->async_done)
return GetLastError();
#endif /* !Windows */
#if MDBX_HAVE_PWRITEV && defined(_SC_IOV_MAX)
assert(osal_iov_max > 0);
#endif /* MDBX_HAVE_PWRITEV && _SC_IOV_MAX */
ior->boundary = ptr_disp(ior->pool, ior->allocated);
return MDBX_SUCCESS;
}
static __inline size_t ior_offset(const ior_item_t *item) {
#if defined(_WIN32) || defined(_WIN64)
return item->ov.Offset | (size_t)((sizeof(size_t) > sizeof(item->ov.Offset))
? (uint64_t)item->ov.OffsetHigh << 32
: 0);
#else
return item->offset;
#endif /* !Windows */
}
static __inline ior_item_t *ior_next(ior_item_t *item, size_t sgvcnt) {
#if defined(ior_sgv_element)
assert(sgvcnt > 0);
return (ior_item_t *)ptr_disp(item, sizeof(ior_item_t) -
sizeof(ior_sgv_element) +
sizeof(ior_sgv_element) * sgvcnt);
#else
assert(sgvcnt == 1);
(void)sgvcnt;
return item + 1;
#endif
}
MDBX_INTERNAL_FUNC int osal_ioring_add(osal_ioring_t *ior, const size_t offset,
void *data, const size_t bytes) {
assert(bytes && data);
assert(bytes % MIN_PAGESIZE == 0 && bytes <= MAX_WRITE);
assert(offset % MIN_PAGESIZE == 0 && offset + (uint64_t)bytes <= MAX_MAPSIZE);
#if defined(_WIN32) || defined(_WIN64)
const unsigned segments = (unsigned)(bytes >> ior->pagesize_ln2);
const bool use_gather =
ior->direct && ior->overlapped_fd && ior->slots_left >= segments;
#endif /* Windows */
ior_item_t *item = ior->pool;
if (likely(ior->last)) {
item = ior->last;
if (unlikely(ior_offset(item) + ior_last_bytes(ior, item) == offset) &&
likely(ior_last_bytes(ior, item) + bytes <= MAX_WRITE)) {
#if defined(_WIN32) || defined(_WIN64)
if (use_gather &&
((bytes | (uintptr_t)data | ior->last_bytes |
(uintptr_t)(uint64_t)item->sgv[0].Buffer) &
ior_alignment_mask) == 0 &&
ior->last_sgvcnt + (size_t)segments < OSAL_IOV_MAX) {
assert(ior->overlapped_fd);
assert((item->single.iov_len & ior_WriteFile_flag) == 0);
assert(item->sgv[ior->last_sgvcnt].Buffer == 0);
ior->last_bytes += bytes;
size_t i = 0;
do {
item->sgv[ior->last_sgvcnt + i].Buffer = PtrToPtr64(data);
data = ptr_disp(data, ior->pagesize);
} while (++i < segments);
ior->slots_left -= segments;
item->sgv[ior->last_sgvcnt += segments].Buffer = 0;
assert((item->single.iov_len & ior_WriteFile_flag) == 0);
return MDBX_SUCCESS;
}
const void *end = ptr_disp(item->single.iov_base,
item->single.iov_len - ior_WriteFile_flag);
if (unlikely(end == data)) {
assert((item->single.iov_len & ior_WriteFile_flag) != 0);
item->single.iov_len += bytes;
return MDBX_SUCCESS;
}
#elif MDBX_HAVE_PWRITEV
assert((int)item->sgvcnt > 0);
const void *end = ptr_disp(item->sgv[item->sgvcnt - 1].iov_base,
item->sgv[item->sgvcnt - 1].iov_len);
if (unlikely(end == data)) {
item->sgv[item->sgvcnt - 1].iov_len += bytes;
ior->last_bytes += bytes;
return MDBX_SUCCESS;
}
if (likely(item->sgvcnt < OSAL_IOV_MAX)) {
if (unlikely(ior->slots_left < 1))
return MDBX_RESULT_TRUE;
item->sgv[item->sgvcnt].iov_base = data;
item->sgv[item->sgvcnt].iov_len = bytes;
ior->last_bytes += bytes;
item->sgvcnt += 1;
ior->slots_left -= 1;
return MDBX_SUCCESS;
}
#else
const void *end = ptr_disp(item->single.iov_base, item->single.iov_len);
if (unlikely(end == data)) {
item->single.iov_len += bytes;
return MDBX_SUCCESS;
}
#endif
}
item = ior_next(item, ior_last_sgvcnt(ior, item));
}
if (unlikely(ior->slots_left < 1))
return MDBX_RESULT_TRUE;
unsigned slots_used = 1;
#if defined(_WIN32) || defined(_WIN64)
item->ov.Internal = item->ov.InternalHigh = 0;
item->ov.Offset = (DWORD)offset;
item->ov.OffsetHigh = HIGH_DWORD(offset);
item->ov.hEvent = 0;
if (!use_gather || ((bytes | (uintptr_t)(data)) & ior_alignment_mask) != 0 ||
segments > OSAL_IOV_MAX) {
/* WriteFile() */
item->single.iov_base = data;
item->single.iov_len = bytes + ior_WriteFile_flag;
assert((item->single.iov_len & ior_WriteFile_flag) != 0);
} else {
/* WriteFileGather() */
assert(ior->overlapped_fd);
item->sgv[0].Buffer = PtrToPtr64(data);
for (size_t i = 1; i < segments; ++i) {
data = ptr_disp(data, ior->pagesize);
item->sgv[slots_used].Buffer = PtrToPtr64(data);
}
item->sgv[slots_used].Buffer = 0;
assert((item->single.iov_len & ior_WriteFile_flag) == 0);
slots_used = segments;
}
ior->last_bytes = bytes;
ior_last_sgvcnt(ior, item) = slots_used;
#elif MDBX_HAVE_PWRITEV
item->offset = offset;
item->sgv[0].iov_base = data;
item->sgv[0].iov_len = bytes;
ior->last_bytes = bytes;
ior_last_sgvcnt(ior, item) = slots_used;
#else
item->offset = offset;
item->single.iov_base = data;
item->single.iov_len = bytes;
#endif /* !Windows */
ior->slots_left -= slots_used;
ior->last = item;
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC void osal_ioring_walk(
osal_ioring_t *ior, iov_ctx_t *ctx,
void (*callback)(iov_ctx_t *ctx, size_t offset, void *data, size_t bytes)) {
for (ior_item_t *item = ior->pool; item <= ior->last;) {
#if defined(_WIN32) || defined(_WIN64)
size_t offset = ior_offset(item);
char *data = item->single.iov_base;
size_t bytes = item->single.iov_len - ior_WriteFile_flag;
size_t i = 1;
if (bytes & ior_WriteFile_flag) {
data = Ptr64ToPtr(item->sgv[0].Buffer);
bytes = ior->pagesize;
/* Zap: Reading invalid data from 'item->sgv' */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6385);
while (item->sgv[i].Buffer) {
if (data + ior->pagesize != item->sgv[i].Buffer) {
callback(ctx, offset, data, bytes);
offset += bytes;
data = Ptr64ToPtr(item->sgv[i].Buffer);
bytes = 0;
}
bytes += ior->pagesize;
++i;
}
}
assert(bytes < MAX_WRITE);
callback(ctx, offset, data, bytes);
#elif MDBX_HAVE_PWRITEV
assert(item->sgvcnt > 0);
size_t offset = item->offset;
size_t i = 0;
do {
callback(ctx, offset, item->sgv[i].iov_base, item->sgv[i].iov_len);
offset += item->sgv[i].iov_len;
} while (++i != item->sgvcnt);
#else
const size_t i = 1;
callback(ctx, item->offset, item->single.iov_base, item->single.iov_len);
#endif
item = ior_next(item, i);
}
}
MDBX_INTERNAL_FUNC osal_ioring_write_result_t
osal_ioring_write(osal_ioring_t *ior, mdbx_filehandle_t fd) {
osal_ioring_write_result_t r = {MDBX_SUCCESS, 0};
#if defined(_WIN32) || defined(_WIN64)
HANDLE *const end_wait_for =
ior->event_pool + ior->allocated +
/* был выделен один дополнительный элемент для async_done */ 1;
HANDLE *wait_for = end_wait_for;
LONG async_started = 0;
for (ior_item_t *item = ior->pool; item <= ior->last;) {
item->ov.Internal = STATUS_PENDING;
size_t i = 1, bytes = item->single.iov_len - ior_WriteFile_flag;
r.wops += 1;
if (bytes & ior_WriteFile_flag) {
assert(ior->overlapped_fd && fd == ior->overlapped_fd);
bytes = ior->pagesize;
/* Zap: Reading invalid data from 'item->sgv' */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6385);
while (item->sgv[i].Buffer) {
bytes += ior->pagesize;
++i;
}
assert(bytes < MAX_WRITE);
item->ov.hEvent = ior_get_event(ior);
if (unlikely(!item->ov.hEvent)) {
bailout_geterr:
r.err = GetLastError();
bailout_rc:
assert(r.err != MDBX_SUCCESS);
CancelIo(fd);
return r;
}
if (WriteFileGather(fd, item->sgv, (DWORD)bytes, nullptr, &item->ov)) {
assert(item->ov.Internal == 0 &&
WaitForSingleObject(item->ov.hEvent, 0) == WAIT_OBJECT_0);
ior_put_event(ior, item->ov.hEvent);
item->ov.hEvent = 0;
} else {
r.err = (int)GetLastError();
if (unlikely(r.err != ERROR_IO_PENDING)) {
ERROR("%s: fd %p, item %p (%zu), pgno %u, bytes %zu, offset %" PRId64
", err %d",
"WriteFileGather", fd, __Wpedantic_format_voidptr(item),
item - ior->pool, ((MDBX_page *)item->single.iov_base)->mp_pgno,
bytes, item->ov.Offset + ((uint64_t)item->ov.OffsetHigh << 32),
r.err);
goto bailout_rc;
}
assert(wait_for > ior->event_pool + ior->event_stack);
*--wait_for = item->ov.hEvent;
}
} else if (fd == ior->overlapped_fd) {
assert(bytes < MAX_WRITE);
retry:
item->ov.hEvent = ior;
if (WriteFileEx(fd, item->single.iov_base, (DWORD)bytes, &item->ov,
ior_wocr)) {
async_started += 1;
} else {
r.err = (int)GetLastError();
switch (r.err) {
default:
ERROR("%s: fd %p, item %p (%zu), pgno %u, bytes %zu, offset %" PRId64
", err %d",
"WriteFileEx", fd, __Wpedantic_format_voidptr(item),
item - ior->pool, ((MDBX_page *)item->single.iov_base)->mp_pgno,
bytes, item->ov.Offset + ((uint64_t)item->ov.OffsetHigh << 32),
r.err);
goto bailout_rc;
case ERROR_NOT_FOUND:
case ERROR_USER_MAPPED_FILE:
case ERROR_LOCK_VIOLATION:
WARNING(
"%s: fd %p, item %p (%zu), pgno %u, bytes %zu, offset %" PRId64
", err %d",
"WriteFileEx", fd, __Wpedantic_format_voidptr(item),
item - ior->pool, ((MDBX_page *)item->single.iov_base)->mp_pgno,
bytes, item->ov.Offset + ((uint64_t)item->ov.OffsetHigh << 32),
r.err);
SleepEx(0, true);
goto retry;
case ERROR_INVALID_USER_BUFFER:
case ERROR_NOT_ENOUGH_MEMORY:
if (SleepEx(0, true) == WAIT_IO_COMPLETION)
goto retry;
goto bailout_rc;
case ERROR_IO_PENDING:
async_started += 1;
}
}
} else {
assert(bytes < MAX_WRITE);
DWORD written = 0;
if (!WriteFile(fd, item->single.iov_base, (DWORD)bytes, &written,
&item->ov)) {
r.err = (int)GetLastError();
ERROR("%s: fd %p, item %p (%zu), pgno %u, bytes %zu, offset %" PRId64
", err %d",
"WriteFile", fd, __Wpedantic_format_voidptr(item),
item - ior->pool, ((MDBX_page *)item->single.iov_base)->mp_pgno,
bytes, item->ov.Offset + ((uint64_t)item->ov.OffsetHigh << 32),
r.err);
goto bailout_rc;
} else if (unlikely(written != bytes)) {
r.err = ERROR_WRITE_FAULT;
goto bailout_rc;
}
}
item = ior_next(item, i);
}
assert(ior->async_waiting > ior->async_completed &&
ior->async_waiting == INT_MAX);
ior->async_waiting = async_started;
if (async_started > ior->async_completed && end_wait_for == wait_for) {
assert(wait_for > ior->event_pool + ior->event_stack);
*--wait_for = ior->async_done;
}
const size_t pending_count = end_wait_for - wait_for;
if (pending_count) {
/* Ждем до MAXIMUM_WAIT_OBJECTS (64) последних хендлов, а после избирательно
* ждем посредством GetOverlappedResult(), если какие-то более ранние
* элементы еще не завершены. В целом, так получается меньше системных
* вызовов, т.е. меньше накладных расходов. Однако, не факт что эта экономия
* не будет перекрыта неэффективностью реализации
* WaitForMultipleObjectsEx(), но тогда это проблемы на стороне M$. */
DWORD madness;
do
madness = WaitForMultipleObjectsEx((pending_count < MAXIMUM_WAIT_OBJECTS)
? (DWORD)pending_count
: MAXIMUM_WAIT_OBJECTS,
wait_for, true,
/* сутки */ 86400000ul, true);
while (madness == WAIT_IO_COMPLETION);
STATIC_ASSERT(WAIT_OBJECT_0 == 0);
if (/* madness >= WAIT_OBJECT_0 && */
madness < WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS)
r.err = MDBX_SUCCESS;
else if (madness >= WAIT_ABANDONED_0 &&
madness < WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS) {
r.err = ERROR_ABANDONED_WAIT_0;
goto bailout_rc;
} else if (madness == WAIT_TIMEOUT) {
r.err = WAIT_TIMEOUT;
goto bailout_rc;
} else {
r.err = /* madness == WAIT_FAILED */ MDBX_PROBLEM;
goto bailout_rc;
}
assert(ior->async_waiting == ior->async_completed);
for (ior_item_t *item = ior->pool; item <= ior->last;) {
size_t i = 1, bytes = item->single.iov_len - ior_WriteFile_flag;
if (bytes & ior_WriteFile_flag) {
bytes = ior->pagesize;
/* Zap: Reading invalid data from 'item->sgv' */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6385);
while (item->sgv[i].Buffer) {
bytes += ior->pagesize;
++i;
}
if (!HasOverlappedIoCompleted(&item->ov)) {
DWORD written = 0;
if (unlikely(!GetOverlappedResult(fd, &item->ov, &written, true))) {
ERROR("%s: item %p (%zu), pgno %u, bytes %zu, offset %" PRId64
", err %d",
"GetOverlappedResult", __Wpedantic_format_voidptr(item),
item - ior->pool,
((MDBX_page *)item->single.iov_base)->mp_pgno, bytes,
item->ov.Offset + ((uint64_t)item->ov.OffsetHigh << 32),
(int)GetLastError());
goto bailout_geterr;
}
assert(MDBX_SUCCESS == item->ov.Internal);
assert(written == item->ov.InternalHigh);
}
} else {
assert(HasOverlappedIoCompleted(&item->ov));
}
assert(item->ov.Internal != ERROR_IO_PENDING);
if (unlikely(item->ov.Internal != MDBX_SUCCESS)) {
DWORD written = 0;
r.err = (int)item->ov.Internal;
if ((r.err & 0x80000000) &&
GetOverlappedResult(NULL, &item->ov, &written, true))
r.err = (int)GetLastError();
ERROR("%s: item %p (%zu), pgno %u, bytes %zu, offset %" PRId64
", err %d",
"Result", __Wpedantic_format_voidptr(item), item - ior->pool,
((MDBX_page *)item->single.iov_base)->mp_pgno, bytes,
item->ov.Offset + ((uint64_t)item->ov.OffsetHigh << 32),
(int)GetLastError());
goto bailout_rc;
}
if (unlikely(item->ov.InternalHigh != bytes)) {
r.err = ERROR_WRITE_FAULT;
goto bailout_rc;
}
item = ior_next(item, i);
}
assert(ior->async_waiting == ior->async_completed);
} else {
assert(r.err == MDBX_SUCCESS);
}
assert(ior->async_waiting == ior->async_completed);
#else
STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
for (ior_item_t *item = ior->pool; item <= ior->last;) {
#if MDBX_HAVE_PWRITEV
assert(item->sgvcnt > 0);
if (item->sgvcnt == 1)
r.err = osal_pwrite(fd, item->sgv[0].iov_base, item->sgv[0].iov_len,
item->offset);
else
r.err = osal_pwritev(fd, item->sgv, item->sgvcnt, item->offset);
// TODO: io_uring_prep_write(sqe, fd, ...);
item = ior_next(item, item->sgvcnt);
#else
r.err = osal_pwrite(fd, item->single.iov_base, item->single.iov_len,
item->offset);
item = ior_next(item, 1);
#endif
r.wops += 1;
if (unlikely(r.err != MDBX_SUCCESS))
break;
}
// TODO: io_uring_submit(&ring)
// TODO: err = io_uring_wait_cqe(&ring, &cqe);
// TODO: io_uring_cqe_seen(&ring, cqe);
#endif /* !Windows */
return r;
}
MDBX_INTERNAL_FUNC void osal_ioring_reset(osal_ioring_t *ior) {
#if defined(_WIN32) || defined(_WIN64)
if (ior->last) {
for (ior_item_t *item = ior->pool; item <= ior->last;) {
if (!HasOverlappedIoCompleted(&item->ov)) {
assert(ior->overlapped_fd);
CancelIoEx(ior->overlapped_fd, &item->ov);
}
if (item->ov.hEvent && item->ov.hEvent != ior)
ior_put_event(ior, item->ov.hEvent);
size_t i = 1;
if ((item->single.iov_len & ior_WriteFile_flag) == 0) {
/* Zap: Reading invalid data from 'item->sgv' */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6385);
while (item->sgv[i].Buffer)
++i;
}
item = ior_next(item, i);
}
}
ior->async_waiting = INT_MAX;
ior->async_completed = 0;
ResetEvent(ior->async_done);
#endif /* !Windows */
ior->slots_left = ior->allocated;
ior->last = nullptr;
}
static void ior_cleanup(osal_ioring_t *ior, const size_t since) {
osal_ioring_reset(ior);
#if defined(_WIN32) || defined(_WIN64)
for (size_t i = since; i < ior->event_stack; ++i) {
/* Zap: Using uninitialized memory '**ior.event_pool' */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6001);
CloseHandle(ior->event_pool[i]);
}
ior->event_stack = 0;
#else
(void)since;
#endif /* Windows */
}
MDBX_INTERNAL_FUNC int osal_ioring_resize(osal_ioring_t *ior, size_t items) {
assert(items > 0 && items < INT_MAX / sizeof(ior_item_t));
#if defined(_WIN32) || defined(_WIN64)
if (ior->state & IOR_STATE_LOCKED)
return MDBX_SUCCESS;
const bool useSetFileIoOverlappedRange =
ior->overlapped_fd && mdbx_SetFileIoOverlappedRange && items > 42;
const size_t ceiling =
useSetFileIoOverlappedRange
? ((items < 65536 / 2 / sizeof(ior_item_t)) ? 65536 : 65536 * 4)
: 1024;
const size_t bytes = ceil_powerof2(sizeof(ior_item_t) * items, ceiling);
items = bytes / sizeof(ior_item_t);
#endif /* Windows */
if (items != ior->allocated) {
assert(items >= osal_ioring_used(ior));
if (items < ior->allocated)
ior_cleanup(ior, items);
#if defined(_WIN32) || defined(_WIN64)
void *ptr = osal_realloc(
ior->event_pool,
(items + /* extra for waiting the async_done */ 1) * sizeof(HANDLE));
if (unlikely(!ptr))
return MDBX_ENOMEM;
ior->event_pool = ptr;
int err = osal_memalign_alloc(ceiling, bytes, &ptr);
if (unlikely(err != MDBX_SUCCESS))
return err;
if (ior->pool) {
memcpy(ptr, ior->pool, ior->allocated * sizeof(ior_item_t));
osal_memalign_free(ior->pool);
}
#else
void *ptr = osal_realloc(ior->pool, sizeof(ior_item_t) * items);
if (unlikely(!ptr))
return MDBX_ENOMEM;
#endif
ior->pool = ptr;
if (items > ior->allocated)
memset(ior->pool + ior->allocated, 0,
sizeof(ior_item_t) * (items - ior->allocated));
ior->allocated = (unsigned)items;
ior->boundary = ptr_disp(ior->pool, ior->allocated);
#if defined(_WIN32) || defined(_WIN64)
if (useSetFileIoOverlappedRange) {
if (mdbx_SetFileIoOverlappedRange(ior->overlapped_fd, ptr, (ULONG)bytes))
ior->state += IOR_STATE_LOCKED;
else
return GetLastError();
}
#endif /* Windows */
}
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC void osal_ioring_destroy(osal_ioring_t *ior) {
if (ior->allocated)
ior_cleanup(ior, 0);
#if defined(_WIN32) || defined(_WIN64)
osal_memalign_free(ior->pool);
osal_free(ior->event_pool);
CloseHandle(ior->async_done);
if (ior->overlapped_fd)
CloseHandle(ior->overlapped_fd);
#else
osal_free(ior->pool);
#endif
memset(ior, 0, sizeof(osal_ioring_t));
}
/*----------------------------------------------------------------------------*/
MDBX_INTERNAL_FUNC int osal_removefile(const pathchar_t *pathname) {
#if defined(_WIN32) || defined(_WIN64)
return DeleteFileW(pathname) ? MDBX_SUCCESS : (int)GetLastError();
#else
return unlink(pathname) ? errno : MDBX_SUCCESS;
#endif
}
#if !(defined(_WIN32) || defined(_WIN64))
static bool is_valid_fd(int fd) { return !(isatty(fd) < 0 && errno == EBADF); }
#endif /*! Windows */
MDBX_INTERNAL_FUNC int osal_removedirectory(const pathchar_t *pathname) {
#if defined(_WIN32) || defined(_WIN64)
return RemoveDirectoryW(pathname) ? MDBX_SUCCESS : (int)GetLastError();
#else
return rmdir(pathname) ? errno : MDBX_SUCCESS;
#endif
}
MDBX_INTERNAL_FUNC int osal_fileexists(const pathchar_t *pathname) {
#if defined(_WIN32) || defined(_WIN64)
if (GetFileAttributesW(pathname) != INVALID_FILE_ATTRIBUTES)
return MDBX_RESULT_TRUE;
int err = GetLastError();
return (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND)
? MDBX_RESULT_FALSE
: err;
#else
if (access(pathname, F_OK) == 0)
return MDBX_RESULT_TRUE;
int err = errno;
return (err == ENOENT || err == ENOTDIR) ? MDBX_RESULT_FALSE : err;
#endif
}
MDBX_INTERNAL_FUNC pathchar_t *osal_fileext(const pathchar_t *pathname,
size_t len) {
const pathchar_t *ext = nullptr;
for (size_t i = 0; i < len && pathname[i]; i++)
if (pathname[i] == '.')
ext = pathname + i;
else if (osal_isdirsep(pathname[i]))
ext = nullptr;
return (pathchar_t *)ext;
}
MDBX_INTERNAL_FUNC bool osal_pathequal(const pathchar_t *l, const pathchar_t *r,
size_t len) {
#if defined(_WIN32) || defined(_WIN64)
for (size_t i = 0; i < len; ++i) {
pathchar_t a = l[i];
pathchar_t b = r[i];
a = (a == '\\') ? '/' : a;
b = (b == '\\') ? '/' : b;
if (a != b)
return false;
}
return true;
#else
return memcmp(l, r, len * sizeof(pathchar_t)) == 0;
#endif
}
MDBX_INTERNAL_FUNC int osal_openfile(const enum osal_openfile_purpose purpose,
const MDBX_env *env,
const pathchar_t *pathname,
mdbx_filehandle_t *fd,
mdbx_mode_t unix_mode_bits) {
*fd = INVALID_HANDLE_VALUE;
#if defined(_WIN32) || defined(_WIN64)
DWORD CreationDisposition = unix_mode_bits ? OPEN_ALWAYS : OPEN_EXISTING;
DWORD FlagsAndAttributes =
FILE_FLAG_POSIX_SEMANTICS | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
DWORD DesiredAccess = FILE_READ_ATTRIBUTES;
DWORD ShareMode = (env->me_flags & MDBX_EXCLUSIVE)
? 0
: (FILE_SHARE_READ | FILE_SHARE_WRITE);
switch (purpose) {
default:
return ERROR_INVALID_PARAMETER;
case MDBX_OPEN_LCK:
CreationDisposition = OPEN_ALWAYS;
DesiredAccess |= GENERIC_READ | GENERIC_WRITE;
FlagsAndAttributes |= FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_TEMPORARY;
break;
case MDBX_OPEN_DXB_READ:
CreationDisposition = OPEN_EXISTING;
DesiredAccess |= GENERIC_READ;
ShareMode |= FILE_SHARE_READ;
break;
case MDBX_OPEN_DXB_LAZY:
DesiredAccess |= GENERIC_READ | GENERIC_WRITE;
break;
case MDBX_OPEN_DXB_OVERLAPPED_DIRECT:
FlagsAndAttributes |= FILE_FLAG_NO_BUFFERING;
/* fall through */
__fallthrough;
case MDBX_OPEN_DXB_OVERLAPPED:
FlagsAndAttributes |= FILE_FLAG_OVERLAPPED;
/* fall through */
__fallthrough;
case MDBX_OPEN_DXB_DSYNC:
CreationDisposition = OPEN_EXISTING;
DesiredAccess |= GENERIC_WRITE | GENERIC_READ;
FlagsAndAttributes |= FILE_FLAG_WRITE_THROUGH;
break;
case MDBX_OPEN_COPY:
CreationDisposition = CREATE_NEW;
ShareMode = 0;
DesiredAccess |= GENERIC_WRITE;
if (env->me_psize >= env->me_os_psize)
FlagsAndAttributes |= FILE_FLAG_NO_BUFFERING;
break;
case MDBX_OPEN_DELETE:
CreationDisposition = OPEN_EXISTING;
ShareMode |= FILE_SHARE_DELETE;
DesiredAccess =
FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | DELETE | SYNCHRONIZE;
break;
}
*fd = CreateFileW(pathname, DesiredAccess, ShareMode, NULL,
CreationDisposition, FlagsAndAttributes, NULL);
if (*fd == INVALID_HANDLE_VALUE) {
int err = (int)GetLastError();
if (err == ERROR_ACCESS_DENIED && purpose == MDBX_OPEN_LCK) {
if (GetFileAttributesW(pathname) == INVALID_FILE_ATTRIBUTES &&
GetLastError() == ERROR_FILE_NOT_FOUND)
err = ERROR_FILE_NOT_FOUND;
}
return err;
}
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(*fd, &info)) {
int err = (int)GetLastError();
CloseHandle(*fd);
*fd = INVALID_HANDLE_VALUE;
return err;
}
const DWORD AttributesDiff =
(info.dwFileAttributes ^ FlagsAndAttributes) &
(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
FILE_ATTRIBUTE_TEMPORARY | FILE_ATTRIBUTE_COMPRESSED);
if (AttributesDiff)
(void)SetFileAttributesW(pathname, info.dwFileAttributes ^ AttributesDiff);
#else
int flags = unix_mode_bits ? O_CREAT : 0;
switch (purpose) {
default:
return EINVAL;
case MDBX_OPEN_LCK:
flags |= O_RDWR;
break;
case MDBX_OPEN_DXB_READ:
flags = O_RDONLY;
break;
case MDBX_OPEN_DXB_LAZY:
flags |= O_RDWR;
break;
case MDBX_OPEN_COPY:
flags = O_CREAT | O_WRONLY | O_EXCL;
break;
case MDBX_OPEN_DXB_DSYNC:
flags |= O_WRONLY;
#if defined(O_DSYNC)
flags |= O_DSYNC;
#elif defined(O_SYNC)
flags |= O_SYNC;
#elif defined(O_FSYNC)
flags |= O_FSYNC;
#endif
break;
case MDBX_OPEN_DELETE:
flags = O_RDWR;
break;
}
const bool direct_nocache_for_copy =
env->me_psize >= env->me_os_psize && purpose == MDBX_OPEN_COPY;
if (direct_nocache_for_copy) {
#if defined(O_DIRECT)
flags |= O_DIRECT;
#endif /* O_DIRECT */
#if defined(O_NOCACHE)
flags |= O_NOCACHE;
#endif /* O_NOCACHE */
}
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif /* O_CLOEXEC */
/* Safeguard for https://libmdbx.dqdkfa.ru/dead-github/issues/144 */
#if STDIN_FILENO == 0 && STDOUT_FILENO == 1 && STDERR_FILENO == 2
int stub_fd0 = -1, stub_fd1 = -1, stub_fd2 = -1;
static const char dev_null[] = "/dev/null";
if (!is_valid_fd(STDIN_FILENO)) {
WARNING("STD%s_FILENO/%d is invalid, open %s for temporary stub", "IN",
STDIN_FILENO, dev_null);
stub_fd0 = open(dev_null, O_RDONLY | O_NOCTTY);
}
if (!is_valid_fd(STDOUT_FILENO)) {
WARNING("STD%s_FILENO/%d is invalid, open %s for temporary stub", "OUT",
STDOUT_FILENO, dev_null);
stub_fd1 = open(dev_null, O_WRONLY | O_NOCTTY);
}
if (!is_valid_fd(STDERR_FILENO)) {
WARNING("STD%s_FILENO/%d is invalid, open %s for temporary stub", "ERR",
STDERR_FILENO, dev_null);
stub_fd2 = open(dev_null, O_WRONLY | O_NOCTTY);
}
#else
#error "Unexpected or unsupported UNIX or POSIX system"
#endif /* STDIN_FILENO == 0 && STDERR_FILENO == 2 */
*fd = open(pathname, flags, unix_mode_bits);
#if defined(O_DIRECT)
if (*fd < 0 && (flags & O_DIRECT) &&
(errno == EINVAL || errno == EAFNOSUPPORT)) {
flags &= ~(O_DIRECT | O_EXCL);
*fd = open(pathname, flags, unix_mode_bits);
}
#endif /* O_DIRECT */
if (*fd < 0 && errno == EACCES && purpose == MDBX_OPEN_LCK) {
struct stat unused;
if (stat(pathname, &unused) == 0 || errno != ENOENT)
errno = EACCES /* restore errno if file exists */;
}
/* Safeguard for https://libmdbx.dqdkfa.ru/dead-github/issues/144 */
#if STDIN_FILENO == 0 && STDOUT_FILENO == 1 && STDERR_FILENO == 2
if (*fd == STDIN_FILENO) {
WARNING("Got STD%s_FILENO/%d, avoid using it by dup(fd)", "IN",
STDIN_FILENO);
assert(stub_fd0 == -1);
*fd = dup(stub_fd0 = *fd);
}
if (*fd == STDOUT_FILENO) {
WARNING("Got STD%s_FILENO/%d, avoid using it by dup(fd)", "OUT",
STDOUT_FILENO);
assert(stub_fd1 == -1);
*fd = dup(stub_fd1 = *fd);
}
if (*fd == STDERR_FILENO) {
WARNING("Got STD%s_FILENO/%d, avoid using it by dup(fd)", "ERR",
STDERR_FILENO);
assert(stub_fd2 == -1);
*fd = dup(stub_fd2 = *fd);
}
if (stub_fd0 != -1)
close(stub_fd0);
if (stub_fd1 != -1)
close(stub_fd1);
if (stub_fd2 != -1)
close(stub_fd2);
if (*fd >= STDIN_FILENO && *fd <= STDERR_FILENO) {
ERROR("Rejecting the use of a FD in the range "
"STDIN_FILENO/%d..STDERR_FILENO/%d to prevent database corruption",
STDIN_FILENO, STDERR_FILENO);
close(*fd);
return EBADF;
}
#else
#error "Unexpected or unsupported UNIX or POSIX system"
#endif /* STDIN_FILENO == 0 && STDERR_FILENO == 2 */
if (*fd < 0)
return errno;
#if defined(FD_CLOEXEC) && !defined(O_CLOEXEC)
const int fd_flags = fcntl(*fd, F_GETFD);
if (fd_flags != -1)
(void)fcntl(*fd, F_SETFD, fd_flags | FD_CLOEXEC);
#endif /* FD_CLOEXEC && !O_CLOEXEC */
if (direct_nocache_for_copy) {
#if defined(F_NOCACHE) && !defined(O_NOCACHE)
(void)fcntl(*fd, F_NOCACHE, 1);
#endif /* F_NOCACHE */
}
#endif
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC int osal_closefile(mdbx_filehandle_t fd) {
#if defined(_WIN32) || defined(_WIN64)
return CloseHandle(fd) ? MDBX_SUCCESS : (int)GetLastError();
#else
assert(fd > STDERR_FILENO);
return (close(fd) == 0) ? MDBX_SUCCESS : errno;
#endif
}
MDBX_INTERNAL_FUNC int osal_pread(mdbx_filehandle_t fd, void *buf, size_t bytes,
uint64_t offset) {
if (bytes > MAX_WRITE)
return MDBX_EINVAL;
#if defined(_WIN32) || defined(_WIN64)
OVERLAPPED ov;
ov.hEvent = 0;
ov.Offset = (DWORD)offset;
ov.OffsetHigh = HIGH_DWORD(offset);
DWORD read = 0;
if (unlikely(!ReadFile(fd, buf, (DWORD)bytes, &read, &ov))) {
int rc = (int)GetLastError();
return (rc == MDBX_SUCCESS) ? /* paranoia */ ERROR_READ_FAULT : rc;
}
#else
STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
intptr_t read = pread(fd, buf, bytes, offset);
if (read < 0) {
int rc = errno;
return (rc == MDBX_SUCCESS) ? /* paranoia */ MDBX_EIO : rc;
}
#endif
return (bytes == (size_t)read) ? MDBX_SUCCESS : MDBX_ENODATA;
}
MDBX_INTERNAL_FUNC int osal_pwrite(mdbx_filehandle_t fd, const void *buf,
size_t bytes, uint64_t offset) {
while (true) {
#if defined(_WIN32) || defined(_WIN64)
OVERLAPPED ov;
ov.hEvent = 0;
ov.Offset = (DWORD)offset;
ov.OffsetHigh = HIGH_DWORD(offset);
DWORD written;
if (unlikely(!WriteFile(
fd, buf, likely(bytes <= MAX_WRITE) ? (DWORD)bytes : MAX_WRITE,
&written, &ov)))
return (int)GetLastError();
if (likely(bytes == written))
return MDBX_SUCCESS;
#else
STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
const intptr_t written =
pwrite(fd, buf, likely(bytes <= MAX_WRITE) ? bytes : MAX_WRITE, offset);
if (likely(bytes == (size_t)written))
return MDBX_SUCCESS;
if (written < 0) {
const int rc = errno;
if (rc != EINTR)
return rc;
continue;
}
#endif
bytes -= written;
offset += written;
buf = ptr_disp(buf, written);
}
}
MDBX_INTERNAL_FUNC int osal_write(mdbx_filehandle_t fd, const void *buf,
size_t bytes) {
while (true) {
#if defined(_WIN32) || defined(_WIN64)
DWORD written;
if (unlikely(!WriteFile(
fd, buf, likely(bytes <= MAX_WRITE) ? (DWORD)bytes : MAX_WRITE,
&written, nullptr)))
return (int)GetLastError();
if (likely(bytes == written))
return MDBX_SUCCESS;
#else
STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
const intptr_t written =
write(fd, buf, likely(bytes <= MAX_WRITE) ? bytes : MAX_WRITE);
if (likely(bytes == (size_t)written))
return MDBX_SUCCESS;
if (written < 0) {
const int rc = errno;
if (rc != EINTR)
return rc;
continue;
}
#endif
bytes -= written;
buf = ptr_disp(buf, written);
}
}
int osal_pwritev(mdbx_filehandle_t fd, struct iovec *iov, size_t sgvcnt,
uint64_t offset) {
size_t expected = 0;
for (size_t i = 0; i < sgvcnt; ++i)
expected += iov[i].iov_len;
#if !MDBX_HAVE_PWRITEV
size_t written = 0;
for (size_t i = 0; i < sgvcnt; ++i) {
int rc = osal_pwrite(fd, iov[i].iov_base, iov[i].iov_len, offset);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
written += iov[i].iov_len;
offset += iov[i].iov_len;
}
return (expected == written) ? MDBX_SUCCESS
: MDBX_EIO /* ERROR_WRITE_FAULT */;
#else
int rc;
intptr_t written;
do {
STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
written = pwritev(fd, iov, sgvcnt, offset);
if (likely(expected == (size_t)written))
return MDBX_SUCCESS;
rc = errno;
} while (rc == EINTR);
return (written < 0) ? rc : MDBX_EIO /* Use which error code? */;
#endif
}
MDBX_INTERNAL_FUNC int osal_fsync(mdbx_filehandle_t fd,
enum osal_syncmode_bits mode_bits) {
#if defined(_WIN32) || defined(_WIN64)
if ((mode_bits & (MDBX_SYNC_DATA | MDBX_SYNC_IODQ)) && !FlushFileBuffers(fd))
return (int)GetLastError();
return MDBX_SUCCESS;
#else
#if defined(__APPLE__) && \
MDBX_OSX_SPEED_INSTEADOF_DURABILITY == MDBX_OSX_WANNA_DURABILITY
if (mode_bits & MDBX_SYNC_IODQ)
return likely(fcntl(fd, F_FULLFSYNC) != -1) ? MDBX_SUCCESS : errno;
#endif /* MacOS */
/* LY: This approach is always safe and without appreciable performance
* degradation, even on a kernel with fdatasync's bug.
*
* For more info about of a corresponding fdatasync() bug
* see http://www.spinics.net/lists/linux-ext4/msg33714.html */
while (1) {
switch (mode_bits & (MDBX_SYNC_DATA | MDBX_SYNC_SIZE)) {
case MDBX_SYNC_NONE:
case MDBX_SYNC_KICK:
return MDBX_SUCCESS /* nothing to do */;
#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
case MDBX_SYNC_DATA:
if (likely(fdatasync(fd) == 0))
return MDBX_SUCCESS;
break /* error */;
#if defined(__linux__) || defined(__gnu_linux__)
case MDBX_SYNC_SIZE:
assert(linux_kernel_version >= 0x03060000);
return MDBX_SUCCESS;
#endif /* Linux */
#endif /* _POSIX_SYNCHRONIZED_IO > 0 */
default:
if (likely(fsync(fd) == 0))
return MDBX_SUCCESS;
}
int rc = errno;
if (rc != EINTR)
return rc;
}
#endif
}
int osal_filesize(mdbx_filehandle_t fd, uint64_t *length) {
#if defined(_WIN32) || defined(_WIN64)
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(fd, &info))
return (int)GetLastError();
*length = info.nFileSizeLow | (uint64_t)info.nFileSizeHigh << 32;
#else
struct stat st;
STATIC_ASSERT_MSG(sizeof(off_t) <= sizeof(uint64_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
if (fstat(fd, &st))
return errno;
*length = st.st_size;
#endif
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC int osal_is_pipe(mdbx_filehandle_t fd) {
#if defined(_WIN32) || defined(_WIN64)
switch (GetFileType(fd)) {
case FILE_TYPE_DISK:
return MDBX_RESULT_FALSE;
case FILE_TYPE_CHAR:
case FILE_TYPE_PIPE:
return MDBX_RESULT_TRUE;
default:
return (int)GetLastError();
}
#else
struct stat info;
if (fstat(fd, &info))
return errno;
switch (info.st_mode & S_IFMT) {
case S_IFBLK:
case S_IFREG:
return MDBX_RESULT_FALSE;
case S_IFCHR:
case S_IFIFO:
case S_IFSOCK:
return MDBX_RESULT_TRUE;
case S_IFDIR:
case S_IFLNK:
default:
return MDBX_INCOMPATIBLE;
}
#endif
}
MDBX_INTERNAL_FUNC int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length) {
#if defined(_WIN32) || defined(_WIN64)
if (mdbx_SetFileInformationByHandle) {
FILE_END_OF_FILE_INFO EndOfFileInfo;
EndOfFileInfo.EndOfFile.QuadPart = length;
return mdbx_SetFileInformationByHandle(fd, FileEndOfFileInfo,
&EndOfFileInfo,
sizeof(FILE_END_OF_FILE_INFO))
? MDBX_SUCCESS
: (int)GetLastError();
} else {
LARGE_INTEGER li;
li.QuadPart = length;
return (SetFilePointerEx(fd, li, NULL, FILE_BEGIN) && SetEndOfFile(fd))
? MDBX_SUCCESS
: (int)GetLastError();
}
#else
STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
return ftruncate(fd, length) == 0 ? MDBX_SUCCESS : errno;
#endif
}
MDBX_INTERNAL_FUNC int osal_fseek(mdbx_filehandle_t fd, uint64_t pos) {
#if defined(_WIN32) || defined(_WIN64)
LARGE_INTEGER li;
li.QuadPart = pos;
return SetFilePointerEx(fd, li, NULL, FILE_BEGIN) ? MDBX_SUCCESS
: (int)GetLastError();
#else
STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t),
"libmdbx requires 64-bit file I/O on 64-bit systems");
return (lseek(fd, pos, SEEK_SET) < 0) ? errno : MDBX_SUCCESS;
#endif
}
/*----------------------------------------------------------------------------*/
MDBX_INTERNAL_FUNC int
osal_thread_create(osal_thread_t *thread,
THREAD_RESULT(THREAD_CALL *start_routine)(void *),
void *arg) {
#if defined(_WIN32) || defined(_WIN64)
*thread = CreateThread(NULL, 0, start_routine, arg, 0, NULL);
return *thread ? MDBX_SUCCESS : (int)GetLastError();
#else
return pthread_create(thread, NULL, start_routine, arg);
#endif
}
MDBX_INTERNAL_FUNC int osal_thread_join(osal_thread_t thread) {
#if defined(_WIN32) || defined(_WIN64)
DWORD code = WaitForSingleObject(thread, INFINITE);
return waitstatus2errcode(code);
#else
void *unused_retval = &unused_retval;
return pthread_join(thread, &unused_retval);
#endif
}
/*----------------------------------------------------------------------------*/
MDBX_INTERNAL_FUNC int osal_msync(const osal_mmap_t *map, size_t offset,
size_t length,
enum osal_syncmode_bits mode_bits) {
if (!MDBX_MMAP_USE_MS_ASYNC && mode_bits == MDBX_SYNC_NONE)
return MDBX_SUCCESS;
void *ptr = ptr_disp(map->base, offset);
#if defined(_WIN32) || defined(_WIN64)
if (!FlushViewOfFile(ptr, length))
return (int)GetLastError();
if ((mode_bits & (MDBX_SYNC_DATA | MDBX_SYNC_IODQ)) &&
!FlushFileBuffers(map->fd))
return (int)GetLastError();
#else
#if defined(__linux__) || defined(__gnu_linux__)
/* Since Linux 2.6.19, MS_ASYNC is in fact a no-op. The kernel properly
* tracks dirty pages and flushes ones as necessary. */
//
// However, this behavior may be changed in custom kernels,
// so just leave such optimization to the libc discretion.
// NOTE: The MDBX_MMAP_USE_MS_ASYNC must be defined to 1 for such cases.
//
// assert(linux_kernel_version > 0x02061300);
// if (mode_bits <= MDBX_SYNC_KICK)
// return MDBX_SUCCESS;
#endif /* Linux */
if (msync(ptr, length, (mode_bits & MDBX_SYNC_DATA) ? MS_SYNC : MS_ASYNC))
return errno;
if ((mode_bits & MDBX_SYNC_SIZE) && fsync(map->fd))
return errno;
#endif
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC int osal_check_fs_rdonly(mdbx_filehandle_t handle,
const pathchar_t *pathname,
int err) {
#if defined(_WIN32) || defined(_WIN64)
(void)pathname;
(void)err;
if (!mdbx_GetVolumeInformationByHandleW)
return MDBX_ENOSYS;
DWORD unused, flags;
if (!mdbx_GetVolumeInformationByHandleW(handle, nullptr, 0, nullptr, &unused,
&flags, nullptr, 0))
return (int)GetLastError();
if ((flags & FILE_READ_ONLY_VOLUME) == 0)
return MDBX_EACCESS;
#else
struct statvfs info;
if (err != MDBX_ENOFILE) {
if (statvfs(pathname, &info) == 0 && (info.f_flag & ST_RDONLY) == 0)
return err;
if (errno != MDBX_ENOFILE)
return errno;
}
if (fstatvfs(handle, &info))
return errno;
if ((info.f_flag & ST_RDONLY) == 0)
return (err == MDBX_ENOFILE) ? MDBX_EACCESS : err;
#endif /* !Windows */
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC int osal_check_fs_incore(mdbx_filehandle_t handle) {
#if defined(_WIN32) || defined(_WIN64)
(void)handle;
#else
struct statfs statfs_info;
if (fstatfs(handle, &statfs_info))
return errno;
#if defined(__OpenBSD__)
const unsigned type = 0;
#else
const unsigned type = statfs_info.f_type;
#endif
switch (type) {
case 0x28cd3d45 /* CRAMFS_MAGIC */:
case 0x858458f6 /* RAMFS_MAGIC */:
case 0x01021994 /* TMPFS_MAGIC */:
case 0x73717368 /* SQUASHFS_MAGIC */:
case 0x7275 /* ROMFS_MAGIC */:
return MDBX_RESULT_TRUE;
}
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
defined(__BSD__) || defined(__bsdi__) || defined(__DragonFly__) || \
defined(__APPLE__) || defined(__MACH__) || defined(MFSNAMELEN) || \
defined(MFSTYPENAMELEN) || defined(VFS_NAMELEN)
const char *const name = statfs_info.f_fstypename;
const size_t name_len = sizeof(statfs_info.f_fstypename);
#else
const char *const name = "";
const size_t name_len = 0;
#endif
if (name_len) {
if (strncasecmp("tmpfs", name, 6) == 0 ||
strncasecmp("mfs", name, 4) == 0 ||
strncasecmp("ramfs", name, 6) == 0 ||
strncasecmp("romfs", name, 6) == 0)
return MDBX_RESULT_TRUE;
}
#endif /* !Windows */
return MDBX_RESULT_FALSE;
}
static int osal_check_fs_local(mdbx_filehandle_t handle, int flags) {
#if defined(_WIN32) || defined(_WIN64)
if (mdbx_RunningUnderWine() && !(flags & MDBX_EXCLUSIVE))
return ERROR_NOT_CAPABLE /* workaround for Wine */;
if (GetFileType(handle) != FILE_TYPE_DISK)
return ERROR_FILE_OFFLINE;
if (mdbx_GetFileInformationByHandleEx) {
FILE_REMOTE_PROTOCOL_INFO RemoteProtocolInfo;
if (mdbx_GetFileInformationByHandleEx(handle, FileRemoteProtocolInfo,
&RemoteProtocolInfo,
sizeof(RemoteProtocolInfo))) {
if ((RemoteProtocolInfo.Flags & REMOTE_PROTOCOL_INFO_FLAG_OFFLINE) &&
!(flags & MDBX_RDONLY))
return ERROR_FILE_OFFLINE;
if (!(RemoteProtocolInfo.Flags & REMOTE_PROTOCOL_INFO_FLAG_LOOPBACK) &&
!(flags & MDBX_EXCLUSIVE))
return ERROR_REMOTE_STORAGE_MEDIA_ERROR;
}
}
if (mdbx_NtFsControlFile) {
NTSTATUS rc;
struct {
WOF_EXTERNAL_INFO wof_info;
union {
WIM_PROVIDER_EXTERNAL_INFO wim_info;
FILE_PROVIDER_EXTERNAL_INFO_V1 file_info;
};
size_t reserved_for_microsoft_madness[42];
} GetExternalBacking_OutputBuffer;
IO_STATUS_BLOCK StatusBlock;
rc = mdbx_NtFsControlFile(handle, NULL, NULL, NULL, &StatusBlock,
FSCTL_GET_EXTERNAL_BACKING, NULL, 0,
&GetExternalBacking_OutputBuffer,
sizeof(GetExternalBacking_OutputBuffer));
if (NT_SUCCESS(rc)) {
if (!(flags & MDBX_EXCLUSIVE))
return ERROR_REMOTE_STORAGE_MEDIA_ERROR;
} else if (rc != STATUS_OBJECT_NOT_EXTERNALLY_BACKED &&
rc != STATUS_INVALID_DEVICE_REQUEST &&
rc != STATUS_NOT_SUPPORTED)
return ntstatus2errcode(rc);
}
if (mdbx_GetVolumeInformationByHandleW && mdbx_GetFinalPathNameByHandleW) {
WCHAR *PathBuffer = osal_malloc(sizeof(WCHAR) * INT16_MAX);
if (!PathBuffer)
return MDBX_ENOMEM;
int rc = MDBX_SUCCESS;
DWORD VolumeSerialNumber, FileSystemFlags;
if (!mdbx_GetVolumeInformationByHandleW(handle, PathBuffer, INT16_MAX,
&VolumeSerialNumber, NULL,
&FileSystemFlags, NULL, 0)) {
rc = (int)GetLastError();
goto bailout;
}
if ((flags & MDBX_RDONLY) == 0) {
if (FileSystemFlags &
(FILE_SEQUENTIAL_WRITE_ONCE | FILE_READ_ONLY_VOLUME |
FILE_VOLUME_IS_COMPRESSED)) {
rc = ERROR_REMOTE_STORAGE_MEDIA_ERROR;
goto bailout;
}
}
if (mdbx_GetFinalPathNameByHandleW(handle, PathBuffer, INT16_MAX,
FILE_NAME_NORMALIZED | VOLUME_NAME_NT)) {
if (_wcsnicmp(PathBuffer, L"\\Device\\Mup\\", 12) == 0) {
if (!(flags & MDBX_EXCLUSIVE)) {
rc = ERROR_REMOTE_STORAGE_MEDIA_ERROR;
goto bailout;
}
}
}
if (F_ISSET(flags, MDBX_RDONLY | MDBX_EXCLUSIVE) &&
(FileSystemFlags & FILE_READ_ONLY_VOLUME)) {
/* without-LCK (exclusive readonly) mode for DB on a read-only volume */
goto bailout;
}
if (mdbx_GetFinalPathNameByHandleW(handle, PathBuffer, INT16_MAX,
FILE_NAME_NORMALIZED |
VOLUME_NAME_DOS)) {
UINT DriveType = GetDriveTypeW(PathBuffer);
if (DriveType == DRIVE_NO_ROOT_DIR &&
_wcsnicmp(PathBuffer, L"\\\\?\\", 4) == 0 &&
_wcsnicmp(PathBuffer + 5, L":\\", 2) == 0) {
PathBuffer[7] = 0;
DriveType = GetDriveTypeW(PathBuffer + 4);
}
switch (DriveType) {
case DRIVE_CDROM:
if (flags & MDBX_RDONLY)
break;
// fall through
case DRIVE_UNKNOWN:
case DRIVE_NO_ROOT_DIR:
case DRIVE_REMOTE:
default:
if (!(flags & MDBX_EXCLUSIVE))
rc = ERROR_REMOTE_STORAGE_MEDIA_ERROR;
// fall through
case DRIVE_REMOVABLE:
case DRIVE_FIXED:
case DRIVE_RAMDISK:
break;
}
}
bailout:
osal_free(PathBuffer);
return rc;
}
#else
struct statvfs statvfs_info;
if (fstatvfs(handle, &statvfs_info))
return errno;
#if defined(ST_LOCAL) || defined(ST_EXPORTED)
const unsigned long st_flags = statvfs_info.f_flag;
#endif /* ST_LOCAL || ST_EXPORTED */
#if defined(__NetBSD__)
const unsigned type = 0;
const char *const name = statvfs_info.f_fstypename;
const size_t name_len = VFS_NAMELEN;
#elif defined(_AIX) || defined(__OS400__)
const char *const name = statvfs_info.f_basetype;
const size_t name_len = sizeof(statvfs_info.f_basetype);
struct stat st;
if (fstat(handle, &st))
return errno;
const unsigned type = st.st_vfstype;
if ((st.st_flag & FS_REMOTE) != 0 && !(flags & MDBX_EXCLUSIVE))
return MDBX_EREMOTE;
#elif defined(FSTYPSZ) || defined(_FSTYPSZ)
const unsigned type = 0;
const char *const name = statvfs_info.f_basetype;
const size_t name_len = sizeof(statvfs_info.f_basetype);
#elif defined(__sun) || defined(__SVR4) || defined(__svr4__) || \
defined(ST_FSTYPSZ) || defined(_ST_FSTYPSZ)
const unsigned type = 0;
struct stat st;
if (fstat(handle, &st))
return errno;
const char *const name = st.st_fstype;
const size_t name_len = strlen(name);
#else
struct statfs statfs_info;
if (fstatfs(handle, &statfs_info))
return errno;
#if defined(__OpenBSD__)
const unsigned type = 0;
#else
const unsigned type = statfs_info.f_type;
#endif
#if defined(MNT_LOCAL) || defined(MNT_EXPORTED)
const unsigned long mnt_flags = statfs_info.f_flags;
#endif /* MNT_LOCAL || MNT_EXPORTED */
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
defined(__BSD__) || defined(__bsdi__) || defined(__DragonFly__) || \
defined(__APPLE__) || defined(__MACH__) || defined(MFSNAMELEN) || \
defined(MFSTYPENAMELEN) || defined(VFS_NAMELEN)
const char *const name = statfs_info.f_fstypename;
const size_t name_len = sizeof(statfs_info.f_fstypename);
#elif defined(__ANDROID_API__) && __ANDROID_API__ < 21
const char *const name = "";
const unsigned name_len = 0;
#else
const char *name = "";
unsigned name_len = 0;
struct stat st;
if (fstat(handle, &st))
return errno;
char pathbuf[PATH_MAX];
FILE *mounted = nullptr;
#if defined(__linux__) || defined(__gnu_linux__)
mounted = setmntent("/proc/mounts", "r");
#endif /* Linux */
if (!mounted)
mounted = setmntent("/etc/mtab", "r");
if (mounted) {
const struct mntent *ent;
#if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(__BIONIC__) || \
(defined(_DEFAULT_SOURCE) && __GLIBC_PREREQ(2, 19))
struct mntent entbuf;
const bool should_copy = false;
while (nullptr !=
(ent = getmntent_r(mounted, &entbuf, pathbuf, sizeof(pathbuf))))
#else
const bool should_copy = true;
while (nullptr != (ent = getmntent(mounted)))
#endif
{
struct stat mnt;
if (!stat(ent->mnt_dir, &mnt) && mnt.st_dev == st.st_dev) {
if (should_copy) {
name =
strncpy(pathbuf, ent->mnt_fsname, name_len = sizeof(pathbuf) - 1);
pathbuf[name_len] = 0;
} else {
name = ent->mnt_fsname;
name_len = strlen(name);
}
break;
}
}
endmntent(mounted);
}
#endif /* !xBSD && !Android/Bionic */
#endif
if (name_len) {
if (((name_len > 2 && strncasecmp("nfs", name, 3) == 0) ||
strncasecmp("cifs", name, name_len) == 0 ||
strncasecmp("ncpfs", name, name_len) == 0 ||
strncasecmp("smbfs", name, name_len) == 0 ||
strcasecmp("9P" /* WSL2 */, name) == 0 ||
((name_len > 3 && strncasecmp("fuse", name, 4) == 0) &&
strncasecmp("fuseblk", name, name_len) != 0)) &&
!(flags & MDBX_EXCLUSIVE))
return MDBX_EREMOTE;
if (strcasecmp("ftp", name) == 0 || strcasecmp("http", name) == 0 ||
strcasecmp("sshfs", name) == 0)
return MDBX_EREMOTE;
}
#ifdef ST_LOCAL
if ((st_flags & ST_LOCAL) == 0 && !(flags & MDBX_EXCLUSIVE))
return MDBX_EREMOTE;
#elif defined(MNT_LOCAL)
if ((mnt_flags & MNT_LOCAL) == 0 && !(flags & MDBX_EXCLUSIVE))
return MDBX_EREMOTE;
#endif /* ST/MNT_LOCAL */
#ifdef ST_EXPORTED
if ((st_flags & ST_EXPORTED) != 0 && !(flags & MDBX_RDONLY))
return MDBX_EREMOTE;
#elif defined(MNT_EXPORTED)
if ((mnt_flags & MNT_EXPORTED) != 0 && !(flags & MDBX_RDONLY))
return MDBX_EREMOTE;
#endif /* ST/MNT_EXPORTED */
switch (type) {
case 0xFF534D42 /* CIFS_MAGIC_NUMBER */:
case 0x6969 /* NFS_SUPER_MAGIC */:
case 0x564c /* NCP_SUPER_MAGIC */:
case 0x517B /* SMB_SUPER_MAGIC */:
#if defined(__digital__) || defined(__osf__) || defined(__osf)
case 0x0E /* Tru64 NFS */:
#endif
#ifdef ST_FST_NFS
case ST_FST_NFS:
#endif
if ((flags & MDBX_EXCLUSIVE) == 0)
return MDBX_EREMOTE;
case 0:
default:
break;
}
#endif /* Unix */
return MDBX_SUCCESS;
}
static int check_mmap_limit(const size_t limit) {
const bool should_check =
#if defined(__SANITIZE_ADDRESS__)
true;
#else
RUNNING_ON_VALGRIND;
#endif /* __SANITIZE_ADDRESS__ */
if (should_check) {
intptr_t pagesize, total_ram_pages, avail_ram_pages;
int err =
mdbx_get_sysraminfo(&pagesize, &total_ram_pages, &avail_ram_pages);
if (unlikely(err != MDBX_SUCCESS))
return err;
const int log2page = log2n_powerof2(pagesize);
if ((limit >> (log2page + 7)) > (size_t)total_ram_pages ||
(limit >> (log2page + 6)) > (size_t)avail_ram_pages) {
ERROR("%s (%zu pages) is too large for available (%zu pages) or total "
"(%zu pages) system RAM",
"database upper size limit", limit >> log2page, avail_ram_pages,
total_ram_pages);
return MDBX_TOO_LARGE;
}
}
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC int osal_mmap(const int flags, osal_mmap_t *map, size_t size,
const size_t limit, const unsigned options) {
assert(size <= limit);
map->limit = 0;
map->current = 0;
map->base = nullptr;
map->filesize = 0;
#if defined(_WIN32) || defined(_WIN64)
map->section = NULL;
#endif /* Windows */
int err = osal_check_fs_local(map->fd, flags);
if (unlikely(err != MDBX_SUCCESS))
return err;
err = check_mmap_limit(limit);
if (unlikely(err != MDBX_SUCCESS))
return err;
if ((flags & MDBX_RDONLY) == 0 && (options & MMAP_OPTION_TRUNCATE) != 0) {
err = osal_ftruncate(map->fd, size);
VERBOSE("ftruncate %zu, err %d", size, err);
if (err != MDBX_SUCCESS)
return err;
map->filesize = size;
#if !(defined(_WIN32) || defined(_WIN64))
map->current = size;
#endif /* !Windows */
} else {
err = osal_filesize(map->fd, &map->filesize);
VERBOSE("filesize %" PRIu64 ", err %d", map->filesize, err);
if (err != MDBX_SUCCESS)
return err;
#if defined(_WIN32) || defined(_WIN64)
if (map->filesize < size) {
WARNING("file size (%zu) less than requested for mapping (%zu)",
(size_t)map->filesize, size);
size = (size_t)map->filesize;
}
#else
map->current = (map->filesize > limit) ? limit : (size_t)map->filesize;
#endif /* !Windows */
}
#if defined(_WIN32) || defined(_WIN64)
LARGE_INTEGER SectionSize;
SectionSize.QuadPart = size;
err = NtCreateSection(
&map->section,
/* DesiredAccess */
(flags & MDBX_WRITEMAP)
? SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE |
SECTION_MAP_WRITE
: SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE,
/* ObjectAttributes */ NULL, /* MaximumSize (InitialSize) */ &SectionSize,
/* SectionPageProtection */
(flags & MDBX_RDONLY) ? PAGE_READONLY : PAGE_READWRITE,
/* AllocationAttributes */ SEC_RESERVE, map->fd);
if (!NT_SUCCESS(err))
return ntstatus2errcode(err);
SIZE_T ViewSize = (flags & MDBX_RDONLY) ? 0
: mdbx_RunningUnderWine() ? size
: limit;
err = NtMapViewOfSection(
map->section, GetCurrentProcess(), &map->base,
/* ZeroBits */ 0,
/* CommitSize */ 0,
/* SectionOffset */ NULL, &ViewSize,
/* InheritDisposition */ ViewUnmap,
/* AllocationType */ (flags & MDBX_RDONLY) ? 0 : MEM_RESERVE,
/* Win32Protect */
(flags & MDBX_WRITEMAP) ? PAGE_READWRITE : PAGE_READONLY);
if (!NT_SUCCESS(err)) {
NtClose(map->section);
map->section = 0;
map->base = nullptr;
return ntstatus2errcode(err);
}
assert(map->base != MAP_FAILED);
map->current = (size_t)SectionSize.QuadPart;
map->limit = ViewSize;
#else /* Windows */
#ifndef MAP_TRYFIXED
#define MAP_TRYFIXED 0
#endif
#ifndef MAP_HASSEMAPHORE
#define MAP_HASSEMAPHORE 0
#endif
#ifndef MAP_CONCEAL
#define MAP_CONCEAL 0
#endif
#ifndef MAP_NOSYNC
#define MAP_NOSYNC 0
#endif
#ifndef MAP_FIXED_NOREPLACE
#define MAP_FIXED_NOREPLACE 0
#endif
#ifndef MAP_NORESERVE
#define MAP_NORESERVE 0
#endif
map->base = mmap(
NULL, limit, (flags & MDBX_WRITEMAP) ? PROT_READ | PROT_WRITE : PROT_READ,
MAP_SHARED | MAP_FILE | MAP_NORESERVE |
(F_ISSET(flags, MDBX_UTTERLY_NOSYNC) ? MAP_NOSYNC : 0) |
((options & MMAP_OPTION_SEMAPHORE) ? MAP_HASSEMAPHORE | MAP_NOSYNC
: MAP_CONCEAL),
map->fd, 0);
if (unlikely(map->base == MAP_FAILED)) {
map->limit = 0;
map->current = 0;
map->base = nullptr;
assert(errno != 0);
return errno;
}
map->limit = limit;
#if MDBX_ENABLE_MADVISE
#ifdef MADV_DONTFORK
if (unlikely(madvise(map->base, map->limit, MADV_DONTFORK) != 0))
return errno;
#endif /* MADV_DONTFORK */
#ifdef MADV_NOHUGEPAGE
(void)madvise(map->base, map->limit, MADV_NOHUGEPAGE);
#endif /* MADV_NOHUGEPAGE */
#endif /* MDBX_ENABLE_MADVISE */
#endif /* ! Windows */
VALGRIND_MAKE_MEM_DEFINED(map->base, map->current);
MDBX_ASAN_UNPOISON_MEMORY_REGION(map->base, map->current);
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC int osal_munmap(osal_mmap_t *map) {
VALGRIND_MAKE_MEM_NOACCESS(map->base, map->current);
/* Unpoisoning is required for ASAN to avoid false-positive diagnostic
* when this memory will re-used by malloc or another mmapping.
* See https://libmdbx.dqdkfa.ru/dead-github/pull/93#issuecomment-613687203 */
MDBX_ASAN_UNPOISON_MEMORY_REGION(
map->base, (map->filesize && map->filesize < map->limit) ? map->filesize
: map->limit);
#if defined(_WIN32) || defined(_WIN64)
if (map->section)
NtClose(map->section);
NTSTATUS rc = NtUnmapViewOfSection(GetCurrentProcess(), map->base);
if (!NT_SUCCESS(rc))
ntstatus2errcode(rc);
#else
if (unlikely(munmap(map->base, map->limit))) {
assert(errno != 0);
return errno;
}
#endif /* ! Windows */
map->limit = 0;
map->current = 0;
map->base = nullptr;
return MDBX_SUCCESS;
}
MDBX_INTERNAL_FUNC int osal_mresize(const int flags, osal_mmap_t *map,
size_t size, size_t limit) {
int rc = osal_filesize(map->fd, &map->filesize);
VERBOSE("flags 0x%x, size %zu, limit %zu, filesize %" PRIu64, flags, size,
limit, map->filesize);
assert(size <= limit);
if (rc != MDBX_SUCCESS) {
map->filesize = 0;
return rc;
}
#if defined(_WIN32) || defined(_WIN64)
assert(size != map->current || limit != map->limit || size < map->filesize);
NTSTATUS status;
LARGE_INTEGER SectionSize;
int err;
if (limit == map->limit && size > map->current) {
if ((flags & MDBX_RDONLY) && map->filesize >= size) {
map->current = size;
return MDBX_SUCCESS;
} else if (!(flags & MDBX_RDONLY) &&
/* workaround for Wine */ mdbx_NtExtendSection) {
/* growth rw-section */
SectionSize.QuadPart = size;
status = mdbx_NtExtendSection(map->section, &SectionSize);
if (!NT_SUCCESS(status))
return ntstatus2errcode(status);
map->current = size;
if (map->filesize < size)
map->filesize = size;
return MDBX_SUCCESS;
}
}
if (limit > map->limit) {
err = check_mmap_limit(limit);
if (unlikely(err != MDBX_SUCCESS))
return err;
/* check ability of address space for growth before unmap */
PVOID BaseAddress = (PBYTE)map->base + map->limit;
SIZE_T RegionSize = limit - map->limit;
status = NtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress, 0,
&RegionSize, MEM_RESERVE, PAGE_NOACCESS);
if (status == (NTSTATUS) /* STATUS_CONFLICTING_ADDRESSES */ 0xC0000018)
return MDBX_UNABLE_EXTEND_MAPSIZE;
if (!NT_SUCCESS(status))
return ntstatus2errcode(status);
status = NtFreeVirtualMemory(GetCurrentProcess(), &BaseAddress, &RegionSize,
MEM_RELEASE);
if (!NT_SUCCESS(status))
return ntstatus2errcode(status);
}
/* Windows unable:
* - shrink a mapped file;
* - change size of mapped view;
* - extend read-only mapping;
* Therefore we should unmap/map entire section. */
if ((flags & MDBX_MRESIZE_MAY_UNMAP) == 0) {
if (size <= map->current && limit == map->limit)
return MDBX_SUCCESS;
return MDBX_EPERM;
}
/* Unpoisoning is required for ASAN to avoid false-positive diagnostic
* when this memory will re-used by malloc or another mmapping.
* See https://libmdbx.dqdkfa.ru/dead-github/pull/93#issuecomment-613687203 */
MDBX_ASAN_UNPOISON_MEMORY_REGION(map->base, map->limit);
status = NtUnmapViewOfSection(GetCurrentProcess(), map->base);
if (!NT_SUCCESS(status))
return ntstatus2errcode(status);
status = NtClose(map->section);
map->section = NULL;
PVOID ReservedAddress = NULL;
SIZE_T ReservedSize = limit;
if (!NT_SUCCESS(status)) {
bailout_ntstatus:
err = ntstatus2errcode(status);
map->base = NULL;
map->current = map->limit = 0;
if (ReservedAddress) {
ReservedSize = 0;
status = NtFreeVirtualMemory(GetCurrentProcess(), &ReservedAddress,
&ReservedSize, MEM_RELEASE);
assert(NT_SUCCESS(status));
(void)status;
}
return err;
}
retry_file_and_section:
/* resizing of the file may take a while,
* therefore we reserve address space to avoid occupy it by other threads */
ReservedAddress = map->base;
status = NtAllocateVirtualMemory(GetCurrentProcess(), &ReservedAddress, 0,
&ReservedSize, MEM_RESERVE, PAGE_NOACCESS);
if (!NT_SUCCESS(status)) {
ReservedAddress = NULL;
if (status != (NTSTATUS) /* STATUS_CONFLICTING_ADDRESSES */ 0xC0000018)
goto bailout_ntstatus /* no way to recovery */;
if (flags & MDBX_MRESIZE_MAY_MOVE)
/* the base address could be changed */
map->base = NULL;
}
if ((flags & MDBX_RDONLY) == 0 && map->filesize != size) {
err = osal_ftruncate(map->fd, size);
if (err == MDBX_SUCCESS)
map->filesize = size;
/* ignore error, because Windows unable shrink file
* that already mapped (by another process) */
}
SectionSize.QuadPart = size;
status = NtCreateSection(
&map->section,
/* DesiredAccess */
(flags & MDBX_WRITEMAP)
? SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE |
SECTION_MAP_WRITE
: SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE,
/* ObjectAttributes */ NULL,
/* MaximumSize (InitialSize) */ &SectionSize,
/* SectionPageProtection */
(flags & MDBX_RDONLY) ? PAGE_READONLY : PAGE_READWRITE,
/* AllocationAttributes */ SEC_RESERVE, map->fd);
if (!NT_SUCCESS(status))
goto bailout_ntstatus;
if (ReservedAddress) {
/* release reserved address space */
ReservedSize = 0;
status = NtFreeVirtualMemory(GetCurrentProcess(), &ReservedAddress,
&ReservedSize, MEM_RELEASE);
ReservedAddress = NULL;
if (!NT_SUCCESS(status))
goto bailout_ntstatus;
}
retry_mapview:;
SIZE_T ViewSize = (flags & MDBX_RDONLY) ? size : limit;
status = NtMapViewOfSection(
map->section, GetCurrentProcess(), &map->base,
/* ZeroBits */ 0,
/* CommitSize */ 0,
/* SectionOffset */ NULL, &ViewSize,
/* InheritDisposition */ ViewUnmap,
/* AllocationType */ (flags & MDBX_RDONLY) ? 0 : MEM_RESERVE,
/* Win32Protect */
(flags & MDBX_WRITEMAP) ? PAGE_READWRITE : PAGE_READONLY);
if (!NT_SUCCESS(status)) {
if (status == (NTSTATUS) /* STATUS_CONFLICTING_ADDRESSES */ 0xC0000018 &&
map->base && (flags & MDBX_MRESIZE_MAY_MOVE) != 0) {
/* try remap at another base address */
map->base = NULL;
goto retry_mapview;
}
NtClose(map->section);
map->section = NULL;
if (map->base && (size != map->current || limit != map->limit)) {
/* try remap with previously size and limit,
* but will return MDBX_UNABLE_EXTEND_MAPSIZE on success */
rc = (limit > map->limit) ? MDBX_UNABLE_EXTEND_MAPSIZE : MDBX_EPERM;
size = map->current;
ReservedSize = limit = map->limit;
goto retry_file_and_section;
}
/* no way to recovery */
goto bailout_ntstatus;
}
assert(map->base != MAP_FAILED);
map->current = (size_t)SectionSize.QuadPart;
map->limit = ViewSize;
#else /* Windows */
if (flags & MDBX_RDONLY) {
if (size > map->filesize)
rc = MDBX_UNABLE_EXTEND_MAPSIZE;
else if (size < map->filesize && map->filesize > limit)
rc = MDBX_EPERM;
map->current = (map->filesize > limit) ? limit : (size_t)map->filesize;
} else {
if (size > map->filesize ||
(size < map->filesize && (flags & MDBX_SHRINK_ALLOWED))) {
rc = osal_ftruncate(map->fd, size);
VERBOSE("ftruncate %zu, err %d", size, rc);
if (rc != MDBX_SUCCESS)
return rc;
map->filesize = size;
}
if (map->current > size) {
/* Clearing asan's bitmask for the region which released in shrinking,
* since:
* - after the shrinking we will get an exception when accessing
* this region and (therefore) do not need the help of ASAN.
* - this allows us to clear the mask only within the file size
* when closing the mapping. */
MDBX_ASAN_UNPOISON_MEMORY_REGION(
ptr_disp(map->base, size),
((map->current < map->limit) ? map->current : map->limit) - size);
}
map->current = (size < map->limit) ? size : map->limit;
}
if (limit == map->limit)
return rc;
if (limit < map->limit) {
/* unmap an excess at end of mapping. */
// coverity[offset_free : FALSE]
if (unlikely(munmap(ptr_disp(map->base, limit), map->limit - limit))) {
assert(errno != 0);
return errno;
}
map->limit = limit;
return rc;
}
int err = check_mmap_limit(limit);
if (unlikely(err != MDBX_SUCCESS))
return err;
assert(limit > map->limit);
void *ptr = MAP_FAILED;
#if (defined(__linux__) || defined(__gnu_linux__)) && defined(_GNU_SOURCE)
ptr = mremap(map->base, map->limit, limit,
#if defined(MREMAP_MAYMOVE)
(flags & MDBX_MRESIZE_MAY_MOVE) ? MREMAP_MAYMOVE :
#endif /* MREMAP_MAYMOVE */
0);
if (ptr == MAP_FAILED) {
err = errno;
assert(err != 0);
switch (err) {
default:
return err;
case 0 /* paranoia */:
case EAGAIN:
case ENOMEM:
return MDBX_UNABLE_EXTEND_MAPSIZE;
case EFAULT /* MADV_DODUMP / MADV_DONTDUMP are mixed for mmap-range */:
break;
}
}
#endif /* Linux & _GNU_SOURCE */
const unsigned mmap_flags =
MAP_CONCEAL | MAP_SHARED | MAP_FILE | MAP_NORESERVE |
(F_ISSET(flags, MDBX_UTTERLY_NOSYNC) ? MAP_NOSYNC : 0);
const unsigned mmap_prot =
(flags & MDBX_WRITEMAP) ? PROT_READ | PROT_WRITE : PROT_READ;
if (ptr == MAP_FAILED) {
/* Try to mmap additional space beyond the end of mapping. */
ptr = mmap(ptr_disp(map->base, map->limit), limit - map->limit, mmap_prot,
mmap_flags | MAP_FIXED_NOREPLACE, map->fd, map->limit);
if (ptr == ptr_disp(map->base, map->limit))
/* успешно прилепили отображение в конец */
ptr = map->base;
else if (ptr != MAP_FAILED) {
/* the desired address is busy, unmap unsuitable one */
if (unlikely(munmap(ptr, limit - map->limit))) {
assert(errno != 0);
return errno;
}
ptr = MAP_FAILED;
} else {
err = errno;
assert(err != 0);
switch (err) {
default:
return err;
case 0 /* paranoia */:
case EAGAIN:
case ENOMEM:
return MDBX_UNABLE_EXTEND_MAPSIZE;
case EEXIST: /* address busy */
case EINVAL: /* kernel don't support MAP_FIXED_NOREPLACE */
break;
}
}
}
if (ptr == MAP_FAILED) {
/* unmap and map again whole region */
if ((flags & MDBX_MRESIZE_MAY_UNMAP) == 0) {
/* TODO: Perhaps here it is worth to implement suspend/resume threads
* and perform unmap/map as like for Windows. */
return MDBX_UNABLE_EXTEND_MAPSIZE;
}
if (unlikely(munmap(map->base, map->limit))) {
assert(errno != 0);
return errno;
}
// coverity[pass_freed_arg : FALSE]
ptr = mmap(map->base, limit, mmap_prot,
(flags & MDBX_MRESIZE_MAY_MOVE)
? mmap_flags
: mmap_flags | (MAP_FIXED_NOREPLACE ? MAP_FIXED_NOREPLACE
: MAP_FIXED),
map->fd, 0);
if (MAP_FIXED_NOREPLACE != 0 && MAP_FIXED_NOREPLACE != MAP_FIXED &&
unlikely(ptr == MAP_FAILED) && !(flags & MDBX_MRESIZE_MAY_MOVE) &&
errno == /* kernel don't support MAP_FIXED_NOREPLACE */ EINVAL)
// coverity[pass_freed_arg : FALSE]
ptr =
mmap(map->base, limit, mmap_prot, mmap_flags | MAP_FIXED, map->fd, 0);
if (unlikely(ptr == MAP_FAILED)) {
/* try to restore prev mapping */
// coverity[pass_freed_arg : FALSE]
ptr = mmap(map->base, map->limit, mmap_prot,
(flags & MDBX_MRESIZE_MAY_MOVE)
? mmap_flags
: mmap_flags | (MAP_FIXED_NOREPLACE ? MAP_FIXED_NOREPLACE
: MAP_FIXED),
map->fd, 0);
if (MAP_FIXED_NOREPLACE != 0 && MAP_FIXED_NOREPLACE != MAP_FIXED &&
unlikely(ptr == MAP_FAILED) && !(flags & MDBX_MRESIZE_MAY_MOVE) &&
errno == /* kernel don't support MAP_FIXED_NOREPLACE */ EINVAL)
// coverity[pass_freed_arg : FALSE]
ptr = mmap(map->base, map->limit, mmap_prot, mmap_flags | MAP_FIXED,
map->fd, 0);
if (unlikely(ptr == MAP_FAILED)) {
VALGRIND_MAKE_MEM_NOACCESS(map->base, map->current);
/* Unpoisoning is required for ASAN to avoid false-positive diagnostic
* when this memory will re-used by malloc or another mmapping.
* See
* https://libmdbx.dqdkfa.ru/dead-github/pull/93#issuecomment-613687203
*/
MDBX_ASAN_UNPOISON_MEMORY_REGION(
map->base, (map->current < map->limit) ? map->current : map->limit);
map->limit = 0;
map->current = 0;
map->base = nullptr;
assert(errno != 0);
return errno;
}
rc = MDBX_UNABLE_EXTEND_MAPSIZE;
limit = map->limit;
}
}
assert(ptr && ptr != MAP_FAILED);
if (map->base != ptr) {
VALGRIND_MAKE_MEM_NOACCESS(map->base, map->current);
/* Unpoisoning is required for ASAN to avoid false-positive diagnostic
* when this memory will re-used by malloc or another mmapping.
* See
* https://libmdbx.dqdkfa.ru/dead-github/pull/93#issuecomment-613687203
*/
MDBX_ASAN_UNPOISON_MEMORY_REGION(
map->base, (map->current < map->limit) ? map->current : map->limit);
VALGRIND_MAKE_MEM_DEFINED(ptr, map->current);
MDBX_ASAN_UNPOISON_MEMORY_REGION(ptr, map->current);
map->base = ptr;
}
map->limit = limit;
map->current = size;
#if MDBX_ENABLE_MADVISE
#ifdef MADV_DONTFORK
if (unlikely(madvise(map->base, map->limit, MADV_DONTFORK) != 0)) {
assert(errno != 0);
return errno;
}
#endif /* MADV_DONTFORK */
#ifdef MADV_NOHUGEPAGE
(void)madvise(map->base, map->limit, MADV_NOHUGEPAGE);
#endif /* MADV_NOHUGEPAGE */
#endif /* MDBX_ENABLE_MADVISE */
#endif /* POSIX / Windows */
/* Zap: Redundant code */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6287);
assert(rc != MDBX_SUCCESS ||
(map->base != nullptr && map->base != MAP_FAILED &&
map->current == size && map->limit == limit &&
map->filesize >= size));
return rc;
}
/*----------------------------------------------------------------------------*/
__cold MDBX_INTERNAL_FUNC void osal_jitter(bool tiny) {
for (;;) {
#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \
defined(__x86_64__)
const unsigned salt = 277u * (unsigned)__rdtsc();
#elif (defined(_WIN32) || defined(_WIN64)) && MDBX_WITHOUT_MSVC_CRT
static ULONG state;
const unsigned salt = (unsigned)RtlRandomEx(&state);
#else
const unsigned salt = rand();
#endif
const unsigned coin = salt % (tiny ? 29u : 43u);
if (coin < 43 / 3)
break;
#if defined(_WIN32) || defined(_WIN64)
SwitchToThread();
if (coin > 43 * 2 / 3)
Sleep(1);
#else
sched_yield();
if (coin > 43 * 2 / 3)
usleep(coin);
#endif
}
}
/*----------------------------------------------------------------------------*/
#if defined(_WIN32) || defined(_WIN64)
static LARGE_INTEGER performance_frequency;
#elif defined(__APPLE__) || defined(__MACH__)
#include <mach/mach_time.h>
static uint64_t ratio_16dot16_to_monotine;
#elif defined(__linux__) || defined(__gnu_linux__)
static clockid_t posix_clockid;
__cold static clockid_t choice_monoclock(void) {
struct timespec probe;
#if defined(CLOCK_BOOTTIME)
if (clock_gettime(CLOCK_BOOTTIME, &probe) == 0)
return CLOCK_BOOTTIME;
#elif defined(CLOCK_MONOTONIC_RAW)
if (clock_gettime(CLOCK_MONOTONIC_RAW, &probe) == 0)
return CLOCK_MONOTONIC_RAW;
#elif defined(CLOCK_MONOTONIC_COARSE)
if (clock_gettime(CLOCK_MONOTONIC_COARSE, &probe) == 0)
return CLOCK_MONOTONIC_COARSE;
#endif
return CLOCK_MONOTONIC;
}
#elif defined(CLOCK_MONOTONIC)
#define posix_clockid CLOCK_MONOTONIC
#else
#define posix_clockid CLOCK_REALTIME
#endif
MDBX_INTERNAL_FUNC uint64_t osal_16dot16_to_monotime(uint32_t seconds_16dot16) {
#if defined(_WIN32) || defined(_WIN64)
const uint64_t ratio = performance_frequency.QuadPart;
#elif defined(__APPLE__) || defined(__MACH__)
const uint64_t ratio = ratio_16dot16_to_monotine;
#else
const uint64_t ratio = UINT64_C(1000000000);
#endif
const uint64_t ret = (ratio * seconds_16dot16 + 32768) >> 16;
return likely(ret || seconds_16dot16 == 0) ? ret : /* fix underflow */ 1;
}
static uint64_t monotime_limit;
MDBX_INTERNAL_FUNC uint32_t osal_monotime_to_16dot16(uint64_t monotime) {
if (unlikely(monotime > monotime_limit))
return UINT32_MAX;
const uint32_t ret =
#if defined(_WIN32) || defined(_WIN64)
(uint32_t)((monotime << 16) / performance_frequency.QuadPart);
#elif defined(__APPLE__) || defined(__MACH__)
(uint32_t)((monotime << 16) / ratio_16dot16_to_monotine);
#else
(uint32_t)((monotime << 7) / 1953125);
#endif
return ret;
}
MDBX_INTERNAL_FUNC uint64_t osal_monotime(void) {
#if defined(_WIN32) || defined(_WIN64)
LARGE_INTEGER counter;
if (QueryPerformanceCounter(&counter))
return counter.QuadPart;
#elif defined(__APPLE__) || defined(__MACH__)
return mach_absolute_time();
#else
struct timespec ts;
if (likely(clock_gettime(posix_clockid, &ts) == 0))
return ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec;
#endif
return 0;
}
MDBX_INTERNAL_FUNC uint64_t osal_cputime(size_t *optional_page_faults) {
#if defined(_WIN32) || defined(_WIN64)
if (optional_page_faults) {
PROCESS_MEMORY_COUNTERS pmc;
*optional_page_faults =
GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))
? pmc.PageFaultCount
: 0;
}
FILETIME unused, usermode;
if (GetThreadTimes(GetCurrentThread(),
/* CreationTime */ &unused,
/* ExitTime */ &unused,
/* KernelTime */ &unused,
/* UserTime */ &usermode)) {
/* one second = 10_000_000 * 100ns = 78125 * (1 << 7) * 100ns;
* result = (h * f / 10_000_000) << 32) + l * f / 10_000_000 =
* = ((h * f) >> 7) / 78125) << 32) + ((l * f) >> 7) / 78125;
* 1) {h, l} *= f;
* 2) {h, l} >>= 7;
* 3) result = ((h / 78125) << 32) + l / 78125; */
uint64_t l = usermode.dwLowDateTime * performance_frequency.QuadPart;
uint64_t h = usermode.dwHighDateTime * performance_frequency.QuadPart;
l = h << (64 - 7) | l >> 7;
h = h >> 7;
return ((h / 78125) << 32) + l / 78125;
}
#elif defined(RUSAGE_THREAD) || defined(RUSAGE_LWP)
#ifndef RUSAGE_THREAD
#define RUSAGE_THREAD RUSAGE_LWP /* Solaris */
#endif
struct rusage usage;
if (getrusage(RUSAGE_THREAD, &usage) == 0) {
if (optional_page_faults)
*optional_page_faults = usage.ru_majflt;
return usage.ru_utime.tv_sec * UINT64_C(1000000000) +
usage.ru_utime.tv_usec * 1000u;
}
if (optional_page_faults)
*optional_page_faults = 0;
#elif defined(CLOCK_THREAD_CPUTIME_ID)
if (optional_page_faults)
*optional_page_faults = 0;
struct timespec ts;
if (likely(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0))
return ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec;
#else
/* FIXME */
if (optional_page_faults)
*optional_page_faults = 0;
#endif
return 0;
}
/*----------------------------------------------------------------------------*/
static void bootid_shake(bin128_t *p) {
/* Bob Jenkins's PRNG: https://burtleburtle.net/bob/rand/smallprng.html */
const uint32_t e = p->a - (p->b << 23 | p->b >> 9);
p->a = p->b ^ (p->c << 16 | p->c >> 16);
p->b = p->c + (p->d << 11 | p->d >> 21);
p->c = p->d + e;
p->d = e + p->a;
}
__cold static void bootid_collect(bin128_t *p, const void *s, size_t n) {
p->y += UINT64_C(64526882297375213);
bootid_shake(p);
for (size_t i = 0; i < n; ++i) {
bootid_shake(p);
p->y ^= UINT64_C(48797879452804441) * ((const uint8_t *)s)[i];
bootid_shake(p);
p->y += 14621231;
}
bootid_shake(p);
/* minor non-linear tomfoolery */
const unsigned z = p->x % 61;
p->y = p->y << z | p->y >> (64 - z);
bootid_shake(p);
bootid_shake(p);
const unsigned q = p->x % 59;
p->y = p->y << q | p->y >> (64 - q);
bootid_shake(p);
bootid_shake(p);
bootid_shake(p);
}
#if defined(_WIN32) || defined(_WIN64)
__cold static uint64_t windows_systemtime_ms() {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
return ((uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime) / 10000ul;
}
__cold static uint64_t windows_bootime(void) {
unsigned confirmed = 0;
uint64_t boottime = 0;
uint64_t up0 = mdbx_GetTickCount64();
uint64_t st0 = windows_systemtime_ms();
for (uint64_t fuse = st0; up0 && st0 < fuse + 1000 * 1000u / 42;) {
YieldProcessor();
const uint64_t up1 = mdbx_GetTickCount64();
const uint64_t st1 = windows_systemtime_ms();
if (st1 > fuse && st1 == st0 && up1 == up0) {
uint64_t diff = st1 - up1;
if (boottime == diff) {
if (++confirmed > 4)
return boottime;
} else {
confirmed = 0;
boottime = diff;
}
fuse = st1;
Sleep(1);
}
st0 = st1;
up0 = up1;
}
return 0;
}
__cold static LSTATUS mdbx_RegGetValue(HKEY hKey, LPCSTR lpSubKey,
LPCSTR lpValue, PVOID pvData,
LPDWORD pcbData) {
LSTATUS rc;
if (!mdbx_RegGetValueA) {
/* an old Windows 2000/XP */
HKEY hSubKey;
rc = RegOpenKeyA(hKey, lpSubKey, &hSubKey);
if (rc == ERROR_SUCCESS) {
rc = RegQueryValueExA(hSubKey, lpValue, NULL, NULL, pvData, pcbData);
RegCloseKey(hSubKey);
}
return rc;
}
rc = mdbx_RegGetValueA(hKey, lpSubKey, lpValue, RRF_RT_ANY, NULL, pvData,
pcbData);
if (rc != ERROR_FILE_NOT_FOUND)
return rc;
rc = mdbx_RegGetValueA(hKey, lpSubKey, lpValue,
RRF_RT_ANY | 0x00010000 /* RRF_SUBKEY_WOW6464KEY */,
NULL, pvData, pcbData);
if (rc != ERROR_FILE_NOT_FOUND)
return rc;
return mdbx_RegGetValueA(hKey, lpSubKey, lpValue,
RRF_RT_ANY | 0x00020000 /* RRF_SUBKEY_WOW6432KEY */,
NULL, pvData, pcbData);
}
#endif
__cold MDBX_MAYBE_UNUSED static bool
bootid_parse_uuid(bin128_t *s, const void *p, const size_t n) {
if (n > 31) {
unsigned bits = 0;
for (unsigned i = 0; i < n; ++i) /* try parse an UUID in text form */ {
uint8_t c = ((const uint8_t *)p)[i];
if (c >= '0' && c <= '9')
c -= '0';
else if (c >= 'a' && c <= 'f')
c -= 'a' - 10;
else if (c >= 'A' && c <= 'F')
c -= 'A' - 10;
else
continue;
assert(c <= 15);
c ^= s->y >> 60;
s->y = s->y << 4 | s->x >> 60;
s->x = s->x << 4 | c;
bits += 4;
}
if (bits > 42 * 3)
/* UUID parsed successfully */
return true;
}
if (n > 15) /* is enough handle it as a binary? */ {
if (n == sizeof(bin128_t)) {
bin128_t aligned;
memcpy(&aligned, p, sizeof(bin128_t));
s->x += aligned.x;
s->y += aligned.y;
} else
bootid_collect(s, p, n);
return true;
}
if (n)
bootid_collect(s, p, n);
return false;
}
__cold MDBX_INTERNAL_FUNC bin128_t osal_bootid(void) {
bin128_t bin = {{0, 0}};
bool got_machineid = false, got_boottime = false, got_bootseq = false;
#if defined(__linux__) || defined(__gnu_linux__)
{
const int fd =
open("/proc/sys/kernel/random/boot_id", O_RDONLY | O_NOFOLLOW);
if (fd != -1) {
struct statfs fs;
char buf[42];
const ssize_t len =
(fstatfs(fd, &fs) == 0 && fs.f_type == /* procfs */ 0x9FA0)
? read(fd, buf, sizeof(buf))
: -1;
const int err = close(fd);
assert(err == 0);
(void)err;
if (len > 0 && bootid_parse_uuid(&bin, buf, len))
return bin;
}
}
#endif /* Linux */
#if defined(__APPLE__) || defined(__MACH__)
{
char buf[42];
size_t len = sizeof(buf);
if (!sysctlbyname("kern.bootsessionuuid", buf, &len, nullptr, 0) &&
bootid_parse_uuid(&bin, buf, len))
return bin;
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
__MAC_OS_X_VERSION_MIN_REQUIRED > 1050
uuid_t uuid;
struct timespec wait = {0, 1000000000u / 42};
if (!gethostuuid(uuid, &wait) &&
bootid_parse_uuid(&bin, uuid, sizeof(uuid)))
got_machineid = true;
#endif /* > 10.5 */
struct timeval boottime;
len = sizeof(boottime);
if (!sysctlbyname("kern.boottime", &boottime, &len, nullptr, 0) &&
len == sizeof(boottime) && boottime.tv_sec)
got_boottime = true;
}
#endif /* Apple/Darwin */
#if defined(_WIN32) || defined(_WIN64)
{
union buf {
DWORD BootId;
DWORD BaseTime;
SYSTEM_TIMEOFDAY_INFORMATION SysTimeOfDayInfo;
struct {
LARGE_INTEGER BootTime;
LARGE_INTEGER CurrentTime;
LARGE_INTEGER TimeZoneBias;
ULONG TimeZoneId;
ULONG Reserved;
ULONGLONG BootTimeBias;
ULONGLONG SleepTimeBias;
} SysTimeOfDayInfoHacked;
wchar_t MachineGuid[42];
char DigitalProductId[248];
} buf;
static const char HKLM_MicrosoftCryptography[] =
"SOFTWARE\\Microsoft\\Cryptography";
DWORD len = sizeof(buf);
/* Windows is madness and must die */
if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_MicrosoftCryptography,
"MachineGuid", &buf.MachineGuid,
&len) == ERROR_SUCCESS &&
len < sizeof(buf))
got_machineid = bootid_parse_uuid(&bin, &buf.MachineGuid, len);
if (!got_machineid) {
/* again, Windows is madness */
static const char HKLM_WindowsNT[] =
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
static const char HKLM_WindowsNT_DPK[] =
"SOFTWARE\\Microsoft\\Windows "
"NT\\CurrentVersion\\DefaultProductKey";
static const char HKLM_WindowsNT_DPK2[] =
"SOFTWARE\\Microsoft\\Windows "
"NT\\CurrentVersion\\DefaultProductKey2";
len = sizeof(buf);
if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_WindowsNT,
"DigitalProductId", &buf.DigitalProductId,
&len) == ERROR_SUCCESS &&
len > 42 && len < sizeof(buf)) {
bootid_collect(&bin, &buf.DigitalProductId, len);
got_machineid = true;
}
len = sizeof(buf);
if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_WindowsNT_DPK,
"DigitalProductId", &buf.DigitalProductId,
&len) == ERROR_SUCCESS &&
len > 42 && len < sizeof(buf)) {
bootid_collect(&bin, &buf.DigitalProductId, len);
got_machineid = true;
}
len = sizeof(buf);
if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_WindowsNT_DPK2,
"DigitalProductId", &buf.DigitalProductId,
&len) == ERROR_SUCCESS &&
len > 42 && len < sizeof(buf)) {
bootid_collect(&bin, &buf.DigitalProductId, len);
got_machineid = true;
}
}
static const char HKLM_PrefetcherParams[] =
"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory "
"Management\\PrefetchParameters";
len = sizeof(buf);
if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_PrefetcherParams, "BootId",
&buf.BootId, &len) == ERROR_SUCCESS &&
len > 1 && len < sizeof(buf)) {
bootid_collect(&bin, &buf.BootId, len);
got_bootseq = true;
}
len = sizeof(buf);
if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_PrefetcherParams, "BaseTime",
&buf.BaseTime, &len) == ERROR_SUCCESS &&
len >= sizeof(buf.BaseTime) && buf.BaseTime) {
bootid_collect(&bin, &buf.BaseTime, len);
got_boottime = true;
}
/* BootTime from SYSTEM_TIMEOFDAY_INFORMATION */
NTSTATUS status = NtQuerySystemInformation(
0x03 /* SystemTmeOfDayInformation */, &buf.SysTimeOfDayInfo,
sizeof(buf.SysTimeOfDayInfo), &len);
if (NT_SUCCESS(status) &&
len >= offsetof(union buf, SysTimeOfDayInfoHacked.BootTimeBias) +
sizeof(buf.SysTimeOfDayInfoHacked.BootTimeBias) &&
buf.SysTimeOfDayInfoHacked.BootTime.QuadPart) {
const uint64_t UnbiasedBootTime =
buf.SysTimeOfDayInfoHacked.BootTime.QuadPart -
buf.SysTimeOfDayInfoHacked.BootTimeBias;
if (UnbiasedBootTime) {
bootid_collect(&bin, &UnbiasedBootTime, sizeof(UnbiasedBootTime));
got_boottime = true;
}
}
if (!got_boottime) {
uint64_t boottime = windows_bootime();
if (boottime) {
bootid_collect(&bin, &boottime, sizeof(boottime));
got_boottime = true;
}
}
}
#endif /* Windows */
#if defined(CTL_HW) && defined(HW_UUID)
if (!got_machineid) {
static const int mib[] = {CTL_HW, HW_UUID};
char buf[42];
size_t len = sizeof(buf);
if (sysctl(
#ifdef SYSCTL_LEGACY_NONCONST_MIB
(int *)
#endif
mib,
ARRAY_LENGTH(mib), &buf, &len, NULL, 0) == 0)
got_machineid = bootid_parse_uuid(&bin, buf, len);
}
#endif /* CTL_HW && HW_UUID */
#if defined(CTL_KERN) && defined(KERN_HOSTUUID)
if (!got_machineid) {
static const int mib[] = {CTL_KERN, KERN_HOSTUUID};
char buf[42];
size_t len = sizeof(buf);
if (sysctl(
#ifdef SYSCTL_LEGACY_NONCONST_MIB
(int *)
#endif
mib,
ARRAY_LENGTH(mib), &buf, &len, NULL, 0) == 0)
got_machineid = bootid_parse_uuid(&bin, buf, len);
}
#endif /* CTL_KERN && KERN_HOSTUUID */
#if defined(__NetBSD__)
if (!got_machineid) {
char buf[42];
size_t len = sizeof(buf);
if (sysctlbyname("machdep.dmi.system-uuid", buf, &len, NULL, 0) == 0)
got_machineid = bootid_parse_uuid(&bin, buf, len);
}
#endif /* __NetBSD__ */
#if _XOPEN_SOURCE_EXTENDED
if (!got_machineid) {
const int hostid = gethostid();
if (hostid > 0) {
bootid_collect(&bin, &hostid, sizeof(hostid));
got_machineid = true;
}
}
#endif /* _XOPEN_SOURCE_EXTENDED */
if (!got_machineid) {
lack:
bin.x = bin.y = 0;
return bin;
}
/*--------------------------------------------------------------------------*/
#if defined(CTL_KERN) && defined(KERN_BOOTTIME)
if (!got_boottime) {
static const int mib[] = {CTL_KERN, KERN_BOOTTIME};
struct timeval boottime;
size_t len = sizeof(boottime);
if (sysctl(
#ifdef SYSCTL_LEGACY_NONCONST_MIB
(int *)
#endif
mib,
ARRAY_LENGTH(mib), &boottime, &len, NULL, 0) == 0 &&
len == sizeof(boottime) && boottime.tv_sec) {
bootid_collect(&bin, &boottime, len);
got_boottime = true;
}
}
#endif /* CTL_KERN && KERN_BOOTTIME */
#if defined(__sun) || defined(__SVR4) || defined(__svr4__)
if (!got_boottime) {
kstat_ctl_t *kc = kstat_open();
if (kc) {
kstat_t *kp = kstat_lookup(kc, "unix", 0, "system_misc");
if (kp && kstat_read(kc, kp, 0) != -1) {
kstat_named_t *kn = (kstat_named_t *)kstat_data_lookup(kp, "boot_time");
if (kn) {
switch (kn->data_type) {
case KSTAT_DATA_INT32:
case KSTAT_DATA_UINT32:
bootid_collect(&bin, &kn->value, sizeof(int32_t));
got_boottime = true;
case KSTAT_DATA_INT64:
case KSTAT_DATA_UINT64:
bootid_collect(&bin, &kn->value, sizeof(int64_t));
got_boottime = true;
}
}
}
kstat_close(kc);
}
}
#endif /* SunOS / Solaris */
#if _XOPEN_SOURCE_EXTENDED && defined(BOOT_TIME)
if (!got_boottime) {
setutxent();
const struct utmpx id = {.ut_type = BOOT_TIME};
const struct utmpx *entry = getutxid(&id);
if (entry) {
bootid_collect(&bin, entry, sizeof(*entry));
got_boottime = true;
while (unlikely((entry = getutxid(&id)) != nullptr)) {
/* have multiple reboot records, assuming we can distinguish next
* bootsession even if RTC is wrong or absent */
bootid_collect(&bin, entry, sizeof(*entry));
got_bootseq = true;
}
}
endutxent();
}
#endif /* _XOPEN_SOURCE_EXTENDED && BOOT_TIME */
if (!got_bootseq) {
if (!got_boottime || !MDBX_TRUST_RTC)
goto lack;
#if defined(_WIN32) || defined(_WIN64)
FILETIME now;
GetSystemTimeAsFileTime(&now);
if (0x1CCCCCC > now.dwHighDateTime)
#else
struct timespec mono, real;
if (clock_gettime(CLOCK_MONOTONIC, &mono) ||
clock_gettime(CLOCK_REALTIME, &real) ||
/* wrong time, RTC is mad or absent */
1555555555l > real.tv_sec ||
/* seems no adjustment by RTC/NTP, i.e. a fake time */
real.tv_sec < mono.tv_sec || 1234567890l > real.tv_sec - mono.tv_sec ||
(real.tv_sec - mono.tv_sec) % 900u == 0)
#endif
goto lack;
}
return bin;
}
__cold int mdbx_get_sysraminfo(intptr_t *page_size, intptr_t *total_pages,
intptr_t *avail_pages) {
if (!page_size && !total_pages && !avail_pages)
return MDBX_EINVAL;
if (total_pages)
*total_pages = -1;
if (avail_pages)
*avail_pages = -1;
const intptr_t pagesize = osal_syspagesize();
if (page_size)
*page_size = pagesize;
if (unlikely(pagesize < MIN_PAGESIZE || !is_powerof2(pagesize)))
return MDBX_INCOMPATIBLE;
MDBX_MAYBE_UNUSED const int log2page = log2n_powerof2(pagesize);
assert(pagesize == (INT64_C(1) << log2page));
(void)log2page;
#if defined(_WIN32) || defined(_WIN64)
MEMORYSTATUSEX info;
memset(&info, 0, sizeof(info));
info.dwLength = sizeof(info);
if (!GlobalMemoryStatusEx(&info))
return (int)GetLastError();
#endif
if (total_pages) {
#if defined(_WIN32) || defined(_WIN64)
const intptr_t total_ram_pages = (intptr_t)(info.ullTotalPhys >> log2page);
#elif defined(_SC_PHYS_PAGES)
const intptr_t total_ram_pages = sysconf(_SC_PHYS_PAGES);
if (total_ram_pages == -1)
return errno;
#elif defined(_SC_AIX_REALMEM)
const intptr_t total_ram_Kb = sysconf(_SC_AIX_REALMEM);
if (total_ram_Kb == -1)
return errno;
const intptr_t total_ram_pages = (total_ram_Kb << 10) >> log2page;
#elif defined(HW_USERMEM) || defined(HW_PHYSMEM64) || defined(HW_MEMSIZE) || \
defined(HW_PHYSMEM)
size_t ram, len = sizeof(ram);
static const int mib[] = {
CTL_HW,
#if defined(HW_USERMEM)
HW_USERMEM
#elif defined(HW_PHYSMEM64)
HW_PHYSMEM64
#elif defined(HW_MEMSIZE)
HW_MEMSIZE
#else
HW_PHYSMEM
#endif
};
if (sysctl(
#ifdef SYSCTL_LEGACY_NONCONST_MIB
(int *)
#endif
mib,
ARRAY_LENGTH(mib), &ram, &len, NULL, 0) != 0)
return errno;
if (len != sizeof(ram))
return MDBX_ENOSYS;
const intptr_t total_ram_pages = (intptr_t)(ram >> log2page);
#else
#error "FIXME: Get User-accessible or physical RAM"
#endif
*total_pages = total_ram_pages;
if (total_ram_pages < 1)
return MDBX_ENOSYS;
}
if (avail_pages) {
#if defined(_WIN32) || defined(_WIN64)
const intptr_t avail_ram_pages = (intptr_t)(info.ullAvailPhys >> log2page);
#elif defined(_SC_AVPHYS_PAGES)
const intptr_t avail_ram_pages = sysconf(_SC_AVPHYS_PAGES);
if (avail_ram_pages == -1)
return errno;
#elif defined(__MACH__)
mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
vm_statistics_data_t vmstat;
mach_port_t mport = mach_host_self();
kern_return_t kerr = host_statistics(mach_host_self(), HOST_VM_INFO,
(host_info_t)&vmstat, &count);
mach_port_deallocate(mach_task_self(), mport);
if (unlikely(kerr != KERN_SUCCESS))
return MDBX_ENOSYS;
const intptr_t avail_ram_pages = vmstat.free_count;
#elif defined(VM_TOTAL) || defined(VM_METER)
struct vmtotal info;
size_t len = sizeof(info);
static const int mib[] = {
CTL_VM,
#if defined(VM_TOTAL)
VM_TOTAL
#elif defined(VM_METER)
VM_METER
#endif
};
if (sysctl(
#ifdef SYSCTL_LEGACY_NONCONST_MIB
(int *)
#endif
mib,
ARRAY_LENGTH(mib), &info, &len, NULL, 0) != 0)
return errno;
if (len != sizeof(info))
return MDBX_ENOSYS;
const intptr_t avail_ram_pages = info.t_free;
#else
#error "FIXME: Get Available RAM"
#endif
*avail_pages = avail_ram_pages;
if (avail_ram_pages < 1)
return MDBX_ENOSYS;
}
return MDBX_SUCCESS;
}
#ifndef xMDBX_ALLOY
unsigned sys_pagesize;
MDBX_MAYBE_UNUSED unsigned sys_pagesize_ln2, sys_allocation_granularity;
#endif /* xMDBX_ALLOY */
void osal_ctor(void) {
#if MDBX_HAVE_PWRITEV && defined(_SC_IOV_MAX)
osal_iov_max = sysconf(_SC_IOV_MAX);
if (RUNNING_ON_VALGRIND && osal_iov_max > 64)
/* чтобы не описывать все 1024 исключения в valgrind_suppress.txt */
osal_iov_max = 64;
#endif /* MDBX_HAVE_PWRITEV && _SC_IOV_MAX */
#if defined(_WIN32) || defined(_WIN64)
SYSTEM_INFO si;
GetSystemInfo(&si);
sys_pagesize = si.dwPageSize;
sys_allocation_granularity = si.dwAllocationGranularity;
#else
sys_pagesize = sysconf(_SC_PAGE_SIZE);
sys_allocation_granularity = (MDBX_WORDBITS > 32) ? 65536 : 4096;
sys_allocation_granularity = (sys_allocation_granularity > sys_pagesize)
? sys_allocation_granularity
: sys_pagesize;
#endif
assert(sys_pagesize > 0 && (sys_pagesize & (sys_pagesize - 1)) == 0);
assert(sys_allocation_granularity >= sys_pagesize &&
sys_allocation_granularity % sys_pagesize == 0);
sys_pagesize_ln2 = log2n_powerof2(sys_pagesize);
#if defined(__linux__) || defined(__gnu_linux__)
posix_clockid = choice_monoclock();
#endif
#if defined(_WIN32) || defined(_WIN64)
QueryPerformanceFrequency(&performance_frequency);
#elif defined(__APPLE__) || defined(__MACH__)
mach_timebase_info_data_t ti;
mach_timebase_info(&ti);
ratio_16dot16_to_monotine = UINT64_C(1000000000) * ti.denom / ti.numer;
#endif
monotime_limit = osal_16dot16_to_monotime(UINT32_MAX - 1);
}
void osal_dtor(void) {}