diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Runtime/r3 | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
288 files changed, 100770 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/Makefile.kup b/src/VBox/Runtime/r3/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/Makefile.kup diff --git a/src/VBox/Runtime/r3/RTPathTemp.cpp b/src/VBox/Runtime/r3/RTPathTemp.cpp new file mode 100644 index 00000000..dc43a612 --- /dev/null +++ b/src/VBox/Runtime/r3/RTPathTemp.cpp @@ -0,0 +1,86 @@ +/* $Id: RTPathTemp.cpp $ */ +/** @file + * IPRT - Path Manipulation, RTPathTemp. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/path.h> + +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include "internal/path.h" + + +RTDECL(int) RTPathTemp(char *pszPath, size_t cchPath) +{ + /* + * Try get it from the environment first. + */ + static const char * const s_apszVars[] = + { + "IPRT_TMPDIR" +#if defined(RT_OS_WINDOWS) + , "TMP", "TEMP", "USERPROFILE" +#elif defined(RT_OS_OS2) + , "TMP", "TEMP", "TMPDIR" +#else + , "TMPDIR" +#endif + }; + for (size_t iVar = 0; iVar < RT_ELEMENTS(s_apszVars); iVar++) + { + int rc = RTEnvGetEx(RTENV_DEFAULT, s_apszVars[iVar], pszPath, cchPath, NULL); + if (rc != VERR_ENV_VAR_NOT_FOUND) + return rc; + } + + /* + * Here we should use some sane system default, instead we just use + * the typical unix temp dir for now. + */ + /** @todo Windows should default to the windows directory, see GetTempPath. + * Some unixes has path.h and _PATH_TMP. There is also a question about + * whether /var/tmp wouldn't be a better place... */ + if (cchPath < sizeof("/tmp") ) + return VERR_BUFFER_OVERFLOW; + memcpy(pszPath, "/tmp", sizeof("/tmp")); + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/alloc-ef-cpp.cpp b/src/VBox/Runtime/r3/alloc-ef-cpp.cpp new file mode 100644 index 00000000..b9496caa --- /dev/null +++ b/src/VBox/Runtime/r3/alloc-ef-cpp.cpp @@ -0,0 +1,169 @@ +/* $Id: alloc-ef-cpp.cpp $ */ +/** @file + * IPRT - Memory Allocation, C++ electric fence. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "alloc-ef.h" + +#include <iprt/asm.h> +#include <new> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @todo test this on MSC */ + +/** MSC declares the operators as cdecl it seems. */ +#ifdef _MSC_VER +# define RT_EF_CDECL __cdecl +#else +# define RT_EF_CDECL +#endif + +/** MSC doesn't use the standard namespace. */ +#ifdef _MSC_VER +# define RT_EF_SIZE_T size_t +#else +# define RT_EF_SIZE_T std::size_t +#endif + +/** The hint that we're throwing std::bad_alloc is not apprecitated by MSC. */ +#ifdef RT_EXCEPTIONS_ENABLED +# ifdef _MSC_VER +# define RT_EF_THROWS_BAD_ALLOC +# define RT_EF_NOTHROW RT_NO_THROW_DEF +# else +# ifdef _GLIBCXX_THROW +# define RT_EF_THROWS_BAD_ALLOC _GLIBCXX_THROW(std::bad_alloc) +# elif defined(__cplusplus) && (__cplusplus + 0) < 201700 +# define RT_EF_THROWS_BAD_ALLOC throw(std::bad_alloc) +# else +# define RT_EF_THROWS_BAD_ALLOC noexcept(false) +# endif +# define RT_EF_NOTHROW throw() +# endif +#else /* !RT_EXCEPTIONS_ENABLED */ +# define RT_EF_THROWS_BAD_ALLOC +# define RT_EF_NOTHROW +#endif /* !RT_EXCEPTIONS_ENABLED */ + + +void *RT_EF_CDECL operator new(RT_EF_SIZE_T cb) RT_EF_THROWS_BAD_ALLOC +{ + void *pv = rtR3MemAlloc("new", RTMEMTYPE_NEW, cb, cb, NULL, ASMReturnAddress(), NULL, 0, NULL); + if (!pv) + throw std::bad_alloc(); + return pv; +} + + +void *RT_EF_CDECL operator new(RT_EF_SIZE_T cb, const std::nothrow_t &) RT_EF_NOTHROW +{ + void *pv = rtR3MemAlloc("new nothrow", RTMEMTYPE_NEW, cb, cb, NULL, ASMReturnAddress(), NULL, 0, NULL); + return pv; +} + + +void RT_EF_CDECL operator delete(void *pv) RT_EF_NOTHROW +{ + rtR3MemFree("delete", RTMEMTYPE_DELETE, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} + + +#ifdef __cpp_sized_deallocation +void RT_EF_CDECL operator delete(void *pv, RT_EF_SIZE_T cb) RT_EF_NOTHROW +{ + NOREF(cb); + AssertMsgFailed(("cb ignored!\n")); + rtR3MemFree("delete", RTMEMTYPE_DELETE, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} +#endif + + +void RT_EF_CDECL operator delete(void * pv, const std::nothrow_t &) RT_EF_NOTHROW +{ + rtR3MemFree("delete nothrow", RTMEMTYPE_DELETE, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} + + +/* + * + * Array object form. + * Array object form. + * Array object form. + * + */ + +void *RT_EF_CDECL operator new[](RT_EF_SIZE_T cb) RT_EF_THROWS_BAD_ALLOC +{ + void *pv = rtR3MemAlloc("new[]", RTMEMTYPE_NEW_ARRAY, cb, cb, NULL, ASMReturnAddress(), NULL, 0, NULL); + if (!pv) + throw std::bad_alloc(); + return pv; +} + + +void * RT_EF_CDECL operator new[](RT_EF_SIZE_T cb, const std::nothrow_t &) RT_EF_NOTHROW +{ + void *pv = rtR3MemAlloc("new[] nothrow", RTMEMTYPE_NEW_ARRAY, cb, cb, NULL, ASMReturnAddress(), NULL, 0, NULL); + return pv; +} + + +void RT_EF_CDECL operator delete[](void * pv) RT_EF_NOTHROW +{ + rtR3MemFree("delete[]", RTMEMTYPE_DELETE_ARRAY, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} + + +#ifdef __cpp_sized_deallocation +void RT_EF_CDECL operator delete[](void * pv, RT_EF_SIZE_T cb) RT_EF_NOTHROW +{ + NOREF(cb); + AssertMsgFailed(("cb ignored!\n")); + rtR3MemFree("delete[]", RTMEMTYPE_DELETE_ARRAY, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} +#endif + + +void RT_EF_CDECL operator delete[](void *pv, const std::nothrow_t &) RT_EF_NOTHROW +{ + rtR3MemFree("delete[] nothrow", RTMEMTYPE_DELETE_ARRAY, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} + diff --git a/src/VBox/Runtime/r3/alloc-ef.cpp b/src/VBox/Runtime/r3/alloc-ef.cpp new file mode 100644 index 00000000..4b61d906 --- /dev/null +++ b/src/VBox/Runtime/r3/alloc-ef.cpp @@ -0,0 +1,1088 @@ +/* $Id: alloc-ef.cpp $ */ +/** @file + * IPRT - Memory Allocation, electric fence. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "alloc-ef.h" +#include <iprt/mem.h> +#include <iprt/log.h> +#include <iprt/asm.h> +#include <iprt/thread.h> +#include <VBox/sup.h> +#include <iprt/errcore.h> +#ifndef IPRT_NO_CRT +# include <errno.h> +# include <stdio.h> +# include <stdlib.h> +#endif + +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/param.h> +#include <iprt/string.h> + +#ifdef RTALLOC_REPLACE_MALLOC +# include <VBox/dis.h> +# include <VBox/disopcode.h> +# include <dlfcn.h> +# ifdef RT_OS_DARWIN +# include <malloc/malloc.h> +# endif +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef RTALLOC_REPLACE_MALLOC +# define RTMEM_REPLACMENT_ALIGN(a_cb) ((a_cb) >= 16 ? RT_ALIGN_Z(a_cb, 16) \ + : (a_cb) >= sizeof(uintptr_t) ? RT_ALIGN_Z(a_cb, sizeof(uintptr_t)) : (a_cb)) +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef RTALLOC_EFENCE_TRACE +/** Spinlock protecting the all the block's globals. */ +static volatile uint32_t g_BlocksLock; +/** Tree tracking the allocations. */ +static AVLPVTREE g_BlocksTree; +# ifdef RTALLOC_EFENCE_FREE_DELAYED +/** Tail of the delayed blocks. */ +static volatile PRTMEMBLOCK g_pBlocksDelayHead; +/** Tail of the delayed blocks. */ +static volatile PRTMEMBLOCK g_pBlocksDelayTail; +/** Number of bytes in the delay list (includes fences). */ +static volatile size_t g_cbBlocksDelay; +# endif /* RTALLOC_EFENCE_FREE_DELAYED */ +# ifdef RTALLOC_REPLACE_MALLOC +/** @name For calling the real allocation API we've replaced. + * @{ */ +void * (*g_pfnOrgMalloc)(size_t); +void * (*g_pfnOrgCalloc)(size_t, size_t); +void * (*g_pfnOrgRealloc)(void *, size_t); +void (*g_pfnOrgFree)(void *); +size_t (*g_pfnOrgMallocSize)(void *); +/** @} */ +# endif +#endif /* RTALLOC_EFENCE_TRACE */ +/** Array of pointers free watches for. */ +void *gapvRTMemFreeWatch[4] = {NULL, NULL, NULL, NULL}; +/** Enable logging of all freed memory. */ +bool gfRTMemFreeLog = false; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef RTALLOC_REPLACE_MALLOC +static void rtMemReplaceMallocAndFriends(void); +#endif + + +/** + * Complains about something. + */ +static void rtmemComplain(const char *pszOp, const char *pszFormat, ...) +{ + va_list args; + fprintf(stderr, "RTMem error: %s: ", pszOp); + va_start(args, pszFormat); + vfprintf(stderr, pszFormat, args); + va_end(args); + RTAssertDoPanic(); +} + +/** + * Log an event. + */ +DECLINLINE(void) rtmemLog(const char *pszOp, const char *pszFormat, ...) +{ +#if 0 + va_list args; + fprintf(stderr, "RTMem info: %s: ", pszOp); + va_start(args, pszFormat); + vfprintf(stderr, pszFormat, args); + va_end(args); +#else + NOREF(pszOp); NOREF(pszFormat); +#endif +} + + +#ifdef RTALLOC_EFENCE_TRACE + +/** + * Acquires the lock. + */ +DECLINLINE(void) rtmemBlockLock(void) +{ + unsigned c = 0; + while (!ASMAtomicCmpXchgU32(&g_BlocksLock, 1, 0)) + RTThreadSleepNoLog(((++c) >> 2) & 31); +} + + +/** + * Releases the lock. + */ +DECLINLINE(void) rtmemBlockUnlock(void) +{ + Assert(g_BlocksLock == 1); + ASMAtomicXchgU32(&g_BlocksLock, 0); +} + + +/** + * Creates a block. + */ +DECLINLINE(PRTMEMBLOCK) rtmemBlockCreate(RTMEMTYPE enmType, size_t cbUnaligned, size_t cbAligned, + const char *pszTag, void *pvCaller, RT_SRC_POS_DECL) +{ +# ifdef RTALLOC_REPLACE_MALLOC + if (!g_pfnOrgMalloc) + rtMemReplaceMallocAndFriends(); + PRTMEMBLOCK pBlock = (PRTMEMBLOCK)g_pfnOrgMalloc(sizeof(*pBlock)); +# else + PRTMEMBLOCK pBlock = (PRTMEMBLOCK)malloc(sizeof(*pBlock)); +# endif + if (pBlock) + { + pBlock->enmType = enmType; + pBlock->cbUnaligned = cbUnaligned; + pBlock->cbAligned = cbAligned; + pBlock->pszTag = pszTag; + pBlock->pvCaller = pvCaller; + pBlock->iLine = iLine; + pBlock->pszFile = pszFile; + pBlock->pszFunction = pszFunction; + } + return pBlock; +} + + +/** + * Frees a block. + */ +DECLINLINE(void) rtmemBlockFree(PRTMEMBLOCK pBlock) +{ +# ifdef RTALLOC_REPLACE_MALLOC + g_pfnOrgFree(pBlock); +# else + free(pBlock); +# endif +} + + +/** + * Insert a block from the tree. + */ +DECLINLINE(void) rtmemBlockInsert(PRTMEMBLOCK pBlock, void *pv) +{ + pBlock->Core.Key = pv; + rtmemBlockLock(); + bool fRc = RTAvlPVInsert(&g_BlocksTree, &pBlock->Core); + rtmemBlockUnlock(); + AssertRelease(fRc); +} + + +/** + * Remove a block from the tree and returns it to the caller. + */ +DECLINLINE(PRTMEMBLOCK) rtmemBlockRemove(void *pv) +{ + rtmemBlockLock(); + PRTMEMBLOCK pBlock = (PRTMEMBLOCK)RTAvlPVRemove(&g_BlocksTree, pv); + rtmemBlockUnlock(); + return pBlock; +} + +/** + * Gets a block. + */ +DECLINLINE(PRTMEMBLOCK) rtmemBlockGet(void *pv) +{ + rtmemBlockLock(); + PRTMEMBLOCK pBlock = (PRTMEMBLOCK)RTAvlPVGet(&g_BlocksTree, pv); + rtmemBlockUnlock(); + return pBlock; +} + +/** + * Dumps one allocation. + */ +static DECLCALLBACK(int) RTMemDumpOne(PAVLPVNODECORE pNode, void *pvUser) +{ + PRTMEMBLOCK pBlock = (PRTMEMBLOCK)pNode; + fprintf(stderr, "%p %08lx(+%02lx) %p\n", + pBlock->Core.Key, + (unsigned long)pBlock->cbUnaligned, + (unsigned long)(pBlock->cbAligned - pBlock->cbUnaligned), + pBlock->pvCaller); + NOREF(pvUser); + return 0; +} + +/** + * Dumps the allocated blocks. + * This is something which you should call from gdb. + */ +extern "C" void RTMemDump(void); +void RTMemDump(void) +{ + fprintf(stderr, "address size(alg) caller\n"); + RTAvlPVDoWithAll(&g_BlocksTree, true, RTMemDumpOne, NULL); +} + +# ifdef RTALLOC_EFENCE_FREE_DELAYED + +/** + * Insert a delayed block. + */ +DECLINLINE(void) rtmemBlockDelayInsert(PRTMEMBLOCK pBlock) +{ + size_t cbBlock = RT_ALIGN_Z(pBlock->cbAligned, PAGE_SIZE) + RTALLOC_EFENCE_SIZE; + pBlock->Core.pRight = NULL; + pBlock->Core.pLeft = NULL; + rtmemBlockLock(); + if (g_pBlocksDelayHead) + { + g_pBlocksDelayHead->Core.pLeft = (PAVLPVNODECORE)pBlock; + pBlock->Core.pRight = (PAVLPVNODECORE)g_pBlocksDelayHead; + g_pBlocksDelayHead = pBlock; + } + else + { + g_pBlocksDelayTail = pBlock; + g_pBlocksDelayHead = pBlock; + } + g_cbBlocksDelay += cbBlock; + rtmemBlockUnlock(); +} + +/** + * Removes a delayed block. + */ +DECLINLINE(PRTMEMBLOCK) rtmemBlockDelayRemove(void) +{ + PRTMEMBLOCK pBlock = NULL; + rtmemBlockLock(); + if (g_cbBlocksDelay > RTALLOC_EFENCE_FREE_DELAYED) + { + pBlock = g_pBlocksDelayTail; + if (pBlock) + { + g_pBlocksDelayTail = (PRTMEMBLOCK)pBlock->Core.pLeft; + if (pBlock->Core.pLeft) + pBlock->Core.pLeft->pRight = NULL; + else + g_pBlocksDelayHead = NULL; + g_cbBlocksDelay -= RT_ALIGN_Z(pBlock->cbAligned, PAGE_SIZE) + RTALLOC_EFENCE_SIZE; + } + } + rtmemBlockUnlock(); + return pBlock; +} + + +/** + * Dumps the freed blocks. + * This is something which you should call from gdb. + */ +extern "C" void RTMemDumpFreed(void); +void RTMemDumpFreed(void) +{ + fprintf(stderr, "address size(alg) caller\n"); + for (PRTMEMBLOCK pCur = g_pBlocksDelayHead; pCur; pCur = (PRTMEMBLOCK)pCur->Core.pRight) + RTMemDumpOne(&pCur->Core, NULL); + +} + +# endif /* RTALLOC_EFENCE_FREE_DELAYED */ + +#endif /* RTALLOC_EFENCE_TRACE */ + + +#if defined(RTALLOC_REPLACE_MALLOC) && defined(RTALLOC_EFENCE_TRACE) +/* + * + * Replacing malloc, calloc, realloc, & free. + * + */ + +/** Replacement for malloc. */ +static void *rtMemReplacementMalloc(size_t cb) +{ + size_t cbAligned = RTMEM_REPLACMENT_ALIGN(cb); + void *pv = rtR3MemAlloc("r-malloc", RTMEMTYPE_RTMEMALLOC, cb, cbAligned, "heap", ASMReturnAddress(), RT_SRC_POS); + if (!pv) + pv = g_pfnOrgMalloc(cb); + return pv; +} + +/** Replacement for calloc. */ +static void *rtMemReplacementCalloc(size_t cbItem, size_t cItems) +{ + size_t cb = cbItem * cItems; + size_t cbAligned = RTMEM_REPLACMENT_ALIGN(cb); + void *pv = rtR3MemAlloc("r-calloc", RTMEMTYPE_RTMEMALLOCZ, cb, cbAligned, "heap", ASMReturnAddress(), RT_SRC_POS); + if (!pv) + pv = g_pfnOrgCalloc(cbItem, cItems); + return pv; +} + +/** Replacement for realloc. */ +static void *rtMemReplacementRealloc(void *pvOld, size_t cbNew) +{ + if (pvOld) + { + /* We're not strict about where the memory was allocated. */ + PRTMEMBLOCK pBlock = rtmemBlockGet(pvOld); + if (pBlock) + { + size_t cbAligned = RTMEM_REPLACMENT_ALIGN(cbNew); + return rtR3MemRealloc("r-realloc", RTMEMTYPE_RTMEMREALLOC, pvOld, cbAligned, "heap", ASMReturnAddress(), RT_SRC_POS); + } + return g_pfnOrgRealloc(pvOld, cbNew); + } + return rtMemReplacementMalloc(cbNew); +} + +/** Replacement for free(). */ +static void rtMemReplacementFree(void *pv) +{ + if (pv) + { + /* We're not strict about where the memory was allocated. */ + PRTMEMBLOCK pBlock = rtmemBlockGet(pv); + if (pBlock) + rtR3MemFree("r-free", RTMEMTYPE_RTMEMFREE, pv, 0, ASMReturnAddress(), RT_SRC_POS); + else + g_pfnOrgFree(pv); + } +} + +# ifdef RT_OS_DARWIN +/** Replacement for malloc. */ +static size_t rtMemReplacementMallocSize(void *pv) +{ + size_t cb; + if (pv) + { + /* We're not strict about where the memory was allocated. */ + PRTMEMBLOCK pBlock = rtmemBlockGet(pv); + if (pBlock) + cb = pBlock->cbUnaligned; + else + cb = g_pfnOrgMallocSize(pv); + } + else + cb = 0; + return cb; +} +# endif + + +static void rtMemReplaceMallocAndFriends(void) +{ + struct + { + const char *pszName; + PFNRT pfnReplacement; + PFNRT pfnOrg; + PFNRT *ppfnJumpBack; + } aApis[] = + { + { "free", (PFNRT)rtMemReplacementFree, (PFNRT)free, (PFNRT *)&g_pfnOrgFree }, + { "realloc", (PFNRT)rtMemReplacementRealloc, (PFNRT)realloc, (PFNRT *)&g_pfnOrgRealloc }, + { "calloc", (PFNRT)rtMemReplacementCalloc, (PFNRT)calloc, (PFNRT *)&g_pfnOrgCalloc }, + { "malloc", (PFNRT)rtMemReplacementMalloc, (PFNRT)malloc, (PFNRT *)&g_pfnOrgMalloc }, +#ifdef RT_OS_DARWIN + { "malloc_size", (PFNRT)rtMemReplacementMallocSize, (PFNRT)malloc_size, (PFNRT *)&g_pfnOrgMallocSize }, +#endif + }; + + /* + * Initialize the jump backs to avoid recursivly entering this function. + */ + for (unsigned i = 0; i < RT_ELEMENTS(aApis); i++) + *aApis[i].ppfnJumpBack = aApis[i].pfnOrg; + + /* + * Give the user an option to skip replacing malloc. + */ + if (getenv("IPRT_DONT_REPLACE_MALLOC")) + return; + + /* + * Allocate a page for jump back code (we leak it). + */ + uint8_t *pbExecPage = (uint8_t *)RTMemPageAlloc(PAGE_SIZE); AssertFatal(pbExecPage); + int rc = RTMemProtect(pbExecPage, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC); AssertFatalRC(rc); + + /* + * Do the ground work. + */ + uint8_t *pb = pbExecPage; + for (unsigned i = 0; i < RT_ELEMENTS(aApis); i++) + { + /* Resolve it. */ + PFNRT pfnOrg = (PFNRT)(uintptr_t)dlsym(RTLD_DEFAULT, aApis[i].pszName); + if (pfnOrg) + aApis[i].pfnOrg = pfnOrg; + else + pfnOrg = aApis[i].pfnOrg; + + /* Figure what we can replace and how much to duplicate in the jump back code. */ +# ifdef RT_ARCH_AMD64 + uint32_t cbNeeded = 12; + DISCPUMODE const enmCpuMode = DISCPUMODE_64BIT; +# elif defined(RT_ARCH_X86) + uint32_t const cbNeeded = 5; + DISCPUMODE const enmCpuMode = DISCPUMODE_32BIT; +# else +# error "Port me" +# endif + uint32_t offJmpBack = 0; + uint32_t cbCopy = 0; + while (offJmpBack < cbNeeded) + { + DISCPUSTATE Dis; + uint32_t cbInstr = 1; + rc = DISInstr((void *)((uintptr_t)pfnOrg + offJmpBack), enmCpuMode, &Dis, &cbInstr); AssertFatalRC(rc); + AssertFatal(!(Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW))); +# ifdef RT_ARCH_AMD64 +# ifdef RT_OS_DARWIN + /* Kludge for: cmp [malloc_def_zone_state], 1; jg 2; call _malloc_initialize; 2: */ + if ( Dis.ModRM.Bits.Mod == 0 + && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */ + && (Dis.Param2.fUse & (DISUSE_IMMEDIATE16_SX8 | DISUSE_IMMEDIATE32_SX8 | DISUSE_IMMEDIATE64_SX8)) + && Dis.Param2.uValue == 1 + && Dis.pCurInstr->uOpcode == OP_CMP) + { + cbCopy = offJmpBack; + + offJmpBack += cbInstr; + rc = DISInstr((void *)((uintptr_t)pfnOrg + offJmpBack), enmCpuMode, &Dis, &cbInstr); AssertFatalRC(rc); + if ( Dis.pCurInstr->uOpcode == OP_JNBE + && Dis.Param1.uDisp.i8 == 5) + { + offJmpBack += cbInstr + 5; + AssertFatal(offJmpBack >= cbNeeded); + break; + } + } +# endif + AssertFatal(!(Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */)); +# endif + offJmpBack += cbInstr; + } + if (!cbCopy) + cbCopy = offJmpBack; + + /* Assemble the jump back. */ + memcpy(pb, (void *)(uintptr_t)pfnOrg, cbCopy); + uint32_t off = cbCopy; +# ifdef RT_ARCH_AMD64 + pb[off++] = 0xff; /* jmp qword [$+8 wrt RIP] */ + pb[off++] = 0x25; + *(uint32_t *)&pb[off] = 0; + off += 4; + *(uint64_t *)&pb[off] = (uintptr_t)pfnOrg + offJmpBack; + off += 8; + off = RT_ALIGN_32(off, 16); +# elif defined(RT_ARCH_X86) + pb[off++] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&pb[off] = (uintptr_t)pfnOrg + offJmpBack - (uintptr_t)&pb[4]; + off += 4; + off = RT_ALIGN_32(off, 8); +# else +# error "Port me" +# endif + *aApis[i].ppfnJumpBack = (PFNRT)(uintptr_t)pb; + pb += off; + } + + /* + * Modify the APIs. + */ + for (unsigned i = 0; i < RT_ELEMENTS(aApis); i++) + { + pb = (uint8_t *)(uintptr_t)aApis[i].pfnOrg; + rc = RTMemProtect(pb, 16, RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC); AssertFatalRC(rc); + +# ifdef RT_ARCH_AMD64 + /* Assemble the LdrLoadDll patch. */ + *pb++ = 0x48; /* mov rax, qword */ + *pb++ = 0xb8; + *(uint64_t *)pb = (uintptr_t)aApis[i].pfnReplacement; + pb += 8; + *pb++ = 0xff; /* jmp rax */ + *pb++ = 0xe0; +# elif defined(RT_ARCH_X86) + *pb++ = 0xe9; /* jmp rel32 */ + *(uint32_t *)pb = (uintptr_t)aApis[i].pfnReplacement - (uintptr_t)&pb[4]; +# else +# error "Port me" +# endif + } +} + +#endif /* RTALLOC_REPLACE_MALLOC && RTALLOC_EFENCE_TRACE */ + + +/** + * Internal allocator. + */ +RTDECL(void *) rtR3MemAlloc(const char *pszOp, RTMEMTYPE enmType, size_t cbUnaligned, size_t cbAligned, + const char *pszTag, void *pvCaller, RT_SRC_POS_DECL) +{ + /* + * Sanity. + */ + if ( RT_ALIGN_Z(RTALLOC_EFENCE_SIZE, PAGE_SIZE) != RTALLOC_EFENCE_SIZE + && RTALLOC_EFENCE_SIZE <= 0) + { + rtmemComplain(pszOp, "Invalid E-fence size! %#x\n", RTALLOC_EFENCE_SIZE); + return NULL; + } + if (!cbUnaligned) + { +#if 0 + rtmemComplain(pszOp, "Request of ZERO bytes allocation!\n"); + return NULL; +#else + cbAligned = cbUnaligned = 1; +#endif + } + +#ifndef RTALLOC_EFENCE_IN_FRONT + /* Alignment decreases fence accuracy, but this is at least partially + * counteracted by filling and checking the alignment padding. When the + * fence is in front then then no extra alignment is needed. */ + cbAligned = RT_ALIGN_Z(cbAligned, RTALLOC_EFENCE_ALIGNMENT); +#endif + +#ifdef RTALLOC_EFENCE_TRACE + /* + * Allocate the trace block. + */ + PRTMEMBLOCK pBlock = rtmemBlockCreate(enmType, cbUnaligned, cbAligned, pszTag, pvCaller, RT_SRC_POS_ARGS); + if (!pBlock) + { + rtmemComplain(pszOp, "Failed to allocate trace block!\n"); + return NULL; + } +#endif + + /* + * Allocate a block with page alignment space + the size of the E-fence. + */ + size_t cbBlock = RT_ALIGN_Z(cbAligned, PAGE_SIZE) + RTALLOC_EFENCE_SIZE; + void *pvBlock = RTMemPageAlloc(cbBlock); + if (pvBlock) + { + /* + * Calc the start of the fence and the user block + * and then change the page protection of the fence. + */ +#ifdef RTALLOC_EFENCE_IN_FRONT + void *pvEFence = pvBlock; + void *pv = (char *)pvEFence + RTALLOC_EFENCE_SIZE; +# ifdef RTALLOC_EFENCE_NOMAN_FILLER + memset((char *)pv + cbUnaligned, RTALLOC_EFENCE_NOMAN_FILLER, cbBlock - RTALLOC_EFENCE_SIZE - cbUnaligned); +# endif +#else + void *pvEFence = (char *)pvBlock + (cbBlock - RTALLOC_EFENCE_SIZE); + void *pv = (char *)pvEFence - cbAligned; +# ifdef RTALLOC_EFENCE_NOMAN_FILLER + memset(pvBlock, RTALLOC_EFENCE_NOMAN_FILLER, cbBlock - RTALLOC_EFENCE_SIZE - cbAligned); + memset((char *)pv + cbUnaligned, RTALLOC_EFENCE_NOMAN_FILLER, cbAligned - cbUnaligned); +# endif +#endif + +#ifdef RTALLOC_EFENCE_FENCE_FILLER + memset(pvEFence, RTALLOC_EFENCE_FENCE_FILLER, RTALLOC_EFENCE_SIZE); +#endif + int rc = RTMemProtect(pvEFence, RTALLOC_EFENCE_SIZE, RTMEM_PROT_NONE); + if (!rc) + { +#ifdef RTALLOC_EFENCE_TRACE + rtmemBlockInsert(pBlock, pv); +#endif + if (enmType == RTMEMTYPE_RTMEMALLOCZ) + memset(pv, 0, cbUnaligned); +#ifdef RTALLOC_EFENCE_FILLER + else + memset(pv, RTALLOC_EFENCE_FILLER, cbUnaligned); +#endif + + rtmemLog(pszOp, "returns %p (pvBlock=%p cbBlock=%#x pvEFence=%p cbUnaligned=%#x)\n", pv, pvBlock, cbBlock, pvEFence, cbUnaligned); + return pv; + } + rtmemComplain(pszOp, "RTMemProtect failed, pvEFence=%p size %d, rc=%d\n", pvEFence, RTALLOC_EFENCE_SIZE, rc); + RTMemPageFree(pvBlock, cbBlock); + } + else + rtmemComplain(pszOp, "Failed to allocated %lu (%lu) bytes.\n", (unsigned long)cbBlock, (unsigned long)cbUnaligned); + +#ifdef RTALLOC_EFENCE_TRACE + rtmemBlockFree(pBlock); +#endif + return NULL; +} + + +/** + * Internal free. + */ +RTDECL(void) rtR3MemFree(const char *pszOp, RTMEMTYPE enmType, void *pv, size_t cbUser, void *pvCaller, RT_SRC_POS_DECL) +{ + NOREF(enmType); RT_SRC_POS_NOREF(); + + /* + * Simple case. + */ + if (!pv) + return; + + /* + * Check watch points. + */ + for (unsigned i = 0; i < RT_ELEMENTS(gapvRTMemFreeWatch); i++) + if (gapvRTMemFreeWatch[i] == pv) + RTAssertDoPanic(); + +#ifdef RTALLOC_EFENCE_TRACE + /* + * Find the block. + */ + PRTMEMBLOCK pBlock = rtmemBlockRemove(pv); + if (pBlock) + { + if (gfRTMemFreeLog) + RTLogPrintf("RTMem %s: pv=%p pvCaller=%p cbUnaligned=%#x\n", pszOp, pv, pvCaller, pBlock->cbUnaligned); + +# ifdef RTALLOC_EFENCE_NOMAN_FILLER + /* + * Check whether the no man's land is untouched. + */ +# ifdef RTALLOC_EFENCE_IN_FRONT + void *pvWrong = ASMMemFirstMismatchingU8((char *)pv + pBlock->cbUnaligned, + RT_ALIGN_Z(pBlock->cbAligned, PAGE_SIZE) - pBlock->cbUnaligned, + RTALLOC_EFENCE_NOMAN_FILLER); +# else + /* Alignment must match allocation alignment in rtMemAlloc(). */ + void *pvWrong = ASMMemFirstMismatchingU8((char *)pv + pBlock->cbUnaligned, + pBlock->cbAligned - pBlock->cbUnaligned, + RTALLOC_EFENCE_NOMAN_FILLER); + if (pvWrong) + RTAssertDoPanic(); + pvWrong = ASMMemFirstMismatchingU8((void *)((uintptr_t)pv & ~(uintptr_t)PAGE_OFFSET_MASK), + RT_ALIGN_Z(pBlock->cbAligned, PAGE_SIZE) - pBlock->cbAligned, + RTALLOC_EFENCE_NOMAN_FILLER); +# endif + if (pvWrong) + RTAssertDoPanic(); +# endif + + /* + * Fill the user part of the block. + */ + AssertMsg(enmType != RTMEMTYPE_RTMEMFREEZ || cbUser == pBlock->cbUnaligned, + ("cbUser=%#zx cbUnaligned=%#zx\n", cbUser, pBlock->cbUnaligned)); + RT_NOREF(cbUser); + if (enmType == RTMEMTYPE_RTMEMFREEZ) + RT_BZERO(pv, pBlock->cbUnaligned); +# ifdef RTALLOC_EFENCE_FREE_FILL + else + memset(pv, RTALLOC_EFENCE_FREE_FILL, pBlock->cbUnaligned); +# endif + +# if defined(RTALLOC_EFENCE_FREE_DELAYED) && RTALLOC_EFENCE_FREE_DELAYED > 0 + /* + * We're doing delayed freeing. + * That means we'll expand the E-fence to cover the entire block. + */ + int rc = RTMemProtect(pv, pBlock->cbAligned, RTMEM_PROT_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Insert it into the free list and process pending frees. + */ + rtmemBlockDelayInsert(pBlock); + while ((pBlock = rtmemBlockDelayRemove()) != NULL) + { + pv = pBlock->Core.Key; +# ifdef RTALLOC_EFENCE_IN_FRONT + void *pvBlock = (char *)pv - RTALLOC_EFENCE_SIZE; +# else + void *pvBlock = (void *)((uintptr_t)pv & ~(uintptr_t)PAGE_OFFSET_MASK); +# endif + size_t cbBlock = RT_ALIGN_Z(pBlock->cbAligned, PAGE_SIZE) + RTALLOC_EFENCE_SIZE; + rc = RTMemProtect(pvBlock, cbBlock, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_SUCCESS(rc)) + RTMemPageFree(pvBlock, RT_ALIGN_Z(pBlock->cbAligned, PAGE_SIZE) + RTALLOC_EFENCE_SIZE); + else + rtmemComplain(pszOp, "RTMemProtect(%p, %#x, RTMEM_PROT_READ | RTMEM_PROT_WRITE) -> %d\n", pvBlock, cbBlock, rc); + rtmemBlockFree(pBlock); + } + } + else + rtmemComplain(pszOp, "Failed to expand the efence of pv=%p cb=%d, rc=%d.\n", pv, pBlock, rc); + +# else /* !RTALLOC_EFENCE_FREE_DELAYED */ + + /* + * Turn of the E-fence and free it. + */ +# ifdef RTALLOC_EFENCE_IN_FRONT + void *pvBlock = (char *)pv - RTALLOC_EFENCE_SIZE; + void *pvEFence = pvBlock; +# else + void *pvBlock = (void *)((uintptr_t)pv & ~(uintptr_t)PAGE_OFFSET_MASK); + void *pvEFence = (char *)pv + pBlock->cb; +# endif + int rc = RTMemProtect(pvEFence, RTALLOC_EFENCE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_SUCCESS(rc)) + RTMemPageFree(pvBlock, RT_ALIGN_Z(pBlock->cbAligned, PAGE_SIZE) + RTALLOC_EFENCE_SIZE); + else + rtmemComplain(pszOp, "RTMemProtect(%p, %#x, RTMEM_PROT_READ | RTMEM_PROT_WRITE) -> %d\n", pvEFence, RTALLOC_EFENCE_SIZE, rc); + rtmemBlockFree(pBlock); + +# endif /* !RTALLOC_EFENCE_FREE_DELAYED */ + } + else + rtmemComplain(pszOp, "pv=%p not found! Incorrect free!\n", pv); + +#else /* !RTALLOC_EFENCE_TRACE */ + + /* + * We have no size tracking, so we're not doing any freeing because + * we cannot if the E-fence is after the block. + * Let's just expand the E-fence to the first page of the user bit + * since we know that it's around. + */ + if (enmType == RTMEMTYPE_RTMEMFREEZ) + RT_BZERO(pv, cbUser); + int rc = RTMemProtect((void *)((uintptr_t)pv & ~(uintptr_t)PAGE_OFFSET_MASK), PAGE_SIZE, RTMEM_PROT_NONE); + if (RT_FAILURE(rc)) + rtmemComplain(pszOp, "RTMemProtect(%p, PAGE_SIZE, RTMEM_PROT_NONE) -> %d\n", (void *)((uintptr_t)pv & ~(uintptr_t)PAGE_OFFSET_MASK), rc); +#endif /* !RTALLOC_EFENCE_TRACE */ +} + + +/** + * Internal realloc. + */ +RTDECL(void *) rtR3MemRealloc(const char *pszOp, RTMEMTYPE enmType, void *pvOld, size_t cbNew, + const char *pszTag, void *pvCaller, RT_SRC_POS_DECL) +{ + /* + * Allocate new and copy. + */ + if (!pvOld) + return rtR3MemAlloc(pszOp, enmType, cbNew, cbNew, pszTag, pvCaller, RT_SRC_POS_ARGS); + if (!cbNew) + { + rtR3MemFree(pszOp, RTMEMTYPE_RTMEMREALLOC, pvOld, 0, pvCaller, RT_SRC_POS_ARGS); + return NULL; + } + +#ifdef RTALLOC_EFENCE_TRACE + + /* + * Get the block, allocate the new, copy the data, free the old one. + */ + PRTMEMBLOCK pBlock = rtmemBlockGet(pvOld); + if (pBlock) + { + void *pvRet = rtR3MemAlloc(pszOp, enmType, cbNew, cbNew, pszTag, pvCaller, RT_SRC_POS_ARGS); + if (pvRet) + { + memcpy(pvRet, pvOld, RT_MIN(cbNew, pBlock->cbUnaligned)); + rtR3MemFree(pszOp, RTMEMTYPE_RTMEMREALLOC, pvOld, 0, pvCaller, RT_SRC_POS_ARGS); + } + return pvRet; + } + else + rtmemComplain(pszOp, "pvOld=%p was not found!\n", pvOld); + return NULL; + +#else /* !RTALLOC_EFENCE_TRACE */ + + rtmemComplain(pszOp, "Not supported if RTALLOC_EFENCE_TRACE isn't defined!\n"); + return NULL; + +#endif /* !RTALLOC_EFENCE_TRACE */ +} + + + + +RTDECL(void *) RTMemEfTmpAlloc(size_t cb, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("TmpAlloc", RTMEMTYPE_RTMEMALLOC, cb, cb, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfTmpAllocZ(size_t cb, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("TmpAlloc", RTMEMTYPE_RTMEMALLOCZ, cb, cb, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void) RTMemEfTmpFree(void *pv, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("Free", RTMEMTYPE_RTMEMFREE, pv, 0, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void) RTMemEfTmpFreeZ(void *pv, size_t cb, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("FreeZ", RTMEMTYPE_RTMEMFREEZ, pv, cb, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfAlloc(size_t cb, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("Alloc", RTMEMTYPE_RTMEMALLOC, cb, cb, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfAllocZ(size_t cb, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("AllocZ", RTMEMTYPE_RTMEMALLOCZ, cb, cb, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfAllocVar(size_t cbUnaligned, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return rtR3MemAlloc("Alloc", RTMEMTYPE_RTMEMALLOC, cbUnaligned, cbAligned, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfAllocZVar(size_t cbUnaligned, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return rtR3MemAlloc("AllocZ", RTMEMTYPE_RTMEMALLOCZ, cbUnaligned, cbAligned, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfRealloc(void *pvOld, size_t cbNew, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + return rtR3MemRealloc("Realloc", RTMEMTYPE_RTMEMREALLOC, pvOld, cbNew, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfReallocZ(void *pvOld, size_t cbOld, size_t cbNew, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + void *pvDst = rtR3MemRealloc("ReallocZ", RTMEMTYPE_RTMEMREALLOC, pvOld, cbNew, pszTag, ASMReturnAddress(), RT_SRC_POS_ARGS); + if (pvDst && cbNew > cbOld) + memset((uint8_t *)pvDst + cbOld, 0, cbNew - cbOld); + return pvDst; +} + + +RTDECL(void) RTMemEfFree(void *pv, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("Free", RTMEMTYPE_RTMEMFREE, pv, 0, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void) RTMemEfFreeZ(void *pv, size_t cb, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("FreeZ", RTMEMTYPE_RTMEMFREEZ, pv, cb, ASMReturnAddress(), RT_SRC_POS_ARGS); +} + + +RTDECL(void *) RTMemEfDup(const void *pvSrc, size_t cb, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + void *pvDst = RTMemEfAlloc(cb, pszTag, RT_SRC_POS_ARGS); + if (pvDst) + memcpy(pvDst, pvSrc, cb); + return pvDst; +} + + +RTDECL(void *) RTMemEfDupEx(const void *pvSrc, size_t cbSrc, size_t cbExtra, const char *pszTag, RT_SRC_POS_DECL) RT_NO_THROW_DEF +{ + void *pvDst = RTMemEfAlloc(cbSrc + cbExtra, pszTag, RT_SRC_POS_ARGS); + if (pvDst) + { + memcpy(pvDst, pvSrc, cbSrc); + memset((uint8_t *)pvDst + cbSrc, 0, cbExtra); + } + return pvDst; +} + + + + +/* + * + * The NP (no position) versions. + * + */ + + + +RTDECL(void *) RTMemEfTmpAllocNP(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("TmpAlloc", RTMEMTYPE_RTMEMALLOC, cb, cb, pszTag, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfTmpAllocZNP(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("TmpAllocZ", RTMEMTYPE_RTMEMALLOCZ, cb, cb, pszTag, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void) RTMemEfTmpFreeNP(void *pv) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("Free", RTMEMTYPE_RTMEMFREE, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void) RTMemEfTmpFreeZNP(void *pv, size_t cb) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("FreeZ", RTMEMTYPE_RTMEMFREEZ, pv, cb, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfAllocNP(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("Alloc", RTMEMTYPE_RTMEMALLOC, cb, cb, pszTag, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfAllocZNP(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtR3MemAlloc("AllocZ", RTMEMTYPE_RTMEMALLOCZ, cb, cb, pszTag, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfAllocVarNP(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return rtR3MemAlloc("Alloc", RTMEMTYPE_RTMEMALLOC, cbUnaligned, cbAligned, pszTag, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfAllocZVarNP(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return rtR3MemAlloc("AllocZ", RTMEMTYPE_RTMEMALLOCZ, cbUnaligned, cbAligned, pszTag, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfReallocNP(void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF +{ + return rtR3MemRealloc("Realloc", RTMEMTYPE_RTMEMREALLOC, pvOld, cbNew, pszTag, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfReallocZNP(void *pvOld, size_t cbOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF +{ + void *pvDst = rtR3MemRealloc("ReallocZ", RTMEMTYPE_RTMEMREALLOC, pvOld, cbNew, pszTag, ASMReturnAddress(), NULL, 0, NULL); + if (pvDst && cbNew > cbOld) + memset((uint8_t *)pvDst + cbOld, 0, cbNew - cbOld); + return pvDst; +} + + +RTDECL(void) RTMemEfFreeNP(void *pv) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("Free", RTMEMTYPE_RTMEMFREE, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void) RTMemEfFreeZNP(void *pv, size_t cb) RT_NO_THROW_DEF +{ + if (pv) + rtR3MemFree("Free", RTMEMTYPE_RTMEMFREEZ, pv, cb, ASMReturnAddress(), NULL, 0, NULL); +} + + +RTDECL(void *) RTMemEfDupNP(const void *pvSrc, size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + void *pvDst = RTMemEfAlloc(cb, pszTag, NULL, 0, NULL); + if (pvDst) + memcpy(pvDst, pvSrc, cb); + return pvDst; +} + + +RTDECL(void *) RTMemEfDupExNP(const void *pvSrc, size_t cbSrc, size_t cbExtra, const char *pszTag) RT_NO_THROW_DEF +{ + void *pvDst = RTMemEfAlloc(cbSrc + cbExtra, pszTag, NULL, 0, NULL); + if (pvDst) + { + memcpy(pvDst, pvSrc, cbSrc); + memset((uint8_t *)pvDst + cbSrc, 0, cbExtra); + } + return pvDst; +} + diff --git a/src/VBox/Runtime/r3/alloc-ef.h b/src/VBox/Runtime/r3/alloc-ef.h new file mode 100644 index 00000000..c6536e7c --- /dev/null +++ b/src/VBox/Runtime/r3/alloc-ef.h @@ -0,0 +1,231 @@ +/* $Id: alloc-ef.h $ */ +/** @file + * IPRT - Memory Allocation, electric fence. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_alloc_ef_h +#define IPRT_INCLUDED_SRC_r3_alloc_ef_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +#if defined(DOXYGEN_RUNNING) +# define RTALLOC_USE_EFENCE +# define RTALLOC_EFENCE_IN_FRONT +# define RTALLOC_EFENCE_FREE_FILL 'f' +#endif + +/** @def RTALLOC_USE_EFENCE + * If defined the electric fence put up for ALL allocations by RTMemAlloc(), + * RTMemAllocZ(), RTMemRealloc(), RTMemTmpAlloc() and RTMemTmpAllocZ(). + */ +#if 0 +# define RTALLOC_USE_EFENCE +#endif + +/** @def RTALLOC_EFENCE_SIZE + * The size of the fence. This must be page aligned. + */ +#define RTALLOC_EFENCE_SIZE PAGE_SIZE + +/** @def RTALLOC_EFENCE_ALIGNMENT + * The allocation alignment, power of two of course. + * + * Use this for working around misaligned sizes, usually stemming from + * allocating a string or something after the main structure. When you + * encounter this, please fix the allocation to RTMemAllocVar or RTMemAllocZVar. + */ +#if 0 +# define RTALLOC_EFENCE_ALIGNMENT (ARCH_BITS / 8) +#else +# define RTALLOC_EFENCE_ALIGNMENT 1 +#endif + +/** @def RTALLOC_EFENCE_IN_FRONT + * Define this to put the fence up in front of the block. + * The default (when this isn't defined) is to up it up after the block. + */ +//# define RTALLOC_EFENCE_IN_FRONT + +/** @def RTALLOC_EFENCE_TRACE + * Define this to support actual free and reallocation of blocks. + */ +#define RTALLOC_EFENCE_TRACE + +/** @def RTALLOC_EFENCE_FREE_DELAYED + * This define will enable free() delay and protection of the freed data + * while it's being delayed. The value of RTALLOC_EFENCE_FREE_DELAYED defines + * the threshold of the delayed blocks. + * Delayed blocks does not consume any physical memory, only virtual address space. + * Requires RTALLOC_EFENCE_TRACE. + */ +#define RTALLOC_EFENCE_FREE_DELAYED (20 * _1M) + +/** @def RTALLOC_EFENCE_FREE_FILL + * This define will enable memset(,RTALLOC_EFENCE_FREE_FILL,)'ing the user memory + * in the block before freeing/decommitting it. This is useful in GDB since GDB + * appears to be able to read the content of the page even after it's been + * decommitted. + * Requires RTALLOC_EFENCE_TRACE. + */ +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) || defined(DOXYGEN_RUNNING) +# define RTALLOC_EFENCE_FREE_FILL 'f' +#endif + +/** @def RTALLOC_EFENCE_FILLER + * This define will enable memset(,RTALLOC_EFENCE_FILLER,)'ing the allocated + * memory when the API doesn't require it to be zero'd. + */ +#define RTALLOC_EFENCE_FILLER 0xef + +/** @def RTALLOC_EFENCE_NOMAN_FILLER + * This define will enable memset(,RTALLOC_EFENCE_NOMAN_FILLER,)'ing the + * unprotected but not allocated area of memory, the so called no man's land. + */ +#define RTALLOC_EFENCE_NOMAN_FILLER 0xaa + +/** @def RTALLOC_EFENCE_FENCE_FILLER + * This define will enable memset(,RTALLOC_EFENCE_FENCE_FILLER,)'ing the + * fence itself, as debuggers can usually read them. + */ +#define RTALLOC_EFENCE_FENCE_FILLER 0xcc + +#if defined(DOXYGEN_RUNNING) +/** @def RTALLOC_EFENCE_CPP + * This define will enable the new and delete wrappers. + */ +# define RTALLOC_EFENCE_CPP +#endif + +#if defined(RUNNING_DOXYGEN) +/** @def RTALLOC_REPLACE_MALLOC + * Replaces the malloc, calloc, realloc, free and friends in libc (experimental). + * Set in LocalConfig.kmk. Requires RTALLOC_EFENCE_TRACE to work. */ +# define RTALLOC_REPLACE_MALLOC +#endif +#if defined(RTALLOC_REPLACE_MALLOC) && !defined(RTALLOC_EFENCE_TRACE) +# error "RTALLOC_REPLACE_MALLOC requires RTALLOC_EFENCE_TRACE." +#endif + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#else +# include <sys/mman.h> +#endif +#include <iprt/avl.h> +#include <iprt/thread.h> + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** + * Allocation types. + */ +typedef enum RTMEMTYPE +{ + RTMEMTYPE_RTMEMALLOC, + RTMEMTYPE_RTMEMALLOCZ, + RTMEMTYPE_RTMEMREALLOC, + RTMEMTYPE_RTMEMFREE, + RTMEMTYPE_RTMEMFREEZ, + + RTMEMTYPE_NEW, + RTMEMTYPE_NEW_ARRAY, + RTMEMTYPE_DELETE, + RTMEMTYPE_DELETE_ARRAY +} RTMEMTYPE; + +#ifdef RTALLOC_EFENCE_TRACE +/** + * Node tracking a memory allocation. + */ +typedef struct RTMEMBLOCK +{ + /** Avl node code, key is the user block pointer. */ + AVLPVNODECORE Core; + /** Allocation type. */ + RTMEMTYPE enmType; + /** The unaligned size of the block. */ + size_t cbUnaligned; + /** The aligned size of the block. */ + size_t cbAligned; + /** The allocation tag (read-only string). */ + const char *pszTag; + /** The return address of the allocator function. */ + void *pvCaller; + /** Line number of the alloc call. */ + unsigned iLine; + /** File from within the allocation was made. */ + const char *pszFile; + /** Function from within the allocation was made. */ + const char *pszFunction; +} RTMEMBLOCK, *PRTMEMBLOCK; + +#endif + + +/******************************************************************************* +* Internal Functions * +******************************************************************************/ +RT_C_DECLS_BEGIN +RTDECL(void *) rtR3MemAlloc(const char *pszOp, RTMEMTYPE enmType, size_t cbUnaligned, size_t cbAligned, + const char *pszTag, void *pvCaller, RT_SRC_POS_DECL); +RTDECL(void *) rtR3MemRealloc(const char *pszOp, RTMEMTYPE enmType, void *pvOld, size_t cbNew, + const char *pszTag, void *pvCaller, RT_SRC_POS_DECL); +RTDECL(void) rtR3MemFree(const char *pszOp, RTMEMTYPE enmType, void *pv, size_t cbUser, void *pvCaller, RT_SRC_POS_DECL); +RT_C_DECLS_END + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +#ifdef RTALLOC_REPLACE_MALLOC +RT_C_DECLS_BEGIN +extern void * (*g_pfnOrgMalloc)(size_t); +extern void * (*g_pfnOrgCalloc)(size_t, size_t); +extern void * (*g_pfnOrgRealloc)(void *, size_t); +extern void (*g_pfnOrgFree)(void *); +RT_C_DECLS_END +#endif + +#endif /* !IPRT_INCLUDED_SRC_r3_alloc_ef_h */ + diff --git a/src/VBox/Runtime/r3/alloc.cpp b/src/VBox/Runtime/r3/alloc.cpp new file mode 100644 index 00000000..015df135 --- /dev/null +++ b/src/VBox/Runtime/r3/alloc.cpp @@ -0,0 +1,317 @@ +/* $Id: alloc.cpp $ */ +/** @file + * IPRT - Memory Allocation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#if defined(RTMEM_WRAP_TO_EF_APIS) && !defined(RTMEM_NO_WRAP_TO_EF_APIS) +# undef RTMEM_WRAP_TO_EF_APIS +# define RTALLOC_USE_EFENCE 1 +#endif + +/*#define RTMEMALLOC_USE_TRACKER*/ +/* Don't enable the tracker when building the minimal IPRT. */ +#ifdef RT_MINI +# undef RTMEMALLOC_USE_TRACKER +#endif + +#if defined(RTMEMALLOC_USE_TRACKER) && defined(RTALLOC_USE_EFENCE) +# error "Cannot define both RTMEMALLOC_USE_TRACKER and RTALLOC_USE_EFENCE!" +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "alloc-ef.h" +#include <iprt/mem.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#ifdef RTMEMALLOC_USE_TRACKER +# include <iprt/memtracker.h> +#endif +#include <iprt/param.h> +#include <iprt/string.h> +#include "internal/mem.h" + +#include <stdlib.h> + +#undef RTMemTmpAlloc +#undef RTMemTmpAllocTag +#undef RTMemTmpAllocZ +#undef RTMemTmpAllocZTag +#undef RTMemTmpFree +#undef RTMemTmpFreeZ +#undef RTMemAlloc +#undef RTMemAllocTag +#undef RTMemAllocZ +#undef RTMemAllocZTag +#undef RTMemAllocVar +#undef RTMemAllocVarTag +#undef RTMemAllocZVar +#undef RTMemAllocZVarTag +#undef RTMemRealloc +#undef RTMemReallocTag +#undef RTMemFree +#undef RTMemFreeZ +#undef RTMemDup +#undef RTMemDupTag +#undef RTMemDupEx +#undef RTMemDupExTag + +#undef RTALLOC_USE_EFENCE + + +#ifdef IPRT_WITH_GCC_SANITIZER +/** + * Checks if @a pszTag is a leak tag. + * + * @returns true if leak tag, false if not. + * @param pszTag Tage to inspect. + */ +DECLINLINE(bool) rtMemIsLeakTag(const char *pszTag) +{ + char ch = *pszTag; + if (ch != 'w') + { /* likely */ } + else + return pszTag[1] == 'i' + && pszTag[2] == 'l' + && pszTag[3] == 'l' + && pszTag[4] == '-' + && pszTag[5] == 'l' + && pszTag[6] == 'e' + && pszTag[7] == 'a' + && pszTag[8] == 'k'; + if (ch != 'm') + return false; + return pszTag[1] == 'm' + && pszTag[2] == 'a' + && pszTag[3] == 'y' + && pszTag[4] == '-' + && pszTag[5] == 'l' + && pszTag[6] == 'e' + && pszTag[7] == 'a' + && pszTag[8] == 'k'; +} +#endif /* IPRT_WITH_GCC_SANITIZER */ + + +RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocTag(cb, pszTag); +} + + +RTDECL(void *) RTMemTmpAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocZTag(cb, pszTag); +} + + +RTDECL(void) RTMemTmpFree(void *pv) RT_NO_THROW_DEF +{ + RTMemFree(pv); +} + + +RTDECL(void) RTMemTmpFreeZ(void *pv, size_t cb) RT_NO_THROW_DEF +{ + RTMemFreeZ(pv, cb); +} + + +RTDECL(void *) RTMemAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ +#ifdef RTALLOC_USE_EFENCE + void *pv = rtR3MemAlloc("Alloc", RTMEMTYPE_RTMEMALLOC, cb, cb, pszTag, ASMReturnAddress(), NULL, 0, NULL); + +#else /* !RTALLOC_USE_EFENCE */ + + AssertMsg(cb, ("Allocating ZERO bytes is really not a good idea! Good luck with the next assertion!\n")); +# ifdef RTMEMALLOC_USE_TRACKER + void *pv = RTMemTrackerHdrAlloc(malloc(cb + sizeof(RTMEMTRACKERHDR)), cb, pszTag, ASMReturnAddress(), RTMEMTRACKERMETHOD_ALLOC); +# else + void *pv = malloc(cb); NOREF(pszTag); +# endif + AssertMsg(pv, ("malloc(%#zx) failed!!!\n", cb)); + AssertMsg( cb < RTMEM_ALIGNMENT + || !((uintptr_t)pv & (RTMEM_ALIGNMENT - 1)) + || ( (cb & RTMEM_ALIGNMENT) + ((uintptr_t)pv & RTMEM_ALIGNMENT)) == RTMEM_ALIGNMENT + , ("pv=%p RTMEM_ALIGNMENT=%#x\n", pv, RTMEM_ALIGNMENT)); +#endif /* !RTALLOC_USE_EFENCE */ +#ifdef IPRT_WITH_GCC_SANITIZER + if (rtMemIsLeakTag(pszTag)) + __lsan_ignore_object(pv); +#endif + return pv; +} + + +RTDECL(void *) RTMemAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ +#ifdef RTALLOC_USE_EFENCE + void *pv = rtR3MemAlloc("AllocZ", RTMEMTYPE_RTMEMALLOCZ, cb, cb, pszTag, ASMReturnAddress(), NULL, 0, NULL); + +#else /* !RTALLOC_USE_EFENCE */ + + AssertMsg(cb, ("Allocating ZERO bytes is really not a good idea! Good luck with the next assertion!\n")); + +# ifdef RTMEMALLOC_USE_TRACKER + void *pv = RTMemTrackerHdrAlloc(calloc(1, cb + sizeof(RTMEMTRACKERHDR)), cb, pszTag, ASMReturnAddress(), RTMEMTRACKERMETHOD_ALLOCZ); +#else + void *pv = calloc(1, cb); NOREF(pszTag); +#endif + AssertMsg(pv, ("calloc(1,%#zx) failed!!!\n", cb)); + AssertMsg( cb < RTMEM_ALIGNMENT + || !((uintptr_t)pv & (RTMEM_ALIGNMENT - 1)) + || ( (cb & RTMEM_ALIGNMENT) + ((uintptr_t)pv & RTMEM_ALIGNMENT)) == RTMEM_ALIGNMENT + , ("pv=%p RTMEM_ALIGNMENT=%#x\n", pv, RTMEM_ALIGNMENT)); +#endif /* !RTALLOC_USE_EFENCE */ +#ifdef IPRT_WITH_GCC_SANITIZER + if (rtMemIsLeakTag(pszTag)) + __lsan_ignore_object(pv); +#endif + return pv; +} + + +RTDECL(void *) RTMemAllocVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); +#ifdef RTALLOC_USE_EFENCE + void *pv = rtR3MemAlloc("AllocVar", RTMEMTYPE_RTMEMALLOC, cbUnaligned, cbAligned, pszTag, ASMReturnAddress(), NULL, 0, NULL); +#else + void *pv = RTMemAllocTag(cbAligned, pszTag); +#endif + return pv; +} + + +RTDECL(void *) RTMemAllocZVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); +#ifdef RTALLOC_USE_EFENCE + void *pv = rtR3MemAlloc("AllocZVar", RTMEMTYPE_RTMEMALLOCZ, cbUnaligned, cbAligned, pszTag, ASMReturnAddress(), NULL, 0, NULL); +#else + void *pv = RTMemAllocZTag(cbAligned, pszTag); +#endif + return pv; +} + + +RTDECL(void *) RTMemReallocTag(void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF +{ +#ifdef RTALLOC_USE_EFENCE + void *pv = rtR3MemRealloc("Realloc", RTMEMTYPE_RTMEMREALLOC, pvOld, cbNew, pszTag, ASMReturnAddress(), NULL, 0, NULL); + +#else /* !RTALLOC_USE_EFENCE */ +# ifdef RT_STRICT + const uintptr_t uOld = (uintptr_t)pvOld; /* overzealous gcc 12 complains it's used over realloc */ +# endif + +# ifdef RTMEMALLOC_USE_TRACKER + void *pvRealOld = RTMemTrackerHdrReallocPrep(pvOld, 0, pszTag, ASMReturnAddress()); + size_t cbRealNew = cbNew || !pvRealOld ? cbNew + sizeof(RTMEMTRACKERHDR) : 0; + void *pvNew = realloc(pvRealOld, cbRealNew); + void *pv = RTMemTrackerHdrReallocDone(pvNew, cbNew, pvOld, pszTag, ASMReturnAddress()); +# else + void *pv = realloc(pvOld, cbNew); NOREF(pszTag); +# endif + AssertMsg(pv || !cbNew, ("realloc(%p, %#zx) failed!!!\n", uOld, cbNew)); + AssertMsg( cbNew < RTMEM_ALIGNMENT + || !((uintptr_t)pv & (RTMEM_ALIGNMENT - 1)) + || ( (cbNew & RTMEM_ALIGNMENT) + ((uintptr_t)pv & RTMEM_ALIGNMENT)) == RTMEM_ALIGNMENT + , ("pv=%p RTMEM_ALIGNMENT=%#x\n", pv, RTMEM_ALIGNMENT)); +#endif /* !RTALLOC_USE_EFENCE */ + return pv; +} + + +RTDECL(void) RTMemFree(void *pv) RT_NO_THROW_DEF +{ + if (pv) +#ifdef RTALLOC_USE_EFENCE + rtR3MemFree("Free", RTMEMTYPE_RTMEMFREE, pv, 0, ASMReturnAddress(), NULL, 0, NULL); +#else +# ifdef RTMEMALLOC_USE_TRACKER + pv = RTMemTrackerHdrFree(pv, 0, NULL, ASMReturnAddress(), RTMEMTRACKERMETHOD_FREE); +# endif + free(pv); +#endif +} + + +RTDECL(void) RTMemFreeZ(void *pv, size_t cb) RT_NO_THROW_DEF +{ + if (pv) + { +#ifdef RTALLOC_USE_EFENCE + rtR3MemFree("Free", RTMEMTYPE_RTMEMFREEZ, pv, cb, ASMReturnAddress(), NULL, 0, NULL); +#else +# ifdef RTMEMALLOC_USE_TRACKER + pv = RTMemTrackerHdrFree(pv, cb, NULL, ASMReturnAddress(), RTMEMTRACKERMETHOD_FREE); +# endif + RT_BZERO(pv, cb); + free(pv); +#endif + } +} + + + +DECLHIDDEN(void *) rtMemBaseAlloc(size_t cb) +{ + Assert(cb > 0 && cb < _1M); + return malloc(cb); +} + + +DECLHIDDEN(void) rtMemBaseFree(void *pv) +{ + free(pv); +} + diff --git a/src/VBox/Runtime/r3/allocex.cpp b/src/VBox/Runtime/r3/allocex.cpp new file mode 100644 index 00000000..64eb19f9 --- /dev/null +++ b/src/VBox/Runtime/r3/allocex.cpp @@ -0,0 +1,152 @@ +/* $Id: allocex.cpp $ */ +/** @file + * IPRT - Memory Allocation, Extended Alloc and Free Functions for Ring-3, posix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define RTMEM_NO_WRAP_TO_EF_APIS +#include <iprt/mem.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include "internal/magics.h" +#include "allocex.h" + + +RTDECL(int) RTMemAllocExTag(size_t cb, size_t cbAlignment, uint32_t fFlags, const char *pszTag, void **ppv) RT_NO_THROW_DEF +{ + RT_NOREF_PV(pszTag); + + /* + * Validate and adjust input. + */ + AssertMsgReturn(!(fFlags & ~RTMEMALLOCEX_FLAGS_VALID_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + AssertReturn(cb > 0, VERR_INVALID_PARAMETER); + AssertReturn(RT_IS_POWER_OF_TWO(cbAlignment), VERR_INVALID_PARAMETER); + AssertMsgReturn(cbAlignment <= sizeof(void *), ("%zu (%#x)\n", cbAlignment, cbAlignment), VERR_UNSUPPORTED_ALIGNMENT); + + if (fFlags & RTMEMALLOCEX_FLAGS_ANY_CTX) + return VERR_NOT_SUPPORTED; + + /* + * Align the request. + */ + size_t cbAligned = cb; + if (cbAlignment) + cbAligned = RT_ALIGN_Z(cb, cbAlignment); + else + cbAligned = RT_ALIGN_Z(cb, sizeof(uint64_t)); + AssertMsgReturn(cbAligned >= cb && cbAligned <= ~(size_t)0, ("cbAligned=%#zx cb=%#zx", cbAligned, cb), + VERR_INVALID_PARAMETER); + + /* + * Allocate the requested memory. + */ + void *pv; + if (fFlags & (RTMEMALLOCEX_FLAGS_16BIT_REACH | RTMEMALLOCEX_FLAGS_32BIT_REACH)) + { + int rc; + if (fFlags & RTMEMALLOCEX_FLAGS_16BIT_REACH) + rc = rtMemAllocEx16BitReach(cbAligned + sizeof(RTMEMHDRR3), fFlags, &pv); + else + rc = rtMemAllocEx32BitReach(cbAligned + sizeof(RTMEMHDRR3), fFlags, &pv); + if (RT_FAILURE(rc)) + return rc; + } + else if (fFlags & RTMEMALLOCEX_FLAGS_EXEC) + { + pv = RTMemPageAlloc(cbAligned + sizeof(RTMEMHDRR3)); + if (pv) + { + if (fFlags & RTMEMALLOCEX_FLAGS_ZEROED) + RT_BZERO(pv, cbAligned + sizeof(RTMEMHDRR3)); + + int rc = RTMemProtect(pv, cbAligned + sizeof(RTMEMHDRR3), RTMEM_PROT_EXEC | RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_FAILURE(rc)) + { + RTMemPageFree(pv, cbAligned + sizeof(RTMEMHDRR3)); + return rc; + } + } + } + else if (fFlags & RTMEMALLOCEX_FLAGS_ZEROED) + pv = RTMemAllocZ(cbAligned + sizeof(RTMEMHDRR3)); + else + pv = RTMemAlloc(cbAligned + sizeof(RTMEMHDRR3)); + if (!pv) + return VERR_NO_MEMORY; + + /* + * Fill in the header and return. + */ + PRTMEMHDRR3 pHdr = (PRTMEMHDRR3)pv; + pHdr->u32Magic = RTMEMHDR_MAGIC; + pHdr->fFlags = fFlags; + pHdr->cb = (uint32_t)cbAligned; + pHdr->cbReq = (uint32_t)cb; + + *ppv = pHdr + 1; + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTMemAllocExTag); + + +RTDECL(void) RTMemFreeEx(void *pv, size_t cb) RT_NO_THROW_DEF +{ + if (!pv) + return; + AssertPtr(pv); + + PRTMEMHDRR3 pHdr = (PRTMEMHDRR3)pv - 1; + AssertMsg(pHdr->u32Magic == RTMEMHDR_MAGIC, ("pHdr->u32Magic=%RX32 pv=%p cb=%#x\n", pHdr->u32Magic, pv, cb)); + pHdr->u32Magic = RTMEMHDR_MAGIC_DEAD; + Assert(pHdr->cbReq == cb); RT_NOREF_PV(cb); + + if (pHdr->fFlags & (RTMEMALLOCEX_FLAGS_16BIT_REACH | RTMEMALLOCEX_FLAGS_32BIT_REACH)) + rtMemFreeExYyBitReach(pHdr, pHdr->cb + sizeof(*pHdr), pHdr->fFlags); + else if (pHdr->fFlags & RTMEMALLOCEX_FLAGS_EXEC) + { + RTMemProtect(pHdr, pHdr->cb + sizeof(*pHdr), RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemPageFree(pHdr, pHdr->cb + sizeof(*pHdr)); + } + else + RTMemFree(pHdr); +} +RT_EXPORT_SYMBOL(RTMemFreeEx); + diff --git a/src/VBox/Runtime/r3/allocex.h b/src/VBox/Runtime/r3/allocex.h new file mode 100644 index 00000000..91ab656d --- /dev/null +++ b/src/VBox/Runtime/r3/allocex.h @@ -0,0 +1,96 @@ +/* $Id: allocex.h $ */ +/** @file + * IPRT - Memory Allocation, Extended Alloc and Free Functions for Ring-3. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_allocex_h +#define IPRT_INCLUDED_SRC_r3_allocex_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> + +/** + * Heading for extended memory allocations in ring-3. + */ +typedef struct RTMEMHDRR3 +{ + /** Magic (RTMEMHDR_MAGIC). */ + uint32_t u32Magic; + /** Block flags (RTMEMALLOCEX_FLAGS_*). */ + uint32_t fFlags; + /** The actual size of the block, header not included. */ + uint32_t cb; + /** The requested allocation size. */ + uint32_t cbReq; +} RTMEMHDRR3; +/** Pointer to a ring-3 extended memory header. */ +typedef RTMEMHDRR3 *PRTMEMHDRR3; + + +/** + * Allocate memory in below 64KB. + * + * @returns IPRT status code. + * @param cbAlloc Number of bytes to allocate (including the + * header if the caller needs one). + * @param fFlags Allocation flags. + * @param ppv Where to return the pointer to the memory. + */ +DECLHIDDEN(int) rtMemAllocEx16BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv); + +/** + * Allocate memory in below 4GB. + * + * @returns IPRT status code. + * @param cbAlloc Number of bytes to allocate (including the + * header if the caller needs one). + * @param fFlags Allocation flags. + * @param ppv Where to return the pointer to the memory. + */ +DECLHIDDEN(int) rtMemAllocEx32BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv); + + +/** + * Frees memory allocated by rtMemAllocEx16BitReach and rtMemAllocEx32BitReach. + * + * @param pv Start of allocation. + * @param cb Allocation size. + * @param fFlags Allocation flags. + */ +DECLHIDDEN(void) rtMemFreeExYyBitReach(void *pv, size_t cb, uint32_t fFlags); + +#endif /* !IPRT_INCLUDED_SRC_r3_allocex_h */ + diff --git a/src/VBox/Runtime/r3/darwin/Makefile.kup b/src/VBox/Runtime/r3/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/Makefile.kup diff --git a/src/VBox/Runtime/r3/darwin/RTCrStoreCreateSnapshotById-darwin.cpp b/src/VBox/Runtime/r3/darwin/RTCrStoreCreateSnapshotById-darwin.cpp new file mode 100644 index 00000000..aa26ac2d --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/RTCrStoreCreateSnapshotById-darwin.cpp @@ -0,0 +1,268 @@ +/* $Id: RTCrStoreCreateSnapshotById-darwin.cpp $ */ +/** @file + * IPRT - RTCrStoreCreateSnapshotById, Darwin. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/crypto/store.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> + +/* HACK ALERT! Shut up those deprecated messages on SecKeychainSearchCreateFromAttributes and SecKeychainSearchCopyNext. */ +#include <CoreFoundation/CoreFoundation.h> +#undef DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER +#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER + +#include <Security/Security.h> + + +/** + * Checks the trust settings of the certificate. + * + * @returns true if not out-right distructed, otherwise false. + * @param hCert The certificate. + * @param enmTrustDomain The trust settings domain to check relative to. + */ +static bool rtCrStoreIsDarwinCertTrustworthy(SecCertificateRef hCert, SecTrustSettingsDomain enmTrustDomain) +{ + bool fResult = true; + CFArrayRef hTrustSettings; + OSStatus orc = SecTrustSettingsCopyTrustSettings(hCert, enmTrustDomain, &hTrustSettings); + if (orc == noErr) + { + CFIndex const cTrustSettings = CFArrayGetCount(hTrustSettings); + for (CFIndex i = 0; i < cTrustSettings; i++) + { + CFDictionaryRef hDict = (CFDictionaryRef)CFArrayGetValueAtIndex(hTrustSettings, i); + AssertContinue(CFGetTypeID(hDict) == CFDictionaryGetTypeID()); + + CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDict, kSecTrustSettingsResult); + if (hNum) + { + AssertContinue(CFGetTypeID(hNum) == CFNumberGetTypeID()); + SInt32 iNum; + if (CFNumberGetValue(hNum, kCFNumberSInt32Type, &iNum)) + { + if (iNum == kSecTrustSettingsResultDeny) + { + fResult = false; + break; + } + } + /* No need to release hNum (get rule). */ + } + /* No need to release hDict (get rule). */ + } + CFRelease(hTrustSettings); + } + else if (orc != errSecItemNotFound) + { + AssertFailed(); + fResult = false; + } + return fResult; +} + + +static int rtCrStoreAddCertsFromNativeKeychain(RTCRSTORE hStore, SecKeychainRef hKeychain, SecTrustSettingsDomain enmTrustDomain, + int rc, PRTERRINFO pErrInfo) +{ + /** @todo The SecKeychainSearchCreateFromAttributes and + * SecKeychainSearchCopyNext APIs have been officially deprecated since 10.7 + * according to the header files. However, the perferred API, + * SecItemCopyMatching (and possibly SecTrustCopyAnchorCertificates) would + * require a larger rewrite here and that's just not worth it right now. We can + * do that should these APIs be removed (unlikely given the amount of grep hits + * in the public 10.15.3 sources). */ + + /* + * Enumerate the certificates in the keychain. + */ + RT_GCC_NO_WARN_DEPRECATED_BEGIN + SecKeychainSearchRef hSearch; + OSStatus orc = SecKeychainSearchCreateFromAttributes(hKeychain, kSecCertificateItemClass, NULL, &hSearch); + if (orc == noErr) + { + SecKeychainItemRef hItem; + while ((orc = SecKeychainSearchCopyNext(hSearch, &hItem)) == noErr) + { + Assert(CFGetTypeID(hItem) == SecCertificateGetTypeID()); + SecCertificateRef hCert = (SecCertificateRef)hItem; + + /* + * Check if the current certificate is at all trusted, skip it if it's isn't. + */ + if (rtCrStoreIsDarwinCertTrustworthy(hCert, enmTrustDomain)) + { + /* + * Get the certificate data. + */ + CFDataRef hEncodedCert = SecCertificateCopyData(hCert); + Assert(hEncodedCert); + if (hEncodedCert) + { + CFIndex cbEncoded = CFDataGetLength(hEncodedCert); + const uint8_t *pbEncoded = CFDataGetBytePtr(hEncodedCert); + + RTERRINFOSTATIC StaticErrInfo; + int rc2 = RTCrStoreCertAddEncoded(hStore, RTCRCERTCTX_F_ENC_X509_DER | RTCRCERTCTX_F_ADD_IF_NOT_FOUND, + pbEncoded, cbEncoded, RTErrInfoInitStatic(&StaticErrInfo)); + if (RT_FAILURE(rc2)) + { + if (RTErrInfoIsSet(&StaticErrInfo.Core)) + RTErrInfoAddF(pErrInfo, -rc2, " %s", StaticErrInfo.Core.pszMsg); + else + RTErrInfoAddF(pErrInfo, -rc2, " %Rrc adding cert", rc2); + rc = -rc2; + } + + CFRelease(hEncodedCert); + } + } + + CFRelease(hItem); + } + if (orc != errSecItemNotFound) + rc = RTErrInfoAddF(pErrInfo, -VERR_SEARCH_ERROR, + " SecKeychainSearchCopyNext failed with %#x", orc); + CFRelease(hSearch); + } + else + rc = RTErrInfoAddF(pErrInfo, -VERR_SEARCH_ERROR, + " SecKeychainSearchCreateFromAttributes failed with %#x", orc); + RT_GCC_NO_WARN_DEPRECATED_END + return rc; +} + + +static int rtCrStoreAddCertsFromNativeKeychainFile(RTCRSTORE hStore, const char *pszKeychain, + SecTrustSettingsDomain enmTrustDomain, + int rc, PRTERRINFO pErrInfo) +{ + /* + * Open the keychain and call common worker to do the job. + */ + SecKeychainRef hKeychain; + OSStatus orc = SecKeychainOpen(pszKeychain, &hKeychain); + if (orc == noErr) + { + rc = rtCrStoreAddCertsFromNativeKeychain(hStore, hKeychain, enmTrustDomain, rc, pErrInfo); + + CFRelease(hKeychain); + } + else if (RTFileExists(pszKeychain)) + rc = RTErrInfoAddF(pErrInfo, -VERR_OPEN_FAILED, " SecKeychainOpen failed with %#x on '%s'", orc, pszKeychain); + return rc; +} + + +static int rtCrStoreAddCertsFromNativeKeystoreDomain(RTCRSTORE hStore, SecPreferencesDomain enmDomain, + SecTrustSettingsDomain enmTrustDomain, + int rc, PRTERRINFO pErrInfo) +{ + /* + * Get a list of keystores for this domain and call common worker on each. + */ + CFArrayRef hKeychains; + OSStatus orc = SecKeychainCopyDomainSearchList(enmDomain, &hKeychains); + if (orc == noErr) + { + CFIndex const cEntries = CFArrayGetCount(hKeychains); + for (CFIndex i = 0; i < cEntries; i++) + { + SecKeychainRef hKeychain = (SecKeychainRef)CFArrayGetValueAtIndex(hKeychains, i); + Assert(CFGetTypeID(hKeychain) == SecKeychainGetTypeID()); + CFRetain(hKeychain); + + rc = rtCrStoreAddCertsFromNativeKeychain(hStore, hKeychain, enmTrustDomain, rc, pErrInfo); + + CFRelease(hKeychain); + } + + CFRelease(hKeychains); + } + else + rc = RTErrInfoAddF(pErrInfo, -VERR_SEARCH_ERROR, + " SecKeychainCopyDomainSearchList failed with %#x on %d", orc, enmDomain); + return rc; +} + + +RTDECL(int) RTCrStoreCreateSnapshotById(PRTCRSTORE phStore, RTCRSTOREID enmStoreId, PRTERRINFO pErrInfo) +{ + AssertReturn(enmStoreId > RTCRSTOREID_INVALID && enmStoreId < RTCRSTOREID_END, VERR_INVALID_PARAMETER); + + /* + * Create an empty in-memory store. + */ + RTCRSTORE hStore; + int rc = RTCrStoreCreateInMem(&hStore, 128); + if (RT_SUCCESS(rc)) + { + *phStore = hStore; + + /* + * Load the certificates corresponding to the given virtual store ID. + */ + switch (enmStoreId) + { + case RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES: + rc = rtCrStoreAddCertsFromNativeKeystoreDomain(hStore, kSecPreferencesDomainUser, + kSecTrustSettingsDomainUser, rc, pErrInfo); + break; + + case RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES: + rc = rtCrStoreAddCertsFromNativeKeystoreDomain(hStore, kSecPreferencesDomainSystem, + kSecTrustSettingsDomainSystem, rc, pErrInfo); + rc = rtCrStoreAddCertsFromNativeKeychainFile(hStore, + "/System/Library/Keychains/SystemRootCertificates.keychain", + kSecTrustSettingsDomainSystem, rc, pErrInfo); + break; + + default: + AssertFailed(); /* implement me */ + } + } + else + RTErrInfoSet(pErrInfo, rc, "RTCrStoreCreateInMem failed"); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCreateSnapshotById); + diff --git a/src/VBox/Runtime/r3/darwin/RTFileQuerySectorSize-darwin.cpp b/src/VBox/Runtime/r3/darwin/RTFileQuerySectorSize-darwin.cpp new file mode 100644 index 00000000..bf246d48 --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/RTFileQuerySectorSize-darwin.cpp @@ -0,0 +1,89 @@ +/* $Id: RTFileQuerySectorSize-darwin.cpp $ */ +/** @file + * IPRT - RTFileQuerySectorSize, Darwin. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#include <sys/stat.h> +#include <sys/disk.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> + + +RTDECL(int) RTFileQuerySectorSize(RTFILE hFile, uint32_t *pcbSector) +{ + AssertPtrReturn(pcbSector, VERR_INVALID_PARAMETER); + + int rc; + int const fd = (int)RTFileToNative(hFile); + struct stat DevStat = { 0 }; + if (!fstat(fd, &DevStat)) + { + if (S_ISBLK(DevStat.st_mode) || S_ISCHR(DevStat.st_mode)) + { + uint32_t cbLogicalBlock = 0; + if (!ioctl(fd, DKIOCGETBLOCKSIZE, &cbLogicalBlock)) + { + AssertReturn(cbLogicalBlock > 0, VERR_INVALID_FUNCTION); + *pcbSector = cbLogicalBlock; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("ioctl failed: errno=%d / %Rrc\n", errno, rc)); + } + else + { + AssertMsgFailed(("not a block or character device.\n")); + rc = VERR_INVALID_FUNCTION; + } + } + else + { + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("fstat failed: errno=%d / %Rrc\n", errno, rc)); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/darwin/RTMpGetDescription-generic.cpp b/src/VBox/Runtime/r3/darwin/RTMpGetDescription-generic.cpp new file mode 100644 index 00000000..cf28744d --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/RTMpGetDescription-generic.cpp @@ -0,0 +1,173 @@ +/* $Id: RTMpGetDescription-generic.cpp $ */ +/** @file + * IPRT - Multiprocessor, RTMpGetDescription for darwin/arm. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/mp.h> +#include "internal/iprt.h" +#include <iprt/err.h> +#include <iprt/string.h> + +#include <sys/sysctl.h> +#if defined(RT_ARCH_ARM64) +# include <IOKit/IOKitLib.h> +#endif + + +RTDECL(int) RTMpGetDescription(RTCPUID idCpu, char *pszBuf, size_t cbBuf) +{ + /* + * Check that the specified cpu is valid & online. + */ + if (idCpu != NIL_RTCPUID && !RTMpIsCpuOnline(idCpu)) + return RTMpIsCpuPossible(idCpu) + ? VERR_CPU_OFFLINE + : VERR_CPU_NOT_FOUND; + + /* + * For ARM there are typically two different types of cores, so look up the + * processor in the IODeviceTree and get the core name and type from there + * if we can. + */ + char szExtra[256]; + size_t cchExtra = 0; + +#if defined(RT_ARCH_ARM64) + char szArmCpuPath[64]; + RTStrPrintf(szArmCpuPath, sizeof(szArmCpuPath), "IODeviceTree:/cpus/cpu%x", idCpu); /** @todo Hex? M1 Max only has 10 cores... */ + io_registry_entry_t hIoRegEntry = IORegistryEntryFromPath(kIOMasterPortDefault, szArmCpuPath); + if (hIoRegEntry != MACH_PORT_NULL) + { + /* This property is typically "E" or "P". Don't know why it's mapped + to a CFDataRef rather than a CFStringRef... */ + CFTypeRef hValRef = IORegistryEntryCreateCFProperty(hIoRegEntry, CFSTR("cluster-type"), kCFAllocatorDefault, kNilOptions); + if (hValRef) + { + if (CFGetTypeID(hValRef) == CFDataGetTypeID()) + { + size_t const cbData = CFDataGetLength((CFDataRef)hValRef); + uint8_t const * const pbData = (uint8_t const *)CFDataGetBytePtr((CFDataRef)hValRef); + if (cbData > 0 && pbData != NULL) + { + int rc = RTStrValidateEncodingEx((const char *)pbData, cbData, RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED); + AssertMsgRC(rc, ("%p LB %#zx: %.*Rhxs\n", pbData, cbData, cbData, pbData)); + if (RT_SUCCESS(rc)) + { + RTStrCopy(&szExtra[1], sizeof(szExtra) - 1, (const char *)pbData); + szExtra[0] = ' '; + cchExtra = strlen(szExtra); + } + } + } + else + AssertMsgFailed(("%p=%#lx\n", hValRef, CFGetTypeID(hValRef))); + + CFRelease(hValRef); + } + + /* The compatible property is an "array" of zero terminated strings. + For the M1 mini the first entry is either "apple,firestorm" (P cores) + or "apple,icestorm" (E cores). We extract the bits after the comma + and append it to the extra string. (Again, dunno why it's a CFDataRef.) */ + hValRef = IORegistryEntryCreateCFProperty(hIoRegEntry, CFSTR("compatible"), kCFAllocatorDefault, 0); + if (hValRef) + { + if (CFGetTypeID(hValRef) == CFDataGetTypeID()) + { + size_t const cbData = CFDataGetLength((CFDataRef)hValRef); + uint8_t const * const pbData = (uint8_t const *)CFDataGetBytePtr((CFDataRef)hValRef); + if (cbData > 0 && pbData != NULL) + { + Assert(pbData[cbData - 1] == '\0'); + if (pbData[cbData - 1] == '\0') + { + size_t offData = 0; + while (offData < cbData) + { + const char *psz = (const char *)&pbData[offData]; + size_t const cch = strlen(psz); + + if (RTStrStartsWith(psz, "apple,")) + { + psz += sizeof("apple,") - 1; + psz = RTStrStripL(psz); + if (*psz) + { + if (RTStrIsValidEncoding(psz)) + cchExtra += RTStrPrintf(&szExtra[cchExtra], sizeof(szExtra) - cchExtra, " (%s)", psz); + else + AssertFailed(); + } + } + + /* advance */ + offData += cch + 1; + } + } + } + } + else + AssertMsgFailed(("%p=%#lx\n", hValRef, CFGetTypeID(hValRef))); + CFRelease(hValRef); + } + + IOObjectRelease(hIoRegEntry); + } +#endif + szExtra[cchExtra] = '\0'; + + /* + * Just use the sysctl machdep.cpu.brand_string value for now. + */ + char szBrand[128] = {0}; + size_t cb = sizeof(szBrand); + int rc = sysctlbyname("machdep.cpu.brand_string", &szBrand, &cb, NULL, 0); + if (rc == -1) + szBrand[0] = '\0'; + + char *pszStripped = RTStrStrip(szBrand); + if (*pszStripped == '\0') + pszStripped = strcpy(szBrand, "Unknown"); + + rc = RTStrCopy(pszBuf, cbBuf, pszStripped); + if (cchExtra > 0 && RT_SUCCESS(rc)) + rc = RTStrCat(pszBuf, cbBuf, szExtra); + return rc; +} +RT_EXPORT_SYMBOL(RTMpGetDescription); + diff --git a/src/VBox/Runtime/r3/darwin/RTPathUserDocuments-darwin.cpp b/src/VBox/Runtime/r3/darwin/RTPathUserDocuments-darwin.cpp new file mode 100644 index 00000000..296c3857 --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/RTPathUserDocuments-darwin.cpp @@ -0,0 +1,123 @@ +/* $Id: RTPathUserDocuments-darwin.cpp $ */ +/** @file + * IPRT - RTPathUserDocuments, darwin ring-3. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/path.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 +# include <sysdir.h> +#else +# include <NSSystemDirectories.h> +#endif +#include <sys/syslimits.h> +#ifdef IPRT_USE_CORE_SERVICE_FOR_USER_DOCUMENTS +# include <CoreServices/CoreServices.h> +#endif + + +RTDECL(int) RTPathUserDocuments(char *pszPath, size_t cchPath) +{ + /* + * Validate input + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cchPath, VERR_INVALID_PARAMETER); + + /* + * Try NSSystemDirectories first since that works for directories that doesn't exist. + * The NSSystemDirectories API was renamed in 10.12 to sysdir. + */ + int rc = VERR_PATH_NOT_FOUND; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + sysdir_search_path_enumeration_state EnmState = sysdir_start_search_path_enumeration(SYSDIR_DIRECTORY_DOCUMENT, + SYSDIR_DOMAIN_MASK_USER); +#else + NSSearchPathEnumerationState EnmState = NSStartSearchPathEnumeration(NSDocumentDirectory, NSUserDomainMask); +#endif + if (EnmState != 0) + { + char szTmp[PATH_MAX]; + szTmp[0] = szTmp[PATH_MAX - 1] = '\0'; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + EnmState = sysdir_get_next_search_path_enumeration(EnmState, szTmp); +#else + EnmState = NSGetNextSearchPathEnumeration(EnmState, szTmp); +#endif + if (EnmState != 0) + { + size_t cchTmp = strlen(szTmp); + if (cchTmp >= cchPath) + return VERR_BUFFER_OVERFLOW; + + if (szTmp[0] == '~' && szTmp[1] == '/') + { + /* Expand tilde. */ + rc = RTPathUserHome(pszPath, cchPath - cchTmp + 2); + if (RT_FAILURE(rc)) + return rc; + rc = RTPathAppend(pszPath, cchPath, &szTmp[2]); + } + else + rc = RTStrCopy(pszPath, cchPath, szTmp); + return rc; + } + } + +#ifdef IPRT_USE_CORE_SERVICE_FOR_USER_DOCUMENTS + /* + * Fall back on FSFindFolder in case the above should fail... + */ + FSRef ref; + OSErr err = FSFindFolder(kOnAppropriateDisk, kDocumentsFolderType, false /* createFolder */, &ref); + if (err == noErr) + { + err = FSRefMakePath(&ref, (UInt8*)pszPath, cchPath); + if (err == noErr) + return VINF_SUCCESS; + } +#endif + Assert(RT_FAILURE_NP(rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/darwin/RTSystemQueryDmiString-darwin.cpp b/src/VBox/Runtime/r3/darwin/RTSystemQueryDmiString-darwin.cpp new file mode 100644 index 00000000..0c644229 --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/RTSystemQueryDmiString-darwin.cpp @@ -0,0 +1,163 @@ +/* $Id: RTSystemQueryDmiString-darwin.cpp $ */ +/** @file + * IPRT - RTSystemQueryDmiString, darwin ring-3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <mach/mach_port.h> +#include <IOKit/IOKitLib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define IOCLASS_PLATFORMEXPERTDEVICE "IOPlatformExpertDevice" +#define PROP_PRODUCT_NAME "product-name" +#define PROP_PRODUCT_VERSION "version" +#define PROP_PRODUCT_SERIAL "IOPlatformSerialNumber" +#define PROP_PRODUCT_UUID "IOPlatformUUID" +#define PROP_MANUFACTURER "manufacturer" + + +RTDECL(int) RTSystemQueryDmiString(RTSYSDMISTR enmString, char *pszBuf, size_t cbBuf) +{ + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER); + *pszBuf = '\0'; + AssertReturn(enmString > RTSYSDMISTR_INVALID && enmString < RTSYSDMISTR_END, VERR_INVALID_PARAMETER); + + CFStringRef PropStringRef = NULL; + switch (enmString) + { + case RTSYSDMISTR_PRODUCT_NAME: PropStringRef = CFSTR(PROP_PRODUCT_NAME); break; + case RTSYSDMISTR_PRODUCT_VERSION: PropStringRef = CFSTR(PROP_PRODUCT_VERSION); break; + case RTSYSDMISTR_PRODUCT_SERIAL: PropStringRef = CFSTR(PROP_PRODUCT_SERIAL); break; + case RTSYSDMISTR_PRODUCT_UUID: PropStringRef = CFSTR(PROP_PRODUCT_UUID); break; + case RTSYSDMISTR_MANUFACTURER: PropStringRef = CFSTR(PROP_MANUFACTURER); break; + default: + return VERR_NOT_SUPPORTED; + } + + mach_port_t MasterPort; + kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &MasterPort); + if (kr != kIOReturnSuccess) + { + if (kr == KERN_NO_ACCESS) + return VERR_ACCESS_DENIED; + return RTErrConvertFromDarwinIO(kr); + } + + CFDictionaryRef ClassToMatch = IOServiceMatching(IOCLASS_PLATFORMEXPERTDEVICE); + if (!ClassToMatch) + return VERR_NOT_SUPPORTED; + + /* IOServiceGetMatchingServices will always consume ClassToMatch. */ + io_iterator_t Iterator; + kr = IOServiceGetMatchingServices(MasterPort, ClassToMatch, &Iterator); + if (kr != kIOReturnSuccess) + return RTErrConvertFromDarwinIO(kr); + + int rc = VERR_NOT_SUPPORTED; + io_service_t ServiceObject; + while ((ServiceObject = IOIteratorNext(Iterator))) + { + if ( enmString == RTSYSDMISTR_PRODUCT_NAME + || enmString == RTSYSDMISTR_PRODUCT_VERSION + || enmString == RTSYSDMISTR_MANUFACTURER + ) + { + CFDataRef DataRef = (CFDataRef)IORegistryEntryCreateCFProperty(ServiceObject, PropStringRef, + kCFAllocatorDefault, kNilOptions); + if (DataRef) + { + size_t cbData = CFDataGetLength(DataRef); + const char *pchData = (const char *)CFDataGetBytePtr(DataRef); + rc = RTStrCopyEx(pszBuf, cbBuf, pchData, cbData); + CFRelease(DataRef); + break; + } + } + else + { + CFStringRef StringRef = (CFStringRef)IORegistryEntryCreateCFProperty(ServiceObject, PropStringRef, + kCFAllocatorDefault, kNilOptions); + if (StringRef) + { + Boolean fRc = CFStringGetCString(StringRef, pszBuf, cbBuf, kCFStringEncodingUTF8); + if (fRc) + rc = VINF_SUCCESS; + else + { + CFIndex cwc = CFStringGetLength(StringRef); + size_t cbTmp = cwc + 1; + char *pszTmp = (char *)RTMemTmpAlloc(cbTmp); + int cTries = 1; + while ( pszTmp + && (fRc = CFStringGetCString(StringRef, pszTmp, cbTmp, kCFStringEncodingUTF8)) == FALSE + && cTries++ < 4) + { + RTMemTmpFree(pszTmp); + cbTmp *= 2; + pszTmp = (char *)RTMemTmpAlloc(cbTmp); + } + if (fRc) + rc = RTStrCopy(pszBuf, cbBuf, pszTmp); + else if (!pszTmp) + rc = VERR_NO_TMP_MEMORY; + else + rc = VERR_ACCESS_DENIED; + RTMemFree(pszTmp); + } + CFRelease(StringRef); + break; + } + } + } + + IOObjectRelease(ServiceObject); + IOObjectRelease(Iterator); + return rc; +} + diff --git a/src/VBox/Runtime/r3/darwin/filelock-darwin.cpp b/src/VBox/Runtime/r3/darwin/filelock-darwin.cpp new file mode 100644 index 00000000..16d7137d --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/filelock-darwin.cpp @@ -0,0 +1,178 @@ +/* $Id: filelock-darwin.cpp $ */ +/** @file + * IPRT - File Locking, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE + +#include <errno.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/fcntl.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/time.h> + +#include <iprt/file.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/file.h" +#include "internal/fs.h" + + + + +RTR3DECL(int) RTFileLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* Check arguments. */ + if (fLock & ~RTFILE_LOCK_MASK) + { + AssertMsgFailed(("Invalid fLock=%08X\n", fLock)); + return VERR_INVALID_PARAMETER; + } + + /* + * Validate offset. + */ + if ( sizeof(off_t) < sizeof(cbLock) + && ( (offLock >> 32) != 0 + || (cbLock >> 32) != 0 + || ((offLock + cbLock) >> 32) != 0)) + { + AssertMsgFailed(("64-bit file i/o not supported! offLock=%lld cbLock=%lld\n", offLock, cbLock)); + return VERR_NOT_SUPPORTED; + } + + /* Prepare flock structure. */ + struct flock fl; + Assert(RTFILE_LOCK_WRITE); + fl.l_type = (fLock & RTFILE_LOCK_WRITE) ? F_WRLCK : F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_start = (off_t)offLock; + fl.l_len = (off_t)cbLock; + fl.l_pid = 0; + + Assert(RTFILE_LOCK_WAIT); + if (fcntl(RTFileToNative(hFile), (fLock & RTFILE_LOCK_WAIT) ? F_SETLKW : F_SETLK, &fl) >= 0) + return VINF_SUCCESS; + int iErr = errno; + if (iErr == ENOTSUP) + { + /* + * This is really bad hack for getting VDIs to work somewhat + * safely on SMB mounts. + */ + /** @todo we need to keep track of these locks really. Anyone requiring to lock more + * than one part of a file will have to fix this. */ + unsigned f = 0; + Assert(RTFILE_LOCK_WAIT); + if (fLock & RTFILE_LOCK_WAIT) + f |= LOCK_NB; + if (fLock & RTFILE_LOCK_WRITE) + f |= LOCK_EX; + else + f |= LOCK_SH; + if (!flock(RTFileToNative(hFile), f)) + return VINF_SUCCESS; + iErr = errno; + if (iErr == EWOULDBLOCK) + return VERR_FILE_LOCK_VIOLATION; + } + + if ( iErr == EAGAIN + || iErr == EACCES) + return VERR_FILE_LOCK_VIOLATION; + + return RTErrConvertFromErrno(iErr); +} + + +RTR3DECL(int) RTFileChangeLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + /** @todo We never returns VERR_FILE_NOT_LOCKED for now. */ + return RTFileLock(hFile, fLock, offLock, cbLock); +} + + +RTR3DECL(int) RTFileUnlock(RTFILE hFile, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* + * Validate offset. + */ + if ( sizeof(off_t) < sizeof(cbLock) + && ( (offLock >> 32) != 0 + || (cbLock >> 32) != 0 + || ((offLock + cbLock) >> 32) != 0)) + { + AssertMsgFailed(("64-bit file i/o not supported! offLock=%lld cbLock=%lld\n", offLock, cbLock)); + return VERR_NOT_SUPPORTED; + } + + /* Prepare flock structure. */ + struct flock fl; + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = (off_t)offLock; + fl.l_len = (off_t)cbLock; + fl.l_pid = 0; + + if (fcntl(RTFileToNative(hFile), F_SETLK, &fl) >= 0) + return VINF_SUCCESS; + + int iErr = errno; + if (iErr == ENOTSUP) + { + /* A SMB hack, see RTFileLock. */ + if (!flock(RTFileToNative(hFile), LOCK_UN)) + return VINF_SUCCESS; + } + + /** @todo check error codes for non existing lock. */ + if ( iErr == EAGAIN + || iErr == EACCES) + return VERR_FILE_LOCK_VIOLATION; + + return RTErrConvertFromErrno(iErr); +} + diff --git a/src/VBox/Runtime/r3/darwin/krnlmod-darwin.cpp b/src/VBox/Runtime/r3/darwin/krnlmod-darwin.cpp new file mode 100644 index 00000000..756786b0 --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/krnlmod-darwin.cpp @@ -0,0 +1,470 @@ +/* $Id: krnlmod-darwin.cpp $ */ +/** @file + * IPRT - Kernel module, Darwin. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <iprt/krnlmod.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/ldr.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/string.h> +#include <iprt/types.h> + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <libkern/OSReturn.h> + + +/** @name Missing/private IOKitLib declarations and definitions. + * @{ */ +/** OSKextCopyLoadedKextInfo in IOKit. */ +typedef CFDictionaryRef (* PFNOSKEXTCOPYLOADEDKEXTINFO)(CFArrayRef, CFArrayRef); +/** KextManagerLoadKextWithURL in IOKit. */ +typedef OSReturn (* PFNKEXTMANAGERLOADKEXTWITHURL)(CFURLRef, CFArrayRef); +/** KextManagerLoadKextWithIdentifier in IOKit */ +typedef OSReturn (* PFNKEXTMANAGERLOADKEXTWITHIDENTIFIER)(CFStringRef, CFArrayRef); +/** KextManagerUnloadKextWithIdentifier in IOKit */ +typedef OSReturn (* PFNKEXTMANAGERUNLOADKEXTWITHIDENTIFIER)(CFStringRef); + +#ifndef kOSBundleRetainCountKey +# define kOSBundleRetainCountKey CFSTR("OSBundleRetainCount") +#endif +#ifndef kOSBundleLoadSizeKey +# define kOSBundleLoadSizeKey CFSTR("OSBundleLoadSize") +#endif +#ifndef kOSBundleLoadAddressKey +# define kOSBundleLoadAddressKey CFSTR("OSBundleLoadAddress") +#endif +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Internal kernel information record state. + */ +typedef struct RTKRNLMODINFOINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** The dictionary containing our data. */ + CFDictionaryRef hDictKext; +} RTKRNLMODINFOINT; +/** Pointer to the internal kernel module information record. */ +typedef RTKRNLMODINFOINT *PRTKRNLMODINFOINT; +/** Pointer to a const internal kernel module information record. */ +typedef const RTKRNLMODINFOINT *PCRTKRNLMODINFOINT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTONCE g_GetIoKitApisOnce = RTONCE_INITIALIZER; +static PFNOSKEXTCOPYLOADEDKEXTINFO g_pfnOSKextCopyLoadedKextInfo = NULL; +static PFNKEXTMANAGERLOADKEXTWITHURL g_pfnKextManagerLoadKextWithUrl = NULL; +static PFNKEXTMANAGERLOADKEXTWITHIDENTIFIER g_pfnKextManagerLoadKextWithIdentifier = NULL; +static PFNKEXTMANAGERUNLOADKEXTWITHIDENTIFIER g_pfnKextManagerUnloadKextWithIdentifier = NULL; + +/** Do-once callback for setting g_pfnOSKextCopyLoadedKextInfo. */ +static DECLCALLBACK(int) rtKrnlModDarwinResolveIoKitApis(void *pvUser) +{ + RTLDRMOD hMod; +// int rc = RTLdrLoad("/System/Library/Frameworks/IOKit.framework/Versions/Current/IOKit", &hMod); + int rc = RTLdrLoadEx("/System/Library/Frameworks/IOKit.framework/Versions/Current/IOKit", &hMod, RTLDRLOAD_FLAGS_NO_SUFFIX, NULL); + if (RT_SUCCESS(rc)) + { + RTLdrGetSymbol(hMod, "OSKextCopyLoadedKextInfo", (void **)&g_pfnOSKextCopyLoadedKextInfo); + RTLdrGetSymbol(hMod, "KextManagerLoadKextWithURL", (void **)&g_pfnKextManagerLoadKextWithUrl); + RTLdrGetSymbol(hMod, "KextManagerLoadKextWithIdentifier", (void **)&g_pfnKextManagerLoadKextWithIdentifier); + RTLdrGetSymbol(hMod, "KextManagerUnloadKextWithIdentifier", (void **)&g_pfnKextManagerUnloadKextWithIdentifier); + } + + RT_NOREF(pvUser); + return VINF_SUCCESS; +} + +/** + * Returns the kext information dictionary structure matching the given name. + * + * @returns Pointer to the matching module information record on success or NULL if not found. + * @param pszName The name to look for. + */ +static CFDictionaryRef rtKrnlModDarwinGetKextInfoByName(const char *pszName) +{ + CFDictionaryRef hDictKext = NULL; + + RTOnce(&g_GetIoKitApisOnce, rtKrnlModDarwinResolveIoKitApis, NULL); + if (g_pfnOSKextCopyLoadedKextInfo) + { + CFStringRef hKextName = CFStringCreateWithCString(kCFAllocatorDefault, pszName, kCFStringEncodingUTF8); + if (hKextName) + { + CFArrayRef hArrKextIdRef = CFArrayCreate(kCFAllocatorDefault, (const void **)&hKextName, 1, &kCFTypeArrayCallBacks); + if (hArrKextIdRef) + { + CFDictionaryRef hLoadedKexts = g_pfnOSKextCopyLoadedKextInfo(hArrKextIdRef, NULL /* all info */); + if (hLoadedKexts) + { + if (CFDictionaryGetCount(hLoadedKexts) > 0) + { + hDictKext = (CFDictionaryRef)CFDictionaryGetValue(hLoadedKexts, hKextName); + CFRetain(hDictKext); + } + + CFRelease(hLoadedKexts); + } + CFRelease(hArrKextIdRef); + } + CFRelease(hKextName); + } + } + + return hDictKext; +} + +/** + * Destroy the given kernel module information record. + * + * @param pThis The record to destroy. + */ +static void rtKrnlModInfoDestroy(PRTKRNLMODINFOINT pThis) +{ + CFRelease(pThis->hDictKext); + RTMemFree(pThis); +} + + +RTDECL(int) RTKrnlModQueryLoaded(const char *pszName, bool *pfLoaded) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pfLoaded, VERR_INVALID_POINTER); + + CFDictionaryRef hDictKext = rtKrnlModDarwinGetKextInfoByName(pszName); + *pfLoaded = hDictKext != NULL; + if (hDictKext) + CFRelease(hDictKext); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfo(const char *pszName, PRTKRNLMODINFO phKrnlModInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(phKrnlModInfo, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + CFDictionaryRef hDictKext = rtKrnlModDarwinGetKextInfoByName(pszName); + if (hDictKext) + { + PRTKRNLMODINFOINT pThis = (PRTKRNLMODINFOINT)RTMemAllocZ(sizeof(RTKRNLMODINFOINT)); + if (pThis) + { + pThis->cRefs = 1; + pThis->hDictKext = hDictKext; + + *phKrnlModInfo = pThis; + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NOT_FOUND; + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModLoadedGetCount(void) +{ + uint32_t cLoadedKexts = 0; + RTOnce(&g_GetIoKitApisOnce, rtKrnlModDarwinResolveIoKitApis, NULL); + if (g_pfnOSKextCopyLoadedKextInfo) + { + CFDictionaryRef hLoadedKexts = g_pfnOSKextCopyLoadedKextInfo(NULL, NULL /* all info */); + if (hLoadedKexts) + { + cLoadedKexts = CFDictionaryGetCount(hLoadedKexts); + CFRelease(hLoadedKexts); + } + } + + return cLoadedKexts; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfoAll(PRTKRNLMODINFO pahKrnlModInfo, uint32_t cEntriesMax, + uint32_t *pcEntries) +{ + if (cEntriesMax > 0) + AssertPtrReturn(pahKrnlModInfo, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + RTOnce(&g_GetIoKitApisOnce, rtKrnlModDarwinResolveIoKitApis, NULL); + if (g_pfnOSKextCopyLoadedKextInfo) + { + CFDictionaryRef hLoadedKexts = g_pfnOSKextCopyLoadedKextInfo(NULL, NULL /* all info */); + if (hLoadedKexts) + { + uint32_t cLoadedKexts = CFDictionaryGetCount(hLoadedKexts); + if (cLoadedKexts <= cEntriesMax) + { + CFDictionaryRef *pahDictKext = (CFDictionaryRef *)RTMemTmpAllocZ(cLoadedKexts * sizeof(CFDictionaryRef)); + if (pahDictKext) + { + CFDictionaryGetKeysAndValues(hLoadedKexts, NULL, (const void **)pahDictKext); + for (uint32_t i = 0; i < cLoadedKexts; i++) + { + PRTKRNLMODINFOINT pThis = (PRTKRNLMODINFOINT)RTMemAllocZ(sizeof(RTKRNLMODINFOINT)); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->hDictKext = pahDictKext[i]; + CFRetain(pThis->hDictKext); + pahKrnlModInfo[i] = pThis; + } + else + { + rc = VERR_NO_MEMORY; + /* Rollback. */ + while (i-- > 0) + { + CFRelease(pahKrnlModInfo[i]->hDictKext); + RTMemFree(pahKrnlModInfo[i]); + } + } + } + + if ( RT_SUCCESS(rc) + && pcEntries) + *pcEntries = cLoadedKexts; + + RTMemTmpFree(pahDictKext); + } + else + rc = VERR_NO_MEMORY; + } + else + { + rc = VERR_BUFFER_OVERFLOW; + + if (pcEntries) + *pcEntries = cLoadedKexts; + } + + CFRelease(hLoadedKexts); + } + else + rc = VERR_NOT_SUPPORTED; + } + else + rc = VERR_NOT_SUPPORTED; + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModInfoRetain(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoRelease(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + if (!pThis) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtKrnlModInfoDestroy(pThis); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoGetRefCnt(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + uint32_t cRefCnt = 0; + CFNumberRef hRetainCnt = (CFNumberRef)CFDictionaryGetValue(pThis->hDictKext, + kOSBundleRetainCountKey); + if (hRetainCnt) + CFNumberGetValue(hRetainCnt, kCFNumberSInt32Type, &cRefCnt); + + return cRefCnt; +} + + +RTDECL(const char *) RTKrnlModInfoGetName(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + const char *pszName = NULL; + CFStringRef hBundleId = (CFStringRef)CFDictionaryGetValue(pThis->hDictKext, + kCFBundleIdentifierKey); + if (hBundleId) + pszName = CFStringGetCStringPtr(hBundleId, kCFStringEncodingUTF8); + + return pszName; +} + + +RTDECL(const char *) RTKrnlModInfoGetFilePath(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + return NULL; +} + + +RTDECL(size_t) RTKrnlModInfoGetSize(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + size_t cbKrnlMod = 0; + CFNumberRef hKrnlModSize = (CFNumberRef)CFDictionaryGetValue(pThis->hDictKext, + kOSBundleLoadSizeKey); + if (hKrnlModSize) + { + uint32_t cbTmp = 0; + CFNumberGetValue(hKrnlModSize, kCFNumberSInt32Type, &cbTmp); + cbKrnlMod = cbTmp; + } + + return cbKrnlMod; +} + + +RTDECL(RTR0UINTPTR) RTKrnlModInfoGetLoadAddr(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + RTR0UINTPTR uKrnlModLoadAddr = 0; + CFNumberRef hKrnlModLoadAddr = (CFNumberRef)CFDictionaryGetValue(pThis->hDictKext, + kOSBundleLoadAddressKey); + if (hKrnlModLoadAddr) + { + uint64_t uAddrTmp = 0; + CFNumberGetValue(hKrnlModLoadAddr, kCFNumberSInt64Type, &uAddrTmp); + uKrnlModLoadAddr = uAddrTmp; + } + + return uKrnlModLoadAddr; +} + + +RTDECL(int) RTKrnlModInfoQueryRefModInfo(RTKRNLMODINFO hKrnlModInfo, uint32_t idx, + PRTKRNLMODINFO phKrnlModInfoRef) +{ + RT_NOREF3(hKrnlModInfo, idx, phKrnlModInfoRef); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(int) RTKrnlModLoadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + RTOnce(&g_GetIoKitApisOnce, rtKrnlModDarwinResolveIoKitApis, NULL); + if (!g_pfnKextManagerLoadKextWithIdentifier) return VERR_NOT_SUPPORTED; + + int rc = VINF_SUCCESS; + CFStringRef hKextName = CFStringCreateWithCString(kCFAllocatorDefault, pszName, kCFStringEncodingUTF8); + if (hKextName) + { + OSReturn rcOsx = g_pfnKextManagerLoadKextWithIdentifier(hKextName, NULL /*dependencyKextAndFolderURLs*/); + if (rcOsx != kOSReturnSuccess) + rc = VERR_NOT_SUPPORTED; /** @todo Convert OSReturn values. */ + CFRelease(hKextName); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTKrnlModLoadByPath(const char *pszPath) +{ + AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModUnloadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + RTOnce(&g_GetIoKitApisOnce, rtKrnlModDarwinResolveIoKitApis, NULL); + if (!g_pfnKextManagerUnloadKextWithIdentifier) return VERR_NOT_SUPPORTED; + + int rc = VINF_SUCCESS; + CFStringRef hKextName = CFStringCreateWithCString(kCFAllocatorDefault, pszName, kCFStringEncodingUTF8); + if (hKextName) + { + OSReturn rcOsx = g_pfnKextManagerUnloadKextWithIdentifier(hKextName); + if (rcOsx != kOSReturnSuccess) + rc = VERR_NOT_SUPPORTED; /** @todo Convert OSReturn values. */ + CFRelease(hKextName); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} diff --git a/src/VBox/Runtime/r3/darwin/mp-darwin.cpp b/src/VBox/Runtime/r3/darwin/mp-darwin.cpp new file mode 100644 index 00000000..75c5433e --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/mp-darwin.cpp @@ -0,0 +1,435 @@ +/* $Id: mp-darwin.cpp $ */ +/** @file + * IPRT - Multiprocessor, Darwin. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT /*RTLOGGROUP_SYSTEM*/ +#include <iprt/types.h> + +#include <unistd.h> +#include <stdio.h> +#include <sys/sysctl.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <errno.h> +#include <mach/mach.h> + +#include <CoreFoundation/CFBase.h> +#include <IOKit/IOKitLib.h> +/*#include <IOKit/IOBSD.h> +#include <IOKit/storage/IOStorageDeviceCharacteristics.h> +#include <IOKit/storage/IOBlockStorageDevice.h> +#include <IOKit/storage/IOMedia.h> +#include <IOKit/storage/IOCDMedia.h> +#include <IOKit/scsi/SCSITaskLib.h> +#include <SystemConfiguration/SystemConfiguration.h> +#include <mach/mach_error.h> +#include <sys/param.h> +#include <paths.h>*/ + +#include <iprt/mp.h> +#include <iprt/assert.h> +#include <iprt/cpuset.h> +#include <iprt/log.h> +#include <iprt/string.h> + + +/** + * Internal worker that determines the max possible logical CPU count (hyperthreads). + * + * @returns Max cpus. + */ +static RTCPUID rtMpDarwinMaxLogicalCpus(void) +{ + int cCpus = -1; + size_t cb = sizeof(cCpus); + int rc = sysctlbyname("hw.logicalcpu_max", &cCpus, &cb, NULL, 0); + if (rc != -1 && cCpus >= 1) + return cCpus; + AssertFailed(); + return 1; +} + +/** + * Internal worker that determines the max possible physical core count. + * + * @returns Max cpus. + */ +static RTCPUID rtMpDarwinMaxPhysicalCpus(void) +{ + int cCpus = -1; + size_t cb = sizeof(cCpus); + int rc = sysctlbyname("hw.physicalcpu_max", &cCpus, &cb, NULL, 0); + if (rc != -1 && cCpus >= 1) + return cCpus; + AssertFailed(); + return 1; +} + + +#if 0 /* unused */ +/** + * Internal worker that determines the current number of logical CPUs (hyperthreads). + * + * @returns Max cpus. + */ +static RTCPUID rtMpDarwinOnlineLogicalCpus(void) +{ + int cCpus = -1; + size_t cb = sizeof(cCpus); + int rc = sysctlbyname("hw.logicalcpu", &cCpus, &cb, NULL, 0); + if (rc != -1 && cCpus >= 1) + return cCpus; + AssertFailed(); + return 1; +} +#endif /* unused */ + + +/** + * Internal worker that determines the current number of physical CPUs. + * + * @returns Max cpus. + */ +static RTCPUID rtMpDarwinOnlinePhysicalCpus(void) +{ + int cCpus = -1; + size_t cb = sizeof(cCpus); + int rc = sysctlbyname("hw.physicalcpu", &cCpus, &cb, NULL, 0); + if (rc != -1 && cCpus >= 1) + return cCpus; + AssertFailed(); + return 1; +} + + +#if defined(RT_ARCH_ARM64) +RTDECL(RTCPUID) RTMpCpuId(void) +{ + /* xnu-7195.50.7.100.1/osfmk/arm64/start.s and machine_routines.c sets TPIDRRO_EL0 + to the cpu_data_t::cpu_id value. */ + uint64_t u64Ret; + __asm__ __volatile__("mrs %0,TPIDRRO_EL0\n\t" : "=r" (u64Ret)); + return (RTCPUID)u64Ret; +} +#elif defined(RT_ARCH_ARM32) +RTDECL(RTCPUID) RTMpCpuId(void) +{ + /* xnu-7195.50.7.100.1/osfmk/arm/start.s and machine_routines.c sets TPIDRURO + to the cpu_data_t::cpu_id value. */ + uint32_t u32Ret; + __asm__ __volatile__("mrs p15, 0, %0, c13, c0, 3\n\t" : "=r" (u32Ret)); + return (RTCPUID)u32Ret; +} +#endif + + +RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu) +{ + return idCpu < RTCPUSET_MAX_CPUS && idCpu < rtMpDarwinMaxLogicalCpus() ? idCpu : -1; +} + + +RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu) +{ + return (unsigned)iCpu < rtMpDarwinMaxLogicalCpus() ? iCpu : NIL_RTCPUID; +} + + +RTDECL(RTCPUID) RTMpGetMaxCpuId(void) +{ + return rtMpDarwinMaxLogicalCpus() - 1; +} + + +RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu) +{ +#if 0 + return RTMpIsCpuPossible(idCpu); +#else + /** @todo proper ring-3 support on darwin, see @bugref{3014}. */ + natural_t cCpus; + processor_basic_info_t paInfo; + mach_msg_type_number_t cInfo; + kern_return_t krc = host_processor_info(mach_host_self(), PROCESSOR_BASIC_INFO, + &cCpus, (processor_info_array_t*)&paInfo, &cInfo); + AssertReturn(krc == KERN_SUCCESS, true); + bool const fIsOnline = idCpu < cCpus ? paInfo[idCpu].running : false; + vm_deallocate(mach_task_self(), (vm_address_t)paInfo, cInfo * sizeof(paInfo[0])); + return fIsOnline; +#endif +} + + +RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) +{ + return idCpu != NIL_RTCPUID + && idCpu < rtMpDarwinMaxLogicalCpus(); +} + + +RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet) +{ +#if 0 + RTCPUID cCpus = rtMpDarwinMaxLogicalCpus(); + return RTCpuSetFromU64(RT_BIT_64(cCpus) - 1); + +#else + RTCpuSetEmpty(pSet); + RTCPUID cMax = rtMpDarwinMaxLogicalCpus(); + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + if (RTMpIsCpuPossible(idCpu)) + RTCpuSetAdd(pSet, idCpu); + return pSet; +#endif +} + + +RTDECL(RTCPUID) RTMpGetCount(void) +{ + return rtMpDarwinMaxLogicalCpus(); +} + + +RTDECL(RTCPUID) RTMpGetCoreCount(void) +{ + return rtMpDarwinMaxPhysicalCpus(); +} + + +RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet) +{ + RTCpuSetEmpty(pSet); +#if 0 + RTCPUID cMax = rtMpDarwinMaxLogicalCpus(); + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + if (RTMpIsCpuOnline(idCpu)) + RTCpuSetAdd(pSet, idCpu); +#else + natural_t cCpus = 0; + processor_basic_info_t paInfo = NULL; + mach_msg_type_number_t cInfo = 0; + kern_return_t krc = host_processor_info(mach_host_self(), PROCESSOR_BASIC_INFO, + &cCpus, (processor_info_array_t *)&paInfo, &cInfo); + AssertReturn(krc == KERN_SUCCESS, pSet); + + AssertStmt(cCpus <= RTCPUSET_MAX_CPUS, cCpus = RTCPUSET_MAX_CPUS); + for (natural_t idCpu = 0; idCpu < cCpus; idCpu++) + if (paInfo[idCpu].running) + RTCpuSetAdd(pSet, idCpu); + + vm_deallocate(mach_task_self(), (vm_address_t)paInfo, cInfo * sizeof(paInfo[0])); +#endif + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetOnlineCount(void) +{ + RTCPUSET Set; + RTMpGetOnlineSet(&Set); + return RTCpuSetCount(&Set); +} + + +RTDECL(RTCPUID) RTMpGetOnlineCoreCount(void) +{ + return rtMpDarwinOnlinePhysicalCpus(); +} + + +RTDECL(uint32_t) RTMpGetCurFrequency(RTCPUID idCpu) +{ + /** @todo figure out how to get the current cpu speed on darwin. Have to + * check what powermanagement does. The powermetrics uses a private + * IOReportXxxx interface and *seems* (guessing) to calculate the frequency + * based on the frequency distribution over the last report period... This + * means that it's not really an suitable API for here. */ + NOREF(idCpu); + return 0; +} + + +/** + * Worker for RTMpGetMaxFrequency. + * @returns Non-zero frequency in MHz on success, 0 on failure. + */ +static uint32_t rtMpDarwinGetMaxFrequencyFromIOService(io_service_t hCpu) +{ + io_struct_inband_t Buf = {0}; + uint32_t cbActual = sizeof(Buf); + kern_return_t krc = IORegistryEntryGetProperty(hCpu, "clock-frequency", Buf, &cbActual); + Log2(("rtMpDarwinGetMaxFrequencyFromIOService: krc=%d; cbActual=%#x %.16Rhxs\n", krc, cbActual, Buf)); + if (krc == kIOReturnSuccess) + { + switch (cbActual) + { + case sizeof(uint32_t): + return RT_BE2H_U32(*(uint32_t *)Buf) / 1000; + case sizeof(uint64_t): + AssertFailed(); + return RT_BE2H_U64(*(uint64_t *)Buf) / 1000; + default: + AssertFailed(); + } + } + return 0; +} + + +RTDECL(uint32_t) RTMpGetMaxFrequency(RTCPUID idCpu) +{ + if (!RTMpIsCpuOnline(idCpu)) + return 0; + + /* + * Try the 'hw.cpufrequency_max' one. + */ + uint64_t CpuFrequencyMax = 0; + size_t cb = sizeof(CpuFrequencyMax); + int rc = sysctlbyname("hw.cpufrequency_max", &CpuFrequencyMax, &cb, NULL, 0); + if (!rc) + return (CpuFrequencyMax + 999999) / 1000000; + + /* + * Use the deprecated one. + */ + int aiMib[2]; + aiMib[0] = CTL_HW; + aiMib[1] = HW_CPU_FREQ; + int iDeprecatedFrequency = -1; + cb = sizeof(iDeprecatedFrequency); + rc = sysctl(aiMib, RT_ELEMENTS(aiMib), &iDeprecatedFrequency, &cb, NULL, 0); + if (rc != -1 && iDeprecatedFrequency >= 1) + return iDeprecatedFrequency; + + /* + * The above does not work for Apple M1 / xnu 20.1.0, so go look at the I/O registry instead. + * + * A sample ARM layout: + * | +-o cpu1@1 <class IOPlatformDevice, id 0x100000110, registered, matched, active, busy 0 (182 ms), retain 8> + * | | +-o AppleARMCPU <class AppleARMCPU, id 0x10000021b, registered, matched, active, busy 0 (1 ms), retain 6> + * | +-o cpu2@2 <class IOPlatformDevice, id 0x100000111, registered, matched, active, busy 0 (175 ms), retain 8> + * | | +-o AppleARMCPU <class AppleARMCPU, id 0x10000021c, registered, matched, active, busy 0 (3 ms), retain 6> + * | +-o cpu3@3 <class IOPlatformDevice, id 0x100000112, registered, matched, active, busy 0 (171 ms), retain 8> + * | | +-o AppleARMCPU <class AppleARMCPU, id 0x10000021d, registered, matched, active, busy 0 (1 ms), retain 6> + * | +-o cpu4@100 <class IOPlatformDevice, id 0x100000113, registered, matched, active, busy 0 (171 ms), retain 8> + * | | +-o AppleARMCPU <class AppleARMCPU, id 0x10000021e, registered, matched, active, busy 0 (1 ms), retain 6> + * | +-o cpu5@101 <class IOPlatformDevice, id 0x100000114, registered, matched, active, busy 0 (179 ms), retain 8> + * | | +-o AppleARMCPU <class AppleARMCPU, id 0x10000021f, registered, matched, active, busy 0 (9 ms), retain 6> + * | +-o cpu6@102 <class IOPlatformDevice, id 0x100000115, registered, matched, active, busy 0 (172 ms), retain 8> + * | | +-o AppleARMCPU <class AppleARMCPU, id 0x100000220, registered, matched, active, busy 0 (1 ms), retain 6> + * | +-o cpu7@103 <class IOPlatformDevice, id 0x100000116, registered, matched, active, busy 0 (175 ms), retain 8> + * | | +-o AppleARMCPU <class AppleARMCPU, id 0x100000221, registered, matched, active, busy 0 (5 ms), retain 6> + * | +-o cpus <class IOPlatformDevice, id 0x10000010e, registered, matched, active, busy 0 (12 ms), retain 15> + * + */ + +#if 1 /* simpler way to get at it inspired by powermetrics, this is also used + in the arm version of RTMpGetDescription. */ + /* Assume names on the form "cpu<N>" are only for CPUs. */ + char szCpuPath[64]; +# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + RTStrPrintf(szCpuPath, sizeof(szCpuPath), "IODeviceTree:/cpus/CPU%X", idCpu); +# else + RTStrPrintf(szCpuPath, sizeof(szCpuPath), "IODeviceTree:/cpus/cpu%x", idCpu); /** @todo Hex? M1 Max only has 10 cores... */ +# endif + io_registry_entry_t hIoRegEntry = IORegistryEntryFromPath(kIOMasterPortDefault, szCpuPath); + if (hIoRegEntry != MACH_PORT_NULL) + { + uint32_t uCpuFrequency = rtMpDarwinGetMaxFrequencyFromIOService(hIoRegEntry); + IOObjectRelease(hIoRegEntry); + if (uCpuFrequency) + return uCpuFrequency; + } + +#else + /* Assume names on the form "cpu<N>" are only for CPUs. */ + char szCpuName[32]; + RTStrPrintf(szCpuName, sizeof(szCpuName), "cpu%u", idCpu); + CFMutableDictionaryRef hMatchingDict = IOServiceNameMatching(szCpuName); + AssertReturn(hMatchingDict, 0); + + /* Just get the first one. */ + io_object_t hCpu = IOServiceGetMatchingService(kIOMasterPortDefault, hMatchingDict); + if (hCpu != 0) + { + uint32_t uCpuFrequency = rtMpDarwinGetMaxFrequencyFromIOService(hCpu); + IOObjectRelease(hCpu); + if (uCpuFrequency) + return uCpuFrequency; + } + +# if 1 /* Just in case... */ + /* Create a matching dictionary for searching for CPU services in the IOKit. */ +# if defined(RT_ARCH_ARM64) || defined(RT_ARCH_ARM32) + hMatchingDict = IOServiceMatching("AppleARMCPU"); +# elif defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + hMatchingDict = IOServiceMatching("AppleACPICPU"); +# else +# error "Port me!" +# endif + AssertReturn(hMatchingDict, 0); + + /* Perform the search and get a collection of Apple CPU services. */ + io_iterator_t hCpuServices = IO_OBJECT_NULL; + IOReturn irc = IOServiceGetMatchingServices(kIOMasterPortDefault, hMatchingDict, &hCpuServices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), 0); + hMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */ + + /* Enumerate the matching services. */ + uint32_t uCpuFrequency = 0; + io_object_t hCurCpu; + while (uCpuFrequency == 0 && (hCurCpu = IOIteratorNext(hCpuServices)) != IO_OBJECT_NULL) + { + io_object_t hParent = (io_object_t)0; + irc = IORegistryEntryGetParentEntry(hCurCpu, kIOServicePlane, &hParent); + if (irc == kIOReturnSuccess && hParent) + { + uCpuFrequency = rtMpDarwinGetMaxFrequencyFromIOService(hParent); + IOObjectRelease(hParent); + } + IOObjectRelease(hCurCpu); + } + IOObjectRelease(hCpuServices); +# endif +#endif + AssertFailed(); + return 0; +} + diff --git a/src/VBox/Runtime/r3/darwin/pathhost-darwin.cpp b/src/VBox/Runtime/r3/darwin/pathhost-darwin.cpp new file mode 100644 index 00000000..58bcd1ec --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/pathhost-darwin.cpp @@ -0,0 +1,117 @@ +/* $Id: pathhost-darwin.cpp $ */ +/** @file + * IPRT - Path Conversions, Darwin. + * + * On darwin path names on the disk are decomposed using normalization + * form D (NFD). Since this behavior is unique for the Mac, we will precompose + * the path name strings we get from the XNU kernel. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PATH +#include <iprt/assert.h> +#include <iprt/string.h> +#include "internal/iprt.h" + +#include "internal/path.h" + + +int rtPathToNative(const char **ppszNativePath, const char *pszPath, const char *pszBasePath) +{ + /** @todo We should decompose the string here, but the file system will do + * that for us if we don't, so why bother. */ + *ppszNativePath = (char *)pszPath; + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return VINF_SUCCESS; +} + + +void rtPathFreeNative(char const *pszNativePath, const char *pszPath) +{ + Assert(pszNativePath == pszPath || !pszNativePath); + NOREF(pszNativePath); + NOREF(pszPath); +} + + +int rtPathFromNative(char const **ppszPath, const char *pszNativePath, const char *pszBasePath) +{ + /** @todo We must compose the codepoints in the string here. We get file names + * in normalization form D so we'll end up with normalization form C + * whatever approach we take. */ + int rc = RTStrValidateEncodingEx(pszNativePath, RTSTR_MAX, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + *ppszPath = pszNativePath; + else + *ppszPath = NULL; + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return rc; +} + + +void rtPathFreeIprt(const char *pszPath, const char *pszNativePath) +{ + Assert(pszPath == pszNativePath || !pszPath); + NOREF(pszPath); NOREF(pszNativePath); +} + + +int rtPathFromNativeCopy(char *pszPath, size_t cbPath, const char *pszNativePath, const char *pszBasePath) +{ + /** @todo We must compose the codepoints in the string here. We get file names + * in normalization form D so we'll end up with normalization form C + * whatever approach we take. */ + int rc = RTStrValidateEncodingEx(pszNativePath, RTSTR_MAX, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + rc = RTStrCopyEx(pszPath, cbPath, pszNativePath, RTSTR_MAX); + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return rc; +} + + +int rtPathFromNativeDup(char **ppszPath, const char *pszNativePath, const char *pszBasePath) +{ + /** @todo We must compose the codepoints in the string here. We get file names + * in normalization form D so we'll end up with normalization form C + * whatever approach we take. */ + int rc = RTStrValidateEncodingEx(pszNativePath, RTSTR_MAX, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + rc = RTStrDupEx(ppszPath, pszNativePath); + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return rc; +} + diff --git a/src/VBox/Runtime/r3/darwin/rtProcInitExePath-darwin.cpp b/src/VBox/Runtime/r3/darwin/rtProcInitExePath-darwin.cpp new file mode 100644 index 00000000..af6116eb --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/rtProcInitExePath-darwin.cpp @@ -0,0 +1,76 @@ +/* $Id: rtProcInitExePath-darwin.cpp $ */ +/** @file + * IPRT - rtProcInitName, Darwin. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#ifdef RT_OS_DARWIN +# include <mach-o/dyld.h> +#endif + +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include "internal/process.h" +#include "internal/path.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ + /* + * Query the image name from the dynamic linker, convert and return it. + */ + const char *pszImageName = _dyld_get_image_name(0); + AssertReturn(pszImageName, VERR_INTERNAL_ERROR); + + char szTmpPath[PATH_MAX + 1]; + const char *psz = realpath(pszImageName, szTmpPath); + int rc; + if (psz) + rc = rtPathFromNativeCopy(pszPath, cchPath, szTmpPath, NULL); + else + rc = RTErrConvertFromErrno(errno); + AssertMsgRCReturn(rc, ("rc=%Rrc pszLink=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, strlen(pszImageName), pszPath), rc); + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/darwin/sched-darwin.cpp b/src/VBox/Runtime/r3/darwin/sched-darwin.cpp new file mode 100644 index 00000000..3e819cb0 --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/sched-darwin.cpp @@ -0,0 +1,363 @@ +/* $Id: sched-darwin.cpp $ */ +/** @file + * IPRT - Scheduling, Darwin. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <mach/thread_act.h> +#include <mach/thread_policy.h> +#include <mach/thread_info.h> +#include <mach/host_info.h> +#include <mach/mach_init.h> +#include <mach/mach_host.h> +#include <sched.h> +#include <pthread.h> +#include <limits.h> +#include <errno.h> + +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include "internal/sched.h" +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Configuration of one priority. + */ +typedef struct +{ + /** The priority. */ + RTPROCPRIORITY enmPriority; + /** The name of this priority. */ + const char *pszName; + /** Array scheduler attributes corresponding to each of the thread types. */ + struct + { + /** For sanity include the array index. */ + RTTHREADTYPE enmType; + /** The desired mach base_priority value. */ + int iBasePriority; + /** The suggested priority value. (Same as iBasePriority seems to do the + * trick.) */ + int iPriority; + } aTypes[RTTHREADTYPE_END]; +} PROCPRIORITY; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Array of static priority configurations. + * + * ASSUMES that pthread_setschedparam takes a sched_priority argument in the + * range 0..127, which is translated into mach base_priority 0..63 and mach + * importance -31..32 (among other things). We also ASSUMES SCHED_OTHER. + * + * The base_priority range can be checked with tstDarwinSched, we're assuming it's + * 0..63 for user processes. + * + * Further we observe that fseventsd and mds both run at (mach) priority 50, + * while Finder runs at 47. At priority 63 we find the dynamic pager, the login + * window, UserEventAgent, SystemUIServer and coreaudiod. We do not wish to upset the + * dynamic pager, UI or audio, but we wish for I/O to not be bothered by spotlight + * (mds/fseventsd). + */ +static const PROCPRIORITY g_aPriorities[] = +{ + { + RTPROCPRIORITY_DEFAULT, "Default", + { + { RTTHREADTYPE_INVALID, INT_MIN, INT_MIN }, + { RTTHREADTYPE_INFREQUENT_POLLER, 29, 29 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 30, 30 }, + { RTTHREADTYPE_EMULATION, 31, 31 }, /* the default priority */ + { RTTHREADTYPE_DEFAULT, 32, 32 }, + { RTTHREADTYPE_GUI, 32, 32 }, + { RTTHREADTYPE_MAIN_WORKER, 32, 32 }, + { RTTHREADTYPE_VRDP_IO, 39, 39 }, + { RTTHREADTYPE_DEBUGGER, 42, 42 }, + { RTTHREADTYPE_MSG_PUMP, 47, 47 }, + { RTTHREADTYPE_IO, 52, 52 }, + { RTTHREADTYPE_TIMER, 55, 55 } + } + }, + { + RTPROCPRIORITY_LOW, "Low", + { + { RTTHREADTYPE_INVALID, INT_MIN, INT_MIN }, + { RTTHREADTYPE_INFREQUENT_POLLER, 20, 20 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 22, 22 }, + { RTTHREADTYPE_EMULATION, 24, 24 }, + { RTTHREADTYPE_DEFAULT, 28, 28 }, + { RTTHREADTYPE_GUI, 29, 29 }, + { RTTHREADTYPE_MAIN_WORKER, 30, 30 }, + { RTTHREADTYPE_VRDP_IO, 31, 31 }, + { RTTHREADTYPE_DEBUGGER, 31, 31 }, + { RTTHREADTYPE_MSG_PUMP, 31, 31 }, + { RTTHREADTYPE_IO, 31, 31 }, + { RTTHREADTYPE_TIMER, 31, 31 } + } + }, + { + RTPROCPRIORITY_NORMAL, "Normal", + { + { RTTHREADTYPE_INVALID, INT_MIN, INT_MIN }, + { RTTHREADTYPE_INFREQUENT_POLLER, 29, 29 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 30, 30 }, + { RTTHREADTYPE_EMULATION, 31, 31 }, /* the default priority */ + { RTTHREADTYPE_DEFAULT, 32, 32 }, + { RTTHREADTYPE_GUI, 32, 32 }, + { RTTHREADTYPE_MAIN_WORKER, 32, 32 }, + { RTTHREADTYPE_VRDP_IO, 39, 39 }, + { RTTHREADTYPE_DEBUGGER, 42, 42 }, + { RTTHREADTYPE_MSG_PUMP, 47, 47 }, + { RTTHREADTYPE_IO, 52, 52 }, + { RTTHREADTYPE_TIMER, 55, 55 } + } + }, + { + RTPROCPRIORITY_HIGH, "High", + { + { RTTHREADTYPE_INVALID, INT_MIN, INT_MIN }, + { RTTHREADTYPE_INFREQUENT_POLLER, 30, 30 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 31, 31 }, + { RTTHREADTYPE_EMULATION, 32, 32 }, + { RTTHREADTYPE_DEFAULT, 40, 40 }, + { RTTHREADTYPE_GUI, 41, 41 }, + { RTTHREADTYPE_MAIN_WORKER, 43, 43 }, + { RTTHREADTYPE_VRDP_IO, 45, 45 }, + { RTTHREADTYPE_DEBUGGER, 47, 47 }, + { RTTHREADTYPE_MSG_PUMP, 49, 49 }, + { RTTHREADTYPE_IO, 57, 57 }, + { RTTHREADTYPE_TIMER, 61, 61 } + } + }, + /* last */ + { + RTPROCPRIORITY_FLAT, "Flat", + { + { RTTHREADTYPE_INVALID, INT_MIN, INT_MIN }, + { RTTHREADTYPE_INFREQUENT_POLLER, 31, 31 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 31, 31 }, + { RTTHREADTYPE_EMULATION, 31, 31 }, + { RTTHREADTYPE_DEFAULT, 31, 31 }, + { RTTHREADTYPE_GUI, 31, 31 }, + { RTTHREADTYPE_MAIN_WORKER, 31, 31 }, + { RTTHREADTYPE_VRDP_IO, 31, 31 }, + { RTTHREADTYPE_DEBUGGER, 31, 31 }, + { RTTHREADTYPE_MSG_PUMP, 31, 31 }, + { RTTHREADTYPE_IO, 31, 31 }, + { RTTHREADTYPE_TIMER, 31, 31 } + } + }, +}; + + +/** + * The dynamic default priority configuration. + * + * This can be recalulated at runtime depending on what the + * system allow us to do. Presently we don't do this as it seems + * Darwin generally lets us do whatever we want. + * + * @remarks this is the same as "Normal" above. + */ +static PROCPRIORITY g_aDefaultPriority = +{ + RTPROCPRIORITY_DEFAULT, "Default", + { + { RTTHREADTYPE_INVALID, INT_MIN, INT_MIN }, + { RTTHREADTYPE_INFREQUENT_POLLER, 29, 29 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 30, 30 }, + { RTTHREADTYPE_EMULATION, 31, 31 }, /* the default priority */ + { RTTHREADTYPE_DEFAULT, 32, 32 }, + { RTTHREADTYPE_GUI, 32, 32 }, + { RTTHREADTYPE_MAIN_WORKER, 32, 32 }, + { RTTHREADTYPE_VRDP_IO, 39, 39 }, + { RTTHREADTYPE_DEBUGGER, 42, 42 }, + { RTTHREADTYPE_MSG_PUMP, 47, 47 }, + { RTTHREADTYPE_IO, 52, 52 }, + { RTTHREADTYPE_TIMER, 55, 55 } + } +}; + + +/** Pointer to the current priority configuration. */ +static const PROCPRIORITY *g_pProcessPriority = &g_aDefaultPriority; + + +/** + * Get's the priority information for the current thread. + * + * @returns The base priority + * @param pThread The thread to get it for. NULL for current. + */ +static int rtSchedDarwinGetBasePriority(PRTTHREADINT pThread) +{ + /* the base_priority. */ + mach_msg_type_number_t Count = POLICY_TIMESHARE_INFO_COUNT; + struct policy_timeshare_info TSInfo = {0,0,0,0,0}; + kern_return_t krc = thread_info(!pThread ? mach_thread_self() : pthread_mach_thread_np((pthread_t)pThread->Core.Key), + THREAD_SCHED_TIMESHARE_INFO, (thread_info_t)&TSInfo, &Count); + Assert(krc == KERN_SUCCESS); NOREF(krc); + + return TSInfo.base_priority; +} + + +DECLHIDDEN(int) rtSchedNativeCalcDefaultPriority(RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + + /* + * Get the current priority. + */ + int iBasePriority = rtSchedDarwinGetBasePriority(NULL); + Assert(iBasePriority >= 0 && iBasePriority <= 63); + + /* + * If it doesn't match the default, select the closest one from the table. + */ + int offBest = RT_ABS(g_pProcessPriority->aTypes[enmType].iBasePriority - iBasePriority); + if (offBest) + { + for (unsigned i = 0; i < RT_ELEMENTS(g_aPriorities); i++) + { + int off = RT_ABS(g_aPriorities[i].aTypes[enmType].iBasePriority - iBasePriority); + if (off < offBest) + { + g_pProcessPriority = &g_aPriorities[i]; + if (!off) + break; + offBest = off; + } + } + } + + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) rtProcNativeSetPriority(RTPROCPRIORITY enmPriority) +{ + Assert(enmPriority > RTPROCPRIORITY_INVALID && enmPriority < RTPROCPRIORITY_LAST); + + /* + * No checks necessary, we assume we can set any priority in the user process range. + */ + const PROCPRIORITY *pProcessPriority = &g_aDefaultPriority; + for (unsigned i = 0; i < RT_ELEMENTS(g_aPriorities); i++) + if (g_aPriorities[i].enmPriority == enmPriority) + { + pProcessPriority = &g_aPriorities[i]; + break; + } + Assert(pProcessPriority != &g_aDefaultPriority); + ASMAtomicUoWritePtr(&g_pProcessPriority, pProcessPriority); + + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) rtThreadNativeSetPriority(PRTTHREADINT pThread, RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + AssertMsg(g_pProcessPriority && g_pProcessPriority->aTypes[enmType].enmType == enmType, + ("enmType=%d entry=%d\n", enmType, g_pProcessPriority->aTypes[enmType].enmType)); + + /* + * Get the current policy and params first since there are + * opaque members in the param structure and we don't wish to + * change the policy. + */ + int iSchedPolicy = SCHED_OTHER; + struct sched_param SchedParam = {0, {0,0,0,0} }; + int err = pthread_getschedparam((pthread_t)pThread->Core.Key, &iSchedPolicy, &SchedParam); + if (!err) + { + int const iDesiredBasePriority = g_pProcessPriority->aTypes[enmType].iBasePriority; + int iPriority = g_pProcessPriority->aTypes[enmType].iPriority; + + /* + * First try with the given pthread priority number. + * Then make adjustments in case we missed the desired base priority (interface + * changed or whatever - its using an obsolete mach api). + */ + SchedParam.sched_priority = iPriority; + err = pthread_setschedparam((pthread_t)pThread->Core.Key, iSchedPolicy, &SchedParam); + if (!err) + { + int i = 0; + int iBasePriority = rtSchedDarwinGetBasePriority(pThread); + + while ( !err + && iBasePriority < iDesiredBasePriority + && i++ < 256) + { + SchedParam.sched_priority = ++iPriority; + err = pthread_setschedparam((pthread_t)pThread->Core.Key, iSchedPolicy, &SchedParam); + iBasePriority = rtSchedDarwinGetBasePriority(pThread); + } + + while ( !err + && iPriority > 0 + && iBasePriority > iDesiredBasePriority + && i++ < 256) + { + SchedParam.sched_priority = --iPriority; + err = pthread_setschedparam((pthread_t)pThread->Core.Key, iSchedPolicy, &SchedParam); + iBasePriority = rtSchedDarwinGetBasePriority(pThread); + } + + return VINF_SUCCESS; + } + } + int rc = RTErrConvertFromErrno(err); + AssertMsgRC(rc, ("rc=%Rrc err=%d iSchedPolicy=%d sched_priority=%d\n", + rc, err, iSchedPolicy, SchedParam.sched_priority)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/darwin/systemmem-darwin.cpp b/src/VBox/Runtime/r3/darwin/systemmem-darwin.cpp new file mode 100644 index 00000000..8c389d27 --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/systemmem-darwin.cpp @@ -0,0 +1,96 @@ +/* $Id: systemmem-darwin.cpp $ */ +/** @file + * IPRT - RTSystemQueryTotalRam, darwin ring-3. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> + +#include <errno.h> +#include <unistd.h> +#include <sys/sysctl.h> +#include <sys/stat.h> +#include <mach/mach.h> + + + +RTDECL(int) RTSystemQueryTotalRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + int aiMib[2]; + aiMib[0] = CTL_HW; + aiMib[1] = HW_MEMSIZE; + uint64_t cbPhysMem = UINT64_MAX; + size_t cb = sizeof(cbPhysMem); + int rc = sysctl(aiMib, RT_ELEMENTS(aiMib), &cbPhysMem, &cb, NULL, 0); + if (rc == 0) + { + *pcb = cbPhysMem; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTSystemQueryAvailableRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + static mach_port_t volatile s_hSelfPort = 0; + mach_port_t hSelfPort = s_hSelfPort; + if (hSelfPort == 0) + s_hSelfPort = hSelfPort = mach_host_self(); + + vm_statistics_data_t VmStats; + mach_msg_type_number_t cItems = sizeof(VmStats) / sizeof(natural_t); + + kern_return_t krc = host_statistics(hSelfPort, HOST_VM_INFO, (host_info_t)&VmStats, &cItems); + if (krc == KERN_SUCCESS) + { + uint64_t cPages = VmStats.inactive_count; + cPages += VmStats.free_count; + *pcb = cPages * PAGE_SIZE; + return VINF_SUCCESS; + } + return RTErrConvertFromDarwin(krc); +} + diff --git a/src/VBox/Runtime/r3/darwin/time-darwin.cpp b/src/VBox/Runtime/r3/darwin/time-darwin.cpp new file mode 100644 index 00000000..bc18badd --- /dev/null +++ b/src/VBox/Runtime/r3/darwin/time-darwin.cpp @@ -0,0 +1,125 @@ +/* $Id: time-darwin.cpp $ */ +/** @file + * IPRT - Time, Darwin. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define RTTIME_INCL_TIMEVAL +#include <mach/mach_time.h> +#include <mach/kern_return.h> +#include <sys/time.h> +#include <time.h> + +#include <iprt/time.h> +#include <iprt/assert.h> +#include "internal/time.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static struct mach_timebase_info g_Info = { 0, 0 }; +static double g_rdFactor = 0.0; +static bool g_fFailedToGetTimeBaseInfo = false; + + +/** + * Perform lazy init (pray we're not racing anyone in a bad way). + */ +static void rtTimeDarwinLazyInit(void) +{ + struct mach_timebase_info Info; + if (mach_timebase_info(&Info) == KERN_SUCCESS) + { + g_rdFactor = (double)Info.numer / (double)Info.denom; + g_Info = Info; + } + else + { + g_fFailedToGetTimeBaseInfo = true; + Assert(g_Info.denom == 0 && g_Info.numer == 0 && g_rdFactor == 0.0); + } +} + + +/** + * Internal worker. + * @returns Nanosecond timestamp. + */ +DECLINLINE(uint64_t) rtTimeGetSystemNanoTS(void) +{ + /* Lazy init. */ + if (RT_UNLIKELY(g_Info.denom == 0 && !g_fFailedToGetTimeBaseInfo)) + rtTimeDarwinLazyInit(); + + /* special case: absolute time is in nanoseconds */ + if (g_Info.denom == 1 && g_Info.numer == 1) + return mach_absolute_time(); + + /* general case: multiply by factor to get nanoseconds. */ + if (g_rdFactor != 0.0) + return mach_absolute_time() * g_rdFactor; + + /* worst case: fallback to gettimeofday(). */ + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * RT_NS_1SEC_64 + + (uint64_t)(tv.tv_usec * RT_NS_1US); +} + + +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return rtTimeGetSystemNanoTS(); +} + + +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return rtTimeGetSystemNanoTS() / RT_NS_1MS; +} + + +RTDECL(PRTTIMESPEC) RTTimeNow(PRTTIMESPEC pTime) +{ + /** @todo find nanosecond API for getting time of day. */ + struct timeval tv; + gettimeofday(&tv, NULL); + return RTTimeSpecSetTimeval(pTime, &tv); +} + diff --git a/src/VBox/Runtime/r3/dir.cpp b/src/VBox/Runtime/r3/dir.cpp new file mode 100644 index 00000000..02058632 --- /dev/null +++ b/src/VBox/Runtime/r3/dir.cpp @@ -0,0 +1,917 @@ +/* $Id: dir.cpp $ */ +/** @file + * IPRT - Directory Manipulation, Part 1. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/dir.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/uni.h> +#define RTDIR_AGNOSTIC +#include "internal/dir.h" +#include "internal/path.h" + + +static DECLCALLBACK(bool) rtDirFilterWinNtMatch(PRTDIRINTERNAL pDir, const char *pszName); +static DECLCALLBACK(bool) rtDirFilterWinNtMatchNoWildcards(PRTDIRINTERNAL pDir, const char *pszName); +DECLINLINE(bool) rtDirFilterWinNtMatchEon(PCRTUNICP puszFilter); +static bool rtDirFilterWinNtMatchDosStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter); +static bool rtDirFilterWinNtMatchStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter); +static bool rtDirFilterWinNtMatchBase(unsigned iDepth, const char *pszName, PCRTUNICP puszFilter); + + + +RTDECL(int) RTDirCreateFullPath(const char *pszPath, RTFMODE fMode) +{ + return RTDirCreateFullPathEx(pszPath, fMode, 0); +} + + +RTDECL(int) RTDirCreateFullPathEx(const char *pszPath, RTFMODE fMode, uint32_t fFlags) +{ + /* + * Resolve the path. + */ + char *pszAbsPath = RTPathAbsDup(pszPath); + if (!pszAbsPath) + return VERR_NO_TMP_MEMORY; + + /* + * Iterate the path components making sure each of them exists. + */ + /* skip volume name */ + char *psz = &pszAbsPath[rtPathVolumeSpecLen(pszAbsPath)]; + + /* skip the root slash if any */ + if (RTPATH_IS_SLASH(*psz)) + psz++; + + /* iterate over path components. */ + int rc = VINF_SUCCESS; + do + { + /* the next component is NULL, stop iterating */ + if (!*psz) + break; +#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS + char *psz2 = strchr(psz, '/'); + psz = strchr(psz, RTPATH_SLASH); + if (psz2 && (!psz || (uintptr_t)psz2 < (uintptr_t)psz)) + psz = psz; +#else + psz = strchr(psz, RTPATH_SLASH); +#endif + if (psz) + *psz = '\0'; + + /* + * ASSUME that RTDirCreate will return VERR_ALREADY_EXISTS and not VERR_ACCESS_DENIED in those cases + * where the directory exists but we don't have write access to the parent directory. + */ + rc = RTDirCreate(pszAbsPath, fMode, fFlags); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + + if (!psz) + break; + *psz++ = RTPATH_DELIMITER; + } while (RT_SUCCESS(rc)); + + RTStrFree(pszAbsPath); + return rc; +} + + +/** + * Filter a the filename in the against a filter. + * + * @returns true if the name matches the filter. + * @returns false if the name doesn't match filter. + * @param pDir The directory handle. + * @param pszName The path to match to the filter. + */ +static DECLCALLBACK(bool) rtDirFilterWinNtMatchNoWildcards(PRTDIRINTERNAL pDir, const char *pszName) +{ + /* + * Walk the string and compare. + */ + PCRTUNICP pucFilter = pDir->puszFilter; + const char *psz = pszName; + RTUNICP uc; + do + { + int rc = RTStrGetCpEx(&psz, &uc); + AssertRCReturn(rc, false); + RTUNICP ucFilter = *pucFilter++; + if ( uc != ucFilter + && RTUniCpToUpper(uc) != ucFilter) + return false; + } while (uc); + return true; +} + + +/** + * Matches end of name. + */ +DECLINLINE(bool) rtDirFilterWinNtMatchEon(PCRTUNICP puszFilter) +{ + RTUNICP ucFilter; + while ( (ucFilter = *puszFilter) == '>' + || ucFilter == '<' + || ucFilter == '*' + || ucFilter == '"') + puszFilter++; + return !ucFilter; +} + + +/** + * Recursive star matching. + * Practically the same as normal star, except that the dos star stops + * when hitting the last dot. + * + * @returns true on match. + * @returns false on miss. + */ +static bool rtDirFilterWinNtMatchDosStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter) +{ + AssertReturn(iDepth++ < 256, false); + + /* + * If there is no dos star, we should work just like the NT star. + * Since that's generally faster algorithms, we jump down to there if we can. + */ + const char *pszDosDot = strrchr(pszNext, '.'); + if (!pszDosDot && uc == '.') + pszDosDot = pszNext - 1; + if (!pszDosDot) + return rtDirFilterWinNtMatchStar(iDepth, uc, pszNext, puszFilter); + + /* + * Inspect the next filter char(s) until we find something to work on. + */ + RTUNICP ucFilter = *puszFilter++; + switch (ucFilter) + { + /* + * The star expression is the last in the pattern. + * We're fine if the name ends with a dot. + */ + case '\0': + return !pszDosDot[1]; + + /* + * Simplified by brute force. + */ + case '>': /* dos question mark */ + case '?': + case '*': + case '<': /* dos star */ + case '"': /* dos dot */ + { + puszFilter--; + const char *pszStart = pszNext; + do + { + if (rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter)) + return true; + int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false); + } while ((intptr_t)pszDosDot - (intptr_t)pszNext >= -1); + + /* backtrack and do the current char. */ + pszNext = RTStrPrevCp(NULL, pszStart); AssertReturn(pszNext, false); + return rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter); + } + + /* + * Ok, we've got zero or more characters. + * We'll try match starting at each occurrence of this character. + */ + default: + { + if ( RTUniCpToUpper(uc) == ucFilter + && rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter)) + return true; + do + { + int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false); + if ( RTUniCpToUpper(uc) == ucFilter + && rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter)) + return true; + } while ((intptr_t)pszDosDot - (intptr_t)pszNext >= -1); + return false; + } + } + /* won't ever get here! */ +} + + +/** + * Recursive star matching. + * + * @returns true on match. + * @returns false on miss. + */ +static bool rtDirFilterWinNtMatchStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter) +{ + AssertReturn(iDepth++ < 256, false); + + /* + * Inspect the next filter char(s) until we find something to work on. + */ + for (;;) + { + RTUNICP ucFilter = *puszFilter++; + switch (ucFilter) + { + /* + * The star expression is the last in the pattern. + * Cool, that means we're done! + */ + case '\0': + return true; + + /* + * Just in case (doubt we ever get here), just merge it with the current one. + */ + case '*': + break; + + /* + * Skip a fixed number of chars. + * Figure out how many by walking the filter ignoring '*'s. + */ + case '?': + { + unsigned cQms = 1; + while ((ucFilter = *puszFilter) == '*' || ucFilter == '?') + { + cQms += ucFilter == '?'; + puszFilter++; + } + do + { + if (!uc) + return false; + int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false); + } while (--cQms > 0); + /* done? */ + if (!ucFilter) + return true; + break; + } + + /* + * The simple way is to try char by char and match the remaining + * expression. If it's trailing we're done. + */ + case '>': /* dos question mark */ + { + if (rtDirFilterWinNtMatchEon(puszFilter)) + return true; + const char *pszStart = pszNext; + do + { + if (rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter)) + return true; + int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false); + } while (uc); + + /* backtrack and do the current char. */ + pszNext = RTStrPrevCp(NULL, pszStart); AssertReturn(pszNext, false); + return rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter); + } + + /* + * This bugger is interesting. + * Time for brute force. Iterate the name char by char. + */ + case '<': + { + do + { + if (rtDirFilterWinNtMatchDosStar(iDepth, uc, pszNext, puszFilter)) + return true; + int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false); + } while (uc); + return false; + } + + /* + * This guy matches a '.' or the end of the name. + * It's very simple if the rest of the filter expression also matches eon. + */ + case '"': + if (rtDirFilterWinNtMatchEon(puszFilter)) + return true; + ucFilter = '.'; + RT_FALL_THRU(); + + /* + * Ok, we've got zero or more characters. + * We'll try match starting at each occurrence of this character. + */ + default: + { + do + { + if ( RTUniCpToUpper(uc) == ucFilter + && rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter)) + return true; + int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false); + } while (uc); + return false; + } + } + } /* for (;;) */ + + /* won't ever get here! */ +} + + +/** + * Filter a the filename in the against a filter. + * + * The rules are as follows: + * '?' Matches exactly one char. + * '*' Matches zero or more chars. + * '<' The dos star, matches zero or more chars except the DOS dot. + * '>' The dos question mark, matches one char, but dots and end-of-name eats them. + * '"' The dos dot, matches a dot or end-of-name. + * + * @returns true if the name matches the filter. + * @returns false if the name doesn't match filter. + * @param iDepth The recursion depth. + * @param pszName The path to match to the filter. + * @param puszFilter The filter string. + */ +static bool rtDirFilterWinNtMatchBase(unsigned iDepth, const char *pszName, PCRTUNICP puszFilter) +{ + AssertReturn(iDepth++ < 256, false); + + /* + * Walk the string and match it up char by char. + */ + RTUNICP uc; + do + { + RTUNICP ucFilter = *puszFilter++; + int rc = RTStrGetCpEx(&pszName, &uc); AssertRCReturn(rc, false); + switch (ucFilter) + { + /* Exactly one char. */ + case '?': + if (!uc) + return false; + break; + + /* One char, but the dos dot and end-of-name eats '>' and '<'. */ + case '>': /* dos ? */ + if (!uc) + return rtDirFilterWinNtMatchEon(puszFilter); + if (uc == '.') + { + while ((ucFilter = *puszFilter) == '>' || ucFilter == '<') + puszFilter++; + if (ucFilter == '"' || ucFilter == '.') /* not 100% sure about the last dot */ + ++puszFilter; + else /* the does question mark doesn't match '.'s, so backtrack. */ + pszName = RTStrPrevCp(NULL, pszName); + } + break; + + /* Match a dot or the end-of-name. */ + case '"': /* dos '.' */ + if (uc != '.') + { + if (uc) + return false; + return rtDirFilterWinNtMatchEon(puszFilter); + } + break; + + /* zero or more */ + case '*': + return rtDirFilterWinNtMatchStar(iDepth, uc, pszName, puszFilter); + case '<': /* dos '*' */ + return rtDirFilterWinNtMatchDosStar(iDepth, uc, pszName, puszFilter); + + + /* uppercased match */ + default: + { + if (RTUniCpToUpper(uc) != ucFilter) + return false; + break; + } + } + } while (uc); + + return true; +} + + +/** + * Filter a the filename in the against a filter. + * + * @returns true if the name matches the filter. + * @returns false if the name doesn't match filter. + * @param pDir The directory handle. + * @param pszName The path to match to the filter. + */ +static DECLCALLBACK(bool) rtDirFilterWinNtMatch(PRTDIRINTERNAL pDir, const char *pszName) +{ + return rtDirFilterWinNtMatchBase(0, pszName, pDir->puszFilter); +} + + +/** + * Initializes a WinNt like wildcard filter. + * + * @returns Pointer to the filter function. + * @returns NULL if the filter doesn't filter out anything. + * @param pDir The directory handle (not yet opened). + */ +static PFNRTDIRFILTER rtDirFilterWinNtInit(PRTDIRINTERNAL pDir) +{ + /* + * Check for the usual * and <"< (*.* in DOS language) patterns. + */ + if ( (pDir->cchFilter == 1 && pDir->pszFilter[0] == '*') + || (pDir->cchFilter == 3 && !memcmp(pDir->pszFilter, "<\".>", 3)) + ) + return NULL; + + /* + * Uppercase the expression, also do a little optimizations when possible. + */ + bool fHaveWildcards = false; + unsigned iRead = 0; + unsigned iWrite = 0; + while (iRead < pDir->cucFilter) + { + RTUNICP uc = pDir->puszFilter[iRead++]; + if (uc == '*') + { + fHaveWildcards = true; + /* remove extra stars. */ + RTUNICP uc2; + while ((uc2 = pDir->puszFilter[iRead + 1]) == '*') + iRead++; + } + else if (uc == '?' || uc == '>' || uc == '<' || uc == '"') + fHaveWildcards = true; + else + uc = RTUniCpToUpper(uc); + pDir->puszFilter[iWrite++] = uc; + } + pDir->puszFilter[iWrite] = 0; + pDir->cucFilter = iWrite; + + return fHaveWildcards + ? rtDirFilterWinNtMatch + : rtDirFilterWinNtMatchNoWildcards; +} + + +/** + * Common worker for opening a directory. + * + * @returns IPRT status code. + * @param phDir Where to store the directory handle. + * @param pszPath The specified path. + * @param pszFilter Pointer to where the filter start in the path. + * NULL if no filter. + * @param enmFilter The type of filter to apply. + * @param fFlags RTDIR_F_XXX. + * @param hRelativeDir The directory @a pvNativeRelative is relative + * to, ~(uintptr_t)0 if absolute. + * @param pvNativeRelative The native relative path. NULL if absolute or + * we're to use (consume) hRelativeDir. + */ +static int rtDirOpenCommon(RTDIR *phDir, const char *pszPath, const char *pszFilter, RTDIRFILTER enmFilter, + uint32_t fFlags, uintptr_t hRelativeDir, void *pvNativeRelative) +{ + /* + * Expand the path. + * + * The purpose of this exercise to have the abs path around + * for querying extra information about the objects we list. + * As a sideeffect we also validate the path here. + * + * Note! The RTDIR_F_NO_ABS_PATH mess is there purely for allowing us to + * work around PATH_MAX using CWD on linux and other unixy systems. + */ + char *pszAbsPath; + size_t cbFilter; /* includes '\0' (thus cb and not cch). */ + size_t cucFilter0; /* includes U+0. */ + bool fDirSlash = false; + if (!pszFilter) + { + if (*pszPath != '\0') + { + const char *pszLast = strchr(pszPath, '\0') - 1; + if (RTPATH_IS_SLASH(*pszLast)) + fDirSlash = true; + } + + cbFilter = cucFilter0 = 0; + if (!(fFlags & RTDIR_F_NO_ABS_PATH)) + pszAbsPath = RTPathAbsExDup(NULL, pszPath, RTPATHABS_F_ENSURE_TRAILING_SLASH); + else + { + size_t cchTmp = strlen(pszPath); + pszAbsPath = RTStrAlloc(cchTmp + 2); + if (pszAbsPath) + { + memcpy(pszAbsPath, pszPath, cchTmp); + pszAbsPath[cchTmp] = RTPATH_SLASH; + pszAbsPath[cchTmp + 1 - fDirSlash] = '\0'; + } + } + } + else + { + cbFilter = strlen(pszFilter) + 1; + cucFilter0 = RTStrUniLen(pszFilter) + 1; + + if (pszFilter != pszPath) + { + /* yea, I'm lazy. sue me. */ + char *pszTmp = RTStrDup(pszPath); + if (!pszTmp) + return VERR_NO_MEMORY; + pszTmp[pszFilter - pszPath] = '\0'; + if (!(fFlags & RTDIR_F_NO_ABS_PATH)) + { + pszAbsPath = RTPathAbsExDup(NULL, pszTmp, RTPATHABS_F_ENSURE_TRAILING_SLASH); + RTStrFree(pszTmp); + } + else + { + pszAbsPath = pszTmp; + RTPathEnsureTrailingSeparator(pszAbsPath, strlen(pszPath) + 1); + } + } + else if (!(fFlags & RTDIR_F_NO_ABS_PATH)) + pszAbsPath = RTPathAbsExDup(NULL, ".", RTPATHABS_F_ENSURE_TRAILING_SLASH); + else + pszAbsPath = RTStrDup("." RTPATH_SLASH_STR); + fDirSlash = true; + } + if (!pszAbsPath) + return VERR_NO_MEMORY; + Assert(strchr(pszAbsPath, '\0')[-1] == RTPATH_SLASH); + + /* + * Allocate and initialize the directory handle. + * + * The posix definition of Data.d_name allows it to be < NAME_MAX + 1, + * thus the horrible ugliness here. Solaris uses d_name[1] for instance. + */ + size_t const cchAbsPath = strlen(pszAbsPath); + size_t const cbDir = rtDirNativeGetStructSize(pszAbsPath); + size_t const cbAllocated = cbDir + + cucFilter0 * sizeof(RTUNICP) + + cbFilter + + cchAbsPath + 1 + 4; + PRTDIRINTERNAL pDir = (PRTDIRINTERNAL)RTMemAllocZ(cbAllocated); + if (!pDir) + { + RTStrFree(pszAbsPath); + return VERR_NO_MEMORY; + } + uint8_t *pb = (uint8_t *)pDir + cbDir; + + /* initialize it */ + pDir->u32Magic = RTDIR_MAGIC; + pDir->cbSelf = cbDir; + if (cbFilter) + { + pDir->puszFilter = (PRTUNICP)pb; + int rc2 = RTStrToUniEx(pszFilter, RTSTR_MAX, &pDir->puszFilter, cucFilter0, &pDir->cucFilter); + AssertRC(rc2); + pb += cucFilter0 * sizeof(RTUNICP); + pDir->pszFilter = (char *)memcpy(pb, pszFilter, cbFilter); + pDir->cchFilter = cbFilter - 1; + pb += cbFilter; + } + else + { + pDir->puszFilter = NULL; + pDir->cucFilter = 0; + pDir->pszFilter = NULL; + pDir->cchFilter = 0; + } + pDir->enmFilter = enmFilter; + switch (enmFilter) + { + default: + case RTDIRFILTER_NONE: + pDir->pfnFilter = NULL; + break; + case RTDIRFILTER_WINNT: + pDir->pfnFilter = rtDirFilterWinNtInit(pDir); + break; + case RTDIRFILTER_UNIX: + pDir->pfnFilter = NULL; + break; + case RTDIRFILTER_UNIX_UPCASED: + pDir->pfnFilter = NULL; + break; + } + pDir->cchPath = cchAbsPath; + pDir->pszPath = (char *)memcpy(pb, pszAbsPath, cchAbsPath); + pb[cchAbsPath] = '\0'; + Assert(pb - (uint8_t *)pDir + cchAbsPath + 1 <= cbAllocated); + pDir->pszName = NULL; + pDir->cchName = 0; + pDir->fFlags = fFlags; + pDir->fDirSlash = fDirSlash; + pDir->fDataUnread = false; + + /* + * Hand it over to the native part. + */ + int rc = rtDirNativeOpen(pDir, hRelativeDir, pvNativeRelative); + if (RT_SUCCESS(rc)) + *phDir = pDir; + else + RTMemFree(pDir); + RTStrFree(pszAbsPath); + return rc; +} + + +RTDECL(int) RTDirOpen(RTDIR *phDir, const char *pszPath) +{ + /* + * Validate input. + */ + AssertPtrReturn(phDir, VERR_INVALID_POINTER); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + + /* + * Take common cause with RTDirOpenFiltered(). + */ + int rc = rtDirOpenCommon(phDir, pszPath, NULL, RTDIRFILTER_NONE, 0 /*fFlags*/, ~(uintptr_t)0, NULL); + LogFlow(("RTDirOpen(%p:{%p}, %p:{%s}): return %Rrc\n", phDir, *phDir, pszPath, pszPath, rc)); + return rc; +} + + +DECLHIDDEN(int) rtDirOpenRelativeOrHandle(RTDIR *phDir, const char *pszPath, RTDIRFILTER enmFilter, uint32_t fFlags, + uintptr_t hRelativeDir, void *pvNativeRelative) +{ + /* + * Validate input. + */ + AssertPtrReturn(phDir, VERR_INVALID_POINTER); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTDIR_F_VALID_MASK), VERR_INVALID_FLAGS); + switch (enmFilter) + { + case RTDIRFILTER_UNIX: + case RTDIRFILTER_UNIX_UPCASED: + AssertMsgFailed(("%d is not implemented!\n", enmFilter)); + return VERR_NOT_IMPLEMENTED; + case RTDIRFILTER_NONE: + case RTDIRFILTER_WINNT: + break; + default: + AssertMsgFailedReturn(("%d\n", enmFilter), VERR_INVALID_PARAMETER); + } + + /* + * Find the last component, i.e. where the filter criteria starts and the dir name ends. + */ + const char *pszFilter; + if (enmFilter == RTDIRFILTER_NONE) + pszFilter = NULL; + else + { + pszFilter = RTPathFilename(pszPath); + if (!pszFilter) /* trailing slash => directory to read => no filter. */ + enmFilter = RTDIRFILTER_NONE; + } + + /* + * Call worker common with RTDirOpen which will verify the path, allocate + * and initialize the handle, and finally call the backend. + */ + int rc = rtDirOpenCommon(phDir, pszPath, pszFilter, enmFilter, fFlags, hRelativeDir, pvNativeRelative); + + LogFlow(("RTDirOpenFiltered(%p:{%p}, %p:{%s}, %d, %#x, %p, %p): return %Rrc\n", + phDir,*phDir, pszPath, pszPath, enmFilter, fFlags, hRelativeDir, pvNativeRelative, rc)); + return rc; +} + + +RTDECL(int) RTDirOpenFiltered(RTDIR *phDir, const char *pszPath, RTDIRFILTER enmFilter, uint32_t fFlags) +{ + return rtDirOpenRelativeOrHandle(phDir, pszPath, enmFilter, fFlags, ~(uintptr_t)0, NULL); +} + + +RTDECL(bool) RTDirIsValid(RTDIR hDir) +{ + return RT_VALID_PTR(hDir) + && hDir->u32Magic == RTDIR_MAGIC; +} + + +RTDECL(int) RTDirFlushParent(const char *pszChild) +{ + char *pszPath; + char *pszPathFree = NULL; + size_t const cchChild = strlen(pszChild); + if (cchChild < RTPATH_MAX) + pszPath = (char *)alloca(cchChild + 1); + else + { + pszPathFree = pszPath = (char *)RTMemTmpAlloc(cchChild + 1); + if (!pszPath) + return VERR_NO_TMP_MEMORY; + } + memcpy(pszPath, pszChild, cchChild); + pszPath[cchChild] = '\0'; + RTPathStripFilename(pszPath); + + int rc = RTDirFlush(pszPath); + + if (pszPathFree) + RTMemTmpFree(pszPathFree); + return rc; +} + + +RTDECL(int) RTDirQueryUnknownTypeEx(const char *pszComposedName, bool fFollowSymlinks, + RTDIRENTRYTYPE *penmType, PRTFSOBJINFO pObjInfo) +{ + int rc = RTPathQueryInfoEx(pszComposedName, pObjInfo, RTFSOBJATTRADD_NOTHING, + fFollowSymlinks ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + return rc; + + if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_DIRECTORY; + else if (RTFS_IS_FILE(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_FILE; + else if (RTFS_IS_SYMLINK(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_SYMLINK; + else if (RTFS_IS_FIFO(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_FIFO; + else if (RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_DEV_CHAR; + else if (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_DEV_BLOCK; + else if (RTFS_IS_SOCKET(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_SOCKET; + else if (RTFS_IS_WHITEOUT(pObjInfo->Attr.fMode)) + *penmType = RTDIRENTRYTYPE_WHITEOUT; + else + *penmType = RTDIRENTRYTYPE_UNKNOWN; + + return VINF_SUCCESS; +} + + +RTDECL(int) RTDirQueryUnknownType(const char *pszComposedName, bool fFollowSymlinks, RTDIRENTRYTYPE *penmType) +{ + if ( *penmType != RTDIRENTRYTYPE_UNKNOWN + && ( !fFollowSymlinks + || *penmType != RTDIRENTRYTYPE_SYMLINK)) + return VINF_SUCCESS; + + RTFSOBJINFO ObjInfo; + return RTDirQueryUnknownTypeEx(pszComposedName, fFollowSymlinks, penmType, &ObjInfo); +} + + +RTDECL(bool) RTDirEntryIsStdDotLink(PRTDIRENTRY pDirEntry) +{ + if (pDirEntry->szName[0] != '.') + return false; + if (pDirEntry->cbName == 1) + return true; + if (pDirEntry->cbName != 2) + return false; + return pDirEntry->szName[1] == '.'; +} + + +RTDECL(bool) RTDirEntryExIsStdDotLink(PCRTDIRENTRYEX pDirEntryEx) +{ + if (pDirEntryEx->szName[0] != '.') + return false; + if (pDirEntryEx->cbName == 1) + return true; + if (pDirEntryEx->cbName != 2) + return false; + return pDirEntryEx->szName[1] == '.'; +} + + +RTDECL(int) RTDirReadExA(RTDIR hDir, PRTDIRENTRYEX *ppDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAddAttr, uint32_t fFlags) +{ + PRTDIRENTRYEX pDirEntry = *ppDirEntry; + size_t cbDirEntry = *pcbDirEntry; + if (pDirEntry != NULL && cbDirEntry >= sizeof(RTDIRENTRYEX)) + { /* likely */ } + else + { + Assert(pDirEntry == NULL); + Assert(cbDirEntry == 0); + + cbDirEntry = RT_ALIGN_Z(sizeof(RTDIRENTRYEX), 16); + *ppDirEntry = pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntry); + if (pDirEntry) + *pcbDirEntry = cbDirEntry; + else + { + *pcbDirEntry = 0; + return VERR_NO_TMP_MEMORY; + } + } + + for (;;) + { + int rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, enmAddAttr, fFlags); + if (rc != VERR_BUFFER_OVERFLOW) + return rc; + + /* Grow the buffer. */ + RTMemTmpFree(pDirEntry); + cbDirEntry = RT_MAX(RT_ALIGN_Z(cbDirEntry, 64), *pcbDirEntry + 64); + *ppDirEntry = pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntry); + if (pDirEntry) + *pcbDirEntry = cbDirEntry; + else + { + *pcbDirEntry = 0; + return VERR_NO_TMP_MEMORY; + } + } +} + + +RTDECL(void) RTDirReadExAFree(PRTDIRENTRYEX *ppDirEntry, size_t *pcbDirEntry) +{ + PRTDIRENTRYEX pDirEntry = *ppDirEntry; + if (pDirEntry != NULL && *pcbDirEntry >= sizeof(*pcbDirEntry)) + RTMemTmpFree(pDirEntry); + else + { + Assert(pDirEntry == NULL); + Assert(*pcbDirEntry == 0); + } + *ppDirEntry = NULL; + *pcbDirEntry = 0; +} + diff --git a/src/VBox/Runtime/r3/dir2.cpp b/src/VBox/Runtime/r3/dir2.cpp new file mode 100644 index 00000000..1f47b3ed --- /dev/null +++ b/src/VBox/Runtime/r3/dir2.cpp @@ -0,0 +1,242 @@ +/* $Id: dir2.cpp $ */ +/** @file + * IPRT - Directory Manipulation, Part 2. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/dir.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include "internal/path.h" + + +/** + * Recursion worker for RTDirRemoveRecursive. + * + * @returns IPRT status code. + * @param pszBuf The path buffer. Contains the abs path to the + * directory to recurse into. Trailing slash. + * @param cchDir The length of the directory we're recursing into, + * including the trailing slash. + * @param cbBuf Size of the buffer @a pszBuf points to. + * @param pDirEntry The dir entry buffer. (Shared to save stack.) + * @param pObjInfo The object info buffer. (ditto) + * @param fFlags RTDIRRMREC_F_XXX. + */ +static int rtDirRemoveRecursiveSub(char *pszBuf, size_t cchDir, size_t cbBuf, PRTDIRENTRY pDirEntry, PRTFSOBJINFO pObjInfo, + uint32_t fFlags) +{ + AssertReturn(RTPATH_IS_SLASH(pszBuf[cchDir - 1]), VERR_INTERNAL_ERROR_4); + + /* + * Enumerate the directory content and dispose of it. + */ + RTDIR hDir; + int rc = RTDirOpenFiltered(&hDir, pszBuf, RTDIRFILTER_NONE, fFlags & RTDIRRMREC_F_NO_ABS_PATH ? RTDIR_F_NO_ABS_PATH : 0); + if (RT_FAILURE(rc)) + return rc; + while (RT_SUCCESS(rc = RTDirRead(hDir, pDirEntry, NULL))) + { + if (!RTDirEntryIsStdDotLink(pDirEntry)) + { + /* Construct the full name of the entry. */ + if (cchDir + pDirEntry->cbName + 1 /* dir slash */ >= cbBuf) + { + rc = VERR_FILENAME_TOO_LONG; + break; + } + memcpy(&pszBuf[cchDir], pDirEntry->szName, pDirEntry->cbName + 1); + + /* Deal with the unknown type. */ + if (pDirEntry->enmType == RTDIRENTRYTYPE_UNKNOWN) + { + rc = RTPathQueryInfoEx(pszBuf, pObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) + pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY; + else if (RT_SUCCESS(rc) && RTFS_IS_FILE(pObjInfo->Attr.fMode)) + pDirEntry->enmType = RTDIRENTRYTYPE_FILE; + else if (RT_SUCCESS(rc) && RTFS_IS_SYMLINK(pObjInfo->Attr.fMode)) + pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK; + } + + /* Try the delete the fs object. */ + switch (pDirEntry->enmType) + { + case RTDIRENTRYTYPE_FILE: + rc = RTFileDelete(pszBuf); + break; + + case RTDIRENTRYTYPE_DIRECTORY: + { + size_t cchSubDir = cchDir + pDirEntry->cbName; + pszBuf[cchSubDir++] = '/'; + pszBuf[cchSubDir] = '\0'; + rc = rtDirRemoveRecursiveSub(pszBuf, cchSubDir, cbBuf, pDirEntry, pObjInfo, fFlags); + if (RT_SUCCESS(rc)) + { + pszBuf[cchSubDir] = '\0'; + rc = RTDirRemove(pszBuf); + } + break; + } + + //case RTDIRENTRYTYPE_SYMLINK: + // rc = RTSymlinkDelete(pszBuf, 0); + // break; + + default: + /** @todo not implemented yet. */ + rc = VINF_SUCCESS; + break; + } + if (RT_FAILURE(rc)) + break; + } + } + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + RTDirClose(hDir); + return rc; +} + + +RTDECL(int) RTDirRemoveRecursive(const char *pszPath, uint32_t fFlags) +{ + AssertReturn(!(fFlags & ~RTDIRRMREC_F_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Allocate path buffer. + */ + char *pszAbsPath; + size_t cbAbsPathBuf = RTPATH_BIG_MAX; + char *pszAbsPathFree = pszAbsPath = (char *)RTMemTmpAlloc(cbAbsPathBuf); + if (!pszAbsPath) + { + cbAbsPathBuf = RTPATH_MAX; + pszAbsPath = (char *)alloca(RTPATH_MAX); + } + + /* + * Get an absolute path because this is easier to work with and + * eliminates any races with changing CWD. + */ + int rc; + if (!(fFlags & RTDIRRMREC_F_NO_ABS_PATH)) + rc = RTPathAbs(pszPath, pszAbsPath, cbAbsPathBuf); + else if (*pszPath != '\0') + rc = RTStrCopy(pszAbsPath, cbAbsPathBuf, pszPath); + else + rc = VERR_PATH_ZERO_LENGTH; + if (RT_SUCCESS(rc)) + { + /* + * This API is not permitted applied to the root of anything. + */ + union + { + RTPATHPARSED Parsed; + uint8_t abParsed[RTPATHPARSED_MIN_SIZE]; + } uBuf; + RTPathParse(pszPath, &uBuf.Parsed, sizeof(uBuf), RTPATH_STR_F_STYLE_HOST); + if ( uBuf.Parsed.cComps <= 1 + && (uBuf.Parsed.fProps & RTPATH_PROP_ROOT_SLASH)) + rc = VERR_ACCESS_DENIED; + else + { + /* + * Because of the above restriction, we never have to deal with the root + * slash problem and can safely strip any trailing slashes and add a + * definite one. + */ + RTPathStripTrailingSlash(pszAbsPath); + size_t cchAbsPath = strlen(pszAbsPath); + if (cchAbsPath + 1 < cbAbsPathBuf) + { + pszAbsPath[cchAbsPath++] = RTPATH_SLASH; + pszAbsPath[cchAbsPath] = '\0'; + + /* + * Check if it exists so we can return quietly if it doesn't. + */ + RTFSOBJINFO SharedObjInfoBuf; + rc = RTPathQueryInfoEx(pszAbsPath, &SharedObjInfoBuf, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if ( rc == VERR_PATH_NOT_FOUND + || rc == VERR_FILE_NOT_FOUND) + rc = VINF_SUCCESS; + else if ( RT_SUCCESS(rc) + && RTFS_IS_DIRECTORY(SharedObjInfoBuf.Attr.fMode)) + { + /* + * We're all set for the recursion now, so get going. + */ + RTDIRENTRY SharedDirEntryBuf; + rc = rtDirRemoveRecursiveSub(pszAbsPath, cchAbsPath, cbAbsPathBuf, + &SharedDirEntryBuf, &SharedObjInfoBuf, fFlags); + + /* + * Remove the specified directory if desired and removing the content was successful. + */ + if ( RT_SUCCESS(rc) + && !(fFlags & RTDIRRMREC_F_CONTENT_ONLY)) + { + pszAbsPath[cchAbsPath] = 0; + rc = RTDirRemove(pszAbsPath); + } + } + else if (RT_SUCCESS(rc)) + rc = VERR_NOT_A_DIRECTORY; + + } + else + rc = VERR_FILENAME_TOO_LONG; + } + } + if (pszAbsPathFree) + RTMemTmpFree(pszAbsPathFree); + return rc; +} + diff --git a/src/VBox/Runtime/r3/fileio.cpp b/src/VBox/Runtime/r3/fileio.cpp new file mode 100644 index 00000000..cece884a --- /dev/null +++ b/src/VBox/Runtime/r3/fileio.cpp @@ -0,0 +1,423 @@ +/* $Id: fileio.cpp $ */ +/** @file + * IPRT - File I/O. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/alloca.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include "internal/file.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Set of forced set open flags for files opened read-only. */ +static unsigned g_fOpenReadSet = 0; + +/** Set of forced cleared open flags for files opened read-only. */ +static unsigned g_fOpenReadMask = 0; + +/** Set of forced set open flags for files opened write-only. */ +static unsigned g_fOpenWriteSet = 0; + +/** Set of forced cleared open flags for files opened write-only. */ +static unsigned g_fOpenWriteMask = 0; + +/** Set of forced set open flags for files opened read-write. */ +static unsigned g_fOpenReadWriteSet = 0; + +/** Set of forced cleared open flags for files opened read-write. */ +static unsigned g_fOpenReadWriteMask = 0; + + +/** + * Force the use of open flags for all files opened after the setting is + * changed. The caller is responsible for not causing races with RTFileOpen(). + * + * @returns iprt status code. + * @param fOpenForAccess Access mode to which the set/mask settings apply. + * @param fSet Open flags to be forced set. + * @param fMask Open flags to be masked out. + */ +RTR3DECL(int) RTFileSetForceFlags(unsigned fOpenForAccess, unsigned fSet, unsigned fMask) +{ + /* + * For now allow only RTFILE_O_WRITE_THROUGH. The other flags either + * make no sense in this context or are not useful to apply to all files. + */ + if ((fSet | fMask) & ~RTFILE_O_WRITE_THROUGH) + return VERR_INVALID_PARAMETER; + switch (fOpenForAccess) + { + case RTFILE_O_READ: + g_fOpenReadSet = fSet; + g_fOpenReadMask = fMask; + break; + case RTFILE_O_WRITE: + g_fOpenWriteSet = fSet; + g_fOpenWriteMask = fMask; + break; + case RTFILE_O_READWRITE: + g_fOpenReadWriteSet = fSet; + g_fOpenReadWriteMask = fMask; + break; + default: + AssertMsgFailed(("Invalid access mode %d\n", fOpenForAccess)); + return VERR_INVALID_PARAMETER; + } + return VINF_SUCCESS; +} + + +/** + * Adjusts and validates the flags. + * + * The adjustments are made according to the wishes specified using the RTFileSetForceFlags API. + * + * @returns IPRT status code. + * @param pfOpen Pointer to the user specified flags on input. + * Updated on successful return. + * @internal + */ +int rtFileRecalcAndValidateFlags(uint64_t *pfOpen) +{ + /* + * Recalc. + */ + uint32_t fOpen = *pfOpen; + switch (fOpen & RTFILE_O_ACCESS_MASK) + { + case RTFILE_O_READ: + fOpen |= g_fOpenReadSet; + fOpen &= ~g_fOpenReadMask; + break; + case RTFILE_O_WRITE: + fOpen |= g_fOpenWriteSet; + fOpen &= ~g_fOpenWriteMask; + break; + case RTFILE_O_READWRITE: + fOpen |= g_fOpenReadWriteSet; + fOpen &= ~g_fOpenReadWriteMask; + break; +#ifdef RT_OS_WINDOWS + case RTFILE_O_ATTR_ONLY: + if (fOpen & RTFILE_O_ACCESS_ATTR_MASK) + break; +#endif + default: + AssertMsgFailed(("Invalid access mode value, fOpen=%#llx\n", fOpen)); + return VERR_INVALID_PARAMETER; + } + + /* + * Validate . + */ +#ifdef RT_OS_WINDOWS + AssertMsgReturn((fOpen & RTFILE_O_ACCESS_MASK) || (fOpen & RTFILE_O_ACCESS_ATTR_MASK), + ("Missing RTFILE_O_READ/WRITE/ACCESS_ATTR: fOpen=%#llx\n", fOpen), VERR_INVALID_PARAMETER); +#else + AssertMsgReturn(fOpen & RTFILE_O_ACCESS_MASK, ("Missing RTFILE_O_READ/WRITE: fOpen=%#llx\n", fOpen), VERR_INVALID_PARAMETER); +#endif +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + AssertMsgReturn(!(fOpen & (~(uint64_t)RTFILE_O_VALID_MASK | RTFILE_O_NON_BLOCK)), ("%#llx\n", fOpen), VERR_INVALID_PARAMETER); +#else + AssertMsgReturn(!(fOpen & ~(uint64_t)RTFILE_O_VALID_MASK), ("%#llx\n", fOpen), VERR_INVALID_PARAMETER); +#endif + AssertMsgReturn((fOpen & (RTFILE_O_TRUNCATE | RTFILE_O_WRITE)) != RTFILE_O_TRUNCATE, ("%#llx\n", fOpen), VERR_INVALID_PARAMETER); + + switch (fOpen & RTFILE_O_ACTION_MASK) + { + case 0: /* temporarily */ + AssertMsgFailed(("Missing RTFILE_O_OPEN/CREATE*! (continuable assertion)\n")); + fOpen |= RTFILE_O_OPEN; + break; + case RTFILE_O_OPEN: + AssertMsgReturn(!(RTFILE_O_NOT_CONTENT_INDEXED & fOpen), ("%#llx\n", fOpen), VERR_INVALID_PARAMETER); + case RTFILE_O_OPEN_CREATE: + case RTFILE_O_CREATE: + case RTFILE_O_CREATE_REPLACE: + break; + default: + AssertMsgFailed(("Invalid action value: fOpen=%#llx\n", fOpen)); + return VERR_INVALID_PARAMETER; + } + + switch (fOpen & RTFILE_O_DENY_MASK) + { + case 0: /* temporarily */ + AssertMsgFailed(("Missing RTFILE_O_DENY_*! (continuable assertion)\n")); + fOpen |= RTFILE_O_DENY_NONE; + break; + case RTFILE_O_DENY_NONE: + case RTFILE_O_DENY_READ: + case RTFILE_O_DENY_WRITE: + case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ: + case RTFILE_O_DENY_NOT_DELETE: + case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_READ: + case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_WRITE: + case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ: + break; + default: + AssertMsgFailed(("Invalid deny value: fOpen=%#llx\n", fOpen)); + return VERR_INVALID_PARAMETER; + } + + /* done */ + *pfOpen = fOpen; + return VINF_SUCCESS; +} + + +RTR3DECL(uint64_t) RTFileTell(RTFILE File) +{ + /* + * Call the seek api to query the stuff. + */ + uint64_t off = 0; + int rc = RTFileSeek(File, 0, RTFILE_SEEK_CURRENT, &off); + if (RT_SUCCESS(rc)) + return off; + AssertMsgFailed(("RTFileSeek(%d) -> %d\n", File, rc)); + return ~0ULL; +} + + +RTR3DECL(RTFOFF) RTFileGetMaxSize(RTFILE File) +{ + RTFOFF cbMax; + int rc = RTFileQueryMaxSizeEx(File, &cbMax); + return RT_SUCCESS(rc) ? cbMax : -1; +} + + +RTDECL(int) RTFileCopyByHandles(RTFILE FileSrc, RTFILE FileDst) +{ + return RTFileCopyByHandlesEx(FileSrc, FileDst, NULL, NULL); +} + + +RTDECL(int) RTFileCompare(const char *pszFile1, const char *pszFile2) +{ + return RTFileCompareEx(pszFile1, pszFile2, 0 /*fFlags*/, NULL, NULL); +} + + +RTDECL(int) RTFileCompareByHandles(RTFILE hFile1, RTFILE hFile2) +{ + return RTFileCompareByHandlesEx(hFile1, hFile2, 0 /*fFlags*/, NULL, NULL); +} + + +RTDECL(int) RTFileCompareEx(const char *pszFile1, const char *pszFile2, uint32_t fFlags, PFNRTPROGRESS pfnProgress, void *pvUser) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszFile1, VERR_INVALID_POINTER); + AssertReturn(*pszFile1, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszFile2, VERR_INVALID_POINTER); + AssertReturn(*pszFile2, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pfnProgress, VERR_INVALID_POINTER); + AssertMsgReturn(!(fFlags & ~RTFILECOMP_FLAGS_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Open the files. + */ + RTFILE hFile1; + int rc = RTFileOpen(&hFile1, pszFile1, + RTFILE_O_READ | RTFILE_O_OPEN + | (fFlags & RTFILECOMP_FLAGS_NO_DENY_WRITE_FILE1 ? RTFILE_O_DENY_NONE : RTFILE_O_DENY_WRITE)); + if (RT_SUCCESS(rc)) + { + RTFILE hFile2; + rc = RTFileOpen(&hFile2, pszFile2, + RTFILE_O_READ | RTFILE_O_OPEN + | (fFlags & RTFILECOMP_FLAGS_NO_DENY_WRITE_FILE2 ? RTFILE_O_DENY_NONE : RTFILE_O_DENY_WRITE)); + if (RT_SUCCESS(rc)) + { + /* + * Call the ByHandles version and let it do the job. + */ + rc = RTFileCompareByHandlesEx(hFile1, hFile2, fFlags, pfnProgress, pvUser); + + /* Clean up */ + int rc2 = RTFileClose(hFile2); + AssertRC(rc2); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + + int rc2 = RTFileClose(hFile1); + AssertRC(rc2); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + return rc; +} + + +RTDECL(int) RTFileCompareByHandlesEx(RTFILE hFile1, RTFILE hFile2, uint32_t fFlags, PFNRTPROGRESS pfnProgress, void *pvUser) +{ + /* + * Validate input. + */ + AssertReturn(RTFileIsValid(hFile1), VERR_INVALID_HANDLE); + AssertReturn(RTFileIsValid(hFile1), VERR_INVALID_HANDLE); + AssertPtrNullReturn(pfnProgress, VERR_INVALID_POINTER); + AssertMsgReturn(!(fFlags & ~RTFILECOMP_FLAGS_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Compare the file sizes first. + */ + uint64_t cbFile1; + int rc = RTFileQuerySize(hFile1, &cbFile1); + if (RT_FAILURE(rc)) + return rc; + + uint64_t cbFile2; + rc = RTFileQuerySize(hFile1, &cbFile2); + if (RT_FAILURE(rc)) + return rc; + + if (cbFile1 != cbFile2) + return VERR_NOT_EQUAL; + + + /* + * Allocate buffer. + */ + size_t cbBuf; + uint8_t *pbBuf1Free = NULL; + uint8_t *pbBuf1; + uint8_t *pbBuf2Free = NULL; + uint8_t *pbBuf2; + if (cbFile1 < _512K) + { + cbBuf = 8*_1K; + pbBuf1 = (uint8_t *)alloca(cbBuf); + pbBuf2 = (uint8_t *)alloca(cbBuf); + } + else + { + cbBuf = _128K; + pbBuf1 = pbBuf1Free = (uint8_t *)RTMemTmpAlloc(cbBuf); + pbBuf2 = pbBuf2Free = (uint8_t *)RTMemTmpAlloc(cbBuf); + } + if (pbBuf1 && pbBuf2) + { + /* + * Seek to the start of each file + * and set the size of the destination file. + */ + rc = RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + { + rc = RTFileSeek(hFile2, 0, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc) && pfnProgress) + rc = pfnProgress(0, pvUser); + if (RT_SUCCESS(rc)) + { + /* + * Compare loop. + */ + unsigned uPercentage = 0; + RTFOFF off = 0; + RTFOFF cbPercent = cbFile1 / 100; + RTFOFF offNextPercent = cbPercent; + while (off < (RTFOFF)cbFile1) + { + /* read the blocks */ + RTFOFF cbLeft = cbFile1 - off; + size_t cbBlock = cbLeft >= (RTFOFF)cbBuf ? cbBuf : (size_t)cbLeft; + rc = RTFileRead(hFile1, pbBuf1, cbBlock, NULL); + if (RT_FAILURE(rc)) + break; + rc = RTFileRead(hFile2, pbBuf2, cbBlock, NULL); + if (RT_FAILURE(rc)) + break; + + /* compare */ + if (memcmp(pbBuf1, pbBuf2, cbBlock)) + { + rc = VERR_NOT_EQUAL; + break; + } + + /* advance */ + off += cbBlock; + if (pfnProgress && offNextPercent < off) + { + while (offNextPercent < off) + { + uPercentage++; + offNextPercent += cbPercent; + } + rc = pfnProgress(uPercentage, pvUser); + if (RT_FAILURE(rc)) + break; + } + } + +#if 0 + /* + * Compare OS specific data (EAs and stuff). + */ + if (RT_SUCCESS(rc)) + rc = rtFileCompareOSStuff(hFile1, hFile2); +#endif + + /* 100% */ + if (pfnProgress && uPercentage < 100 && RT_SUCCESS(rc)) + rc = pfnProgress(100, pvUser); + } + } + } + else + rc = VERR_NO_MEMORY; + RTMemTmpFree(pbBuf2Free); + RTMemTmpFree(pbBuf1Free); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/freebsd/Makefile.kup b/src/VBox/Runtime/r3/freebsd/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/freebsd/Makefile.kup diff --git a/src/VBox/Runtime/r3/freebsd/RTFileQuerySectorSize-freebsd.cpp b/src/VBox/Runtime/r3/freebsd/RTFileQuerySectorSize-freebsd.cpp new file mode 100644 index 00000000..15075d63 --- /dev/null +++ b/src/VBox/Runtime/r3/freebsd/RTFileQuerySectorSize-freebsd.cpp @@ -0,0 +1,88 @@ +/* $Id: RTFileQuerySectorSize-freebsd.cpp $ */ +/** @file + * IPRT - RTFileQuerySectorSize, FreeBSD. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#include <errno.h> +#include <sys/disk.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + + + +RTDECL(int) RTFileQuerySectorSize(RTFILE hFile, uint32_t *pcbSector) +{ + AssertPtrReturn(pcbSector, VERR_INVALID_PARAMETER); + + int rc; + int const fd = (int)RTFileToNative(hFile); + struct stat DevStat = { 0 }; + if (!fstat(fd, &DevStat)) + { + if (S_ISCHR(DevStat.st_mode)) + { + u_int cbSector = 0; + if (!ioctl(fd, DIOCGSECTORSIZE, &cbSector)) + { + AssertReturn(cbSector > 0, VERR_INVALID_FUNCTION); + *pcbSector = cbSector; + return VINF_SUCCESS; + } + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("ioctl failed: errno=%d / %Rrc\n", errno, rc)); + } + else + { + AssertMsgFailed(("not a character device.\n")); + rc = VERR_INVALID_FUNCTION; + } + } + else + { + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("fstat failed: errno=%d / %Rrc\n", errno, rc)); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/freebsd/fileaio-freebsd.cpp b/src/VBox/Runtime/r3/freebsd/fileaio-freebsd.cpp new file mode 100644 index 00000000..46190a5f --- /dev/null +++ b/src/VBox/Runtime/r3/freebsd/fileaio-freebsd.cpp @@ -0,0 +1,682 @@ +/* $Id: fileaio-freebsd.cpp $ */ +/** @file + * IPRT - File async I/O, native implementation for the FreeBSD host platform. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include <iprt/asm.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/thread.h> +#include "internal/fileaio.h" + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/time.h> +#include <sys/sysctl.h> +#include <aio.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Async I/O completion context state. + */ +typedef struct RTFILEAIOCTXINTERNAL +{ + /** Handle to the kernel queue. */ + int iKQueue; + /** Current number of requests active on this context. */ + volatile int32_t cRequests; + /** The ID of the thread which is currently waiting for requests. */ + volatile RTTHREAD hThreadWait; + /** Flag whether the thread was woken up. */ + volatile bool fWokenUp; + /** Flag whether the thread is currently waiting in the syscall. */ + volatile bool fWaiting; + /** Flags given during creation. */ + uint32_t fFlags; + /** Magic value (RTFILEAIOCTX_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOCTXINTERNAL; +/** Pointer to an internal context structure. */ +typedef RTFILEAIOCTXINTERNAL *PRTFILEAIOCTXINTERNAL; + +/** + * Async I/O request state. + */ +typedef struct RTFILEAIOREQINTERNAL +{ + /** The aio control block. Must be the FIRST + * element. */ + struct aiocb AioCB; + /** Current state the request is in. */ + RTFILEAIOREQSTATE enmState; + /** Flag whether this is a flush request. */ + bool fFlush; + /** Opaque user data. */ + void *pvUser; + /** Completion context we are assigned to. */ + PRTFILEAIOCTXINTERNAL pCtxInt; + /** Number of bytes actually transferred. */ + size_t cbTransfered; + /** Status code. */ + int Rc; + /** Magic value (RTFILEAIOREQ_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOREQINTERNAL; +/** Pointer to an internal request structure. */ +typedef RTFILEAIOREQINTERNAL *PRTFILEAIOREQINTERNAL; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max number of events to get in one call. */ +#define AIO_MAXIMUM_REQUESTS_PER_CONTEXT 64 + +RTR3DECL(int) RTFileAioGetLimits(PRTFILEAIOLIMITS pAioLimits) +{ + int rcBSD = 0; + AssertPtrReturn(pAioLimits, VERR_INVALID_POINTER); + + /* + * The AIO API is implemented in a kernel module which is not + * loaded by default. + * If it is loaded there are additional sysctl parameters. + */ + int cReqsOutstandingMax = 0; + size_t cbParameter = sizeof(int); + + rcBSD = sysctlbyname("vfs.aio.max_aio_per_proc", /* name */ + &cReqsOutstandingMax, /* Where to store the old value. */ + &cbParameter, /* Size of the memory pointed to. */ + NULL, /* Where the new value is located. */ + 0); /* Where the size of the new value is stored. */ + if (rcBSD == -1) + { + /* ENOENT means the value is unknown thus the module is not loaded. */ + if (errno == ENOENT) + return VERR_NOT_SUPPORTED; + else + return RTErrConvertFromErrno(errno); + } + + pAioLimits->cReqsOutstandingMax = cReqsOutstandingMax; + pAioLimits->cbBufferAlignment = 0; + + return VINF_SUCCESS; +} + +RTR3DECL(int) RTFileAioReqCreate(PRTFILEAIOREQ phReq) +{ + AssertPtrReturn(phReq, VERR_INVALID_POINTER); + + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOREQINTERNAL)); + if (RT_UNLIKELY(!pReqInt)) + return VERR_NO_MEMORY; + + /* Ininitialize static parts. */ + pReqInt->AioCB.aio_sigevent.sigev_notify = SIGEV_KEVENT; + pReqInt->AioCB.aio_sigevent.sigev_value.sival_ptr = pReqInt; + pReqInt->pCtxInt = NULL; + pReqInt->u32Magic = RTFILEAIOREQ_MAGIC; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + *phReq = (RTFILEAIOREQ)pReqInt; + + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioReqDestroy(RTFILEAIOREQ hReq) +{ + /* + * Validate the handle and ignore nil. + */ + if (hReq == NIL_RTFILEAIOREQ) + return VINF_SUCCESS; + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + + /* + * Trash the magic and free it. + */ + ASMAtomicUoWriteU32(&pReqInt->u32Magic, ~RTFILEAIOREQ_MAGIC); + RTMemFree(pReqInt); + return VINF_SUCCESS; +} + +/** + * Worker setting up the request. + */ +DECLINLINE(int) rtFileAioReqPrepareTransfer(RTFILEAIOREQ hReq, RTFILE hFile, + unsigned uTransferDirection, + RTFOFF off, void *pvBuf, size_t cbTransfer, + void *pvUser) +{ + /* + * Validate the input. + */ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + Assert(hFile != NIL_RTFILE); + AssertPtr(pvBuf); + Assert(off >= 0); + Assert(cbTransfer > 0); + + pReqInt->AioCB.aio_sigevent.sigev_notify = SIGEV_KEVENT; + pReqInt->AioCB.aio_sigevent.sigev_value.sival_ptr = pReqInt; + pReqInt->AioCB.aio_lio_opcode = uTransferDirection; + pReqInt->AioCB.aio_fildes = RTFileToNative(hFile); + pReqInt->AioCB.aio_offset = off; + pReqInt->AioCB.aio_nbytes = cbTransfer; + pReqInt->AioCB.aio_buf = pvBuf; + pReqInt->fFlush = false; + pReqInt->pvUser = pvUser; + pReqInt->pCtxInt = NULL; + pReqInt->Rc = VERR_FILE_AIO_IN_PROGRESS; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioReqPrepareRead(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void *pvBuf, size_t cbRead, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LIO_READ, + off, pvBuf, cbRead, pvUser); +} + +RTDECL(int) RTFileAioReqPrepareWrite(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void const *pvBuf, size_t cbWrite, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LIO_WRITE, + off, (void *)pvBuf, cbWrite, pvUser); +} + +RTDECL(int) RTFileAioReqPrepareFlush(RTFILEAIOREQ hReq, RTFILE hFile, void *pvUser) +{ + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)hReq; + + RTFILEAIOREQ_VALID_RETURN(pReqInt); + Assert(hFile != NIL_RTFILE); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + + pReqInt->fFlush = true; + pReqInt->AioCB.aio_fildes = RTFileToNative(hFile); + pReqInt->AioCB.aio_offset = 0; + pReqInt->AioCB.aio_nbytes = 0; + pReqInt->AioCB.aio_buf = NULL; + pReqInt->pvUser = pvUser; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + + return VINF_SUCCESS; +} + +RTDECL(void *) RTFileAioReqGetUser(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN_RC(pReqInt, NULL); + + return pReqInt->pvUser; +} + +RTDECL(int) RTFileAioReqCancel(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_NOT_SUBMITTED); + + + int rcBSD = aio_cancel(pReqInt->AioCB.aio_fildes, &pReqInt->AioCB); + + if (rcBSD == AIO_CANCELED) + { + /* + * Decrement request count because the request will never arrive at the + * completion port. + */ + AssertMsg(RT_VALID_PTR(pReqInt->pCtxInt), + ("Invalid state. Request was canceled but wasn't submitted\n")); + + ASMAtomicDecS32(&pReqInt->pCtxInt->cRequests); + pReqInt->Rc = VERR_FILE_AIO_CANCELED; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + return VINF_SUCCESS; + } + else if (rcBSD == AIO_ALLDONE) + return VERR_FILE_AIO_COMPLETED; + else if (rcBSD == AIO_NOTCANCELED) + return VERR_FILE_AIO_IN_PROGRESS; + else + return RTErrConvertFromErrno(errno); +} + +RTDECL(int) RTFileAioReqGetRC(RTFILEAIOREQ hReq, size_t *pcbTransfered) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + AssertPtrNull(pcbTransfered); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, PREPARED, VERR_FILE_AIO_NOT_SUBMITTED); + + if ( (RT_SUCCESS(pReqInt->Rc)) + && (pcbTransfered)) + *pcbTransfered = pReqInt->cbTransfered; + + return pReqInt->Rc; +} + +RTDECL(int) RTFileAioCtxCreate(PRTFILEAIOCTX phAioCtx, uint32_t cAioReqsMax, + uint32_t fFlags) +{ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt; + AssertPtrReturn(phAioCtx, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTFILEAIOCTX_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + pCtxInt = (PRTFILEAIOCTXINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOCTXINTERNAL)); + if (RT_UNLIKELY(!pCtxInt)) + return VERR_NO_MEMORY; + + /* Init the event handle. */ + pCtxInt->iKQueue = kqueue(); + if (RT_LIKELY(pCtxInt->iKQueue > 0)) + { + pCtxInt->fFlags = fFlags; + pCtxInt->u32Magic = RTFILEAIOCTX_MAGIC; + *phAioCtx = (RTFILEAIOCTX)pCtxInt; + } + else + { + RTMemFree(pCtxInt); + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + +RTDECL(int) RTFileAioCtxDestroy(RTFILEAIOCTX hAioCtx) +{ + /* Validate the handle and ignore nil. */ + if (hAioCtx == NIL_RTFILEAIOCTX) + return VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + /* Cannot destroy a busy context. */ + if (RT_UNLIKELY(pCtxInt->cRequests)) + return VERR_FILE_AIO_BUSY; + + close(pCtxInt->iKQueue); + ASMAtomicUoWriteU32(&pCtxInt->u32Magic, RTFILEAIOCTX_MAGIC_DEAD); + RTMemFree(pCtxInt); + + return VINF_SUCCESS; +} + +RTDECL(uint32_t) RTFileAioCtxGetMaxReqCount(RTFILEAIOCTX hAioCtx) +{ + return RTFILEAIO_UNLIMITED_REQS; +} + +RTDECL(int) RTFileAioCtxAssociateWithFile(RTFILEAIOCTX hAioCtx, RTFILE hFile) +{ + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioCtxSubmit(RTFILEAIOCTX hAioCtx, PRTFILEAIOREQ pahReqs, size_t cReqs) +{ + /* + * Parameter validation. + */ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertReturn(cReqs > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + + do + { + int rcBSD = 0; + size_t cReqsSubmit = 0; + size_t i = 0; + PRTFILEAIOREQINTERNAL pReqInt; + + while ( (i < cReqs) + && (i < AIO_LISTIO_MAX)) + { + pReqInt = pahReqs[i]; + if (RTFILEAIOREQ_IS_NOT_VALID(pReqInt)) + { + /* Undo everything and stop submitting. */ + for (size_t iUndo = 0; iUndo < i; iUndo++) + { + pReqInt = pahReqs[iUndo]; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + pReqInt->pCtxInt = NULL; + pReqInt->AioCB.aio_sigevent.sigev_notify_kqueue = 0; + } + rc = VERR_INVALID_HANDLE; + break; + } + + pReqInt->AioCB.aio_sigevent.sigev_notify_kqueue = pCtxInt->iKQueue; + pReqInt->pCtxInt = pCtxInt; + RTFILEAIOREQ_SET_STATE(pReqInt, SUBMITTED); + + if (pReqInt->fFlush) + break; + + cReqsSubmit++; + i++; + } + + if (cReqsSubmit) + { + rcBSD = lio_listio(LIO_NOWAIT, (struct aiocb **)pahReqs, cReqsSubmit, NULL); + if (RT_UNLIKELY(rcBSD < 0)) + { + if (errno == EAGAIN) + rc = VERR_FILE_AIO_INSUFFICIENT_RESSOURCES; + else + rc = RTErrConvertFromErrno(errno); + + /* Check which requests got actually submitted and which not. */ + for (i = 0; i < cReqs; i++) + { + pReqInt = pahReqs[i]; + rcBSD = aio_error(&pReqInt->AioCB); + if ( rcBSD == -1 + && errno == EINVAL) + { + /* Was not submitted. */ + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + pReqInt->pCtxInt = NULL; + } + else if (rcBSD != EINPROGRESS) + { + /* The request encountered an error. */ + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + pReqInt->Rc = RTErrConvertFromErrno(rcBSD); + pReqInt->pCtxInt = NULL; + pReqInt->cbTransfered = 0; + } + } + break; + } + + ASMAtomicAddS32(&pCtxInt->cRequests, cReqsSubmit); + cReqs -= cReqsSubmit; + pahReqs += cReqsSubmit; + } + + /* Check if we have a flush request now. */ + if (cReqs && RT_SUCCESS_NP(rc)) + { + pReqInt = pahReqs[0]; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + + if (pReqInt->fFlush) + { + /* + * lio_listio does not work with flush requests so + * we have to use aio_fsync directly. + */ + rcBSD = aio_fsync(O_SYNC, &pReqInt->AioCB); + if (RT_UNLIKELY(rcBSD < 0)) + { + if (rcBSD == EAGAIN) + { + /* Was not submitted. */ + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + pReqInt->pCtxInt = NULL; + return VERR_FILE_AIO_INSUFFICIENT_RESSOURCES; + } + else + { + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + pReqInt->Rc = RTErrConvertFromErrno(errno); + pReqInt->cbTransfered = 0; + return pReqInt->Rc; + } + } + + ASMAtomicIncS32(&pCtxInt->cRequests); + cReqs--; + pahReqs++; + } + } + } while (cReqs); + + return rc; +} + +RTDECL(int) RTFileAioCtxWait(RTFILEAIOCTX hAioCtx, size_t cMinReqs, RTMSINTERVAL cMillies, + PRTFILEAIOREQ pahReqs, size_t cReqs, uint32_t *pcReqs) +{ + int rc = VINF_SUCCESS; + int cRequestsCompleted = 0; + + /* + * Validate the parameters, making sure to always set pcReqs. + */ + AssertPtrReturn(pcReqs, VERR_INVALID_POINTER); + *pcReqs = 0; /* always set */ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + AssertReturn(cReqs != 0, VERR_INVALID_PARAMETER); + AssertReturn(cReqs >= cMinReqs, VERR_OUT_OF_RANGE); + + if ( RT_UNLIKELY(ASMAtomicReadS32(&pCtxInt->cRequests) == 0) + && !(pCtxInt->fFlags & RTFILEAIOCTX_FLAGS_WAIT_WITHOUT_PENDING_REQUESTS)) + return VERR_FILE_AIO_NO_REQUEST; + + /* + * Convert the timeout if specified. + */ + struct timespec *pTimeout = NULL; + struct timespec Timeout = {0,0}; + uint64_t StartNanoTS = 0; + if (cMillies != RT_INDEFINITE_WAIT) + { + Timeout.tv_sec = cMillies / 1000; + Timeout.tv_nsec = cMillies % 1000 * 1000000; + pTimeout = &Timeout; + StartNanoTS = RTTimeNanoTS(); + } + + /* Wait for at least one. */ + if (!cMinReqs) + cMinReqs = 1; + + /* For the wakeup call. */ + Assert(pCtxInt->hThreadWait == NIL_RTTHREAD); + ASMAtomicWriteHandle(&pCtxInt->hThreadWait, RTThreadSelf()); + + while ( cMinReqs + && RT_SUCCESS_NP(rc)) + { + struct kevent aKEvents[AIO_MAXIMUM_REQUESTS_PER_CONTEXT]; + int cRequestsToWait = cMinReqs < AIO_MAXIMUM_REQUESTS_PER_CONTEXT ? cReqs : AIO_MAXIMUM_REQUESTS_PER_CONTEXT; + int rcBSD; + uint64_t StartTime; + + ASMAtomicXchgBool(&pCtxInt->fWaiting, true); + rcBSD = kevent(pCtxInt->iKQueue, NULL, 0, aKEvents, cRequestsToWait, pTimeout); + ASMAtomicXchgBool(&pCtxInt->fWaiting, false); + + if (RT_UNLIKELY(rcBSD < 0)) + { + rc = RTErrConvertFromErrno(errno); + break; + } + + uint32_t const cDone = rcBSD; + + /* Process received events. */ + for (uint32_t i = 0; i < cDone; i++) + { + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)aKEvents[i].udata; + AssertPtr(pReqInt); + Assert(pReqInt->u32Magic == RTFILEAIOREQ_MAGIC); + + /* + * Retrieve the status code here already because the + * user may omit the RTFileAioReqGetRC() call and + * we will leak kernel resources then. + * This will result in errors during submission + * of other requests as soon as the max_aio_queue_per_proc + * limit is reached. + */ + int cbTransfered = aio_return(&pReqInt->AioCB); + + if (cbTransfered < 0) + { + pReqInt->Rc = RTErrConvertFromErrno(cbTransfered); + pReqInt->cbTransfered = 0; + } + else + { + pReqInt->Rc = VINF_SUCCESS; + pReqInt->cbTransfered = cbTransfered; + } + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + pahReqs[cRequestsCompleted++] = (RTFILEAIOREQ)pReqInt; + } + + /* + * Done Yet? If not advance and try again. + */ + if (cDone >= cMinReqs) + break; + cMinReqs -= cDone; + cReqs -= cDone; + + if (cMillies != RT_INDEFINITE_WAIT) + { + /* The API doesn't return ETIMEDOUT, so we have to fix that ourselves. */ + uint64_t NanoTS = RTTimeNanoTS(); + uint64_t cMilliesElapsed = (NanoTS - StartNanoTS) / 1000000; + if (cMilliesElapsed >= cMillies) + { + rc = VERR_TIMEOUT; + break; + } + + /* The syscall supposedly updates it, but we're paranoid. :-) */ + Timeout.tv_sec = (cMillies - (RTMSINTERVAL)cMilliesElapsed) / 1000; + Timeout.tv_nsec = (cMillies - (RTMSINTERVAL)cMilliesElapsed) % 1000 * 1000000; + } + } + + /* + * Update the context state and set the return value. + */ + *pcReqs = cRequestsCompleted; + ASMAtomicSubS32(&pCtxInt->cRequests, cRequestsCompleted); + Assert(pCtxInt->hThreadWait == RTThreadSelf()); + ASMAtomicWriteHandle(&pCtxInt->hThreadWait, NIL_RTTHREAD); + + /* + * Clear the wakeup flag and set rc. + */ + if ( pCtxInt->fWokenUp + && RT_SUCCESS(rc)) + { + ASMAtomicXchgBool(&pCtxInt->fWokenUp, false); + rc = VERR_INTERRUPTED; + } + + return rc; +} + +RTDECL(int) RTFileAioCtxWakeup(RTFILEAIOCTX hAioCtx) +{ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + /** @todo r=bird: Define the protocol for how to resume work after calling + * this function. */ + + bool fWokenUp = ASMAtomicXchgBool(&pCtxInt->fWokenUp, true); + + /* + * Read the thread handle before the status flag. + * If we read the handle after the flag we might + * end up with an invalid handle because the thread + * waiting in RTFileAioCtxWakeup() might get scheduled + * before we read the flag and returns. + * We can ensure that the handle is valid if fWaiting is true + * when reading the handle before the status flag. + */ + RTTHREAD hThread; + ASMAtomicReadHandle(&pCtxInt->hThreadWait, &hThread); + bool fWaiting = ASMAtomicReadBool(&pCtxInt->fWaiting); + if ( !fWokenUp + && fWaiting) + { + /* + * If a thread waits the handle must be valid. + * It is possible that the thread returns from + * kevent() before the signal is send. + * This is no problem because we already set fWokenUp + * to true which will let the thread return VERR_INTERRUPTED + * and the next call to RTFileAioCtxWait() will not + * return VERR_INTERRUPTED because signals are not saved + * and will simply vanish if the destination thread can't + * receive it. + */ + Assert(hThread != NIL_RTTHREAD); + RTThreadPoke(hThread); + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/freebsd/mp-freebsd.cpp b/src/VBox/Runtime/r3/freebsd/mp-freebsd.cpp new file mode 100644 index 00000000..8b2f5f83 --- /dev/null +++ b/src/VBox/Runtime/r3/freebsd/mp-freebsd.cpp @@ -0,0 +1,211 @@ +/* $Id: mp-freebsd.cpp $ */ +/** @file + * IPRT - Multiprocessor, FreeBSD. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <sys/sysctl.h> + +#include <iprt/mp.h> +#include <iprt/cpuset.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/alloc.h> +#include <iprt/log.h> +#include <iprt/once.h> +#include <iprt/critsect.h> + + +/** + * Internal worker that determines the max possible CPU count. + * + * @returns Max cpus. + */ +static RTCPUID rtMpFreeBsdMaxCpus(void) +{ + int aiMib[2]; + aiMib[0] = CTL_HW; + aiMib[1] = HW_NCPU; + int cCpus = -1; + size_t cb = sizeof(cCpus); + int rc = sysctl(aiMib, RT_ELEMENTS(aiMib), &cCpus, &cb, NULL, 0); + if (rc != -1 && cCpus >= 1) + return cCpus; + AssertFailed(); + return 1; +} + + +RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu) +{ + return idCpu < RTCPUSET_MAX_CPUS && idCpu < rtMpFreeBsdMaxCpus() ? idCpu : -1; +} + + +RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu) +{ + return (unsigned)iCpu < rtMpFreeBsdMaxCpus() ? iCpu : NIL_RTCPUID; +} + + +RTDECL(RTCPUID) RTMpGetMaxCpuId(void) +{ + return rtMpFreeBsdMaxCpus() - 1; +} + + +RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu) +{ + /* + * FreeBSD doesn't support CPU hotplugging so every CPU which appears + * in the tree is also online. + */ + char szName[40]; + RTStrPrintf(szName, sizeof(szName), "dev.cpu.%d.%%driver", (int)idCpu); + + char szDriver[10]; + size_t cbDriver = sizeof(szDriver); + RT_ZERO(szDriver); /* this shouldn't be necessary. */ + int rcBsd = sysctlbyname(szName, szDriver, &cbDriver, NULL, 0); + if (rcBsd == 0) + return true; + + return false; +} + + +RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) +{ + return idCpu != NIL_RTCPUID + && idCpu < rtMpFreeBsdMaxCpus(); +} + + +RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet) +{ + RTCpuSetEmpty(pSet); + RTCPUID cMax = rtMpFreeBsdMaxCpus(); + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + if (RTMpIsCpuPossible(idCpu)) + RTCpuSetAdd(pSet, idCpu); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetCount(void) +{ + return rtMpFreeBsdMaxCpus(); +} + + +RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet) +{ + RTCpuSetEmpty(pSet); + RTCPUID cMax = rtMpFreeBsdMaxCpus(); + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + if (RTMpIsCpuOnline(idCpu)) + RTCpuSetAdd(pSet, idCpu); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetOnlineCount(void) +{ + /* + * FreeBSD has sysconf. + */ + return sysconf(_SC_NPROCESSORS_ONLN); +} + + +RTDECL(uint32_t) RTMpGetCurFrequency(RTCPUID idCpu) +{ + int uFreqCurr = 0; + size_t cbParameter = sizeof(uFreqCurr); + + if (!RTMpIsCpuOnline(idCpu)) + return 0; + + /* CPU's have a common frequency. */ + int rc = sysctlbyname("dev.cpu.0.freq", &uFreqCurr, &cbParameter, NULL, 0); + if (rc) + return 0; + + return (uint32_t)uFreqCurr; +} + + +RTDECL(uint32_t) RTMpGetMaxFrequency(RTCPUID idCpu) +{ + char szFreqLevels[20]; /* Should be enough to get the highest level which is always the first. */ + size_t cbFreqLevels = sizeof(szFreqLevels); + + if (!RTMpIsCpuOnline(idCpu)) + return 0; + + memset(szFreqLevels, 0, sizeof(szFreqLevels)); + + /* + * CPU 0 has the freq levels entry. ENOMEM is ok as we don't need all supported + * levels but only the first one. + */ + int rc = sysctlbyname("dev.cpu.0.freq_levels", szFreqLevels, &cbFreqLevels, NULL, 0); + if ( (rc && (errno != ENOMEM)) + || (cbFreqLevels == 0)) + return 0; + + /* Clear everything starting from the '/' */ + unsigned i = 0; + + do + { + if (szFreqLevels[i] == '/') + { + memset(&szFreqLevels[i], 0, sizeof(szFreqLevels) - i); + break; + } + i++; + } while (i < sizeof(szFreqLevels)); + + /* Returns 0 on failure. */ + return RTStrToUInt32(szFreqLevels); +} + diff --git a/src/VBox/Runtime/r3/freebsd/rtProcInitExePath-freebsd.cpp b/src/VBox/Runtime/r3/freebsd/rtProcInitExePath-freebsd.cpp new file mode 100644 index 00000000..5994f994 --- /dev/null +++ b/src/VBox/Runtime/r3/freebsd/rtProcInitExePath-freebsd.cpp @@ -0,0 +1,139 @@ +/* $Id: rtProcInitExePath-freebsd.cpp $ */ +/** @file + * IPRT - rtProcInitName, FreeBSD. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <sys/param.h> +#include <sys/sysctl.h> +#include <unistd.h> +#include <errno.h> +#include <dlfcn.h> +#include <link.h> + +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include "internal/process.h" +#include "internal/path.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ +#ifdef KERN_PROC_PATHNAME + int aiName[4]; + aiName[0] = CTL_KERN; + aiName[1] = KERN_PROC; + aiName[2] = KERN_PROC_PATHNAME; /* This was introduced in FreeBSD 6.0, thus the #ifdef above. */ + aiName[3] = -1; /* Shorthand for the current process. */ + + size_t cchExePath = cchPath; + if (sysctl(aiName, RT_ELEMENTS(aiName), pszPath, &cchExePath, NULL, 0) == 0) + { + const char *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszPath, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszPath=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, cchExePath, pszPath), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp, pszPath); + } + return rc; + } + + int rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("rc=%Rrc errno=%d cchExePath=%d\n", rc, errno, cchExePath)); + return rc; + +#else + + /* + * Read the /proc/curproc/file link, convert to native and return it. + */ + int cchLink = readlink("/proc/curproc/file", pszPath, cchPath - 1); + if (cchLink > 0 && (size_t)cchLink <= cchPath - 1) + { + pszPath[cchLink] = '\0'; + + char const *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszPath); + AssertMsgRCReturn(rc, ("rc=%Rrc pszLink=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, cchLink, pszPath), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp); + } + return rc; + } + + int err = errno; + + /* + * Fall back on the dynamic linker since /proc is optional. + */ + void *hExe = dlopen(NULL, 0); + if (hExe) + { + struct link_map const *pLinkMap = 0; + if (dlinfo(hExe, RTLD_DI_LINKMAP, &pLinkMap) == 0) + { + const char *pszImageName = pLinkMap->l_name; + if (*pszImageName == '/') /* this may not always be absolute, despite the docs. :-( */ + { + char const *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszImageName, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszImageName=\"%s\"\n", rc, pszImageName), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp, pszPath); + } + return rc; + } + /** @todo Try search the PATH for the file name or append the current + * directory, which ever makes sense... */ + } + } + + int rc = RTErrConvertFromErrno(err); + AssertMsgFailed(("rc=%Rrc err=%d cchLink=%d hExe=%p\n", rc, err, cchLink, hExe)); + return rc; +#endif +} + diff --git a/src/VBox/Runtime/r3/freebsd/systemmem-freebsd.cpp b/src/VBox/Runtime/r3/freebsd/systemmem-freebsd.cpp new file mode 100644 index 00000000..b662fc67 --- /dev/null +++ b/src/VBox/Runtime/r3/freebsd/systemmem-freebsd.cpp @@ -0,0 +1,108 @@ +/* $Id: systemmem-freebsd.cpp $ */ +/** @file + * IPRT - RTSystemQueryTotalRam, Linux ring-3. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> + +#include <sys/types.h> +#include <sys/sysctl.h> +#include <errno.h> + + +RTDECL(int) RTSystemQueryTotalRam(uint64_t *pcb) +{ + int rc = VINF_SUCCESS; + u_long cbMemPhys = 0; + size_t cbParameter = sizeof(cbMemPhys); + + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + if (!sysctlbyname("hw.physmem", &cbMemPhys, &cbParameter, NULL, 0)) + { + *pcb = cbMemPhys; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTSystemQueryAvailableRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + u_int cPagesMemFree = 0; + u_int cPagesMemInactive = 0; + u_int cPagesMemCached = 0; + u_int cPagesMemUsed = 0; + int cbPage = 0; + size_t cbParameter; + int cProcessed = 0; + + cbParameter = sizeof(cPagesMemFree); + if (sysctlbyname("vm.stats.vm.v_free_count", &cPagesMemFree, &cbParameter, NULL, 0)) + rc = RTErrConvertFromErrno(errno); + cbParameter = sizeof(cPagesMemUsed); + if ( RT_SUCCESS(rc) + && sysctlbyname("vm.stats.vm.v_active_count", &cPagesMemUsed, &cbParameter, NULL, 0)) + rc = RTErrConvertFromErrno(errno); + cbParameter = sizeof(cPagesMemInactive); + if ( RT_SUCCESS(rc) + && sysctlbyname("vm.stats.vm.v_inactive_count", &cPagesMemInactive, &cbParameter, NULL, 0)) + rc = RTErrConvertFromErrno(errno); + cbParameter = sizeof(cPagesMemCached); + if ( RT_SUCCESS(rc) + && sysctlbyname("vm.stats.vm.v_cache_count", &cPagesMemCached, &cbParameter, NULL, 0)) + rc = RTErrConvertFromErrno(errno); + cbParameter = sizeof(cbPage); + if ( RT_SUCCESS(rc) + && sysctlbyname("hw.pagesize", &cbPage, &cbParameter, NULL, 0)) + rc = RTErrConvertFromErrno(errno); + + if (RT_SUCCESS(rc)) + *pcb = (cPagesMemFree + cPagesMemInactive + cPagesMemCached ) * cbPage; + + return rc; +} + diff --git a/src/VBox/Runtime/r3/fs.cpp b/src/VBox/Runtime/r3/fs.cpp new file mode 100644 index 00000000..41f7de8d --- /dev/null +++ b/src/VBox/Runtime/r3/fs.cpp @@ -0,0 +1,274 @@ +/* $Id: fs.cpp $ */ +/** @file + * IPRT - File System. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fs.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include "internal/fs.h" + + +/** + * Converts dos-style attributes to Unix attributes. + * + * @returns Normalized mode mask. + * @param fMode The mode mask containing dos-style attributes only. + * @param pszName The filename which this applies to (exe check). + * @param cbName The length of that filename. (optional, set 0) + * @param uReparseTag The reparse tag if RTFS_DOS_NT_REPARSE_POINT is set. + * @param fType RTFS_TYPE_XXX to normalize against, 0 if not known. + */ +RTFMODE rtFsModeFromDos(RTFMODE fMode, const char *pszName, size_t cbName, uint32_t uReparseTag, RTFMODE fType) +{ + Assert(!(fType & ~RTFS_TYPE_MASK)); + + fMode &= ~((1 << RTFS_DOS_SHIFT) - 1); + + /* Forcibly set the directory attribute if caller desires it. */ + if (fType == RTFS_TYPE_DIRECTORY) + fMode |= RTFS_DOS_DIRECTORY; + + /* Everything is readable. */ + fMode |= RTFS_UNIX_IRUSR | RTFS_UNIX_IRGRP | RTFS_UNIX_IROTH; + if (fMode & RTFS_DOS_DIRECTORY) + /* Directories are executable. */ + fMode |= RTFS_TYPE_DIRECTORY | RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH; + else + { + fMode |= RTFS_TYPE_FILE; + if (!cbName && pszName) + cbName = strlen(pszName); + if (cbName >= 4 && pszName[cbName - 4] == '.') + { + /* check for executable extension. */ + const char *pszExt = &pszName[cbName - 3]; + char szExt[4]; + szExt[0] = RT_C_TO_LOWER(pszExt[0]); + szExt[1] = RT_C_TO_LOWER(pszExt[1]); + szExt[2] = RT_C_TO_LOWER(pszExt[2]); + szExt[3] = '\0'; + if ( !memcmp(szExt, "exe", 4) + || !memcmp(szExt, "bat", 4) + || !memcmp(szExt, "com", 4) + || !memcmp(szExt, "cmd", 4) + || !memcmp(szExt, "btm", 4) + ) + fMode |= RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH; + } + } + + /* Is it really a symbolic link? */ + if ((fMode & RTFS_DOS_NT_REPARSE_POINT) && uReparseTag == RTFSMODE_SYMLINK_REPARSE_TAG) + fMode = (fMode & ~RTFS_TYPE_MASK) | RTFS_TYPE_SYMLINK; + + /* + * Writable? + * + * Note! We ignore the read-only flag on directories as windows seems to + * use it for purposes other than writability (@ticketref{18345}): + * https://support.microsoft.com/en-gb/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo + * + */ + if ((fMode & (RTFS_DOS_DIRECTORY | RTFS_DOS_READONLY)) != RTFS_DOS_READONLY) + fMode |= RTFS_UNIX_IWUSR | RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH; + return fMode; +} + + +/** + * Converts Unix attributes to Dos-style attributes. + * + * @returns File mode mask. + * @param fMode The mode mask containing dos-style attributes only. + * @param pszName The filename which this applies to (hidden check). + * @param cbName The length of that filename. (optional, set 0) + * @param fType RTFS_TYPE_XXX to normalize against, 0 if not known. + */ +RTFMODE rtFsModeFromUnix(RTFMODE fMode, const char *pszName, size_t cbName, RTFMODE fType) +{ + Assert(!(fType & ~RTFS_TYPE_MASK)); + NOREF(cbName); + + fMode &= RTFS_UNIX_MASK; + + if (!(fType & RTFS_TYPE_MASK) && fType) + fMode |= fType; + + if (!(fMode & (RTFS_UNIX_IWUSR | RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH))) + fMode |= RTFS_DOS_READONLY; + if (RTFS_IS_DIRECTORY(fMode)) + fMode |= RTFS_DOS_DIRECTORY; + if (!(fMode & RTFS_DOS_MASK)) + fMode |= RTFS_DOS_NT_NORMAL; + if (!(fMode & RTFS_DOS_HIDDEN) && pszName) + { + pszName = RTPathFilename(pszName); + if ( pszName + && pszName[0] == '.' + && pszName[1] != '\0' /* exclude "." */ + && (pszName[1] != '.' || pszName[2] != '\0')) /* exclude ".." */ + fMode |= RTFS_DOS_HIDDEN; + } + return fMode; +} + + +/** + * Normalizes the give mode mask. + * + * It will create the missing unix or dos mask from the other (one + * of them is required by all APIs), and guess the file type if that's + * missing. + * + * @returns Normalized file mode. + * @param fMode The mode mask that may contain a partial/incomplete mask. + * @param pszName The filename which this applies to (exe check). + * @param cbName The length of that filename. (optional, set 0) + * @param fType RTFS_TYPE_XXX to normalize against, 0 if not known. + */ +RTFMODE rtFsModeNormalize(RTFMODE fMode, const char *pszName, size_t cbName, RTFMODE fType) +{ + Assert(!(fType & ~RTFS_TYPE_MASK)); + + if (!(fMode & RTFS_UNIX_MASK)) + fMode = rtFsModeFromDos(fMode, pszName, cbName, RTFSMODE_SYMLINK_REPARSE_TAG, fType); + else if (!(fMode & RTFS_DOS_MASK)) + fMode = rtFsModeFromUnix(fMode, pszName, cbName, fType); + else if (!(fMode & RTFS_TYPE_MASK)) + fMode |= fMode & RTFS_DOS_DIRECTORY ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE; + else if (RTFS_IS_DIRECTORY(fMode)) + fMode |= RTFS_DOS_DIRECTORY; + return fMode; +} + + +/** + * Checks if the file mode is valid or not. + * + * @return true if valid. + * @return false if invalid, done bitching. + * @param fMode The file mode. + */ +bool rtFsModeIsValid(RTFMODE fMode) +{ + AssertMsgReturn( (!RTFS_IS_DIRECTORY(fMode) && !(fMode & RTFS_DOS_DIRECTORY)) + || (RTFS_IS_DIRECTORY(fMode) && (fMode & RTFS_DOS_DIRECTORY)), + ("%RTfmode\n", fMode), false); + AssertMsgReturn(RTFS_TYPE_MASK & fMode, + ("%RTfmode\n", fMode), false); + /** @todo more checks! */ + return true; +} + + +/** + * Checks if the file mode is valid as a permission mask or not. + * + * @return true if valid. + * @return false if invalid, done bitching. + * @param fMode The file mode. + */ +bool rtFsModeIsValidPermissions(RTFMODE fMode) +{ + AssertMsgReturn( (!RTFS_IS_DIRECTORY(fMode) && !(fMode & RTFS_DOS_DIRECTORY)) + || (RTFS_IS_DIRECTORY(fMode) && (fMode & RTFS_DOS_DIRECTORY)), + ("%RTfmode\n", fMode), false); + /** @todo more checks! */ + return true; +} + + +RTDECL(const char *) RTFsTypeName(RTFSTYPE enmType) +{ + switch (enmType) + { + case RTFSTYPE_UNKNOWN: return "unknown"; + case RTFSTYPE_UDF: return "udf"; + case RTFSTYPE_ISO9660: return "iso9660"; + case RTFSTYPE_FUSE: return "fuse"; + case RTFSTYPE_VBOXSHF: return "vboxshf"; + + case RTFSTYPE_EXT: return "ext"; + case RTFSTYPE_EXT2: return "ext2"; + case RTFSTYPE_EXT3: return "ext3"; + case RTFSTYPE_EXT4: return "ext4"; + case RTFSTYPE_XFS: return "xfs"; + case RTFSTYPE_CIFS: return "cifs"; + case RTFSTYPE_SMBFS: return "smbfs"; + case RTFSTYPE_TMPFS: return "tmpfs"; + case RTFSTYPE_SYSFS: return "sysfs"; + case RTFSTYPE_PROC: return "proc"; + case RTFSTYPE_OCFS2: return "ocfs2"; + case RTFSTYPE_BTRFS: return "btrfs"; + + case RTFSTYPE_NTFS: return "ntfs"; + case RTFSTYPE_FAT: return "fat"; + case RTFSTYPE_EXFAT: return "exfat"; + case RTFSTYPE_REFS: return "refs"; + + case RTFSTYPE_ZFS: return "zfs"; + case RTFSTYPE_UFS: return "ufs"; + case RTFSTYPE_NFS: return "nfs"; + + case RTFSTYPE_HFS: return "hfs"; + case RTFSTYPE_APFS: return "apfs"; + case RTFSTYPE_AUTOFS: return "autofs"; + case RTFSTYPE_DEVFS: return "devfs"; + + case RTFSTYPE_HPFS: return "hpfs"; + case RTFSTYPE_JFS: return "jfs"; + + case RTFSTYPE_END: return "end"; + case RTFSTYPE_32BIT_HACK: break; + } + + /* Don't put this in as 'default:', we wish GCC to warn about missing cases. */ + static char s_asz[4][64]; + static uint32_t volatile s_i = 0; + uint32_t i = ASMAtomicIncU32(&s_i) % RT_ELEMENTS(s_asz); + RTStrPrintf(s_asz[i], sizeof(s_asz[i]), "type=%d", enmType); + return s_asz[i]; +} + diff --git a/src/VBox/Runtime/r3/ftp-server.cpp b/src/VBox/Runtime/r3/ftp-server.cpp new file mode 100644 index 00000000..425aad03 --- /dev/null +++ b/src/VBox/Runtime/r3/ftp-server.cpp @@ -0,0 +1,2589 @@ +/* $Id: ftp-server.cpp $ */ +/** @file + * Generic FTP server (RFC 959) implementation. + * + * Partly also implements RFC 3659 (Extensions to FTP, for "SIZE", ++). + * + * Known limitations so far: + * - UTF-8 support only. + * - Only supports ASCII + binary (image type) file streams for now. + * - No directory / file caching yet. + * - No support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++). + * - No FTPS / SFTP support. + * - No passive mode ("PASV") support. + * - No IPv6 support. + * - No proxy support. + * - No FXP support. + */ + +/* + * Copyright (C) 2020-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FTP +#include <iprt/ftp.h> +#include "internal/iprt.h" +#include "internal/magics.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/circbuf.h> +#include <iprt/err.h> +#include <iprt/file.h> /* For file mode flags. */ +#include <iprt/getopt.h> +#include <iprt/mem.h> +#include <iprt/log.h> +#include <iprt/path.h> +#include <iprt/poll.h> +#include <iprt/socket.h> +#include <iprt/sort.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/tcp.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Internal FTP server instance. + */ +typedef struct RTFTPSERVERINTERNAL +{ + /** Magic value. */ + uint32_t u32Magic; + /** Callback table. */ + RTFTPSERVERCALLBACKS Callbacks; + /** Pointer to TCP server instance. */ + PRTTCPSERVER pTCPServer; + /** Number of currently connected clients. */ + uint32_t cClients; + /** Pointer to user-specific data. Optional. */ + void *pvUser; + /** Size of user-specific data. Optional. */ + size_t cbUser; +} RTFTPSERVERINTERNAL; +/** Pointer to an internal FTP server instance. */ +typedef RTFTPSERVERINTERNAL *PRTFTPSERVERINTERNAL; + +/** + * FTP directory entry. + */ +typedef struct RTFTPDIRENTRY +{ + /** The information about the entry. */ + RTFSOBJINFO Info; + /** Symbolic link target (allocated after the name). */ + const char *pszTarget; + /** Owner if applicable (allocated after the name). */ + const char *pszOwner; + /** Group if applicable (allocated after the name). */ + const char *pszGroup; + /** The length of szName. */ + size_t cchName; + /** The entry name. */ + RT_FLEXIBLE_ARRAY_EXTENSION + char szName[RT_FLEXIBLE_ARRAY]; +} RTFTPDIRENTRY; +/** Pointer to a FTP directory entry. */ +typedef RTFTPDIRENTRY *PRTFTPDIRENTRY; +/** Pointer to a FTP directory entry pointer. */ +typedef PRTFTPDIRENTRY *PPRTFTPDIRENTRY; + +/** + * Collection of directory entries. + * Used for also caching stuff. + */ +typedef struct RTFTPDIRCOLLECTION +{ + /** Current size of papEntries. */ + size_t cEntries; + /** Memory allocated for papEntries. */ + size_t cEntriesAllocated; + /** Current entries pending sorting and display. */ + PPRTFTPDIRENTRY papEntries; + + /** Total number of bytes allocated for the above entries. */ + uint64_t cbTotalAllocated; + /** Total number of file content bytes. */ + uint64_t cbTotalFiles; + +} RTFTPDIRCOLLECTION; +/** Pointer to a directory collection. */ +typedef RTFTPDIRCOLLECTION *PRTFTPDIRCOLLECTION; +/** Pointer to a directory entry collection pointer. */ +typedef PRTFTPDIRCOLLECTION *PPRTFTPDIRCOLLECTION; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTFTPSERVER_VALID_RETURN_RC(hFTPServer, a_rc) \ + do { \ + AssertPtrReturn((hFTPServer), (a_rc)); \ + AssertReturn((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC, (a_rc)); \ + } while (0) + +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTFTPSERVER_VALID_RETURN(hFTPServer) RTFTPSERVER_VALID_RETURN_RC((hFTPServer), VERR_INVALID_HANDLE) + +/** Validates a handle and returns (void) if not valid. */ +#define RTFTPSERVER_VALID_RETURN_VOID(hFTPServer) \ + do { \ + AssertPtrReturnVoid(hFTPServer); \ + AssertReturnVoid((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC); \ + } while (0) + + +/** Handles a FTP server callback with no arguments and returns. */ +#define RTFTPSERVER_HANDLE_CALLBACK_RET(a_Name) \ + do \ + { \ + PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTFTPCALLBACKDATA Data = { &pClient->State }; \ + return pCallbacks->a_Name(&Data); \ + } \ + return VERR_NOT_IMPLEMENTED; \ + } while (0) + +/** Handles a FTP server callback with no arguments and sets rc accordingly. */ +#define RTFTPSERVER_HANDLE_CALLBACK(a_Name) \ + do \ + { \ + PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \ + rc = pCallbacks->a_Name(&Data); \ + } \ + else \ + rc = VERR_NOT_IMPLEMENTED; \ + } while (0) + +/** Handles a FTP server callback with arguments and sets rc accordingly. */ +#define RTFTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \ + do \ + { \ + PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \ + rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \ + } \ + else \ + rc = VERR_NOT_IMPLEMENTED; \ + } while (0) + +/** Handles a FTP server callback with arguments and returns. */ +#define RTFTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \ + do \ + { \ + PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \ + return pCallbacks->a_Name(&Data, __VA_ARGS__); \ + } \ + return VERR_NOT_IMPLEMENTED; \ + } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Supported FTP server command IDs. + * Alphabetically, named after their official command names. */ +typedef enum RTFTPSERVERCMD +{ + /** Invalid command, do not use. Always must come first. */ + RTFTPSERVERCMD_INVALID = 0, + /** Aborts the current command on the server. */ + RTFTPSERVERCMD_ABOR, + /** Changes the current working directory. */ + RTFTPSERVERCMD_CDUP, + /** Changes the current working directory. */ + RTFTPSERVERCMD_CWD, + /** Reports features supported by the server. */ + RTFTPSERVERCMD_FEAT, + /** Lists a directory. */ + RTFTPSERVERCMD_LIST, + /** Sets the transfer mode. */ + RTFTPSERVERCMD_MODE, + /** Sends a nop ("no operation") to the server. */ + RTFTPSERVERCMD_NOOP, + /** Sets the password for authentication. */ + RTFTPSERVERCMD_PASS, + /** Sets the port to use for the data connection. */ + RTFTPSERVERCMD_PORT, + /** Gets the current working directory. */ + RTFTPSERVERCMD_PWD, + /** Get options. Needed in conjunction with the FEAT command. */ + RTFTPSERVERCMD_OPTS, + /** Terminates the session (connection). */ + RTFTPSERVERCMD_QUIT, + /** Retrieves a specific file. */ + RTFTPSERVERCMD_RETR, + /** Retrieves the size of a file. */ + RTFTPSERVERCMD_SIZE, + /** Retrieves the current status of a transfer. */ + RTFTPSERVERCMD_STAT, + /** Sets the structure type to use. */ + RTFTPSERVERCMD_STRU, + /** Gets the server's OS info. */ + RTFTPSERVERCMD_SYST, + /** Sets the (data) representation type. */ + RTFTPSERVERCMD_TYPE, + /** Sets the user name for authentication. */ + RTFTPSERVERCMD_USER, + /** End marker. */ + RTFTPSERVERCMD_END, + /** The usual 32-bit hack. */ + RTFTPSERVERCMD_32BIT_HACK = 0x7fffffff +} RTFTPSERVERCMD; + +struct RTFTPSERVERCLIENT; + +/** + * Structure for maintaining a single data connection. + */ +typedef struct RTFTPSERVERDATACONN +{ + /** Pointer to associated client of this data connection. */ + RTFTPSERVERCLIENT *pClient; + /** Data connection IP. */ + RTNETADDRIPV4 Addr; + /** Data connection port number. */ + uint16_t uPort; + /** The current data socket to use. + * Can be NIL_RTSOCKET if no data port has been specified (yet) or has been closed. */ + RTSOCKET hSocket; + /** Thread serving the data connection. */ + RTTHREAD hThread; + /** Thread started indicator. */ + volatile bool fStarted; + /** Thread stop indicator. */ + volatile bool fStop; + /** Thread stopped indicator. */ + volatile bool fStopped; + /** Overall result when closing the data connection. */ + int rc; + /** Number of command arguments. */ + uint8_t cArgs; + /** Command arguments array. Optional and can be NULL. + * Will be free'd by the data connection thread. */ + char** papszArgs; + /** Circular buffer for caching data before writing. */ + PRTCIRCBUF pCircBuf; +} RTFTPSERVERDATACONN; +/** Pointer to a data connection struct. */ +typedef RTFTPSERVERDATACONN *PRTFTPSERVERDATACONN; + +/** + * Structure for maintaining an internal FTP server client. + */ +typedef struct RTFTPSERVERCLIENT +{ + /** Pointer to internal server state. */ + PRTFTPSERVERINTERNAL pServer; + /** Socket handle the client is bound to. */ + RTSOCKET hSocket; + /** Actual client state. */ + RTFTPSERVERCLIENTSTATE State; + /** The last set data connection IP. */ + RTNETADDRIPV4 DataConnAddr; + /** The last set data connection port number. */ + uint16_t uDataConnPort; + /** Data connection information. + * At the moment we only allow one data connection per client at a time. */ + PRTFTPSERVERDATACONN pDataConn; +} RTFTPSERVERCLIENT; +/** Pointer to an internal FTP server client state. */ +typedef RTFTPSERVERCLIENT *PRTFTPSERVERCLIENT; + +/** Function pointer declaration for a specific FTP server command handler. */ +typedef DECLCALLBACKTYPE(int, FNRTFTPSERVERCMD,(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)); +/** Pointer to a FNRTFTPSERVERCMD(). */ +typedef FNRTFTPSERVERCMD *PFNRTFTPSERVERCMD; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort); +static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn); +static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn); +static int rtFtpServerDataConnStart(PRTFTPSERVERDATACONN pDataConn, PFNRTTHREAD pfnThread, uint8_t cArgs, const char * const *apszArgs); +static int rtFtpServerDataConnStop(PRTFTPSERVERDATACONN pDataConn); +static void rtFtpServerDataConnDestroy(PRTFTPSERVERDATACONN pDataConn); +static int rtFtpServerDataConnFlush(PRTFTPSERVERDATACONN pDataConn); + +static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState); + +/** @name Command handlers. + * @{ + */ +static FNRTFTPSERVERCMD rtFtpServerHandleABOR; +static FNRTFTPSERVERCMD rtFtpServerHandleCDUP; +static FNRTFTPSERVERCMD rtFtpServerHandleCWD; +static FNRTFTPSERVERCMD rtFtpServerHandleFEAT; +static FNRTFTPSERVERCMD rtFtpServerHandleLIST; +static FNRTFTPSERVERCMD rtFtpServerHandleMODE; +static FNRTFTPSERVERCMD rtFtpServerHandleNOOP; +static FNRTFTPSERVERCMD rtFtpServerHandlePASS; +static FNRTFTPSERVERCMD rtFtpServerHandlePORT; +static FNRTFTPSERVERCMD rtFtpServerHandlePWD; +static FNRTFTPSERVERCMD rtFtpServerHandleOPTS; +static FNRTFTPSERVERCMD rtFtpServerHandleQUIT; +static FNRTFTPSERVERCMD rtFtpServerHandleRETR; +static FNRTFTPSERVERCMD rtFtpServerHandleSIZE; +static FNRTFTPSERVERCMD rtFtpServerHandleSTAT; +static FNRTFTPSERVERCMD rtFtpServerHandleSTRU; +static FNRTFTPSERVERCMD rtFtpServerHandleSYST; +static FNRTFTPSERVERCMD rtFtpServerHandleTYPE; +static FNRTFTPSERVERCMD rtFtpServerHandleUSER; +/** @} */ + +/** + * Structure for maintaining a single command entry for the command table. + */ +typedef struct RTFTPSERVERCMD_ENTRY +{ + /** Command ID. */ + RTFTPSERVERCMD enmCmd; + /** Command represented as ASCII string. */ + char szCmd[RTFTPSERVER_MAX_CMD_LEN]; + /** Whether the commands needs a logged in (valid) user. */ + bool fNeedsUser; + /** Function pointer invoked to handle the command. */ + PFNRTFTPSERVERCMD pfnCmd; +} RTFTPSERVERCMD_ENTRY; +/** Pointer to a command entry. */ +typedef RTFTPSERVERCMD_ENTRY *PRTFTPSERVERCMD_ENTRY; + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Table of handled commands. + */ +static const RTFTPSERVERCMD_ENTRY g_aCmdMap[] = +{ + { RTFTPSERVERCMD_ABOR, "ABOR", true, rtFtpServerHandleABOR }, + { RTFTPSERVERCMD_CDUP, "CDUP", true, rtFtpServerHandleCDUP }, + { RTFTPSERVERCMD_CWD, "CWD", true, rtFtpServerHandleCWD }, + { RTFTPSERVERCMD_FEAT, "FEAT", false, rtFtpServerHandleFEAT }, + { RTFTPSERVERCMD_LIST, "LIST", true, rtFtpServerHandleLIST }, + { RTFTPSERVERCMD_MODE, "MODE", true, rtFtpServerHandleMODE }, + { RTFTPSERVERCMD_NOOP, "NOOP", true, rtFtpServerHandleNOOP }, + { RTFTPSERVERCMD_PASS, "PASS", false, rtFtpServerHandlePASS }, + { RTFTPSERVERCMD_PORT, "PORT", true, rtFtpServerHandlePORT }, + { RTFTPSERVERCMD_PWD, "PWD", true, rtFtpServerHandlePWD }, + { RTFTPSERVERCMD_OPTS, "OPTS", false, rtFtpServerHandleOPTS }, + { RTFTPSERVERCMD_QUIT, "QUIT", false, rtFtpServerHandleQUIT }, + { RTFTPSERVERCMD_RETR, "RETR", true, rtFtpServerHandleRETR }, + { RTFTPSERVERCMD_SIZE, "SIZE", true, rtFtpServerHandleSIZE }, + { RTFTPSERVERCMD_STAT, "STAT", true, rtFtpServerHandleSTAT }, + { RTFTPSERVERCMD_STRU, "STRU", true, rtFtpServerHandleSTRU }, + { RTFTPSERVERCMD_SYST, "SYST", false, rtFtpServerHandleSYST }, + { RTFTPSERVERCMD_TYPE, "TYPE", true, rtFtpServerHandleTYPE }, + { RTFTPSERVERCMD_USER, "USER", false, rtFtpServerHandleUSER }, + { RTFTPSERVERCMD_END, "", false, NULL } +}; + +/** RFC-1123 month of the year names. */ +static const char * const g_apszMonths[1+12] = +{ + "000", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/** Feature string which represents all commands we support in addition to RFC 959 (see RFC 2398). + * Must match the command table above. + * + * Don't forget the beginning space (" ") at each feature. */ +#define RTFTPSERVER_FEATURES_STRING \ + " SIZE\r\n" \ + " UTF8" + +/** Maximum length in characters a FTP server path can have (excluding termination). */ +#define RTFTPSERVER_MAX_PATH RTPATH_MAX + + +/********************************************************************************************************************************* +* Protocol Functions * +*********************************************************************************************************************************/ + +/** + * Replies a (three digit) reply code back to the client. + * + * @returns VBox status code. + * @param pClient Client to reply to. + * @param enmReply Reply code to send. + */ +static int rtFtpServerSendReplyRc(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply) +{ + /* Note: If we don't supply any additional text, make sure to include an empty stub, as + * some clients expect this as part of their parsing code. */ + char szReply[32]; + RTStrPrintf2(szReply, sizeof(szReply), "%RU32 -\r\n", enmReply); + + LogFlowFunc(("Sending reply code %RU32\n", enmReply)); + + return RTTcpWrite(pClient->hSocket, szReply, strlen(szReply)); +} + +/** + * Replies a (three digit) reply code with a custom message back to the client. + * + * @returns VBox status code. + * @param pClient Client to reply to. + * @param enmReply Reply code to send. + * @param pszFormat Format string of message to send with the reply code. + */ +static int rtFtpServerSendReplyRcEx(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply, + const char *pszFormat, ...) +{ + char *pszMsg = NULL; + + va_list args; + va_start(args, pszFormat); + char *pszFmt = NULL; + const int cch = RTStrAPrintfV(&pszFmt, pszFormat, args); + va_end(args); + AssertReturn(cch > 0, VERR_NO_MEMORY); + + int rc = RTStrAPrintf(&pszMsg, "%RU32 -", enmReply); + AssertRCReturn(rc, rc); + + /** @todo Support multi-line replies (see 4.2ff). */ + + if (pszFmt) + { + rc = RTStrAAppend(&pszMsg, " "); + AssertRCReturn(rc, rc); + + rc = RTStrAAppend(&pszMsg, pszFmt); + AssertRCReturn(rc, rc); + } + + + rc = RTStrAAppend(&pszMsg, "\r\n"); + AssertRCReturn(rc, rc); + + RTStrFree(pszFmt); + + rc = RTTcpWrite(pClient->hSocket, pszMsg, strlen(pszMsg)); + + RTStrFree(pszMsg); + + return rc; +} + +/** + * Replies a string back to the client. + * + * @returns VBox status code. + * @param pClient Client to reply to. + * @param pszFormat Format to reply. + * @param ... Format arguments. + */ +static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + const int cch = RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + AssertReturn(cch > 0, VERR_NO_MEMORY); + + int rc = RTStrAAppend(&psz, "\r\n"); + AssertRCReturn(rc, rc); + + LogFlowFunc(("Sending reply '%s'\n", psz)); + + rc = RTTcpWrite(pClient->hSocket, psz, strlen(psz)); + + RTStrFree(psz); + + return rc; +} + +/** + * Validates if a given absolute path is valid or not. + * + * @returns \c true if path is valid, or \c false if not. + * @param pszPath Path to check. + * @param fIsAbsolute Whether the path to check is an absolute path or not. + */ +static bool rtFtpServerPathIsValid(const char *pszPath, bool fIsAbsolute) +{ + if (!pszPath) + return false; + + bool fIsValid = strlen(pszPath) + && RTStrIsValidEncoding(pszPath) + && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */ + if ( fIsValid + && fIsAbsolute) + { + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc2)) + { + fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode) + || RTFS_IS_FILE(objInfo.Attr.fMode); + + /* No symlinks and other stuff not allowed. */ + } + else + fIsValid = false; + } + + LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid)); + return fIsValid; +} + +/** + * Sets the current working directory for a client. + * + * @returns VBox status code. + * @param pState Client state to set current working directory for. + * @param pszPath Working directory to set. + */ +static int rtFtpSetCWD(PRTFTPSERVERCLIENTSTATE pState, const char *pszPath) +{ + RTStrFree(pState->pszCWD); + + if (!rtFtpServerPathIsValid(pszPath, false /* fIsAbsolute */)) + return VERR_INVALID_PARAMETER; + + pState->pszCWD = RTStrDup(pszPath); + + LogFlowFunc(("Current CWD is now '%s'\n", pState->pszCWD)); + + int rc = pState->pszCWD ? VINF_SUCCESS : VERR_NO_MEMORY; + AssertRC(rc); + return rc; +} + +/** + * Looks up an user account. + * + * @returns VBox status code, or VERR_NOT_FOUND if user has not been found. + * @param pClient Client to look up user for. + * @param pszUser User name to look up. + */ +static int rtFtpServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pszUser) +{ + RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pszUser); +} + +/** + * Handles the actual client authentication. + * + * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed. + * @param pClient Client to authenticate. + * @param pszUser User name to authenticate with. + * @param pszPassword Password to authenticate with. + */ +static int rtFtpServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pszUser, const char *pszPassword) +{ + RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pszUser, pszPassword); +} + +/** + * Converts a RTFSOBJINFO struct to a string. + * + * @returns VBox status code. + * @param pObjInfo RTFSOBJINFO object to convert. + * @param pszFsObjInfo Where to store the output string. + * @param cbFsObjInfo Size of the output string in bytes. + */ +static int rtFtpServerFsObjInfoToStr(PRTFSOBJINFO pObjInfo, char *pszFsObjInfo, size_t cbFsObjInfo) +{ + RTFMODE fMode = pObjInfo->Attr.fMode; + char chFileType; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FIFO: chFileType = 'f'; break; + case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break; + case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break; + case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break; + case RTFS_TYPE_FILE: chFileType = '-'; break; + case RTFS_TYPE_SYMLINK: chFileType = 'l'; break; + case RTFS_TYPE_SOCKET: chFileType = 's'; break; + case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break; + default: chFileType = '?'; break; + } + + char szTimeBirth[RTTIME_STR_LEN]; + char szTimeChange[RTTIME_STR_LEN]; + char szTimeModification[RTTIME_STR_LEN]; + char szTimeAccess[RTTIME_STR_LEN]; + +#define INFO_TO_STR(a_Format, ...) \ + do \ + { \ + const ssize_t cchSize = RTStrPrintf2(szTemp, sizeof(szTemp), a_Format, __VA_ARGS__); \ + AssertReturn(cchSize > 0, VERR_BUFFER_OVERFLOW); \ + const int rc2 = RTStrCat(pszFsObjInfo, cbFsObjInfo, szTemp); \ + AssertRCReturn(rc2, rc2); \ + } while (0); + + char szTemp[128]; + + INFO_TO_STR("%c", chFileType); + INFO_TO_STR("%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-'); + INFO_TO_STR("%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-'); + INFO_TO_STR("%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-'); + + INFO_TO_STR( " %c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-'); + + INFO_TO_STR( " %d %4d %4d %10lld %10lld", + pObjInfo->Attr.u.Unix.cHardlinks, + pObjInfo->Attr.u.Unix.uid, + pObjInfo->Attr.u.Unix.gid, + pObjInfo->cbObject, + pObjInfo->cbAllocated); + + INFO_TO_STR( " %s %s %s %s", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) ); + +#undef INFO_TO_STR + + return VINF_SUCCESS; +} + +/** + * Parses a string which consists of an IPv4 (ww,xx,yy,zz) and a port number (hi,lo), all separated by comma delimiters. + * See RFC 959, 4.1.2. + * + * @returns VBox status code. + * @param pszStr String to parse. + * @param pAddr Where to store the IPv4 address on success. + * @param puPort Where to store the port number on success. + */ +static int rtFtpParseHostAndPort(const char *pszStr, PRTNETADDRIPV4 pAddr, uint16_t *puPort) +{ + AssertPtrReturn(pszStr, VERR_INVALID_POINTER); + AssertPtrReturn(pAddr, VERR_INVALID_POINTER); + AssertPtrReturn(puPort, VERR_INVALID_POINTER); + + char *pszNext; + int rc; + + /* Parse IP (v4). */ + /** @todo I don't think IPv6 ever will be a thing here, or will it? */ + rc = RTStrToUInt8Ex(pszStr, &pszNext, 10, &pAddr->au8[0]); + if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) + return VERR_INVALID_PARAMETER; + if (*pszNext++ != ',') + return VERR_INVALID_PARAMETER; + + rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]); + if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) + return VERR_INVALID_PARAMETER; + if (*pszNext++ != ',') + return VERR_INVALID_PARAMETER; + + rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]); + if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) + return VERR_INVALID_PARAMETER; + if (*pszNext++ != ',') + return VERR_INVALID_PARAMETER; + + rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]); + if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS) + return VERR_INVALID_PARAMETER; + if (*pszNext++ != ',') + return VERR_INVALID_PARAMETER; + + /* Parse port. */ + uint8_t uPortHi; + rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortHi); + if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS) + return VERR_INVALID_PARAMETER; + if (*pszNext++ != ',') + return VERR_INVALID_PARAMETER; + uint8_t uPortLo; + rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortLo); + if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS) + return VERR_INVALID_PARAMETER; + + *puPort = RT_MAKE_U16(uPortLo, uPortHi); + + return rc; +} + +/** + * Duplicates a command argument vector. + * + * @returns Duplicated argument vector or NULL if failed or no arguments given. Needs to be free'd with rtFtpCmdArgsFree(). + * @param cArgs Number of arguments in argument vector. + * @param apszArgs Pointer to argument vector to duplicate. + */ +static char** rtFtpCmdArgsDup(uint8_t cArgs, const char * const *apszArgs) +{ + if (!cArgs) + return NULL; + + char **apszArgsDup = (char **)RTMemAlloc(cArgs * sizeof(char *)); + if (!apszArgsDup) + { + AssertFailed(); + return NULL; + } + + int rc2 = VINF_SUCCESS; + + uint8_t i; + for (i = 0; i < cArgs; i++) + { + apszArgsDup[i] = RTStrDup(apszArgs[i]); + if (!apszArgsDup[i]) + rc2 = VERR_NO_MEMORY; + } + + if (RT_FAILURE(rc2)) + { + while (i--) + RTStrFree(apszArgsDup[i]); + + RTMemFree(apszArgsDup); + return NULL; + } + + return apszArgsDup; +} + +/** + * Frees a command argument vector. + * + * @param cArgs Number of arguments in argument vector. + * @param papszArgs Pointer to argument vector to free. + */ +static void rtFtpCmdArgsFree(uint8_t cArgs, char **papszArgs) +{ + while (cArgs--) + RTStrFree(papszArgs[cArgs]); + + RTMemFree(papszArgs); +} + +/** + * Opens a data connection to the client. + * + * @returns VBox status code. + * @param pDataConn Data connection to open. + * @param pAddr Address for the data connection. + * @param uPort Port for the data connection. + */ +static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort) +{ + LogFlowFuncEnter(); + + /** @todo Implement IPv6 handling here. */ + char szAddress[32]; + const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", + pAddr->au8[0], pAddr->au8[1], pAddr->au8[2], pAddr->au8[3]); + AssertReturn(cchAdddress > 0, VERR_NO_MEMORY); + + int rc = VINF_SUCCESS; /* Shut up MSVC. */ + + /* Try a bit harder if the data connection is not ready (yet). */ + for (int i = 0; i < 10; i++) + { + rc = RTTcpClientConnect(szAddress, uPort, &pDataConn->hSocket); + if (RT_SUCCESS(rc)) + break; + RTThreadSleep(100); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Closes a data connection to the client. + * + * @returns VBox status code. + * @param pDataConn Data connection to close. + */ +static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn) +{ + int rc = VINF_SUCCESS; + + if (pDataConn->hSocket != NIL_RTSOCKET) + { + LogFlowFuncEnter(); + + rtFtpServerDataConnFlush(pDataConn); + + rc = RTTcpClientClose(pDataConn->hSocket); + pDataConn->hSocket = NIL_RTSOCKET; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Writes data to the data connection. + * + * @returns VBox status code. + * @param pDataConn Data connection to write to. + * @param pvData Data to write. + * @param cbData Size (in bytes) of data to write. + * @param pcbWritten How many bytes were written. Optional. + */ +static int rtFtpServerDataConnWrite(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData, size_t *pcbWritten) +{ + int rc = RTTcpWrite(pDataConn->hSocket, pvData, cbData); + if (RT_SUCCESS(rc)) + { + if (pcbWritten) + *pcbWritten = cbData; + } + + return rc; +} + +/** + * Flushes a data connection. + * + * @returns VBox status code. + * @param pDataConn Data connection to flush. + */ +static int rtFtpServerDataConnFlush(PRTFTPSERVERDATACONN pDataConn) +{ + int rc = VINF_SUCCESS; + + size_t cbUsed = RTCircBufUsed(pDataConn->pCircBuf); + while (cbUsed) + { + void *pvBlock; + size_t cbBlock; + RTCircBufAcquireReadBlock(pDataConn->pCircBuf, cbUsed, &pvBlock, &cbBlock); + if (cbBlock) + { + size_t cbWritten = 0; + rc = rtFtpServerDataConnWrite(pDataConn, pvBlock, cbBlock, &cbWritten); + if (RT_SUCCESS(rc)) + { + AssertBreak(cbUsed >= cbWritten); + cbUsed -= cbWritten; + } + + RTCircBufReleaseReadBlock(pDataConn->pCircBuf, cbWritten); + + if (RT_FAILURE(rc)) + break; + } + } + + return rc; +} + +/** + * Checks if flushing a data connection is necessary, and if so, flush it. + * + * @returns VBox status code. + * @param pDataConn Data connection to check / do flushing for. + */ +static int rtFtpServerDataCheckFlush(PRTFTPSERVERDATACONN pDataConn) +{ + int rc = VINF_SUCCESS; + + size_t cbUsed = RTCircBufUsed(pDataConn->pCircBuf); + if (cbUsed >= _4K) /** @todo Make this more dynamic. */ + { + rc = rtFtpServerDataConnFlush(pDataConn); + } + + return rc; +} + +/** + * Adds new data for a data connection to be sent. + * + * @returns VBox status code. + * @param pDataConn Data connection to add new data to. + * @param pvData Pointer to data to add. + * @param cbData Size (in bytes) of data to add. + */ +static int rtFtpServerDataConnAddData(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData) +{ + AssertReturn(cbData <= RTCircBufFree(pDataConn->pCircBuf), VERR_BUFFER_OVERFLOW); + + int rc = VINF_SUCCESS; + + size_t cbToWrite = cbData; + do + { + void *pvBlock; + size_t cbBlock; + RTCircBufAcquireWriteBlock(pDataConn->pCircBuf, cbToWrite, &pvBlock, &cbBlock); + if (cbBlock) + { + AssertBreak(cbData >= cbBlock); + memcpy(pvBlock, pvData, cbBlock); + + AssertBreak(cbToWrite >= cbBlock); + cbToWrite -= cbBlock; + + RTCircBufReleaseWriteBlock(pDataConn->pCircBuf, cbBlock); + } + + } while (cbToWrite); + + if (RT_SUCCESS(rc)) + rc = rtFtpServerDataCheckFlush(pDataConn); + + return rc; +} + +/** + * Does a printf-style write on a data connection. + * + * @returns VBox status code. + * @param pDataConn Data connection to write to. + * @param pszFormat Format string to send. No (terminal) termination added. + */ +static int rtFtpServerDataConnPrintf(PRTFTPSERVERDATACONN pDataConn, const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *pszFmt = NULL; + const int cch = RTStrAPrintfV(&pszFmt, pszFormat, args); + va_end(args); + AssertReturn(cch > 0, VERR_NO_MEMORY); + + char *pszMsg = NULL; + int rc = RTStrAAppend(&pszMsg, pszFmt); + AssertRCReturn(rc, rc); + + RTStrFree(pszFmt); + + rc = rtFtpServerDataConnAddData(pDataConn, pszMsg, strlen(pszMsg)); + + RTStrFree(pszMsg); + + return rc; +} + +/** + * Data connection thread for writing (sending) a file to the client. + * + * @returns VBox status code. + * @param ThreadSelf Thread handle. Unused at the moment. + * @param pvUser Pointer to user-provided data. Of type PRTFTPSERVERCLIENT. + */ +static DECLCALLBACK(int) rtFtpServerDataConnFileWriteThread(RTTHREAD ThreadSelf, void *pvUser) +{ + RT_NOREF(ThreadSelf); + + PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser; + AssertPtr(pClient); + + PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn; + AssertPtr(pDataConn); + + LogFlowFuncEnter(); + + uint32_t cbBuf = _64K; /** @todo Improve this. */ + void *pvBuf = RTMemAlloc(cbBuf); + if (!pvBuf) + return VERR_NO_MEMORY; + + int rc; + + /* Set start indicator. */ + pDataConn->fStarted = true; + + RTThreadUserSignal(RTThreadSelf()); + + AssertPtr(pDataConn->papszArgs); + const char *pszFile = pDataConn->papszArgs[0]; + AssertPtr(pszFile); + + void *pvHandle = NULL; /* Opaque handle known to the actual implementation. */ + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileOpen, pszFile, + RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pvHandle); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Transfer started\n")); + + do + { + size_t cbRead = 0; + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead); + if ( RT_SUCCESS(rc) + && cbRead) + { + rc = rtFtpServerDataConnWrite(pDataConn, pvBuf, cbRead, NULL /* pcbWritten */); + } + + if ( !cbRead + || ASMAtomicReadBool(&pDataConn->fStop)) + { + break; + } + } + while (RT_SUCCESS(rc)); + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle); + + LogFlowFunc(("Transfer done\n")); + } + + RTMemFree(pvBuf); + pvBuf = NULL; + + pDataConn->fStopped = true; + pDataConn->rc = rc; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Creates a data connection. + * + * @returns VBox status code. + * @param pClient Client to create data connection for. + * @param ppDataConn Where to return the (allocated) data connection. + */ +static int rtFtpServerDataConnCreate(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN *ppDataConn) +{ + if (pClient->pDataConn) + return VERR_FTP_DATA_CONN_LIMIT_REACHED; + + PRTFTPSERVERDATACONN pDataConn = (PRTFTPSERVERDATACONN)RTMemAllocZ(sizeof(RTFTPSERVERDATACONN)); + if (!pDataConn) + return VERR_NO_MEMORY; + + rtFtpServerDataConnReset(pDataConn); + + pDataConn->pClient = pClient; + + /* Use the last configured addr + port. */ + pDataConn->Addr = pClient->DataConnAddr; + pDataConn->uPort = pClient->uDataConnPort; + + int rc = RTCircBufCreate(&pDataConn->pCircBuf, _16K); /** @todo Some random value; improve. */ + if (RT_SUCCESS(rc)) + { + *ppDataConn = pDataConn; + } + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return rc; +} + +/** + * Starts a data connection. + * + * @returns VBox status code. + * @param pDataConn Data connection to start. + * @param pfnThread Thread function for the data connection to use. + * @param cArgs Number of arguments. + * @param apszArgs Array of arguments. + */ +static int rtFtpServerDataConnStart(PRTFTPSERVERDATACONN pDataConn, PFNRTTHREAD pfnThread, + uint8_t cArgs, const char * const *apszArgs) +{ + AssertPtrReturn(pDataConn, VERR_INVALID_POINTER); + AssertPtrReturn(pfnThread, VERR_INVALID_POINTER); + + AssertReturn(!pDataConn->fStarted, VERR_WRONG_ORDER); + AssertReturn(!pDataConn->fStop, VERR_WRONG_ORDER); + AssertReturn(!pDataConn->fStopped, VERR_WRONG_ORDER); + + int rc = VINF_SUCCESS; + + if (cArgs) + { + pDataConn->papszArgs = rtFtpCmdArgsDup(cArgs, apszArgs); + if (!pDataConn->papszArgs) + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + pDataConn->cArgs = cArgs; + + rc = rtFtpServerDataConnOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&pDataConn->hThread, pfnThread, + pDataConn->pClient, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, + "ftpdata"); + if (RT_SUCCESS(rc)) + { + int rc2 = RTThreadUserWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */); + AssertRC(rc2); + + if (!pDataConn->fStarted) /* Did the thread indicate that it started correctly? */ + rc = VERR_FTP_DATA_CONN_INIT_FAILED; + } + + if (RT_FAILURE(rc)) + rtFtpServerDataConnClose(pDataConn); + } + } + + if (RT_FAILURE(rc)) + { + rtFtpCmdArgsFree(pDataConn->cArgs, pDataConn->papszArgs); + + pDataConn->cArgs = 0; + pDataConn->papszArgs = NULL; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Stops a data connection. + * + * @returns VBox status code. + * @param pDataConn Data connection to stop. + */ +static int rtFtpServerDataConnStop(PRTFTPSERVERDATACONN pDataConn) +{ + if (!pDataConn) + return VINF_SUCCESS; + + LogFlowFuncEnter(); + + int rc = VINF_SUCCESS; + + if (pDataConn->hThread != NIL_RTTHREAD) + { + /* Set stop indicator. */ + pDataConn->fStop = true; + + int rcThread = VERR_WRONG_ORDER; + rc = RTThreadWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */, &rcThread); + } + + if (RT_SUCCESS(rc)) + rtFtpServerDataConnClose(pDataConn); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Destroys a data connection. + * + * @param pDataConn Data connection to destroy. The pointer is not valid anymore after successful return. + */ +static void rtFtpServerDataConnDestroy(PRTFTPSERVERDATACONN pDataConn) +{ + if (!pDataConn) + return; + + LogFlowFuncEnter(); + + rtFtpServerDataConnClose(pDataConn); + rtFtpCmdArgsFree(pDataConn->cArgs, pDataConn->papszArgs); + + RTCircBufDestroy(pDataConn->pCircBuf); + + RTMemFree(pDataConn); + pDataConn = NULL; + + LogFlowFuncLeave(); + return; +} + +/** + * Resets a data connection structure. + * + * @param pDataConn Data connection structure to reset. + */ +static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn) +{ + LogFlowFuncEnter(); + + pDataConn->hSocket = NIL_RTSOCKET; + pDataConn->uPort = 20; /* Default port to use. */ + pDataConn->hThread = NIL_RTTHREAD; + pDataConn->fStarted = false; + pDataConn->fStop = false; + pDataConn->fStopped = false; + pDataConn->rc = VERR_IPE_UNINITIALIZED_STATUS; +} + + +/********************************************************************************************************************************* +* Command Protocol Handlers * +*********************************************************************************************************************************/ + +static DECLCALLBACK(int) rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + int rc = rtFtpServerDataConnClose(pClient->pDataConn); + if (RT_SUCCESS(rc)) + { + rtFtpServerDataConnDestroy(pClient->pDataConn); + pClient->pDataConn = NULL; + + rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); + } + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + int rc; + + RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp); + + if (RT_SUCCESS(rc)) + { + const size_t cbPath = sizeof(char) * RTFTPSERVER_MAX_PATH; + char *pszPath = RTStrAlloc(cbPath); + if (pszPath) + { + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, pszPath, cbPath); + + if (RT_SUCCESS(rc)) + rc = rtFtpSetCWD(&pClient->State, pszPath); + + RTStrFree(pszPath); + + rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); + } + else + rc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(rc)) + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN); + AssertRC(rc2); + } + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return VERR_INVALID_PARAMETER; + + int rc; + + const char *pszPath = apszArgs[0]; + + if (!rtFtpServerPathIsValid(pszPath, false /* fIsAbsolute */)) + return VERR_INVALID_PARAMETER; + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, pszPath); + + if (RT_SUCCESS(rc)) + rc = rtFtpSetCWD(&pClient->State, pszPath); + + return rtFtpServerSendReplyRc(pClient, + RT_SUCCESS(rc) + ? RTFTPSERVER_REPLY_OKAY : RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN); +} + +static DECLCALLBACK(int) rtFtpServerHandleFEAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + int rc = rtFtpServerSendReplyStr(pClient, "211-BEGIN Features:"); + if (RT_SUCCESS(rc)) + { + rc = rtFtpServerSendReplyStr(pClient, RTFTPSERVER_FEATURES_STRING); + if (RT_SUCCESS(rc)) + rc = rtFtpServerSendReplyStr(pClient, "211 END Features"); + } + + return rc; +} + +/** + * Formats the given user ID according to the specified options. + * + * @returns pszDst + * @param uid The UID to format. + * @param pszOwner The owner returned by the FS. + * @param pszDst The output buffer. + * @param cbDst The output buffer size. + */ +static const char *rtFtpServerDecimalFormatOwner(RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst) +{ + if (pszOwner) + { + RTStrCopy(pszDst, cbDst, pszOwner); + return pszDst; + } + if (uid == NIL_RTUID) + return "<Nil>"; + + RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0); + return pszDst; +} + +/** + * Formats the given group ID according to the specified options. + * + * @returns pszDst + * @param gid The GID to format. + * @param pszGroup The group returned by the FS. + * @param pszDst The output buffer. + * @param cbDst The output buffer size. + */ +static const char *rtFtpServerDecimalFormatGroup(RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst) +{ + if (pszGroup) + { + RTStrCopy(pszDst, cbDst, pszGroup); + return pszDst; + } + if (gid == NIL_RTGID) + return "<Nil>"; + + RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0); + return pszDst; +} + +/** + * Format file size. + */ +static const char *rtFtpServerFormatSize(uint64_t cb, char *pszDst, size_t cbDst) +{ + RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0); + return pszDst; +} + +/** + * Formats the given timestamp according to (non-standardized) FTP LIST command. + * + * @returns pszDst + * @param pTimestamp The timestamp to format. + * @param pszDst The output buffer. + * @param cbDst The output buffer size. + */ +static const char *rtFtpServerFormatTimestamp(PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst) +{ + RTTIME Time; + RTTimeExplode(&Time, pTimestamp); + + /* Calc the UTC offset part. */ + int32_t offUtc = Time.offUTC; + Assert(offUtc <= 840 && offUtc >= -840); + char chSign; + if (offUtc >= 0) + chSign = '+'; + else + { + chSign = '-'; + offUtc = -offUtc; + } + uint32_t offUtcHour = (uint32_t)offUtc / 60; + uint32_t offUtcMinute = (uint32_t)offUtc % 60; + + /** @todo Cache this. */ + RTTIMESPEC TimeSpecNow; + RTTimeNow(&TimeSpecNow); + RTTIME TimeNow; + RTTimeExplode(&TimeNow, &TimeSpecNow); + + /* Only include the year if it's not the same year as today. */ + if (TimeNow.i32Year != Time.i32Year) + { + RTStrPrintf(pszDst, cbDst, "%s %02RU8 %5RU32", + g_apszMonths[Time.u8Month], Time.u8MonthDay, Time.i32Year); + } + else /* ... otherwise include the (rough) time (as GMT). */ + { + RTStrPrintf(pszDst, cbDst, "%s %02RU8 %02RU32:%02RU32", + g_apszMonths[Time.u8Month], Time.u8MonthDay, offUtcHour, offUtcMinute); + } + + return pszDst; +} + +/** + * Format name, i.e. escape, hide, quote stuff. + */ +static const char *rtFtpServerFormatName(const char *pszName, char *pszDst, size_t cbDst) +{ + /** @todo implement name formatting. */ + RT_NOREF(pszDst, cbDst); + return pszName; +} + +/** + * Figures out the length for a 32-bit number when formatted as decimal. + * @returns Number of digits. + * @param uValue The number. + */ +DECLINLINE(size_t) rtFtpServerDecimalFormatLengthU32(uint32_t uValue) +{ + if (uValue < 10) + return 1; + if (uValue < 100) + return 2; + if (uValue < 1000) + return 3; + if (uValue < 10000) + return 4; + if (uValue < 100000) + return 5; + if (uValue < 1000000) + return 6; + if (uValue < 10000000) + return 7; + if (uValue < 100000000) + return 8; + if (uValue < 1000000000) + return 9; + return 10; +} + +/** + * Allocates a new directory collection. + * + * @returns The collection allocated. + */ +static PRTFTPDIRCOLLECTION rtFtpServerDataConnDirCollAlloc(void) +{ + return (PRTFTPDIRCOLLECTION)RTMemAllocZ(sizeof(RTFTPDIRCOLLECTION)); +} + +/** + * Frees a directory collection and its entries. + * + * @param pCollection The collection to free. + */ +static void rtFtpServerDataConnDirCollFree(PRTFTPDIRCOLLECTION pCollection) +{ + PPRTFTPDIRENTRY papEntries = pCollection->papEntries; + size_t j = pCollection->cEntries; + while (j-- > 0) + { + RTMemFree(papEntries[j]); + papEntries[j] = NULL; + } + RTMemFree(papEntries); + pCollection->papEntries = NULL; + pCollection->cEntries = 0; + pCollection->cEntriesAllocated = 0; + RTMemFree(pCollection); +} + +/** + * Adds one entry to a collection. + * + * @returns VBox status code. + * @param pCollection The collection to add entry to. + * @param pszEntry The entry name. + * @param pInfo The entry info. + * @param pszOwner The owner name if available, otherwise NULL. + * @param pszGroup The group anme if available, otherwise NULL. + * @param pszTarget The symbolic link target if applicable and + * available, otherwise NULL. + */ +static int rtFtpServerDataConnDirCollAddEntry(PRTFTPDIRCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo, + const char *pszOwner, const char *pszGroup, const char *pszTarget) +{ + /* Filter out entries we don't want to report to the client, even if they were reported by the actual implementation. */ + if ( !RTStrCmp(pszEntry, ".") + || !RTStrCmp(pszEntry, "..")) + { + return VINF_SUCCESS; + } + + /* Anything else besides files and directores is not allowed; just don't show them at all for the moment. */ + switch (pInfo->Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + RT_FALL_THROUGH(); + case RTFS_TYPE_FILE: + break; + + default: + return VINF_SUCCESS; + } + + /* Make sure there is space in the collection for the new entry. */ + if (pCollection->cEntries >= pCollection->cEntriesAllocated) + { + size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16; + void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0])); + if (!pvNew) + return VERR_NO_MEMORY; + pCollection->papEntries = (PPRTFTPDIRENTRY)pvNew; + pCollection->cEntriesAllocated = cNew; + } + + /* Create and insert a new entry. */ + size_t const cchEntry = strlen(pszEntry); + size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0; + size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0; + size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0; + size_t const cbEntry = RT_UOFFSETOF_DYN(RTFTPDIRENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]); + PRTFTPDIRENTRY pEntry = (PRTFTPDIRENTRY)RTMemAlloc(cbEntry); + if (pEntry) + { + pEntry->Info = *pInfo; + pEntry->pszTarget = NULL; /** @todo symbolic links. */ + pEntry->pszOwner = NULL; + pEntry->pszGroup = NULL; + pEntry->cchName = cchEntry; + memcpy(pEntry->szName, pszEntry, cchEntry); + pEntry->szName[cchEntry] = '\0'; + + char *psz = &pEntry->szName[cchEntry + 1]; + if (pszTarget) + { + pEntry->pszTarget = psz; + memcpy(psz, pszTarget, cbTarget); + psz += cbTarget; + } + if (pszOwner) + { + pEntry->pszOwner = psz; + memcpy(psz, pszOwner, cbOwner); + psz += cbOwner; + } + if (pszGroup) + { + pEntry->pszGroup = psz; + memcpy(psz, pszGroup, cbGroup); + } + + pCollection->papEntries[pCollection->cEntries++] = pEntry; + pCollection->cbTotalAllocated += pEntry->Info.cbAllocated; + pCollection->cbTotalFiles += pEntry->Info.cbObject; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + +/** @callback_method_impl{FNRTSORTCMP, Name} */ +static DECLCALLBACK(int) rtFtpServerCollEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1; + PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2; + return RTStrCmp(pEntry1->szName, pEntry2->szName); +} + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */ +static DECLCALLBACK(int) rtFtpServerCollEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1; + PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtFtpServerCollEntryCmpName(pEntry1, pEntry2, pvUser); + return iDiff; +} + +/** + * Sorts a given directory collection according to the FTP server's LIST style. + * + * @param pCollection Collection to sort. + */ +static void rtFtpServerCollSort(PRTFTPDIRCOLLECTION pCollection) +{ + PFNRTSORTCMP pfnCmp = rtFtpServerCollEntryCmpDirFirstName; + if (pfnCmp) + RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL); +} + +/** + * Writes a directory collection to a specific data connection. + * + * @returns VBox status code. + * @param pDataConn Data connection to write directory collection to. + * @param pCollection Collection to write. + * @param pszTmp Temporary buffer used for writing. + * @param cbTmp Size (in bytes) of temporary buffer used for writing. + */ +static int rtFtpServerDataConnDirCollWrite(PRTFTPSERVERDATACONN pDataConn, PRTFTPDIRCOLLECTION pCollection, + char *pszTmp, size_t cbTmp) +{ + size_t cchSizeCol = 4; + size_t cchLinkCol = 1; + size_t cchUidCol = 1; + size_t cchGidCol = 1; + + size_t i = pCollection->cEntries; + while (i-- > 0) + { + PRTFTPDIRENTRY pEntry = pCollection->papEntries[i]; + + rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp); + size_t cchTmp = strlen(pszTmp); + if (cchTmp > cchSizeCol) + cchSizeCol = cchTmp; + + cchTmp = rtFtpServerDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1; + if (cchTmp > cchLinkCol) + cchLinkCol = cchTmp; + + rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp); + cchTmp = strlen(pszTmp); + if (cchTmp > cchUidCol) + cchUidCol = cchTmp; + + rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp); + cchTmp = strlen(pszTmp); + if (cchTmp > cchGidCol) + cchGidCol = cchTmp; + } + + size_t offTime = RT_UOFFSETOF(RTFTPDIRENTRY, Info.ModificationTime); + + /* + * Display the entries. + */ + for (i = 0; i < pCollection->cEntries; i++) + { + PRTFTPDIRENTRY pEntry = pCollection->papEntries[i]; + + RTFMODE fMode = pEntry->Info.Attr.fMode; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FIFO: rtFtpServerDataConnPrintf(pDataConn, "f"); break; + case RTFS_TYPE_DEV_CHAR: rtFtpServerDataConnPrintf(pDataConn, "c"); break; + case RTFS_TYPE_DIRECTORY: rtFtpServerDataConnPrintf(pDataConn, "d"); break; + case RTFS_TYPE_DEV_BLOCK: rtFtpServerDataConnPrintf(pDataConn, "b"); break; + case RTFS_TYPE_FILE: rtFtpServerDataConnPrintf(pDataConn, "-"); break; + case RTFS_TYPE_SYMLINK: rtFtpServerDataConnPrintf(pDataConn, "l"); break; + case RTFS_TYPE_SOCKET: rtFtpServerDataConnPrintf(pDataConn, "s"); break; + case RTFS_TYPE_WHITEOUT: rtFtpServerDataConnPrintf(pDataConn, "w"); break; + default: rtFtpServerDataConnPrintf(pDataConn, "?"); AssertFailed(); break; + } + + rtFtpServerDataConnPrintf(pDataConn, "%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-'); + rtFtpServerDataConnPrintf(pDataConn, "%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-'); + rtFtpServerDataConnPrintf(pDataConn, "%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-'); + + rtFtpServerDataConnPrintf(pDataConn, " %*u", + cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks); + + if (cchUidCol) + rtFtpServerDataConnPrintf(pDataConn, " %*s", cchUidCol, + rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp)); + if (cchGidCol) + rtFtpServerDataConnPrintf(pDataConn, " %*s", cchGidCol, + rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp)); + + rtFtpServerDataConnPrintf(pDataConn, "%*s", cchSizeCol, rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp)); + + PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime); + rtFtpServerDataConnPrintf(pDataConn," %s", rtFtpServerFormatTimestamp(pTime, pszTmp, cbTmp)); + + rtFtpServerDataConnPrintf(pDataConn," %s\r\n", rtFtpServerFormatName(pEntry->szName, pszTmp, cbTmp)); + } + + return VINF_SUCCESS; +} + +/** + * Thread for handling the LIST command's output in a separate data connection. + * + * @returns VBox status code. + * @param ThreadSelf Thread handle. Unused. + * @param pvUser User-provided arguments. Of type PRTFTPSERVERCLIENT. + */ +static DECLCALLBACK(int) rtFtpServerDataConnListThread(RTTHREAD ThreadSelf, void *pvUser) +{ + RT_NOREF(ThreadSelf); + + PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser; + AssertPtr(pClient); + + PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn; + AssertPtr(pDataConn); + + LogFlowFuncEnter(); + + int rc; + + char szTmp[RTPATH_MAX * 2]; + PRTFTPDIRCOLLECTION pColl = rtFtpServerDataConnDirCollAlloc(); + AssertPtrReturn(pColl, VERR_NO_MEMORY); + + /* Set start indicator. */ + pDataConn->fStarted = true; + + RTThreadUserSignal(RTThreadSelf()); + + /* The first argument might indicate a directory to list. + * If no argument is given, the implementation must use the last directory set. */ + char *pszPath = RTStrDup( pDataConn->cArgs == 1 + ? pDataConn->papszArgs[0] : pDataConn->pClient->State.pszCWD); /** @todo Needs locking. */ + AssertPtrReturn(pszPath, VERR_NO_MEMORY); + /* The paths already have been validated in the actual command handlers. */ + + void *pvHandle = NULL; /* Shut up MSVC. */ + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirOpen, pszPath, &pvHandle); + + for (;;) + { + RTFSOBJINFO objInfo; + RT_ZERO(objInfo); + + char *pszEntry = NULL; + char *pszOwner = NULL; + char *pszGroup = NULL; + char *pszTarget = NULL; + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirRead, pvHandle, &pszEntry, + &objInfo, &pszOwner, &pszGroup, &pszTarget); + if (RT_SUCCESS(rc)) + { + int rc2 = rtFtpServerDataConnDirCollAddEntry(pColl, pszEntry, + &objInfo, pszOwner, pszGroup, pszTarget); + + RTStrFree(pszEntry); + pszEntry = NULL; + + RTStrFree(pszOwner); + pszOwner = NULL; + + RTStrFree(pszGroup); + pszGroup = NULL; + + RTStrFree(pszTarget); + pszTarget = NULL; + + if (RT_SUCCESS(rc)) + rc = rc2; + } + else + { + if (rc == VERR_NO_MORE_FILES) + { + rc = VINF_SUCCESS; + break; + } + } + + if (RT_FAILURE(rc)) + break; + + if (ASMAtomicReadBool(&pDataConn->fStop)) + break; + } + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirClose, pvHandle); + pvHandle = NULL; + + rtFtpServerCollSort(pColl); + + if (RT_SUCCESS(rc)) + { + int rc2 = rtFtpServerDataConnDirCollWrite(pDataConn, pColl, szTmp, sizeof(szTmp)); + AssertRC(rc2); + } + + rtFtpServerDataConnDirCollFree(pColl); + + RTStrFree(pszPath); + + pDataConn->fStopped = true; + pDataConn->rc = rc; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + /* If no argument is given, use the server's CWD as the path. */ + const char *pszPath = cArgs ? apszArgs[0] : pClient->State.pszCWD; + AssertPtr(pszPath); + + int rc = VINF_SUCCESS; + + if (!rtFtpServerPathIsValid(pszPath, false /* fIsAbsolute */)) + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN); + AssertRC(rc2); + } + else + { + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pszPath, NULL /* PRTFSOBJINFO */); + + if (RT_SUCCESS(rc)) + { + if (pClient->pDataConn == NULL) + { + rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn); + if (RT_SUCCESS(rc)) + rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnListThread, cArgs, apszArgs); + + int rc2 = rtFtpServerSendReplyRc( pClient, RT_SUCCESS(rc) + ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN + : RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN); + AssertRC(rc2); + } + else + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN); + AssertRC(rc2); + } + } + else + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN); + AssertRC(rc2); + } + } + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(pClient, cArgs, apszArgs); + + /** @todo Anything to do here? */ + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + /* Save timestamp of last command sent. */ + pClient->State.tsLastCmdMs = RTTimeMilliTS(); + + return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); +} + +static DECLCALLBACK(int) rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS); + + const char *pszPassword = apszArgs[0]; + AssertPtrReturn(pszPassword, VERR_INVALID_PARAMETER); + + int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pszPassword); + if (RT_SUCCESS(rc)) + { + rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED); + } + else + { + pClient->State.cFailedLoginAttempts++; + + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS); + + RTFTPSERVER_REPLY rcClient; + + int rc = rtFtpParseHostAndPort(apszArgs[0], &pClient->DataConnAddr, &pClient->uDataConnPort); + if (RT_SUCCESS(rc)) + rcClient = RTFTPSERVER_REPLY_OKAY; + else + rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS; + + int rc2 = rtFtpServerSendReplyRc(pClient, rcClient); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + int rc; + + char szPWD[RTPATH_MAX]; + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD)); + + if (RT_SUCCESS(rc)) + rc = rtFtpServerSendReplyRcEx(pClient, RTFTPSERVER_REPLY_PATHNAME_OK, "\"%s\"", szPWD); /* See RFC 959, APPENDIX II. */ + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleOPTS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + int rc = VINF_SUCCESS; + + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + int rc = VINF_SUCCESS; + + if (pClient->pDataConn) + { + rc = rtFtpServerDataConnClose(pClient->pDataConn); + if (RT_SUCCESS(rc)) + { + rtFtpServerDataConnDestroy(pClient->pDataConn); + pClient->pDataConn = NULL; + } + } + + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) /* File name needs to be present. */ + return VERR_INVALID_PARAMETER; + + int rc; + + const char *pszPath = apszArgs[0]; + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pszPath, NULL /* PRTFSOBJINFO */); + + if (RT_SUCCESS(rc)) + { + if (RT_SUCCESS(rc)) + { + if (pClient->pDataConn == NULL) + { + rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn); + if (RT_SUCCESS(rc)) + rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnFileWriteThread, cArgs, apszArgs); + + int rc2 = rtFtpServerSendReplyRc( pClient, RT_SUCCESS(rc) + ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN + : RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN); + AssertRC(rc2); + } + else + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN); + AssertRC(rc2); + } + } + else + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN); + AssertRC(rc2); + } + } + + if (RT_FAILURE(rc)) + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN); + AssertRC(rc2); + } + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return VERR_INVALID_PARAMETER; + + int rc; + + const char *pszPath = apszArgs[0]; + uint64_t uSize = 0; + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pszPath, &uSize); + + if (RT_SUCCESS(rc)) + { + rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize); + } + else + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN); + AssertRC(rc2); + } + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return VERR_INVALID_PARAMETER; + + int rc; + + RTFSOBJINFO objInfo; + RT_ZERO(objInfo); + + const char *pszPath = apszArgs[0]; + + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pszPath, &objInfo); + + if (RT_SUCCESS(rc)) + { + char szFsObjInfo[_4K]; /** @todo Check this size. */ + rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo)); + if (RT_SUCCESS(rc)) + { + char szFsPathInfo[RTPATH_MAX + 16]; + const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pszPath), pszPath); + if (cchPathInfo > 0) + { + rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo); + if (RT_SUCCESS(rc)) + rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo); + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + + if (RT_FAILURE(rc)) + { + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN); + AssertRC(rc2); + } + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return VERR_INVALID_PARAMETER; + + const char *pszType = apszArgs[0]; + + int rc; + + if (!RTStrICmp(pszType, "F")) + { + pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE; + + rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); + } + else + rc = VERR_NOT_IMPLEMENTED; + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + RT_NOREF(cArgs, apszArgs); + + char szOSInfo[64]; + int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo)); + if (RT_SUCCESS(rc)) + rc = rtFtpServerSendReplyStr(pClient, "215 %s", szOSInfo); + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return VERR_INVALID_PARAMETER; + + const char *pszType = apszArgs[0]; + + int rc = VINF_SUCCESS; + + if (!RTStrICmp(pszType, "A")) + { + pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII; + } + else if (!RTStrICmp(pszType, "I")) /* Image (binary). */ + { + pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE; + } + else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */ + rc = VERR_NOT_IMPLEMENTED; + + if (RT_SUCCESS(rc)) + rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY); + + return rc; +} + +static DECLCALLBACK(int) rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs) +{ + if (cArgs != 1) + return VERR_INVALID_PARAMETER; + + const char *pszUser = apszArgs[0]; + AssertPtrReturn(pszUser, VERR_INVALID_PARAMETER); + + rtFtpServerClientStateReset(&pClient->State); + + int rc = rtFtpServerLookupUser(pClient, pszUser); + if (RT_SUCCESS(rc)) + { + pClient->State.pszUser = RTStrDup(pszUser); + AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY); + + rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD); + } + else + { + pClient->State.cFailedLoginAttempts++; + + int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +/********************************************************************************************************************************* +* Internal server functions * +*********************************************************************************************************************************/ + +/** + * Parses FTP command arguments handed in by the client. + * + * @returns VBox status code. + * @param pszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given. + * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20). + * @param ppapszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree(). + */ +static int rtFtpServerCmdArgsParse(const char *pszCmdParms, uint8_t *pcArgs, char ***ppapszArgs) +{ + *pcArgs = 0; + *ppapszArgs = NULL; + + if (!pszCmdParms) /* No parms given? Bail out early. */ + return VINF_SUCCESS; + + /** @todo Anything else to do here? */ + /** @todo Check if quoting is correct. */ + + int cArgs = 0; + int rc = RTGetOptArgvFromString(ppapszArgs, &cArgs, pszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */); + if (RT_SUCCESS(rc)) + { + if (cArgs <= UINT8_MAX) + { + *pcArgs = (uint8_t)cArgs; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse(). + * + * @param ppapszArgs Argument string array to free. + */ +static void rtFtpServerCmdArgsFree(char **ppapszArgs) +{ + RTGetOptArgvFree(ppapszArgs); +} + +/** + * Main function for processing client commands for the control connection. + * + * @returns VBox status code. + * @param pClient Client to process commands for. + * @param pszCmd Command string to parse and handle. + * @param cbCmd Size (in bytes) of command string. + */ +static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pszCmd, size_t cbCmd) +{ + /* Make sure to terminate the string in any case. */ + pszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0'; + + /* A tiny bit of sanitation. */ + RTStrStripL(pszCmd); + + /* First, terminate string by finding the command end marker (telnet style). */ + /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */ + char *pszCmdEnd = RTStrStr(pszCmd, "\r\n"); + if (pszCmdEnd) + *pszCmdEnd = '\0'; + + /* Reply which gets sent back to the client. */ + RTFTPSERVER_REPLY rcClient = RTFTPSERVER_REPLY_INVALID; + + int rcCmd = VINF_SUCCESS; + + uint8_t cArgs = 0; + char **papszArgs = NULL; + int rc = rtFtpServerCmdArgsParse(pszCmd, &cArgs, &papszArgs); + if ( RT_SUCCESS(rc) + && cArgs) /* At least the actual command (without args) must be present. */ + { + LogFlowFunc(("Handling command '%s'\n", papszArgs[0])); + for (uint8_t a = 0; a < cArgs; a++) + LogFlowFunc(("\targ[%RU8] = '%s'\n", a, papszArgs[a])); + + unsigned i = 0; + for (; i < RT_ELEMENTS(g_aCmdMap); i++) + { + const RTFTPSERVERCMD_ENTRY *pCmdEntry = &g_aCmdMap[i]; + + if (!RTStrICmp(papszArgs[0], pCmdEntry->szCmd)) + { + /* Some commands need a valid user before they can be executed. */ + if ( pCmdEntry->fNeedsUser + && pClient->State.pszUser == NULL) + { + rcClient = RTFTPSERVER_REPLY_NOT_LOGGED_IN; + break; + } + + /* Save timestamp of last command sent. */ + pClient->State.tsLastCmdMs = RTTimeMilliTS(); + + /* Hand in arguments only without the actual command. */ + rcCmd = pCmdEntry->pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL); + if (RT_FAILURE(rcCmd)) + { + LogFunc(("Handling command '%s' failed with %Rrc\n", papszArgs[0], rcCmd)); + + switch (rcCmd) + { + case VERR_INVALID_PARAMETER: + RT_FALL_THROUGH(); + case VERR_INVALID_POINTER: + rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS; + break; + + case VERR_NOT_IMPLEMENTED: + rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL; + break; + + default: + break; + } + } + break; + } + } + + rtFtpServerCmdArgsFree(papszArgs); + + if (i == RT_ELEMENTS(g_aCmdMap)) + { + LogFlowFunc(("Command not implemented\n")); + Assert(rcClient == RTFTPSERVER_REPLY_INVALID); + rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL; + } + + const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVERCMD_QUIT + || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */ + if (fDisconnect) + { + RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser); + + rtFtpServerClientStateReset(&pClient->State); + + Assert(rcClient == RTFTPSERVER_REPLY_INVALID); + rcClient = RTFTPSERVER_REPLY_CLOSING_CTRL_CONN; + } + } + else + rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS; + + if (rcClient != RTFTPSERVER_REPLY_INVALID) + { + int rc2 = rtFtpServerSendReplyRc(pClient, rcClient); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main loop for processing client commands. + * + * @returns VBox status code. + * @param pClient Client to process commands for. + */ +static int rtFtpServerClientMain(PRTFTPSERVERCLIENT pClient) +{ + int rc; + + size_t cbRead; + char szCmd[RTFTPSERVER_MAX_CMD_LEN + 1]; + + for (;;) + { + rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */ + if (RT_SUCCESS(rc)) + { + rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead); + if ( RT_SUCCESS(rc) + && cbRead) + { + AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW); + rc = rtFtpServerProcessCommands(pClient, szCmd, cbRead); + } + } + else if (rc == VERR_TIMEOUT) + rc = VINF_SUCCESS; + else + break; + + /* + * Handle data connection replies. + */ + if (pClient->pDataConn) + { + if ( ASMAtomicReadBool(&pClient->pDataConn->fStarted) + && ASMAtomicReadBool(&pClient->pDataConn->fStopped)) + { + Assert(pClient->pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS); + + int rc2 = rtFtpServerSendReplyRc(pClient, + RT_SUCCESS(pClient->pDataConn->rc) + ? RTFTPSERVER_REPLY_CLOSING_DATA_CONN : RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN); + AssertRC(rc2); + + rc = rtFtpServerDataConnStop(pClient->pDataConn); + if (RT_SUCCESS(rc)) + { + rtFtpServerDataConnDestroy(pClient->pDataConn); + pClient->pDataConn = NULL; + } + } + } + } + + /* Make sure to destroy all data connections. */ + rtFtpServerDataConnDestroy(pClient->pDataConn); + pClient->pDataConn = NULL; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Resets the client's state. + * + * @param pState Client state to reset. + */ +static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState) +{ + LogFlowFuncEnter(); + + RTStrFree(pState->pszUser); + pState->pszUser = NULL; + + int rc2 = rtFtpSetCWD(pState, "/"); + AssertRC(rc2); + + pState->cFailedLoginAttempts = 0; + pState->tsLastCmdMs = RTTimeMilliTS(); + pState->enmDataType = RTFTPSERVER_DATA_TYPE_ASCII; + pState->enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE; +} + +/** + * Per-client thread for serving the server's control connection. + * + * @returns VBox status code. + * @param hSocket Socket handle to use for the control connection. + * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL. + */ +static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser) +{ + PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser; + RTFTPSERVER_VALID_RETURN(pThis); + + RTFTPSERVERCLIENT Client; + RT_ZERO(Client); + + Client.pServer = pThis; + Client.hSocket = hSocket; + + LogFlowFunc(("New client connected\n")); + + rtFtpServerClientStateReset(&Client.State); + + /* + * Send welcome message. + * Note: Some clients (like FileZilla / Firefox) expect a message together with the reply code, + * so make sure to include at least *something*. + */ + int rc = rtFtpServerSendReplyRcEx(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER, + "Welcome!"); + if (RT_SUCCESS(rc)) + { + ASMAtomicIncU32(&pThis->cClients); + + rc = rtFtpServerClientMain(&Client); + + ASMAtomicDecU32(&pThis->cClients); + } + + rtFtpServerClientStateReset(&Client.State); + + return rc; +} + +RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pszAddress, uint16_t uPort, + PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser) +{ + AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER); + AssertPtrReturn(pszAddress, VERR_INVALID_POINTER); + AssertReturn (uPort, VERR_INVALID_PARAMETER); + AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER); + /* pvUser is optional. */ + + int rc; + + PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL)); + if (pThis) + { + pThis->u32Magic = RTFTPSERVER_MAGIC; + pThis->Callbacks = *pCallbacks; + pThis->pvUser = pvUser; + pThis->cbUser = cbUser; + + rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv", + rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer); + if (RT_SUCCESS(rc)) + { + *phFTPServer = (RTFTPSERVER)pThis; + } + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer) +{ + if (hFTPServer == NIL_RTFTPSERVER) + return VINF_SUCCESS; + + PRTFTPSERVERINTERNAL pThis = hFTPServer; + RTFTPSERVER_VALID_RETURN(pThis); + + AssertPtr(pThis->pTCPServer); + + int rc = RTTcpServerDestroy(pThis->pTCPServer); + if (RT_SUCCESS(rc)) + { + pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD; + + RTMemFree(pThis); + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/generic/Makefile.kup b/src/VBox/Runtime/r3/generic/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/generic/Makefile.kup diff --git a/src/VBox/Runtime/r3/generic/RTLocaleQueryLocaleName-r3-generic.cpp b/src/VBox/Runtime/r3/generic/RTLocaleQueryLocaleName-r3-generic.cpp new file mode 100644 index 00000000..1618d593 --- /dev/null +++ b/src/VBox/Runtime/r3/generic/RTLocaleQueryLocaleName-r3-generic.cpp @@ -0,0 +1,67 @@ +/* $Id: RTLocaleQueryLocaleName-r3-generic.cpp $ */ +/** @file + * IPRT - RTLocaleQueryLocaleName, ring-3 generic. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <locale.h> + +#include <iprt/locale.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/string.h> +#ifdef RT_OS_SOLARIS +#include <iprt/path.h> +#endif /* RT_OS_SOLARIS */ + + + +RTDECL(int) RTLocaleQueryLocaleName(char *pszName, size_t cbName) +{ + const char *pszLocale = setlocale(LC_ALL, NULL); + if (pszLocale) + { +#ifdef RT_OS_SOLARIS /* Solaris can return a locale starting with a slash ('/'), e.g. /en_GB.UTF-8/C/C/C/C/C */ + if (RTPATH_IS_SLASH(*pszLocale)) + pszLocale++; +#endif /* RT_OS_SOLARIS */ + return RTStrCopy(pszName, cbName, pszLocale); + } + return VERR_NOT_AVAILABLE; +} + diff --git a/src/VBox/Runtime/r3/generic/RTLocaleQueryNormalizedBaseLocaleName-r3-generic.cpp b/src/VBox/Runtime/r3/generic/RTLocaleQueryNormalizedBaseLocaleName-r3-generic.cpp new file mode 100644 index 00000000..6ae8ef83 --- /dev/null +++ b/src/VBox/Runtime/r3/generic/RTLocaleQueryNormalizedBaseLocaleName-r3-generic.cpp @@ -0,0 +1,102 @@ +/* $Id: RTLocaleQueryNormalizedBaseLocaleName-r3-generic.cpp $ */ +/** @file + * IPRT - RTLocaleQueryNormalizedBaseLocaleName, ring-3 generic. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/locale.h> +#include "internal/iprt.h" + +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + + +/* + * Note! Code duplicated in r3/win/RTLocaleQueryNormalizedBaseLocaleName-win.cpp (adds fallback). + */ +RTDECL(int) RTLocaleQueryNormalizedBaseLocaleName(char *pszName, size_t cbName) +{ + char szLocale[_1K]; + int rc = RTLocaleQueryLocaleName(szLocale, sizeof(szLocale)); + if (RT_SUCCESS(rc)) + { + /* + * May return some complicated "LC_XXX=yyy;LC.." sequence if + * partially set (like IPRT does). Try get xx_YY sequence first + * because 'C' or 'POSIX' may be LC_xxx variants that haven't been + * set yet. + * + * ASSUMES complicated locale mangling is done in a certain way... + */ + const char *pszLocale = strchr(szLocale, '='); + if (!pszLocale) + pszLocale = szLocale; + else + pszLocale++; + bool fSeenC = false; + bool fSeenPOSIX = false; + do + { + const char *pszEnd = strchr(pszLocale, ';'); + + if ( RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszLocale) + && ( pszLocale[5] == '\0' + || RT_C_IS_PUNCT(pszLocale[5])) ) + return RTStrCopyEx(pszName, cbName, pszLocale, 5); + + if ( pszLocale[0] == 'C' + && ( pszLocale[1] == '\0' + || RT_C_IS_PUNCT(pszLocale[1])) ) + fSeenC = true; + else if ( strncmp(pszLocale, "POSIX", 5) == 0 + && ( pszLocale[5] == '\0' + || RT_C_IS_PUNCT(pszLocale[5])) ) + fSeenPOSIX = true; + + /* advance */ + pszLocale = pszEnd ? strchr(pszEnd + 1, '=') : NULL; + } while (pszLocale++); + + if (fSeenC || fSeenPOSIX) + return RTStrCopy(pszName, cbName, "C"); /* C and POSIX should be identical IIRC, so keep it simple. */ + + rc = VERR_NOT_AVAILABLE; + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/generic/RTLocaleQueryUserCountryCode-r3-generic.cpp b/src/VBox/Runtime/r3/generic/RTLocaleQueryUserCountryCode-r3-generic.cpp new file mode 100644 index 00000000..64d675e2 --- /dev/null +++ b/src/VBox/Runtime/r3/generic/RTLocaleQueryUserCountryCode-r3-generic.cpp @@ -0,0 +1,87 @@ +/* $Id: RTLocaleQueryUserCountryCode-r3-generic.cpp $ */ +/** @file + * IPRT - RTLocaleQueryLocaleName, ring-3 generic. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <locale.h> + +#include <iprt/locale.h> +#include "internal/iprt.h" + +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + + + +RTDECL(int) RTLocaleQueryUserCountryCode(char pszCountryCode[3]) +{ + static const int s_aiLocales[] = + { + LC_ALL, + LC_CTYPE, + LC_COLLATE, + LC_MONETARY, + LC_NUMERIC, + LC_TIME + }; + + for (unsigned i = 0; i < RT_ELEMENTS(s_aiLocales); i++) + { + const char *pszLocale = setlocale(s_aiLocales[i], NULL); + if ( pszLocale + && strlen(pszLocale) >= 5 + && RT_C_IS_ALPHA(pszLocale[0]) + && RT_C_IS_ALPHA(pszLocale[1]) + && pszLocale[2] == '_' + && RT_C_IS_ALPHA(pszLocale[3]) + && RT_C_IS_ALPHA(pszLocale[4])) + { + pszCountryCode[0] = RT_C_TO_UPPER(pszLocale[3]); + pszCountryCode[1] = RT_C_TO_UPPER(pszLocale[4]); + pszCountryCode[2] = '\0'; + return VINF_SUCCESS; + } + } + + pszCountryCode[0] = 'Z'; + pszCountryCode[1] = 'Z'; + pszCountryCode[2] = '\0'; + return VERR_NOT_AVAILABLE; +} + diff --git a/src/VBox/Runtime/r3/generic/RTTimeZoneGetCurrent-generic.cpp b/src/VBox/Runtime/r3/generic/RTTimeZoneGetCurrent-generic.cpp new file mode 100644 index 00000000..5cc6b39a --- /dev/null +++ b/src/VBox/Runtime/r3/generic/RTTimeZoneGetCurrent-generic.cpp @@ -0,0 +1,51 @@ +/* $Id: RTTimeZoneGetCurrent-generic.cpp $ */ +/** @file + * IPRT - RTTimeZoneGetCurrent, generic. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/time.h> +#include "internal/iprt.h" + +#include <iprt/env.h> + + +RTDECL(int) RTTimeZoneGetCurrent(char *pszName, size_t cbName) +{ + return RTEnvGetEx(RTENV_DEFAULT, "TZ", pszName, cbName, NULL); +} + diff --git a/src/VBox/Runtime/r3/generic/allocex-r3-generic.cpp b/src/VBox/Runtime/r3/generic/allocex-r3-generic.cpp new file mode 100644 index 00000000..2e7f0528 --- /dev/null +++ b/src/VBox/Runtime/r3/generic/allocex-r3-generic.cpp @@ -0,0 +1,70 @@ +/* $Id: allocex-r3-generic.cpp $ */ +/** @file + * IPRT - Memory Allocation, Extended Alloc Workers, generic. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define RTMEM_NO_WRAP_TO_EF_APIS +#include <iprt/mem.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include "../allocex.h" + + + +DECLHIDDEN(int) rtMemAllocEx16BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv) +{ + RT_NOREF(cbAlloc, fFlags, ppv); + return VERR_NOT_SUPPORTED; +} + + +DECLHIDDEN(int) rtMemAllocEx32BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv) +{ + RT_NOREF(cbAlloc, fFlags, ppv); + return VERR_NOT_SUPPORTED; +} + + +DECLHIDDEN(void) rtMemFreeExYyBitReach(void *pv, size_t cb, uint32_t fFlags) +{ + RT_NOREF(pv, cb, fFlags); + AssertFailed(); +} + diff --git a/src/VBox/Runtime/r3/generic/dirrel-r3-generic.cpp b/src/VBox/Runtime/r3/generic/dirrel-r3-generic.cpp new file mode 100644 index 00000000..da34638e --- /dev/null +++ b/src/VBox/Runtime/r3/generic/dirrel-r3-generic.cpp @@ -0,0 +1,369 @@ +/* $Id: dirrel-r3-generic.cpp $ */ +/** @file + * IPRT - Directory relative base APIs, generic implementation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/dir.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#define RTDIR_AGNOSTIC +#include "internal/dir.h" + + + +/** + * Helper that builds a full path for a directory relative path. + * + * @returns IPRT status code. + * @param pThis The directory. + * @param pszPathDst The destination buffer. + * @param cbPathDst The size of the destination buffer. + * @param pszRelPath The relative path. + */ +static int rtDirRelBuildFullPath(PRTDIRINTERNAL pThis, char *pszPathDst, size_t cbPathDst, const char *pszRelPath) +{ + AssertMsgReturn(!RTPathStartsWithRoot(pszRelPath), ("pszRelPath='%s'\n", pszRelPath), VERR_PATH_IS_NOT_RELATIVE); + + /* + * Let's hope we can avoid checking for ascension. + * + * Note! We don't take symbolic links into account here. That can be + * done later if desired. + */ + if ( !(pThis->fFlags & RTDIR_F_DENY_ASCENT) + || strstr(pszRelPath, "..") == NULL) + { + size_t const cchRelPath = strlen(pszRelPath); + size_t const cchDirPath = pThis->cchPath; + if (cchDirPath + cchRelPath < cbPathDst) + { + memcpy(pszPathDst, pThis->pszPath, cchDirPath); + memcpy(&pszPathDst[cchDirPath], pszRelPath, cchRelPath); + pszPathDst[cchDirPath + cchRelPath] = '\0'; + return VINF_SUCCESS; + } + return VERR_FILENAME_TOO_LONG; + } + + /* + * Calc the absolute path using the directory as a base, then check if the result + * still starts with the full directory path. + * + * This ASSUMES that pThis->pszPath is an absolute path. + */ + int rc = RTPathAbsEx(pThis->pszPath, pszRelPath, RTPATH_STR_F_STYLE_HOST, pszPathDst, &cbPathDst); + if (RT_SUCCESS(rc)) + { + if (RTPathStartsWith(pszPathDst, pThis->pszPath)) + return VINF_SUCCESS; + return VERR_PATH_NOT_FOUND; + } + return rc; +} + + +/* + * + * + * RTFile stuff. + * RTFile stuff. + * RTFile stuff. + * + * + */ + + + + +RTDECL(int) RTDirRelFileOpen(RTDIR hDir, const char *pszRelFilename, uint64_t fOpen, PRTFILE phFile) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelFilename); + if (RT_SUCCESS(rc)) + rc = RTFileOpen(phFile, szPath, fOpen); + return rc; +} + + + +/* + * + * + * RTDir stuff. + * RTDir stuff. + * RTDir stuff. + * + * + */ + + + +RTDECL(int) RTDirRelDirOpen(RTDIR hDir, const char *pszDir, RTDIR *phDir) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszDir); + if (RT_SUCCESS(rc)) + rc = RTDirOpen(phDir, szPath); + return rc; + +} + + +RTDECL(int) RTDirRelDirOpenFiltered(RTDIR hDir, const char *pszDirAndFilter, RTDIRFILTER enmFilter, + uint32_t fFlags, RTDIR *phDir) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszDirAndFilter); + if (RT_SUCCESS(rc)) + rc = RTDirOpenFiltered(phDir, szPath, enmFilter, fFlags); + return rc; +} + + +RTDECL(int) RTDirRelDirCreate(RTDIR hDir, const char *pszRelPath, RTFMODE fMode, uint32_t fCreate, RTDIR *phSubDir) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreate(szPath, fMode, fCreate); + if (RT_SUCCESS(rc) && phSubDir) + rc = RTDirOpen(phSubDir, szPath); + } + return rc; +} + + +RTDECL(int) RTDirRelDirRemove(RTDIR hDir, const char *pszRelPath) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTDirRemove(szPath); + return rc; +} + + +/* + * + * RTPath stuff. + * RTPath stuff. + * RTPath stuff. + * + * + */ + + +RTDECL(int) RTDirRelPathQueryInfo(RTDIR hDir, const char *pszRelPath, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, pObjInfo, enmAddAttr, fFlags); + return rc; +} + + +RTDECL(int) RTDirRelPathSetMode(RTDIR hDir, const char *pszRelPath, RTFMODE fMode, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_FLAGS); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_WINDOWS + rc = RTPathSetMode(szPath, fMode); /** @todo fFlags is currently ignored. */ +#else + rc = VERR_NOT_IMPLEMENTED; /** @todo implement RTPathSetMode on windows. */ + RT_NOREF(fMode); +#endif + } + return rc; +} + + +RTDECL(int) RTDirRelPathSetTimes(RTDIR hDir, const char *pszRelPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTPathSetTimesEx(szPath, pAccessTime, pModificationTime, pChangeTime, pBirthTime, fFlags); + return rc; +} + + +RTDECL(int) RTDirRelPathSetOwner(RTDIR hDir, const char *pszRelPath, uint32_t uid, uint32_t gid, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_WINDOWS + rc = RTPathSetOwnerEx(szPath, uid, gid, fFlags); +#else + rc = VERR_NOT_IMPLEMENTED; + RT_NOREF(uid, gid, fFlags); +#endif + } + return rc; +} + + +RTDECL(int) RTDirRelPathRename(RTDIR hDirSrc, const char *pszSrc, RTDIR hDirDst, const char *pszDst, unsigned fRename) +{ + PRTDIRINTERNAL pThis = hDirSrc; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + PRTDIRINTERNAL pThat = hDirDst; + if (pThat != pThis) + { + AssertPtrReturn(pThat, VERR_INVALID_HANDLE); + AssertReturn(pThat->u32Magic != RTDIR_MAGIC, VERR_INVALID_HANDLE); + } + + char szSrcPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szSrcPath, sizeof(szSrcPath), pszSrc); + if (RT_SUCCESS(rc)) + { + char szDstPath[RTPATH_MAX]; + rc = rtDirRelBuildFullPath(pThis, szDstPath, sizeof(szDstPath), pszDst); + if (RT_SUCCESS(rc)) + rc = RTPathRename(szSrcPath, szDstPath, fRename); + } + return rc; +} + + +RTDECL(int) RTDirRelPathUnlink(RTDIR hDir, const char *pszRelPath, uint32_t fUnlink) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTPathUnlink(szPath, fUnlink); + return rc; +} + + +/* + * + * RTSymlink stuff. + * RTSymlink stuff. + * RTSymlink stuff. + * + * + */ + + +RTDECL(int) RTDirRelSymlinkCreate(RTDIR hDir, const char *pszSymlink, const char *pszTarget, + RTSYMLINKTYPE enmType, uint32_t fCreate) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszSymlink); + if (RT_SUCCESS(rc)) + rc = RTSymlinkCreate(szPath, pszTarget, enmType, fCreate); + return rc; +} + + +RTDECL(int) RTDirRelSymlinkRead(RTDIR hDir, const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszSymlink); + if (RT_SUCCESS(rc)) + rc = RTSymlinkRead(szPath, pszTarget, cbTarget, fRead); + return rc; +} + diff --git a/src/VBox/Runtime/r3/generic/semspinmutex-r3-generic.cpp b/src/VBox/Runtime/r3/generic/semspinmutex-r3-generic.cpp new file mode 100644 index 00000000..ae6d7c5e --- /dev/null +++ b/src/VBox/Runtime/r3/generic/semspinmutex-r3-generic.cpp @@ -0,0 +1,103 @@ +/* $Id: semspinmutex-r3-generic.cpp $ */ +/** @file + * IPRT - Spinning Mutex Semaphores, Ring-3, Generic. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/alloc.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> + + + +RTDECL(int) RTSemSpinMutexCreate(PRTSEMSPINMUTEX phSpinMtx, uint32_t fFlags) +{ + AssertReturn(!(fFlags & ~RTSEMSPINMUTEX_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtr(phSpinMtx); + + PRTCRITSECT pCritSect = (PRTCRITSECT)RTMemAlloc(sizeof(RTCRITSECT)); + if (!pCritSect) + return VERR_NO_MEMORY; + int rc = RTCritSectInitEx(pCritSect, RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_NO_LOCK_VAL, + NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "RTSemSpinMutex"); + if (RT_SUCCESS(rc)) + *phSpinMtx = (RTSEMSPINMUTEX)pCritSect; + else + RTMemFree(pCritSect); + return rc; +} +RT_EXPORT_SYMBOL(RTSemSpinMutexCreate); + + +RTDECL(int) RTSemSpinMutexDestroy(RTSEMSPINMUTEX hSpinMtx) +{ + if (hSpinMtx == NIL_RTSEMSPINMUTEX) + return VERR_INVALID_PARAMETER; + PRTCRITSECT pCritSect = (PRTCRITSECT)hSpinMtx; + int rc = RTCritSectDelete(pCritSect); + if (RT_SUCCESS(rc)) + RTMemFree(pCritSect); + return rc; +} +RT_EXPORT_SYMBOL(RTSemSpinMutexDestroy); + + +RTDECL(int) RTSemSpinMutexTryRequest(RTSEMSPINMUTEX hSpinMtx) +{ + return RTCritSectTryEnter((PRTCRITSECT)hSpinMtx); + +} +RT_EXPORT_SYMBOL(RTSemSpinMutexTryRequest); + + +RTDECL(int) RTSemSpinMutexRequest(RTSEMSPINMUTEX hSpinMtx) +{ + return RTCritSectEnter((PRTCRITSECT)hSpinMtx); +} +RT_EXPORT_SYMBOL(RTSemSpinMutexRequest); + + +RTDECL(int) RTSemSpinMutexRelease(RTSEMSPINMUTEX hSpinMtx) +{ + return RTCritSectLeave((PRTCRITSECT)hSpinMtx); +} +RT_EXPORT_SYMBOL(RTSemSpinMutexRelease); + diff --git a/src/VBox/Runtime/r3/haiku/Makefile.kup b/src/VBox/Runtime/r3/haiku/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/haiku/Makefile.kup diff --git a/src/VBox/Runtime/r3/haiku/rtProcInitExePath-haiku.cpp b/src/VBox/Runtime/r3/haiku/rtProcInitExePath-haiku.cpp new file mode 100644 index 00000000..51905b73 --- /dev/null +++ b/src/VBox/Runtime/r3/haiku/rtProcInitExePath-haiku.cpp @@ -0,0 +1,71 @@ +/* $Id: rtProcInitExePath-haiku.cpp $ */ +/** @file + * IPRT - rtProcInitName, Haiku. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#ifdef RT_OS_HAIKU +# include <image.h> +#endif + +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include "internal/process.h" +#include "internal/path.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ + image_info ImageInfo; + int32 Cookie = 0; + status_t status; + + /* + * Query the image name from the OS, convert and return it. + */ + status = get_next_image_info(0, &Cookie, &ImageInfo); + AssertReturn((status == B_OK), VERR_INTERNAL_ERROR); + + int rc = rtPathFromNativeCopy(pszPath, MIN(cchPath, MAXPATHLEN), ImageInfo.name, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszLink=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, MIN(cchPath, MAXPATHLEN), pszPath), rc); + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/haiku/time-haiku.cpp b/src/VBox/Runtime/r3/haiku/time-haiku.cpp new file mode 100644 index 00000000..a6419b83 --- /dev/null +++ b/src/VBox/Runtime/r3/haiku/time-haiku.cpp @@ -0,0 +1,95 @@ +/* $Id: time-haiku.cpp $ */ +/** @file + * IPRT - Time, Haiku. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define RTTIME_INCL_TIMEVAL +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#include <OS.h> + +#include <iprt/time.h> +#include <iprt/errcore.h> +#include "internal/time.h" + + +DECLINLINE(uint64_t) rtTimeGetSystemNanoTS(void) +{ + return (uint64_t)system_time() * RT_NS_1US; +} + + +/** + * Gets the current nanosecond timestamp. + * + * This differs from RTTimeNanoTS in that it will use system APIs and not do any + * resolution or performance optimizations. + * + * @returns nanosecond timestamp. + */ +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return rtTimeGetSystemNanoTS(); +} + + +/** + * Gets the current millisecond timestamp. + * + * This differs from RTTimeNanoTS in that it will use system APIs and not do any + * resolution or performance optimizations. + * + * @returns millisecond timestamp. + */ +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return rtTimeGetSystemNanoTS() / RT_NS_1MS; +} + + +RTDECL(int) RTTimeSet(PCRTTIMESPEC pTime) +{ + struct timeval tv; + RTTimeSpecGetTimeval(pTime, &tv); + set_real_time_clock(tv.tv_sec); + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/http-server.cpp b/src/VBox/Runtime/r3/http-server.cpp new file mode 100644 index 00000000..93c3f3fa --- /dev/null +++ b/src/VBox/Runtime/r3/http-server.cpp @@ -0,0 +1,1455 @@ +/* $Id: http-server.cpp $ */ +/** @file + * Simple HTTP server (RFC 7231) implementation. + * + * Known limitations so far: + * - Only HTTP 1.1. + * - Only supports GET + HEAD methods so far. + * - Only supports UTF-8 charset. + * - Only supports plain text and octet stream MIME types. + * - No content compression ("gzip", "x-gzip", ++). + * - No caching. + * - No redirections (via 302). + * - No encryption (TLS). + * - No IPv6 support. + * - No multi-threading. + * + * For WebDAV (optional via IPRT_HTTP_WITH_WEBDAV): + * - Only OPTIONS + PROPLIST methods are implemented (e.g. simple read-only support). + * - No pagination support for directory listings. + */ + +/* + * Copyright (C) 2020-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_HTTP +#include <iprt/http.h> +#include <iprt/http-server.h> +#include "internal/iprt.h" +#include "internal/magics.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/circbuf.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> /* For file mode flags. */ +#include <iprt/getopt.h> +#include <iprt/mem.h> +#include <iprt/log.h> +#include <iprt/path.h> +#include <iprt/poll.h> +#include <iprt/socket.h> +#include <iprt/sort.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/tcp.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Internal HTTP server instance. + */ +typedef struct RTHTTPSERVERINTERNAL +{ + /** Magic value. */ + uint32_t u32Magic; + /** Callback table. */ + RTHTTPSERVERCALLBACKS Callbacks; + /** Pointer to TCP server instance. */ + PRTTCPSERVER pTCPServer; + /** Pointer to user-specific data. Optional. */ + void *pvUser; + /** Size of user-specific data. Optional. */ + size_t cbUser; +} RTHTTPSERVERINTERNAL; +/** Pointer to an internal HTTP server instance. */ +typedef RTHTTPSERVERINTERNAL *PRTHTTPSERVERINTERNAL; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTHTTPSERVER_VALID_RETURN_RC(hHttpServer, a_rc) \ + do { \ + AssertPtrReturn((hHttpServer), (a_rc)); \ + AssertReturn((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC, (a_rc)); \ + } while (0) + +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTHTTPSERVER_VALID_RETURN(hHttpServer) RTHTTPSERVER_VALID_RETURN_RC((hHttpServer), VERR_INVALID_HANDLE) + +/** Validates a handle and returns (void) if not valid. */ +#define RTHTTPSERVER_VALID_RETURN_VOID(hHttpServer) \ + do { \ + AssertPtrReturnVoid(hHttpServer); \ + AssertReturnVoid((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC); \ + } while (0) + + +/** Handles a HTTP server callback with no arguments and returns. */ +#define RTHTTPSERVER_HANDLE_CALLBACK_RET(a_Name) \ + do \ + { \ + PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTHTTPCALLBACKDATA Data = { &pClient->State }; \ + return pCallbacks->a_Name(&Data); \ + } \ + return VERR_NOT_IMPLEMENTED; \ + } while (0) + +/** Handles a HTTP server callback with no arguments and sets rc accordingly. */ +#define RTHTTPSERVER_HANDLE_CALLBACK(a_Name) \ + do \ + { \ + PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \ + rc = pCallbacks->a_Name(&Data); \ + } \ + } while (0) + +/** Handles a HTTP server callback with arguments and sets rc accordingly. */ +#define RTHTTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \ + do \ + { \ + PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \ + rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \ + } \ + } while (0) + +/** Handles a HTTP server callback with arguments and returns. */ +#define RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \ + do \ + { \ + PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \ + if (pCallbacks->a_Name) \ + { \ + RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \ + return pCallbacks->a_Name(&Data, __VA_ARGS__); \ + } \ + } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Structure for maintaining an internal HTTP server client. + */ +typedef struct RTHTTPSERVERCLIENT +{ + /** Pointer to internal server state. */ + PRTHTTPSERVERINTERNAL pServer; + /** Socket handle the client is bound to. */ + RTSOCKET hSocket; + /** Actual client state. */ + RTHTTPSERVERCLIENTSTATE State; +} RTHTTPSERVERCLIENT; +/** Pointer to an internal HTTP server client state. */ +typedef RTHTTPSERVERCLIENT *PRTHTTPSERVERCLIENT; + +/** Function pointer declaration for a specific HTTP server method handler. */ +typedef DECLCALLBACKTYPE(int, FNRTHTTPSERVERMETHOD,(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)); +/** Pointer to a FNRTHTTPSERVERMETHOD(). */ +typedef FNRTHTTPSERVERMETHOD *PFNRTHTTPSERVERMETHOD; + +/** + * Static lookup table for some file extensions <-> MIME type. Add more as needed. + * Keep this alphabetical (file extension). + */ +static const struct +{ + /** File extension. */ + const char *pszExt; + /** MIME type. */ + const char *pszMIMEType; +} s_aFileExtMIMEType[] = { + { ".arj", "application/x-arj-compressed" }, + { ".asf", "video/x-ms-asf" }, + { ".avi", "video/x-msvideo" }, + { ".bmp", "image/bmp" }, + { ".css", "text/css" }, + { ".doc", "application/msword" }, + { ".exe", "application/octet-stream" }, + { ".gif", "image/gif" }, + { ".gz", "application/x-gunzip" }, + { ".htm", "text/html" }, + { ".html", "text/html" }, + { ".ico", "image/x-icon" }, + { ".js", "application/x-javascript" }, + { ".json", "text/json" }, + { ".jpg", "image/jpeg" }, + { ".jpeg", "image/jpeg" }, + { ".ogg", "application/ogg" }, + { ".m3u", "audio/x-mpegurl" }, + { ".m4v", "video/x-m4v" }, + { ".mid", "audio/mid" }, + { ".mov", "video/quicktime" }, + { ".mp3", "audio/x-mp3" }, + { ".mp4", "video/mp4" }, + { ".mpg", "video/mpeg" }, + { ".mpeg", "video/mpeg" }, + { ".pdf", "application/pdf" }, + { ".png", "image/png" }, + { ".ra", "audio/x-pn-realaudio" }, + { ".ram", "audio/x-pn-realaudio" }, + { ".rar", "application/x-arj-compressed" }, + { ".rtf", "application/rtf" }, + { ".shtm", "text/html" }, + { ".shtml", "text/html" }, + { ".svg", "image/svg+xml" }, + { ".swf", "application/x-shockwave-flash" }, + { ".torrent", "application/x-bittorrent" }, + { ".tar", "application/x-tar" }, + { ".tgz", "application/x-tar-gz" }, + { ".ttf", "application/x-font-ttf" }, + { ".txt", "text/plain" }, + { ".wav", "audio/x-wav" }, + { ".webm", "video/webm" }, + { ".xml", "text/xml" }, + { ".xls", "application/excel" }, + { ".xsl", "application/xml" }, + { ".xslt", "application/xml" }, + { ".zip", "application/x-zip-compressed" }, + { NULL, NULL } +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** @name Method handlers. + * @{ + */ +static FNRTHTTPSERVERMETHOD rtHttpServerHandleGET; +static FNRTHTTPSERVERMETHOD rtHttpServerHandleHEAD; +#ifdef IPRT_HTTP_WITH_WEBDAV + static FNRTHTTPSERVERMETHOD rtHttpServerHandleOPTIONS; + static FNRTHTTPSERVERMETHOD rtHttpServerHandlePROPFIND; +#endif +/** @} */ + +/** + * Structure for maintaining a single method entry for the methods table. + */ +typedef struct RTHTTPSERVERMETHOD_ENTRY +{ + /** Method ID. */ + RTHTTPMETHOD enmMethod; + /** Function pointer invoked to handle the command. */ + PFNRTHTTPSERVERMETHOD pfnMethod; +} RTHTTPSERVERMETHOD_ENTRY; +/** Pointer to a command entry. */ +typedef RTHTTPSERVERMETHOD_ENTRY *PRTHTTPMETHOD_ENTRY; + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Table of handled methods. + */ +static const RTHTTPSERVERMETHOD_ENTRY g_aMethodMap[] = +{ + { RTHTTPMETHOD_GET, rtHttpServerHandleGET }, + { RTHTTPMETHOD_HEAD, rtHttpServerHandleHEAD }, +#ifdef IPRT_HTTP_WITH_WEBDAV + { RTHTTPMETHOD_OPTIONS, rtHttpServerHandleOPTIONS }, + { RTHTTPMETHOD_PROPFIND, rtHttpServerHandlePROPFIND }, +#endif + { RTHTTPMETHOD_END, NULL } +}; + +/** Maximum length in characters a HTTP server path can have (excluding termination). */ +#define RTHTTPSERVER_MAX_PATH RTPATH_MAX + + +/********************************************************************************************************************************* +* Internal functions * +*********************************************************************************************************************************/ + +/** + * Guesses the HTTP MIME type based on a given file extension. + * + * Note: Has to include the beginning dot, e.g. ".mp3" (see IPRT). + * + * @returns Guessed MIME type, or "application/octet-stream" if not found. + * @param pszFileExt File extension to guess MIME type for. + */ +static const char *rtHttpServerGuessMIMEType(const char *pszFileExt) +{ + if (pszFileExt) + { + size_t i = 0; + while (s_aFileExtMIMEType[i++].pszExt) /* Slow, but does the job for now. */ + { + if (!RTStrICmp(pszFileExt, s_aFileExtMIMEType[i].pszExt)) + return s_aFileExtMIMEType[i].pszMIMEType; + } + } + + return "application/octet-stream"; +} + +/** + * Initializes a HTTP body. + * + * @param pBody Body to initialize. + * @param cbSize Size of body (in bytes) to allocate. Optional and can be 0. + */ +static int rtHttpServerBodyInit(PRTHTTPBODY pBody, size_t cbSize) +{ + if (cbSize) + { + pBody->pvBody = RTMemAlloc(cbSize); + AssertPtrReturn(pBody->pvBody, VERR_NO_MEMORY); + pBody->cbBodyAlloc = cbSize; + } + else + { + pBody->pvBody = NULL; + pBody->cbBodyAlloc = 0; + } + + pBody->cbBodyUsed = 0; + pBody->offBody = 0; + + return VINF_SUCCESS; +} + +/** + * Destroys a HTTP body. + * + * @param pBody Body to destroy. + */ +static void rtHttpServerBodyDestroy(PRTHTTPBODY pBody) +{ + if (!pBody) + return; + + if (pBody->pvBody) + { + Assert(pBody->cbBodyAlloc); + + RTMemFree(pBody->pvBody); + pBody->pvBody = NULL; + } + + pBody->cbBodyAlloc = 0; + pBody->cbBodyUsed = 0; + pBody->offBody = 0; +} + +/** + * Allocates and initializes a new client request. + * + * @returns Pointer to the new client request, or NULL on OOM. + * Needs to be free'd with rtHttpServerReqFree(). + */ +static PRTHTTPSERVERREQ rtHttpServerReqAlloc(void) +{ + PRTHTTPSERVERREQ pReq = (PRTHTTPSERVERREQ)RTMemAllocZ(sizeof(RTHTTPSERVERREQ)); + AssertPtrReturn(pReq, NULL); + + int rc2 = RTHttpHeaderListInit(&pReq->hHdrLst); + AssertRC(rc2); + + rc2 = rtHttpServerBodyInit(&pReq->Body, 0 /* cbSize */); + AssertRC(rc2); + + return pReq; +} + +/** + * Frees a formerly allocated client request. + * + * @param pReq Pointer to client request to free. + * The pointer will be invalid on return. + */ +static void rtHttpServerReqFree(PRTHTTPSERVERREQ pReq) +{ + if (!pReq) + return; + + RTStrFree(pReq->pszUrl); + + RTHttpHeaderListDestroy(pReq->hHdrLst); + + rtHttpServerBodyDestroy(&pReq->Body); + + RTMemFree(pReq); + pReq = NULL; +} + +/** + * Initializes a HTTP server response with an allocated body size. + * + * @returns VBox status code. + * @param pResp HTTP server response to initialize. + * @param cbBody Body size (in bytes) to allocate. + */ +RTR3DECL(int) RTHttpServerResponseInitEx(PRTHTTPSERVERRESP pResp, size_t cbBody) +{ + pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET; + + int rc = RTHttpHeaderListInit(&pResp->hHdrLst); + AssertRCReturn(rc, rc); + + rc = rtHttpServerBodyInit(&pResp->Body, cbBody); + + return rc; +} + +/** + * Initializes a HTTP server response. + * + * @returns VBox status code. + * @param pResp HTTP server response to initialize. + */ +RTR3DECL(int) RTHttpServerResponseInit(PRTHTTPSERVERRESP pResp) +{ + return RTHttpServerResponseInitEx(pResp, 0 /* cbBody */); +} + +/** + * Destroys a formerly initialized HTTP server response. + * + * @param pResp Pointer to HTTP server response to destroy. + */ +RTR3DECL(void) RTHttpServerResponseDestroy(PRTHTTPSERVERRESP pResp) +{ + if (!pResp) + return; + + pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET; + + RTHttpHeaderListDestroy(pResp->hHdrLst); + + rtHttpServerBodyDestroy(&pResp->Body); +} + + +/********************************************************************************************************************************* +* Protocol Functions * +*********************************************************************************************************************************/ + +/** + * Logs the HTTP protocol communication to the debug logger (2). + * + * @param pClient Client to log communication for. + * @param fWrite Whether the server is writing (to client) or reading (from client). + * @param pszData Actual protocol communication data to log. + */ +static void rtHttpServerLogProto(PRTHTTPSERVERCLIENT pClient, bool fWrite, const char *pszData) +{ + RT_NOREF(pClient); + +#ifdef LOG_ENABLED + if (!pszData) /* Nothing to log? Bail out. */ + return; + + char **ppapszStrings; + size_t cStrings; + int rc2 = RTStrSplit(pszData, strlen(pszData), RTHTTPSERVER_HTTP11_EOL_STR, &ppapszStrings, &cStrings); + if (RT_SUCCESS(rc2)) + { + for (size_t i = 0; i < cStrings; i++) + { + Log2(("%s %s\n", fWrite ? ">" : "<", ppapszStrings[i])); + RTStrFree(ppapszStrings[i]); + } + + RTMemFree(ppapszStrings); + } +#else + RT_NOREF(fWrite, pszData); +#endif +} + +/** + * Writes HTTP protocol communication data to a connected client. + * + * @returns VBox status code. + * @param pClient Client to write data to. + * @param pszData Data to write. Must be zero-terminated. + */ +static int rtHttpServerWriteProto(PRTHTTPSERVERCLIENT pClient, const char *pszData) +{ + rtHttpServerLogProto(pClient, true /* fWrite */, pszData); + return RTTcpWrite(pClient->hSocket, pszData, strlen(pszData)); +} + +/** + * Main function for sending a response back to the client. + * + * @returns VBox status code. + * @param pClient Client to reply to. + * @param enmSts Status code to send. + */ +static int rtHttpServerSendResponse(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts) +{ + char *pszResp; + int rc = RTStrAPrintf(&pszResp, + "%s %RU32 %s\r\n", RTHTTPVER_1_1_STR, enmSts, RTHttpStatusToStr(enmSts)); + AssertRCReturn(rc, rc); + rc = rtHttpServerWriteProto(pClient, pszResp); + RTStrFree(pszResp); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main function for sending response headers back to the client. + * + * @returns VBox status code. + * @param pClient Client to reply to. + * @param pHdrLst Header list to send. Optional and can be NULL. + */ +static int rtHttpServerSendResponseHdrEx(PRTHTTPSERVERCLIENT pClient, PRTHTTPHEADERLIST pHdrLst) +{ + RTHTTPHEADERLIST HdrLst; + int rc = RTHttpHeaderListInit(&HdrLst); + AssertRCReturn(rc, rc); + +#ifdef DEBUG + /* Include a timestamp when running a debug build. */ + RTTIMESPEC tsNow; + char szTS[64]; + RTTimeSpecToString(RTTimeNow(&tsNow), szTS, sizeof(szTS)); + rc = RTHttpHeaderListAdd(HdrLst, "Date", szTS, strlen(szTS), RTHTTPHEADERLISTADD_F_BACK); + AssertRCReturn(rc, rc); +#endif + + /* Note: Deliberately don't include the VBox version due to security reasons. */ + rc = RTHttpHeaderListAdd(HdrLst, "Server", "Oracle VirtualBox", strlen("Oracle VirtualBox"), RTHTTPHEADERLISTADD_F_BACK); + AssertRCReturn(rc, rc); + +#ifdef IPRT_HTTP_WITH_WEBDAV + rc = RTHttpHeaderListAdd(HdrLst, "Allow", "GET, HEAD, PROPFIND", strlen("GET, HEAD, PROPFIND"), RTHTTPHEADERLISTADD_F_BACK); + AssertRCReturn(rc, rc); + rc = RTHttpHeaderListAdd(HdrLst, "DAV", "1", strlen("1"), RTHTTPHEADERLISTADD_F_BACK); /* Note: v1 is sufficient for read-only access. */ + AssertRCReturn(rc, rc); +#endif + + char *pszHdr = NULL; + + size_t i = 0; + const char *pszEntry; + while ((pszEntry = RTHttpHeaderListGetByOrdinal(HdrLst, i++)) != NULL) + { + rc = RTStrAAppend(&pszHdr, pszEntry); + AssertRCBreak(rc); + rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR); + AssertRCBreak(rc); + } + + /* Append optional headers, if any. */ + if (pHdrLst) + { + i = 0; + while ((pszEntry = RTHttpHeaderListGetByOrdinal(*pHdrLst, i++)) != NULL) + { + rc = RTStrAAppend(&pszHdr, pszEntry); + AssertRCBreak(rc); + rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR); + AssertRCBreak(rc); + } + } + + if (RT_SUCCESS(rc)) + { + /* Append trailing EOL. */ + rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR); + if (RT_SUCCESS(rc)) + rc = rtHttpServerWriteProto(pClient, pszHdr); + } + + RTStrFree(pszHdr); + + RTHttpHeaderListDestroy(HdrLst); + + LogFlowFunc(("rc=%Rrc\n", rc)); + return rc; +} + +/** + * Replies with (three digit) response status back to the client, extended version. + * + * @returns VBox status code. + * @param pClient Client to reply to. + * @param enmSts Status code to send. + * @param pHdrLst Header list to send. Optional and can be NULL. + */ +static int rtHttpServerSendResponseEx(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst) +{ + int rc = rtHttpServerSendResponse(pClient, enmSts); + if (RT_SUCCESS(rc)) + rc = rtHttpServerSendResponseHdrEx(pClient, pHdrLst); + + return rc; +} + +/** + * Replies with (three digit) response status back to the client. + * + * @returns VBox status code. + * @param pClient Client to reply to. + * @param enmSts Status code to send. + */ +static int rtHttpServerSendResponseSimple(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts) +{ + return rtHttpServerSendResponseEx(pClient, enmSts, NULL /* pHdrLst */); +} + +/** + * Sends a chunk of the response body to the client. + * + * @returns VBox status code. + * @param pClient Client to send body to. + * @param pvBuf Data buffer to send. + * @param cbBuf Size (in bytes) of data buffer to send. + * @param pcbSent Where to store the sent bytes. Optional and can be NULL. + */ +static int rtHttpServerSendResponseBody(PRTHTTPSERVERCLIENT pClient, void *pvBuf, size_t cbBuf, size_t *pcbSent) +{ + int rc = RTTcpWrite(pClient->hSocket, pvBuf, cbBuf); + if ( RT_SUCCESS(rc) + && pcbSent) + *pcbSent = cbBuf; + + return rc; +} + +/** + * Resolves a VBox status code to a HTTP status code. + * + * @returns Resolved HTTP status code, or RTHTTPSTATUS_INTERNALSERVERERROR if not able to resolve. + * @param rc VBox status code to resolve. + */ +static RTHTTPSTATUS rtHttpServerRcToStatus(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return RTHTTPSTATUS_OK; + case VERR_INVALID_PARAMETER: return RTHTTPSTATUS_BADREQUEST; + case VERR_INVALID_POINTER: return RTHTTPSTATUS_BADREQUEST; + case VERR_NOT_IMPLEMENTED: return RTHTTPSTATUS_NOTIMPLEMENTED; + case VERR_NOT_SUPPORTED: return RTHTTPSTATUS_NOTIMPLEMENTED; + case VERR_PATH_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND; + case VERR_FILE_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND; + case VERR_IS_A_DIRECTORY: return RTHTTPSTATUS_FORBIDDEN; + case VERR_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND; + default: + break; + } + + AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc)); + return RTHTTPSTATUS_INTERNALSERVERERROR; +} + + +/********************************************************************************************************************************* +* Command Protocol Handlers * +*********************************************************************************************************************************/ + +/** + * Handler for the GET method. + * + * @returns VBox status code. + * @param pClient Client to handle GET method for. + * @param pReq Client request to handle. + */ +static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq) +{ + LogFlowFuncEnter(); + + int rc = VINF_SUCCESS; + + /* If a low-level GET request handler is defined, call it and return. */ + RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq); + + RTFSOBJINFO fsObj; + RT_ZERO(fsObj); /* Shut up MSVC. */ + + char *pszMIMEHint = NULL; + + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, &pszMIMEHint); + if (RT_FAILURE(rc)) + return rc; + + void *pvHandle = NULL; + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle); + + if (RT_SUCCESS(rc)) + { + size_t cbBuf = _64K; + void *pvBuf = RTMemAlloc(cbBuf); + AssertPtrReturn(pvBuf, VERR_NO_MEMORY); + + for (;;) + { + RTHTTPHEADERLIST HdrLst; + rc = RTHttpHeaderListInit(&HdrLst); + AssertRCReturn(rc, rc); + + char szVal[16]; + + /* Note: For directories fsObj.cbObject contains the actual size (in bytes) + * of the body data for the directory listing. */ + + ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject); + AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW); + rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK); + AssertRCBreak(rc); + + cch = RTStrPrintf2(szVal, sizeof(szVal), "identity"); + AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW); + rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK); + AssertRCBreak(rc); + + if (pszMIMEHint == NULL) + { + const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl)); + rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK); + } + else + { + rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIMEHint, strlen(pszMIMEHint), RTHTTPHEADERLISTADD_F_BACK); + RTStrFree(pszMIMEHint); + pszMIMEHint = NULL; + } + AssertRCBreak(rc); + + if (pClient->State.msKeepAlive) + { + /* If the client requested to keep alive the connection, + * always override this with 30s and report this back to the client. */ + pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Make this configurable. */ +#ifdef DEBUG_andy + pClient->State.msKeepAlive = 5000; +#endif + cch = RTStrPrintf2(szVal, sizeof(szVal), "timeout=%RU64", pClient->State.msKeepAlive / RT_MS_1SEC); /** @todo No pipelining support here yet. */ + AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW); + rc = RTHttpHeaderListAdd(HdrLst, "Keep-Alive", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK); + AssertRCReturn(rc, rc); + } + + rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst); + + RTHttpHeaderListDestroy(HdrLst); + + if (rc == VERR_BROKEN_PIPE) /* Could happen on fast reloads. */ + break; + AssertRCReturn(rc, rc); + + size_t cbToRead = fsObj.cbObject; + size_t cbRead = 0; /* Shut up GCC. */ + size_t cbWritten = 0; /* Ditto. */ + while (cbToRead) + { + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead); + if (RT_FAILURE(rc)) + break; + rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten); + AssertBreak(cbToRead >= cbWritten); + cbToRead -= cbWritten; + if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */ + { + rc = VINF_SUCCESS; + break; + } + AssertRCBreak(rc); + } + + break; + } /* for (;;) */ + + RTMemFree(pvBuf); + + int rc2 = rc; /* Save rc. */ + + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pvHandle); + + if (RT_FAILURE(rc2)) /* Restore original rc on failure. */ + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Handler for the HEAD method. + * + * @returns VBox status code. + * @param pClient Client to handle HEAD method for. + * @param pReq Client request to handle. + */ +static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq) +{ + LogFlowFuncEnter(); + + /* If a low-level HEAD request handler is defined, call it and return. */ + RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnHeadRequest, pReq); + + int rc = VINF_SUCCESS; + + RTFSOBJINFO fsObj; + RT_ZERO(fsObj); /* Shut up MSVC. */ + + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */); + if (RT_SUCCESS(rc)) + { + RTHTTPHEADERLIST HdrLst; + rc = RTHttpHeaderListInit(&HdrLst); + AssertRCReturn(rc, rc); + + /* + * Note: A response to a HEAD request does not have a body. + * All entity headers below are assumed to describe the the response a similar GET + * request would return (but then with a body). + */ + char szVal[16]; + + ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject); + AssertReturn(cch, VERR_BUFFER_OVERFLOW); + rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK); + AssertRCReturn(rc, rc); + + cch = RTStrPrintf2(szVal, sizeof(szVal), "identity"); + AssertReturn(cch, VERR_BUFFER_OVERFLOW); + rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK); + AssertRCReturn(rc, rc); + + const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl)); + rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK); + AssertRCReturn(rc, rc); + + rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst); + AssertRCReturn(rc, rc); + + RTHttpHeaderListDestroy(HdrLst); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef IPRT_HTTP_WITH_WEBDAV +/** + * Handler for the OPTIONS method. + * + * @returns VBox status code. + * @param pClient Client to handle OPTIONS method for. + * @param pReq Client request to handle. + */ +static DECLCALLBACK(int) rtHttpServerHandleOPTIONS(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq) +{ + LogFlowFuncEnter(); + + RT_NOREF(pReq); + + int rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, NULL /* pHdrLst */); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Handler for the PROPFIND (WebDAV) method. + * + * @returns VBox status code. + * @param pClient Client to handle PROPFIND method for. + * @param pReq Client request to handle. + */ +static DECLCALLBACK(int) rtHttpServerHandlePROPFIND(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq) +{ + LogFlowFuncEnter(); + + int rc = VINF_SUCCESS; + + /* If a low-level GET request handler is defined, call it and return. */ + RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq); + + RTFSOBJINFO fsObj; + RT_ZERO(fsObj); /* Shut up MSVC. */ + + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */); + if (RT_FAILURE(rc)) + return rc; + + void *pvHandle = NULL; + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle); + + if (RT_SUCCESS(rc)) + { + size_t cbBuf = _64K; + void *pvBuf = RTMemAlloc(cbBuf); + AssertPtrReturn(pvBuf, VERR_NO_MEMORY); + + for (;;) + { + RTHTTPHEADERLIST HdrLst; + rc = RTHttpHeaderListInit(&HdrLst); + AssertRCReturn(rc, rc); + + char szVal[16]; + + rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", "text/xml; charset=utf-8", strlen("text/xml; charset=utf-8"), RTHTTPHEADERLISTADD_F_BACK); + AssertRCBreak(rc); + + /* Note: For directories fsObj.cbObject contains the actual size (in bytes) + * of the body data for the directory listing. */ + + ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject); + AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW); + rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK); + AssertRCBreak(rc); + + rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_MULTISTATUS, &HdrLst); + AssertRCReturn(rc, rc); + + RTHttpHeaderListDestroy(HdrLst); + + size_t cbToRead = fsObj.cbObject; + size_t cbRead = 0; /* Shut up GCC. */ + size_t cbWritten = 0; /* Ditto. */ + while (cbToRead) + { + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead); + if (RT_FAILURE(rc)) + break; + //rtHttpServerLogProto(pClient, true /* fWrite */, (const char *)pvBuf); + rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten); + AssertBreak(cbToRead >= cbWritten); + cbToRead -= cbWritten; + if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */ + { + rc = VINF_SUCCESS; + break; + } + AssertRCBreak(rc); + } + + break; + } /* for (;;) */ + + RTMemFree(pvBuf); + + int rc2 = rc; /* Save rc. */ + + RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pvHandle); + + if (RT_FAILURE(rc2)) /* Restore original rc on failure. */ + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* IPRT_HTTP_WITH_WEBDAV */ + +/** + * Validates if a given path is valid or not. + * + * @returns \c true if path is valid, or \c false if not. + * @param pszPath Path to check. + * @param fIsAbsolute Whether the path to check is an absolute path or not. + */ +static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute) +{ + if (!pszPath) + return false; + + bool fIsValid = strlen(pszPath) + && RTStrIsValidEncoding(pszPath) + && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */ + if ( fIsValid + && fIsAbsolute) + { + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc2)) + { + fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode) + || RTFS_IS_FILE(objInfo.Attr.fMode); + + /* No symlinks and other stuff not allowed. */ + } + else + fIsValid = false; + } + + LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid)); + return fIsValid; + +} + +/** + * Parses headers and sets (replaces) a given header list. + * + * @returns VBox status code. + * @param hList Header list to fill parsed headers in. + * @param cStrings Number of strings to parse for \a papszStrings. + * @param papszStrings Array of strings to parse. + */ +static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, size_t cStrings, char **papszStrings) +{ + /* Nothing to parse left? Bail out early. */ + if ( !cStrings + || !papszStrings) + return VINF_SUCCESS; + +#ifdef LOG_ENABLED + for (size_t i = 0; i < cStrings; i++) + LogFlowFunc(("Header: %s\n", papszStrings[i])); +#endif + + int rc = RTHttpHeaderListSet(hList, cStrings, papszStrings); + + LogFlowFunc(("rc=%Rrc, cHeaders=%zu\n", rc, cStrings)); + return rc; +} + +/** + * Main function for parsing and allocating a client request. + * + * See: https://tools.ietf.org/html/rfc2616#section-2.2 + * + * @returns VBox status code. + * @param pClient Client to parse request from. + * @param pszReq Request string with headers to parse. + * @param cbReq Size (in bytes) of request string to parse. + * @param ppReq Where to store the allocated client request on success. + * Needs to be free'd via rtHttpServerReqFree(). + */ +static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, const char *pszReq, size_t cbReq, + PRTHTTPSERVERREQ *ppReq) +{ + RT_NOREF(pClient); + + AssertPtrReturn(pszReq, VERR_INVALID_POINTER); + AssertReturn(cbReq, VERR_INVALID_PARAMETER); + + /* We only support UTF-8 charset for now. */ + AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER); + + char **ppapszReq = NULL; + size_t cReq = 0; + int rc = RTStrSplit(pszReq, cbReq, RTHTTPSERVER_HTTP11_EOL_STR, &ppapszReq, &cReq); + if (RT_FAILURE(rc)) + return rc; + + if (!cReq) + return VERR_INVALID_PARAMETER; + +#ifdef LOG_ENABLED + for (size_t i = 0; i < cReq; i++) + LogFlowFunc(("%s\n", ppapszReq[i])); +#endif + + PRTHTTPSERVERREQ pReq = NULL; + + char **ppapszFirstLine = NULL; + size_t cFirstLine = 0; + rc = RTStrSplit(ppapszReq[0], strlen(ppapszReq[0]), " ", &ppapszFirstLine, &cFirstLine); + if (RT_SUCCESS(rc)) + { + if (cFirstLine < 3) /* At leat the method, path and version has to be present. */ + rc = VERR_INVALID_PARAMETER; + } + + while (RT_SUCCESS(rc)) /* To use break. */ + { + pReq = rtHttpServerReqAlloc(); + AssertPtrBreakStmt(pReq, rc = VERR_NO_MEMORY); + + /* + * Parse method to use. Method names are case sensitive. + */ + const char *pszMethod = ppapszFirstLine[0]; + if (!RTStrCmp(pszMethod, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET; + else if (!RTStrCmp(pszMethod, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD; +#ifdef IPRT_HTTP_WITH_WEBDAV + else if (!RTStrCmp(pszMethod, "OPTIONS")) pReq->enmMethod = RTHTTPMETHOD_OPTIONS; + else if (!RTStrCmp(pszMethod, "PROPFIND")) pReq->enmMethod = RTHTTPMETHOD_PROPFIND; +#endif + else + { + rc = VERR_NOT_SUPPORTED; + break; + } + + /* + * Parse requested path. + */ + /** @todo Do URL unescaping here. */ + const char *pszPath = ppapszFirstLine[1]; + if (!rtHttpServerPathIsValid(pszPath, false /* fIsAbsolute */)) + { + rc = VERR_PATH_NOT_FOUND; + break; + } + + pReq->pszUrl = RTStrDup(pszPath); + + /* + * Parse HTTP version to use. + * We're picky heree: Only HTTP 1.1 is supported by now. + */ + const char *pszVer = ppapszFirstLine[2]; + if (RTStrCmp(pszVer, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */ + { + rc = VERR_NOT_SUPPORTED; + break; + } + + /** @todo Anything else needed for the first line here? */ + + /* + * Process headers, if any. + */ + if (cReq > 1) + { + rc = rtHttpServerParseHeaders(pReq->hHdrLst, cReq - 1, &ppapszReq[1]); + if (RT_SUCCESS(rc)) + { + if (RTHttpHeaderListGet(pReq->hHdrLst, "Connection", RTSTR_MAX)) + pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Insert the real value here. */ + } + } + break; + } /* for (;;) */ + + /* + * Cleanup. + */ + + for (size_t i = 0; i < cFirstLine; i++) + RTStrFree(ppapszFirstLine[i]); + RTMemFree(ppapszFirstLine); + + for (size_t i = 0; i < cReq; i++) + RTStrFree(ppapszReq[i]); + RTMemFree(ppapszReq); + + if (RT_FAILURE(rc)) + { + rtHttpServerReqFree(pReq); + pReq = NULL; + } + else + *ppReq = pReq; + + return rc; +} + +/** + * Main function for processing client requests. + * + * @returns VBox status code. + * @param pClient Client to process request for. + * @param pszReq Request string to parse and handle. + * @param cbReq Size (in bytes) of request string. + */ +static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq) +{ + RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET; + + PRTHTTPSERVERREQ pReq = NULL; /* Shut up GCC. */ + int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl)); + + unsigned i = 0; + for (; i < RT_ELEMENTS(g_aMethodMap); i++) + { + const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i]; + if (pReq->enmMethod == pMethodEntry->enmMethod) + { + /* Hand in request to method handler. */ + int rcMethod = pMethodEntry->pfnMethod(pClient, pReq); + if (RT_FAILURE(rcMethod)) + LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod)); + + enmSts = rtHttpServerRcToStatus(rcMethod); + break; + } + } + + if (i == RT_ELEMENTS(g_aMethodMap)) + enmSts = RTHTTPSTATUS_NOTIMPLEMENTED; + + rtHttpServerReqFree(pReq); + } + else + enmSts = RTHTTPSTATUS_BADREQUEST; + + if (enmSts != RTHTTPSTATUS_INTERNAL_NOT_SET) + { + int rc2 = rtHttpServerSendResponseSimple(pClient, enmSts); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main loop for processing client requests. + * + * @returns VBox status code. + * @param pClient Client to process requests for. + */ +static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient) +{ + int rc; + + char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1]; + + LogFlowFunc(("Client connected\n")); + + /* Initialize client state. */ + pClient->State.msKeepAlive = 0; + + RTMSINTERVAL cWaitMs = RT_INDEFINITE_WAIT; /* The first wait always waits indefinitely. */ + uint64_t tsLastReadMs = 0; + + for (;;) + { + rc = RTTcpSelectOne(pClient->hSocket, cWaitMs); + if (RT_FAILURE(rc)) + { + LogFlowFunc(("RTTcpSelectOne=%Rrc (cWaitMs=%RU64)\n", rc, cWaitMs)); + if (rc == VERR_TIMEOUT) + { + if (pClient->State.msKeepAlive) /* Keep alive handling needed? */ + { + if (!tsLastReadMs) + tsLastReadMs = RTTimeMilliTS(); + const uint64_t tsDeltaMs = pClient->State.msKeepAlive - (RTTimeMilliTS() - tsLastReadMs); + LogFlowFunc(("tsLastReadMs=%RU64, tsDeltaMs=%RU64\n", tsLastReadMs, tsDeltaMs)); + Log3Func(("Keep alive active (%RU32ms): %RU64ms remaining\n", pClient->State.msKeepAlive, tsDeltaMs)); + if ( tsDeltaMs > cWaitMs + && tsDeltaMs < pClient->State.msKeepAlive) + continue; + + LogFunc(("Keep alive active: Client did not respond within %RU32ms, closing\n", pClient->State.msKeepAlive)); + rc = VINF_SUCCESS; + break; + } + } + + break; + } + + LogFlowFunc(("Reading client request ...\n")); + + tsLastReadMs = RTTimeMilliTS(); + cWaitMs = 200; /* All consequtive waits do busy waiting for now. */ + + char *pszReq = szReq; + size_t cbRead; + size_t cbToRead = sizeof(szReq); + size_t cbReadTotal = 0; + + do + { + rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead); + if (RT_FAILURE(rc)) + break; + + if (!cbRead) + break; + + /* Make sure to terminate string read so far. */ + pszReq[cbRead] = '\0'; + + /* End of request reached? */ + /** @todo BUGBUG Improve this. */ + char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n"); + if (pszEOR) + { + cbReadTotal = pszEOR - pszReq; + *pszEOR = '\0'; + break; + } + + AssertBreak(cbToRead >= cbRead); + cbToRead -= cbRead; + cbReadTotal += cbRead; + AssertBreak(cbReadTotal <= sizeof(szReq)); + pszReq += cbRead; + + } while (cbToRead); + + if ( RT_SUCCESS(rc) + && cbReadTotal) + { + LogFlowFunc(("Received client request (%zu bytes)\n", cbReadTotal)); + + rtHttpServerLogProto(pClient, false /* fWrite */, szReq); + + rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal); + } + else + break; + + } /* for */ + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_NET_CONNECTION_RESET_BY_PEER: + { + LogFunc(("Client closed the connection\n")); + rc = VINF_SUCCESS; + break; + } + + default: + LogFunc(("Client processing failed with %Rrc\n", rc)); + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Per-client thread for serving the server's control connection. + * + * @returns VBox status code. + * @param hSocket Socket handle to use for the control connection. + * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL. + */ +static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser) +{ + PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser; + RTHTTPSERVER_VALID_RETURN(pThis); + + LogFlowFuncEnter(); + + RTHTTPSERVERCLIENT Client; + RT_ZERO(Client); + + Client.pServer = pThis; + Client.hSocket = hSocket; + + return rtHttpServerClientMain(&Client); +} + +RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort, + PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser) +{ + AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER); + AssertPtrReturn(pszAddress, VERR_INVALID_POINTER); + AssertReturn (uPort, VERR_INVALID_PARAMETER); + AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER); + /* pvUser is optional. */ + + int rc; + + PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL)); + if (pThis) + { + pThis->u32Magic = RTHTTPSERVER_MAGIC; + pThis->Callbacks = *pCallbacks; + pThis->pvUser = pvUser; + pThis->cbUser = cbUser; + + rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv", + rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer); + if (RT_SUCCESS(rc)) + { + *hHttpServer = (RTHTTPSERVER)pThis; + } + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer) +{ + if (hHttpServer == NIL_RTHTTPSERVER) + return VINF_SUCCESS; + + PRTHTTPSERVERINTERNAL pThis = hHttpServer; + RTHTTPSERVER_VALID_RETURN(pThis); + + AssertPtr(pThis->pTCPServer); + + int rc = VINF_SUCCESS; + + PRTHTTPSERVERCALLBACKS pCallbacks = &pThis->Callbacks; + if (pCallbacks->pfnDestroy) + { + RTHTTPCALLBACKDATA Data = { NULL /* pClient */, pThis->pvUser, pThis->cbUser }; + rc = pCallbacks->pfnDestroy(&Data); + } + + if (RT_SUCCESS(rc)) + { + rc = RTTcpServerDestroy(pThis->pTCPServer); + if (RT_SUCCESS(rc)) + { + pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD; + + RTMemFree(pThis); + } + } + + return rc; +} diff --git a/src/VBox/Runtime/r3/init-data.cpp b/src/VBox/Runtime/r3/init-data.cpp new file mode 100644 index 00000000..8cc2cb09 --- /dev/null +++ b/src/VBox/Runtime/r3/init-data.cpp @@ -0,0 +1,63 @@ +/* $Id: init-data.cpp $ */ +/** @file + * IPRT - Init Data Ring-3. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include "internal/initterm.h" +#include "internal/time.h" /* g_u64ProgramStartNanoTS */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The number of calls to RTR3Init*. */ +DECL_HIDDEN_DATA(int32_t volatile) g_crtR3Users = 0; +/** Whether we're currently initializing the IPRT. */ +DECL_HIDDEN_DATA(bool volatile) g_frtR3Initializing = false; +/** + * Set if the atexit callback has been called, i.e. indicating + * that the process is terminating. + */ +DECL_HIDDEN_DATA(bool volatile) g_frtAtExitCalled = false; + +/** + * Program start nanosecond TS. + */ +DECL_HIDDEN_DATA(uint64_t) g_u64ProgramStartNanoTS = 0; + diff --git a/src/VBox/Runtime/r3/init.cpp b/src/VBox/Runtime/r3/init.cpp new file mode 100644 index 00000000..e10ba6b4 --- /dev/null +++ b/src/VBox/Runtime/r3/init.cpp @@ -0,0 +1,666 @@ +/* $Id: init.cpp $ */ +/** @file + * IPRT - Init Ring-3. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/types.h> /* darwin: UINT32_C and others. */ + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#else +# include <unistd.h> +# ifndef RT_OS_OS2 +# include <pthread.h> +# include <signal.h> +# include <errno.h> +# define IPRT_USE_SIG_CHILD_DUMMY +# endif +#endif +#ifdef RT_OS_OS2 +# include <InnoTekLIBC/fork.h> +# define INCL_DOSMISC +# include <os2.h> +#endif +#ifndef IPRT_NO_CRT +# include <locale.h> +#endif + +#include <iprt/initterm.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/time.h> +#include <iprt/string.h> +#include <iprt/param.h> +#ifdef RT_OS_WINDOWS +# include <iprt/getopt.h> +# include <iprt/utf16.h> +#endif +#if !defined(IN_GUEST) && !defined(RT_NO_GIP) +# include <iprt/file.h> +# include <VBox/sup.h> +#endif +#include <stdlib.h> + +#include "init.h" +#include "internal/alignmentchecks.h" +#include "internal/initterm.h" +#include "internal/path.h" +#include "internal/process.h" +#include "internal/thread.h" +#include "internal/time.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The IPRT init flags. */ +static uint32_t g_fInitFlags; + +/** The argument count of the program. */ +static int g_crtArgs = -1; +/** The arguments of the program (UTF-8). This is "leaked". */ +static char ** g_papszrtArgs; +/** The original argument vector of the program. */ +static char ** g_papszrtOrgArgs; + +#ifdef IPRT_WITH_ALIGNMENT_CHECKS +/** + * Whether alignment checks are enabled. + * This is set if the environment variable IPRT_ALIGNMENT_CHECKS is 1. + */ +RTDATADECL(bool) g_fRTAlignmentChecks = false; +#endif + + +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_HAIKU) \ + || defined(RT_OS_LINUX) || defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) /** @todo add host init hooks everywhere. */ +/* Stubs */ +DECLHIDDEN(int) rtR3InitNativeFirst(uint32_t fFlags) { RT_NOREF_PV(fFlags); return VINF_SUCCESS; } +DECLHIDDEN(int) rtR3InitNativeFinal(uint32_t fFlags) { RT_NOREF_PV(fFlags); return VINF_SUCCESS; } +DECLHIDDEN(void) rtR3InitNativeObtrusive(uint32_t fFlags) { RT_NOREF_PV(fFlags); } +#endif + + +/** + * atexit callback. + * + * This makes sure any loggers are flushed and will later also work the + * termination callback chain. + */ +static void rtR3ExitCallback(void) RT_NOTHROW_DEF +{ + ASMAtomicWriteBool(&g_frtAtExitCalled, true); + + if (g_crtR3Users > 0) + { + PRTLOGGER pLogger = RTLogGetDefaultInstance(); + if (pLogger) + RTLogFlush(pLogger); + + pLogger = RTLogRelGetDefaultInstance(); + if (pLogger) + RTLogFlush(pLogger); + } +} + + +#ifndef RT_OS_WINDOWS +/** + * Fork callback, child context. + */ +static void rtR3ForkChildCallback(void) +{ + g_ProcessSelf = getpid(); +} +#endif /* RT_OS_WINDOWS */ + +#ifdef RT_OS_OS2 +/** Fork completion callback for OS/2. Only called in the child. */ +static void rtR3ForkOs2ChildCompletionCallback(void *pvArg, int rc, __LIBC_FORKCTX enmCtx) +{ + Assert(enmCtx == __LIBC_FORK_CTX_CHILD); NOREF(enmCtx); + NOREF(pvArg); + + if (!rc) + rtR3ForkChildCallback(); +} + +/** Low-level fork callback for OS/2. */ +int rtR3ForkOs2Child(__LIBC_PFORKHANDLE pForkHandle, __LIBC_FORKOP enmOperation) +{ + if (enmOperation == __LIBC_FORK_OP_EXEC_CHILD) + return pForkHandle->pfnCompletionCallback(pForkHandle, rtR3ForkOs2ChildCompletionCallback, NULL, __LIBC_FORK_CTX_CHILD); + return 0; +} + +# define static static volatile /** @todo _FORK_CHILD1 causes unresolved externals in optimized builds. Fix macro. */ +_FORK_CHILD1(0, rtR3ForkOs2Child); +# undef static +#endif /* RT_OS_OS2 */ + + + +/** + * Internal worker which initializes or re-initializes the + * program path, name and directory globals. + * + * @returns IPRT status code. + * @param pszProgramPath The program path, NULL if not specified. + */ +static int rtR3InitProgramPath(const char *pszProgramPath) +{ + /* + * We're reserving 32 bytes here for file names as what not. + */ + if (!pszProgramPath) + { + int rc = rtProcInitExePath(g_szrtProcExePath, sizeof(g_szrtProcExePath) - 32); + if (RT_FAILURE(rc)) + return rc; + } + else + { + size_t cch = strlen(pszProgramPath); + Assert(cch > 1); + AssertMsgReturn(cch < sizeof(g_szrtProcExePath) - 32, ("%zu\n", cch), VERR_BUFFER_OVERFLOW); + memcpy(g_szrtProcExePath, pszProgramPath, cch + 1); + } + + /* + * Parse the name. + */ + ssize_t offName; + g_cchrtProcExePath = RTPathParseSimple(g_szrtProcExePath, &g_cchrtProcExeDir, &offName, NULL); + g_offrtProcName = offName; + return VINF_SUCCESS; +} + + +#ifdef RT_OS_WINDOWS +/** + * Checks the two argument vectors contains the same strings. + */ +DECLINLINE(bool) rtR3InitArgvEquals(int cArgs, char **papszArgs1, char **papszArgs2) +{ + if (papszArgs1 != papszArgs2) + while (cArgs-- > 0) + if (strcmp(papszArgs1[cArgs], papszArgs2[cArgs]) != 0) + return false; + return true; +} +#endif + + +/** + * Internal worker which initializes or re-initializes the + * program path, name and directory globals. + * + * @returns IPRT status code. + * @param fFlags Flags, see RTR3INIT_XXX. + * @param cArgs Pointer to the argument count. + * @param ppapszArgs Pointer to the argument vector pointer. NULL + * allowed if @a cArgs is 0. + */ +static int rtR3InitArgv(uint32_t fFlags, int cArgs, char ***ppapszArgs) +{ + NOREF(fFlags); + if (cArgs) + { + AssertPtr(ppapszArgs); + AssertPtr(*ppapszArgs); + char **papszOrgArgs = *ppapszArgs; + + /* + * Normally we should only be asked to convert arguments once. If we + * are though, it should be the already convered arguments. + */ + if (g_crtArgs != -1) + { + AssertReturn( g_crtArgs == cArgs + && g_papszrtArgs == papszOrgArgs, + VERR_WRONG_ORDER); /* only init once! */ + return VINF_SUCCESS; + } + +#if !defined(IPRT_NO_CRT) || !defined(RT_OS_WINDOWS) + if (!(fFlags & RTR3INIT_FLAGS_UTF8_ARGV)) + { + /* + * Convert the arguments. + */ + char **papszArgs; + +# ifdef RT_OS_WINDOWS + /* HACK ALERT! Try convert from unicode versions if possible. + Unfortunately for us, __wargv is only initialized if we have a unicode + main function. So, use getoptarv.cpp code to do the conversions and + hope it gives us the same result. (CommandLineToArgvW was not in NT 3.1.) */ + if ( cArgs == __argc + && rtR3InitArgvEquals(cArgs, papszOrgArgs, __argv)) + { + char *pszCmdLine = NULL; + int rc = RTUtf16ToUtf8Tag(GetCommandLineW(), &pszCmdLine, "will-leak:rtR3InitArgv"); + AssertRCReturn(rc, rc); + + int cArgsFromCmdLine = -1; + rc = RTGetOptArgvFromString(&papszArgs, &cArgsFromCmdLine, pszCmdLine, + RTGETOPTARGV_CNV_QUOTE_MS_CRT | RTGETOPTARGV_CNV_MODIFY_INPUT, NULL); + AssertMsgRCReturn(rc, ("pszCmdLine='%s' rc=%Rrc\n", pszCmdLine, rc), rc); + AssertMsg(cArgsFromCmdLine == cArgs, + ("cArgsFromCmdLine=%d cArgs=%d pszCmdLine='%s' rc=%Rrc\n", cArgsFromCmdLine, cArgs, pszCmdLine)); + } + else +# endif + { + papszArgs = (char **)RTMemAllocZTag((cArgs + 1) * sizeof(char *), "will-leak:rtR3InitArgv"); + if (!papszArgs) + return VERR_NO_MEMORY; + + for (int i = 0; i < cArgs; i++) + { + int rc = RTStrCurrentCPToUtf8(&papszArgs[i], papszOrgArgs[i]); + if (RT_FAILURE(rc)) + { + while (i--) + RTStrFree(papszArgs[i]); + RTMemFree(papszArgs); + return rc; + } + } + } + + papszArgs[cArgs] = NULL; + + g_papszrtOrgArgs = papszOrgArgs; + g_papszrtArgs = papszArgs; + g_crtArgs = cArgs; + + *ppapszArgs = papszArgs; + } + else +#endif /* !IPRT_NO_CRT || !RT_OS_WINDOWS */ + { + /* + * The arguments are already UTF-8, no conversion needed. + */ + g_papszrtOrgArgs = papszOrgArgs; + g_papszrtArgs = papszOrgArgs; + g_crtArgs = cArgs; + } + } + + return VINF_SUCCESS; +} + + +#ifdef IPRT_USE_SIG_CHILD_DUMMY +/** + * Dummy SIGCHILD handler. + * + * Assigned on rtR3Init only when SIGCHILD handler is set SIGIGN or SIGDEF to + * ensure waitpid works properly for the terminated processes. + */ +static void rtR3SigChildHandler(int iSignal) +{ + NOREF(iSignal); +} +#endif /* IPRT_USE_SIG_CHILD_DUMMY */ + + +/** + * rtR3Init worker. + */ +static int rtR3InitBody(uint32_t fFlags, int cArgs, char ***ppapszArgs, const char *pszProgramPath) +{ + /* + * Early native initialization. + */ + int rc = rtR3InitNativeFirst(fFlags); + AssertMsgRCReturn(rc, ("rtR3InitNativeFirst failed with %Rrc\n", rc), rc); + + /* + * Disable error popups. + */ +#if defined(RT_OS_OS2) /** @todo move to private code. */ + DosError(FERR_DISABLEHARDERR); +#endif + +#ifndef IPRT_NO_CRT + /* + * Init C runtime locale before we do anything that may end up converting + * paths or we'll end up using the "C" locale for path conversion. + */ + setlocale(LC_CTYPE, ""); +#endif + + /* + * The Process ID. + */ +#ifdef _MSC_VER + g_ProcessSelf = GetCurrentProcessId(); /* since NT 3.1, not 3.51+ as listed on geoffchappell.com */ +#else + g_ProcessSelf = getpid(); +#endif + + /* + * Save the init flags. + */ + g_fInitFlags |= fFlags; + +#if !defined(IN_GUEST) && !defined(RT_NO_GIP) +# ifdef VBOX + /* + * This MUST be done as the very first thing, before any file is opened. + * The log is opened on demand, but the first log entries may be caused + * by rtThreadInit() below. + */ + const char *pszDisableHostCache = getenv("VBOX_DISABLE_HOST_DISK_CACHE"); + if ( pszDisableHostCache != NULL + && *pszDisableHostCache + && strcmp(pszDisableHostCache, "0") != 0) + { + RTFileSetForceFlags(RTFILE_O_WRITE, RTFILE_O_WRITE_THROUGH, 0); + RTFileSetForceFlags(RTFILE_O_READWRITE, RTFILE_O_WRITE_THROUGH, 0); + } +# endif /* VBOX */ +#endif /* !IN_GUEST && !RT_NO_GIP */ + + /* + * Thread Thread database and adopt the caller thread as 'main'. + * This must be done before everything else or else we'll call into threading + * without having initialized TLS entries and suchlike. + */ + rc = rtThreadInit(); + AssertMsgRCReturn(rc, ("Failed to initialize threads, rc=%Rrc!\n", rc), rc); + + /* + * The executable path before SUPLib (windows requirement). + */ + rc = rtR3InitProgramPath(pszProgramPath); + AssertLogRelMsgRCReturn(rc, ("Failed to get executable directory path, rc=%Rrc!\n", rc), rc); + +#if !defined(IN_GUEST) && !defined(RT_NO_GIP) + /* + * Initialize SUPLib here so the GIP can get going as early as possible + * (improves accuracy for the first client). + */ + if (fFlags & (RTR3INIT_FLAGS_SUPLIB | RTR3INIT_FLAGS_TRY_SUPLIB)) + { + if (!(fFlags & ((SUPR3INIT_F_UNRESTRICTED | SUPR3INIT_F_LIMITED) << RTR3INIT_FLAGS_SUPLIB_SHIFT))) + g_fInitFlags |= fFlags |= SUPR3INIT_F_UNRESTRICTED << RTR3INIT_FLAGS_SUPLIB_SHIFT; + rc = SUPR3InitEx(fFlags >> RTR3INIT_FLAGS_SUPLIB_SHIFT, NULL /*ppSession*/); + AssertMsgReturn(RT_SUCCESS(rc) || (fFlags & RTR3INIT_FLAGS_TRY_SUPLIB), + ("Failed to initialize the support library, rc=%Rrc!\n", rc), rc); + } +#endif + + /* + * Convert arguments. + */ + rc = rtR3InitArgv(fFlags, cArgs, ppapszArgs); + AssertLogRelMsgRCReturn(rc, ("Failed to convert the arguments, rc=%Rrc!\n", rc), rc); + +#if !defined(IN_GUEST) && !defined(RT_NO_GIP) + /* + * The threading is initialized, so we can safely sleep a bit if GIP + * needs some time to start updating itself. Currently limited to + * the first mapping of GIP (u32TransactionId <= 4), quite possible we + * could just ditch this now. + */ + /** @todo consider dropping this... */ + PSUPGLOBALINFOPAGE pGip; + if ( (fFlags & (RTR3INIT_FLAGS_SUPLIB | RTR3INIT_FLAGS_TRY_SUPLIB)) + && (pGip = g_pSUPGlobalInfoPage) != NULL + && pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC) + { + PSUPGIPCPU pGipCpu = SUPGetGipCpuPtr(pGip); + if ( pGipCpu + && pGipCpu->u32TransactionId <= 4) + { + RTThreadSleep(pGip->u32UpdateIntervalNS / RT_NS_1MS + 2); + RTTimeNanoTS(); + } + } +#endif + + /* + * Init the program start timestamp TS. + * Do that here to be sure that the GIP time was properly updated the 1st time. + */ + g_u64ProgramStartNanoTS = RTTimeNanoTS(); + + /* + * The remainder cannot easily be undone, so it has to go last. + */ + + /* Fork and exit callbacks. */ +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) + rc = pthread_atfork(NULL, NULL, rtR3ForkChildCallback); + AssertMsg(rc == 0, ("%d\n", rc)); +#endif + atexit(rtR3ExitCallback); + +#ifdef IPRT_USE_SIG_CHILD_DUMMY + /* + * SIGCHLD must not be ignored (that's default), otherwise posix compliant waitpid + * implementations won't work right. + */ + for (;;) + { + struct sigaction saOld; + rc = sigaction(SIGCHLD, 0, &saOld); AssertMsg(rc == 0, ("%d/%d\n", rc, errno)); + if ( rc != 0 + || (saOld.sa_flags & SA_SIGINFO) + || ( saOld.sa_handler != SIG_IGN + && saOld.sa_handler != SIG_DFL) + ) + break; + + /* Try install dummy handler. */ + struct sigaction saNew = saOld; + saNew.sa_flags = SA_NOCLDSTOP | SA_RESTART; + saNew.sa_handler = rtR3SigChildHandler; + rc = sigemptyset(&saNew.sa_mask); AssertMsg(rc == 0, ("%d/%d\n", rc, errno)); + struct sigaction saOld2; + rc = sigaction(SIGCHLD, &saNew, &saOld2); AssertMsg(rc == 0, ("%d/%d\n", rc, errno)); + if ( rc != 0 + || ( saOld2.sa_handler == saOld.sa_handler + && !(saOld2.sa_flags & SA_SIGINFO)) + ) + break; + + /* Race during dynamic load, restore and try again... */ + sigaction(SIGCHLD, &saOld2, NULL); + RTThreadYield(); + } +#endif /* IPRT_USE_SIG_CHILD_DUMMY */ + +#ifdef IPRT_WITH_ALIGNMENT_CHECKS + /* + * Enable alignment checks. + */ + const char *pszAlignmentChecks = RTEnvGet("IPRT_ALIGNMENT_CHECKS"); /** @todo add RTEnvGetBool */ + g_fRTAlignmentChecks = pszAlignmentChecks != NULL + && pszAlignmentChecks[0] == '1' + && pszAlignmentChecks[1] == '\0'; + if (g_fRTAlignmentChecks) + IPRT_ALIGNMENT_CHECKS_ENABLE(); +#endif + + /* + * Final native initialization. + */ + rc = rtR3InitNativeFinal(fFlags); + AssertMsgRCReturn(rc, ("rtR3InitNativeFinal failed with %Rrc\n", rc), rc); + + return VINF_SUCCESS; +} + + +/** + * Internal initialization worker. + * + * @returns IPRT status code. + * @param fFlags Flags, see RTR3INIT_XXX. + * @param cArgs Pointer to the argument count. + * @param ppapszArgs Pointer to the argument vector pointer. NULL + * allowed if @a cArgs is 0. + * @param pszProgramPath The program path. Pass NULL if we're to figure it + * out ourselves. + */ +static int rtR3Init(uint32_t fFlags, int cArgs, char ***ppapszArgs, const char *pszProgramPath) +{ + /* no entry log flow, because prefixes and thread may freak out. */ + Assert(!(fFlags & ~RTR3INIT_FLAGS_VALID_MASK)); + Assert(!(fFlags & RTR3INIT_FLAGS_DLL) || cArgs == 0); + + /* + * Do reference counting, only initialize the first time around. + * + * We are ASSUMING that nobody will be able to race RTR3Init* calls when the + * first one, the real init, is running (second assertion). + */ + int32_t cUsers = ASMAtomicIncS32(&g_crtR3Users); + if (cUsers != 1) + { + AssertMsg(cUsers > 1, ("%d\n", cUsers)); + Assert(!g_frtR3Initializing); + +#if !defined(IN_GUEST) && !defined(RT_NO_GIP) + /* Initialize the support library if requested. We've always ignored the + status code here for some reason, making the two flags same. */ + if (fFlags & (RTR3INIT_FLAGS_SUPLIB | RTR3INIT_FLAGS_TRY_SUPLIB)) + { + if (!(fFlags & ((SUPR3INIT_F_UNRESTRICTED | SUPR3INIT_F_LIMITED) << RTR3INIT_FLAGS_SUPLIB_SHIFT))) + fFlags |= SUPR3INIT_F_UNRESTRICTED << RTR3INIT_FLAGS_SUPLIB_SHIFT; + SUPR3InitEx(fFlags >> RTR3INIT_FLAGS_SUPLIB_SHIFT, NULL /*ppSession*/); + g_fInitFlags |= fFlags & (RTR3INIT_FLAGS_SUPLIB | RTR3INIT_FLAGS_TRY_SUPLIB | RTR3INIT_FLAGS_SUPLIB_MASK); + } +#endif + g_fInitFlags |= fFlags & RTR3INIT_FLAGS_UTF8_ARGV; + + if ( !(fFlags & RTR3INIT_FLAGS_UNOBTRUSIVE) + && (g_fInitFlags & RTR3INIT_FLAGS_UNOBTRUSIVE)) + { + g_fInitFlags &= ~RTR3INIT_FLAGS_UNOBTRUSIVE; + g_fInitFlags |= fFlags & RTR3INIT_FLAGS_STANDALONE_APP; + rtR3InitNativeObtrusive(g_fInitFlags | fFlags); + rtThreadReInitObtrusive(); + } + else + Assert(!(fFlags & RTR3INIT_FLAGS_STANDALONE_APP) || (g_fInitFlags & RTR3INIT_FLAGS_STANDALONE_APP)); + + int rc = VINF_SUCCESS; + if (pszProgramPath) + rc = rtR3InitProgramPath(pszProgramPath); + if (RT_SUCCESS(rc)) + rc = rtR3InitArgv(fFlags, cArgs, ppapszArgs); + return rc; + } + + /* + * Do the initialization. + */ + ASMAtomicWriteBool(&g_frtR3Initializing, true); + int rc = rtR3InitBody(fFlags, cArgs, ppapszArgs, pszProgramPath); + ASMAtomicWriteBool(&g_frtR3Initializing, false); + if (RT_FAILURE(rc)) + { + /* failure */ + ASMAtomicDecS32(&g_crtR3Users); + return rc; + } + + /* success */ + LogFlow(("rtR3Init: returns VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTR3InitExe(int cArgs, char ***ppapszArgs, uint32_t fFlags) +{ + Assert(!(fFlags & RTR3INIT_FLAGS_DLL)); + return rtR3Init(fFlags, cArgs, ppapszArgs, NULL); +} + + +RTR3DECL(int) RTR3InitExeNoArguments(uint32_t fFlags) +{ + Assert(!(fFlags & RTR3INIT_FLAGS_DLL)); + return rtR3Init(fFlags, 0, NULL, NULL); +} + + +RTR3DECL(int) RTR3InitDll(uint32_t fFlags) +{ + Assert(!(fFlags & RTR3INIT_FLAGS_DLL)); + return rtR3Init(fFlags | RTR3INIT_FLAGS_DLL, 0, NULL, NULL); +} + + +RTR3DECL(int) RTR3InitEx(uint32_t iVersion, uint32_t fFlags, int cArgs, char ***ppapszArgs, const char *pszProgramPath) +{ + AssertReturn(iVersion == RTR3INIT_VER_CUR, VERR_NOT_SUPPORTED); + return rtR3Init(fFlags, cArgs, ppapszArgs, pszProgramPath); +} + + +RTR3DECL(bool) RTR3InitIsInitialized(void) +{ + return g_crtR3Users >= 1 && !g_frtR3Initializing; +} + + +RTR3DECL(bool) RTR3InitIsUnobtrusive(void) +{ + return RT_BOOL(g_fInitFlags & RTR3INIT_FLAGS_UNOBTRUSIVE); +} + + +#if 0 /** @todo implement RTR3Term. */ +RTR3DECL(void) RTR3Term(void) +{ +} +#endif + diff --git a/src/VBox/Runtime/r3/init.h b/src/VBox/Runtime/r3/init.h new file mode 100644 index 00000000..95d7e52c --- /dev/null +++ b/src/VBox/Runtime/r3/init.h @@ -0,0 +1,50 @@ +/* $Id: init.h $ */ +/** @file + * IPRT - Ring-3 initialization. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_init_h +#define IPRT_INCLUDED_SRC_r3_init_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> + +DECLHIDDEN(int) rtR3InitNativeFirst(uint32_t fFlags); +DECLHIDDEN(int) rtR3InitNativeFinal(uint32_t fFlags); +DECLHIDDEN(void) rtR3InitNativeObtrusive(uint32_t fFlags); + +#endif /* !IPRT_INCLUDED_SRC_r3_init_h */ + diff --git a/src/VBox/Runtime/r3/linux/Makefile.kup b/src/VBox/Runtime/r3/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/linux/Makefile.kup diff --git a/src/VBox/Runtime/r3/linux/RTFileCopyPartEx-linux.cpp b/src/VBox/Runtime/r3/linux/RTFileCopyPartEx-linux.cpp new file mode 100644 index 00000000..0b1d93d7 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTFileCopyPartEx-linux.cpp @@ -0,0 +1,196 @@ +/* $Id: RTFileCopyPartEx-linux.cpp $ */ +/** @file + * IPRT - RTFileCopyPartEx, linux specific implementation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/file.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> + +#include <errno.h> +#include <unistd.h> +#include <sys/syscall.h> + +#ifndef __NR_copy_file_range +# if defined(RT_ARCH_X86) +# define __NR_copy_file_range 377 +# elif defined(RT_ARCH_AMD64) +# define __NR_copy_file_range 326 +# endif +#endif + + +#ifndef __NR_copy_file_range +# include "../../generic/RTFileCopyPartEx-generic.cpp" +#else /* __NR_copy_file_range - whole file */ +/* Include the generic code as a fallback since copy_file_range is rather new . */ +# define IPRT_FALLBACK_VERSION +# include "../../generic/RTFileCopyPartEx-generic.cpp" +# undef IPRT_FALLBACK_VERSION + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static int32_t volatile g_fCopyFileRangeSupported = -1; + + +DECLINLINE(loff_t) +MyCopyFileRangeSysCall(int fdIn, loff_t *poffIn, int fdOut, loff_t *poffOut, size_t cbChunk, unsigned int fFlags) +{ + return syscall(__NR_copy_file_range, fdIn, poffIn, fdOut, poffOut, cbChunk, fFlags); +} + + +DECL_NO_INLINE(static, bool) HasCopyFileRangeSyscallSlow(void) +{ + errno = 0; + MyCopyFileRangeSysCall(-1, NULL, -1, NULL, 4096, 0); + if (errno != ENOSYS) + { + ASMAtomicWriteS32(&g_fCopyFileRangeSupported, 1); + return true; + } + ASMAtomicWriteS32(&g_fCopyFileRangeSupported, 0); + return false; +} + +DECLINLINE(bool) HasCopyFileRangeSyscall(void) +{ + int32_t i = ASMAtomicUoReadS32(&g_fCopyFileRangeSupported); + if (i != -1) + return i == 1; + return HasCopyFileRangeSyscallSlow(); +} + + + +RTDECL(int) RTFileCopyPartPrep(PRTFILECOPYPARTBUFSTATE pBufState, uint64_t cbToCopy) +{ + if (HasCopyFileRangeSyscall()) + { + pBufState->iAllocType = -42; + pBufState->pbBuf = NULL; + pBufState->cbBuf = 0; + pBufState->uMagic = RTFILECOPYPARTBUFSTATE_MAGIC; + return VINF_SUCCESS; + } + return rtFileCopyPartPrepFallback(pBufState, cbToCopy); +} + + +RTDECL(void) RTFileCopyPartCleanup(PRTFILECOPYPARTBUFSTATE pBufState) +{ + return rtFileCopyPartCleanupFallback(pBufState); +} + + +RTDECL(int) RTFileCopyPartEx(RTFILE hFileSrc, RTFOFF offSrc, RTFILE hFileDst, RTFOFF offDst, uint64_t cbToCopy, + uint32_t fFlags, PRTFILECOPYPARTBUFSTATE pBufState, uint64_t *pcbCopied) +{ + /* + * Validate input. + */ + if (pcbCopied) + *pcbCopied = 0; + AssertReturn(pBufState->uMagic == RTFILECOPYPARTBUFSTATE_MAGIC, VERR_INVALID_FLAGS); + if (pBufState->iAllocType == -42) + { /* more and more likely as time goes */ } + else + return rtFileCopyPartExFallback(hFileSrc, offSrc, hFileDst, offDst, cbToCopy, fFlags, pBufState, pcbCopied); + AssertReturn(offSrc >= 0, VERR_NEGATIVE_SEEK); + AssertReturn(offDst >= 0, VERR_NEGATIVE_SEEK); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * If nothing to copy, return right away. + */ + if (!cbToCopy) + return VINF_SUCCESS; + + /* + * Do the copying. + */ + uint64_t cbCopied = 0; + int rc = VINF_SUCCESS; + do + { + size_t cbThisCopy = (size_t)RT_MIN(cbToCopy - cbCopied, _1G); + loff_t offThisDst = offSrc + cbCopied; + loff_t offThisSrc = offDst + cbCopied; + ssize_t cbActual = MyCopyFileRangeSysCall((int)RTFileToNative(hFileSrc), &offThisSrc, + (int)RTFileToNative(hFileDst), &offThisDst, + cbThisCopy, 0); + if (cbActual < 0) + { + rc = errno; + Assert(rc != 0); + rc = rc != 0 ? RTErrConvertFromErrno(rc) : VERR_READ_ERROR; + if (rc != VERR_NOT_SAME_DEVICE || cbCopied != 0) + break; + + /* Fall back to generic implementation if the syscall refuses to handle the case. */ + rc = rtFileCopyPartPrepFallback(pBufState, cbToCopy); + if (RT_SUCCESS(rc)) + return rtFileCopyPartExFallback(hFileSrc, offSrc, hFileDst, offDst, cbToCopy, fFlags, pBufState, pcbCopied); + return rc; + } + Assert(offThisSrc == offSrc + (int64_t)cbCopied + cbActual); + Assert(offThisDst == offDst + (int64_t)cbCopied + cbActual); + + if (cbActual == 0) + { + if (!pcbCopied) + rc = VERR_EOF; + break; + } + + cbCopied += cbActual; + } while (cbCopied < cbToCopy); + + if (pcbCopied) + *pcbCopied = cbCopied; + + return rc; +} + +#endif /* __NR_copy_file_range */ + diff --git a/src/VBox/Runtime/r3/linux/RTFileQuerySectorSize-linux.cpp b/src/VBox/Runtime/r3/linux/RTFileQuerySectorSize-linux.cpp new file mode 100644 index 00000000..ffd615fb --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTFileQuerySectorSize-linux.cpp @@ -0,0 +1,88 @@ +/* $Id: RTFileQuerySectorSize-linux.cpp $ */ +/** @file + * IPRT - RTFileQuerySectorSize, Linux implementation. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#include <errno.h> +#include <linux/fs.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + + +RTDECL(int) RTFileQuerySectorSize(RTFILE hFile, uint32_t *pcbSector) +{ + AssertPtrReturn(pcbSector, VERR_INVALID_PARAMETER); + + int rc; + int const fd = (int)RTFileToNative(hFile); + struct stat DevStat = { 0 }; + if (!fstat(fd, &DevStat)) + { + if (S_ISBLK(DevStat.st_mode)) + { + int cbLogicalBlock = 0; + if (!ioctl(fd, BLKSSZGET, &cbLogicalBlock)) + { + AssertReturn(cbLogicalBlock > 0, VERR_INVALID_FUNCTION); + *pcbSector = cbLogicalBlock; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("ioctl failed: errno=%d / %Rrc\n", errno, rc)); + } + else + { + AssertMsgFailed(("not a block device.\n")); + rc = VERR_INVALID_FUNCTION; + } + } + else + { + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("fstat failed: errno=%d / %Rrc\n", errno, rc)); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/linux/RTFileSetAllocationSize-linux.cpp b/src/VBox/Runtime/r3/linux/RTFileSetAllocationSize-linux.cpp new file mode 100644 index 00000000..f3acd7fe --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTFileSetAllocationSize-linux.cpp @@ -0,0 +1,86 @@ +/* $Id: RTFileSetAllocationSize-linux.cpp $ */ +/** @file + * IPRT - RTFileSetAllocationSize, linux implementation. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include <iprt/file.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#include <dlfcn.h> +#include <errno.h> +#include <unistd.h> +#include <sys/fcntl.h> + +/** + * The Linux specific fallocate() method. + */ +typedef int (*PFNLNXFALLOCATE) (int iFd, int fMode, off_t offStart, off_t cb); +/** Flag to specify that the file size should not be extended. */ +#define LNX_FALLOC_FL_KEEP_SIZE 1 + +RTDECL(int) RTFileSetAllocationSize(RTFILE hFile, uint64_t cbSize, uint32_t fFlags) +{ + AssertReturn(hFile != NIL_RTFILE, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTFILE_ALLOC_SIZE_F_VALID), VERR_INVALID_PARAMETER); + AssertMsgReturn(sizeof(off_t) >= sizeof(cbSize) || RT_HIDWORD(cbSize) == 0, + ("64-bit filesize not supported! cbSize=%lld\n", cbSize), + VERR_NOT_SUPPORTED); + + int rc; + PFNLNXFALLOCATE pfnLnxFAllocate = (PFNLNXFALLOCATE)(uintptr_t)dlsym(RTLD_DEFAULT, "fallocate64"); + if (RT_VALID_PTR(pfnLnxFAllocate)) + { + int fLnxFlags = (fFlags & RTFILE_ALLOC_SIZE_F_KEEP_SIZE) ? LNX_FALLOC_FL_KEEP_SIZE : 0; + int rcLnx = pfnLnxFAllocate(RTFileToNative(hFile), fLnxFlags, 0, cbSize); + if (rcLnx == 0) + rc = VINF_SUCCESS; + else if (errno == EOPNOTSUPP) + rc = VERR_NOT_SUPPORTED; + else + rc = RTErrConvertFromErrno(errno); + } + else + rc = VERR_NOT_SUPPORTED; + + return rc; +} +RT_EXPORT_SYMBOL(RTFileSetAllocationSize); diff --git a/src/VBox/Runtime/r3/linux/RTProcIsRunningByName-linux.cpp b/src/VBox/Runtime/r3/linux/RTProcIsRunningByName-linux.cpp new file mode 100644 index 00000000..6049fa1a --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTProcIsRunningByName-linux.cpp @@ -0,0 +1,128 @@ +/* $Id: RTProcIsRunningByName-linux.cpp $ */ +/** @file + * IPRT - RTProcIsRunningByName, Linux implementation. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/param.h> +#include <iprt/assert.h> + +#include <unistd.h> + + +RTR3DECL(bool) RTProcIsRunningByName(const char *pszName) +{ + /* + * Quick validation. + */ + if (!pszName) + return false; + + bool const fWithPath = RTPathHavePath(pszName); + + /* + * Enumerate /proc. + */ + RTDIR hDir; + int rc = RTDirOpen(&hDir, "/proc"); + AssertMsgRCReturn(rc, ("RTDirOpen on /proc failed: rc=%Rrc\n", rc), false); + if (RT_SUCCESS(rc)) + { + RTDIRENTRY DirEntry; + while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL))) + { + /* + * Filter numeric directory entries only. + */ + if ( ( DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY + || DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN) + && RTStrToUInt32(DirEntry.szName) > 0) + { + /* + * Try readlink on exe first since it's more faster and reliable. + * Fall back on reading the first line in cmdline if that fails + * (access errors typically). cmdline is unreliable as it might + * contain whatever the execv caller passes as argv[0]. + */ + char szName[RTPATH_MAX]; + RTStrPrintf(szName, sizeof(szName), "/proc/%s/exe", &DirEntry.szName[0]); + char szExe[RTPATH_MAX]; + int cchLink = readlink(szName, szExe, sizeof(szExe) - 1); + if ( cchLink > 0 + && (size_t)cchLink < sizeof(szExe)) + { + szExe[cchLink] = '\0'; + rc = VINF_SUCCESS; + } + else + { + RTStrPrintf(szName, sizeof(szName), "/proc/%s/cmdline", &DirEntry.szName[0]); + PRTSTREAM pStream; + rc = RTStrmOpen(szName, "r", &pStream); + if (RT_SUCCESS(rc)) + { + rc = RTStrmGetLine(pStream, szExe, sizeof(szExe)); + RTStrmClose(pStream); + } + } + if (RT_SUCCESS(rc)) + { + /* + * We are interested on the file name part only. + */ + char const *pszProcName = fWithPath ? szExe : RTPathFilename(szExe); + if (RTStrCmp(pszProcName, pszName) == 0) + { + /* Found it! */ + RTDirClose(hDir); + return true; + } + } + } + } + RTDirClose(hDir); + } + + return false; +} + diff --git a/src/VBox/Runtime/r3/linux/RTSystemFirmware-linux.cpp b/src/VBox/Runtime/r3/linux/RTSystemFirmware-linux.cpp new file mode 100644 index 00000000..2d7b8986 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTSystemFirmware-linux.cpp @@ -0,0 +1,115 @@ +/* $Id: RTSystemFirmware-linux.cpp $ */ +/** @file + * IPRT - System firmware information, linux. + */ + +/* + * Copyright (C) 2019-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/system.h> + +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/string.h> +#include <iprt/linux/sysfs.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Defines the UEFI Globals UUID that is used here as filename suffix (case sensitive). */ +#define VBOX_UEFI_UUID_GLOBALS "8be4df61-93ca-11d2-aa0d-00e098032b8c" + + +RTDECL(int) RTSystemQueryFirmwareType(PRTSYSFWTYPE penmFirmwareType) +{ + if (RTLinuxSysFsExists("firmware/efi/")) + *penmFirmwareType = RTSYSFWTYPE_UEFI; + else if (RTLinuxSysFsExists("")) + *penmFirmwareType = RTSYSFWTYPE_BIOS; + else + { + *penmFirmwareType = RTSYSFWTYPE_INVALID; + return VERR_NOT_SUPPORTED; + } + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTSystemQueryFirmwareType); + + +RTDECL(int) RTSystemQueryFirmwareBoolean(RTSYSFWBOOL enmBoolean, bool *pfValue) +{ + *pfValue = false; + + /* + * Translate the property to variable base filename. + */ + const char *pszName; + switch (enmBoolean) + { + case RTSYSFWBOOL_SECURE_BOOT: + pszName = "firmware/efi/efivars/SecureBoot"; + break; + + default: + AssertReturn(enmBoolean > RTSYSFWBOOL_INVALID && enmBoolean < RTSYSFWBOOL_END, VERR_INVALID_PARAMETER); + return VERR_SYS_UNSUPPORTED_FIRMWARE_PROPERTY; + + } + + /* + * Try open and read the variable value. + */ + RTFILE hFile; + int rc = RTLinuxSysFsOpen(&hFile, "%s-" VBOX_UEFI_UUID_GLOBALS, pszName); + /** @todo try other suffixes if file-not-found. */ + if (RT_SUCCESS(rc)) + { + uint8_t abBuf[16]; + size_t cbRead = 0; + rc = RTLinuxSysFsReadFile(hFile, abBuf, sizeof(abBuf), &cbRead); + *pfValue = cbRead > 1 && abBuf[cbRead - 1] != 0; + RTFileClose(hFile); + } + else if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + rc = VINF_SUCCESS; + else if (rc == VERR_PERMISSION_DENIED) + rc = VERR_NOT_SUPPORTED; + + return rc; +} +RT_EXPORT_SYMBOL(RTSystemQueryFirmwareBoolean); + diff --git a/src/VBox/Runtime/r3/linux/RTSystemQueryDmiString-linux.cpp b/src/VBox/Runtime/r3/linux/RTSystemQueryDmiString-linux.cpp new file mode 100644 index 00000000..91cf6eb2 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTSystemQueryDmiString-linux.cpp @@ -0,0 +1,96 @@ +/* $Id: RTSystemQueryDmiString-linux.cpp $ */ +/** @file + * IPRT - RTSystemQueryDmiString, linux ring-3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/err.h> +#include <iprt/assert.h> +#include <iprt/linux/sysfs.h> + +#include <errno.h> + + +RTDECL(int) RTSystemQueryDmiString(RTSYSDMISTR enmString, char *pszBuf, size_t cbBuf) +{ + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER); + *pszBuf = '\0'; + AssertReturn(enmString > RTSYSDMISTR_INVALID && enmString < RTSYSDMISTR_END, VERR_INVALID_PARAMETER); + + const char *pszSysFsName; + switch (enmString) + { + case RTSYSDMISTR_PRODUCT_NAME: pszSysFsName = "id/product_name"; break; + case RTSYSDMISTR_PRODUCT_VERSION: pszSysFsName = "id/product_version"; break; + case RTSYSDMISTR_PRODUCT_UUID: pszSysFsName = "id/product_uuid"; break; + case RTSYSDMISTR_PRODUCT_SERIAL: pszSysFsName = "id/product_serial"; break; + case RTSYSDMISTR_MANUFACTURER: pszSysFsName = "id/sys_vendor"; break; + default: + return VERR_NOT_SUPPORTED; + } + + size_t cbRead = 0; + int rc = RTLinuxSysFsReadStrFile(pszBuf, cbBuf, &cbRead, "devices/virtual/dmi/%s", pszSysFsName); + if (RT_FAILURE(rc) && rc != VERR_BUFFER_OVERFLOW) + rc = RTLinuxSysFsReadStrFile(pszBuf, cbBuf, &cbRead, "class/dmi/%s", pszSysFsName); + if (RT_FAILURE(rc) && rc != VERR_BUFFER_OVERFLOW) + { + switch (rc) + { + case VINF_SUCCESS: + AssertFailed(); + break; + case VERR_FILE_NOT_FOUND: + case VERR_PATH_NOT_FOUND: + case VERR_IS_A_DIRECTORY: + rc = VERR_NOT_SUPPORTED; + break; + case VERR_PERMISSION_DENIED: + case VERR_ACCESS_DENIED: + rc = VERR_ACCESS_DENIED; + break; + } + } + + return rc; +} +RT_EXPORT_SYMBOL(RTSystemQueryDmiString); + diff --git a/src/VBox/Runtime/r3/linux/RTSystemShutdown-linux.cpp b/src/VBox/Runtime/r3/linux/RTSystemShutdown-linux.cpp new file mode 100644 index 00000000..fd198e30 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTSystemShutdown-linux.cpp @@ -0,0 +1,111 @@ +/* $Id: RTSystemShutdown-linux.cpp $ */ +/** @file + * IPRT - RTSystemShutdown, linux implementation. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/process.h> +#include <iprt/string.h> + + +RTDECL(int) RTSystemShutdown(RTMSINTERVAL cMsDelay, uint32_t fFlags, const char *pszLogMsg) +{ + AssertPtrReturn(pszLogMsg, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTSYSTEM_SHUTDOWN_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Assemble the argument vector. + */ + int iArg = 0; + const char *apszArgs[6]; + + RT_BZERO(apszArgs, sizeof(apszArgs)); + + apszArgs[iArg++] = "/sbin/shutdown"; + switch (fFlags & RTSYSTEM_SHUTDOWN_ACTION_MASK) + { + case RTSYSTEM_SHUTDOWN_HALT: + apszArgs[iArg++] = "-h"; + apszArgs[iArg++] = "-H"; + break; + case RTSYSTEM_SHUTDOWN_REBOOT: + apszArgs[iArg++] = "-r"; + break; + case RTSYSTEM_SHUTDOWN_POWER_OFF: + case RTSYSTEM_SHUTDOWN_POWER_OFF_HALT: + apszArgs[iArg++] = "-h"; + apszArgs[iArg++] = "-P"; + break; + } + + char szWhen[80]; + if (cMsDelay < 500) + strcpy(szWhen, "now"); + else + RTStrPrintf(szWhen, sizeof(szWhen), "%u", (unsigned)((cMsDelay + 499) / 1000)); + apszArgs[iArg++] = szWhen; + + apszArgs[iArg++] = pszLogMsg; + + + /* + * Start the shutdown process and wait for it to complete. + */ + RTPROCESS hProc; + int rc = RTProcCreate(apszArgs[0], apszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &hProc); + if (RT_FAILURE(rc)) + return rc; + + RTPROCSTATUS ProcStatus; + rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus); + if (RT_SUCCESS(rc)) + { + if ( ProcStatus.enmReason != RTPROCEXITREASON_NORMAL + || ProcStatus.iStatus != 0) + rc = VERR_SYS_SHUTDOWN_FAILED; + } + + return rc; +} +RT_EXPORT_SYMBOL(RTSystemShutdown); + diff --git a/src/VBox/Runtime/r3/linux/RTThreadGetNativeState-linux.cpp b/src/VBox/Runtime/r3/linux/RTThreadGetNativeState-linux.cpp new file mode 100644 index 00000000..26c0afdb --- /dev/null +++ b/src/VBox/Runtime/r3/linux/RTThreadGetNativeState-linux.cpp @@ -0,0 +1,121 @@ +/* $Id: RTThreadGetNativeState-linux.cpp $ */ +/** @file + * IPRT - RTThreadGetNativeState, linux implementation. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <iprt/thread.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include "internal/thread.h" + +#include <unistd.h> +#include <sys/fcntl.h> + + +RTDECL(RTTHREADNATIVESTATE) RTThreadGetNativeState(RTTHREAD hThread) +{ + RTTHREADNATIVESTATE enmRet = RTTHREADNATIVESTATE_INVALID; + PRTTHREADINT pThread = rtThreadGet(hThread); + if (pThread) + { + enmRet = RTTHREADNATIVESTATE_UNKNOWN; + + char szName[512]; + RTStrPrintf(szName, sizeof(szName), "/proc/self/task/%u/stat", pThread->tid); + int fd = open(szName, O_RDONLY, 0); + if (fd >= 0) + { + ssize_t cch = read(fd, szName, sizeof(szName) - 1); + close(fd); + if (cch > 0) + { + szName[cch] = '\0'; + + /* skip the pid, the (comm name) and stop at the status char. */ + const char *psz = szName; + while ( *psz + && ( *psz != ')' + || !RT_C_IS_SPACE(psz[1]) + || !RT_C_IS_ALPHA(psz[2]) + || !RT_C_IS_SPACE(psz[3]) + ) + ) + psz++; + if (*psz == ')') + { + switch (psz[2]) + { + case 'R': /* running */ + enmRet = RTTHREADNATIVESTATE_RUNNING; + break; + + case 'S': /* sleeping */ + case 'D': /* disk sleeping */ + enmRet = RTTHREADNATIVESTATE_BLOCKED; + break; + + case 'T': /* stopped or tracking stop */ + enmRet = RTTHREADNATIVESTATE_SUSPENDED; + break; + + case 'Z': /* zombie */ + case 'X': /* dead */ + enmRet = RTTHREADNATIVESTATE_TERMINATED; + break; + + default: + AssertMsgFailed(("state=%c\n", psz[2])); + enmRet = RTTHREADNATIVESTATE_UNKNOWN; + break; + } + } + else + AssertMsgFailed(("stat='%s'\n", szName)); + } + } + rtThreadRelease(pThread); + } + return enmRet; +} + diff --git a/src/VBox/Runtime/r3/linux/fileaio-linux.cpp b/src/VBox/Runtime/r3/linux/fileaio-linux.cpp new file mode 100644 index 00000000..2f365a45 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/fileaio-linux.cpp @@ -0,0 +1,847 @@ +/* $Id: fileaio-linux.cpp $ */ +/** @file + * IPRT - File async I/O, native implementation for the Linux host platform. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @page pg_rtfileaio_linux RTFile Async I/O - Linux Implementation Notes + * @internal + * + * Linux implements the kernel async I/O API through the io_* syscalls. They are + * not exposed in the glibc (the aio_* API uses userspace threads and blocking + * I/O operations to simulate async behavior). There is an external library + * called libaio which implements these syscalls but because we don't want to + * have another dependency and this library is not installed by default and the + * interface is really simple we use the kernel interface directly using wrapper + * functions. + * + * The interface has some limitations. The first one is that the file must be + * opened with O_DIRECT. This disables caching done by the kernel which can be + * compensated if the user of this API implements caching itself. The next + * limitation is that data buffers must be aligned at a 512 byte boundary or the + * request will fail. + */ +/** @todo r=bird: What's this about "must be opened with O_DIRECT"? An + * explanation would be nice, esp. seeing what Linus is quoted saying + * about it in the open man page... */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/thread.h> +#include "internal/fileaio.h" + +#include <unistd.h> +#include <sys/syscall.h> +#include <errno.h> + +#include <iprt/file.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The async I/O context handle */ +typedef unsigned long LNXKAIOCONTEXT; + +/** + * Supported commands for the iocbs + */ +enum +{ + LNXKAIO_IOCB_CMD_READ = 0, + LNXKAIO_IOCB_CMD_WRITE = 1, + LNXKAIO_IOCB_CMD_FSYNC = 2, + LNXKAIO_IOCB_CMD_FDSYNC = 3 +}; + +/** + * The iocb structure of a request which is passed to the kernel. + * + * We redefined this here because the version in the header lacks padding + * for 32bit. + */ +typedef struct LNXKAIOIOCB +{ + /** Opaque pointer to data which is returned on an I/O event. */ + void *pvUser; +#ifdef RT_ARCH_X86 + uint32_t u32Padding0; +#endif + /** Contains the request number and is set by the kernel. */ + uint32_t u32Key; + /** Reserved. */ + uint32_t u32Reserved0; + /** The I/O opcode. */ + uint16_t u16IoOpCode; + /** Request priority. */ + int16_t i16Priority; + /** The file descriptor. */ + uint32_t uFileDesc; + /** The userspace pointer to the buffer containing/receiving the data. */ + void *pvBuf; +#ifdef RT_ARCH_X86 + uint32_t u32Padding1; +#endif + /** How many bytes to transfer. */ +#if ARCH_BITS == 32 + uint32_t cbTransfer; + uint32_t u32Padding2; +#elif ARCH_BITS == 64 + uint64_t cbTransfer; +#else +# error "Unknown architecture" +#endif + /** At which offset to start the transfer. */ + int64_t off; + /** Reserved. */ + uint64_t u64Reserved1; + /** Flags */ + uint32_t fFlags; + /** Readyness signal file descriptor. */ + uint32_t u32ResFd; +} LNXKAIOIOCB, *PLNXKAIOIOCB; + +/** + * I/O event structure to notify about completed requests. + * Redefined here too because of the padding. + */ +typedef struct LNXKAIOIOEVENT +{ + /** The pvUser field from the iocb. */ + void *pvUser; +#if ARCH_BITS == 32 + uint32_t u32Padding0; +#endif + /** The LNXKAIOIOCB object this event is for. */ + PLNXKAIOIOCB *pIoCB; +#if ARCH_BITS == 32 + uint32_t u32Padding1; +#endif + /** The result code of the operation .*/ +#if ARCH_BITS == 32 + int32_t rc; + uint32_t u32Padding2; +#elif ARCH_BITS == 64 + int64_t rc; +#else +# error "Unknown architecture" +#endif + /** Secondary result code. */ +#if ARCH_BITS == 32 + int32_t rc2; + uint32_t u32Padding3; +#elif ARCH_BITS == 64 + int64_t rc2; +#else +# error "Unknown architecture" +#endif +} LNXKAIOIOEVENT, *PLNXKAIOIOEVENT; + + +/** + * Async I/O completion context state. + */ +typedef struct RTFILEAIOCTXINTERNAL +{ + /** Handle to the async I/O context. */ + LNXKAIOCONTEXT AioContext; + /** Maximum number of requests this context can handle. */ + int cRequestsMax; + /** Current number of requests active on this context. */ + volatile int32_t cRequests; + /** The ID of the thread which is currently waiting for requests. */ + volatile RTTHREAD hThreadWait; + /** Flag whether the thread was woken up. */ + volatile bool fWokenUp; + /** Flag whether the thread is currently waiting in the syscall. */ + volatile bool fWaiting; + /** Flags given during creation. */ + uint32_t fFlags; + /** Magic value (RTFILEAIOCTX_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOCTXINTERNAL; +/** Pointer to an internal context structure. */ +typedef RTFILEAIOCTXINTERNAL *PRTFILEAIOCTXINTERNAL; + +/** + * Async I/O request state. + */ +typedef struct RTFILEAIOREQINTERNAL +{ + /** The aio control block. This must be the FIRST elment in + * the structure! (see notes below) */ + LNXKAIOIOCB AioCB; + /** Current state the request is in. */ + RTFILEAIOREQSTATE enmState; + /** The I/O context this request is associated with. */ + LNXKAIOCONTEXT AioContext; + /** Return code the request completed with. */ + int Rc; + /** Number of bytes actually transferred. */ + size_t cbTransfered; + /** Completion context we are assigned to. */ + PRTFILEAIOCTXINTERNAL pCtxInt; + /** Magic value (RTFILEAIOREQ_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOREQINTERNAL; +/** Pointer to an internal request structure. */ +typedef RTFILEAIOREQINTERNAL *PRTFILEAIOREQINTERNAL; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max number of events to get in one call. */ +#define AIO_MAXIMUM_REQUESTS_PER_CONTEXT 64 + + +/** + * Creates a new async I/O context. + */ +DECLINLINE(int) rtFileAsyncIoLinuxCreate(unsigned cEvents, LNXKAIOCONTEXT *pAioContext) +{ + int rc = syscall(__NR_io_setup, cEvents, pAioContext); + if (RT_UNLIKELY(rc == -1)) + { + if (errno == EAGAIN) + return VERR_FILE_AIO_INSUFFICIENT_EVENTS; + else + return RTErrConvertFromErrno(errno); + } + + return VINF_SUCCESS; +} + +/** + * Destroys a async I/O context. + */ +DECLINLINE(int) rtFileAsyncIoLinuxDestroy(LNXKAIOCONTEXT AioContext) +{ + int rc = syscall(__NR_io_destroy, AioContext); + if (RT_UNLIKELY(rc == -1)) + return RTErrConvertFromErrno(errno); + + return VINF_SUCCESS; +} + +/** + * Submits an array of I/O requests to the kernel. + */ +DECLINLINE(int) rtFileAsyncIoLinuxSubmit(LNXKAIOCONTEXT AioContext, long cReqs, LNXKAIOIOCB **ppIoCB, int *pcSubmitted) +{ + int rc = syscall(__NR_io_submit, AioContext, cReqs, ppIoCB); + if (RT_UNLIKELY(rc == -1)) + return RTErrConvertFromErrno(errno); + + *pcSubmitted = rc; + + return VINF_SUCCESS; +} + +/** + * Cancels a I/O request. + */ +DECLINLINE(int) rtFileAsyncIoLinuxCancel(LNXKAIOCONTEXT AioContext, PLNXKAIOIOCB pIoCB, PLNXKAIOIOEVENT pIoResult) +{ + int rc = syscall(__NR_io_cancel, AioContext, pIoCB, pIoResult); + if (RT_UNLIKELY(rc == -1)) + return RTErrConvertFromErrno(errno); + + return VINF_SUCCESS; +} + +/** + * Waits for I/O events. + * @returns Number of events (natural number w/ 0), IPRT error code (negative). + */ +DECLINLINE(int) rtFileAsyncIoLinuxGetEvents(LNXKAIOCONTEXT AioContext, long cReqsMin, long cReqs, + PLNXKAIOIOEVENT paIoResults, struct timespec *pTimeout) +{ + int rc = syscall(__NR_io_getevents, AioContext, cReqsMin, cReqs, paIoResults, pTimeout); + if (RT_UNLIKELY(rc == -1)) + return RTErrConvertFromErrno(errno); + + return rc; +} + +RTR3DECL(int) RTFileAioGetLimits(PRTFILEAIOLIMITS pAioLimits) +{ + int rc = VINF_SUCCESS; + AssertPtrReturn(pAioLimits, VERR_INVALID_POINTER); + + /* + * Check if the API is implemented by creating a + * completion port. + */ + LNXKAIOCONTEXT AioContext = 0; + rc = rtFileAsyncIoLinuxCreate(1, &AioContext); + if (RT_FAILURE(rc)) + return rc; + + rc = rtFileAsyncIoLinuxDestroy(AioContext); + if (RT_FAILURE(rc)) + return rc; + + /* Supported - fill in the limits. The alignment is the only restriction. */ + pAioLimits->cReqsOutstandingMax = RTFILEAIO_UNLIMITED_REQS; + pAioLimits->cbBufferAlignment = 512; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTFileAioReqCreate(PRTFILEAIOREQ phReq) +{ + AssertPtrReturn(phReq, VERR_INVALID_POINTER); + + /* + * Allocate a new request and initialize it. + */ + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)RTMemAllocZ(sizeof(*pReqInt)); + if (RT_UNLIKELY(!pReqInt)) + return VERR_NO_MEMORY; + + pReqInt->pCtxInt = NULL; + pReqInt->u32Magic = RTFILEAIOREQ_MAGIC; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + *phReq = (RTFILEAIOREQ)pReqInt; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFileAioReqDestroy(RTFILEAIOREQ hReq) +{ + /* + * Validate the handle and ignore nil. + */ + if (hReq == NIL_RTFILEAIOREQ) + return VINF_SUCCESS; + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + + /* + * Trash the magic and free it. + */ + ASMAtomicUoWriteU32(&pReqInt->u32Magic, ~RTFILEAIOREQ_MAGIC); + RTMemFree(pReqInt); + return VINF_SUCCESS; +} + + +/** + * Worker setting up the request. + */ +DECLINLINE(int) rtFileAioReqPrepareTransfer(RTFILEAIOREQ hReq, RTFILE hFile, + uint16_t uTransferDirection, + RTFOFF off, void *pvBuf, size_t cbTransfer, + void *pvUser) +{ + /* + * Validate the input. + */ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + Assert(hFile != NIL_RTFILE); + + if (uTransferDirection != LNXKAIO_IOCB_CMD_FSYNC) + { + AssertPtr(pvBuf); + Assert(off >= 0); + Assert(cbTransfer > 0); + } + + /* + * Setup the control block and clear the finished flag. + */ + pReqInt->AioCB.u16IoOpCode = uTransferDirection; + pReqInt->AioCB.uFileDesc = RTFileToNative(hFile); + pReqInt->AioCB.off = off; + pReqInt->AioCB.cbTransfer = cbTransfer; + pReqInt->AioCB.pvBuf = pvBuf; + pReqInt->AioCB.pvUser = pvUser; + + pReqInt->pCtxInt = NULL; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTFileAioReqPrepareRead(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void *pvBuf, size_t cbRead, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LNXKAIO_IOCB_CMD_READ, + off, pvBuf, cbRead, pvUser); +} + + +RTDECL(int) RTFileAioReqPrepareWrite(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void const *pvBuf, size_t cbWrite, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LNXKAIO_IOCB_CMD_WRITE, + off, (void *)pvBuf, cbWrite, pvUser); +} + + +RTDECL(int) RTFileAioReqPrepareFlush(RTFILEAIOREQ hReq, RTFILE hFile, void *pvUser) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + AssertReturn(hFile != NIL_RTFILE, VERR_INVALID_HANDLE); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + + return rtFileAioReqPrepareTransfer(pReqInt, hFile, LNXKAIO_IOCB_CMD_FSYNC, + 0, NULL, 0, pvUser); +} + + +RTDECL(void *) RTFileAioReqGetUser(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN_RC(pReqInt, NULL); + + return pReqInt->AioCB.pvUser; +} + + +RTDECL(int) RTFileAioReqCancel(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_NOT_SUBMITTED); + + LNXKAIOIOEVENT AioEvent; + int rc = rtFileAsyncIoLinuxCancel(pReqInt->AioContext, &pReqInt->AioCB, &AioEvent); + if (RT_SUCCESS(rc)) + { + /* + * Decrement request count because the request will never arrive at the + * completion port. + */ + AssertMsg(RT_VALID_PTR(pReqInt->pCtxInt), ("Invalid state. Request was canceled but wasn't submitted\n")); + + ASMAtomicDecS32(&pReqInt->pCtxInt->cRequests); + pReqInt->Rc = VERR_FILE_AIO_CANCELED; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + return VINF_SUCCESS; + } + if (rc == VERR_TRY_AGAIN) + return VERR_FILE_AIO_IN_PROGRESS; + return rc; +} + + +RTDECL(int) RTFileAioReqGetRC(RTFILEAIOREQ hReq, size_t *pcbTransfered) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + AssertPtrNull(pcbTransfered); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, PREPARED, VERR_FILE_AIO_NOT_SUBMITTED); + + if ( pcbTransfered + && RT_SUCCESS(pReqInt->Rc)) + *pcbTransfered = pReqInt->cbTransfered; + + return pReqInt->Rc; +} + + +RTDECL(int) RTFileAioCtxCreate(PRTFILEAIOCTX phAioCtx, uint32_t cAioReqsMax, + uint32_t fFlags) +{ + PRTFILEAIOCTXINTERNAL pCtxInt; + AssertPtrReturn(phAioCtx, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTFILEAIOCTX_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + /* The kernel interface needs a maximum. */ + if (cAioReqsMax == RTFILEAIO_UNLIMITED_REQS) + return VERR_OUT_OF_RANGE; + + pCtxInt = (PRTFILEAIOCTXINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOCTXINTERNAL)); + if (RT_UNLIKELY(!pCtxInt)) + return VERR_NO_MEMORY; + + /* Init the event handle. */ + int rc = rtFileAsyncIoLinuxCreate(cAioReqsMax, &pCtxInt->AioContext); + if (RT_SUCCESS(rc)) + { + pCtxInt->fWokenUp = false; + pCtxInt->fWaiting = false; + pCtxInt->hThreadWait = NIL_RTTHREAD; + pCtxInt->cRequestsMax = cAioReqsMax; + pCtxInt->fFlags = fFlags; + pCtxInt->u32Magic = RTFILEAIOCTX_MAGIC; + *phAioCtx = (RTFILEAIOCTX)pCtxInt; + } + else + RTMemFree(pCtxInt); + + return rc; +} + + +RTDECL(int) RTFileAioCtxDestroy(RTFILEAIOCTX hAioCtx) +{ + /* Validate the handle and ignore nil. */ + if (hAioCtx == NIL_RTFILEAIOCTX) + return VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + /* Cannot destroy a busy context. */ + if (RT_UNLIKELY(pCtxInt->cRequests)) + return VERR_FILE_AIO_BUSY; + + /* The native bit first, then mark it as dead and free it. */ + int rc = rtFileAsyncIoLinuxDestroy(pCtxInt->AioContext); + if (RT_FAILURE(rc)) + return rc; + ASMAtomicUoWriteU32(&pCtxInt->u32Magic, RTFILEAIOCTX_MAGIC_DEAD); + RTMemFree(pCtxInt); + + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTFileAioCtxGetMaxReqCount(RTFILEAIOCTX hAioCtx) +{ + /* Nil means global here. */ + if (hAioCtx == NIL_RTFILEAIOCTX) + return RTFILEAIO_UNLIMITED_REQS; /** @todo r=bird: I'm a bit puzzled by this return value since it + * is completely useless in RTFileAioCtxCreate. */ + + /* Return 0 if the handle is invalid, it's better than garbage I think... */ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN_RC(pCtxInt, 0); + + return pCtxInt->cRequestsMax; +} + +RTDECL(int) RTFileAioCtxAssociateWithFile(RTFILEAIOCTX hAioCtx, RTFILE hFile) +{ + /* Nothing to do. */ + NOREF(hAioCtx); NOREF(hFile); + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioCtxSubmit(RTFILEAIOCTX hAioCtx, PRTFILEAIOREQ pahReqs, size_t cReqs) +{ + int rc = VINF_SUCCESS; + + /* + * Parameter validation. + */ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertReturn(cReqs > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + uint32_t i = cReqs; + PRTFILEAIOREQINTERNAL pReqInt = NULL; + + /* + * Validate requests and associate with the context. + */ + while (i-- > 0) + { + pReqInt = pahReqs[i]; + if (RTFILEAIOREQ_IS_NOT_VALID(pReqInt)) + { + /* Undo everything and stop submitting. */ + size_t iUndo = cReqs; + while (iUndo-- > i) + { + pReqInt = pahReqs[iUndo]; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + pReqInt->pCtxInt = NULL; + } + return VERR_INVALID_HANDLE; + } + + pReqInt->AioContext = pCtxInt->AioContext; + pReqInt->pCtxInt = pCtxInt; + RTFILEAIOREQ_SET_STATE(pReqInt, SUBMITTED); + } + + do + { + /* + * We cast pahReqs to the Linux iocb structure to avoid copying the requests + * into a temporary array. This is possible because the iocb structure is + * the first element in the request structure (see PRTFILEAIOCTXINTERNAL). + */ + int cReqsSubmitted = 0; + rc = rtFileAsyncIoLinuxSubmit(pCtxInt->AioContext, cReqs, + (PLNXKAIOIOCB *)pahReqs, + &cReqsSubmitted); + if (RT_FAILURE(rc)) + { + /* + * We encountered an error. + * This means that the first IoCB + * is not correctly initialized + * (invalid buffer alignment or bad file descriptor). + * Revert every request into the prepared state except + * the first one which will switch to completed. + * Another reason could be insufficient resources. + */ + i = cReqs; + while (i-- > 0) + { + /* Already validated. */ + pReqInt = pahReqs[i]; + pReqInt->pCtxInt = NULL; + pReqInt->AioContext = 0; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + } + + if (rc == VERR_TRY_AGAIN) + return VERR_FILE_AIO_INSUFFICIENT_RESSOURCES; + else + { + /* The first request failed. */ + pReqInt = pahReqs[0]; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + pReqInt->Rc = rc; + pReqInt->cbTransfered = 0; + return rc; + } + } + + /* Advance. */ + cReqs -= cReqsSubmitted; + pahReqs += cReqsSubmitted; + ASMAtomicAddS32(&pCtxInt->cRequests, cReqsSubmitted); + + } while (cReqs); + + return rc; +} + + +RTDECL(int) RTFileAioCtxWait(RTFILEAIOCTX hAioCtx, size_t cMinReqs, RTMSINTERVAL cMillies, + PRTFILEAIOREQ pahReqs, size_t cReqs, uint32_t *pcReqs) +{ + /* + * Validate the parameters, making sure to always set pcReqs. + */ + AssertPtrReturn(pcReqs, VERR_INVALID_POINTER); + *pcReqs = 0; /* always set */ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + AssertReturn(cReqs != 0, VERR_INVALID_PARAMETER); + AssertReturn(cReqs >= cMinReqs, VERR_OUT_OF_RANGE); + + /* + * Can't wait if there are not requests around. + */ + if ( RT_UNLIKELY(ASMAtomicUoReadS32(&pCtxInt->cRequests) == 0) + && !(pCtxInt->fFlags & RTFILEAIOCTX_FLAGS_WAIT_WITHOUT_PENDING_REQUESTS)) + return VERR_FILE_AIO_NO_REQUEST; + + /* + * Convert the timeout if specified. + */ + struct timespec *pTimeout = NULL; + struct timespec Timeout = {0,0}; + uint64_t StartNanoTS = 0; + if (cMillies != RT_INDEFINITE_WAIT) + { + Timeout.tv_sec = cMillies / 1000; + Timeout.tv_nsec = cMillies % 1000 * 1000000; + pTimeout = &Timeout; + StartNanoTS = RTTimeNanoTS(); + } + + /* Wait for at least one. */ + if (!cMinReqs) + cMinReqs = 1; + + /* For the wakeup call. */ + Assert(pCtxInt->hThreadWait == NIL_RTTHREAD); + ASMAtomicWriteHandle(&pCtxInt->hThreadWait, RTThreadSelf()); + + /* + * Loop until we're woken up, hit an error (incl timeout), or + * have collected the desired number of requests. + */ + int rc = VINF_SUCCESS; + int cRequestsCompleted = 0; + while (!pCtxInt->fWokenUp) + { + LNXKAIOIOEVENT aPortEvents[AIO_MAXIMUM_REQUESTS_PER_CONTEXT]; + int cRequestsToWait = RT_MIN(cReqs, AIO_MAXIMUM_REQUESTS_PER_CONTEXT); + ASMAtomicXchgBool(&pCtxInt->fWaiting, true); + rc = rtFileAsyncIoLinuxGetEvents(pCtxInt->AioContext, cMinReqs, cRequestsToWait, &aPortEvents[0], pTimeout); + ASMAtomicXchgBool(&pCtxInt->fWaiting, false); + if (RT_FAILURE(rc)) + break; + uint32_t const cDone = rc; + rc = VINF_SUCCESS; + + /* + * Process received events / requests. + */ + for (uint32_t i = 0; i < cDone; i++) + { + /* + * The iocb is the first element in our request structure. + * So we can safely cast it directly to the handle (see above) + */ + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)aPortEvents[i].pIoCB; + AssertPtr(pReqInt); + Assert(pReqInt->u32Magic == RTFILEAIOREQ_MAGIC); + + /** @todo aeichner: The rc field contains the result code + * like you can find in errno for the normal read/write ops. + * But there is a second field called rc2. I don't know the + * purpose for it yet. + */ + if (RT_UNLIKELY(aPortEvents[i].rc < 0)) + pReqInt->Rc = RTErrConvertFromErrno(-aPortEvents[i].rc); /* Convert to positive value. */ + else + { + pReqInt->Rc = VINF_SUCCESS; + pReqInt->cbTransfered = aPortEvents[i].rc; + } + + /* Mark the request as finished. */ + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + pahReqs[cRequestsCompleted++] = (RTFILEAIOREQ)pReqInt; + } + + /* + * Done Yet? If not advance and try again. + */ + if (cDone >= cMinReqs) + break; + cMinReqs -= cDone; + cReqs -= cDone; + + if (cMillies != RT_INDEFINITE_WAIT) + { + /* The API doesn't return ETIMEDOUT, so we have to fix that ourselves. */ + uint64_t NanoTS = RTTimeNanoTS(); + uint64_t cMilliesElapsed = (NanoTS - StartNanoTS) / 1000000; + if (cMilliesElapsed >= cMillies) + { + rc = VERR_TIMEOUT; + break; + } + + /* The syscall supposedly updates it, but we're paranoid. :-) */ + Timeout.tv_sec = (cMillies - (RTMSINTERVAL)cMilliesElapsed) / 1000; + Timeout.tv_nsec = (cMillies - (RTMSINTERVAL)cMilliesElapsed) % 1000 * 1000000; + } + } + + /* + * Update the context state and set the return value. + */ + *pcReqs = cRequestsCompleted; + ASMAtomicSubS32(&pCtxInt->cRequests, cRequestsCompleted); + Assert(pCtxInt->hThreadWait == RTThreadSelf()); + ASMAtomicWriteHandle(&pCtxInt->hThreadWait, NIL_RTTHREAD); + + /* + * Clear the wakeup flag and set rc. + */ + if ( pCtxInt->fWokenUp + && RT_SUCCESS(rc)) + { + ASMAtomicXchgBool(&pCtxInt->fWokenUp, false); + rc = VERR_INTERRUPTED; + } + + return rc; +} + + +RTDECL(int) RTFileAioCtxWakeup(RTFILEAIOCTX hAioCtx) +{ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + /** @todo r=bird: Define the protocol for how to resume work after calling + * this function. */ + + bool fWokenUp = ASMAtomicXchgBool(&pCtxInt->fWokenUp, true); + + /* + * Read the thread handle before the status flag. + * If we read the handle after the flag we might + * end up with an invalid handle because the thread + * waiting in RTFileAioCtxWakeup() might get scheduled + * before we read the flag and returns. + * We can ensure that the handle is valid if fWaiting is true + * when reading the handle before the status flag. + */ + RTTHREAD hThread; + ASMAtomicReadHandle(&pCtxInt->hThreadWait, &hThread); + bool fWaiting = ASMAtomicReadBool(&pCtxInt->fWaiting); + if ( !fWokenUp + && fWaiting) + { + /* + * If a thread waits the handle must be valid. + * It is possible that the thread returns from + * rtFileAsyncIoLinuxGetEvents() before the signal + * is send. + * This is no problem because we already set fWokenUp + * to true which will let the thread return VERR_INTERRUPTED + * and the next call to RTFileAioCtxWait() will not + * return VERR_INTERRUPTED because signals are not saved + * and will simply vanish if the destination thread can't + * receive it. + */ + Assert(hThread != NIL_RTTHREAD); + RTThreadPoke(hThread); + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/linux/ioqueue-iouringfile-provider.cpp b/src/VBox/Runtime/r3/linux/ioqueue-iouringfile-provider.cpp new file mode 100644 index 00000000..f6719664 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/ioqueue-iouringfile-provider.cpp @@ -0,0 +1,940 @@ +/* $Id: ioqueue-iouringfile-provider.cpp $ */ +/** @file + * IPRT - I/O queue, Linux io_uring interface I/O file provider. + */ + +/* + * Copyright (C) 2019-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @page pg_rtioqueue_linux RTIoQueue - Linux io_uring implementation notes + * @internal + * + * The io_uring interface is the most recent interface added to the Linux kernel + * to deliver fast and efficient I/O. It was first added with kernel version 5.1 and is + * thus not available on most systems as of writing this backend (July 2019). + * It supersedes the old async I/O interface and cleans up with some restrictions like + * having to disable caching for the file. + * The interface is centered around a submission and completion queue to queue multiple new + * requests for the kernel to process and get notified about completions to reduce the amount + * of context switches to an absolute minimum. It also offers advanced features like + * registering a fixed set of memory buffers for I/O upfront to reduce the processing overhead + * even more. + * + * The first implementation will only make use of the basic features and more advanced features + * will be added later. + * The adept developer probably noticed that the public IPRT I/O queue API resembles the io_uring + * interface in many aspects. This is not by accident but to reduce our own overhead as much as possible + * while still keeping a consistent platform independent API which allows efficient implementations on + * other hosts when they come up. + * + * The public kernel io_uring interface is completely defined in this file to avoid dragging in additional + * dependencies and to avoid compile problems on older hosts missing the interface just like it is done + * for the Linux RTFileAio* API The necessary interface definitions and descriptions where retrieved from: + * * http://kernel.dk/io_uring.pdf + * * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/io_uring.h + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_IOQUEUE +#include <iprt/ioqueue.h> + +#include <iprt/assertcompile.h> +#include <iprt/asm.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/uio.h> + +#include "internal/ioqueue.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** The syscall number of io_uring_setup(). */ +#define LNX_IOURING_SYSCALL_SETUP 425 +/** The syscall number of io_uring_enter(). */ +#define LNX_IOURING_SYSCALL_ENTER 426 +/** The syscall number of io_uring_register(). */ +#define LNX_IOURING_SYSCALL_REGISTER 427 +/** eventfd2() syscall not associated with io_uring but used for kicking waiters. */ +#define LNX_SYSCALL_EVENTFD2 290 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Linux io_uring completion event. + */ +typedef struct LNXIOURINGCQE +{ + /** Opaque user data associated with the completed request. */ + uint64_t u64User; + /** The status code of the request. */ + int32_t rcLnx; + /** Some flags which are not used as of now. */ + uint32_t fFlags; +} LNXIOURINGCQE; +AssertCompileSize(LNXIOURINGCQE, 16); +/** Pointer to a Linux io_uring completion event. */ +typedef LNXIOURINGCQE *PLNXIOURINGCQE; +/** Pointer to a constant linux io_uring completion event. */ +typedef const LNXIOURINGCQE *PCLNXIOURINGCQE; + + +/** + * Linux io_uring submission queue entry. + */ +typedef struct LNXIOURINGSQE +{ + /** The opcode for the request. */ + uint8_t u8Opc; + /** Common flags for the request. */ + uint8_t u8Flags; + /** Assigned I/O priority. */ + uint16_t u16IoPrio; + /** The file descriptor the request is for. */ + int32_t i32Fd; + /** The start offset into the file for the request. */ + uint64_t u64OffStart; + /** Buffer pointer or Pointer to io vector array depending on opcode. */ + uint64_t u64AddrBufIoVec; + /** Size of the buffer in bytes or number of io vectors. */ + uint32_t u32BufIoVecSz; + /** Opcode dependent data. */ + union + { + /** Flags for read/write requests. */ + uint32_t u32KrnlRwFlags; + /** Flags for fsync() like requests. */ + uint32_t u32FsyncFlags; + /** Flags for poll() like requests. */ + uint16_t u16PollFlags; + /** Flags for sync_file_range() like requests. */ + uint32_t u32SyncFileRangeFlags; + /** Flags for requests requiring a msg structure. */ + uint32_t u32MsgFlags; + } uOpc; + /** Opaque user data associated with the request and returned durign completion. */ + uint64_t u64User; + /** Request type dependent data. */ + union + { + /** Fixed buffer index if indicated by the request flags. */ + uint16_t u16FixedBufIdx; + /** Padding to align the structure to 64 bytes. */ + uint64_t au64Padding[3]; + } uReq; +} LNXIOURINGSQE; +AssertCompileSize(LNXIOURINGSQE, 64); +/** Pointer to a Linux io_uring submission queue entry. */ +typedef LNXIOURINGSQE *PLNXIOURINGSQE; +/** Pointer to a constant Linux io_uring submission queue entry. */ +typedef const LNXIOURINGSQE *PCLNXIOURINGSQE; + + +/** + * Linux u_ioring SQ ring header structure to maintain the queue. + */ +typedef struct LNXIOURINGSQ +{ + /** The current head position to fill in new requests. */ + uint32_t u32OffHead; + /** The current tail position the kernel starts processing from. */ + uint32_t u32OffTail; + /** The mask for the head and tail counters to apply to retrieve the index. */ + uint32_t u32OffRingMask; + /** Number of entries in the SQ ring. */ + uint32_t u32OffRingEntries; + /** Flags set asychronously by the kernel. */ + uint32_t u32OffFlags; + /** Counter of dropped requests. */ + uint32_t u32OffDroppedReqs; + /** Offset where to find the array of SQ entries. */ + uint32_t u32OffArray; + /** Reserved. */ + uint32_t u32Rsvd0; + /** Reserved. */ + uint64_t u64Rsvd1; +} LNXIOURINGSQ; +AssertCompileSize(LNXIOURINGSQ, 40); +/** Pointer to a Linux u_ioring SQ ring header. */ +typedef LNXIOURINGSQ *PLNXIOURINGSQ; +/** Pointer to a constant Linux u_ioring SQ ring header. */ +typedef const LNXIOURINGSQ *PCLNXIOURINGSQ; + + +/** + * Linux io_uring CQ ring header structure to maintain the queue. + */ +typedef struct LNXIOURINGCQ +{ + /** The current head position the kernel modifies when completion events happen. */ + uint32_t u32OffHead; + /** The current tail position to read completion events from. */ + uint32_t u32OffTail; + /** The mask for the head and tail counters to apply to retrieve the index. */ + uint32_t u32OffRingMask; + /** Number of entries in the CQ ring. */ + uint32_t u32OffRingEntries; + /** Number of CQ overflows happened. */ + uint32_t u32OffOverflowCnt; + /** */ + uint32_t u32OffCqes; + /** Reserved. */ + uint64_t au64Rsvd0[2]; +} LNXIOURINGCQ; +AssertCompileSize(LNXIOURINGCQ, 40); +/** Pointer to a Linux u_ioring CQ ring header. */ +typedef LNXIOURINGCQ *PLNXIOURINGCQ; +/** Pointer to a constant Linux u_ioring CQ ring header. */ +typedef const LNXIOURINGCQ *PCLNXIOURINGCQ; + + +/** + * Linux io_uring parameters passed to io_uring_setup(). + */ +typedef struct LNXIOURINGPARAMS +{ + /** Number of SQ entries requested, must be power of 2. */ + uint32_t u32SqEntriesCnt; + /** Number of CQ entries requested, must be power of 2. */ + uint32_t u32CqEntriesCnt; + /** Flags for the ring, , see LNX_IOURING_SETUP_F_*. */ + uint32_t u32Flags; + /** Affinity of the kernel side SQ polling thread if enabled. */ + uint32_t u32SqPollCpu; + /** Milliseconds after the kernel side SQ polling thread goes to sleep + * if there is are no requests to process. */ + uint32_t u32SqPollIdleMs; + /** Reserved. */ + uint32_t au32Rsvd0[5]; + /** Offsets returned for the submission queue. */ + LNXIOURINGSQ SqOffsets; + /** Offsets returned for the completion queue. */ + LNXIOURINGCQ CqOffsets; +} LNXIOURINGPARAMS; +/** Pointer to Linux io_uring parameters. */ +typedef LNXIOURINGPARAMS *PLNXIOURINGPARAMS; +/** Pointer to constant Linux io_uring parameters. */ +typedef const LNXIOURINGPARAMS *PCLNXIOURINGPARAMS; + + +/** @name LNXIOURINGSQE::u8Opc defined opcodes. + * @{ */ +/** Opcode to profile the interface, does nothing. */ +#define LNX_IOURING_OPC_NOP 0 +/** preadv() like request. */ +#define LNX_IOURING_OPC_READV 1 +/** pwritev() like request. */ +#define LNX_IOURING_OPC_WRITEV 2 +/** fsync() like request. */ +#define LNX_IOURING_OPC_FSYNC 3 +/** Read request using a fixed preset buffer. */ +#define LNX_IOURING_OPC_READ_FIXED 4 +/** Write request using a fixed preset buffer. */ +#define LNX_IOURING_OPC_WRITE_FIXED 5 +/** Add file descriptor to pollset. */ +#define LNX_IOURING_OPC_POLL_ADD 6 +/** Remove file descriptor from pollset. */ +#define LNX_IOURING_OPC_POLL_REMOVE 7 +/** sync_file_range() like request. */ +#define LNX_IOURING_OPC_SYNC_FILE_RANGE 8 +/** sendmsg() like request. */ +#define LNX_IOURING_OPC_SENDMSG 9 +/** recvmsg() like request. */ +#define LNX_IOURING_OPC_RECVMSG 10 +/** @} */ + + +/** @name Additional flags for LNX_IOURING_OPC_FSYNC requests. + * @{ */ +/** Sync userdata as well instead of metadata only. */ +#define LNX_IOURING_OPC_FSYNC_DATASYNC RT_BIT_32(0) +/** @} */ + + +/** @name Flags for the LNX_IOURING_SYSCALL_SETUP syscall. + * @{ */ +/** The I/O context is polled. */ +#define LNX_IOURING_SETUP_F_IOPOLL RT_BIT_32(0) +/** The kernel should poll the submission queue. */ +#define LNX_IOURING_SETUP_F_SQPOLL RT_BIT_32(1) +/** Sets the CPU affinity of the kernel thread polling the submission queue. */ +#define LNX_IOURING_SETUP_F_SQAFF RT_BIT_32(2) +/** @} */ + + +/** @name Flags for LNXIOURINGSQE::u8Flags. + * @{ */ +/** The file descriptor was registered before use. */ +#define LNX_IOURING_SQE_F_FIXED_FILE RT_BIT(0) +/** Complete all active requests before issuing the request with the flag set. */ +#define LNX_IOURING_SQE_F_IO_DRAIN RT_BIT(1) +/** Links the request with the flag set to the next one. */ +#define LNX_IOURING_SQE_F_IO_LINK RT_BIT(2) +/** @} */ + + +/** @name Magic mmap offsets to map submission and completion queues. + * @{ */ +/** Used to map the submission queue. */ +#define LNX_IOURING_MMAP_OFF_SQ UINT64_C(0) +/** Used to map the completion queue. */ +#define LNX_IOURING_MMAP_OFF_CQ UINT64_C(0x8000000) +/** Used to map the submission queue entries array. */ +#define LNX_IOURING_MMAP_OFF_SQES UINT64_C(0x10000000) +/** @} */ + + +/** @name Flags used for the SQ ring structure. + * @{ */ +/** The kernel thread needs a io_uring_enter() wakeup to continue processing requests. */ +#define LNX_IOURING_SQ_RING_F_NEED_WAKEUP RT_BIT_32(0) +/** @} */ + + +/** @name Flags for the LNX_IOURING_SYSCALL_ENTER syscall. + * @{ */ +/** Retrieve completion events for the completion queue. */ +#define LNX_IOURING_ENTER_F_GETEVENTS RT_BIT_32(0) +/** Wakes the suspended kernel thread processing the requests. */ +#define LNX_IOURING_ENTER_F_SQ_WAKEUP RT_BIT_32(1) +/** @} */ + + +/** @name Opcodes for the LNX_IOURING_SYSCALL_REGISTER syscall. + * @{ */ +/** Register a fixed set of buffers. */ +#define LNX_IOURING_REGISTER_OPC_BUFFERS_REGISTER 0 +/** Unregisters a fixed set of buffers registered previously. */ +#define LNX_IOURING_REGISTER_OPC_BUFFERS_UNREGISTER 1 +/** Register a fixed set of files. */ +#define LNX_IOURING_REGISTER_OPC_FILES_REGISTER 2 +/** Unregisters a fixed set of files registered previously. */ +#define LNX_IOURING_REGISTER_OPC_FILES_UNREGISTER 3 +/** Register an eventfd associated with the I/O ring. */ +#define LNX_IOURING_REGISTER_OPC_EVENTFD_REGISTER 4 +/** Unregisters an eventfd registered previously. */ +#define LNX_IOURING_REGISTER_OPC_EVENTFD_UNREGISTER 5 +/** @} */ + + +/** + * SQ ring structure. + * + * @note Some members of this structure point to memory shared with the kernel, + * hence the volatile keyword. + */ +typedef struct RTIOQUEUESQ +{ + /** Pointer to the head counter. */ + volatile uint32_t *pidxHead; + /** Pointer to the tail counter. */ + volatile uint32_t *pidxTail; + /** Mask to apply for the counters to get to the index. */ + uint32_t fRingMask; + /** Number of entries in the ring. */ + uint32_t cEntries; + /** Pointer to the global flags. */ + volatile uint32_t *pfFlags; + /** Pointer to the indirection array used for indexing the real SQ entries. */ + volatile uint32_t *paidxSqes; +} RTIOQUEUESQ; + + +/** + * CQ ring structure. + * + * @note Some members of this structure point to memory shared with the kernel, + * hence the volatile keyword. + */ +typedef struct RTIOQUEUECQ +{ + /** Pointer to the head counter. */ + volatile uint32_t *pidxHead; + /** Pointer to the tail counter. */ + volatile uint32_t *pidxTail; + /** Mask to apply for the counters to get to the index. */ + uint32_t fRingMask; + /** Number of entries in the ring. */ + uint32_t cEntries; + /** Pointer to the completion entry ring. */ + volatile LNXIOURINGCQE *paCqes; +} RTIOQUEUECQ; + + +/** + * Internal I/O queue provider instance data. + */ +typedef struct RTIOQUEUEPROVINT +{ + /** The io_uring file descriptor. */ + int iFdIoCtx; + /** The eventfd file descriptor registered with the ring. */ + int iFdEvt; + /** The submission queue. */ + RTIOQUEUESQ Sq; + /** The currently uncommitted tail for the SQ. */ + uint32_t idxSqTail; + /** Numbere of uncommitted SQEs. */ + uint32_t cSqesToCommit; + /** The completion queue. */ + RTIOQUEUECQ Cq; + /** Pointer to the mapped SQES entries. */ + PLNXIOURINGSQE paSqes; + /** Pointer to the iovec structure used for non S/G requests. */ + struct iovec *paIoVecs; + /** Pointer returned by mmap() for the SQ ring, used for unmapping. */ + void *pvMMapSqRing; + /** Pointer returned by mmap() for the CQ ring, used for unmapping. */ + void *pvMMapCqRing; + /** Pointer returned by mmap() for the SQ entries array, used for unmapping. */ + void *pvMMapSqes; + /** Size of the mapped SQ ring, used for unmapping. */ + size_t cbMMapSqRing; + /** Size of the mapped CQ ring, used for unmapping. */ + size_t cbMMapCqRing; + /** Size of the mapped SQ entries array, used for unmapping. */ + size_t cbMMapSqes; + /** Flag whether the waiter was woken up externally. */ + volatile bool fExtIntr; +} RTIOQUEUEPROVINT; +/** Pointer to the internal I/O queue provider instance data. */ +typedef RTIOQUEUEPROVINT *PRTIOQUEUEPROVINT; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Syscall wrapper for io_uring_setup(). + * + * @returns IPRT status code. + * @param cEntries Number of entries for submission and completion queues. + * @param pParams Additional parameters for the I/O ring and updated return values + * on success. + * @param piFdIoCtx Where to store the file descriptor of the I/O ring on success. + */ +DECLINLINE(int) rtIoQueueLnxIoURingSetup(uint32_t cEntries, PLNXIOURINGPARAMS pParams, int32_t *piFdIoCtx) +{ + int rcLnx = syscall(LNX_IOURING_SYSCALL_SETUP, cEntries, pParams); + if (RT_UNLIKELY(rcLnx == -1)) + return RTErrConvertFromErrno(errno); + + *piFdIoCtx = rcLnx; + return VINF_SUCCESS; +} + + +/** + * Syscall wrapper for io_uring_enter(). + * + * @returns IPRT status code. + * @param iFdIoCtx The I/O ring file descriptor. + * @param cToSubmit Maximum number of requests waiting for processing. + * @param cMinComplete Minimum number of completion events to accumulate before returning. + * @param fFlags Flags for io_uring_enter(), see LNX_IOURING_ENTER_F_*. + */ +DECLINLINE(int) rtIoQueueLnxIoURingEnter(int32_t iFdIoCtx, uint32_t cToSubmit, uint32_t cMinComplete, + uint32_t fFlags) +{ + int rcLnx = syscall(LNX_IOURING_SYSCALL_ENTER, iFdIoCtx, cToSubmit, cMinComplete, fFlags, + NULL, 0); + if (RT_UNLIKELY(rcLnx == -1)) + return RTErrConvertFromErrno(errno); + + return VINF_SUCCESS; +} + + +/** + * Syscall wrapper for io_uring_register(). + * + * @returns IPRT status code. + * @param iFdIoCtx The I/O ring file descriptor. + * @param uOpc Operation to perform, see LNX_IOURING_REGISTER_OPC_*. + * @param pvArg Opaque arguments. + * @param cArgs Number of arguments. + */ +DECLINLINE(int) rtIoQueueLnxIoURingRegister(int32_t iFdIoCtx, uint32_t uOpc, void *pvArg, + uint32_t cArgs) +{ + int rcLnx = syscall(LNX_IOURING_SYSCALL_REGISTER, iFdIoCtx, uOpc, pvArg, cArgs); + if (RT_UNLIKELY(rcLnx == -1)) + return RTErrConvertFromErrno(errno); + + return VINF_SUCCESS; +} + + +/** + * mmap() wrapper for the common bits and returning an IPRT status code. + * + * @returns IPRT status code. + * @param iFdIoCtx The I/O ring file descriptor. + * @param offMmap The mmap() offset. + * @param cbMmap How much to map. + * @param ppv Where to store the pointer to the mapping on success. + */ +DECLINLINE(int) rtIoQueueLnxIoURingMmap(int iFdIoCtx, off_t offMmap, size_t cbMmap, void **ppv) +{ + void *pv = mmap(0, cbMmap, PROT_READ | PROT_WRITE , MAP_SHARED | MAP_POPULATE, iFdIoCtx, offMmap); + if (pv != MAP_FAILED) + { + *ppv = pv; + return VINF_SUCCESS; + } + + return RTErrConvertFromErrno(errno); +} + + +/** + * eventfd2() syscall wrapper. + * + * @returns IPRT status code. + * @param uValInit The initial value of the maintained counter. + * @param fFlags Flags controlling the eventfd behavior. + * @param piFdEvt Where to store the file descriptor of the eventfd object on success. + */ +DECLINLINE(int) rtIoQueueLnxEventfd2(uint32_t uValInit, uint32_t fFlags, int *piFdEvt) +{ + int rcLnx = syscall(LNX_SYSCALL_EVENTFD2, uValInit, fFlags); + if (RT_UNLIKELY(rcLnx == -1)) + return RTErrConvertFromErrno(errno); + + *piFdEvt = rcLnx; + return VINF_SUCCESS; +} + + +/** + * Checks the completion event queue for pending events. + * + * @param pThis The provider instance. + * @param paCEvt Pointer to the array of completion events. + * @param cCEvt Maximum number of completion events the array can hold. + * @param pcCEvtSeen Where to store the number of completion events processed. + */ +static void rtIoQueueLnxIoURingFileProvCqCheck(PRTIOQUEUEPROVINT pThis, PRTIOQUEUECEVT paCEvt, + uint32_t cCEvt, uint32_t *pcCEvtSeen) +{ + /* The fencing and atomic accesses are kind of overkill and probably not required (dev paranoia). */ + ASMReadFence(); + uint32_t idxCqHead = ASMAtomicReadU32(pThis->Cq.pidxHead); + uint32_t idxCqTail = ASMAtomicReadU32(pThis->Cq.pidxTail); + ASMReadFence(); + + uint32_t cCEvtSeen = 0; + + while ( idxCqTail != idxCqHead + && cCEvtSeen < cCEvt) + { + /* Get the index. */ + uint32_t idxCqe = idxCqHead & pThis->Cq.fRingMask; + volatile LNXIOURINGCQE *pCqe = &pThis->Cq.paCqes[idxCqe]; + + paCEvt->pvUser = (void *)(uintptr_t)pCqe->u64User; + if (pCqe->rcLnx >= 0) + { + paCEvt->rcReq = VINF_SUCCESS; + paCEvt->cbXfered = (size_t)pCqe->rcLnx; + } + else + paCEvt->rcReq = RTErrConvertFromErrno(-pCqe->rcLnx); + +#ifdef RT_STRICT /* poison */ + memset((void *)pCqe, 0xff, sizeof(*pCqe)); +#endif + + paCEvt++; + cCEvtSeen++; + idxCqHead++; + } + + *pcCEvtSeen = cCEvtSeen; + + /* Paranoia strikes again. */ + ASMWriteFence(); + ASMAtomicWriteU32(pThis->Cq.pidxHead, idxCqHead); + ASMWriteFence(); +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnIsSupported} */ +static DECLCALLBACK(bool) rtIoQueueLnxIoURingFileProv_IsSupported(void) +{ + /* + * Try to create a simple I/O ring and close it again. + * The common code/public API already checked for the proper handle type. + */ + int iFdIoCtx = 0; + bool fSupp = false; + LNXIOURINGPARAMS Params; + RT_ZERO(Params); + + int rc = rtIoQueueLnxIoURingSetup(16, &Params, &iFdIoCtx); + if (RT_SUCCESS(rc)) + { + /* + * Check that we can register an eventfd descriptor to get notified about + * completion events while being able to kick the waiter externally out of the wait. + */ + int iFdEvt = 0; + rc = rtIoQueueLnxEventfd2(0 /*uValInit*/, 0 /*fFlags*/, &iFdEvt); + if (RT_SUCCESS(rc)) + { + rc = rtIoQueueLnxIoURingRegister(iFdIoCtx, LNX_IOURING_REGISTER_OPC_EVENTFD_REGISTER, + &iFdEvt, 1 /*cArgs*/); + if (RT_SUCCESS(rc)) + fSupp = true; + + int rcLnx = close(iFdEvt); Assert(!rcLnx); RT_NOREF(rcLnx); + } + int rcLnx = close(iFdIoCtx); Assert(!rcLnx); RT_NOREF(rcLnx); + } + + return fSupp; +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnQueueInit} */ +static DECLCALLBACK(int) rtIoQueueLnxIoURingFileProv_QueueInit(RTIOQUEUEPROV hIoQueueProv, uint32_t fFlags, + uint32_t cSqEntries, uint32_t cCqEntries) +{ + RT_NOREF(fFlags, cCqEntries); + + PRTIOQUEUEPROVINT pThis = hIoQueueProv; + LNXIOURINGPARAMS Params; + RT_ZERO(Params); + + pThis->cSqesToCommit = 0; + pThis->fExtIntr = false; + + int rc = rtIoQueueLnxIoURingSetup(cSqEntries, &Params, &pThis->iFdIoCtx); + if (RT_SUCCESS(rc)) + { + /* Map the rings into userspace. */ + pThis->cbMMapSqRing = Params.SqOffsets.u32OffArray + Params.u32SqEntriesCnt * sizeof(uint32_t); + pThis->cbMMapCqRing = Params.CqOffsets.u32OffCqes + Params.u32CqEntriesCnt * sizeof(LNXIOURINGCQE); + pThis->cbMMapSqes = Params.u32SqEntriesCnt * sizeof(LNXIOURINGSQE); + + pThis->paIoVecs = (struct iovec *)RTMemAllocZ(Params.u32SqEntriesCnt * sizeof(struct iovec)); + if (RT_LIKELY(pThis->paIoVecs)) + { + rc = rtIoQueueLnxEventfd2(0 /*uValInit*/, 0 /*fFlags*/, &pThis->iFdEvt); + if (RT_SUCCESS(rc)) + { + rc = rtIoQueueLnxIoURingRegister(pThis->iFdIoCtx, LNX_IOURING_REGISTER_OPC_EVENTFD_REGISTER, &pThis->iFdEvt, 1 /*cArgs*/); + if (RT_SUCCESS(rc)) + { + rc = rtIoQueueLnxIoURingMmap(pThis->iFdIoCtx, LNX_IOURING_MMAP_OFF_SQ, pThis->cbMMapSqRing, &pThis->pvMMapSqRing); + if (RT_SUCCESS(rc)) + { + rc = rtIoQueueLnxIoURingMmap(pThis->iFdIoCtx, LNX_IOURING_MMAP_OFF_CQ, pThis->cbMMapCqRing, &pThis->pvMMapCqRing); + if (RT_SUCCESS(rc)) + { + rc = rtIoQueueLnxIoURingMmap(pThis->iFdIoCtx, LNX_IOURING_MMAP_OFF_SQES, pThis->cbMMapSqes, &pThis->pvMMapSqes); + if (RT_SUCCESS(rc)) + { + uint8_t *pbTmp = (uint8_t *)pThis->pvMMapSqRing; + + pThis->Sq.pidxHead = (uint32_t *)(pbTmp + Params.SqOffsets.u32OffHead); + pThis->Sq.pidxTail = (uint32_t *)(pbTmp + Params.SqOffsets.u32OffTail); + pThis->Sq.fRingMask = *(uint32_t *)(pbTmp + Params.SqOffsets.u32OffRingMask); + pThis->Sq.cEntries = *(uint32_t *)(pbTmp + Params.SqOffsets.u32OffRingEntries); + pThis->Sq.pfFlags = (uint32_t *)(pbTmp + Params.SqOffsets.u32OffFlags); + pThis->Sq.paidxSqes = (uint32_t *)(pbTmp + Params.SqOffsets.u32OffArray); + pThis->idxSqTail = *pThis->Sq.pidxTail; + + pThis->paSqes = (PLNXIOURINGSQE)pThis->pvMMapSqes; + + pbTmp = (uint8_t *)pThis->pvMMapCqRing; + + pThis->Cq.pidxHead = (uint32_t *)(pbTmp + Params.CqOffsets.u32OffHead); + pThis->Cq.pidxTail = (uint32_t *)(pbTmp + Params.CqOffsets.u32OffTail); + pThis->Cq.fRingMask = *(uint32_t *)(pbTmp + Params.CqOffsets.u32OffRingMask); + pThis->Cq.cEntries = *(uint32_t *)(pbTmp + Params.CqOffsets.u32OffRingEntries); + pThis->Cq.paCqes = (PLNXIOURINGCQE)(pbTmp + Params.CqOffsets.u32OffCqes); + return VINF_SUCCESS; + } + + munmap(pThis->pvMMapCqRing, pThis->cbMMapCqRing); + } + + munmap(pThis->pvMMapSqRing, pThis->cbMMapSqRing); + } + + rc = rtIoQueueLnxIoURingRegister(pThis->iFdIoCtx, LNX_IOURING_REGISTER_OPC_EVENTFD_UNREGISTER, NULL, 0); + AssertRC(rc); + } + + close(pThis->iFdEvt); + } + + RTMemFree(pThis->paIoVecs); + } + + int rcLnx = close(pThis->iFdIoCtx); Assert(!rcLnx); RT_NOREF(rcLnx); + } + + return rc; +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnQueueDestroy} */ +static DECLCALLBACK(void) rtIoQueueLnxIoURingFileProv_QueueDestroy(RTIOQUEUEPROV hIoQueueProv) +{ + PRTIOQUEUEPROVINT pThis = hIoQueueProv; + + int rcLnx = munmap(pThis->pvMMapSqRing, pThis->cbMMapSqRing); Assert(!rcLnx); RT_NOREF(rcLnx); + rcLnx = munmap(pThis->pvMMapCqRing, pThis->cbMMapCqRing); Assert(!rcLnx); RT_NOREF(rcLnx); + rcLnx = munmap(pThis->pvMMapSqes, pThis->cbMMapSqes); Assert(!rcLnx); RT_NOREF(rcLnx); + + int rc = rtIoQueueLnxIoURingRegister(pThis->iFdIoCtx, LNX_IOURING_REGISTER_OPC_EVENTFD_UNREGISTER, NULL, 0); + AssertRC(rc); + + close(pThis->iFdEvt); + close(pThis->iFdIoCtx); + RTMemFree(pThis->paIoVecs); + + RT_ZERO(pThis); +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnHandleRegister} */ +static DECLCALLBACK(int) rtIoQueueLnxIoURingFileProv_HandleRegister(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle) +{ + RT_NOREF(hIoQueueProv, pHandle); + /** @todo Add support for fixed file sets later. */ + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnHandleDeregister} */ +static DECLCALLBACK(int) rtIoQueueLnxIoURingFileProv_HandleDeregister(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle) +{ + RT_NOREF(hIoQueueProv, pHandle); + /** @todo Add support for fixed file sets later. */ + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnReqPrepare} */ +static DECLCALLBACK(int) rtIoQueueLnxIoURingFileProv_ReqPrepare(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle, RTIOQUEUEOP enmOp, + uint64_t off, void *pvBuf, size_t cbBuf, uint32_t fReqFlags, + void *pvUser) +{ + PRTIOQUEUEPROVINT pThis = hIoQueueProv; + RT_NOREF(fReqFlags); + + uint32_t idx = pThis->idxSqTail & pThis->Sq.fRingMask; + PLNXIOURINGSQE pSqe = &pThis->paSqes[idx]; + struct iovec *pIoVec = &pThis->paIoVecs[idx]; + + pIoVec->iov_base = pvBuf; + pIoVec->iov_len = cbBuf; + + pSqe->u8Flags = 0; + pSqe->u16IoPrio = 0; + pSqe->i32Fd = (int32_t)RTFileToNative(pHandle->u.hFile); + pSqe->u64OffStart = off; + pSqe->u64AddrBufIoVec = (uint64_t)(uintptr_t)pIoVec; + pSqe->u32BufIoVecSz = 1; + pSqe->u64User = (uint64_t)(uintptr_t)pvUser; + + switch (enmOp) + { + case RTIOQUEUEOP_READ: + pSqe->u8Opc = LNX_IOURING_OPC_READV; + pSqe->uOpc.u32KrnlRwFlags = 0; + break; + case RTIOQUEUEOP_WRITE: + pSqe->u8Opc = LNX_IOURING_OPC_WRITEV; + pSqe->uOpc.u32KrnlRwFlags = 0; + break; + case RTIOQUEUEOP_SYNC: + pSqe->u8Opc = LNX_IOURING_OPC_FSYNC; + pSqe->uOpc.u32FsyncFlags = 0; + break; + default: + AssertMsgFailedReturn(("Invalid I/O queue operation: %d\n", enmOp), + VERR_INVALID_PARAMETER); + } + + pThis->Sq.paidxSqes[idx] = idx; + pThis->idxSqTail++; + pThis->cSqesToCommit++; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnCommit} */ +static DECLCALLBACK(int) rtIoQueueLnxIoURingFileProv_Commit(RTIOQUEUEPROV hIoQueueProv, uint32_t *pcReqsCommitted) +{ + PRTIOQUEUEPROVINT pThis = hIoQueueProv; + + ASMWriteFence(); + ASMAtomicWriteU32(pThis->Sq.pidxTail, pThis->idxSqTail); + ASMWriteFence(); + + int rc = rtIoQueueLnxIoURingEnter(pThis->iFdIoCtx, pThis->cSqesToCommit, 0, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + *pcReqsCommitted = pThis->cSqesToCommit; + pThis->cSqesToCommit = 0; + } + + return rc; +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnEvtWait} */ +static DECLCALLBACK(int) rtIoQueueLnxIoURingFileProv_EvtWait(RTIOQUEUEPROV hIoQueueProv, PRTIOQUEUECEVT paCEvt, uint32_t cCEvt, + uint32_t cMinWait, uint32_t *pcCEvt, uint32_t fFlags) +{ + PRTIOQUEUEPROVINT pThis = hIoQueueProv; + int rc = VINF_SUCCESS; + uint32_t cCEvtSeen = 0; + + RT_NOREF(fFlags); + + /* + * Check the completion queue first for any completed events which might save us a + * context switch later on. + */ + rtIoQueueLnxIoURingFileProvCqCheck(pThis, paCEvt, cCEvt, &cCEvtSeen); + + while ( cCEvtSeen < cMinWait + && RT_SUCCESS(rc)) + { + /* + * We can employ a blocking read on the event file descriptor, it will return + * either when woken up externally or when there are completion events pending. + */ + uint64_t uCnt = 0; /**< The counter value returned upon a successful read(). */ + ssize_t rcLnx = read(pThis->iFdEvt, &uCnt, sizeof(uCnt)); + if (rcLnx == sizeof(uCnt)) + { + uint32_t cCEvtThisSeen = 0; + rtIoQueueLnxIoURingFileProvCqCheck(pThis, &paCEvt[cCEvtSeen], cCEvt - cCEvtSeen, &cCEvtThisSeen); + cCEvtSeen += cCEvtThisSeen; + + /* Whether we got woken up externally. */ + if (ASMAtomicXchgBool(&pThis->fExtIntr, false)) + rc = VERR_INTERRUPTED; + } + else if (rcLnx == -1) + rc = RTErrConvertFromErrno(errno); + else + AssertMsgFailed(("Unexpected read() -> 0\n")); + } + + *pcCEvt = cCEvtSeen; + return rc; +} + + +/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnEvtWaitWakeup} */ +static DECLCALLBACK(int) rtIoQueueLnxIoURingFileProv_EvtWaitWakeup(RTIOQUEUEPROV hIoQueueProv) +{ + PRTIOQUEUEPROVINT pThis = hIoQueueProv; + int rc = VINF_SUCCESS; + + if (!ASMAtomicXchgBool(&pThis->fExtIntr, true)) + { + const uint64_t uValAdd = 1; + ssize_t rcLnx = write(pThis->iFdEvt, &uValAdd, sizeof(uValAdd)); + + Assert(rcLnx == -1 || rcLnx == sizeof(uValAdd)); + if (rcLnx == -1) + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + + +/** + * Async file I/O queue provider virtual method table. + */ +RT_DECL_DATA_CONST(RTIOQUEUEPROVVTABLE const) g_RTIoQueueLnxIoURingProv = +{ + /** uVersion */ + RTIOQUEUEPROVVTABLE_VERSION, + /** pszId */ + "LnxIoURingFile", + /** cbIoQueueProv */ + sizeof(RTIOQUEUEPROVINT), + /** enmHnd */ + RTHANDLETYPE_FILE, + /** fFlags */ + 0, + /** pfnIsSupported */ + rtIoQueueLnxIoURingFileProv_IsSupported, + /** pfnQueueInit */ + rtIoQueueLnxIoURingFileProv_QueueInit, + /** pfnQueueDestroy */ + rtIoQueueLnxIoURingFileProv_QueueDestroy, + /** pfnHandleRegister */ + rtIoQueueLnxIoURingFileProv_HandleRegister, + /** pfnHandleDeregister */ + rtIoQueueLnxIoURingFileProv_HandleDeregister, + /** pfnReqPrepare */ + rtIoQueueLnxIoURingFileProv_ReqPrepare, + /** pfnReqPrepareSg */ + NULL, + /** pfnCommit */ + rtIoQueueLnxIoURingFileProv_Commit, + /** pfnEvtWait */ + rtIoQueueLnxIoURingFileProv_EvtWait, + /** pfnEvtWaitWakeup */ + rtIoQueueLnxIoURingFileProv_EvtWaitWakeup, + /** uEndMarker */ + RTIOQUEUEPROVVTABLE_VERSION +}; + diff --git a/src/VBox/Runtime/r3/linux/krnlmod-linux.cpp b/src/VBox/Runtime/r3/linux/krnlmod-linux.cpp new file mode 100644 index 00000000..6d81d530 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/krnlmod-linux.cpp @@ -0,0 +1,358 @@ +/* $Id: krnlmod-linux.cpp $ */ +/** @file + * IPRT - Kernel module, Linux. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <iprt/krnlmod.h> +#include <iprt/linux/sysfs.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/types.h> + + +/** + * Internal kernel information record state. + */ +typedef struct RTKRNLMODINFOINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Reference count for the kernel module. */ + uint32_t cRefKrnlMod; + /** Load address of the kernel module. */ + RTR0UINTPTR uLoadAddr; + /** Size of the kernel module. */ + size_t cbKrnlMod; + /** Size of the name in characters including the zero terminator. */ + size_t cchName; + /** Module name - variable in size. */ + char achName[1]; +} RTKRNLMODINFOINT; +/** Pointer to the internal kernel module information record. */ +typedef RTKRNLMODINFOINT *PRTKRNLMODINFOINT; +/** Pointer to a const internal kernel module information record. */ +typedef const RTKRNLMODINFOINT *PCRTKRNLMODINFOINT; + + + +/** + * Destroy the given kernel module information record. + * + * @param pThis The record to destroy. + */ +static void rtKrnlModInfoDestroy(PRTKRNLMODINFOINT pThis) +{ + RTMemFree(pThis); +} + + +static int rtKrnlModLinuxReadIntFileDef(unsigned uBase, int64_t *pi64, int64_t i64Def, + const char *pszName, const char *pszPath) +{ + int rc = RTLinuxSysFsReadIntFile(uBase, pi64, "module/%s/%s", pszName, pszPath); + if (rc == VERR_FILE_NOT_FOUND) + { + *pi64 = i64Def; + rc = VINF_SUCCESS; + } + + return rc; +} + +/** + * Creates a new kernel module information record for the given module. + * + * @returns IPRT status code. + * @param pszName The kernel module name. + * @param phKrnlModInfo Where to store the handle to the kernel module information record + * on success. + */ +static int rtKrnlModLinuxInfoCreate(const char *pszName, PRTKRNLMODINFO phKrnlModInfo) +{ + int rc = VINF_SUCCESS; + size_t cchName = strlen(pszName) + 1; + PRTKRNLMODINFOINT pThis = (PRTKRNLMODINFOINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTKRNLMODINFOINT, achName[cchName])); + if (RT_LIKELY(pThis)) + { + memcpy(&pThis->achName[0], pszName, cchName); + pThis->cchName = cchName; + pThis->cRefs = 1; + + int64_t iTmp = 0; + rc = rtKrnlModLinuxReadIntFileDef(10, &iTmp, 0, pszName, "refcnt"); + if (RT_SUCCESS(rc)) + pThis->cRefKrnlMod = (uint32_t)iTmp; + + rc = rtKrnlModLinuxReadIntFileDef(10, &iTmp, 0, pszName, "coresize"); + if (RT_SUCCESS(rc)) + pThis->cbKrnlMod = iTmp; + + rc = rtKrnlModLinuxReadIntFileDef(16, &iTmp, 0, pszName, "sections/.text"); + if (RT_SUCCESS(rc)) + pThis->uLoadAddr = iTmp; + + if (RT_SUCCESS(rc)) + *phKrnlModInfo = pThis; + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTKrnlModQueryLoaded(const char *pszName, bool *pfLoaded) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pfLoaded, VERR_INVALID_POINTER); + + int rc = RTLinuxSysFsExists("module/%s", pszName); + if (rc == VINF_SUCCESS) + *pfLoaded = true; + else if (rc == VERR_FILE_NOT_FOUND) + { + *pfLoaded = false; + rc = VINF_SUCCESS; + } + + return rc; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfo(const char *pszName, PRTKRNLMODINFO phKrnlModInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(phKrnlModInfo, VERR_INVALID_POINTER); + + int rc = RTLinuxSysFsExists("module/%s", pszName); + if (rc == VINF_SUCCESS) + rc = rtKrnlModLinuxInfoCreate(pszName, phKrnlModInfo); + else if (rc == VERR_FILE_NOT_FOUND) + rc = VERR_NOT_FOUND; + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModLoadedGetCount(void) +{ + uint32_t cKmodsLoaded = 0; + + RTDIR hDir = NULL; + int rc = RTDirOpen(&hDir, "/sys/module"); + if (RT_SUCCESS(rc)) + { + RTDIRENTRY DirEnt; + rc = RTDirRead(hDir, &DirEnt, NULL); + while (RT_SUCCESS(rc)) + { + if (!RTDirEntryIsStdDotLink(&DirEnt)) + cKmodsLoaded++; + rc = RTDirRead(hDir, &DirEnt, NULL); + } + + RTDirClose(hDir); + } + + + return cKmodsLoaded; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfoAll(PRTKRNLMODINFO pahKrnlModInfo, uint32_t cEntriesMax, + uint32_t *pcEntries) +{ + if (cEntriesMax > 0) + AssertPtrReturn(pahKrnlModInfo, VERR_INVALID_POINTER); + + uint32_t cKmodsLoaded = RTKrnlModLoadedGetCount(); + if (cEntriesMax < cKmodsLoaded) + { + if (*pcEntries) + *pcEntries = cKmodsLoaded; + return VERR_BUFFER_OVERFLOW; + } + + RTDIR hDir = NULL; + int rc = RTDirOpen(&hDir, "/sys/module"); + if (RT_SUCCESS(rc)) + { + unsigned idxKrnlModInfo = 0; + RTDIRENTRY DirEnt; + + rc = RTDirRead(hDir, &DirEnt, NULL); + while (RT_SUCCESS(rc)) + { + if (!RTDirEntryIsStdDotLink(&DirEnt)) + { + rc = rtKrnlModLinuxInfoCreate(DirEnt.szName, &pahKrnlModInfo[idxKrnlModInfo]); + if (RT_SUCCESS(rc)) + idxKrnlModInfo++; + } + + if (RT_SUCCESS(rc)) + rc = RTDirRead(hDir, &DirEnt, NULL); + } + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + else if (RT_FAILURE(rc)) + { + /* Rollback */ + while (idxKrnlModInfo-- > 0) + RTKrnlModInfoRelease(pahKrnlModInfo[idxKrnlModInfo]); + } + + if (*pcEntries) + *pcEntries = cKmodsLoaded; + + RTDirClose(hDir); + } + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModInfoRetain(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoRelease(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + if (!pThis) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtKrnlModInfoDestroy(pThis); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoGetRefCnt(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->cRefKrnlMod; +} + + +RTDECL(const char *) RTKrnlModInfoGetName(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + return &pThis->achName[0]; +} + + +RTDECL(const char *) RTKrnlModInfoGetFilePath(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + return NULL; +} + + +RTDECL(size_t) RTKrnlModInfoGetSize(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->cbKrnlMod; +} + + +RTDECL(RTR0UINTPTR) RTKrnlModInfoGetLoadAddr(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->uLoadAddr; +} + + +RTDECL(int) RTKrnlModInfoQueryRefModInfo(RTKRNLMODINFO hKrnlModInfo, uint32_t idx, + PRTKRNLMODINFO phKrnlModInfoRef) +{ + RT_NOREF3(hKrnlModInfo, idx, phKrnlModInfoRef); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(int) RTKrnlModLoadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModLoadByPath(const char *pszPath) +{ + AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModUnloadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} diff --git a/src/VBox/Runtime/r3/linux/mp-linux.cpp b/src/VBox/Runtime/r3/linux/mp-linux.cpp new file mode 100644 index 00000000..935dfd22 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/mp-linux.cpp @@ -0,0 +1,328 @@ +/* $Id: mp-linux.cpp $ */ +/** @file + * IPRT - Multiprocessor, Linux. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <stdio.h> +#include <errno.h> + +#include <iprt/mp.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/cpuset.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/linux/sysfs.h> + + +/** + * Internal worker that determines the max possible CPU count. + * + * @returns Max cpus. + */ +static RTCPUID rtMpLinuxMaxCpus(void) +{ +#if 0 /* this doesn't do the right thing :-/ */ + int cMax = sysconf(_SC_NPROCESSORS_CONF); + Assert(cMax >= 1); + return cMax; +#else + static uint32_t s_cMax = 0; + if (!s_cMax) + { + int cMax = 1; + for (unsigned iCpu = 0; iCpu < RTCPUSET_MAX_CPUS; iCpu++) + if (RTLinuxSysFsExists("devices/system/cpu/cpu%d", iCpu)) + cMax = iCpu + 1; + ASMAtomicUoWriteU32((uint32_t volatile *)&s_cMax, cMax); + return cMax; + } + return s_cMax; +#endif +} + +/** + * Internal worker that picks the processor speed in MHz from /proc/cpuinfo. + * + * @returns CPU frequency. + */ +static uint32_t rtMpLinuxGetFrequency(RTCPUID idCpu) +{ + FILE *pFile = fopen("/proc/cpuinfo", "r"); + if (!pFile) + return 0; + + char sz[256]; + RTCPUID idCpuFound = NIL_RTCPUID; + uint32_t Frequency = 0; + while (fgets(sz, sizeof(sz), pFile)) + { + char *psz; + if ( !strncmp(sz, RT_STR_TUPLE("processor")) + && (sz[10] == ' ' || sz[10] == '\t' || sz[10] == ':') + && (psz = strchr(sz, ':'))) + { + psz += 2; + int64_t iCpu; + int rc = RTStrToInt64Ex(psz, NULL, 0, &iCpu); + if (RT_SUCCESS(rc)) + idCpuFound = iCpu; + } + else if ( idCpu == idCpuFound + && !strncmp(sz, RT_STR_TUPLE("cpu MHz")) + && (sz[10] == ' ' || sz[10] == '\t' || sz[10] == ':') + && (psz = strchr(sz, ':'))) + { + psz += 2; + int64_t v; + int rc = RTStrToInt64Ex(psz, &psz, 0, &v); + if (RT_SUCCESS(rc)) + { + Frequency = v; + break; + } + } + } + fclose(pFile); + return Frequency; +} + + +/** @todo RTmpCpuId(). */ + +RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu) +{ + return idCpu < rtMpLinuxMaxCpus() ? (int)idCpu : -1; +} + + +RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu) +{ + return (unsigned)iCpu < rtMpLinuxMaxCpus() ? iCpu : NIL_RTCPUID; +} + + +RTDECL(RTCPUID) RTMpGetMaxCpuId(void) +{ + return rtMpLinuxMaxCpus() - 1; +} + + +RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu) +{ + /** @todo check if there is a simpler interface than this... */ + int64_t i = 0; + int rc = RTLinuxSysFsReadIntFile(0, &i, "devices/system/cpu/cpu%d/online", (int)idCpu); + if ( RT_FAILURE(rc) + && RTLinuxSysFsExists("devices/system/cpu/cpu%d", (int)idCpu)) + { + /** @todo Assert(!RTLinuxSysFsExists("devices/system/cpu/cpu%d/online", + * (int)idCpu)); + * Unfortunately, the online file wasn't always world readable (centos + * 2.6.18-164). */ + i = 1; + rc = VINF_SUCCESS; + } + + AssertMsg(i == 0 || i == -1 || i == 1, ("i=%d\n", i)); + return RT_SUCCESS(rc) && i != 0; +} + + +RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) +{ + /** @todo check this up with hotplugging! */ + return RTLinuxSysFsExists("devices/system/cpu/cpu%d", (int)idCpu); +} + + +RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet) +{ + RTCpuSetEmpty(pSet); + RTCPUID cMax = rtMpLinuxMaxCpus(); + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + if (RTMpIsCpuPossible(idCpu)) + RTCpuSetAdd(pSet, idCpu); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetCount(void) +{ + RTCPUSET Set; + RTMpGetSet(&Set); + return RTCpuSetCount(&Set); +} + + +RTDECL(RTCPUID) RTMpGetCoreCount(void) +{ + RTCPUID cMax = rtMpLinuxMaxCpus(); + uint32_t *paidCores = (uint32_t *)alloca(sizeof(paidCores[0]) * (cMax + 1)); + uint32_t *paidPckgs = (uint32_t *)alloca(sizeof(paidPckgs[0]) * (cMax + 1)); + uint32_t cCores = 0; + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + { + if (RTMpIsCpuPossible(idCpu)) + { + int64_t idCore = 0; + int64_t idPckg = 0; + + int rc = RTLinuxSysFsReadIntFile(0, &idCore, "devices/system/cpu/cpu%d/topology/core_id", (int)idCpu); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(0, &idPckg, "devices/system/cpu/cpu%d/topology/physical_package_id", (int)idCpu); + + if (RT_SUCCESS(rc)) + { + uint32_t i; + + for (i = 0; i < cCores; i++) + if ( paidCores[i] == (uint32_t)idCore + && paidPckgs[i] == (uint32_t)idPckg) + break; + if (i >= cCores) + { + paidCores[cCores] = (uint32_t)idCore; + paidPckgs[cCores] = (uint32_t)idPckg; + cCores++; + } + } + } + } + Assert(cCores > 0); + return cCores; +} + + +RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet) +{ + RTCpuSetEmpty(pSet); + RTCPUID cMax = rtMpLinuxMaxCpus(); + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + if (RTMpIsCpuOnline(idCpu)) + RTCpuSetAdd(pSet, idCpu); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetOnlineCount(void) +{ + RTCPUSET Set; + RTMpGetOnlineSet(&Set); + return RTCpuSetCount(&Set); +} + + +RTDECL(RTCPUID) RTMpGetOnlineCoreCount(void) +{ + RTCPUID cMax = rtMpLinuxMaxCpus(); + uint32_t *paidCores = (uint32_t *)alloca(sizeof(paidCores[0]) * (cMax + 1)); + uint32_t *paidPckgs = (uint32_t *)alloca(sizeof(paidPckgs[0]) * (cMax + 1)); + uint32_t cCores = 0; + for (RTCPUID idCpu = 0; idCpu < cMax; idCpu++) + { + if (RTMpIsCpuOnline(idCpu)) + { + int64_t idCore = 0; + int64_t idPckg = 0; + + int rc = RTLinuxSysFsReadIntFile(0, &idCore, "devices/system/cpu/cpu%d/topology/core_id", (int)idCpu); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(0, &idPckg, "devices/system/cpu/cpu%d/topology/physical_package_id", (int)idCpu); + + if (RT_SUCCESS(rc)) + { + uint32_t i; + + for (i = 0; i < cCores; i++) + if ( paidCores[i] == idCore + && paidPckgs[i] == idPckg) + break; + if (i >= cCores) + { + paidCores[cCores] = idCore; + paidPckgs[cCores] = idPckg; + cCores++; + } + } + } + } + Assert(cCores > 0); + return cCores; +} + + + +RTDECL(uint32_t) RTMpGetCurFrequency(RTCPUID idCpu) +{ + int64_t kHz = 0; + int rc = RTLinuxSysFsReadIntFile(0, &kHz, "devices/system/cpu/cpu%d/cpufreq/cpuinfo_cur_freq", (int)idCpu); + if (RT_FAILURE(rc)) + { + /* + * The file may be just unreadable - in that case use plan B, i.e. + * /proc/cpuinfo to get the data we want. The assumption is that if + * cpuinfo_cur_freq doesn't exist then the speed won't change, and + * thus cur == max. If it does exist then cpuinfo contains the + * current frequency. + */ + kHz = rtMpLinuxGetFrequency(idCpu) * 1000; + } + return (kHz + 999) / 1000; +} + + +RTDECL(uint32_t) RTMpGetMaxFrequency(RTCPUID idCpu) +{ + int64_t kHz = 0; + int rc = RTLinuxSysFsReadIntFile(0, &kHz, "devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", (int)idCpu); + if (RT_FAILURE(rc)) + { + /* + * Check if the file isn't there - if it is there, then /proc/cpuinfo + * would provide current frequency information, which is wrong. + */ + if (!RTLinuxSysFsExists("devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", (int)idCpu)) + kHz = rtMpLinuxGetFrequency(idCpu) * 1000; + else + kHz = 0; + } + return (kHz + 999) / 1000; +} diff --git a/src/VBox/Runtime/r3/linux/rtProcInitExePath-linux.cpp b/src/VBox/Runtime/r3/linux/rtProcInitExePath-linux.cpp new file mode 100644 index 00000000..bd3edc12 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/rtProcInitExePath-linux.cpp @@ -0,0 +1,79 @@ +/* $Id: rtProcInitExePath-linux.cpp $ */ +/** @file + * IPRT - rtProcInitName, Linux. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <unistd.h> +#include <errno.h> + +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include "internal/process.h" +#include "internal/path.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ + /* + * Read the /proc/self/exe link, convert to native and return it. + */ + int cchLink = readlink("/proc/self/exe", pszPath, cchPath - 1); + if (cchLink > 0 && (size_t)cchLink <= cchPath - 1) + { + pszPath[cchLink] = '\0'; + + char const *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszPath, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszLink=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, cchLink, pszPath), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp, pszPath); + } + return rc; + } + + int err = errno; + int rc = RTErrConvertFromErrno(err); + AssertMsgFailed(("rc=%Rrc err=%d cchLink=%d\n", rc, err, cchLink)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/linux/sched-linux.cpp b/src/VBox/Runtime/r3/linux/sched-linux.cpp new file mode 100644 index 00000000..0b7370e0 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/sched-linux.cpp @@ -0,0 +1,707 @@ +/* $Id: sched-linux.cpp $ */ +/** @file + * IPRT - Scheduling, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * !WARNING! + * + * When talking about lowering and raising priority, we do *NOT* refer to + * the common direction priority values takes on unix systems (lower means + * higher). So, when we raise the priority of a linux thread the nice + * value will decrease, and when we lower the priority the nice value + * will increase. Confusing, right? + * + * !WARNING! + */ + + + +/** @def THREAD_LOGGING + * Be very careful with enabling this, it may cause deadlocks when combined + * with the 'thread' logging prefix. + */ +#ifdef DOXYGEN_RUNNING +# define THREAD_LOGGING +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <errno.h> +#include <pthread.h> +#include <limits.h> +#include <sched.h> +#include <unistd.h> +#include <sys/resource.h> + +#include <iprt/thread.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/log.h> +#include <iprt/errcore.h> +#include "internal/sched.h" +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Array scheduler attributes corresponding to each of the thread types. + * @internal */ +typedef struct PROCPRIORITYTYPE +{ + /** For sanity include the array index. */ + RTTHREADTYPE enmType; + /** The thread priority or nice delta - depends on which priority type. */ + int iPriority; +} PROCPRIORITYTYPE; + + +/** + * Configuration of one priority. + * @internal + */ +typedef struct +{ + /** The priority. */ + RTPROCPRIORITY enmPriority; + /** The name of this priority. */ + const char *pszName; + /** The process nice value. */ + int iNice; + /** The delta applied to the iPriority value. */ + int iDelta; + /** Array scheduler attributes corresponding to each of the thread types. */ + const PROCPRIORITYTYPE *paTypes; +} PROCPRIORITY; + + +/** + * Saved priority settings + * @internal + */ +typedef struct +{ + /** Process priority. */ + int iPriority; + /** Process level. */ + struct sched_param SchedParam; + /** Process level. */ + int iPolicy; + /** pthread level. */ + struct sched_param PthreadSchedParam; + /** pthread level. */ + int iPthreadPolicy; +} SAVEDPRIORITY, *PSAVEDPRIORITY; + + +/** + * Priorities for checking by separate thread + * @internal + */ +typedef struct +{ + /** The current thread priority to assume first. */ + int iCurrent; + /** The thread priority to try set afterwards. */ + int iNew; +} VALIDATORPRIORITYPAIR, *PVALIDATORPRIORITYPAIR; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Deltas for a process in which we are not restricted + * to only be lowering the priority. + */ +static const PROCPRIORITYTYPE g_aTypesLinuxFree[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, -999999999 }, + { RTTHREADTYPE_INFREQUENT_POLLER, +3 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, +2 }, + { RTTHREADTYPE_EMULATION, +1 }, + { RTTHREADTYPE_DEFAULT, 0 }, + { RTTHREADTYPE_GUI, 0 }, + { RTTHREADTYPE_MAIN_WORKER, 0 }, + { RTTHREADTYPE_VRDP_IO, -1 }, + { RTTHREADTYPE_DEBUGGER, -1 }, + { RTTHREADTYPE_MSG_PUMP, -2 }, + { RTTHREADTYPE_IO, -3 }, + { RTTHREADTYPE_TIMER, -4 } +}; + +/** + * Deltas for a process in which we are restricted and can only lower the priority. + */ +static const PROCPRIORITYTYPE g_aTypesLinuxRestricted[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, -999999999 }, + { RTTHREADTYPE_INFREQUENT_POLLER, +3 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, +2 }, + { RTTHREADTYPE_EMULATION, +1 }, + { RTTHREADTYPE_DEFAULT, 0 }, + { RTTHREADTYPE_GUI, 0 }, + { RTTHREADTYPE_MAIN_WORKER, 0 }, + { RTTHREADTYPE_VRDP_IO, 0 }, + { RTTHREADTYPE_DEBUGGER, 0 }, + { RTTHREADTYPE_MSG_PUMP, 0 }, + { RTTHREADTYPE_IO, 0 }, + { RTTHREADTYPE_TIMER, 0 } +}; + +/** + * All threads have the same priority. + * + * This is typically chosen when we find that we can't raise the priority + * to the process default of a thread created by a low priority thread. + */ +static const PROCPRIORITYTYPE g_aTypesLinuxFlat[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, -999999999 }, + { RTTHREADTYPE_INFREQUENT_POLLER, 0 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 0 }, + { RTTHREADTYPE_EMULATION, 0 }, + { RTTHREADTYPE_DEFAULT, 0 }, + { RTTHREADTYPE_GUI, 0 }, + { RTTHREADTYPE_MAIN_WORKER, 0 }, + { RTTHREADTYPE_VRDP_IO, 0 }, + { RTTHREADTYPE_DEBUGGER, 0 }, + { RTTHREADTYPE_MSG_PUMP, 0 }, + { RTTHREADTYPE_IO, 0 }, + { RTTHREADTYPE_TIMER, 0 } +}; + +/** + * Process and thread level priority, full access at thread level. + */ +static const PROCPRIORITY g_aUnixConfigs[] = +{ + { RTPROCPRIORITY_FLAT, "Flat", 0, 0, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_LOW, "Low", 9, 9, g_aTypesLinuxFree }, + { RTPROCPRIORITY_LOW, "Low", 9, 9, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_LOW, "Low", 15, 15, g_aTypesLinuxFree }, + { RTPROCPRIORITY_LOW, "Low", 15, 15, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_LOW, "Low", 17, 17, g_aTypesLinuxFree }, + { RTPROCPRIORITY_LOW, "Low", 17, 17, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_LOW, "Low", 19, 19, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_LOW, "Low", 9, 9, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_LOW, "Low", 15, 15, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_LOW, "Low", 17, 17, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesLinuxFree }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_HIGH, "High", -9, -9, g_aTypesLinuxFree }, + { RTPROCPRIORITY_HIGH, "High", -7, -7, g_aTypesLinuxFree }, + { RTPROCPRIORITY_HIGH, "High", -5, -5, g_aTypesLinuxFree }, + { RTPROCPRIORITY_HIGH, "High", -3, -3, g_aTypesLinuxFree }, + { RTPROCPRIORITY_HIGH, "High", -1, -1, g_aTypesLinuxFree }, + { RTPROCPRIORITY_HIGH, "High", -9, -9, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_HIGH, "High", -7, -7, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_HIGH, "High", -5, -5, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_HIGH, "High", -3, -3, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_HIGH, "High", -1, -1, g_aTypesLinuxRestricted }, + { RTPROCPRIORITY_HIGH, "High", -9, -9, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_HIGH, "High", -7, -7, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_HIGH, "High", -5, -5, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_HIGH, "High", -3, -3, g_aTypesLinuxFlat }, + { RTPROCPRIORITY_HIGH, "High", -1, -1, g_aTypesLinuxFlat } +}; + +/** + * The dynamic default priority configuration. + * + * This will be recalulated at runtime depending on what the + * system allow us to do and what the current priority is. + */ +static PROCPRIORITY g_aDefaultPriority = +{ + RTPROCPRIORITY_LOW, "Default", 0, 0, g_aTypesLinuxRestricted +}; + +/** Pointer to the current priority configuration. */ +static const PROCPRIORITY *g_pProcessPriority = &g_aDefaultPriority; + +/** Set if we can raise the priority of a thread beyond the default. + * + * It might mean we have the CAP_SYS_NICE capability or that the + * process's RLIMIT_NICE is higher than the priority of the thread + * calculating the defaults. + */ +static bool g_fCanRaisePriority = false; + +/** Set if we can restore the priority after having temporarily lowered or raised it. */ +static bool g_fCanRestorePriority = false; + +/** Set if we can NOT raise the priority to the process default in a thread + * created by a thread running below the process default. + */ +static bool g_fScrewedUpMaxPriorityLimitInheritance = true; + +/** The highest priority we can set. */ +static int g_iMaxPriority = 0; + +/** The lower priority we can set. */ +static int g_iMinPriority = 19; + +/** Set when we've successfully determined the capabilities of the process and kernel. */ +static bool g_fInitialized = false; + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Saves all the scheduling attributes we can think of. + */ +static void rtSchedNativeSave(PSAVEDPRIORITY pSave) +{ + memset(pSave, 0xff, sizeof(*pSave)); + + errno = 0; + pSave->iPriority = getpriority(PRIO_PROCESS, 0 /* current process */); + Assert(errno == 0); + + errno = 0; + sched_getparam(0 /* current process */, &pSave->SchedParam); + Assert(errno == 0); + + errno = 0; + pSave->iPolicy = sched_getscheduler(0 /* current process */); + Assert(errno == 0); + + int rc = pthread_getschedparam(pthread_self(), &pSave->iPthreadPolicy, &pSave->PthreadSchedParam); + Assert(rc == 0); NOREF(rc); +} + + +/** + * Restores scheduling attributes. + * Most of this won't work right, but anyway... + */ +static void rtSchedNativeRestore(PSAVEDPRIORITY pSave) +{ + setpriority(PRIO_PROCESS, 0, pSave->iPriority); + sched_setscheduler(0, pSave->iPolicy, &pSave->SchedParam); + sched_setparam(0, &pSave->SchedParam); + pthread_setschedparam(pthread_self(), pSave->iPthreadPolicy, &pSave->PthreadSchedParam); +} + + +/** + * Called on the priority proxy thread if requested running, otherwise + * rtSchedRunThread() calls it directly. + */ +static DECLCALLBACK(int) rtSchedRunThreadCallback(pthread_t *pThread, void *(*pfnThread)(void *pvArg), void *pvArg) +{ + int rc = pthread_create(pThread, NULL, pfnThread, pvArg); + if (!rc) + return VINF_SUCCESS; + return RTErrConvertFromErrno(rc); +} + + +/** + * Starts a worker thread and wait for it to complete. + * + * We cannot use RTThreadCreate since we're already owner of the RW lock. + */ +static int rtSchedRunThread(void *(*pfnThread)(void *pvArg), void *pvArg, bool fUsePriorityProxy) +{ + /* + * Create the thread. + */ + pthread_t Thread; + int rc; +#ifndef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY + RT_NOREF(fUsePriorityProxy); +#else + if ( fUsePriorityProxy + && rtThreadPosixPriorityProxyStart()) + rc = rtThreadPosixPriorityProxyCall(NULL, (PFNRT)rtSchedRunThreadCallback, 3, &Thread, pfnThread, pvArg); + else +#endif + rc = rtSchedRunThreadCallback(&Thread, pfnThread, pvArg); + if (RT_SUCCESS(rc)) + { + /* + * Wait for the thread to finish. + */ + void *pvRet = (void *)-1; + do + { + rc = pthread_join(Thread, &pvRet); + } while (rc == EINTR); + if (rc) + return RTErrConvertFromErrno(rc); + return (int)(uintptr_t)pvRet; + } + return rc; +} + + +static void rtSchedDumpPriority(void) +{ +#ifdef THREAD_LOGGING + Log(("Priority: g_fCanRaisePriority=%RTbool g_fCanRestorePriority=%RTbool g_fScrewedUpMaxPriorityLimitInheritance=%RTbool\n", + g_fCanRaisePriority, g_fCanRestorePriority, g_fScrewedUpMaxPriorityLimitInheritance)); + Log(("Priority: g_iMaxPriority=%d g_iMinPriority=%d\n", g_iMaxPriority, g_iMinPriority)); + Log(("Priority: enmPriority=%d \"%s\" iNice=%d iDelta=%d\n", + g_pProcessPriority->enmPriority, + g_pProcessPriority->pszName, + g_pProcessPriority->iNice, + g_pProcessPriority->iDelta)); + Log(("Priority: %2d INFREQUENT_POLLER = %d\n", RTTHREADTYPE_INFREQUENT_POLLER, g_pProcessPriority->paTypes[RTTHREADTYPE_INFREQUENT_POLLER].iPriority)); + Log(("Priority: %2d MAIN_HEAVY_WORKER = %d\n", RTTHREADTYPE_MAIN_HEAVY_WORKER, g_pProcessPriority->paTypes[RTTHREADTYPE_MAIN_HEAVY_WORKER].iPriority)); + Log(("Priority: %2d EMULATION = %d\n", RTTHREADTYPE_EMULATION , g_pProcessPriority->paTypes[RTTHREADTYPE_EMULATION ].iPriority)); + Log(("Priority: %2d DEFAULT = %d\n", RTTHREADTYPE_DEFAULT , g_pProcessPriority->paTypes[RTTHREADTYPE_DEFAULT ].iPriority)); + Log(("Priority: %2d GUI = %d\n", RTTHREADTYPE_GUI , g_pProcessPriority->paTypes[RTTHREADTYPE_GUI ].iPriority)); + Log(("Priority: %2d MAIN_WORKER = %d\n", RTTHREADTYPE_MAIN_WORKER , g_pProcessPriority->paTypes[RTTHREADTYPE_MAIN_WORKER ].iPriority)); + Log(("Priority: %2d VRDP_IO = %d\n", RTTHREADTYPE_VRDP_IO , g_pProcessPriority->paTypes[RTTHREADTYPE_VRDP_IO ].iPriority)); + Log(("Priority: %2d DEBUGGER = %d\n", RTTHREADTYPE_DEBUGGER , g_pProcessPriority->paTypes[RTTHREADTYPE_DEBUGGER ].iPriority)); + Log(("Priority: %2d MSG_PUMP = %d\n", RTTHREADTYPE_MSG_PUMP , g_pProcessPriority->paTypes[RTTHREADTYPE_MSG_PUMP ].iPriority)); + Log(("Priority: %2d IO = %d\n", RTTHREADTYPE_IO , g_pProcessPriority->paTypes[RTTHREADTYPE_IO ].iPriority)); + Log(("Priority: %2d TIMER = %d\n", RTTHREADTYPE_TIMER , g_pProcessPriority->paTypes[RTTHREADTYPE_TIMER ].iPriority)); +#endif +} + + +/** + * This just checks if it can raise the priority after having been + * created by a thread with a low priority. + * + * @returns zero on success, non-zero on failure. + * @param pvUser The priority of the parent before it was lowered (cast to int). + */ +static void *rtSchedNativeSubProberThread(void *pvUser) +{ + int iPriority = getpriority(PRIO_PROCESS, 0); + Assert(iPriority == g_iMinPriority); + + if (setpriority(PRIO_PROCESS, 0, iPriority + 1)) + return (void *)-1; + if (setpriority(PRIO_PROCESS, 0, (int)(intptr_t)pvUser)) + return (void *)-1; + return (void *)0; +} + + +/** + * The prober thread. + * We don't want to mess with the priority of the calling thread. + * + * @remark This is pretty presumptive stuff, but if it works on Linux and + * FreeBSD it does what I want. + */ +static void *rtSchedNativeProberThread(void *pvUser) +{ + NOREF(pvUser); + SAVEDPRIORITY SavedPriority; + rtSchedNativeSave(&SavedPriority); + + /* + * Check if we can get higher priority (typically only root can do this). + * (Won't work right if our priority is -19 to start with, but what the heck.) + * + * We assume that the priority range is -19 to 19. Should probably find the right + * define for this. + */ + int iStart = getpriority(PRIO_PROCESS, 0); + int i = iStart; + while (i-- > -20) + if (setpriority(PRIO_PROCESS, 0, i)) + break; + g_iMaxPriority = getpriority(PRIO_PROCESS, 0); + g_fCanRaisePriority = g_iMaxPriority < iStart; + g_fCanRestorePriority = setpriority(PRIO_PROCESS, 0, iStart) == 0; + + /* + * Check if we temporarily lower the thread priority. + * Again, we assume we're not at the extreme end of the priority scale. + */ + iStart = getpriority(PRIO_PROCESS, 0); + i = iStart; + while (i++ < 19) + if (setpriority(PRIO_PROCESS, 0, i)) + break; + g_iMinPriority = getpriority(PRIO_PROCESS, 0); + if ( setpriority(PRIO_PROCESS, 0, iStart) + || getpriority(PRIO_PROCESS, 0) != iStart) + g_fCanRestorePriority = false; + if (g_iMinPriority == g_iMaxPriority) + g_fCanRestorePriority = g_fCanRaisePriority = false; + + /* + * Check what happens to child threads when the parent lowers the + * priority when it's being created. + */ + iStart = getpriority(PRIO_PROCESS, 0); + g_fScrewedUpMaxPriorityLimitInheritance = true; + if ( g_fCanRestorePriority + && !setpriority(PRIO_PROCESS, 0, g_iMinPriority) + && iStart != g_iMinPriority) + { + if (rtSchedRunThread(rtSchedNativeSubProberThread, (void *)(intptr_t)iStart, false /*fUsePriorityProxy*/) == 0) + g_fScrewedUpMaxPriorityLimitInheritance = false; + } + + /* done */ + rtSchedNativeRestore(&SavedPriority); + return (void *)VINF_SUCCESS; +} + + +/** + * Calculate the scheduling properties for all the threads in the default + * process priority, assuming the current thread have the type enmType. + * + * @returns iprt status code. + * @param enmType The thread type to be assumed for the current thread. + */ +DECLHIDDEN(int) rtSchedNativeCalcDefaultPriority(RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + + /* + * First figure out what's we're allowed to do in this process. + */ + if (!g_fInitialized) + { + int iPriority = getpriority(PRIO_PROCESS, 0); +#ifdef RLIMIT_RTPRIO + /** @todo */ +#endif + int rc = rtSchedRunThread(rtSchedNativeProberThread, NULL, false /*fUsePriorityProxy*/); + if (RT_FAILURE(rc)) + return rc; + Assert(getpriority(PRIO_PROCESS, 0) == iPriority); NOREF(iPriority); + g_fInitialized = true; + } + + /* + * Select the right priority type table and update the default + * process priority structure. + */ + if (g_fCanRaisePriority && g_fCanRestorePriority && !g_fScrewedUpMaxPriorityLimitInheritance) + g_aDefaultPriority.paTypes = &g_aTypesLinuxFree[0]; + else if (!g_fCanRaisePriority && g_fCanRestorePriority && !g_fScrewedUpMaxPriorityLimitInheritance) + g_aDefaultPriority.paTypes = &g_aTypesLinuxRestricted[0]; + else + g_aDefaultPriority.paTypes = &g_aTypesLinuxFlat[0]; + Assert(enmType == g_aDefaultPriority.paTypes[enmType].enmType); + + int iPriority = getpriority(PRIO_PROCESS, 0 /* current process */); + g_aDefaultPriority.iNice = iPriority - g_aDefaultPriority.paTypes[enmType].iPriority; + g_aDefaultPriority.iDelta = g_aDefaultPriority.iNice; + + rtSchedDumpPriority(); + return VINF_SUCCESS; +} + + +/** + * The process priority validator thread. + * (We don't want to mess with the priority of the calling thread.) + */ +static void *rtSchedNativeValidatorThread(void *pvUser) +{ + PVALIDATORPRIORITYPAIR pPrioPair = (PVALIDATORPRIORITYPAIR)pvUser; + SAVEDPRIORITY SavedPriority; + rtSchedNativeSave(&SavedPriority); + + int rc = VINF_SUCCESS; + + /* + * Set the priority to the current value for specified thread type, but + * only if we have any threads of this type (caller checked - INT_MAX). + */ + if (pPrioPair->iCurrent != INT_MAX) + if (setpriority(PRIO_PROCESS, 0, pPrioPair->iCurrent)) + rc = RTErrConvertFromErrno(errno); + + /* + * Try set the new priority. + */ + if (RT_SUCCESS(rc) && setpriority(PRIO_PROCESS, 0, pPrioPair->iNew)) + rc = RTErrConvertFromErrno(errno); + + /* done */ + rtSchedNativeRestore(&SavedPriority); + return (void *)(intptr_t)rc; +} + + +/** + * Validates the ability to apply suggested priority scheme. + * + * The function checks that we're able to apply all the thread types in the + * suggested priority scheme. + * + * @returns iprt status code. + * @param pCfg The priority scheme to validate. + * @param fHavePriorityProxy Set if we've got a priority proxy thread, + * otherwise clear. + */ +static int rtSchedNativeCheckThreadTypes(const PROCPRIORITY *pCfg, bool fHavePriorityProxy) +{ + int i = RTTHREADTYPE_END; + while (--i > RTTHREADTYPE_INVALID) + { + VALIDATORPRIORITYPAIR PrioPair; + PrioPair.iCurrent = g_pProcessPriority->paTypes[i].iPriority + g_pProcessPriority->iDelta; + PrioPair.iNew = pCfg->paTypes[i].iPriority + pCfg->iDelta; + if (g_acRTThreadTypeStats[i] == 0) + PrioPair.iCurrent = INT_MAX; + +#ifdef RT_STRICT + int const iPriority = getpriority(PRIO_PROCESS, 0); +#endif + int rc = rtSchedRunThread(rtSchedNativeValidatorThread, &PrioPair, fHavePriorityProxy /*fUsePriorityProxy*/); + Assert(getpriority(PRIO_PROCESS, 0) == iPriority); + + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) rtProcNativeSetPriority(RTPROCPRIORITY enmPriority) +{ + Assert(enmPriority > RTPROCPRIORITY_INVALID && enmPriority < RTPROCPRIORITY_LAST); + +#ifdef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY + /* + * Make sure the proxy creation thread is started so we don't 'lose' our + * initial priority if it's lowered. + */ + bool const fHavePriorityProxy = rtThreadPosixPriorityProxyStart(); +#else + bool const fHavePriorityProxy = false; +#endif + + int rc; + if (enmPriority == RTPROCPRIORITY_DEFAULT) + { + /* + * If we've lowered priority since the process started, it may be impossible + * to raise it again for existing thread (new threads will work fine). + */ + rc = rtSchedNativeCheckThreadTypes(&g_aDefaultPriority, fHavePriorityProxy); + if (RT_SUCCESS(rc)) + g_pProcessPriority = &g_aDefaultPriority; + } + else + { + /* + * Find a configuration which matches and can be applied. + */ + rc = VERR_NOT_FOUND; + for (unsigned i = 0; i < RT_ELEMENTS(g_aUnixConfigs); i++) + if (g_aUnixConfigs[i].enmPriority == enmPriority) + { + int rc2 = rtSchedNativeCheckThreadTypes(&g_aUnixConfigs[i], fHavePriorityProxy); + if (RT_SUCCESS(rc2)) + { + g_pProcessPriority = &g_aUnixConfigs[i]; + rc = VINF_SUCCESS; + break; + } + if (rc == VERR_NOT_FOUND || rc == VERR_ACCESS_DENIED) + rc = rc2; + } + } + +#ifdef THREAD_LOGGING + LogFlow(("rtProcNativeSetPriority: returns %Rrc enmPriority=%d\n", rc, enmPriority)); + rtSchedDumpPriority(); +#endif + return rc; +} + + +/** + * Called on the priority proxy thread if it's running, otherwise + * rtThreadNativeSetPriority calls it directly. + */ +static DECLCALLBACK(int) rtThreadLinuxSetPriorityCallback(PRTTHREADINT pThread, int iPriority) +{ + if (!setpriority(PRIO_PROCESS, pThread->tid, iPriority)) + { + AssertMsg(iPriority == getpriority(PRIO_PROCESS, pThread->tid), + ("iPriority=%d getpriority()=%d\n", iPriority, getpriority(PRIO_PROCESS, pThread->tid))); +#ifdef THREAD_LOGGING + Log(("rtThreadNativeSetPriority: Thread=%p enmType=%d iPriority=%d pid=%d tid=%d\n", + pThread->Core.Key, enmType, iPriority, getpid(), pThread->tid)); +#endif + return VINF_SUCCESS; + } + AssertMsgFailed(("setpriority(,, %d) -> errno=%d rc=%Rrc\n", iPriority, errno, RTErrConvertFromErrno(errno))); + return VINF_SUCCESS; //non-fatal for now. +} + + +DECLHIDDEN(int) rtThreadNativeSetPriority(PRTTHREADINT pThread, RTTHREADTYPE enmType) +{ + /* sanity */ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + Assert(enmType == g_pProcessPriority->paTypes[enmType].enmType); + + /* + * The thread ID is zero for alien threads, so skip these or we'd risk + * modifying our own priority. + */ + if (!pThread->tid) + return VINF_SUCCESS; + + /* + * Calculate the thread priority and apply it, preferrably via the priority proxy thread. + */ + int const iPriority = g_pProcessPriority->paTypes[enmType].iPriority + g_pProcessPriority->iDelta; +#ifdef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY + if (rtThreadPosixPriorityProxyStart()) + return rtThreadPosixPriorityProxyCall(pThread, (PFNRT)rtThreadLinuxSetPriorityCallback, 2, pThread, iPriority); +#endif + return rtThreadLinuxSetPriorityCallback(pThread, iPriority); +} + diff --git a/src/VBox/Runtime/r3/linux/semevent-linux.cpp b/src/VBox/Runtime/r3/linux/semevent-linux.cpp new file mode 100644 index 00000000..c3a973c1 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/semevent-linux.cpp @@ -0,0 +1,607 @@ +/* $Id: semevent-linux.cpp $ */ +/** @file + * IPRT - Event Semaphore, Linux (2.6.0 and later). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include <features.h> +#if __GLIBC_PREREQ(2,6) && !defined(IPRT_WITH_FUTEX_BASED_SEMS) + +/* + * glibc 2.6 fixed a serious bug in the mutex implementation. We wrote this + * linux specific event semaphores code in order to work around the bug. We + * will fall back on the pthread-based implementation if glibc is known to + * contain the bug fix. + * + * The external reference to epoll_pwait is a hack which prevents that we link + * against glibc < 2.6. + */ +# include "../posix/semevent-posix.cpp" +__asm__ (".global epoll_pwait"); + +#else /* glibc < 2.6 */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/mem.h> +#include <iprt/time.h> +#include "internal/magics.h" +#include "internal/mem.h" +#include "internal/strict.h" + +#include <errno.h> +#include <limits.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/syscall.h> + +#include "semwait-linux.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Linux (single wakup) event semaphore. + */ +struct RTSEMEVENTINTERNAL +{ + /** Magic value. */ + intptr_t volatile iMagic; + /** The futex state variable. + * 0 means not signalled. + 1 means signalled. */ + uint32_t volatile fSignalled; + /** The number of waiting threads */ + int32_t volatile cWaiters; +#ifdef RTSEMEVENT_STRICT + /** Signallers. */ + RTLOCKVALRECSHRD Signallers; + /** Indicates that lock validation should be performed. */ + bool volatile fEverHadSignallers; +#endif + /** The creation flags. */ + uint32_t fFlags; +}; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Whether we can use FUTEX_WAIT_BITSET. */ +static int volatile g_fCanUseWaitBitSet = -1; + + + + +RTDECL(int) RTSemEventCreate(PRTSEMEVENT phEventSem) +{ + return RTSemEventCreateEx(phEventSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventCreateEx(PRTSEMEVENT phEventSem, uint32_t fFlags, RTLOCKVALCLASS hClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~(RTSEMEVENT_FLAGS_NO_LOCK_VAL | RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)), VERR_INVALID_PARAMETER); + Assert(!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) || (fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL)); + + /* + * Make sure we know whether FUTEX_WAIT_BITSET works. + */ + rtSemLinuxCheckForFutexWaitBitSet(&g_fCanUseWaitBitSet); +#if defined(DEBUG_bird) && !defined(IN_GUEST) + Assert(g_fCanUseWaitBitSet == true); +#endif + + /* + * Allocate semaphore handle. + */ + struct RTSEMEVENTINTERNAL *pThis; + if (!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + pThis = (struct RTSEMEVENTINTERNAL *)RTMemAlloc(sizeof(struct RTSEMEVENTINTERNAL)); + else + pThis = (struct RTSEMEVENTINTERNAL *)rtMemBaseAlloc(sizeof(struct RTSEMEVENTINTERNAL)); + if (pThis) + { + pThis->iMagic = RTSEMEVENT_MAGIC; + pThis->cWaiters = 0; + pThis->fSignalled = 0; + pThis->fFlags = fFlags; +#ifdef RTSEMEVENT_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iSemEventAnon = 0; + RTLockValidatorRecSharedInit(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL), + "RTSemEvent-%u", ASMAtomicIncU32(&s_iSemEventAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL), + pszNameFmt, va); + va_end(va); + } + pThis->fEverHadSignallers = false; +#else + RT_NOREF(hClass, pszNameFmt); +#endif + + *phEventSem = pThis; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + +RTDECL(int) RTSemEventDestroy(RTSEMEVENT hEventSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + if (pThis == NIL_RTSEMEVENT) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->iMagic == RTSEMEVENT_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the semaphore and wake up anyone waiting on it. + */ + ASMAtomicXchgSize(&pThis->iMagic, RTSEMEVENT_MAGIC | UINT32_C(0x80000000)); + if (ASMAtomicXchgS32(&pThis->cWaiters, INT32_MIN / 2) > 0) + { + sys_futex(&pThis->fSignalled, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + usleep(1000); + } + + /* + * Free the semaphore memory and be gone. + */ +#ifdef RTSEMEVENT_STRICT + RTLockValidatorRecSharedDelete(&pThis->Signallers); +#endif + if (!(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + RTMemFree(pThis); + else + rtMemBaseFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSemEventSignal(RTSEMEVENT hEventSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->iMagic == RTSEMEVENT_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSEMEVENT_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckSignaller(&pThis->Signallers, NIL_RTTHREAD); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif + + ASMAtomicWriteU32(&pThis->fSignalled, 1); + if (ASMAtomicReadS32(&pThis->cWaiters) < 1) + return VINF_SUCCESS; + + /* somebody is waiting, try wake up one of them. */ + long cWoken = sys_futex(&pThis->fSignalled, FUTEX_WAKE, 1, NULL, NULL, 0); + if (RT_LIKELY(cWoken >= 0)) + return VINF_SUCCESS; + + if (RT_UNLIKELY(pThis->iMagic != RTSEMEVENT_MAGIC)) + return VERR_SEM_DESTROYED; + + return VERR_INVALID_PARAMETER; +} + + +/** + * Performs an indefinite wait on the event. + */ +static int rtSemEventLinuxWaitIndefinite(struct RTSEMEVENTINTERNAL *pThis, uint32_t fFlags, PCRTLOCKVALSRCPOS pSrcPos) +{ + RT_NOREF_PV(pSrcPos); + + /* + * Quickly check whether it's signaled and there are no other waiters. + */ + uint32_t cWaiters = ASMAtomicIncS32(&pThis->cWaiters); + if ( cWaiters == 1 + && ASMAtomicCmpXchgU32(&pThis->fSignalled, 0, 1)) + { + ASMAtomicDecS32(&pThis->cWaiters); + return VINF_SUCCESS; + } + + /* + * The wait loop. + */ +#ifdef RTSEMEVENT_STRICT + RTTHREAD hThreadSelf = !(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) + ? RTThreadSelfAutoAdopt() + : RTThreadSelf(); +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + int rc = VINF_SUCCESS; + for (;;) + { +#ifdef RTSEMEVENT_STRICT + if (pThis->fEverHadSignallers) + { + rc = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + RT_INDEFINITE_WAIT, RTTHREADSTATE_EVENT, true); + if (RT_FAILURE(rc)) + break; + } +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT, true); + long lrc = sys_futex(&pThis->fSignalled, FUTEX_WAIT, 0, NULL /*pTimeout*/, NULL, 0); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT); + if (RT_UNLIKELY(pThis->iMagic != RTSEMEVENT_MAGIC)) + { + rc = VERR_SEM_DESTROYED; + break; + } + + if (RT_LIKELY(lrc == 0 || lrc == -EWOULDBLOCK)) + { + /* successful wakeup or fSignalled > 0 in the meantime */ + if (ASMAtomicCmpXchgU32(&pThis->fSignalled, 0, 1)) + break; + } + else if (lrc == -ETIMEDOUT) + { + rc = VERR_TIMEOUT; + break; + } + else if (lrc == -EINTR) + { + if (fFlags & RTSEMWAIT_FLAGS_NORESUME) + { + rc = VERR_INTERRUPTED; + break; + } + } + else + { + /* this shouldn't happen! */ + AssertMsgFailed(("rc=%ld errno=%d\n", lrc, errno)); + rc = RTErrConvertFromErrno(lrc); + break; + } + } + + ASMAtomicDecS32(&pThis->cWaiters); + return rc; +} + + +/** + * Handle polling (timeout already expired at the time of the call). + * + * @returns VINF_SUCCESS, VERR_TIMEOUT, VERR_SEM_DESTROYED. + * @param pThis The semaphore. + */ +static int rtSemEventLinuxWaitPoll(struct RTSEMEVENTINTERNAL *pThis) +{ + /* + * What we do here is isn't quite fair to anyone else waiting on it, however + * it might not be as bad as all that for callers making repeated poll calls + * because they cannot block, as that would be a virtual wait but without the + * chance of a permanept queue position. So, I hope we can live with this. + */ + if (ASMAtomicCmpXchgU32(&pThis->fSignalled, 0, 1)) + return VINF_SUCCESS; + return VERR_TIMEOUT; +} + + +/** + * Performs an timed wait on the event. + */ +static int rtSemEventLinuxWaitTimed(struct RTSEMEVENTINTERNAL *pThis, uint32_t fFlags, + uint64_t uTimeout, PCRTLOCKVALSRCPOS pSrcPos) +{ + RT_NOREF_PV(pSrcPos); + + /* + * Convert the timeout value. + */ + struct timespec TsTimeout; + int iWaitOp; + uint32_t uWaitVal3; + uint64_t nsAbsTimeout = uTimeout; /* (older gcc maybe used uninitialized) */ + uTimeout = rtSemLinuxCalcDeadline(fFlags, uTimeout, g_fCanUseWaitBitSet, &TsTimeout, &iWaitOp, &uWaitVal3, &nsAbsTimeout); + if (uTimeout == 0) + return rtSemEventLinuxWaitPoll(pThis); + if (uTimeout == UINT64_MAX) + return rtSemEventLinuxWaitIndefinite(pThis, fFlags, pSrcPos); + + /* + * Quickly check whether it's signaled and there are no other waiters. + */ + uint32_t cWaiters = ASMAtomicIncS32(&pThis->cWaiters); + if ( cWaiters == 1 + && ASMAtomicCmpXchgU32(&pThis->fSignalled, 0, 1)) + { + ASMAtomicDecS32(&pThis->cWaiters); + return VINF_SUCCESS; + } + + /* + * The wait loop. + */ +#ifdef RTSEMEVENT_STRICT + RTTHREAD hThreadSelf = !(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) + ? RTThreadSelfAutoAdopt() + : RTThreadSelf(); +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + int rc = VINF_SUCCESS; + for (;;) + { +#ifdef RTSEMEVENT_STRICT + if (pThis->fEverHadSignallers) + { + rc = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + iWaitOp == FUTEX_WAIT ? uTimeout / RT_NS_1MS : RT_MS_1HOUR /*whatever*/, + RTTHREADSTATE_EVENT, true); + if (RT_FAILURE(rc)) + break; + } +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT, true); + long lrc = sys_futex(&pThis->fSignalled, iWaitOp, 0, &TsTimeout, NULL, uWaitVal3); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT); + if (RT_UNLIKELY(pThis->iMagic != RTSEMEVENT_MAGIC)) + { + rc = VERR_SEM_DESTROYED; + break; + } + + if (RT_LIKELY(lrc == 0 || lrc == -EWOULDBLOCK)) + { + /* successful wakeup or fSignalled > 0 in the meantime */ + if (ASMAtomicCmpXchgU32(&pThis->fSignalled, 0, 1)) + break; + } + else if (lrc == -ETIMEDOUT) + { +#ifdef RT_STRICT + uint64_t const uNow = RTTimeNanoTS(); + AssertMsg(uNow >= nsAbsTimeout || nsAbsTimeout - uNow < RT_NS_1MS, + ("%#RX64 - %#RX64 => %#RX64 (%RI64)\n", nsAbsTimeout, uNow, nsAbsTimeout - uNow, nsAbsTimeout - uNow)); +#endif + rc = VERR_TIMEOUT; + break; + } + else if (lrc == -EINTR) + { + if (fFlags & RTSEMWAIT_FLAGS_NORESUME) + { + rc = VERR_INTERRUPTED; + break; + } + } + else + { + /* this shouldn't happen! */ + AssertMsgFailed(("rc=%ld errno=%d\n", lrc, errno)); + rc = RTErrConvertFromErrno(lrc); + break; + } + + /* adjust the relative timeout */ + if (iWaitOp == FUTEX_WAIT) + { + int64_t i64Diff = nsAbsTimeout - RTTimeSystemNanoTS(); + if (i64Diff < 1000) + { + rc = VERR_TIMEOUT; + break; + } + TsTimeout.tv_sec = (uint64_t)i64Diff / RT_NS_1SEC; + TsTimeout.tv_nsec = (uint64_t)i64Diff % RT_NS_1SEC; + } + } + + ASMAtomicDecS32(&pThis->cWaiters); + return rc; +} + + +/** + * Internal wait worker function. + */ +DECLINLINE(int) rtSemEventLinuxWait(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->iMagic == RTSEMEVENT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTSEMWAIT_FLAGS_ARE_VALID(fFlags), VERR_INVALID_PARAMETER); +#ifdef RT_STRICT + uint32_t const fSignalled = pThis->fSignalled; + Assert(fSignalled == false || fSignalled == true); +#endif + + /* + * Timed or indefinite wait? + */ + if (fFlags & RTSEMWAIT_FLAGS_INDEFINITE) + return rtSemEventLinuxWaitIndefinite(pThis, fFlags, pSrcPos); + return rtSemEventLinuxWaitTimed(hEventSem, fFlags, uTimeout, pSrcPos); +} + + +RTDECL(int) RTSemEventWait(RTSEMEVENT hEventSem, RTMSINTERVAL cMillies) +{ + int rc; +#ifndef RTSEMEVENT_STRICT + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, NULL); + else + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, &SrcPos); + else + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, &SrcPos); +#endif + Assert(rc != VERR_INTERRUPTED); + return rc; +} + + +RTDECL(int) RTSemEventWaitNoResume(RTSEMEVENT hEventSem, RTMSINTERVAL cMillies) +{ + int rc; +#ifndef RTSEMEVENT_STRICT + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, NULL); + else + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, &SrcPos); + else + rc = rtSemEventLinuxWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, &SrcPos); +#endif + Assert(rc != VERR_INTERRUPTED); + return rc; +} + + +RTDECL(int) RTSemEventWaitEx(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout) +{ +#ifndef RTSEMEVENT_STRICT + return rtSemEventLinuxWait(hEventSem, fFlags, uTimeout, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemEventLinuxWait(hEventSem, fFlags, uTimeout, &SrcPos); +#endif +} + + +RTDECL(int) RTSemEventWaitExDebug(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout, + RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemEventLinuxWait(hEventSem, fFlags, uTimeout, &SrcPos); +} + + +RTDECL(uint32_t) RTSemEventGetResolution(void) +{ + /** @todo we have 1ns parameter resolution, but need to verify that this is what + * the kernel actually will use when setting the timer. Most likely + * it's rounded a little, but hopefully not to a multiple of HZ. */ + return 1; +} + + +RTDECL(void) RTSemEventSetSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->iMagic == RTSEMEVENT_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedResetOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF(hEventSem, hThread); +#endif +} + + +RTDECL(void) RTSemEventAddSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->iMagic == RTSEMEVENT_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedAddOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF(hEventSem, hThread); +#endif +} + + +RTDECL(void) RTSemEventRemoveSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->iMagic == RTSEMEVENT_MAGIC); + + RTLockValidatorRecSharedRemoveOwner(&pThis->Signallers, hThread); +#else + RT_NOREF(hEventSem, hThread); +#endif +} + +#endif /* glibc < 2.6 || IPRT_WITH_FUTEX_BASED_SEMS */ + diff --git a/src/VBox/Runtime/r3/linux/semeventmulti-linux.cpp b/src/VBox/Runtime/r3/linux/semeventmulti-linux.cpp new file mode 100644 index 00000000..87554838 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/semeventmulti-linux.cpp @@ -0,0 +1,600 @@ +/* $Id: semeventmulti-linux.cpp $ */ +/** @file + * IPRT - Multiple Release Event Semaphore, Linux (2.6.x+). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +#include <features.h> +#if __GLIBC_PREREQ(2,6) && !defined(IPRT_WITH_FUTEX_BASED_SEMS) + +/* + * glibc 2.6 fixed a serious bug in the mutex implementation. We wrote this + * linux specific event semaphores code in order to work around the bug. As it + * turns out, this code seems to have an unresolved issue (@bugref{2599}), so we'll + * fall back on the pthread based implementation if glibc is known to contain + * the bug fix. + * + * The external reference to epoll_pwait is a hack which prevents that we link + * against glibc < 2.6. + */ +#include "../posix/semeventmulti-posix.cpp" +__asm__ (".global epoll_pwait"); + +#else /* glibc < 2.6 */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/mem.h> +#include <iprt/time.h> +#include "internal/magics.h" +#include "internal/strict.h" + + +#include <errno.h> +#include <limits.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/syscall.h> + +#include "semwait-linux.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Linux multiple wakup event semaphore. + */ +struct RTSEMEVENTMULTIINTERNAL +{ + /** Magic value. */ + uint32_t volatile u32Magic; + /** The futex state variable, see RTSEMEVENTMULTI_LNX_XXX. */ + uint32_t volatile uState; +#ifdef RT_STRICT + /** Increased on every signalling call. */ + uint32_t volatile uSignalSerialNo; +#endif +#ifdef RTSEMEVENTMULTI_STRICT + /** Signallers. */ + RTLOCKVALRECSHRD Signallers; + /** Indicates that lock validation should be performed. */ + bool volatile fEverHadSignallers; +#endif +}; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @name RTSEMEVENTMULTI_LNX_XXX - state + * @{ */ +#define RTSEMEVENTMULTI_LNX_NOT_SIGNALED UINT32_C(0x00000000) +#define RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS UINT32_C(0x00000001) +#define RTSEMEVENTMULTI_LNX_SIGNALED UINT32_C(0x00000003) +/** @} */ + +#define ASSERT_VALID_STATE(a_uState) \ + AssertMsg( (a_uState) == RTSEMEVENTMULTI_LNX_NOT_SIGNALED \ + || (a_uState) == RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS \ + || (a_uState) == RTSEMEVENTMULTI_LNX_SIGNALED, \ + (#a_uState "=%s\n", a_uState)) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Whether we can use FUTEX_WAIT_BITSET. */ +static int volatile g_fCanUseWaitBitSet = -1; + + +RTDECL(int) RTSemEventMultiCreate(PRTSEMEVENTMULTI phEventMultiSem) +{ + return RTSemEventMultiCreateEx(phEventMultiSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventMultiCreateEx(PRTSEMEVENTMULTI phEventMultiSem, uint32_t fFlags, RTLOCKVALCLASS hClass, + const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Make sure we know whether FUTEX_WAIT_BITSET works. + */ + rtSemLinuxCheckForFutexWaitBitSet(&g_fCanUseWaitBitSet); +#if defined(DEBUG_bird) && !defined(IN_GUEST) + Assert(g_fCanUseWaitBitSet == true); +#endif + + /* + * Allocate semaphore handle. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = (struct RTSEMEVENTMULTIINTERNAL *)RTMemAlloc(sizeof(struct RTSEMEVENTMULTIINTERNAL)); + if (pThis) + { + pThis->u32Magic = RTSEMEVENTMULTI_MAGIC; + pThis->uState = RTSEMEVENTMULTI_LNX_NOT_SIGNALED; +#ifdef RT_STRICT + pThis->uSignalSerialNo = 0; +#endif +#ifdef RTSEMEVENTMULTI_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iSemEventMultiAnon = 0; + RTLockValidatorRecSharedInit(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), + "RTSemEventMulti-%u", ASMAtomicIncU32(&s_iSemEventMultiAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), + pszNameFmt, va); + va_end(va); + } + pThis->fEverHadSignallers = false; +#else + RT_NOREF(hClass, pszNameFmt); +#endif + + *phEventMultiSem = pThis; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + +RTDECL(int) RTSemEventMultiDestroy(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + if (pThis == NIL_RTSEMEVENTMULTI) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the semaphore and wake up anyone waiting on it. + */ + ASMAtomicWriteU32(&pThis->u32Magic, RTSEMEVENTMULTI_MAGIC + 1); + if (ASMAtomicXchgU32(&pThis->uState, RTSEMEVENTMULTI_LNX_SIGNALED) == RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS) + { + sys_futex(&pThis->uState, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + usleep(1000); + } + + /* + * Free the semaphore memory and be gone. + */ +#ifdef RTSEMEVENTMULTI_STRICT + RTLockValidatorRecSharedDelete(&pThis->Signallers); +#endif + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSemEventMultiSignal(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSEMEVENTMULTI_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckSignaller(&pThis->Signallers, NIL_RTTHREAD); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif + + /* + * Signal it. + */ +#ifdef RT_STRICT + ASMAtomicIncU32(&pThis->uSignalSerialNo); +#endif + uint32_t uOld = ASMAtomicXchgU32(&pThis->uState, RTSEMEVENTMULTI_LNX_SIGNALED); + if (uOld == RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS) + { + /* wake up sleeping threads. */ + long cWoken = sys_futex(&pThis->uState, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + AssertMsg(cWoken >= 0, ("%ld\n", cWoken)); NOREF(cWoken); + } + ASSERT_VALID_STATE(uOld); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSemEventMultiReset(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_STRICT + uint32_t const uState = pThis->uState; + ASSERT_VALID_STATE(uState); +#endif + + /* + * Reset it. + */ + ASMAtomicCmpXchgU32(&pThis->uState, RTSEMEVENTMULTI_LNX_NOT_SIGNALED, RTSEMEVENTMULTI_LNX_SIGNALED); + return VINF_SUCCESS; +} + + +/** + * Performs an indefinite wait on the event. + */ +static int rtSemEventMultiLinuxWaitIndefinite(struct RTSEMEVENTMULTIINTERNAL *pThis, uint32_t fFlags, PCRTLOCKVALSRCPOS pSrcPos) +{ + RT_NOREF(pSrcPos); + + /* + * Quickly check whether it's signaled. + */ + uint32_t uState = ASMAtomicUoReadU32(&pThis->uState); + if (uState == RTSEMEVENTMULTI_LNX_SIGNALED) + return VINF_SUCCESS; + ASSERT_VALID_STATE(uState); + + /* + * The wait loop. + */ +#ifdef RTSEMEVENTMULTI_STRICT + RTTHREAD hThreadSelf = RTThreadSelfAutoAdopt(); +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + for (unsigned i = 0;; i++) + { + /* + * Start waiting. We only account for there being or having been + * threads waiting on the semaphore to keep things simple. + */ + uState = ASMAtomicUoReadU32(&pThis->uState); + if ( uState == RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS + || ( uState == RTSEMEVENTMULTI_LNX_NOT_SIGNALED + && ASMAtomicCmpXchgU32(&pThis->uState, RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS, + RTSEMEVENTMULTI_LNX_NOT_SIGNALED))) + { +#ifdef RTSEMEVENTMULTI_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + RT_INDEFINITE_WAIT, RTTHREADSTATE_EVENT_MULTI, true); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif +#ifdef RT_STRICT + uint32_t const uPrevSignalSerialNo = ASMAtomicReadU32(&pThis->uSignalSerialNo); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT_MULTI, true); + long rc = sys_futex(&pThis->uState, FUTEX_WAIT, 1, NULL /*pTimeout*/, NULL, 0); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT_MULTI); + + /* Check that the structure is still alive before continuing. */ + if (RT_LIKELY(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC)) + { /*likely*/ } + else + return VERR_SEM_DESTROYED; + + /* + * Return if success. + */ + if (rc == 0) + { + Assert(uPrevSignalSerialNo != ASMAtomicReadU32(&pThis->uSignalSerialNo)); + return VINF_SUCCESS; + } + + /* + * Act on the wakup code. + */ + if (rc == -EWOULDBLOCK) + /* retry, the value changed. */; + else if (rc == -EINTR) + { + if (fFlags & RTSEMWAIT_FLAGS_NORESUME) + return VERR_INTERRUPTED; + } + else + { + /* this shouldn't happen! */ + AssertMsgFailed(("rc=%ld errno=%d\n", rc, errno)); + return RTErrConvertFromErrno(rc); + } + } + else if (uState == RTSEMEVENTMULTI_LNX_SIGNALED) + return VINF_SUCCESS; + else + ASSERT_VALID_STATE(uState); + } +} + + +/** + * Handle polling (timeout already expired at the time of the call). + * + * @returns VINF_SUCCESS, VERR_TIMEOUT, VERR_SEM_DESTROYED. + * @param pThis The semaphore. + */ +static int rtSemEventMultiLinuxWaitPoll(struct RTSEMEVENTMULTIINTERNAL *pThis) +{ + uint32_t uState = ASMAtomicUoReadU32(&pThis->uState); + if (uState == RTSEMEVENTMULTI_LNX_SIGNALED) + return VINF_SUCCESS; + return VERR_TIMEOUT; +} + + +/** + * Performs an indefinite wait on the event. + */ +static int rtSemEventMultiLinuxWaitTimed(struct RTSEMEVENTMULTIINTERNAL *pThis, uint32_t fFlags, uint64_t uTimeout, + PCRTLOCKVALSRCPOS pSrcPos) +{ + RT_NOREF(pSrcPos); + + /* + * Quickly check whether it's signaled. + */ + uint32_t uState = ASMAtomicUoReadU32(&pThis->uState); + if (uState == RTSEMEVENTMULTI_LNX_SIGNALED) + return VINF_SUCCESS; + ASSERT_VALID_STATE(uState); + + /* + * Convert the timeout value. + */ + struct timespec TsTimeout; + int iWaitOp; + uint32_t uWaitVal3; + uint64_t nsAbsTimeout = uTimeout; /* (older gcc maybe used uninitialized) */ + uTimeout = rtSemLinuxCalcDeadline(fFlags, uTimeout, g_fCanUseWaitBitSet, &TsTimeout, &iWaitOp, &uWaitVal3, &nsAbsTimeout); + if (uTimeout == 0) + return rtSemEventMultiLinuxWaitPoll(pThis); + if (uTimeout == UINT64_MAX) + return rtSemEventMultiLinuxWaitIndefinite(pThis, fFlags, pSrcPos); + + /* + * The wait loop. + */ +#ifdef RTSEMEVENTMULTI_STRICT + RTTHREAD hThreadSelf = RTThreadSelfAutoAdopt(); +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + for (unsigned i = 0;; i++) + { + /* + * Start waiting. We only account for there being or having been + * threads waiting on the semaphore to keep things simple. + */ + uState = ASMAtomicUoReadU32(&pThis->uState); + if ( uState == RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS + || ( uState == RTSEMEVENTMULTI_LNX_NOT_SIGNALED + && ASMAtomicCmpXchgU32(&pThis->uState, RTSEMEVENTMULTI_LNX_NOT_SIGNALED_WAITERS, + RTSEMEVENTMULTI_LNX_NOT_SIGNALED))) + { +#ifdef RTSEMEVENTMULTI_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + uTimeout / UINT32_C(1000000), RTTHREADSTATE_EVENT_MULTI, true); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif +#ifdef RT_STRICT + uint32_t const uPrevSignalSerialNo = ASMAtomicReadU32(&pThis->uSignalSerialNo); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT_MULTI, true); + long rc = sys_futex(&pThis->uState, iWaitOp, 1, &TsTimeout, NULL, uWaitVal3); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT_MULTI); + + /* Check that the structure is still alive before continuing. */ + if (RT_LIKELY(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC)) + { /*likely*/ } + else + return VERR_SEM_DESTROYED; + + /* + * Return if success. + */ + if (rc == 0) + { + Assert(uPrevSignalSerialNo != ASMAtomicReadU32(&pThis->uSignalSerialNo)); + return VINF_SUCCESS; + } + + /* + * Act on the wakup code. + */ + if (rc == -ETIMEDOUT) + { + /** @todo something is broken here. shows up every now and again in the ata + * code. Should try to run the timeout against RTTimeMilliTS to + * check that it's doing the right thing... */ +#ifdef RT_STRICT + uint64_t const uNow = RTTimeNanoTS(); + AssertMsg(uNow >= nsAbsTimeout || nsAbsTimeout - uNow < RT_NS_1MS, + ("%#RX64 - %#RX64 => %#RX64 (%RI64)\n", nsAbsTimeout, uNow, nsAbsTimeout - uNow, nsAbsTimeout - uNow)); +#endif + return VERR_TIMEOUT; + } + if (rc == -EWOULDBLOCK) + { + /* retry, the value changed. */; + } + else if (rc == -EINTR) + { + if (fFlags & RTSEMWAIT_FLAGS_NORESUME) + return VERR_INTERRUPTED; + } + else + { + /* this shouldn't happen! */ + AssertMsgFailed(("rc=%ld errno=%d\n", rc, errno)); + return RTErrConvertFromErrno(rc); + } + } + else if (uState == RTSEMEVENTMULTI_LNX_SIGNALED) + return VINF_SUCCESS; + else + ASSERT_VALID_STATE(uState); + + /* adjust the relative timeout if relative */ + if (iWaitOp == FUTEX_WAIT) + { + int64_t i64Diff = nsAbsTimeout - RTTimeSystemNanoTS(); + if (i64Diff < 1000) + return VERR_TIMEOUT; + TsTimeout.tv_sec = (uint64_t)i64Diff / RT_NS_1SEC; + TsTimeout.tv_nsec = (uint64_t)i64Diff % RT_NS_1SEC; + } + } +} + +/** + * Internal wait worker function. + */ +DECLINLINE(int) rtSemEventLnxMultiWait(RTSEMEVENTMULTI hEventSem, uint32_t fFlags, uint64_t uTimeout, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTSEMWAIT_FLAGS_ARE_VALID(fFlags), VERR_INVALID_PARAMETER); + + /* + * Timed or indefinite wait? + */ + if (fFlags & RTSEMWAIT_FLAGS_INDEFINITE) + return rtSemEventMultiLinuxWaitIndefinite(pThis, fFlags, pSrcPos); + return rtSemEventMultiLinuxWaitTimed(hEventSem, fFlags, uTimeout, pSrcPos); +} + + +#undef RTSemEventMultiWaitEx +RTDECL(int) RTSemEventMultiWaitEx(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout) +{ +#ifndef RTSEMEVENT_STRICT + return rtSemEventLnxMultiWait(hEventMultiSem, fFlags, uTimeout, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemEventLnxMultiWait(hEventMultiSem, fFlags, uTimeout, &SrcPos); +#endif +} + + +RTDECL(int) RTSemEventMultiWaitExDebug(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, + RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemEventLnxMultiWait(hEventMultiSem, fFlags, uTimeout, &SrcPos); +} + + +RTDECL(void) RTSemEventMultiSetSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENTMULTI_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedResetOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF(hEventMultiSem, hThread); +#endif +} + + +RTDECL(void) RTSemEventMultiAddSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENTMULTI_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedAddOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF(hEventMultiSem, hThread); +#endif +} + + +RTDECL(void) RTSemEventMultiRemoveSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENTMULTI_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC); + + RTLockValidatorRecSharedRemoveOwner(&pThis->Signallers, hThread); +#else + RT_NOREF(hEventMultiSem, hThread); +#endif +} + +#endif /* glibc < 2.6 || IPRT_WITH_FUTEX_BASED_SEMS */ + diff --git a/src/VBox/Runtime/r3/linux/semmutex-linux.cpp b/src/VBox/Runtime/r3/linux/semmutex-linux.cpp new file mode 100644 index 00000000..09cd866f --- /dev/null +++ b/src/VBox/Runtime/r3/linux/semmutex-linux.cpp @@ -0,0 +1,475 @@ +/* $Id: semmutex-linux.cpp $ */ +/** @file + * IPRT - Mutex Semaphore, Linux (2.6.x+). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/alloc.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include "internal/magics.h" +#include "internal/strict.h" + +#include <errno.h> +#include <limits.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/syscall.h> +#if 0 /* With 2.6.17 futex.h has become C++ unfriendly. */ +# include <linux/futex.h> +#else +# define FUTEX_WAIT 0 +# define FUTEX_WAKE 1 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Linux internal representation of a Mutex semaphore. + */ +struct RTSEMMUTEXINTERNAL +{ + /** The futex state variable. + * 0 means unlocked. + * 1 means locked, no waiters. + * 2 means locked, one or more waiters. + */ + int32_t volatile iState; + /** Nesting count. */ + uint32_t volatile cNestings; + /** The owner of the mutex. */ + pthread_t volatile Owner; + /** Magic value (RTSEMMUTEX_MAGIC). */ + uint32_t volatile u32Magic; +#ifdef RTSEMMUTEX_STRICT + /** Lock validator record associated with this mutex. */ + RTLOCKVALRECEXCL ValidatorRec; +#endif +}; + + + +/** + * Wrapper for the futex syscall. + */ +static long sys_futex(int32_t volatile *uaddr, int op, int val, struct timespec *utime, int32_t *uaddr2, int val3) +{ + errno = 0; + long rc = syscall(__NR_futex, uaddr, op, val, utime, uaddr2, val3); + if (rc < 0) + { + Assert(rc == -1); + rc = -errno; + } + return rc; +} + + +#undef RTSemMutexCreate +RTDECL(int) RTSemMutexCreate(PRTSEMMUTEX phMutexSem) +{ + return RTSemMutexCreateEx(phMutexSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL); +} + + +RTDECL(int) RTSemMutexCreateEx(PRTSEMMUTEX phMutexSem, uint32_t fFlags, + RTLOCKVALCLASS hClass, uint32_t uSubClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMMUTEX_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Allocate semaphore handle. + */ + struct RTSEMMUTEXINTERNAL *pThis = (struct RTSEMMUTEXINTERNAL *)RTMemAlloc(sizeof(struct RTSEMMUTEXINTERNAL)); + if (pThis) + { + pThis->u32Magic = RTSEMMUTEX_MAGIC; + pThis->iState = 0; + pThis->Owner = (pthread_t)~0; + pThis->cNestings = 0; +#ifdef RTSEMMUTEX_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iMutexAnon = 0; + RTLockValidatorRecExclInit(&pThis->ValidatorRec, hClass, uSubClass, pThis, + !(fFlags & RTSEMMUTEX_FLAGS_NO_LOCK_VAL), + "RTSemMutex-%u", ASMAtomicIncU32(&s_iMutexAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecExclInitV(&pThis->ValidatorRec, hClass, uSubClass, pThis, + !(fFlags & RTSEMMUTEX_FLAGS_NO_LOCK_VAL), pszNameFmt, va); + va_end(va); + } +#else + RT_NOREF(hClass, uSubClass, pszNameFmt); +#endif + + *phMutexSem = pThis; + return VINF_SUCCESS; + } + + return VERR_NO_MEMORY; +} + + +RTDECL(int) RTSemMutexDestroy(RTSEMMUTEX hMutexSem) +{ + /* + * Validate input. + */ + if (hMutexSem == NIL_RTSEMMUTEX) + return VINF_SUCCESS; + struct RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertMsgReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, + ("hMutexSem=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + VERR_INVALID_HANDLE); + + /* + * Invalidate the semaphore and wake up anyone waiting on it. + */ + ASMAtomicWriteU32(&pThis->u32Magic, RTSEMMUTEX_MAGIC_DEAD); + if (ASMAtomicXchgS32(&pThis->iState, 0) > 0) + { + sys_futex(&pThis->iState, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + usleep(1000); + } + pThis->Owner = (pthread_t)~0; + pThis->cNestings = 0; +#ifdef RTSEMMUTEX_STRICT + RTLockValidatorRecExclDelete(&pThis->ValidatorRec); +#endif + + /* + * Free the semaphore memory and be gone. + */ + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTSemMutexSetSubClass(RTSEMMUTEX hMutexSem, uint32_t uSubClass) +{ +#ifdef RTSEMMUTEX_STRICT + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID); + + return RTLockValidatorRecExclSetSubClass(&pThis->ValidatorRec, uSubClass); +#else + RT_NOREF(hMutexSem, uSubClass); + return RTLOCKVAL_SUB_CLASS_INVALID; +#endif +} + + +DECL_FORCE_INLINE(int) rtSemMutexRequest(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, bool fAutoResume, PCRTLOCKVALSRCPOS pSrcPos) +{ + RT_NOREF(pSrcPos); + + /* + * Validate input. + */ + struct RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + + /* + * Check if nested request. + */ + pthread_t Self = pthread_self(); + if ( pThis->Owner == Self + && pThis->cNestings > 0) + { +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclRecursion(&pThis->ValidatorRec, pSrcPos); + if (RT_FAILURE(rc9)) + return rc9; +#endif + ASMAtomicIncU32(&pThis->cNestings); + return VINF_SUCCESS; + } + +#ifdef RTSEMMUTEX_STRICT + RTTHREAD hThreadSelf = RTThreadSelfAutoAdopt(); + if (cMillies) + { + int rc9 = RTLockValidatorRecExclCheckOrder(&pThis->ValidatorRec, hThreadSelf, pSrcPos, cMillies); + if (RT_FAILURE(rc9)) + return rc9; + } +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + + /* + * Convert timeout value. + */ + struct timespec ts; + struct timespec *pTimeout = NULL; + uint64_t u64End = 0; /* shut up gcc */ + if (cMillies != RT_INDEFINITE_WAIT) + { + ts.tv_sec = cMillies / 1000; + ts.tv_nsec = (cMillies % 1000) * UINT32_C(1000000); + u64End = RTTimeSystemNanoTS() + cMillies * UINT64_C(1000000); + pTimeout = &ts; + } + + /* + * Lock the mutex. + * Optimize for the uncontended case (makes 1-2 ns difference). + */ + if (RT_UNLIKELY(!ASMAtomicCmpXchgS32(&pThis->iState, 1, 0))) + { + for (;;) + { + int32_t iOld = ASMAtomicXchgS32(&pThis->iState, 2); + + /* + * Was the lock released in the meantime? This is unlikely (but possible) + */ + if (RT_UNLIKELY(iOld == 0)) + break; + + /* + * Go to sleep. + */ + if (pTimeout && ( pTimeout->tv_sec || pTimeout->tv_nsec )) + { +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclCheckBlocking(&pThis->ValidatorRec, hThreadSelf, pSrcPos, true, + cMillies, RTTHREADSTATE_MUTEX, true); + if (RT_FAILURE(rc9)) + return rc9; +#else + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_MUTEX, true); +#endif + } + + long rc = sys_futex(&pThis->iState, FUTEX_WAIT, 2, pTimeout, NULL, 0); + + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_MUTEX); + if (RT_UNLIKELY(pThis->u32Magic != RTSEMMUTEX_MAGIC)) + return VERR_SEM_DESTROYED; + + /* + * Act on the wakup code. + */ + if (rc == -ETIMEDOUT) + { + Assert(pTimeout); + return VERR_TIMEOUT; + } + if (rc == 0) + /* we'll leave the loop now unless another thread is faster */; + else if (rc == -EWOULDBLOCK) + /* retry with new value. */; + else if (rc == -EINTR) + { + if (!fAutoResume) + return VERR_INTERRUPTED; + } + else + { + /* this shouldn't happen! */ + AssertMsgFailed(("rc=%ld errno=%d\n", rc, errno)); + return RTErrConvertFromErrno(rc); + } + + /* adjust the relative timeout */ + if (pTimeout) + { + int64_t i64Diff = u64End - RTTimeSystemNanoTS(); + if (i64Diff < 1000) + { + rc = VERR_TIMEOUT; + break; + } + ts.tv_sec = (uint64_t)i64Diff / UINT32_C(1000000000); + ts.tv_nsec = (uint64_t)i64Diff % UINT32_C(1000000000); + } + } + + /* + * When leaving this loop, iState is set to 2. This means that we gained the + * lock and there are _possibly_ some waiters. We don't know exactly as another + * thread might entered this loop at nearly the same time. Therefore we will + * call futex_wakeup once too often (if _no_ other thread entered this loop). + * The key problem is the simple futex_wait test for x != y (iState != 2) in + * our case). + */ + } + + /* + * Set the owner and nesting. + */ + pThis->Owner = Self; + ASMAtomicWriteU32(&pThis->cNestings, 1); +#ifdef RTSEMMUTEX_STRICT + RTLockValidatorRecExclSetOwner(&pThis->ValidatorRec, hThreadSelf, pSrcPos, true); +#endif + return VINF_SUCCESS; +} + + +#undef RTSemMutexRequest +RTDECL(int) RTSemMutexRequest(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies) +{ +#ifndef RTSEMMUTEX_STRICT + int rc = rtSemMutexRequest(hMutexSem, cMillies, true, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + int rc = rtSemMutexRequest(hMutexSem, cMillies, true, &SrcPos); +#endif + Assert(rc != VERR_INTERRUPTED); + return rc; +} + + +RTDECL(int) RTSemMutexRequestDebug(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + int rc = rtSemMutexRequest(hMutexSem, cMillies, true, &SrcPos); + Assert(rc != VERR_INTERRUPTED); + return rc; +} + + +#undef RTSemMutexRequestNoResume +RTDECL(int) RTSemMutexRequestNoResume(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies) +{ +#ifndef RTSEMMUTEX_STRICT + return rtSemMutexRequest(hMutexSem, cMillies, false, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemMutexRequest(hMutexSem, cMillies, false, &SrcPos); +#endif +} + + +RTDECL(int) RTSemMutexRequestNoResumeDebug(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemMutexRequest(hMutexSem, cMillies, false, &SrcPos); +} + + +RTDECL(int) RTSemMutexRelease(RTSEMMUTEX hMutexSem) +{ + /* + * Validate input. + */ + struct RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclReleaseOwner(&pThis->ValidatorRec, pThis->cNestings == 1); + if (RT_FAILURE(rc9)) + return rc9; +#endif + + /* + * Check if nested. + */ + pthread_t Self = pthread_self(); + if (RT_UNLIKELY( pThis->Owner != Self + || pThis->cNestings == 0)) + { + AssertMsgFailed(("Not owner of mutex %p!! Self=%08x Owner=%08x cNestings=%d\n", + pThis, Self, pThis->Owner, pThis->cNestings)); + return VERR_NOT_OWNER; + } + + /* + * If nested we'll just pop a nesting. + */ + if (pThis->cNestings > 1) + { + ASMAtomicDecU32(&pThis->cNestings); + return VINF_SUCCESS; + } + + /* + * Clear the state. (cNestings == 1) + */ + pThis->Owner = (pthread_t)~0; + ASMAtomicWriteU32(&pThis->cNestings, 0); + + /* + * Release the mutex. + */ + int32_t iNew = ASMAtomicDecS32(&pThis->iState); + if (RT_UNLIKELY(iNew != 0)) + { + /* somebody is waiting, try wake up one of them. */ + ASMAtomicXchgS32(&pThis->iState, 0); + (void)sys_futex(&pThis->iState, FUTEX_WAKE, 1, NULL, NULL, 0); + } + return VINF_SUCCESS; +} + + +RTDECL(bool) RTSemMutexIsOwned(RTSEMMUTEX hMutexSem) +{ + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, false); + + return pThis->Owner != (pthread_t)~0; +} + diff --git a/src/VBox/Runtime/r3/linux/semwait-linux.h b/src/VBox/Runtime/r3/linux/semwait-linux.h new file mode 100644 index 00000000..0f533845 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/semwait-linux.h @@ -0,0 +1,233 @@ +/* $Id: semwait-linux.h $ */ +/** @file + * IPRT - Common semaphore wait code, Linux. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_linux_semwait_linux_h +#define IPRT_INCLUDED_SRC_r3_linux_semwait_linux_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +/* With 2.6.17 futex.h has become C++ unfriendly, so define the bits we need. */ +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_WAIT_BITSET 9 /**< @since 2.6.25 - uses absolute timeout. */ + + +/** + * Wrapper for the futex syscall. + */ +DECLINLINE(long) sys_futex(uint32_t volatile *uaddr, int op, int val, struct timespec *utime, int32_t *uaddr2, int val3) +{ + errno = 0; + long rc = syscall(__NR_futex, uaddr, op, val, utime, uaddr2, val3); + if (rc < 0) + { + Assert(rc == -1); + rc = -errno; + } + return rc; +} + + +DECL_NO_INLINE(static, void) rtSemLinuxCheckForFutexWaitBitSetSlow(int volatile *pfCanUseWaitBitSet) +{ + uint32_t uTestVar = UINT32_MAX; + long rc = sys_futex(&uTestVar, FUTEX_WAIT_BITSET, UINT32_C(0xf0f0f0f0), NULL, NULL, UINT32_MAX); + *pfCanUseWaitBitSet = rc == -EAGAIN; + AssertMsg(rc == -ENOSYS || rc == -EAGAIN, ("%d\n", rc)); +} + + +DECLINLINE(void) rtSemLinuxCheckForFutexWaitBitSet(int volatile *pfCanUseWaitBitSet) +{ + if (*pfCanUseWaitBitSet != -1) + { /* likely */ } + else + rtSemLinuxCheckForFutexWaitBitSetSlow(pfCanUseWaitBitSet); +} + + +/** + * Converts a extended wait timeout specification to an timespec and + * corresponding futex operation, as well as an approximate relative nanosecond + * interval. + * + * @note This does not check for RTSEMWAIT_FLAGS_INDEFINITE, caller should've + * done that already. + * + * @returns The relative wait in nanoseconds. 0 for a poll call, UINT64_MAX for + * an effectively indefinite wait. + * @param fFlags RTSEMWAIT_FLAGS_XXX. + * @param fCanUseWaitBitSet Whether we can use FUTEX_WAIT_BITMSET or not. + * @param uTimeout The timeout. + * @param pDeadline Where to return the deadline. + * @param piWaitOp Where to return the FUTEX wait operation number. + * @param puWaitVal3 Where to return the FUTEX wait value 3. + * @param pnsAbsTimeout Where to return the absolute timeout in case of + * a resuming relative call (i.e. FUTEX_WAIT). + */ +DECL_FORCE_INLINE(uint64_t) +rtSemLinuxCalcDeadline(uint32_t fFlags, uint64_t uTimeout, int fCanUseWaitBitSet, + struct timespec *pDeadline, int *piWaitOp, uint32_t *puWaitVal3, uint64_t *pnsAbsTimeout) +{ + Assert(!(fFlags & RTSEMWAIT_FLAGS_INDEFINITE)); + + if (fFlags & RTSEMWAIT_FLAGS_RELATIVE) + { + Assert(!(fFlags & RTSEMWAIT_FLAGS_ABSOLUTE)); + + /* + * Polling call? + */ + if (uTimeout == 0) + return 0; + + /* + * We use FUTEX_WAIT here as it takes a relative timespec. + * + * Note! For non-resuming waits, we can skip calculating the absolute + * time ASSUMING it is only needed for timeout adjustments + * after an -EINTR return. + */ + if (fFlags & RTSEMWAIT_FLAGS_MILLISECS) + { + if ( sizeof(pDeadline->tv_sec) >= sizeof(uint64_t) + || uTimeout < (uint64_t)UINT32_MAX * RT_MS_1SEC) + { + pDeadline->tv_sec = uTimeout / RT_MS_1SEC; + pDeadline->tv_nsec = (uTimeout % RT_MS_1SEC) & RT_NS_1MS; + uTimeout *= RT_NS_1MS; + } + else + return UINT64_MAX; + } + else + { + Assert(fFlags & RTSEMWAIT_FLAGS_NANOSECS); + if ( sizeof(pDeadline->tv_sec) >= sizeof(uint64_t) + || uTimeout < (uint64_t)UINT32_MAX * RT_NS_1SEC) + { + pDeadline->tv_sec = uTimeout / RT_NS_1SEC; + pDeadline->tv_nsec = uTimeout % RT_NS_1SEC; + } + else + return UINT64_MAX; + } + +#ifdef RT_STRICT + if (!(fFlags & RTSEMWAIT_FLAGS_RESUME)) + *pnsAbsTimeout = uTimeout; + else +#endif + *pnsAbsTimeout = RTTimeNanoTS() + uTimeout; /* Note! only relevant for relative waits (FUTEX_WAIT). */ + } + else + { + /* Absolute deadline: */ + Assert(fFlags & RTSEMWAIT_FLAGS_ABSOLUTE); + if (fCanUseWaitBitSet == true) + { + /* + * Use FUTEX_WAIT_BITSET as it takes an absolute deadline. + */ + if (fFlags & RTSEMWAIT_FLAGS_MILLISECS) + { + if ( sizeof(pDeadline->tv_sec) >= sizeof(uint64_t) + || uTimeout < (uint64_t)UINT32_MAX * RT_MS_1SEC) + { + pDeadline->tv_sec = uTimeout / RT_MS_1SEC; + pDeadline->tv_nsec = (uTimeout % RT_MS_1SEC) & RT_NS_1MS; + } + else + return UINT64_MAX; + } + else + { + Assert(fFlags & RTSEMWAIT_FLAGS_NANOSECS); + if ( sizeof(pDeadline->tv_sec) >= sizeof(uint64_t) + || uTimeout < (uint64_t)UINT32_MAX * RT_NS_1SEC) + { + pDeadline->tv_sec = uTimeout / RT_NS_1SEC; + pDeadline->tv_nsec = uTimeout % RT_NS_1SEC; + } + else + return UINT64_MAX; + } + *pnsAbsTimeout = uTimeout; + *piWaitOp = FUTEX_WAIT_BITSET; + *puWaitVal3 = UINT32_MAX; + return RT_MS_1SEC; /* Whatever non-zero; Whole point is not calling RTTimeNanoTS() in this path. */ + } + + /* + * FUTEX_WAIT_BITSET is not available, so use FUTEX_WAIT with a + * relative timeout. + */ + if (fFlags & RTSEMWAIT_FLAGS_MILLISECS) + { + if (uTimeout < UINT64_MAX / RT_NS_1MS) + uTimeout *= RT_NS_1MS; + else + return UINT64_MAX; + } + + uint64_t const u64Now = RTTimeNanoTS(); + if (u64Now < uTimeout) + { + *pnsAbsTimeout = uTimeout; + uTimeout -= u64Now; + } + else + return 0; + + if ( sizeof(pDeadline->tv_sec) >= sizeof(uint64_t) + || uTimeout < (uint64_t)UINT32_MAX * RT_NS_1SEC) + { + pDeadline->tv_sec = uTimeout / RT_NS_1SEC; + pDeadline->tv_nsec = uTimeout % RT_NS_1SEC; + } + else + return UINT64_MAX; + } + + *piWaitOp = FUTEX_WAIT; + *puWaitVal3 = 0; + return uTimeout; +} + +#endif /* !IPRT_INCLUDED_SRC_r3_linux_semwait_linux_h */ + diff --git a/src/VBox/Runtime/r3/linux/sysfs.cpp b/src/VBox/Runtime/r3/linux/sysfs.cpp new file mode 100644 index 00000000..6324fe00 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/sysfs.cpp @@ -0,0 +1,736 @@ +/* $Id: sysfs.cpp $ */ +/** @file + * IPRT - Linux sysfs access. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/fs.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/symlink.h> + +#include <iprt/linux/sysfs.h> + +#include <unistd.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <sys/sysmacros.h> +#include <errno.h> + + + +/** + * Constructs the path of a sysfs file from the format parameters passed, + * prepending a prefix if the path is relative. + * + * @returns IPRT status code. + * @param pszPrefix The prefix to prepend if the path is relative. Must end + * in '/'. + * @param pszBuf Where to write the path. Must be at least + * sizeof(@a pszPrefix) characters long + * @param cchBuf The size of the buffer pointed to by @a pszBuf. + * @param pszFormat The name format, either absolute or relative to the + * prefix specified by @a pszPrefix. + * @param va The format args. + */ +static int rtLinuxConstructPathV(char *pszBuf, size_t cchBuf, + const char *pszPrefix, + const char *pszFormat, va_list va) +{ + size_t const cchPrefix = strlen(pszPrefix); + AssertReturn(pszPrefix[cchPrefix - 1] == '/', VERR_INVALID_PARAMETER); + AssertReturn(cchBuf > cchPrefix + 1, VERR_INVALID_PARAMETER); + + ssize_t cch = RTStrPrintf2V(pszBuf, cchBuf, pszFormat, va); + AssertReturn(cch >= 0, VERR_BUFFER_OVERFLOW); + + if (*pszBuf != '/') + { + AssertReturn(cchBuf >= (size_t)cch + cchPrefix + 1, VERR_BUFFER_OVERFLOW); + memmove(pszBuf + cchPrefix, pszBuf, (size_t)cch + 1); + memcpy(pszBuf, pszPrefix, cchPrefix); + } + return VINF_SUCCESS; +} + + +/** + * Constructs the path of a sysfs file from the format parameters passed, + * prepending a prefix if the path is relative. + * + * @returns IPRT status code. + * @param pszPrefix The prefix to prepend if the path is relative. Must end + * in '/'. + * @param pszBuf Where to write the path. Must be at least + * sizeof(@a pszPrefix) characters long + * @param cchBuf The size of the buffer pointed to by @a pszBuf. + * @param pszFormat The name format, either absolute or relative to "/sys/". + * @param ... The format args. + */ +DECLINLINE(int) rtLinuxConstructPath(char *pszBuf, size_t cchBuf, + const char *pszPrefix, + const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = rtLinuxConstructPathV(pszBuf, cchBuf, pszPrefix, pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Constructs the path of a sysfs file from the format parameters passed, + * prepending "/sys/" if the path is relative. + * + * @returns IPRT status code. + * @param pszBuf Where to write the path. Must be at least + * sizeof("/sys/") characters long + * @param cchBuf The size of the buffer pointed to by @a pszBuf. + * @param pszFormat The name format, either absolute or relative to "/sys/". + * @param va The format args. + */ +DECLINLINE(int) rtLinuxSysFsConstructPath(char *pszBuf, size_t cchBuf, const char *pszFormat, va_list va) +{ + return rtLinuxConstructPathV(pszBuf, cchBuf, "/sys/", pszFormat, va); +} + + +RTDECL(int) RTLinuxConstructPathV(char *pszPath, size_t cbPath, const char *pszFormat, va_list va) +{ + return rtLinuxSysFsConstructPath(pszPath, cbPath, pszFormat, va); +} + + +RTDECL(int) RTLinuxConstructPath(char *pszPath, size_t cbPath, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = rtLinuxSysFsConstructPath(pszPath, cbPath, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsExistsExV(const char *pszFormat, va_list va) +{ + int iSavedErrno = errno; + + /* + * Construct the filename and call stat. + */ + char szFilename[RTPATH_MAX]; + int rc = rtLinuxSysFsConstructPath(szFilename, sizeof(szFilename), pszFormat, va); + if (RT_SUCCESS(rc)) + { + struct stat st; + int rcStat = stat(szFilename, &st); + if (rcStat != 0) + rc = RTErrConvertFromErrno(errno); + } + + errno = iSavedErrno; + return rc; +} + + +RTDECL(bool) RTLinuxSysFsExistsV(const char *pszFormat, va_list va) +{ + return RT_SUCCESS(RTLinuxSysFsExistsExV(pszFormat, va)); +} + + +RTDECL(int) RTLinuxSysFsExistsEx(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsExistsExV(pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(bool) RTLinuxSysFsExists(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + bool fRet = RTLinuxSysFsExistsV(pszFormat, va); + va_end(va); + return fRet; +} + + +RTDECL(int) RTLinuxSysFsOpenV(PRTFILE phFile, const char *pszFormat, va_list va) +{ + /* + * Construct the filename and call open. + */ + char szFilename[RTPATH_MAX]; + int rc = rtLinuxSysFsConstructPath(szFilename, sizeof(szFilename), pszFormat, va); + if (RT_SUCCESS(rc)) + rc = RTFileOpen(phFile, szFilename, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + return rc; +} + + +RTDECL(int) RTLinuxSysFsOpenExV(PRTFILE phFile, uint64_t fOpen, const char *pszFormat, va_list va) +{ + /* + * Construct the filename and call open. + */ + char szFilename[RTPATH_MAX]; + int rc = rtLinuxSysFsConstructPath(szFilename, sizeof(szFilename), pszFormat, va); + if (RT_SUCCESS(rc)) + rc = RTFileOpen(phFile, szFilename, fOpen); + return rc; +} + + +RTDECL(int) RTLinuxSysFsOpen(PRTFILE phFile, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsOpenV(phFile, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsOpenEx(PRTFILE phFile, uint64_t fOpen, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsOpenExV(phFile, fOpen, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsReadStr(RTFILE hFile, char *pszBuf, size_t cchBuf, size_t *pcchRead) +{ + Assert(cchBuf > 1); /* not mandatory */ + + int rc; + size_t cchRead; + rc = RTFileRead(hFile, pszBuf, cchBuf, &cchRead); + if (RT_SUCCESS(rc)) + { + /* + * ASSUME that if we've read less than we asked for, we've reached the + * end of the file. Otherwise, we've been given a buffer too small for + * the entire remainder of the file. + */ + if (cchRead < cchBuf) + pszBuf[cchRead] = '\0'; + else if (cchBuf) + { + rc = RTFileSeek(hFile, -1, RTFILE_SEEK_CURRENT, NULL); + if (RT_SUCCESS(rc)) + rc = VERR_BUFFER_OVERFLOW; + cchRead = cchBuf - 1; + pszBuf[cchRead] = '\0'; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + else + { + if (cchBuf > 0) + *pszBuf = '\0'; + cchRead = 0; + } + + if (pcchRead) + *pcchRead = cchRead; + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteStr(RTFILE hFile, const char *pszBuf, size_t cchBuf, size_t *pcchWritten) +{ + if (!cchBuf) + cchBuf = strlen(pszBuf) + 1; /* Include the terminator */ + return RTFileWrite(hFile, pszBuf, cchBuf, pcchWritten); +} + + +RTDECL(int) RTLinuxSysFsReadFile(RTFILE hFile, void *pvBuf, size_t cbBuf, size_t *pcbRead) +{ + int rc; + size_t cbRead = 0; + + rc = RTFileRead(hFile, pvBuf, cbBuf, &cbRead); + if (RT_SUCCESS(rc)) + { + if (pcbRead) + *pcbRead = cbRead; + if (cbRead < cbBuf) + rc = VINF_SUCCESS; + else + { + /* Check for EOF */ + uint64_t offCur = 0; + uint8_t bRead; + rc = RTFileSeek(hFile, 0, RTFILE_SEEK_CURRENT, &offCur); + if (RT_SUCCESS(rc)) + { + int rc2 = RTFileRead(hFile, &bRead, 1, NULL); + if (RT_SUCCESS(rc2)) + { + rc = VERR_BUFFER_OVERFLOW; + + rc2 = RTFileSeek(hFile, offCur, RTFILE_SEEK_BEGIN, NULL); + if (RT_FAILURE(rc2)) + rc = rc2; + } + else if (rc2 != VERR_EOF) + rc = rc2; + } + } + } + + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteFile(RTFILE hFile, void *pvBuf, size_t cbBuf, size_t *pcbWritten) +{ + return RTFileWrite(hFile, pvBuf, cbBuf, pcbWritten); +} + + +RTDECL(int) RTLinuxSysFsReadIntFileV(unsigned uBase, int64_t *pi64, const char *pszFormat, va_list va) +{ + RTFILE hFile; + + AssertPtrReturn(pi64, VERR_INVALID_POINTER); + + int rc = RTLinuxSysFsOpenV(&hFile, pszFormat, va); + if (RT_SUCCESS(rc)) + { + char szNum[128]; + size_t cchNum; + rc = RTLinuxSysFsReadStr(hFile, szNum, sizeof(szNum), &cchNum); + if (RT_SUCCESS(rc)) + { + if (cchNum > 0) + { + int64_t i64Ret = -1; + rc = RTStrToInt64Ex(szNum, NULL, uBase, &i64Ret); + if (RT_SUCCESS(rc)) + *pi64 = i64Ret; + } + else + rc = VERR_INVALID_PARAMETER; + } + + RTFileClose(hFile); + } + + return rc; +} + + +RTDECL(int) RTLinuxSysFsReadIntFile(unsigned uBase, int64_t *pi64, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsReadIntFileV(uBase, pi64, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteU8FileV(unsigned uBase, uint8_t u8, const char *pszFormat, va_list va) +{ + return RTLinuxSysFsWriteU64FileV(uBase, u8, pszFormat, va); +} + + +RTDECL(int) RTLinuxSysFsWriteU8File(unsigned uBase, uint8_t u8, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsWriteU64FileV(uBase, u8, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteU16FileV(unsigned uBase, uint16_t u16, const char *pszFormat, va_list va) +{ + return RTLinuxSysFsWriteU64FileV(uBase, u16, pszFormat, va); +} + + +RTDECL(int) RTLinuxSysFsWriteU16File(unsigned uBase, uint16_t u16, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsWriteU64FileV(uBase, u16, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteU32FileV(unsigned uBase, uint32_t u32, const char *pszFormat, va_list va) +{ + return RTLinuxSysFsWriteU64FileV(uBase, u32, pszFormat, va); +} + + +RTDECL(int) RTLinuxSysFsWriteU32File(unsigned uBase, uint32_t u32, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsWriteU64FileV(uBase, u32, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteU64FileV(unsigned uBase, uint64_t u64, const char *pszFormat, va_list va) +{ + RTFILE hFile; + + const char *pszFmt = NULL; + switch (uBase) + { + case 8: + pszFmt = "%#llo"; + break; + case 10: + pszFmt = "%llu"; + break; + case 16: + pszFmt = "%#llx"; + break; + default: + return VERR_INVALID_PARAMETER; + } + + int rc = RTLinuxSysFsOpenExV(&hFile, RTFILE_O_OPEN | RTFILE_O_WRITE | RTFILE_O_DENY_NONE, pszFormat, va); + if (RT_SUCCESS(rc)) + { + char szNum[128]; + size_t cchNum = RTStrPrintf(szNum, sizeof(szNum), pszFmt, u64); + if (cchNum > 0) + { + size_t cbWritten = 0; + rc = RTLinuxSysFsWriteStr(hFile, &szNum[0], cchNum, &cbWritten); + if ( RT_SUCCESS(rc) + && cbWritten != cchNum) + rc = VERR_BUFFER_OVERFLOW; + } + else + rc = VERR_INVALID_PARAMETER; + + RTFileClose(hFile); + } + + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteU64File(unsigned uBase, uint32_t u64, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsWriteU64FileV(uBase, u64, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsReadDevNumFileV(dev_t *pDevNum, const char *pszFormat, va_list va) +{ + RTFILE hFile; + + AssertPtrReturn(pDevNum, VERR_INVALID_POINTER); + + int rc = RTLinuxSysFsOpenV(&hFile, pszFormat, va); + if (RT_SUCCESS(rc)) + { + size_t cchNum = 0; + char szNum[128]; + rc = RTLinuxSysFsReadStr(hFile, szNum, sizeof(szNum), &cchNum); + if (RT_SUCCESS(rc)) + { + if (cchNum > 0) + { + uint32_t u32Maj = 0; + uint32_t u32Min = 0; + char *pszNext = NULL; + rc = RTStrToUInt32Ex(szNum, &pszNext, 10, &u32Maj); + if (RT_FAILURE(rc) || (rc != VWRN_TRAILING_CHARS) || (*pszNext != ':')) + rc = VERR_INVALID_PARAMETER; + else + { + rc = RTStrToUInt32Ex(pszNext + 1, NULL, 10, &u32Min); + if ( rc != VINF_SUCCESS + && rc != VWRN_TRAILING_CHARS + && rc != VWRN_TRAILING_SPACES) + rc = VERR_INVALID_PARAMETER; + else + *pDevNum = makedev(u32Maj, u32Min); + } + } + else + rc = VERR_INVALID_PARAMETER; + } + + RTFileClose(hFile); + } + + return rc; +} + + +RTDECL(int) RTLinuxSysFsReadDevNumFile(dev_t *pDevNum, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsReadDevNumFileV(pDevNum, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsReadStrFileV(char *pszBuf, size_t cchBuf, size_t *pcchRead, const char *pszFormat, va_list va) +{ + RTFILE hFile; + + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + + int rc = RTLinuxSysFsOpenV(&hFile, pszFormat, va); + if (RT_SUCCESS(rc)) + { + /* + * Note! We cannot use RTLinuxSysFsReadStr here as it has different + * semantics wrt to newline characters. It is not known why + * the semantics has to differ... Michael, any clues? + */ + size_t cchRead; + rc = RTFileRead(hFile, pszBuf, cchBuf, &cchRead); + if (RT_SUCCESS(rc)) + { + char *pchNewLine = (char *)memchr(pszBuf, '\n', cchRead); + if (pchNewLine) + { + *pchNewLine = '\0'; + cchRead = pchNewLine - pszBuf; + } + else if (cchRead < cchBuf) + pszBuf[cchRead] = '\0'; + else + { + if (cchBuf) + { + cchRead = cchBuf - 1; + pszBuf[cchRead] = '\0'; + } + else + cchRead = 0; + rc = VERR_BUFFER_OVERFLOW; + } + } + else + cchRead = 0; + + RTFileClose(hFile); + + if (pcchRead) + *pcchRead = cchRead; + } + else + { + if (cchBuf) + *pszBuf = '\0'; + if (pcchRead) + *pcchRead = 0; + } + return rc; +} + + +RTDECL(int) RTLinuxSysFsReadStrFile(char *pszBuf, size_t cchBuf, size_t *pcchRead, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsReadStrFileV(pszBuf, cchBuf, pcchRead, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteStrFileV(const char *pszBuf, size_t cchBuf, size_t *pcchWritten, const char *pszFormat, va_list va) +{ + RTFILE hFile; + + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + + int rc = RTLinuxSysFsOpenExV(&hFile, RTFILE_O_OPEN | RTFILE_O_WRITE | RTFILE_O_DENY_NONE, pszFormat, va); + if (RT_SUCCESS(rc)) + { + rc = RTLinuxSysFsWriteStr(hFile, pszBuf, cchBuf, pcchWritten); + RTFileClose(hFile); + } + return rc; +} + + +RTDECL(int) RTLinuxSysFsWriteStrFile(const char *pszBuf, size_t cchBuf, size_t *pcchWritten, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsWriteStrFileV(pszBuf, cchBuf, pcchWritten, pszFormat, va); + va_end(va); + return rc; +} + +RTDECL(int) RTLinuxSysFsGetLinkDestV(char *pszBuf, size_t cchBuf, size_t *pchBuf, const char *pszFormat, va_list va) +{ + AssertReturn(cchBuf >= 2, VERR_INVALID_PARAMETER); + + /* + * Construct the filename and read the link. + */ + char szFilename[RTPATH_MAX]; + int rc = rtLinuxSysFsConstructPath(szFilename, sizeof(szFilename), pszFormat, va); + if (RT_SUCCESS(rc)) + { + char szLink[RTPATH_MAX]; + rc = RTSymlinkRead(szFilename, szLink, sizeof(szLink), 0); + if (RT_SUCCESS(rc)) + { + /* + * Extract the file name component and copy it into the return buffer. + */ + size_t cchName; + const char *pszName = RTPathFilename(szLink); + if (pszName) + { + cchName = strlen(pszName); + if (cchName < cchBuf) + memcpy(pszBuf, pszName, cchName + 1); + else + rc = VERR_BUFFER_OVERFLOW; + } + else + { + *pszBuf = '\0'; + cchName = 0; + } + + if (pchBuf) + *pchBuf = cchName; + } + } + + return rc; +} + + +RTDECL(int) RTLinuxSysFsGetLinkDest(char *pszBuf, size_t cchBuf, size_t *pchBuf, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTLinuxSysFsGetLinkDestV(pszBuf, cchBuf, pchBuf, pszFormat, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLinuxCheckDevicePathV(dev_t DevNum, RTFMODE fMode, char *pszBuf, + size_t cchBuf, const char *pszPattern, + va_list va) +{ + AssertReturn(cchBuf >= 2, VERR_INVALID_PARAMETER); + AssertReturn( fMode == RTFS_TYPE_DEV_CHAR + || fMode == RTFS_TYPE_DEV_BLOCK, + VERR_INVALID_PARAMETER); + AssertPtrReturn(pszPattern, VERR_INVALID_PARAMETER); + + /* + * Construct the filename and read the link. + */ + char szFilename[RTPATH_MAX]; + int rc = rtLinuxConstructPathV(szFilename, sizeof(szFilename), "/dev/", + pszPattern, va); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO Info; + rc = RTPathQueryInfo(szFilename, &Info, RTFSOBJATTRADD_UNIX); + if ( rc == VERR_PATH_NOT_FOUND + || ( RT_SUCCESS(rc) + && ( Info.Attr.u.Unix.Device != DevNum + || (Info.Attr.fMode & RTFS_TYPE_MASK) != fMode))) + rc = VERR_FILE_NOT_FOUND; + + if (RT_SUCCESS(rc)) + { + size_t cchPath = strlen(szFilename); + if (cchPath < cchBuf) + memcpy(pszBuf, szFilename, cchPath + 1); + else + rc = VERR_BUFFER_OVERFLOW; + } + } + + return rc; +} + + +RTDECL(int) RTLinuxCheckDevicePath(dev_t DevNum, RTFMODE fMode, char *pszBuf, + size_t cchBuf, const char *pszPattern, + ...) +{ + va_list va; + va_start(va, pszPattern); + int rc = RTLinuxCheckDevicePathV(DevNum, fMode, pszBuf, cchBuf, + pszPattern, va); + va_end(va); + return rc; +} + diff --git a/src/VBox/Runtime/r3/linux/systemmem-linux.cpp b/src/VBox/Runtime/r3/linux/systemmem-linux.cpp new file mode 100644 index 00000000..11764ab7 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/systemmem-linux.cpp @@ -0,0 +1,119 @@ +/* $Id: systemmem-linux.cpp $ */ +/** @file + * IPRT - RTSystemQueryTotalRam, Linux ring-3. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> + +#include <stdio.h> +#include <errno.h> + +/* Satisfy compiller warning */ +#define __EXPORTED_HEADERS__ +#include <sys/sysinfo.h> +#undef __EXPORTED_HEADERS__ + + +RTDECL(int) RTSystemQueryTotalRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + struct sysinfo info; + int rc = sysinfo(&info); + if (rc == 0) + { + *pcb = (uint64_t)info.totalram * info.mem_unit; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTSystemQueryAvailableRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + FILE *pFile = fopen("/proc/meminfo", "r"); + if (pFile) + { + int rc = VERR_NOT_FOUND; + uint64_t cbTotal = 0; + uint64_t cbFree = 0; + uint64_t cbBuffers = 0; + uint64_t cbCached = 0; + char sz[256]; + while (fgets(sz, sizeof(sz), pFile)) + { + if (!strncmp(sz, RT_STR_TUPLE("MemTotal:"))) + rc = RTStrToUInt64Ex(RTStrStripL(&sz[sizeof("MemTotal:")]), NULL, 0, &cbTotal); + else if (!strncmp(sz, RT_STR_TUPLE("MemFree:"))) + rc = RTStrToUInt64Ex(RTStrStripL(&sz[sizeof("MemFree:")]), NULL, 0, &cbFree); + else if (!strncmp(sz, RT_STR_TUPLE("Buffers:"))) + rc = RTStrToUInt64Ex(RTStrStripL(&sz[sizeof("Buffers:")]), NULL, 0, &cbBuffers); + else if (!strncmp(sz, RT_STR_TUPLE("Cached:"))) + rc = RTStrToUInt64Ex(RTStrStripL(&sz[sizeof("Cached:")]), NULL, 0, &cbCached); + if (RT_FAILURE(rc)) + break; + } + fclose(pFile); + if (RT_SUCCESS(rc)) + { + *pcb = (cbFree + cbBuffers + cbCached) * _1K; + return VINF_SUCCESS; + } + } + /* + * Fallback (e.g. /proc not mapped) to sysinfo. Less accurat because there + * is no information about the cached memory. 'Cached:' from above is only + * accessible through proc :-( + */ + struct sysinfo info; + int rc = sysinfo(&info); + if (rc == 0) + { + *pcb = ((uint64_t)info.freeram + info.bufferram) * info.mem_unit; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + diff --git a/src/VBox/Runtime/r3/linux/thread-affinity-linux.cpp b/src/VBox/Runtime/r3/linux/thread-affinity-linux.cpp new file mode 100644 index 00000000..2726e716 --- /dev/null +++ b/src/VBox/Runtime/r3/linux/thread-affinity-linux.cpp @@ -0,0 +1,105 @@ +/* $Id: thread-affinity-linux.cpp $ */ +/** @file + * IPRT - Thread Affinity, Linux ring-3 implementation. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include <features.h> +#if __GLIBC_PREREQ(2,4) + +#include <sched.h> +#include <unistd.h> +#include <errno.h> +#include <pthread.h> + +#include <iprt/thread.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/cpuset.h> +#include <iprt/err.h> +#include <iprt/mp.h> + + + +RTR3DECL(int) RTThreadSetAffinity(PCRTCPUSET pCpuSet) +{ + /* convert */ + cpu_set_t LnxCpuSet; + CPU_ZERO(&LnxCpuSet); + if (!pCpuSet) + for (unsigned iCpu = 0; iCpu < CPU_SETSIZE; iCpu++) + CPU_SET(iCpu, &LnxCpuSet); + else + for (unsigned iCpu = 0; iCpu < RT_MIN(CPU_SETSIZE, RTCPUSET_MAX_CPUS); iCpu++) + if (RTCpuSetIsMemberByIndex(pCpuSet, iCpu)) + CPU_SET(iCpu, &LnxCpuSet); + + int rc = pthread_setaffinity_np(pthread_self(), sizeof(LnxCpuSet), &LnxCpuSet); + if (!rc) + return VINF_SUCCESS; + rc = errno; + if (rc == ENOENT) + return VERR_CPU_NOT_FOUND; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTThreadGetAffinity(PRTCPUSET pCpuSet) +{ + cpu_set_t LnxCpuSet; + int rc = pthread_getaffinity_np(pthread_self(), sizeof(LnxCpuSet), &LnxCpuSet); + if (rc != 0) + return RTErrConvertFromErrno(errno); + + /* convert */ + RTCpuSetEmpty(pCpuSet); + for (unsigned iCpu = 0; iCpu < RT_MIN(CPU_SETSIZE, RTCPUSET_MAX_CPUS); iCpu++) + if (CPU_ISSET(iCpu, &LnxCpuSet)) + RTCpuSetAddByIndex(pCpuSet, iCpu); + + return VINF_SUCCESS; +} + +#else +# include "../../generic/RTThreadGetAffinity-stub-generic.cpp" +# include "../../generic/RTThreadSetAffinity-stub-generic.cpp" +#endif + diff --git a/src/VBox/Runtime/r3/linux/time-linux.cpp b/src/VBox/Runtime/r3/linux/time-linux.cpp new file mode 100644 index 00000000..6ceac2de --- /dev/null +++ b/src/VBox/Runtime/r3/linux/time-linux.cpp @@ -0,0 +1,169 @@ +/* $Id: time-linux.cpp $ */ +/** @file + * IPRT - Time, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define RTTIME_INCL_TIMEVAL +#include <sys/time.h> +#include <time.h> +#include <sys/syscall.h> +#include <unistd.h> +#ifndef __NR_clock_gettime +# define __NR_timer_create 259 +# define __NR_clock_gettime (__NR_timer_create+6) +#endif + +#include <iprt/time.h> +#include "internal/time.h" + + +DECLINLINE(int) sys_clock_gettime(clockid_t id, struct timespec *ts) +{ + int rc = syscall(__NR_clock_gettime, id, ts); + if (rc >= 0) + return rc; + return -1; +} + + +/** + * Wrapper around various monotone time sources. + */ +DECLINLINE(int) mono_clock(struct timespec *ts) +{ + static int iWorking = -1; + switch (iWorking) + { +#ifdef CLOCK_MONOTONIC + /* + * Standard clock_gettime() + */ + case 0: + return clock_gettime(CLOCK_MONOTONIC, ts); + + /* + * Syscall clock_gettime(). + */ + case 1: + return sys_clock_gettime(CLOCK_MONOTONIC, ts); + +#endif /* CLOCK_MONOTONIC */ + + + /* + * Figure out what's working. + */ + case -1: + { +#ifdef CLOCK_MONOTONIC + /* + * Real-Time API. + */ + int rc = clock_gettime(CLOCK_MONOTONIC, ts); + if (!rc) + { + iWorking = 0; + return 0; + } + + rc = sys_clock_gettime(CLOCK_MONOTONIC, ts); + if (!rc) + { + iWorking = 1; + return 0; + } +#endif /* CLOCK_MONOTONIC */ + + /* give up */ + iWorking = -2; + break; + } + } + return -1; +} + + +DECLINLINE(uint64_t) rtTimeGetSystemNanoTS(void) +{ + /* check monotonic clock first. */ + static bool fMonoClock = true; + if (fMonoClock) + { + struct timespec ts; + if (!mono_clock(&ts)) + return (uint64_t)ts.tv_sec * RT_NS_1SEC_64 + + ts.tv_nsec; + fMonoClock = false; + } + + /* fallback to gettimeofday(). */ + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * RT_NS_1SEC_64 + + (uint64_t)(tv.tv_usec * RT_NS_1US); +} + + +/** + * Gets the current nanosecond timestamp. + * + * This differs from RTTimeNanoTS in that it will use system APIs and not do any + * resolution or performance optimizations. + * + * @returns nanosecond timestamp. + */ +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return rtTimeGetSystemNanoTS(); +} + + +/** + * Gets the current millisecond timestamp. + * + * This differs from RTTimeNanoTS in that it will use system APIs and not do any + * resolution or performance optimizations. + * + * @returns millisecond timestamp. + */ +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return rtTimeGetSystemNanoTS() / RT_NS_1MS; +} + diff --git a/src/VBox/Runtime/r3/linux/tpm-linux.cpp b/src/VBox/Runtime/r3/linux/tpm-linux.cpp new file mode 100644 index 00000000..4851eabc --- /dev/null +++ b/src/VBox/Runtime/r3/linux/tpm-linux.cpp @@ -0,0 +1,229 @@ +/* $Id: tpm-linux.cpp $ */ +/** @file + * IPRT - Trusted Platform Module (TPM) access, Linux variant. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/tpm.h> + +#include <iprt/assertcompile.h> +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/linux/sysfs.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal TPM instance data. + */ +typedef struct RTTPMINT +{ + /** Handle to the /dev/tpmX device. */ + RTFILE hTpmDev; + /** Handle to the sysfs cancel interface. */ + RTFILE hTpmCancel; + /** The deduced TPM version. */ + RTTPMVERSION enmTpmVers; + /** Flag whether a request is currently being executed. */ + volatile bool fReqExec; +} RTTPMINT; +/** Pointer to the internal TPM instance data. */ +typedef RTTPMINT *PRTTPMINT; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +RTDECL(int) RTTpmOpen(PRTTPM phTpm, uint32_t idTpm) +{ + AssertPtrReturn(phTpm, VERR_INVALID_POINTER); + if (idTpm == RTTPM_ID_DEFAULT) + idTpm = 0; + + int rc = VINF_SUCCESS; + PRTTPMINT pThis = (PRTTPMINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->hTpmDev = NIL_RTFILE; + pThis->hTpmCancel = NIL_RTFILE; + pThis->enmTpmVers = RTTPMVERSION_UNKNOWN; + pThis->fReqExec = false; + + rc = RTFileOpenF(&pThis->hTpmDev, RTFILE_O_OPEN | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE, + "/dev/tpm%u", idTpm); + if (RT_SUCCESS(rc)) + { + /* Open the sysfs path to cancel a request, either /sys/class/tpm/tpmX/device/cancel or /sys/class/misc/tpmX/device/cancel. */ + rc = RTFileOpenF(&pThis->hTpmCancel, RTFILE_O_OPEN | RTFILE_O_WRITE | RTFILE_O_DENY_NONE, + "/sys/class/tpm/tpm%u/device/cancel", idTpm); + if (rc == VERR_FILE_NOT_FOUND) + rc = RTFileOpenF(&pThis->hTpmCancel, RTFILE_O_OPEN | RTFILE_O_WRITE | RTFILE_O_DENY_NONE, + "/sys/class/misc/tpm%u/device/cancel", idTpm); + if ( RT_SUCCESS(rc) + || rc == VERR_FILE_NOT_FOUND) + { + /* Try to figure out the TPM version. */ + int64_t iVersion = 0; + rc = RTLinuxSysFsReadIntFile(10 /*uBase*/, &iVersion, "/sys/class/tpm/tpm%u/tpm_version_major", idTpm); + if (rc == VERR_FILE_NOT_FOUND) + rc = RTLinuxSysFsReadIntFile(10 /*uBase*/, &iVersion, "/sys/class/misc/tpm%u/tpm_version_major", idTpm); + if (RT_SUCCESS(rc)) + { + if (iVersion == 1) + pThis->enmTpmVers = RTTPMVERSION_1_2; + else if (iVersion == 2) + pThis->enmTpmVers = RTTPMVERSION_2_0; + } + + *phTpm = pThis; + return VINF_SUCCESS; + } + + RTFileClose(pThis->hTpmDev); + pThis->hTpmDev = NIL_RTFILE; + } + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +RTDECL(int) RTTpmClose(RTTPM hTpm) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + RTFileClose(pThis->hTpmDev); + if (pThis->hTpmCancel != NIL_RTFILE) + RTFileClose(pThis->hTpmCancel); + + pThis->hTpmDev = NIL_RTFILE; + pThis->hTpmCancel = NIL_RTFILE; + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(RTTPMVERSION) RTTpmGetVersion(RTTPM hTpm) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, RTTPMVERSION_INVALID); + return pThis->enmTpmVers; +} + + +RTDECL(uint32_t) RTTpmGetLocalityMax(RTTPM hTpm) +{ + RT_NOREF(hTpm); + return 0; /* On Linux only TPM locality 0 is supported. */ +} + + +RTDECL(int) RTTpmReqCancel(RTTPM hTpm) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + if (pThis->hTpmCancel == NIL_RTFILE) + return VERR_NOT_SUPPORTED; + + if (ASMAtomicReadBool(&pThis->fReqExec)) + { + uint8_t bCancel = '-'; + return RTFileWrite(pThis->hTpmCancel, &bCancel, sizeof(bCancel), NULL /*pcbWritten*/); + } + + return VINF_SUCCESS; +} + + +RTDECL(int) RTTpmReqExec(RTTPM hTpm, uint8_t bLoc, const void *pvReq, size_t cbReq, + void *pvResp, size_t cbRespMax, size_t *pcbResp) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pvReq, VERR_INVALID_POINTER); + AssertPtrReturn(pvResp, VERR_INVALID_POINTER); + AssertReturn(cbReq && cbRespMax, VERR_INVALID_PARAMETER); + AssertReturn(bLoc == 0, VERR_NOT_SUPPORTED); /** @todo There doesn't seem to be a way to use a different locality. */ + + /* The request has to be supplied by a single blocking write. */ + ASMAtomicXchgBool(&pThis->fReqExec, true); + int rc = RTFileWrite(pThis->hTpmDev, pvReq, cbReq, NULL /*pcbWritten*/); + if (RT_SUCCESS(rc)) + { + size_t cbResp = 0; + /* The response has to be retrieved in a single read as well. */ + rc = RTFileRead(pThis->hTpmDev, pvResp, cbRespMax, &cbResp); + ASMAtomicXchgBool(&pThis->fReqExec, false); + if (RT_SUCCESS(rc)) + { + /* Check whether the response is complete. */ + if ( cbResp >= sizeof(TPMRESPHDR) + && RTTpmRespGetSz((PCTPMRESPHDR)pvResp) == cbResp) + { + if (pcbResp) + *pcbResp = cbResp; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/memsafer-r3.cpp b/src/VBox/Runtime/r3/memsafer-r3.cpp new file mode 100644 index 00000000..bafeed96 --- /dev/null +++ b/src/VBox/Runtime/r3/memsafer-r3.cpp @@ -0,0 +1,691 @@ +/* $Id: memsafer-r3.cpp $ */ +/** @file + * IPRT - Memory Allocate for Sensitive Data, generic heap-based implementation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/memsafer.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/rand.h> +#include <iprt/param.h> +#include <iprt/string.h> +#ifdef IN_SUP_R3 +# include <VBox/sup.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Allocation size alignment (power of two). */ +#define RTMEMSAFER_ALIGN 16 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Allocators. + */ +typedef enum RTMEMSAFERALLOCATOR +{ + /** Invalid method. */ + RTMEMSAFERALLOCATOR_INVALID = 0, + /** RTMemPageAlloc. */ + RTMEMSAFERALLOCATOR_RTMEMPAGE, + /** SUPR3PageAllocEx. */ + RTMEMSAFERALLOCATOR_SUPR3 +} RTMEMSAFERALLOCATOR; + +/** + * Tracking node (lives on normal heap). + */ +typedef struct RTMEMSAFERNODE +{ + /** Node core. + * The core key is a scrambled pointer the user memory. */ + AVLPVNODECORE Core; + /** The allocation flags. */ + uint32_t fFlags; + /** The offset into the allocation of the user memory. */ + uint32_t offUser; + /** The requested allocation size. */ + size_t cbUser; + /** The allocation size in pages, this includes the two guard pages. */ + uint32_t cPages; + /** The allocator used for this node. */ + RTMEMSAFERALLOCATOR enmAllocator; + /** XOR scrambler value for memory. */ + uintptr_t uScramblerXor; +} RTMEMSAFERNODE; +/** Pointer to an allocation tracking node. */ +typedef RTMEMSAFERNODE *PRTMEMSAFERNODE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init once structure for this module. */ +static RTONCE g_MemSaferOnce = RTONCE_INITIALIZER; +/** Critical section protecting the allocation tree. */ +static RTCRITSECTRW g_MemSaferCritSect; +/** Tree of allocation nodes. */ +static AVLPVTREE g_pMemSaferTree; +/** XOR scrambler value pointers. */ +static uintptr_t g_uMemSaferPtrScramblerXor; +/** Pointer rotate shift count.*/ +static uintptr_t g_cMemSaferPtrScramblerRotate; + + +/** + * @callback_method_impl{FNRTONCE, Inits globals.} + */ +static DECLCALLBACK(int32_t) rtMemSaferOnceInit(void *pvUserIgnore) +{ + RT_NOREF_PV(pvUserIgnore); + + g_uMemSaferPtrScramblerXor = (uintptr_t)RTRandU64(); + g_cMemSaferPtrScramblerRotate = RTRandU32Ex(0, ARCH_BITS - 1); + return RTCritSectRwInit(&g_MemSaferCritSect); +} + + +/** + * @callback_method_impl{PFNRTONCECLEANUP, Cleans up globals.} + */ +static DECLCALLBACK(void) rtMemSaferOnceTerm(void *pvUser, bool fLazyCleanUpOk) +{ + RT_NOREF_PV(pvUser); + + if (!fLazyCleanUpOk) + { + RTCritSectRwDelete(&g_MemSaferCritSect); + Assert(!g_pMemSaferTree); + } +} + + + +DECLINLINE(void *) rtMemSaferScramblePointer(void *pvUser) +{ + uintptr_t uPtr = (uintptr_t)pvUser; + uPtr ^= g_uMemSaferPtrScramblerXor; +#if ARCH_BITS == 64 + uPtr = ASMRotateRightU64(uPtr, g_cMemSaferPtrScramblerRotate); +#elif ARCH_BITS == 32 + uPtr = ASMRotateRightU32(uPtr, g_cMemSaferPtrScramblerRotate); +#else +# error "Unsupported/missing ARCH_BITS." +#endif + return (void *)uPtr; +} + + +/** + * Inserts a tracking node into the tree. + * + * @param pThis The allocation tracking node to insert. + */ +static void rtMemSaferNodeInsert(PRTMEMSAFERNODE pThis) +{ + RTCritSectRwEnterExcl(&g_MemSaferCritSect); + pThis->Core.Key = rtMemSaferScramblePointer(pThis->Core.Key); + bool fRc = RTAvlPVInsert(&g_pMemSaferTree, &pThis->Core); + RTCritSectRwLeaveExcl(&g_MemSaferCritSect); + Assert(fRc); NOREF(fRc); +} + + +/** + * Finds a tracking node into the tree. + * + * @returns The allocation tracking node for @a pvUser. NULL if not found. + * @param pvUser The user pointer to the allocation. + */ +static PRTMEMSAFERNODE rtMemSaferNodeLookup(void *pvUser) +{ + void *pvKey = rtMemSaferScramblePointer(pvUser); + RTCritSectRwEnterShared(&g_MemSaferCritSect); + PRTMEMSAFERNODE pThis = (PRTMEMSAFERNODE)RTAvlPVGet(&g_pMemSaferTree, pvKey); + RTCritSectRwLeaveShared(&g_MemSaferCritSect); + return pThis; +} + + +/** + * Removes a tracking node from the tree. + * + * @returns The allocation tracking node for @a pvUser. NULL if not found. + * @param pvUser The user pointer to the allocation. + */ +static PRTMEMSAFERNODE rtMemSaferNodeRemove(void *pvUser) +{ + void *pvKey = rtMemSaferScramblePointer(pvUser); + RTCritSectRwEnterExcl(&g_MemSaferCritSect); + PRTMEMSAFERNODE pThis = (PRTMEMSAFERNODE)RTAvlPVRemove(&g_pMemSaferTree, pvKey); + RTCritSectRwLeaveExcl(&g_MemSaferCritSect); + return pThis; +} + + +RTDECL(int) RTMemSaferScramble(void *pv, size_t cb) +{ + PRTMEMSAFERNODE pThis = rtMemSaferNodeLookup(pv); + AssertReturn(pThis, VERR_INVALID_POINTER); + AssertMsgReturn(cb == pThis->cbUser, ("cb=%#zx != %#zx\n", cb, pThis->cbUser), VERR_INVALID_PARAMETER); + + /* First time we get a new xor value. */ + if (!pThis->uScramblerXor) + pThis->uScramblerXor = (uintptr_t)RTRandU64(); + + /* Note! This isn't supposed to be safe, just less obvious. */ + uintptr_t *pu = (uintptr_t *)pv; + cb = RT_ALIGN_Z(cb, RTMEMSAFER_ALIGN); + while (cb > 0) + { + *pu ^= pThis->uScramblerXor; + pu++; + cb -= sizeof(*pu); + } + + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTMemSaferScramble); + + +RTDECL(int) RTMemSaferUnscramble(void *pv, size_t cb) +{ + PRTMEMSAFERNODE pThis = rtMemSaferNodeLookup(pv); + AssertReturn(pThis, VERR_INVALID_POINTER); + AssertMsgReturn(cb == pThis->cbUser, ("cb=%#zx != %#zx\n", cb, pThis->cbUser), VERR_INVALID_PARAMETER); + + /* Note! This isn't supposed to be safe, just less obvious. */ + uintptr_t *pu = (uintptr_t *)pv; + cb = RT_ALIGN_Z(cb, RTMEMSAFER_ALIGN); + while (cb > 0) + { + *pu ^= pThis->uScramblerXor; + pu++; + cb -= sizeof(*pu); + } + + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTMemSaferUnscramble); + + +/** + * Initializes the pages. + * + * Fills the memory with random bytes in order to make it less obvious where the + * secret data starts and ends. We also zero the user memory in case the + * allocator does not do this. + * + * @param pThis The allocation tracer node. The Core.Key member + * will be set. + * @param pvPages The pages to initialize. + */ +static void rtMemSaferInitializePages(PRTMEMSAFERNODE pThis, void *pvPages) +{ + RTRandBytes(pvPages, PAGE_SIZE + pThis->offUser); + + uint8_t *pbUser = (uint8_t *)pvPages + PAGE_SIZE + pThis->offUser; + pThis->Core.Key = pbUser; + RT_BZERO(pbUser, pThis->cbUser); /* paranoia */ + + RTRandBytes(pbUser + pThis->cbUser, (size_t)pThis->cPages * PAGE_SIZE - PAGE_SIZE - pThis->offUser - pThis->cbUser); +} + + +/** + * Allocates and initializes pages from the support driver and initializes it. + * + * @returns VBox status code. + * @param pThis The allocator node. Core.Key will be set on successful + * return (unscrambled). + */ +static int rtMemSaferSupR3AllocPages(PRTMEMSAFERNODE pThis) +{ +#ifdef IN_SUP_R3 + /* + * Try allocate the memory. + */ + void *pvPages; + int rc = SUPR3PageAllocEx(pThis->cPages, 0 /* fFlags */, &pvPages, NULL /* pR0Ptr */, NULL /* paPages */); + if (RT_SUCCESS(rc)) + { + rtMemSaferInitializePages(pThis, pvPages); + + /* + * On darwin we cannot allocate pages without an R0 mapping and + * SUPR3PageAllocEx falls back to another method which is incompatible with + * the way SUPR3PageProtect works. Ignore changing the protection of the guard + * pages. + */ +#ifdef RT_OS_DARWIN + return VINF_SUCCESS; +#else + /* + * Configure the guard pages. + * SUPR3PageProtect isn't supported on all hosts, we ignore that. + */ + rc = SUPR3PageProtect(pvPages, NIL_RTR0PTR, 0, PAGE_SIZE, RTMEM_PROT_NONE); + if (RT_SUCCESS(rc)) + { + rc = SUPR3PageProtect(pvPages, NIL_RTR0PTR, (pThis->cPages - 1) * PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_NONE); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + SUPR3PageProtect(pvPages, NIL_RTR0PTR, 0, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + } + else if (rc == VERR_NOT_SUPPORTED) + return VINF_SUCCESS; + + /* failed. */ + int rc2 = SUPR3PageFreeEx(pvPages, pThis->cPages); AssertRC(rc2); +#endif + } + return rc; + +#else /* !IN_SUP_R3 */ + RT_NOREF_PV(pThis); + return VERR_NOT_SUPPORTED; +#endif /* !IN_SUP_R3 */ +} + + +/** + * Allocates and initializes pages using the IPRT page allocator API. + * + * @returns VBox status code. + * @param pThis The allocator node. Core.Key will be set on successful + * return (unscrambled). + */ +static int rtMemSaferMemAllocPages(PRTMEMSAFERNODE pThis) +{ + /* + * Try allocate the memory. + */ + int rc = VINF_SUCCESS; + void *pvPages = RTMemPageAllocEx((size_t)pThis->cPages * PAGE_SIZE, + RTMEMPAGEALLOC_F_ADVISE_LOCKED | RTMEMPAGEALLOC_F_ADVISE_NO_DUMP | RTMEMPAGEALLOC_F_ZERO); + if (pvPages) + { + rtMemSaferInitializePages(pThis, pvPages); + + /* + * Configure the guard pages. + */ + rc = RTMemProtect(pvPages, PAGE_SIZE, RTMEM_PROT_NONE); + if (RT_SUCCESS(rc)) + { + rc = RTMemProtect((uint8_t *)pvPages + (size_t)(pThis->cPages - 1U) * PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_NONE); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + rc = RTMemProtect(pvPages, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + } + + /* failed. */ + RTMemPageFree(pvPages, (size_t)pThis->cPages * PAGE_SIZE); + } + else + rc = VERR_NO_PAGE_MEMORY; + + return rc; +} + + +RTDECL(int) RTMemSaferAllocZExTag(void **ppvNew, size_t cb, uint32_t fFlags, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF_PV(pszTag); + + /* + * Validate input. + */ + AssertPtrReturn(ppvNew, VERR_INVALID_PARAMETER); + *ppvNew = NULL; + AssertReturn(cb, VERR_INVALID_PARAMETER); + AssertReturn(cb <= 32U*_1M - PAGE_SIZE * 3U, VERR_ALLOCATION_TOO_BIG); /* Max 32 MB minus padding and guard pages. */ + AssertReturn(!(fFlags & ~RTMEMSAFER_F_VALID_MASK), VERR_INVALID_FLAGS); + + /* + * Initialize globals. + */ + int rc = RTOnceEx(&g_MemSaferOnce, rtMemSaferOnceInit, rtMemSaferOnceTerm, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Allocate a tracker node first. + */ + PRTMEMSAFERNODE pThis = (PRTMEMSAFERNODE)RTMemAllocZ(sizeof(RTMEMSAFERNODE)); + if (pThis) + { + /* + * Prepare the allocation. + */ + pThis->cbUser = cb; + pThis->offUser = (RTRandU32Ex(0, 128) * RTMEMSAFER_ALIGN) & PAGE_OFFSET_MASK; + + size_t cbNeeded = pThis->offUser + pThis->cbUser; + cbNeeded = RT_ALIGN_Z(cbNeeded, PAGE_SIZE); + + pThis->cPages = (uint32_t)(cbNeeded / PAGE_SIZE) + 2; /* +2 for guard pages */ + + /* + * Try allocate the memory, using the best allocator by default and + * falling back on the less safe one. + */ + rc = rtMemSaferSupR3AllocPages(pThis); + if (RT_SUCCESS(rc)) + pThis->enmAllocator = RTMEMSAFERALLOCATOR_SUPR3; + else if (!(fFlags & RTMEMSAFER_F_REQUIRE_NOT_PAGABLE)) + { + rc = rtMemSaferMemAllocPages(pThis); + if (RT_SUCCESS(rc)) + pThis->enmAllocator = RTMEMSAFERALLOCATOR_RTMEMPAGE; + } + if (RT_SUCCESS(rc)) + { + /* + * Insert the node. + */ + *ppvNew = pThis->Core.Key; + rtMemSaferNodeInsert(pThis); /* (Scrambles Core.Key) */ + return VINF_SUCCESS; + } + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + return rc; +} +RT_EXPORT_SYMBOL(RTMemSaferAllocZExTag); + + +RTDECL(void) RTMemSaferFree(void *pv, size_t cb) RT_NO_THROW_DEF +{ + if (pv) + { + PRTMEMSAFERNODE pThis = rtMemSaferNodeRemove(pv); + AssertReturnVoid(pThis); + if (cb == 0) /* for openssl use */ + cb = pThis->cbUser; + else + AssertMsg(cb == pThis->cbUser, ("cb=%#zx != %#zx\n", cb, pThis->cbUser)); + + /* + * Wipe the user memory first. + */ + RTMemWipeThoroughly(pv, RT_ALIGN_Z(cb, RTMEMSAFER_ALIGN), 3); + + /* + * Free the pages. + */ + uint8_t *pbPages = (uint8_t *)pv - pThis->offUser - PAGE_SIZE; + size_t cbPages = (size_t)pThis->cPages * PAGE_SIZE; + switch (pThis->enmAllocator) + { +#ifdef IN_SUP_R3 + case RTMEMSAFERALLOCATOR_SUPR3: + SUPR3PageProtect(pbPages, NIL_RTR0PTR, 0, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + SUPR3PageProtect(pbPages, NIL_RTR0PTR, (uint32_t)(cbPages - PAGE_SIZE), PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + SUPR3PageFreeEx(pbPages, pThis->cPages); + break; +#endif + case RTMEMSAFERALLOCATOR_RTMEMPAGE: + RTMemProtect(pbPages, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemProtect(pbPages + cbPages - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemPageFree(pbPages, cbPages); + break; + + default: + AssertFailed(); + } + + /* + * Free the tracking node. + */ + pThis->Core.Key = NULL; + pThis->offUser = 0; + pThis->cbUser = 0; + RTMemFree(pThis); + } + else + Assert(cb == 0); +} +RT_EXPORT_SYMBOL(RTMemSaferFree); + + +RTDECL(size_t) RTMemSaferGetSize(void *pv) RT_NO_THROW_DEF +{ + size_t cbRet = 0; + if (pv) + { + /* + * We use this API for testing whether pv is a safer allocation or not, + * so we may be called before the allocators. Thus, it's prudent to + * make sure initialization has taken place before attempting to enter + * the critical section and such. + */ + int rc = RTOnceEx(&g_MemSaferOnce, rtMemSaferOnceInit, rtMemSaferOnceTerm, NULL); + if (RT_SUCCESS(rc)) + { + void *pvKey = rtMemSaferScramblePointer(pv); + RTCritSectRwEnterShared(&g_MemSaferCritSect); + PRTMEMSAFERNODE pThis = (PRTMEMSAFERNODE)RTAvlPVGet(&g_pMemSaferTree, pvKey); + if (pThis) + cbRet = pThis->cbUser; + RTCritSectRwLeaveShared(&g_MemSaferCritSect); + } + } + return cbRet; +} +RT_EXPORT_SYMBOL(RTMemSaferGetSize); + + +/** + * The simplest reallocation method: allocate new block, copy over the data, + * free old block. + */ +static int rtMemSaferReallocSimpler(size_t cbOld, void *pvOld, size_t cbNew, void **ppvNew, uint32_t fFlags, const char *pszTag) +{ + void *pvNew; + int rc = RTMemSaferAllocZExTag(&pvNew, cbNew, fFlags, pszTag); + if (RT_SUCCESS(rc)) + { + memcpy(pvNew, pvOld, RT_MIN(cbNew, cbOld)); + RTMemSaferFree(pvOld, cbOld); + *ppvNew = pvNew; + } + return rc; +} + + +RTDECL(int) RTMemSaferReallocZExTag(size_t cbOld, void *pvOld, size_t cbNew, void **ppvNew, uint32_t fFlags, const char *pszTag) RT_NO_THROW_DEF +{ + int rc; + /* Real realloc. */ + if (cbNew && cbOld) + { + PRTMEMSAFERNODE pThis = rtMemSaferNodeLookup(pvOld); + AssertReturn(pThis, VERR_INVALID_POINTER); + AssertMsgStmt(cbOld == pThis->cbUser, ("cbOld=%#zx != %#zx\n", cbOld, pThis->cbUser), cbOld = pThis->cbUser); + + if (pThis->fFlags == fFlags) + { + if (cbNew > cbOld) + { + /* + * Is the enough room for us to grow? + */ + size_t cbMax = (size_t)(pThis->cPages - 2) * PAGE_SIZE; + if (cbNew <= cbMax) + { + size_t const cbAdded = (cbNew - cbOld); + size_t const cbAfter = cbMax - pThis->offUser - cbOld; + if (cbAfter >= cbAdded) + { + /* + * Sufficient space after the current allocation. + */ + uint8_t *pbNewSpace = (uint8_t *)pvOld + cbOld; + RT_BZERO(pbNewSpace, cbAdded); + *ppvNew = pvOld; + } + else + { + /* + * Have to move the allocation to make enough room at the + * end. In order to make it a little less predictable and + * maybe avoid a relocation or two in the next call, divide + * the page offset by four until it it fits. + */ + AssertReturn(rtMemSaferNodeRemove(pvOld) == pThis, VERR_INTERNAL_ERROR_3); + uint32_t offNewUser = pThis->offUser; + do + offNewUser = offNewUser / 2; + while ((pThis->offUser - offNewUser) + cbAfter < cbAdded); + offNewUser &= ~(RTMEMSAFER_ALIGN - 1U); + + uint32_t const cbMove = pThis->offUser - offNewUser; + uint8_t *pbNew = (uint8_t *)pvOld - cbMove; + memmove(pbNew, pvOld, cbOld); + + RT_BZERO(pbNew + cbOld, cbAdded); + if (cbMove > cbAdded) + RTMemWipeThoroughly(pbNew + cbNew, cbMove - cbAdded, 3); + + pThis->offUser = offNewUser; + pThis->Core.Key = pbNew; + *ppvNew = pbNew; + + rtMemSaferNodeInsert(pThis); + } + Assert(((uintptr_t)*ppvNew & PAGE_OFFSET_MASK) == pThis->offUser); + pThis->cbUser = cbNew; + rc = VINF_SUCCESS; + } + else + { + /* + * Not enough space, allocate a new block and copy over the data. + */ + rc = rtMemSaferReallocSimpler(cbOld, pvOld, cbNew, ppvNew, fFlags, pszTag); + } + } + else + { + /* + * Shrinking the allocation, just wipe the memory that is no longer + * being used. + */ + if (cbNew != cbOld) + { + uint8_t *pbAbandond = (uint8_t *)pvOld + cbNew; + RTMemWipeThoroughly(pbAbandond, cbOld - cbNew, 3); + } + pThis->cbUser = cbNew; + *ppvNew = pvOld; + rc = VINF_SUCCESS; + } + } + else if (!pThis->fFlags) + { + /* + * New flags added. Allocate a new block and copy over the old one. + */ + rc = rtMemSaferReallocSimpler(cbOld, pvOld, cbNew, ppvNew, fFlags, pszTag); + } + else + { + /* Compatible flags. */ + AssertMsgFailed(("fFlags=%#x old=%#x\n", fFlags, pThis->fFlags)); + rc = VERR_INVALID_FLAGS; + } + } + /* + * First allocation. Pass it on. + */ + else if (!cbOld) + { + Assert(pvOld == NULL); + rc = RTMemSaferAllocZExTag(ppvNew, cbNew, fFlags, pszTag); + } + /* + * Free operation. Pass it on. + */ + else + { + RTMemSaferFree(pvOld, cbOld); + *ppvNew = NULL; + rc = VINF_SUCCESS; + } + return rc; +} +RT_EXPORT_SYMBOL(RTMemSaferReallocZExTag); + + +RTDECL(void *) RTMemSaferAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + void *pvNew = NULL; + int rc = RTMemSaferAllocZExTag(&pvNew, cb, 0 /*fFlags*/, pszTag); + if (RT_SUCCESS(rc)) + return pvNew; + return NULL; +} +RT_EXPORT_SYMBOL(RTMemSaferAllocZTag); + + +RTDECL(void *) RTMemSaferReallocZTag(size_t cbOld, void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF +{ + void *pvNew = NULL; + int rc = RTMemSaferReallocZExTag(cbOld, pvOld, cbNew, &pvNew, 0 /*fFlags*/, pszTag); + if (RT_SUCCESS(rc)) + return pvNew; + return NULL; +} +RT_EXPORT_SYMBOL(RTMemSaferReallocZTag); + diff --git a/src/VBox/Runtime/r3/netbsd/Makefile.kup b/src/VBox/Runtime/r3/netbsd/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/netbsd/Makefile.kup diff --git a/src/VBox/Runtime/r3/netbsd/rtProcInitExePath-netbsd.cpp b/src/VBox/Runtime/r3/netbsd/rtProcInitExePath-netbsd.cpp new file mode 100644 index 00000000..90566b7a --- /dev/null +++ b/src/VBox/Runtime/r3/netbsd/rtProcInitExePath-netbsd.cpp @@ -0,0 +1,110 @@ +/* $Id: rtProcInitExePath-netbsd.cpp $ */ +/** @file + * IPRT - rtProcInitName, NetBSD. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <sys/param.h> +#include <sys/sysctl.h> +#include <unistd.h> +#include <errno.h> +#include <dlfcn.h> +#include <link.h> + +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include "internal/process.h" +#include "internal/path.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ + /* + * Read the /proc/curproc/file link, convert to native and return it. + */ + int cchLink = readlink("/proc/curproc/exe", pszPath, cchPath - 1); + if (cchLink > 0 && (size_t)cchLink <= cchPath - 1) + { + pszPath[cchLink] = '\0'; + + char const *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszPath, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszLink=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, cchLink, pszPath), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp, pszPath); + } + return rc; + } + + int err = errno; + + /* + * Fall back on the dynamic linker since /proc is optional. + */ + void *hExe = dlopen(NULL, 0); + if (hExe) + { + struct link_map const *pLinkMap = 0; + if (dlinfo(hExe, RTLD_DI_LINKMAP, &pLinkMap) == 0) + { + const char *pszImageName = pLinkMap->l_name; + if (*pszImageName == '/') /* this may not always be absolute, despite the docs. :-( */ + { + char const *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszImageName, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszImageName=\"%s\"\n", rc, pszImageName), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp, pszPath); + } + return rc; + } + /** @todo Try search the PATH for the file name or append the current + * directory, which ever makes sense... */ + } + } + + int rc = RTErrConvertFromErrno(err); + AssertMsgFailed(("rc=%Rrc err=%d cchLink=%d hExe=%p\n", rc, err, cchLink, hExe)); + return rc; +} diff --git a/src/VBox/Runtime/r3/nocrt-cerr.cpp b/src/VBox/Runtime/r3/nocrt-cerr.cpp new file mode 100644 index 00000000..71c294f8 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-cerr.cpp @@ -0,0 +1,52 @@ +/* $Id: nocrt-cerr.cpp $ */ +/** @file + * IPRT - No-CRT - std::cerr. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/nocrt.h" +#include <iprt/nocrt/iostream> +#include <iprt/nocrt/fstream> +#include <iprt/stream.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +std::basic_filebuf<char, std::char_traits<char> > g_rtNoCrtCerrBuf(g_pStdErr, true /*a_fStdStream*/); +std::ostream std::cerr(&g_rtNoCrtCerrBuf, &std::cout /*a_pTiedStream*/, true /*a_fUnbuffered*/); + diff --git a/src/VBox/Runtime/r3/nocrt-clearerr.cpp b/src/VBox/Runtime/r3/nocrt-clearerr.cpp new file mode 100644 index 00000000..ecd3ce69 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-clearerr.cpp @@ -0,0 +1,53 @@ +/* $Id: nocrt-clearerr.cpp $ */ +/** @file + * IPRT - No-CRT - clearerr(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/stream.h> + + +#undef clearerr +void RT_NOCRT(clearerr)(FILE *pFile) +{ + RTStrmClearError(pFile); +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(clearerr); + diff --git a/src/VBox/Runtime/r3/nocrt-cout.cpp b/src/VBox/Runtime/r3/nocrt-cout.cpp new file mode 100644 index 00000000..5323359f --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-cout.cpp @@ -0,0 +1,52 @@ +/* $Id: nocrt-cout.cpp $ */ +/** @file + * IPRT - No-CRT - std::cout. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/nocrt.h" +#include <iprt/nocrt/iostream> +#include <iprt/nocrt/fstream> +#include <iprt/stream.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +std::basic_filebuf<char, std::char_traits<char> > g_rtNoCrtCoutBuf(g_pStdOut, true /*a_fStdStream*/); +std::ostream std::cout(&g_rtNoCrtCoutBuf, NULL /*a_pTiedStream*/, false /*a_fUnbuffered*/); + diff --git a/src/VBox/Runtime/r3/nocrt-errno.cpp b/src/VBox/Runtime/r3/nocrt-errno.cpp new file mode 100644 index 00000000..7a931a0f --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-errno.cpp @@ -0,0 +1,55 @@ +/* $Id: nocrt-errno.cpp $ */ +/** @file + * IPRT - No-CRT - Per-thread errno variable. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/errno.h> + + +RTDECL(int *) rtNoCrtGetErrnoPtr(void) +{ + PRTNOCRTTHREADDATA pNoCrtData = rtNoCrtThreadDataGet(); + if (pNoCrtData) + return &pNoCrtData->iErrno; + + static int s_iFallbackErrno; + return &s_iFallbackErrno; +} + diff --git a/src/VBox/Runtime/r3/nocrt-fclose.cpp b/src/VBox/Runtime/r3/nocrt-fclose.cpp new file mode 100644 index 00000000..8cd90a79 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fclose.cpp @@ -0,0 +1,59 @@ +/* $Id: nocrt-fclose.cpp $ */ +/** @file + * IPRT - No-CRT - fclose(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fclose +int RT_NOCRT(fclose)(FILE *pFile) +{ + int rc = RTStrmClose(pFile); + if (RT_SUCCESS(rc)) + return 0; + errno = RTErrConvertToErrno(rc); + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fclose); + diff --git a/src/VBox/Runtime/r3/nocrt-fdopen.cpp b/src/VBox/Runtime/r3/nocrt-fdopen.cpp new file mode 100644 index 00000000..8ecef72f --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fdopen.cpp @@ -0,0 +1,66 @@ +/* $Id: nocrt-fdopen.cpp $ */ +/** @file + * IPRT - No-CRT - fdopen(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/stream.h> + + +#undef fdopen +FILE *RT_NOCRT(fdopen)(int fd, const char *pszMode) +{ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileFromNative(&hFile, fd); + if (RT_SUCCESS(rc)) + { + PRTSTREAM pStrm = NULL; + rc = RTStrmOpenFileHandle(hFile, pszMode, 0, &pStrm); + if (RT_SUCCESS(rc)) + return pStrm; + } + errno = RTErrConvertToErrno(rc); + return NULL; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fdopen); + diff --git a/src/VBox/Runtime/r3/nocrt-ferror.cpp b/src/VBox/Runtime/r3/nocrt-ferror.cpp new file mode 100644 index 00000000..b566cd87 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-ferror.cpp @@ -0,0 +1,57 @@ +/* $Id: nocrt-ferror.cpp $ */ +/** @file + * IPRT - No-CRT - ferror(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef ferror +int RT_NOCRT(ferror)(FILE *pFile) +{ + int rc = RTStrmError(pFile); + if (RT_SUCCESS(rc)) + return 0; + return 1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(ferror); + diff --git a/src/VBox/Runtime/r3/nocrt-fflush.cpp b/src/VBox/Runtime/r3/nocrt-fflush.cpp new file mode 100644 index 00000000..2535da4b --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fflush.cpp @@ -0,0 +1,59 @@ +/* $Id: nocrt-fflush.cpp $ */ +/** @file + * IPRT - No-CRT - fflush(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fflush +int RT_NOCRT(fflush)(FILE *pFile) +{ + int rc = RTStrmFlush(pFile); + if (RT_SUCCESS(rc)) + return 0; + errno = RTErrConvertToErrno(rc); + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fflush); + diff --git a/src/VBox/Runtime/r3/nocrt-fgetc.cpp b/src/VBox/Runtime/r3/nocrt-fgetc.cpp new file mode 100644 index 00000000..cbb0cfdc --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fgetc.cpp @@ -0,0 +1,61 @@ +/* $Id: nocrt-fgetc.cpp $ */ +/** @file + * IPRT - No-CRT - fgetc(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +/* Note! This is identical to the getc implemention. */ +#undef fgetc +int RT_NOCRT(fgetc)(FILE *pFile) +{ + unsigned char ch; + int rc = RTStrmReadEx(pFile, &ch, 1, NULL); + if (RT_SUCCESS(rc)) + return ch; + errno = RTErrConvertToErrno(rc); + return RT_NOCRT_EOF; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fgetc); + diff --git a/src/VBox/Runtime/r3/nocrt-fileno.cpp b/src/VBox/Runtime/r3/nocrt-fileno.cpp new file mode 100644 index 00000000..506606a5 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fileno.cpp @@ -0,0 +1,61 @@ +/* $Id: nocrt-fileno.cpp $ */ +/** @file + * IPRT - No-CRT - fileno(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/file.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fileno +int RT_NOCRT(fileno)(FILE *pFile) +{ + RTFILE hFile; + int rc = RTStrmQueryFileHandle(pFile, &hFile); + if (RT_SUCCESS(rc)) + return (int)RTFileToNative(hFile); + errno = EINVAL; + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fileno); + diff --git a/src/VBox/Runtime/r3/nocrt-fopen.cpp b/src/VBox/Runtime/r3/nocrt-fopen.cpp new file mode 100644 index 00000000..3231a765 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fopen.cpp @@ -0,0 +1,60 @@ +/* $Id: nocrt-fopen.cpp $ */ +/** @file + * IPRT - No-CRT - fopen(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fopen +FILE *RT_NOCRT(fopen)(const char *pszFilename, const char *pszMode) +{ + PRTSTREAM pStrm = NULL; + int rc = RTStrmOpen(pszFilename, pszMode, &pStrm); + if (RT_SUCCESS(rc)) + return pStrm; + errno = RTErrConvertToErrno(rc); + return NULL; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fopen); + diff --git a/src/VBox/Runtime/r3/nocrt-fputc.cpp b/src/VBox/Runtime/r3/nocrt-fputc.cpp new file mode 100644 index 00000000..5e3e57c6 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fputc.cpp @@ -0,0 +1,61 @@ +/* $Id: nocrt-fputc.cpp $ */ +/** @file + * IPRT - No-CRT - fputc(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +/* Note! This is identical to the putc implemention. */ +#undef fputc +int RT_NOCRT(fputc)(int iChar, FILE *pFile) +{ + unsigned char ch = (unsigned char)iChar; + int rc = RTStrmWriteEx(pFile, &ch, 1, NULL); + if (RT_SUCCESS(rc)) + return ch; + errno = RTErrConvertToErrno(rc); + return RT_NOCRT_EOF; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fputc); + diff --git a/src/VBox/Runtime/r3/nocrt-fputs.cpp b/src/VBox/Runtime/r3/nocrt-fputs.cpp new file mode 100644 index 00000000..64656496 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fputs.cpp @@ -0,0 +1,63 @@ +/* $Id: nocrt-fputs.cpp $ */ +/** @file + * IPRT - No-CRT - fputs(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/nocrt/limits.h> +#include <iprt/nocrt/string.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fputs +int RT_NOCRT(fputs)(const char *psz, FILE *pFile) +{ + /* We're using RTStrmPutStr here for the benefit of console output optimization. */ + size_t cch = strlen(psz); + int rc = RTStrmPutStr(pFile, psz); + if (RT_SUCCESS(rc)) + return (int)RT_MIN(cch, (size_t)INT_MAX); + errno = RTErrConvertToErrno(rc); + return RT_NOCRT_EOF; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fputs); + diff --git a/src/VBox/Runtime/r3/nocrt-fread.cpp b/src/VBox/Runtime/r3/nocrt-fread.cpp new file mode 100644 index 00000000..9c0e8b47 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fread.cpp @@ -0,0 +1,63 @@ +/* $Id: nocrt-fread.cpp $ */ +/** @file + * IPRT - No-CRT - fread(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fread +size_t RT_NOCRT(fread)(void *pvBuf, size_t cbItem, size_t cItems, FILE *pFile) +{ + size_t cbToRead = cbItem * cItems; + AssertReturnStmt(cbToRead >= cbItem && cbItem >= cItems, errno = ERANGE, 0); + size_t cbRead = 0; + int rc = RTStrmReadEx(pFile, pvBuf, cbToRead, &cbRead); + if (RT_SUCCESS(rc)) + return cbRead; + errno = RTErrConvertToErrno(rc); + return 0; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fread); + diff --git a/src/VBox/Runtime/r3/nocrt-fseek.cpp b/src/VBox/Runtime/r3/nocrt-fseek.cpp new file mode 100644 index 00000000..2548c926 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fseek.cpp @@ -0,0 +1,59 @@ +/* $Id: nocrt-fseek.cpp $ */ +/** @file + * IPRT - No-CRT - fseek(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fseek +int RT_NOCRT(fseek)(FILE *pFile, long off, int iMethod) +{ + int rc = RTStrmSeek(pFile, off, (uint32_t)iMethod); + if (RT_SUCCESS(rc)) + return 0; + errno = RTErrConvertToErrno(rc); + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fseek); + diff --git a/src/VBox/Runtime/r3/nocrt-fseeko.cpp b/src/VBox/Runtime/r3/nocrt-fseeko.cpp new file mode 100644 index 00000000..bdeb6a9e --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fseeko.cpp @@ -0,0 +1,59 @@ +/* $Id: nocrt-fseeko.cpp $ */ +/** @file + * IPRT - No-CRT - fseeko(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fseeko +int RT_NOCRT(fseeko)(FILE *pFile, off_t off, int iMethod) +{ + int rc = RTStrmSeek(pFile, off, (uint32_t)iMethod); + if (RT_SUCCESS(rc)) + return 0; + errno = RTErrConvertToErrno(rc); + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fseeko); + diff --git a/src/VBox/Runtime/r3/nocrt-ftell.cpp b/src/VBox/Runtime/r3/nocrt-ftell.cpp new file mode 100644 index 00000000..c8acc950 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-ftell.cpp @@ -0,0 +1,65 @@ +/* $Id: nocrt-ftell.cpp $ */ +/** @file + * IPRT - No-CRT - ftell(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/nocrt/limits.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef ftell +long RT_NOCRT(ftell)(FILE *pFile) +{ + RTFOFF off = RTStrmTell(pFile); + if (off >= 0) + { + if (off < LONG_MAX) + return (long)off; + errno = EOVERFLOW; + } + else + errno = RTErrConvertToErrno((int)off); + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(ftell); + diff --git a/src/VBox/Runtime/r3/nocrt-ftello.cpp b/src/VBox/Runtime/r3/nocrt-ftello.cpp new file mode 100644 index 00000000..0cf2b6d8 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-ftello.cpp @@ -0,0 +1,60 @@ +/* $Id: nocrt-ftello.cpp $ */ +/** @file + * IPRT - No-CRT - ftello(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef ftello +off_t RT_NOCRT(ftello)(FILE *pFile) +{ + RTFOFF off = RTStrmTell(pFile); + AssertCompile(sizeof(off_t) == sizeof(off)); + if (off >= 0) + return off; + errno = RTErrConvertToErrno((int)off); + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(ftello); + diff --git a/src/VBox/Runtime/r3/nocrt-fwrite.cpp b/src/VBox/Runtime/r3/nocrt-fwrite.cpp new file mode 100644 index 00000000..05d108d2 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-fwrite.cpp @@ -0,0 +1,63 @@ +/* $Id: nocrt-fwrite.cpp $ */ +/** @file + * IPRT - No-CRT - fwrite(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef fwrite +size_t RT_NOCRT(fwrite)(void const *pvBuf, size_t cbItem, size_t cItems, FILE *pFile) +{ + size_t cbToWrite = cbItem * cItems; + AssertReturnStmt(cbToWrite >= cbItem && cbItem >= cItems, errno = ERANGE, 0); + size_t cbWritten = 0; + int rc = RTStrmWriteEx(pFile, pvBuf, cbToWrite, &cbWritten); + if (RT_SUCCESS(rc)) + return cbWritten; + errno = RTErrConvertToErrno(rc); + return 0; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fwrite); + diff --git a/src/VBox/Runtime/r3/nocrt-getc.cpp b/src/VBox/Runtime/r3/nocrt-getc.cpp new file mode 100644 index 00000000..4f94cbdb --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-getc.cpp @@ -0,0 +1,61 @@ +/* $Id: nocrt-getc.cpp $ */ +/** @file + * IPRT - No-CRT - getc(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +/* Note! This is identical to the fgetc implemention. */ +#undef getc +int RT_NOCRT(getc)(FILE *pFile) +{ + unsigned char ch; + int rc = RTStrmReadEx(pFile, &ch, 1, NULL); + if (RT_SUCCESS(rc)) + return ch; + errno = RTErrConvertToErrno(rc); + return RT_NOCRT_EOF; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(getc); + diff --git a/src/VBox/Runtime/r3/nocrt-per-thread-1.cpp b/src/VBox/Runtime/r3/nocrt-per-thread-1.cpp new file mode 100644 index 00000000..52bccab8 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-per-thread-1.cpp @@ -0,0 +1,58 @@ +/* $Id: nocrt-per-thread-1.cpp $ */ +/** @file + * IPRT - No-Crt - Per Thread Data, TLS index. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#include "internal/nocrt.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The TLS index for the no-CRT per thread data. */ +RTTLS volatile g_iTlsRtNoCrtPerThread = NIL_RTTLS; + +/** The TLS entry of an IPRT thread points to this during the cleanup (paranoia). */ +RTNOCRTTHREADDATA g_RtNoCrtPerThreadDummy = +{ + { NULL, NULL }, + RTNOCRTTHREADDATA::kAllocType_CleanupDummy, + 0, + NULL +}; + diff --git a/src/VBox/Runtime/r3/nocrt-per-thread-2.cpp b/src/VBox/Runtime/r3/nocrt-per-thread-2.cpp new file mode 100644 index 00000000..9179d33a --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-per-thread-2.cpp @@ -0,0 +1,238 @@ +/* $Id: nocrt-per-thread-2.cpp $ */ +/** @file + * IPRT - No-Crt - Per Thread Data, Managment code + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#include "internal/nocrt.h" +#include <iprt/asm.h> +#include <iprt/critsect.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/thread.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init/term once state. */ +static RTONCE g_NoCrtPerThreadOnce = RTONCE_INITIALIZER; + +/** List of heap allocations (PRTNOCRTTHREADDATA). */ +static RTLISTANCHOR g_NoCrtPerThreadHeapList; +/** Critical section protecting g_NoCrtPerThreadHeapList. */ +static RTCRITSECT g_NoCrtPerThreadCritSect; + +/** Allocation bitmap for g_aNoCrtPerThreadStatic. + * + * In debug builds we only have one slot here, so we have a better chance of + * testing the heap code path. */ +#ifdef DEBUG +static uint32_t volatile g_fNoCrtPerThreadStaticAlloc = UINT32_C(0xffffefff); +#else +static uint32_t volatile g_fNoCrtPerThreadStaticAlloc = 0; +#endif +/* Static allocations to avoid the heap and associate slowness. */ +static RTNOCRTTHREADDATA g_aNoCrtPerThreadStatic[32]; + + +/** + * @callback_method_impl{FNRTTLSDTOR} + */ +static DECLCALLBACK(void) rtNoCrtPerThreadDtor(void *pvValue) +{ + PRTNOCRTTHREADDATA pNoCrtData = (PRTNOCRTTHREADDATA)pvValue; + if (pNoCrtData->enmAllocType == RTNOCRTTHREADDATA::kAllocType_Heap) + { + AssertReturnVoid(RTOnceWasInitialized(&g_NoCrtPerThreadOnce)); + + RTCritSectEnter(&g_NoCrtPerThreadCritSect); /* timeout? */ + + RTListNodeRemove(&pNoCrtData->ListEntry); + pNoCrtData->enmAllocType = RTNOCRTTHREADDATA::kAllocType_End; + + RTCritSectLeave(&g_NoCrtPerThreadCritSect); + + RTMemFree(pNoCrtData); + } + else if (pNoCrtData->enmAllocType == RTNOCRTTHREADDATA::kAllocType_Static) + { + size_t iSlot = (size_t)(pNoCrtData - &g_aNoCrtPerThreadStatic[0]); + AssertReturnVoid(iSlot < RT_ELEMENTS(g_aNoCrtPerThreadStatic)); + + pNoCrtData->enmAllocType = RTNOCRTTHREADDATA::kAllocType_Invalid; + ASMAtomicAndU32(&g_fNoCrtPerThreadStaticAlloc, ~(uint32_t)iSlot); + } +} + + +/** + * @callback_method_impl{FNRTONCE} + */ +static DECLCALLBACK(int32_t) rtNoCrtPerThreadInit(void *pvUser) +{ + RTListInit(&g_NoCrtPerThreadHeapList); + + RTTLS iTls = NIL_RTTLS; + int rc = RTTlsAllocEx(&iTls, rtNoCrtPerThreadDtor); + if (iTls != NIL_RTTLS) + { + rc = RTCritSectInit(&g_NoCrtPerThreadCritSect); + if (RT_SUCCESS(rc)) + { + g_iTlsRtNoCrtPerThread = iTls; + return VINF_SUCCESS; + } + RTTlsFree(iTls); + } + RT_NOREF(pvUser); + return rc; +} + + +/** + * @callback_method_impl{FNRTONCECLEANUP} + */ +static DECLCALLBACK(void) rtNoCrtPerThreadCleanup(void *pvUser, bool fLazyCleanUpOk) +{ + RT_NOREF(pvUser); + if (fLazyCleanUpOk) + return; + + /* + * First destroy the TLS entry. + */ + RTTLS iTls = g_iTlsRtNoCrtPerThread; + g_iTlsRtNoCrtPerThread = NIL_RTTLS; + int rc = RTTlsFree(iTls); + AssertRC(rc); + + /* + * Then destroy the critical section and free all entries in the list. + */ + RTCritSectDelete(&g_NoCrtPerThreadCritSect); + + PRTNOCRTTHREADDATA pNoCrtData; + while ((pNoCrtData = RTListRemoveFirst(&g_NoCrtPerThreadHeapList, RTNOCRTTHREADDATA, ListEntry)) != NULL) + { + AssertContinue(pNoCrtData->enmAllocType == RTNOCRTTHREADDATA::kAllocType_Heap); + pNoCrtData->enmAllocType = RTNOCRTTHREADDATA::kAllocType_End; + RTMemFree(pNoCrtData); + } +} + +PRTNOCRTTHREADDATA rtNoCrtThreadDataGet(void) +{ + int rc = RTOnceEx(&g_NoCrtPerThreadOnce, rtNoCrtPerThreadInit, rtNoCrtPerThreadCleanup, NULL); + if (RT_SUCCESS(rc)) + { + /* + * We typically have an entry already. + */ + PRTNOCRTTHREADDATA pNoCrtData = (PRTNOCRTTHREADDATA)RTTlsGet(g_iTlsRtNoCrtPerThread); + if (pNoCrtData) + { + AssertReturn( pNoCrtData->enmAllocType > RTNOCRTTHREADDATA::kAllocType_Invalid + && pNoCrtData->enmAllocType < RTNOCRTTHREADDATA::kAllocType_End, + NULL); + return pNoCrtData; + } + + /* + * Okay, allocate a new entry first using some of the statically allocated + * ones then falling back on heap allocations. + */ + for (;;) + { + uint32_t const fAlloc = ASMAtomicUoReadU32(&g_fNoCrtPerThreadStaticAlloc); + uint32_t iSlot = ASMBitFirstSetU32(~fAlloc); + if (iSlot != 0) + iSlot--; + else + break; + if (ASMAtomicCmpXchgU32(&g_fNoCrtPerThreadStaticAlloc, fAlloc | RT_BIT_32(iSlot), fAlloc)) + { + pNoCrtData = &g_aNoCrtPerThreadStatic[iSlot]; + + /* Init the entry in case it's being re-used: */ + Assert(pNoCrtData->enmAllocType == RTNOCRTTHREADDATA::kAllocType_Invalid); + rc = RTTlsSet(g_iTlsRtNoCrtPerThread, pNoCrtData); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + pNoCrtData->enmAllocType = RTNOCRTTHREADDATA::kAllocType_Static; + RTListInit(&pNoCrtData->ListEntry); + pNoCrtData->iErrno = 0; + pNoCrtData->pszStrToken = NULL; + return pNoCrtData; + } + + ASMAtomicOrU32(&g_fNoCrtPerThreadStaticAlloc, RT_BIT_32(iSlot)); + return NULL; + } + ASMNopPause(); + } + + /* + * Heap. + */ + pNoCrtData = (PRTNOCRTTHREADDATA)RTMemAllocZ(sizeof(*pNoCrtData)); + if (pNoCrtData) + { + pNoCrtData->enmAllocType = RTNOCRTTHREADDATA::kAllocType_Heap; + + RTCritSectEnter(&g_NoCrtPerThreadCritSect); + RTListAppend(&g_NoCrtPerThreadHeapList, &pNoCrtData->ListEntry); + RTCritSectLeave(&g_NoCrtPerThreadCritSect); + + rc = RTTlsSet(g_iTlsRtNoCrtPerThread, pNoCrtData); + AssertRC(rc); + if (RT_SUCCESS(rc)) + return pNoCrtData; + + RTCritSectEnter(&g_NoCrtPerThreadCritSect); + RTListNodeRemove(&pNoCrtData->ListEntry); + RTCritSectLeave(&g_NoCrtPerThreadCritSect); + + pNoCrtData->enmAllocType = RTNOCRTTHREADDATA::kAllocType_End; + RTMemFree(pNoCrtData); + } + } + return NULL; +} + diff --git a/src/VBox/Runtime/r3/nocrt-putc.cpp b/src/VBox/Runtime/r3/nocrt-putc.cpp new file mode 100644 index 00000000..8f334057 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-putc.cpp @@ -0,0 +1,61 @@ +/* $Id: nocrt-putc.cpp $ */ +/** @file + * IPRT - No-CRT - putc(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +/* Note! This is identical to the fputc implemention. */ +#undef putc +int RT_NOCRT(putc)(int iChar, FILE *pFile) +{ + unsigned char ch = (unsigned char)iChar; + int rc = RTStrmWriteEx(pFile, &ch, 1, NULL); + if (RT_SUCCESS(rc)) + return ch; + errno = RTErrConvertToErrno(rc); + return RT_NOCRT_EOF; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(putc); + diff --git a/src/VBox/Runtime/r3/nocrt-puts.cpp b/src/VBox/Runtime/r3/nocrt-puts.cpp new file mode 100644 index 00000000..11f90e52 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-puts.cpp @@ -0,0 +1,68 @@ +/* $Id: nocrt-puts.cpp $ */ +/** @file + * IPRT - No-CRT - puts(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/nocrt/string.h> +#include <iprt/nocrt/limits.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef puts +int RT_NOCRT(puts)(const char *psz) +{ + /* We're using RTStrmPutStr here for the benefit of console output optimization. */ + size_t cch = strlen(psz); + int rc = RTStrmPutStr(g_pStdOut, psz); + if (RT_SUCCESS(rc)) + { + /** @todo This isn't entirely perfect wrt multithreading. */ + rc = RTStrmPutStr(g_pStdOut, "\n"); + if (RT_SUCCESS(rc)) + return (int)RT_MIN(cch + 1, (size_t)INT_MAX); + } + errno = RTErrConvertToErrno(rc); + return RT_NOCRT_EOF; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(puts); + diff --git a/src/VBox/Runtime/r3/nocrt-setvbuf.cpp b/src/VBox/Runtime/r3/nocrt-setvbuf.cpp new file mode 100644 index 00000000..76e0e46c --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-setvbuf.cpp @@ -0,0 +1,72 @@ +/* $Id: nocrt-setvbuf.cpp $ */ +/** @file + * IPRT - No-CRT - setvbuf(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/stream.h> + + +#undef setvbuf +int RT_NOCRT(setvbuf)(FILE *pFile, char *pchBuf, int iBufferingType, size_t cbBuf) +{ + Assert(!pchBuf); RT_NOREF(pchBuf); /* ignored */ + Assert(!cbBuf); RT_NOREF(cbBuf); /* ignored */ + + RTSTRMBUFMODE enmBufMode; + switch (iBufferingType) + { + case _IOFBF: enmBufMode = RTSTRMBUFMODE_FULL; break; + case _IOLBF: enmBufMode = RTSTRMBUFMODE_LINE; break; + case _IONBF: enmBufMode = RTSTRMBUFMODE_UNBUFFERED; break; + default: AssertFailedReturnStmt(errno = EINVAL, -1); + } + + int rc = RTStrmSetBufferingMode(pFile, enmBufMode); + if (RT_SUCCESS(rc)) + return 0; + errno = RTErrConvertToErrno(rc); + return -1; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(setvbuf); + diff --git a/src/VBox/Runtime/r3/nocrt-tmpfile.cpp b/src/VBox/Runtime/r3/nocrt-tmpfile.cpp new file mode 100644 index 00000000..f8064326 --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-tmpfile.cpp @@ -0,0 +1,59 @@ +/* $Id: nocrt-tmpfile.cpp $ */ +/** @file + * IPRT - No-CRT - tmpfile(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> + + +#undef tmpfile +FILE *RT_NOCRT(tmpfile)(void) +{ + FILE *pFile = NULL; + errno_t rc = tmpfile_s(&pFile); + if (rc == 0) + return pFile; + errno = rc; + return NULL; +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(tmpfile); + diff --git a/src/VBox/Runtime/r3/nocrt-tmpfile_s.cpp b/src/VBox/Runtime/r3/nocrt-tmpfile_s.cpp new file mode 100644 index 00000000..dad6ee7a --- /dev/null +++ b/src/VBox/Runtime/r3/nocrt-tmpfile_s.cpp @@ -0,0 +1,72 @@ +/* $Id: nocrt-tmpfile_s.cpp $ */ +/** @file + * IPRT - No-CRT - tmpfile_s(). + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_NO_CRT_FOR_3RD_PARTY +#include "internal/nocrt.h" +#include <iprt/nocrt/stdio.h> +#include <iprt/nocrt/errno.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/stream.h> + + +#undef tmpfile_s +errno_t RT_NOCRT(tmpfile_s)(FILE **ppFile) +{ + char szPath[RTPATH_MAX]; /* The file is inaccessible by this name, it's just for RTFileOpenTemp's use really. */ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpenTemp(&hFile, szPath, sizeof(szPath), + RTFILE_O_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_TEMP_AUTO_DELETE); + if (RT_SUCCESS(rc)) + { + PRTSTREAM pFile = NULL; + rc = RTStrmOpenFileHandle(hFile, "w+b", 0 /*fFlags*/, &pFile); + if (RT_SUCCESS(rc)) + { + *ppFile = pFile; + return 0; + } + RTFileClose(hFile); + } + return RTErrConvertToErrno(rc); +} +RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(tmpfile_s); + diff --git a/src/VBox/Runtime/r3/nt/Makefile.kup b/src/VBox/Runtime/r3/nt/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/nt/Makefile.kup diff --git a/src/VBox/Runtime/r3/nt/RTFileQueryFsSizes-nt.cpp b/src/VBox/Runtime/r3/nt/RTFileQueryFsSizes-nt.cpp new file mode 100644 index 00000000..63a88211 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTFileQueryFsSizes-nt.cpp @@ -0,0 +1,102 @@ +/* $Id: RTFileQueryFsSizes-nt.cpp $ */ +/** @file + * IPRT - RTFileQueryFsSizes, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/file.h> +#include <iprt/errcore.h> + + + +RTR3DECL(int) RTFileQueryFsSizes(RTFILE hFile, PRTFOFF pcbTotal, RTFOFF *pcbFree, + uint32_t *pcbBlock, uint32_t *pcbSector) +{ + int rc; + + /* + * Get the volume information. + */ + FILE_FS_SIZE_INFORMATION FsSizeInfo; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile((HANDLE)RTFileToNative(hFile), &Ios, + &FsSizeInfo, sizeof(FsSizeInfo), FileFsSizeInformation); + if (NT_SUCCESS(rcNt)) + { + /* + * Calculate the return values. + */ + if (pcbTotal) + { + *pcbTotal = FsSizeInfo.TotalAllocationUnits.QuadPart + * FsSizeInfo.SectorsPerAllocationUnit + * FsSizeInfo.BytesPerSector; + if ( *pcbTotal / FsSizeInfo.SectorsPerAllocationUnit / FsSizeInfo.BytesPerSector + != FsSizeInfo.TotalAllocationUnits.QuadPart) + *pcbTotal = UINT64_MAX; + } + + if (pcbFree) + { + *pcbFree = FsSizeInfo.AvailableAllocationUnits.QuadPart + * FsSizeInfo.SectorsPerAllocationUnit + * FsSizeInfo.BytesPerSector; + if ( *pcbFree / FsSizeInfo.SectorsPerAllocationUnit / FsSizeInfo.BytesPerSector + != FsSizeInfo.AvailableAllocationUnits.QuadPart) + *pcbFree = UINT64_MAX; + } + + rc = VINF_SUCCESS; + if (pcbBlock) + { + *pcbBlock = FsSizeInfo.SectorsPerAllocationUnit * FsSizeInfo.BytesPerSector; + if (*pcbBlock / FsSizeInfo.BytesPerSector != FsSizeInfo.SectorsPerAllocationUnit) + rc = VERR_OUT_OF_RANGE; + } + + if (pcbSector) + *pcbSector = FsSizeInfo.BytesPerSector; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/RTFileSetMode-r3-nt.cpp b/src/VBox/Runtime/r3/nt/RTFileSetMode-r3-nt.cpp new file mode 100644 index 00000000..2ec31d14 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTFileSetMode-r3-nt.cpp @@ -0,0 +1,92 @@ +/* $Id: RTFileSetMode-r3-nt.cpp $ */ +/** @file + * IPRT - RTFileSetMode, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/file.h> +#include <iprt/err.h> + +#include "internal/fs.h" + + +/** + * Common worker for RTFileSetMode, RTPathSetMode and RTDirRelPathSetMode. + * + * @returns IPRT status code. + * @param hNativeFile The NT handle to the file system object. + * @param fMode Valid and normalized file mode mask to set. + */ +DECLHIDDEN(int) rtNtFileSetModeWorker(HANDLE hNativeFile, RTFMODE fMode) +{ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + FILE_BASIC_INFORMATION BasicInfo; + BasicInfo.CreationTime.QuadPart = 0; + BasicInfo.ChangeTime.QuadPart = 0; + BasicInfo.LastAccessTime.QuadPart = 0; + BasicInfo.LastWriteTime.QuadPart = 0; + BasicInfo.FileAttributes = (fMode & ~( RTFS_DOS_NT_ENCRYPTED + | RTFS_DOS_NT_COMPRESSED + | RTFS_DOS_NT_REPARSE_POINT + | RTFS_DOS_NT_SPARSE_FILE + | RTFS_DOS_NT_DEVICE + | RTFS_DOS_DIRECTORY) + & RTFS_DOS_MASK_NT) + >> RTFS_DOS_SHIFT; + Assert(!(BasicInfo.FileAttributes & ~0x31a7U /* FILE_ATTRIBUTE_VALID_SET_FLAGS */)); + if (!BasicInfo.FileAttributes) + BasicInfo.FileAttributes |= FILE_ATTRIBUTE_NORMAL; + + NTSTATUS rcNt = NtSetInformationFile(hNativeFile, &Ios, &BasicInfo, sizeof(BasicInfo), FileBasicInformation); + if (NT_SUCCESS(rcNt)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(rcNt); +} + + +RTDECL(int) RTFileSetMode(RTFILE hFile, RTFMODE fMode) +{ + HANDLE hNative = (HANDLE)RTFileToNative(hFile); + AssertReturn(hNative != RTNT_INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + fMode = rtFsModeNormalize(fMode, NULL, 0, RTFS_TYPE_FILE); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + + return rtNtFileSetModeWorker(hNative, fMode); +} diff --git a/src/VBox/Runtime/r3/nt/RTPathQueryInfo-nt.cpp b/src/VBox/Runtime/r3/nt/RTPathQueryInfo-nt.cpp new file mode 100644 index 00000000..a7b6459b --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTPathQueryInfo-nt.cpp @@ -0,0 +1,680 @@ +/* $Id: RTPathQueryInfo-nt.cpp $ */ +/** @file + * IPRT - RTPathQueryInfo[Ex], Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/path.h> +#include <iprt/err.h> +#include <iprt/time.h> +#include "internal/fs.h" +#include "internal/path.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Helper for comparing a UNICODE_STRING with a string litteral. */ +#define ARE_UNICODE_STRINGS_EQUAL(a_UniStr, a_wszType) \ + ( (a_UniStr)->Length == sizeof(a_wszType) - sizeof(RTUTF16) \ + && memcmp((a_UniStr)->Buffer, a_wszType, sizeof(a_wszType) - sizeof(RTUTF16)) == 0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +typedef NTSTATUS (NTAPI *PFNNTQUERYFULLATTRIBUTESFILE)(struct _OBJECT_ATTRIBUTES *, struct _FILE_NETWORK_OPEN_INFORMATION *); +extern PFNNTQUERYFULLATTRIBUTESFILE g_pfnNtQueryFullAttributesFile; /* init-win.cpp */ + + +/* ASSUMES FileID comes after ShortName and the structs are identical up to that point. */ +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, NextEntryOffset, FILE_ID_BOTH_DIR_INFORMATION, NextEntryOffset); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileIndex , FILE_ID_BOTH_DIR_INFORMATION, FileIndex ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, CreationTime , FILE_ID_BOTH_DIR_INFORMATION, CreationTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastAccessTime , FILE_ID_BOTH_DIR_INFORMATION, LastAccessTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastWriteTime , FILE_ID_BOTH_DIR_INFORMATION, LastWriteTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ChangeTime , FILE_ID_BOTH_DIR_INFORMATION, ChangeTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EndOfFile , FILE_ID_BOTH_DIR_INFORMATION, EndOfFile ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, AllocationSize , FILE_ID_BOTH_DIR_INFORMATION, AllocationSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileAttributes , FILE_ID_BOTH_DIR_INFORMATION, FileAttributes ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileNameLength , FILE_ID_BOTH_DIR_INFORMATION, FileNameLength ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EaSize , FILE_ID_BOTH_DIR_INFORMATION, EaSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortNameLength, FILE_ID_BOTH_DIR_INFORMATION, ShortNameLength); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortName , FILE_ID_BOTH_DIR_INFORMATION, ShortName ); + + + +/** + * Splits up an NT path into directory and filename. + * + * @param pNtName The path to split. + * @param pNtParent Where to return the directory path. + * @param pNtFilename Where to return the filename part. + * @param fNoParentDirSlash Whether to make sure the directory path doesn't + * end with a slash (except root). + */ +static void ntPathNtSplitName(UNICODE_STRING const *pNtName, UNICODE_STRING *pNtParent, UNICODE_STRING *pNtFilename, + bool fNoParentDirSlash) +{ + PRTUTF16 pwszBuffer = pNtName->Buffer; + size_t off = pNtName->Length / sizeof(RTUTF16); + + /* Skip trailing slash if present. */ + if ( off > 0 + && pwszBuffer[off - 1] == '\\') + off--; + + /* Find the slash before that. */ + RTUTF16 wc; + while ( off > 0 + && (wc = pwszBuffer[off - 1]) != '\\' + && wc != '/') + off--; + if (off != 0) + { + pNtParent->Buffer = pwszBuffer; + pNtParent->MaximumLength = pNtParent->Length = (USHORT)(off * sizeof(RTUTF16)); + } + else + { + AssertFailed(); /* This is impossible and won't work (NT doesn't know '.' or '..'). */ + /** @todo query the whole path as it is possible relative. Use the buffer for + * temporary name storage. */ + pNtParent->Buffer = L"."; + pNtParent->Length = 1 * sizeof(RTUTF16); + pNtParent->MaximumLength = 2 * sizeof(RTUTF16); + } + + pNtFilename->Buffer = &pwszBuffer[off]; + pNtFilename->Length = pNtName->Length - (USHORT)(off * sizeof(RTUTF16)); + pNtFilename->MaximumLength = pNtName->MaximumLength - (USHORT)(off * sizeof(RTUTF16)); + + while ( fNoParentDirSlash + && pNtParent->Length > sizeof(RTUTF16) + && pNtParent->Buffer[pNtParent->Length / sizeof(RTUTF16) - 1] == '\\') + pNtParent->Length -= sizeof(RTUTF16); +} + + +/** + * Deals with enmAddAttr != RTFSOBJATTRADD_UNIX. + * + * @returns IPRT status code (usually @a rc). + * @param rc The return code. + * @param pObjInfo The info to complete. + * @param enmAddAttr What to complete it with. Caller should fill in + * RTFSOBJATTRADD_UNIX. + */ +static int rtPathNtQueryInfoFillInDummyData(int rc, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + switch (enmAddAttr) + { + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + break; + + case RTFSOBJATTRADD_NOTHING: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + rc = VERR_INTERNAL_ERROR; + } + return rc; +} + + +/** + * Deal with getting info about something that could be in a directory object. + * + * @returns IPRT status code + * @param pObjAttr The NT object attribute. + * @param pObjInfo Where to return the info. + * @param enmAddAttr Which extra attributes to get (/fake). + * @param fFlags The flags. + * @param pvBuf Query buffer space. + * @param cbBuf Size of the buffer. ASSUMES lots of space. + * @param rcNtCaller The status code that got us here. + */ +static int rtPathNtQueryInfoInDirectoryObject(OBJECT_ATTRIBUTES *pObjAttr, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags, + void *pvBuf, size_t cbBuf, NTSTATUS rcNtCaller) +{ + RT_NOREF(fFlags); + + /* + * Special case: Root dir. + */ + if ( pObjAttr->RootDirectory == NULL + && pObjAttr->ObjectName->Length == sizeof(RTUTF16) + && pObjAttr->ObjectName->Buffer[0] == '\\') + { + pObjInfo->cbObject = 0; + pObjInfo->cbAllocated = 0; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0); + pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + + /* + * We must open and scan the parent directory object. + */ + UNICODE_STRING NtDirName; + UNICODE_STRING NtDirEntry; + ntPathNtSplitName(pObjAttr->ObjectName, &NtDirName, &NtDirEntry, true /*fNoParentDirSlash*/); + + while ( NtDirEntry.Length > sizeof(RTUTF16) + && NtDirEntry.Buffer[NtDirEntry.Length / sizeof(RTUTF16) - 1] == '\\') + NtDirEntry.Length -= sizeof(RTUTF16); + + pObjAttr->ObjectName = &NtDirName; + HANDLE hDir = RTNT_INVALID_HANDLE_VALUE; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, pObjAttr); + if (NT_SUCCESS(rcNt)) + { + ULONG uObjDirCtx = 0; + for (;;) + { + ULONG cbReturned = 0; + rcNt = NtQueryDirectoryObject(hDir, + pvBuf, + (ULONG)cbBuf, + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbReturned); + if (!NT_SUCCESS(rcNt)) + break; + + for (POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)pvBuf; + pObjDir->Name.Length != 0; + pObjDir++) + { + if ( pObjDir->Name.Length == NtDirEntry.Length + && memcmp(pObjDir->Name.Buffer, NtDirEntry.Buffer, NtDirEntry.Length) == 0) + { + /* + * Find it. Fill in the info we've got and return (see similar code in direnum-r3-nt.cpp). + */ + NtClose(hDir); + + pObjInfo->cbObject = 0; + pObjInfo->cbAllocated = 0; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0); + + if (ARE_UNICODE_STRINGS_EQUAL(&pObjDir->TypeName, L"Directory")) + pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + else if (ARE_UNICODE_STRINGS_EQUAL(&pObjDir->TypeName, L"SymbolicLink")) + pObjInfo->Attr.fMode = RTFS_DOS_NT_REPARSE_POINT | RTFS_TYPE_SYMLINK | 0777; + else if (ARE_UNICODE_STRINGS_EQUAL(&pObjDir->TypeName, L"Device")) + pObjInfo->Attr.fMode = RTFS_DOS_NT_DEVICE | RTFS_TYPE_DEV_CHAR | 0666; + else + pObjInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0666; + + pObjInfo->Attr.enmAdditional = enmAddAttr; + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + } + } + + NtClose(hDir); + if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES || rcNt == STATUS_NO_SUCH_FILE) + return VERR_FILE_NOT_FOUND; + } + else + return RTErrConvertFromNtStatus(rcNtCaller); + return RTErrConvertFromNtStatus(rcNt); +} + + +/** + * Queries information from a file or directory handle. + * + * This is shared between the RTPathQueryInfo, RTFileQueryInfo and + * RTDirQueryInfo code. + * + * @returns IPRT status code. + * @param hFile The handle to query information from. Must have + * the necessary privileges. + * @param pvBuf Pointer to a scratch buffer. + * @param cbBuf The size of the buffer. This must be large + * enough to hold a FILE_ALL_INFORMATION struct. + * @param pObjInfo Where to return information about the handle. + * @param enmAddAttr What extra info to return. + * @param pszPath The path if this is a file (for exe detect). + * @param uReparseTag The reparse tag number (0 if not applicable) for + * symlink detection/whatnot. + */ +DECLHIDDEN(int) rtPathNtQueryInfoFromHandle(HANDLE hFile, void *pvBuf, size_t cbBuf, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, const char *pszPath, ULONG uReparseTag) +{ + Assert(cbBuf >= sizeof(FILE_ALL_INFORMATION)); + + /** @todo Try optimize this for when RTFSOBJATTRADD_UNIX isn't set? */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pvBuf, sizeof(FILE_ALL_INFORMATION), FileAllInformation); + if ( NT_SUCCESS(rcNt) + || rcNt == STATUS_BUFFER_OVERFLOW) + { + FILE_ALL_INFORMATION *pAllInfo = (FILE_ALL_INFORMATION *)pvBuf; + pObjInfo->cbObject = pAllInfo->StandardInformation.EndOfFile.QuadPart; + pObjInfo->cbAllocated = pAllInfo->StandardInformation.AllocationSize.QuadPart; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, pAllInfo->BasicInformation.CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, pAllInfo->BasicInformation.LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, pAllInfo->BasicInformation.LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, pAllInfo->BasicInformation.ChangeTime.QuadPart); + pObjInfo->Attr.fMode = rtFsModeFromDos( (pAllInfo->BasicInformation.FileAttributes << RTFS_DOS_SHIFT) + & RTFS_DOS_MASK_NT, + pszPath, pszPath ? strlen(pszPath) : 0, uReparseTag, 0); + pObjInfo->Attr.enmAdditional = enmAddAttr; + if (enmAddAttr == RTFSOBJATTRADD_UNIX) + { + pObjInfo->Attr.u.Unix.uid = ~0U; + pObjInfo->Attr.u.Unix.gid = ~0U; + pObjInfo->Attr.u.Unix.cHardlinks = RT_MAX(1, pAllInfo->StandardInformation.NumberOfLinks); + pObjInfo->Attr.u.Unix.INodeIdDevice = 0; /* below */ + pObjInfo->Attr.u.Unix.INodeId = pAllInfo->InternalInformation.IndexNumber.QuadPart; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + + /* Get the serial number. */ + rcNt = NtQueryVolumeInformationFile(hFile, &Ios, pvBuf, (ULONG)RT_MIN(cbBuf, _2K), FileFsVolumeInformation); + if (NT_SUCCESS(rcNt) || rcNt == STATUS_BUFFER_OVERFLOW) + { + FILE_FS_VOLUME_INFORMATION *pVolInfo = (FILE_FS_VOLUME_INFORMATION *)pvBuf; + pObjInfo->Attr.u.Unix.INodeIdDevice = pVolInfo->VolumeSerialNumber; + } + } + + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + return RTErrConvertFromNtStatus(rcNt); +} + + +/** + * Worker for RTPathQueryInfoEx and RTDirRelPathQueryInfo. + * + * @returns IPRT status code. + * @param hRootDir The root directory that pNtName is relative to. + * @param pNtName The NT path which we want to query info for. + * @param pObjInfo Where to return the info. + * @param enmAddAttr What additional info to get/fake. + * @param fFlags Query flags (RTPATH_F_XXX). + * @param pszPath The path for detecting executables and such. + * Pass empty string if not applicable/available. + */ +DECLHIDDEN(int) rtPathNtQueryInfoWorker(HANDLE hRootDir, UNICODE_STRING *pNtName, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags, const char *pszPath) +{ + /* + * There are a three different ways of doing this: + * 1. Use NtQueryFullAttributesFile to the get basic file info. + * 2. Open whatever the path points to and use NtQueryInformationFile. + * 3. Open the parent directory and use NtQueryDirectoryFile like RTDirReadEx. + * + * The first two options may fail with sharing violations or access denied, + * in which case we must use the last one as fallback. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt; + OBJECT_ATTRIBUTES ObjAttr; + union + { + FILE_NETWORK_OPEN_INFORMATION NetOpenInfo; + FILE_ALL_INFORMATION AllInfo; + FILE_FS_VOLUME_INFORMATION VolInfo; + FILE_BOTH_DIR_INFORMATION Both; + FILE_ID_BOTH_DIR_INFORMATION BothId; + uint8_t abPadding[sizeof(FILE_ID_BOTH_DIR_INFORMATION) + RTPATH_MAX * sizeof(wchar_t)]; + } uBuf; + + /* + * We can only use the first option if no additional UNIX attribs are + * requested and it isn't a symbolic link. NT directory object + */ + int rc = VINF_TRY_AGAIN; + if ( enmAddAttr != RTFSOBJATTRADD_UNIX + && g_pfnNtQueryFullAttributesFile) + { + InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rcNt = g_pfnNtQueryFullAttributesFile(&ObjAttr, &uBuf.NetOpenInfo); + if (NT_SUCCESS(rcNt)) + { + if (!(uBuf.NetOpenInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + { + pObjInfo->cbObject = uBuf.NetOpenInfo.EndOfFile.QuadPart; + pObjInfo->cbAllocated = uBuf.NetOpenInfo.AllocationSize.QuadPart; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, uBuf.NetOpenInfo.CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, uBuf.NetOpenInfo.LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, uBuf.NetOpenInfo.LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, uBuf.NetOpenInfo.ChangeTime.QuadPart); + pObjInfo->Attr.fMode = rtFsModeFromDos((uBuf.NetOpenInfo.FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszPath, strlen(pszPath), 0 /*uReparseTag*/, 0); + pObjInfo->Attr.enmAdditional = enmAddAttr; + + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + } + else if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + || rcNt == STATUS_OBJECT_NAME_INVALID + || rcNt == STATUS_INVALID_PARAMETER) + { + rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt); + if (RT_SUCCESS(rc)) + return rc; + } + else if ( rcNt != STATUS_ACCESS_DENIED + && rcNt != STATUS_SHARING_VIOLATION) + rc = RTErrConvertFromNtStatus(rcNt); + else + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + } + + /* + * Try the 2nd option. We might have to redo this if not following symbolic + * links and the reparse point isn't a symbolic link but a mount point or similar. + * We want to return information about the mounted root directory if we can, not + * the directory in which it was mounted. + */ + if (rc == VINF_TRY_AGAIN) + { + static int volatile g_fReparsePoints = -1; + uint32_t fOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT; + int fReparsePoints = g_fReparsePoints; + if (fReparsePoints != 0 && !(fFlags & RTPATH_F_FOLLOW_LINK)) + fOptions |= FILE_OPEN_REPARSE_POINT; + + InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rcNt = NtCreateFile(&hFile, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + fOptions, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if ( ( rcNt == STATUS_INVALID_PARAMETER + || rcNt == STATUS_INVALID_PARAMETER_9) + && fReparsePoints == -1 + && (fOptions & FILE_OPEN_REPARSE_POINT)) + { + fOptions &= ~FILE_OPEN_REPARSE_POINT; + rcNt = NtCreateFile(&hFile, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + fOptions, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if (rcNt != STATUS_INVALID_PARAMETER) + g_fReparsePoints = fReparsePoints = 0; + } + if (NT_SUCCESS(rcNt)) + { + /* Query tag information first in order to try re-open non-symlink reparse points. */ + FILE_ATTRIBUTE_TAG_INFORMATION TagInfo; + rcNt = NtQueryInformationFile(hFile, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation); + if (!NT_SUCCESS(rcNt)) + TagInfo.FileAttributes = TagInfo.ReparseTag = 0; + if ( !(TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + || TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK + || (fFlags & RTPATH_F_FOLLOW_LINK)) + { /* likely */ } + else + { + /* Reparse point that isn't a symbolic link, try follow the reparsing. */ + HANDLE hFile2; + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtCreateFile(&hFile2, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if (NT_SUCCESS(rcNt)) + { + NtClose(hFile); + hFile = hFile2; + TagInfo.FileAttributes = TagInfo.ReparseTag = 0; + } + } + + /* + * Get the information we need and convert it. + */ + rc = rtPathNtQueryInfoFromHandle(hFile, &uBuf, sizeof(uBuf), pObjInfo, enmAddAttr, pszPath, TagInfo.ReparseTag); + NtClose(hFile); + if (RT_SUCCESS(rc)) + return rc; + + if (RT_FAILURE(rc)) + rc = VINF_TRY_AGAIN; + } + else if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + || rcNt == STATUS_OBJECT_NAME_INVALID + /*|| rcNt == STATUS_INVALID_PARAMETER*/) + { + rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt); + if (RT_SUCCESS(rc)) + return rc; + } + else if ( rcNt != STATUS_ACCESS_DENIED + && rcNt != STATUS_SHARING_VIOLATION) + rc = RTErrConvertFromNtStatus(rcNt); + else + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + } + + /* + * Try the 3rd option if none of the other worked. + * If none of the above worked, try open the directory and enumerate + * the file we're after. This + */ + if (rc == VINF_TRY_AGAIN) + { + /* Split up the name into parent directory path and filename. */ + UNICODE_STRING NtDirName; + UNICODE_STRING NtFilter; + ntPathNtSplitName(pNtName, &NtDirName, &NtFilter, false /*fNoParentDirSlash*/); + + /* Try open the directory. */ + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rcNt = NtCreateFile(&hFile, + FILE_LIST_DIRECTORY | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if (NT_SUCCESS(rcNt)) + { + FILE_INFORMATION_CLASS enmInfoClass; + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */) + enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */ + else + enmInfoClass = FileBothDirectoryInformation; + rcNt = NtQueryDirectoryFile(hFile, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + &uBuf, + RT_MIN(sizeof(uBuf), 0xfff0), + enmInfoClass, + TRUE /*ReturnSingleEntry */, + &NtFilter, + FALSE /*RestartScan */); + if (NT_SUCCESS(rcNt)) + { + pObjInfo->cbObject = uBuf.Both.EndOfFile.QuadPart; + pObjInfo->cbAllocated = uBuf.Both.AllocationSize.QuadPart; + + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, uBuf.Both.CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, uBuf.Both.LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, uBuf.Both.LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, uBuf.Both.ChangeTime.QuadPart); + + pObjInfo->Attr.fMode = rtFsModeFromDos((uBuf.Both.FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszPath, strlen(pszPath), uBuf.Both.EaSize, 0); + + pObjInfo->Attr.enmAdditional = enmAddAttr; + if (enmAddAttr == RTFSOBJATTRADD_UNIX) + { + pObjInfo->Attr.u.Unix.uid = ~0U; + pObjInfo->Attr.u.Unix.gid = ~0U; + pObjInfo->Attr.u.Unix.cHardlinks = 1; + pObjInfo->Attr.u.Unix.INodeIdDevice = 0; /* below */ + pObjInfo->Attr.u.Unix.INodeId = enmInfoClass == FileIdBothDirectoryInformation + ? uBuf.BothId.FileId.QuadPart : 0; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + + /* Get the serial number. */ + rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &uBuf, RT_MIN(sizeof(uBuf), _2K), + FileFsVolumeInformation); + if (NT_SUCCESS(rcNt)) + pObjInfo->Attr.u.Unix.INodeIdDevice = uBuf.VolInfo.VolumeSerialNumber; + } + + rc = rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + NtClose(hFile); + } + /* + * Quite possibly a object directory. + */ + else if ( rcNt == STATUS_OBJECT_NAME_INVALID /* with trailing slash */ + || rcNt == STATUS_OBJECT_TYPE_MISMATCH /* without trailing slash */ ) + { + InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt); + if (RT_FAILURE(rc)) + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + } + + return rc; +} + + +RTR3DECL(int) RTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + AssertMsgReturn( enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING + && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + ("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs), + VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + + /* + * Convert the input path and call common worker. + */ + HANDLE hRootDir; + UNICODE_STRING NtName; + int rc = RTNtPathFromWinUtf8(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + rc = rtPathNtQueryInfoWorker(hRootDir, &NtName, pObjInfo, enmAdditionalAttribs, fFlags, pszPath); + RTNtPathFree(&NtName, &hRootDir); + } + return rc; +} + + +RTR3DECL(int) RTPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + return RTPathQueryInfoEx(pszPath, pObjInfo, enmAdditionalAttribs, RTPATH_F_ON_LINK); +} + diff --git a/src/VBox/Runtime/r3/nt/RTPathSetMode-r3-nt.cpp b/src/VBox/Runtime/r3/nt/RTPathSetMode-r3-nt.cpp new file mode 100644 index 00000000..d6fb0444 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTPathSetMode-r3-nt.cpp @@ -0,0 +1,98 @@ +/* $Id: RTPathSetMode-r3-nt.cpp $ */ +/** @file + * IPRT - RTPathSetMode, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/path.h> +#include <iprt/err.h> + +#include "internal/fs.h" + + + +RTDECL(int) RTPathSetMode(const char *pszPath, RTFMODE fMode) +{ + fMode = rtFsModeNormalize(fMode, pszPath, 0, 0); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRootDir; + int rc = RTNtPathFromWinUtf8(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + HANDLE hPath = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRootDir, NULL); + + ULONG fOpenOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT; + //if (fFlags & RTPATH_F_ON_LINK) + // fOpenOptions |= FILE_OPEN_REPARSE_POINT; + NTSTATUS rcNt = NtCreateFile(&hPath, + FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + fOpenOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + rc = rtNtFileSetModeWorker(hPath, fMode); + + rcNt = NtClose(hPath); + if (!NT_SUCCESS(rcNt) && RT_SUCCESS(rc)) + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathFree(&NtName, &hRootDir); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/RTProcQueryParent-r3-nt.cpp b/src/VBox/Runtime/r3/nt/RTProcQueryParent-r3-nt.cpp new file mode 100644 index 00000000..650131d1 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTProcQueryParent-r3-nt.cpp @@ -0,0 +1,103 @@ +/* $Id: RTProcQueryParent-r3-nt.cpp $ */ +/** @file + * IPRT - Process, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <iprt/nt/nt.h> + +#include <iprt/process.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> + + + +RTR3DECL(int) RTProcQueryParent(RTPROCESS hProcess, PRTPROCESS phParent) +{ + NTSTATUS rcNt; + HANDLE hClose = RTNT_INVALID_HANDLE_VALUE; + HANDLE hNtProc; + + /* + * Open the process. We take a shortcut if it's the current process. + */ + if (hProcess == RTProcSelf()) + hNtProc = NtCurrentProcess(); + else + { + CLIENT_ID ClientId; + ClientId.UniqueProcess = (HANDLE)(uintptr_t)hProcess; + ClientId.UniqueThread = NULL; + + OBJECT_ATTRIBUTES ObjAttrs; + InitializeObjectAttributes(&ObjAttrs, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL); + + rcNt = NtOpenProcess(&hClose, PROCESS_QUERY_LIMITED_INFORMATION, &ObjAttrs, &ClientId); + if (!NT_SUCCESS(rcNt)) + rcNt = NtOpenProcess(&hClose, PROCESS_QUERY_INFORMATION, &ObjAttrs, &ClientId); + if (!NT_SUCCESS(rcNt)) + return RTErrConvertFromNtStatus(rcNt); + hNtProc = hClose; + } + + /* + * Query the information. + */ + int rc; + PROCESS_BASIC_INFORMATION BasicInfo; + ULONG cbIgn; + rcNt = NtQueryInformationProcess(hNtProc, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), &cbIgn); + if (NT_SUCCESS(rcNt)) + { + *phParent = BasicInfo.InheritedFromUniqueProcessId; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + /* + * Clean up. + */ + if (hClose != RTNT_INVALID_HANDLE_VALUE) + NtClose(hClose); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp b/src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp new file mode 100644 index 00000000..0325e842 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp @@ -0,0 +1,1011 @@ +/* $Id: direnum-r3-nt.cpp $ */ +/** @file + * IPRT - Directory Enumeration, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include "internal-r3-nt.h" + +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include <iprt/utf16.h> +#include "internal/fs.h" +#include "internal/dir.h" +#include "internal/path.h" +#include "../win/internal-r3-win.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Whether to return a single record (TRUE) or multiple (FALSE). */ +#define RTDIR_NT_SINGLE_RECORD FALSE + +/** Go hard on record chaining (has slight performance impact). */ +#ifdef RT_STRICT +# define RTDIR_NT_STRICT +#endif + + +/* ASSUMES FileID comes after ShortName and the structs are identical up to that point. */ +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, NextEntryOffset, FILE_ID_BOTH_DIR_INFORMATION, NextEntryOffset); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileIndex , FILE_ID_BOTH_DIR_INFORMATION, FileIndex ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, CreationTime , FILE_ID_BOTH_DIR_INFORMATION, CreationTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastAccessTime , FILE_ID_BOTH_DIR_INFORMATION, LastAccessTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastWriteTime , FILE_ID_BOTH_DIR_INFORMATION, LastWriteTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ChangeTime , FILE_ID_BOTH_DIR_INFORMATION, ChangeTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EndOfFile , FILE_ID_BOTH_DIR_INFORMATION, EndOfFile ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, AllocationSize , FILE_ID_BOTH_DIR_INFORMATION, AllocationSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileAttributes , FILE_ID_BOTH_DIR_INFORMATION, FileAttributes ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileNameLength , FILE_ID_BOTH_DIR_INFORMATION, FileNameLength ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EaSize , FILE_ID_BOTH_DIR_INFORMATION, EaSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortNameLength, FILE_ID_BOTH_DIR_INFORMATION, ShortNameLength); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortName , FILE_ID_BOTH_DIR_INFORMATION, ShortName ); + + + +size_t rtDirNativeGetStructSize(const char *pszPath) +{ + NOREF(pszPath); + return sizeof(RTDIRINTERNAL); +} + + +int rtDirNativeOpen(PRTDIRINTERNAL pDir, uintptr_t hRelativeDir, void *pvNativeRelative) +{ + /* + * Convert the filter to UTF-16. + */ + int rc; + pDir->pNtFilterStr = NULL; + if ( pDir->cchFilter > 0 + && pDir->enmFilter == RTDIRFILTER_WINNT) + { + PRTUTF16 pwszTmp; + rc = RTStrToUtf16(pDir->pszFilter, &pwszTmp); + if (RT_FAILURE(rc)) + return rc; + pDir->NtFilterStr.Buffer = pwszTmp; + pDir->NtFilterStr.Length = pDir->NtFilterStr.MaximumLength = (uint16_t)(RTUtf16Len(pwszTmp) * sizeof(RTUTF16)); + pDir->pNtFilterStr = &pDir->NtFilterStr; + } + + /* + * Try open the directory + */ +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + bool fObjDir = false; +#endif + if (hRelativeDir != ~(uintptr_t)0 && pvNativeRelative == NULL) + { + /* Caller already opened it, easy! */ + pDir->hDir = (HANDLE)hRelativeDir; + rc = VINF_SUCCESS; + } + else + { + /* + * If we have to check for reparse points, this gets complicated! + */ + static int volatile g_fReparsePoints = -1; + uint32_t fOptions = FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT; + int fReparsePoints = g_fReparsePoints; + if ( fReparsePoints != 0 + && (pDir->fFlags & RTDIR_F_NO_FOLLOW) + && !pDir->fDirSlash) + fOptions |= FILE_OPEN_REPARSE_POINT; + + ACCESS_MASK fDesiredAccess = FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_TRAVERSE | SYNCHRONIZE; + for (;;) + { + if (pvNativeRelative == NULL) + rc = RTNtPathOpenDir(pDir->pszPath, + fDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE, + fOptions, + OBJ_CASE_INSENSITIVE, + &pDir->hDir, +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + &fObjDir +#else + NULL +#endif + ); + else + rc = RTNtPathOpenDirEx((HANDLE)hRelativeDir, + (struct _UNICODE_STRING *)pvNativeRelative, + fDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE, + fOptions, + OBJ_CASE_INSENSITIVE, + &pDir->hDir, +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + &fObjDir +#else + NULL +#endif + ); + if ( rc == VERR_ACCESS_DENIED /* Seen with c:\windows\system32\com\dmp on w7 & w10 (admin mode). */ + && (fDesiredAccess & FILE_TRAVERSE)) + { + fDesiredAccess &= ~FILE_TRAVERSE; + continue; + } + if ( !(fOptions & FILE_OPEN_REPARSE_POINT) + || (rc != VINF_SUCCESS && rc != VERR_INVALID_PARAMETER) ) + break; + if (rc == VINF_SUCCESS) + { + if (fReparsePoints == -1) + g_fReparsePoints = 1; + + /* + * We now need to check if we opened a symbolic directory link. + * (These can be enumerated, but contains only '.' and '..'.) + */ + FILE_ATTRIBUTE_TAG_INFORMATION TagInfo = { 0, 0 }; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryInformationFile(pDir->hDir, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation); + AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + if (!NT_SUCCESS(rcNt)) + TagInfo.FileAttributes = TagInfo.ReparseTag = 0; + if (!(TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + break; + + NtClose(pDir->hDir); + pDir->hDir = RTNT_INVALID_HANDLE_VALUE; + + if (TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK) + { + rc = VERR_IS_A_SYMLINK; + break; + } + + /* Reparse point that isn't a symbolic link, try follow the reparsing. */ + } + else if (fReparsePoints == -1) + g_fReparsePoints = fReparsePoints = 0; + fOptions &= ~FILE_OPEN_REPARSE_POINT; + } + + } + if (RT_SUCCESS(rc)) + { + /* + * Init data. + */ + pDir->fDataUnread = false; /* spelling it out */ + pDir->uDirDev = 0; +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (fObjDir) + pDir->enmInfoClass = FileMaximumInformation; /* object directory. */ +#endif + } + return rc; +} + + +RTDECL(int) RTDirClose(RTDIR hDir) +{ + PRTDIRINTERNAL pDir = hDir; + + /* + * Validate input. + */ + if (!pDir) + return VERR_INVALID_PARAMETER; + if (pDir->u32Magic != RTDIR_MAGIC) + { + AssertMsgFailed(("Invalid pDir=%p\n", pDir)); + return VERR_INVALID_PARAMETER; + } + + /* + * Close the handle. + */ + pDir->u32Magic = ~RTDIR_MAGIC; + if (pDir->hDir != RTNT_INVALID_HANDLE_VALUE) + { + int rc = RTNtPathClose(pDir->hDir); + AssertRC(rc); + pDir->hDir = RTNT_INVALID_HANDLE_VALUE; + } + RTStrFree(pDir->pszName); + pDir->pszName = NULL; + RTUtf16Free(pDir->NtFilterStr.Buffer); + pDir->NtFilterStr.Buffer = NULL; + RTMemFree(pDir->pabBuffer); + pDir->pabBuffer = NULL; + RTMemFree(pDir); + + return VINF_SUCCESS; +} + + +/** + * Checks the validity of the current record. + * + * @returns IPRT status code + * @param pThis The directory instance data. + */ +static int rtDirNtCheckRecord(PRTDIRINTERNAL pThis) +{ +#if defined(RTDIR_NT_STRICT) || defined(RT_ARCH_X86) +# ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pThis->enmInfoClass != FileMaximumInformation) +# endif + { + uintptr_t uEndAddr; + if (pThis->enmInfoClass == FileIdBothDirectoryInformation) + uEndAddr = (uintptr_t)&pThis->uCurData.pBothId->FileName[0]; + else + uEndAddr = (uintptr_t)&pThis->uCurData.pBoth->FileName[0]; + +# ifdef RT_ARCH_X86 + /* Workaround for NT 3.1 bug where FAT returns a too short buffer length. + Including all NT 3.x versions in case it bug was fixed till NT 4. */ + uintptr_t const uEndBuffer = (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer]; + if ( uEndAddr < uEndBuffer + && uEndAddr + pThis->uCurData.pBoth->FileNameLength <= uEndBuffer) + { /* likely */ } + else if ( ( g_enmWinVer == kRTWinOSType_NT310 + || g_enmWinVer == kRTWinOSType_NT350 // not sure when it was fixed... + || g_enmWinVer == kRTWinOSType_NT351) + && pThis->enmInfoClass == FileBothDirectoryInformation) + { + size_t cbLeft = (uintptr_t)&pThis->pabBuffer[pThis->cbBufferAlloc] - (uintptr_t)pThis->uCurData.pBoth; + if ( cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName) + && pThis->uCurData.pBoth->FileNameLength > 0 + && cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName) + pThis->uCurData.pBoth->FileNameLength) + { + pThis->cbBuffer = ((uintptr_t)&pThis->uCurData.pBoth->FileName[0] + pThis->uCurData.pBoth->FileNameLength) + - (uintptr_t)&pThis->pabBuffer[0]; + } + } +# endif + +# ifdef RTDIR_NT_STRICT + AssertReturn(uEndAddr < (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE); + AssertReturn(pThis->uCurData.pBoth->FileNameLength < _64K, VERR_FILENAME_TOO_LONG); + AssertReturn((pThis->uCurData.pBoth->FileNameLength & 1) == 0, VERR_IO_GEN_FAILURE); + + uEndAddr += pThis->uCurData.pBoth->FileNameLength; + AssertReturn(uEndAddr <= (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE); + + AssertReturn((unsigned)pThis->uCurData.pBoth->ShortNameLength <= sizeof(pThis->uCurData.pBoth->ShortName), + VERR_IO_GEN_FAILURE); +# endif + } +#else + RT_NOREF_PV(pThis); +#endif + + return VINF_SUCCESS; +} + + +/** + * Advances the buffer pointer. + * + * @param pThis The directory instance data. + */ +static int rtDirNtAdvanceBuffer(PRTDIRINTERNAL pThis) +{ + int rc; + +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pThis->enmInfoClass == FileMaximumInformation) + { + pThis->uCurData.pObjDir++; + pThis->fDataUnread = pThis->uCurData.pObjDir->Name.Length != 0; + return VINF_SUCCESS; + } +#endif + + pThis->fDataUnread = false; + + uint32_t const offNext = pThis->uCurData.pBoth->NextEntryOffset; + if (offNext == 0) + return VINF_SUCCESS; + +#ifdef RTDIR_NT_STRICT + /* Make sure the next-record offset is beyond the current record. */ + size_t cbRec; + if (pThis->enmInfoClass == FileIdBothDirectoryInformation) + cbRec = RT_UOFFSETOF(FILE_ID_BOTH_DIR_INFORMATION, FileName); + else + cbRec = RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName); + cbRec += pThis->uCurData.pBoth->FileNameLength; + AssertReturn(offNext >= cbRec, VERR_IO_GEN_FAILURE); +#endif + pThis->uCurData.u += offNext; + + rc = rtDirNtCheckRecord(pThis); + pThis->fDataUnread = RT_SUCCESS(rc); + return rc; +} + + +/** + * Fetches more data from the file system. + * + * @returns IPRT status code + * @param pThis The directory instance data. + */ +static int rtDirNtFetchMore(PRTDIRINTERNAL pThis) +{ + Assert(!pThis->fDataUnread); + + /* + * Allocate the buffer the first time around. + * We do this in lazy fashion as some users of RTDirOpen will not actually + * list any files, just open it for various reasons. + * + * We also reduce the buffer size for networked devices as the windows 7-8.1, + * server 2012, ++ CIFS servers or/and IFSes screws up buffers larger than 64KB. + * There is an alternative hack below, btw. We'll leave both in for now. + */ + bool fFirst = false; + if (!pThis->pabBuffer) + { + pThis->cbBufferAlloc = _256K; + if (true) /** @todo skip for known local devices, like the boot device? */ + { + IO_STATUS_BLOCK Ios2 = RTNT_IO_STATUS_BLOCK_INITIALIZER; + FILE_FS_DEVICE_INFORMATION Info = { 0, 0 }; + NTSTATUS rcNt2 = NtQueryVolumeInformationFile(pThis->hDir, &Ios2, &Info, sizeof(Info), FileFsDeviceInformation); + if ( !NT_SUCCESS(rcNt2) + || (Info.Characteristics & FILE_REMOTE_DEVICE) + || Info.DeviceType == FILE_DEVICE_NETWORK + || Info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM + || Info.DeviceType == FILE_DEVICE_NETWORK_REDIRECTOR + || Info.DeviceType == FILE_DEVICE_SMB) + pThis->cbBufferAlloc = _64K; + } + + fFirst = false; + pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); + if (!pThis->pabBuffer) + { + do + { + pThis->cbBufferAlloc /= 4; + pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); + } while (pThis->pabBuffer == NULL && pThis->cbBufferAlloc > _4K); + if (!pThis->pabBuffer) + return VERR_NO_MEMORY; + } + + /* + * Also try determining the device number. + */ + PFILE_FS_VOLUME_INFORMATION pVolInfo = (PFILE_FS_VOLUME_INFORMATION)pThis->pabBuffer; + pVolInfo->VolumeSerialNumber = 0; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(pThis->hDir, &Ios, + pVolInfo, RT_MIN(_2K, pThis->cbBufferAlloc), + FileFsVolumeInformation); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + pThis->uDirDev = pVolInfo->VolumeSerialNumber; + else + pThis->uDirDev = 0; + AssertCompile(sizeof(pThis->uDirDev) == sizeof(pVolInfo->VolumeSerialNumber)); + /** @todo Grow RTDEV to 64-bit and add low dword of VolumeCreationTime to the top of uDirDev. */ + } + + /* + * Read more. + */ + NTSTATUS rcNt; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + if (pThis->enmInfoClass != (FILE_INFORMATION_CLASS)0) + { +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pThis->enmInfoClass == FileMaximumInformation) + { + Ios.Information = 0; + Ios.Status = rcNt = NtQueryDirectoryObject(pThis->hDir, + pThis->pabBuffer, + pThis->cbBufferAlloc, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->fRestartScan, + &pThis->uObjDirCtx, + (PULONG)&Ios.Information); + } + else +#endif + rcNt = NtQueryDirectoryFile(pThis->hDir, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + pThis->pabBuffer, + pThis->cbBufferAlloc, + pThis->enmInfoClass, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->pNtFilterStr, + pThis->fRestartScan); + } + else + { + /* + * The first time around we have to figure which info class we can use + * as well as the right buffer size. We prefer an info class which + * gives us file IDs (Vista+ IIRC) and we prefer large buffers (for long + * ReFS file names and such), but we'll settle for whatever works... + * + * The windows 7 thru 8.1 CIFS servers have been observed to have + * trouble with large buffers, but weirdly only when listing large + * directories. Seems 0x10000 is the max. (Samba does not exhibit + * these problems, of course.) + * + * This complicates things. The buffer size issues causes an + * STATUS_INVALID_PARAMETER error. Now, you would expect the lack of + * FileIdBothDirectoryInformation support to return + * STATUS_INVALID_INFO_CLASS, but I'm not entirely sure if we can 100% + * depend on third IFSs to get that right. Nor, am I entirely confident + * that we can depend on them to check the class before the buffer size. + * + * Thus the mess. + */ + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */) + pThis->enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */ + else + pThis->enmInfoClass = FileBothDirectoryInformation; + rcNt = NtQueryDirectoryFile(pThis->hDir, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + pThis->pabBuffer, + pThis->cbBufferAlloc, + pThis->enmInfoClass, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->pNtFilterStr, + pThis->fRestartScan); + if (NT_SUCCESS(rcNt)) + { /* likely */ } + else + { + bool fRestartScan = pThis->fRestartScan; + for (unsigned iRetry = 0; iRetry < 2; iRetry++) + { + if ( rcNt == STATUS_INVALID_INFO_CLASS + || rcNt == STATUS_INVALID_PARAMETER_8 + || iRetry != 0) + pThis->enmInfoClass = FileBothDirectoryInformation; + + uint32_t cbBuffer = pThis->cbBufferAlloc; + if ( rcNt == STATUS_INVALID_PARAMETER + || rcNt == STATUS_INVALID_PARAMETER_7 + || rcNt == STATUS_INVALID_NETWORK_RESPONSE + || iRetry != 0) + { + cbBuffer = RT_MIN(cbBuffer / 2, _64K); + fRestartScan = true; + } + + for (;;) + { + rcNt = NtQueryDirectoryFile(pThis->hDir, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + pThis->pabBuffer, + cbBuffer, + pThis->enmInfoClass, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->pNtFilterStr, + fRestartScan); + if ( NT_SUCCESS(rcNt) + || cbBuffer == pThis->cbBufferAlloc + || cbBuffer <= sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260) + break; + + /* Reduce the buffer size agressivly and try again. We fall back to + FindFirstFile values for the final lap. This means we'll do 4 rounds + with the current initial buffer size (64KB, 8KB, 1KB, 0x278/0x268). */ + cbBuffer /= 8; + if (cbBuffer < 1024) + cbBuffer = pThis->enmInfoClass == FileIdBothDirectoryInformation + ? sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260 + : sizeof(*pThis->uCurData.pBoth) + sizeof(WCHAR) * 260; + } + if (NT_SUCCESS(rcNt)) + { + pThis->cbBufferAlloc = cbBuffer; + break; + } + } + } + } + if (!NT_SUCCESS(rcNt)) + { + /* Note! VBoxSVR and CIFS file systems both ends up with STATUS_NO_SUCH_FILE here instead of STATUS_NO_MORE_FILES. */ + if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES || rcNt == STATUS_NO_SUCH_FILE) + return VERR_NO_MORE_FILES; + return RTErrConvertFromNtStatus(rcNt); + } + pThis->fRestartScan = false; + AssertMsg( Ios.Information + > (pThis->enmInfoClass == FileMaximumInformation ? sizeof(*pThis->uCurData.pObjDir) : sizeof(*pThis->uCurData.pBoth)), + ("Ios.Information=%#x\n", Ios.Information)); + + /* + * Set up the data members. + */ + pThis->uCurData.u = (uintptr_t)pThis->pabBuffer; + pThis->cbBuffer = Ios.Information; + + int rc = rtDirNtCheckRecord(pThis); + pThis->fDataUnread = RT_SUCCESS(rc); + + return rc; +} + + +/** + * Converts the name from UTF-16 to UTF-8. + * + * Fortunately, the names are relative to the directory, so we won't have to do + * any sweaty path style coversion. :-) + * + * @returns IPRT status code + * @param pThis The directory instance data. + * @param cbName The file name length in bytes. + * @param pwsName The file name, not terminated. + */ +static int rtDirNtConvertName(PRTDIRINTERNAL pThis, uint32_t cbName, PCRTUTF16 pwsName) +{ + int rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName); + if (RT_SUCCESS(rc)) + { + if (!pThis->cbNameAlloc) + pThis->cbNameAlloc = pThis->cchName + 1; + } + else if (rc == VERR_BUFFER_OVERFLOW) + { + RTStrFree(pThis->pszName); + pThis->pszName = NULL; + pThis->cbNameAlloc = 0; + + rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName); + if (RT_SUCCESS(rc)) + pThis->cbNameAlloc = pThis->cchName + 1; + } + Assert(RT_SUCCESS(rc) ? pThis->pszName != NULL : pThis->pszName == NULL); + return rc; +} + + +/** + * Converts the name of the current record. + * + * @returns IPRT status code. + * @param pThis The directory instance data. + */ +static int rtDirNtConvertCurName(PRTDIRINTERNAL pThis) +{ + switch (pThis->enmInfoClass) + { + case FileIdBothDirectoryInformation: + return rtDirNtConvertName(pThis, pThis->uCurData.pBothId->FileNameLength, pThis->uCurData.pBothId->FileName); + case FileBothDirectoryInformation: + return rtDirNtConvertName(pThis, pThis->uCurData.pBoth->FileNameLength, pThis->uCurData.pBoth->FileName); +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + case FileMaximumInformation: + return rtDirNtConvertName(pThis, pThis->uCurData.pObjDir->Name.Length, pThis->uCurData.pObjDir->Name.Buffer); +#endif + + default: + AssertFailedReturn(VERR_INTERNAL_ERROR_3); + } +} + + +RTDECL(int) RTDirRead(RTDIR hDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry) +{ + PRTDIRINTERNAL pDir = hDir; + int rc; + + /* + * Validate input. + */ + AssertPtrReturn(pDir, VERR_INVALID_POINTER); + AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER); + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + cbDirEntry = *pcbDirEntry; + AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRY, szName[2]), + ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRY, szName[2])), + VERR_INVALID_PARAMETER); + } + + /* + * Fetch data? + */ + if (!pDir->fDataUnread) + { + rc = rtDirNtFetchMore(pDir); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Convert the filename to UTF-8. + */ + rc = rtDirNtConvertCurName(pDir); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRY, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired > cbDirEntry) + return VERR_BUFFER_OVERFLOW; + + /* + * Setup the returned data. + */ + pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + + pDirEntry->INodeId = pDir->enmInfoClass == FileIdBothDirectoryInformation + ? pDir->uCurData.pBothId->FileId.QuadPart : 0; + +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pDir->enmInfoClass != FileMaximumInformation) +#endif + { + switch ( pDir->uCurData.pBoth->FileAttributes + & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) + { + default: + AssertFailed(); + case 0: + pDirEntry->enmType = RTDIRENTRYTYPE_FILE; + break; + + case FILE_ATTRIBUTE_DIRECTORY: + pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY; + break; + + case FILE_ATTRIBUTE_REPARSE_POINT: + case FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY: + /* EaSize is here reused for returning the repharse tag value. */ + if (pDir->uCurData.pBoth->EaSize == IO_REPARSE_TAG_SYMLINK) + pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK; + break; + } + } +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + else + { + pDirEntry->enmType = RTDIRENTRYTYPE_UNKNOWN; + if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("Directory"))) + pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY; + else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("SymbolicLink"))) + pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK; + } +#endif + + return rtDirNtAdvanceBuffer(pDir); +} + + +RTDECL(int) RTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, + RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + PRTDIRINTERNAL pDir = hDir; + int rc; + + /* + * Validate input. + */ + AssertPtrReturn(pDir, VERR_INVALID_POINTER); + AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER); + + AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + cbDirEntry = *pcbDirEntry; + AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]), + ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])), + VERR_INVALID_PARAMETER); + } + + /* + * Fetch data? + */ + if (!pDir->fDataUnread) + { + rc = rtDirNtFetchMore(pDir); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Convert the filename to UTF-8. + */ + rc = rtDirNtConvertCurName(pDir); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRYEX, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired > cbDirEntry) + return VERR_BUFFER_OVERFLOW; + + /* + * Setup the returned data. + */ + PFILE_BOTH_DIR_INFORMATION pBoth = pDir->uCurData.pBoth; + + pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName)); +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pDir->enmInfoClass != FileMaximumInformation) +#endif + { + uint8_t cbShort = pBoth->ShortNameLength; + if (cbShort > 0) + { + AssertStmt(cbShort < sizeof(pDirEntry->wszShortName), cbShort = sizeof(pDirEntry->wszShortName) - 2); + memcpy(pDirEntry->wszShortName, pBoth->ShortName, cbShort); + pDirEntry->cwcShortName = cbShort / 2; + } + else + pDirEntry->cwcShortName = 0; + + pDirEntry->Info.cbObject = pBoth->EndOfFile.QuadPart; + pDirEntry->Info.cbAllocated = pBoth->AllocationSize.QuadPart; + + Assert(sizeof(uint64_t) == sizeof(pBoth->CreationTime)); + RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, pBoth->CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, pBoth->LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, pBoth->LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, pBoth->ChangeTime.QuadPart); + + pDirEntry->Info.Attr.fMode = rtFsModeFromDos((pBoth->FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszName, cchName, pBoth->EaSize, 0); + } +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + else + { + pDirEntry->cwcShortName = 0; + pDirEntry->Info.cbObject = 0; + pDirEntry->Info.cbAllocated = 0; + RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, 0); + RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, 0); + RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, 0); + RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, 0); + + if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("Directory"))) + pDirEntry->Info.Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("SymbolicLink"))) + pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_REPARSE_POINT | RTFS_TYPE_SYMLINK | 0777; + else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("Device"))) + pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_DEVICE | RTFS_TYPE_DEV_CHAR | 0666; + else + pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0666; + } +#endif + + /* + * Requested attributes (we cannot provide anything actually). + */ + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_EASIZE: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pDir->enmInfoClass == FileMaximumInformation) + pDirEntry->Info.Attr.u.EASize.cb = 0; + else +#endif + pDirEntry->Info.Attr.u.EASize.cb = pBoth->EaSize; + break; + + case RTFSOBJATTRADD_UNIX: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pDirEntry->Info.Attr.u.Unix.uid = NIL_RTUID; + pDirEntry->Info.Attr.u.Unix.gid = NIL_RTGID; + pDirEntry->Info.Attr.u.Unix.cHardlinks = 1; + pDirEntry->Info.Attr.u.Unix.INodeIdDevice = pDir->uDirDev; + pDirEntry->Info.Attr.u.Unix.INodeId = 0; + if ( pDir->enmInfoClass == FileIdBothDirectoryInformation + && pDir->uCurData.pBothId->FileId.QuadPart != UINT64_MAX) + pDirEntry->Info.Attr.u.Unix.INodeId = pDir->uCurData.pBothId->FileId.QuadPart; + pDirEntry->Info.Attr.u.Unix.fFlags = 0; + pDirEntry->Info.Attr.u.Unix.GenerationId = 0; + pDirEntry->Info.Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_NOTHING: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pDirEntry->Info.Attr.u.UnixOwner.uid = NIL_RTUID; + pDirEntry->Info.Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pDirEntry->Info.Attr.u.UnixGroup.gid = NIL_RTGID; + pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0'; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR; + } + + /* + * Follow links if requested. + */ + if ( (fFlags & RTPATH_F_FOLLOW_LINK) + && RTFS_IS_SYMLINK(fFlags)) + { + /** @todo Symlinks: Find[First|Next]FileW will return info about + the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */ + } + + /* + * Finally advance the buffer. + */ + return rtDirNtAdvanceBuffer(pDir); +} + + +RTDECL(int) RTDirRewind(RTDIR hDir) +{ + /* + * Validate and digest input. + */ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * The work is done on the next call to rtDirNtFetchMore. + */ + pThis->fRestartScan = true; + pThis->fDataUnread = false; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + PRTDIRINTERNAL pDir = hDir; + AssertPtrReturn(pDir, VERR_INVALID_POINTER); + AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + VERR_INVALID_PARAMETER); + + if (pDir->enmInfoClass == FileMaximumInformation) + { + /* + * Directory object (see similar code above and rtPathNtQueryInfoInDirectoryObject). + */ + pObjInfo->cbObject = 0; + pObjInfo->cbAllocated = 0; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0); + pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + pObjInfo->Attr.enmAdditional = enmAdditionalAttribs; + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_NOTHING: + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.u.Unix.uid = NIL_RTUID; + pObjInfo->Attr.u.Unix.gid = NIL_RTGID; + pObjInfo->Attr.u.Unix.cHardlinks = 1; + pObjInfo->Attr.u.Unix.INodeIdDevice = pDir->uDirDev; + pObjInfo->Attr.u.Unix.INodeId = 0; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.u.EASize.cb = 0; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR_2; + } + return VINF_SUCCESS; + } + + /* + * Regular directory file. + */ + uint8_t abBuf[_2K]; + return rtPathNtQueryInfoFromHandle(pDir->hDir, abBuf, sizeof(abBuf), pObjInfo, enmAdditionalAttribs, "", 0); +} + diff --git a/src/VBox/Runtime/r3/nt/dirrel-r3-nt.cpp b/src/VBox/Runtime/r3/nt/dirrel-r3-nt.cpp new file mode 100644 index 00000000..a4130725 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/dirrel-r3-nt.cpp @@ -0,0 +1,651 @@ +/* $Id: dirrel-r3-nt.cpp $ */ +/** @file + * IPRT - Directory relative base APIs, NT implementation + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/dir.h> +#include "internal-r3-nt.h" + +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#include <iprt/utf16.h> +#include "internal/dir.h" +#include "internal/file.h" +#include "internal/fs.h" +#include "internal/path.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Getst the RTNTPATHRELATIVEASCENT value for RTNtPathRelativeFromUtf8. */ +#define RTDIRREL_NT_GET_ASCENT(a_pThis) \ + ( !(pThis->fFlags & RTDIR_F_DENY_ASCENT) ? kRTNtPathRelativeAscent_Allow : kRTNtPathRelativeAscent_Fail ) + + + +/** + * Helper that builds a full path for a directory relative path. + * + * @returns IPRT status code. + * @param pThis The directory. + * @param pszPathDst The destination buffer. + * @param cbPathDst The size of the destination buffer. + * @param pszRelPath The relative path. + */ +static int rtDirRelBuildFullPath(PRTDIRINTERNAL pThis, char *pszPathDst, size_t cbPathDst, const char *pszRelPath) +{ + AssertMsgReturn(!RTPathStartsWithRoot(pszRelPath), ("pszRelPath='%s'\n", pszRelPath), VERR_PATH_IS_NOT_RELATIVE); + + /* + * Let's hope we can avoid checking for ascension. + * + * Note! We don't take symbolic links into account here. That can be + * done later if desired. + */ + if ( !(pThis->fFlags & RTDIR_F_DENY_ASCENT) + || strstr(pszRelPath, "..") == NULL) + { + size_t const cchRelPath = strlen(pszRelPath); + size_t const cchDirPath = pThis->cchPath; + if (cchDirPath + cchRelPath < cbPathDst) + { + memcpy(pszPathDst, pThis->pszPath, cchDirPath); + memcpy(&pszPathDst[cchDirPath], pszRelPath, cchRelPath); + pszPathDst[cchDirPath + cchRelPath] = '\0'; + return VINF_SUCCESS; + } + return VERR_FILENAME_TOO_LONG; + } + + /* + * Calc the absolute path using the directory as a base, then check if the result + * still starts with the full directory path. + * + * This ASSUMES that pThis->pszPath is an absolute path. + */ + int rc = RTPathAbsEx(pThis->pszPath, pszRelPath, RTPATH_STR_F_STYLE_HOST, pszPathDst, &cbPathDst); + if (RT_SUCCESS(rc)) + { + if (RTPathStartsWith(pszPathDst, pThis->pszPath)) + return VINF_SUCCESS; + return VERR_PATH_NOT_FOUND; + } + return rc; +} + + +/* + * + * + * RTFile stuff. + * RTFile stuff. + * RTFile stuff. + * + * + */ + + +RTDECL(int) RTDirRelFileOpen(RTDIR hDir, const char *pszRelFilename, uint64_t fOpen, PRTFILE phFile) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Validate and convert flags. + */ + uint32_t fDesiredAccess; + uint32_t fObjAttribs; + uint32_t fFileAttribs; + uint32_t fShareAccess; + uint32_t fCreateDisposition; + uint32_t fCreateOptions; + int rc = rtFileNtValidateAndConvertFlags(fOpen, &fDesiredAccess, &fObjAttribs, &fFileAttribs, + &fShareAccess, &fCreateDisposition, &fCreateOptions); + if (RT_SUCCESS(rc)) + { + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelFilename, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, fObjAttribs, hRoot, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + fFileAttribs, + fShareAccess, + fCreateDisposition, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + rc = RTFileFromNative(phFile, (uintptr_t)hFile); + if (RT_FAILURE(rc)) + NtClose(hFile); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + RTNtPathFree(&NtName, NULL); + } + } + return rc; +} + + + +/* + * + * + * RTDir stuff. + * RTDir stuff. + * RTDir stuff. + * + * + */ + + +/** + * Helper for cooking up a path string for rtDirOpenRelativeOrHandle. + * + * @returns IPRT status code. + * @param pszDst The destination buffer. + * @param cbDst The size of the destination buffer. + * @param pThis The directory this is relative to. + * @param pNtPath The NT path with a possibly relative path. + * @param fRelative Whether @a pNtPath is relative or not. + * @param pszPath The input path. + */ +static int rtDirRelJoinPathForDirOpen(char *pszDst, size_t cbDst, PRTDIRINTERNAL pThis, + PUNICODE_STRING pNtPath, bool fRelative, const char *pszPath) +{ + int rc; + if (fRelative) + { + size_t cchRel = 0; + rc = RTUtf16CalcUtf8LenEx(pNtPath->Buffer, pNtPath->Length / sizeof(RTUTF16), &cchRel); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (pThis->cchPath + cchRel < cbDst) + { + size_t cchBase = pThis->cchPath; + memcpy(pszDst, pThis->pszPath, cchBase); + pszDst += cchBase; + cbDst -= cchBase; + rc = RTUtf16ToUtf8Ex(pNtPath->Buffer, pNtPath->Length / sizeof(RTUTF16), &pszDst, cbDst, NULL); + } + else + rc = VERR_FILENAME_TOO_LONG; + } + } + else + { + /** @todo would be better to convert pNtName to DOS/WIN path here, + * as it is absolute and doesn't need stuff resolved. */ + rc = RTPathJoin(pszDst, cbDst, pThis->pszPath, pszPath); + } + return rc; +} + +RTDECL(int) RTDirRelDirOpen(RTDIR hDir, const char *pszDir, RTDIR *phDir) +{ + return RTDirRelDirOpenFiltered(hDir, pszDir, RTDIRFILTER_NONE, 0 /*fFlags*/, phDir); +} + + +RTDECL(int) RTDirRelDirOpenFiltered(RTDIR hDir, const char *pszDirAndFilter, RTDIRFILTER enmFilter, + uint32_t fFlags, RTDIR *phDir) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszDirAndFilter, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + char szAbsDirAndFilter[RTPATH_MAX]; + rc = rtDirRelJoinPathForDirOpen(szAbsDirAndFilter, sizeof(szAbsDirAndFilter), pThis, + &NtName, hRoot != NULL, pszDirAndFilter); + if (RT_SUCCESS(rc)) + { + /* Drop the filter from the NT name. */ + switch (enmFilter) + { + case RTDIRFILTER_NONE: + break; + case RTDIRFILTER_WINNT: + case RTDIRFILTER_UNIX: + case RTDIRFILTER_UNIX_UPCASED: + { + size_t cwc = NtName.Length / sizeof(RTUTF16); + while ( cwc > 0 + && NtName.Buffer[cwc - 1] != '\\') + cwc--; + NtName.Buffer[cwc] = '\0'; + NtName.Length = (uint16_t)(cwc * sizeof(RTUTF16)); + break; + } + default: + AssertFailedBreak(); + } + + rc = rtDirOpenRelativeOrHandle(phDir, szAbsDirAndFilter, enmFilter, fFlags, (uintptr_t)hRoot, &NtName); + } + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelDirCreate(RTDIR hDir, const char *pszRelPath, RTFMODE fMode, uint32_t fCreate, RTDIR *phSubDir) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fCreate & ~RTDIRCREATE_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + fMode = rtFsModeNormalize(fMode, pszRelPath, 0, RTFS_TYPE_DIRECTORY); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + AssertPtrNullReturn(phSubDir, VERR_INVALID_POINTER); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hNewDir = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRoot, NULL); + + ULONG fDirAttribs = (fCreate & RTFS_DOS_MASK_NT) >> RTFS_DOS_SHIFT; + if (!(fCreate & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET)) + fDirAttribs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + if (!fDirAttribs) + fDirAttribs = FILE_ATTRIBUTE_NORMAL; + + NTSTATUS rcNt = NtCreateFile(&hNewDir, + phSubDir + ? FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE + : SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + fDirAttribs, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_CREATE, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + + /* Just in case someone takes offence at FILE_ATTRIBUTE_NOT_CONTENT_INDEXED. */ + if ( ( rcNt == STATUS_INVALID_PARAMETER + || rcNt == STATUS_INVALID_PARAMETER_7) + && (fDirAttribs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) + && (fCreate & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL) ) + { + fDirAttribs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + if (!fDirAttribs) + fDirAttribs = FILE_ATTRIBUTE_NORMAL; + rcNt = NtCreateFile(&hNewDir, + phSubDir + ? FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE + : SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + fDirAttribs, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_CREATE, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + } + + if (NT_SUCCESS(rcNt)) + { + if (!phSubDir) + { + NtClose(hNewDir); + rc = VINF_SUCCESS; + } + else + { + char szAbsDirAndFilter[RTPATH_MAX]; + rc = rtDirRelJoinPathForDirOpen(szAbsDirAndFilter, sizeof(szAbsDirAndFilter), pThis, + &NtName, hRoot != NULL, pszRelPath); + if (RT_SUCCESS(rc)) + rc = rtDirOpenRelativeOrHandle(phSubDir, pszRelPath, RTDIRFILTER_NONE, 0 /*fFlags*/, + (uintptr_t)hNewDir, NULL /*pvNativeRelative*/); + if (RT_FAILURE(rc)) + NtClose(hNewDir); + } + } + else + rc = RTErrConvertFromNtStatus(rcNt); + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelDirRemove(RTDIR hDir, const char *pszRelPath) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hSubDir = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRoot, NULL); + + NTSTATUS rcNt = NtCreateFile(&hSubDir, + DELETE | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + FILE_DISPOSITION_INFORMATION DispInfo; + DispInfo.DeleteFile = TRUE; + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtSetInformationFile(hSubDir, &Ios, &DispInfo, sizeof(DispInfo), FileDispositionInformation); + + NTSTATUS rcNt2 = NtClose(hSubDir); + if (!NT_SUCCESS(rcNt2) && NT_SUCCESS(rcNt)) + rcNt = rcNt2; + } + + if (NT_SUCCESS(rcNt)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +/* + * + * RTPath stuff. + * RTPath stuff. + * RTPath stuff. + * + * + */ + + +RTDECL(int) RTDirRelPathQueryInfo(RTDIR hDir, const char *pszRelPath, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Validate and convert flags. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + if (NtName.Length != 0 || hRoot == NULL) + rc = rtPathNtQueryInfoWorker(hRoot, &NtName, pObjInfo, enmAddAttr, fFlags, pszRelPath); + else + rc = RTDirQueryInfo(hDir, pObjInfo, enmAddAttr); + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelPathSetMode(RTDIR hDir, const char *pszRelPath, RTFMODE fMode, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + fMode = rtFsModeNormalize(fMode, pszRelPath, 0, 0); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_FLAGS); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hSubDir = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRoot, NULL); + + ULONG fOpenOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT; + if (fFlags & RTPATH_F_ON_LINK) + fOpenOptions |= FILE_OPEN_REPARSE_POINT; + NTSTATUS rcNt = NtCreateFile(&hSubDir, + FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + fOpenOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + rc = rtNtFileSetModeWorker(hSubDir, fMode); + + rcNt = NtClose(hSubDir); + if (!NT_SUCCESS(rcNt) && RT_SUCCESS(rc)) + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelPathSetTimes(RTDIR hDir, const char *pszRelPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTPathSetTimesEx(szPath, pAccessTime, pModificationTime, pChangeTime, pBirthTime, fFlags); + return rc; +} + + +RTDECL(int) RTDirRelPathSetOwner(RTDIR hDir, const char *pszRelPath, uint32_t uid, uint32_t gid, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_WINDOWS + rc = RTPathSetOwnerEx(szPath, uid, gid, fFlags); +#else + rc = VERR_NOT_IMPLEMENTED; + RT_NOREF(uid, gid, fFlags); +#endif + } + return rc; +} + + +RTDECL(int) RTDirRelPathRename(RTDIR hDirSrc, const char *pszSrc, RTDIR hDirDst, const char *pszDst, unsigned fRename) +{ + PRTDIRINTERNAL pThis = hDirSrc; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + PRTDIRINTERNAL pThat = hDirDst; + if (pThat != pThis) + { + AssertPtrReturn(pThat, VERR_INVALID_HANDLE); + AssertReturn(pThat->u32Magic != RTDIR_MAGIC, VERR_INVALID_HANDLE); + } + + char szSrcPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szSrcPath, sizeof(szSrcPath), pszSrc); + if (RT_SUCCESS(rc)) + { + char szDstPath[RTPATH_MAX]; + rc = rtDirRelBuildFullPath(pThis, szDstPath, sizeof(szDstPath), pszDst); + if (RT_SUCCESS(rc)) + rc = RTPathRename(szSrcPath, szDstPath, fRename); + } + return rc; +} + + +RTDECL(int) RTDirRelPathUnlink(RTDIR hDir, const char *pszRelPath, uint32_t fUnlink) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTPathUnlink(szPath, fUnlink); + return rc; +} + + +/* + * + * RTSymlink stuff. + * RTSymlink stuff. + * RTSymlink stuff. + * + * + */ + + +RTDECL(int) RTDirRelSymlinkCreate(RTDIR hDir, const char *pszSymlink, const char *pszTarget, + RTSYMLINKTYPE enmType, uint32_t fCreate) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszSymlink); + if (RT_SUCCESS(rc)) + rc = RTSymlinkCreate(szPath, pszTarget, enmType, fCreate); + return rc; +} + + +RTDECL(int) RTDirRelSymlinkRead(RTDIR hDir, const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszSymlink); + if (RT_SUCCESS(rc)) + rc = RTSymlinkRead(szPath, pszTarget, cbTarget, fRead); + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/fs-nt.cpp b/src/VBox/Runtime/r3/nt/fs-nt.cpp new file mode 100644 index 00000000..70add1ee --- /dev/null +++ b/src/VBox/Runtime/r3/nt/fs-nt.cpp @@ -0,0 +1,279 @@ +/* $Id: fs-nt.cpp $ */ +/** @file + * IPRT - File System, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal-r3-nt.h" + +#include <iprt/fs.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/param.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include "internal/fs.h" + + + + +RTR3DECL(int) RTFsQuerySizes(const char *pszFsPath, RTFOFF *pcbTotal, RTFOFF *pcbFree, + uint32_t *pcbBlock, uint32_t *pcbSector) +{ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + RTFILE hIprtFile = NIL_RTFILE; + rc = RTFileFromNative(&hIprtFile, (RTHCINTPTR)hFile); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = RTFileQueryFsSizes(hIprtFile, pcbTotal, pcbFree, pcbBlock, pcbSector); + + RTNtPathClose(hFile); + } + return rc; +} + + +RTR3DECL(int) RTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial) +{ + /* + * Validate & get valid root path. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertPtrReturn(pu32Serial, VERR_INVALID_POINTER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + /* + * Get the volume information. + */ + union + { + FILE_FS_VOLUME_INFORMATION FsVolInfo; + uint8_t abBuf[sizeof(FILE_FS_VOLUME_INFORMATION) + 4096]; + } u; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &u, sizeof(u), FileFsVolumeInformation); + if (NT_SUCCESS(rcNt)) + *pu32Serial = u.FsVolInfo.VolumeSerialNumber; + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathClose(hFile); + } + return rc; +} + + +RTR3DECL(int) RTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties) +{ + /* + * Validate & get valid root path. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertPtrReturn(pProperties, VERR_INVALID_POINTER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + /* + * Get the volume information. + */ + union + { + FILE_FS_ATTRIBUTE_INFORMATION FsAttrInfo; + uint8_t abBuf[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + 4096]; + } u; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &u, sizeof(u), FileFsAttributeInformation); + if (NT_SUCCESS(rcNt)) + { + FILE_FS_DEVICE_INFORMATION FsDevInfo; + rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &FsDevInfo, sizeof(FsDevInfo), FileFsDeviceInformation); + if (NT_SUCCESS(rcNt)) + { + /* + * Fill in the return structure. + */ + memset(pProperties, 0, sizeof(*pProperties)); + pProperties->cbMaxComponent = u.FsAttrInfo.MaximumComponentNameLength; + pProperties->fFileCompression = !!(u.FsAttrInfo.FileSystemAttributes & FILE_FILE_COMPRESSION); + pProperties->fCompressed = !!(u.FsAttrInfo.FileSystemAttributes & FILE_VOLUME_IS_COMPRESSED); + pProperties->fReadOnly = !!(u.FsAttrInfo.FileSystemAttributes & FILE_READ_ONLY_VOLUME); + pProperties->fSupportsUnicode = !!(u.FsAttrInfo.FileSystemAttributes & FILE_UNICODE_ON_DISK); + pProperties->fCaseSensitive = false; /* win32 is case preserving only */ + /** @todo r=bird: What about FILE_CASE_SENSITIVE_SEARCH ? Is this set for NTFS + * as well perchance? If so, better mention it instead of just setting + * fCaseSensitive to false. */ + + /* figure the remote stuff */ + pProperties->fRemote = RT_BOOL(FsDevInfo.Characteristics & FILE_REMOTE_DEVICE); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathClose(hFile); + } + return rc; +} + + +RTR3DECL(bool) RTFsIsCaseSensitive(const char *pszFsPath) +{ + RT_NOREF_PV(pszFsPath); + return false; +} + + +int rtNtQueryFsType(HANDLE hHandle, PRTFSTYPE penmType) +{ + /* + * Get the file system name. + */ + union + { + FILE_FS_ATTRIBUTE_INFORMATION FsAttrInfo; + uint8_t abBuf[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + 4096]; + } u; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(hHandle, &Ios, &u, sizeof(u), FileFsAttributeInformation); + if (NT_SUCCESS(rcNt)) + { +#define IS_FS(a_szName) rtNtCompWideStrAndAscii(u.FsAttrInfo.FileSystemName, u.FsAttrInfo.FileSystemNameLength, RT_STR_TUPLE(a_szName)) + if (IS_FS("NTFS")) + *penmType = RTFSTYPE_NTFS; + else if (IS_FS("FAT")) + *penmType = RTFSTYPE_FAT; + else if (IS_FS("FAT32")) + *penmType = RTFSTYPE_FAT; + else if (IS_FS("exFAT")) + *penmType = RTFSTYPE_EXFAT; + else if (IS_FS("UDF")) + *penmType = RTFSTYPE_UDF; + else if (IS_FS("CDFS")) + *penmType = RTFSTYPE_ISO9660; + else if (IS_FS("HPFS")) + *penmType = RTFSTYPE_HPFS; + else if (IS_FS("ReFS")) /** @todo verify ReFS signature. */ + *penmType = RTFSTYPE_REFS; + else if (IS_FS("VBoxSharedFolderFS")) + *penmType = RTFSTYPE_VBOXSHF; +#undef IS_FS + return VINF_SUCCESS; + } + + *penmType = RTFSTYPE_UNKNOWN; + return RTErrConvertFromNtStatus(rcNt); +} + + +RTR3DECL(int) RTFsQueryType(const char *pszFsPath, PRTFSTYPE penmType) +{ + /* + * Validate input. + */ + *penmType = RTFSTYPE_UNKNOWN; + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath, VERR_INVALID_PARAMETER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + rc = rtNtQueryFsType(hFile, penmType); + RTNtPathClose(hFile); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/internal-r3-nt.h b/src/VBox/Runtime/r3/nt/internal-r3-nt.h new file mode 100644 index 00000000..c76a3f6b --- /dev/null +++ b/src/VBox/Runtime/r3/nt/internal-r3-nt.h @@ -0,0 +1,92 @@ +/* $Id: internal-r3-nt.h $ */ +/** @file + * IPRT - Internal Header for the Native NT code. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_nt_internal_r3_nt_h +#define IPRT_INCLUDED_SRC_r3_nt_internal_r3_nt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef IN_SUP_HARDENED_R3 +# include <iprt/nt/nt-and-windows.h> +#else +# include <iprt/nt/nt.h> +#endif +#include "internal/iprt.h" + + +#if 1 +/** Enables the "\\!\" NT path pass thru as well as hacks for listing NT object + * directories. */ +# define IPRT_WITH_NT_PATH_PASSTHRU 1 +#endif + + + +/** + * Internal helper for comparing a WCHAR string with a char string. + * + * @returns @c true if equal, @c false if not. + * @param pwsz1 The first string. + * @param cch1 The length of the first string, in bytes. + * @param psz2 The second string. + * @param cch2 The length of the second string. + */ +DECLINLINE(bool) rtNtCompWideStrAndAscii(WCHAR const *pwsz1, size_t cch1, const char *psz2, size_t cch2) +{ + if (cch1 != cch2 * 2) + return false; + while (cch2-- > 0) + { + unsigned ch1 = *pwsz1++; + unsigned ch2 = (unsigned char)*psz2++; + if (ch1 != ch2) + return false; + } + return true; +} + +#endif /* !IPRT_INCLUDED_SRC_r3_nt_internal_r3_nt_h */ + +/** + * Common worker for RTFileSetMode, RTPathSetMode and RTDirRelPathSetMode. + * + * @returns IPRT status code. + * @param hNativeFile The NT handle to the file system object. + * @param fMode Valid and normalized file mode mask to set. + */ +DECLHIDDEN(int) rtNtFileSetModeWorker(HANDLE hNativeFile, RTFMODE fMode); + diff --git a/src/VBox/Runtime/r3/nt/pathint-nt.cpp b/src/VBox/Runtime/r3/nt/pathint-nt.cpp new file mode 100644 index 00000000..95281ca7 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/pathint-nt.cpp @@ -0,0 +1,1161 @@ +/* $Id: pathint-nt.cpp $ */ +/** @file + * IPRT - Native NT, Internal Path stuff. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal-r3-nt.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char const g_szPrefixUnc[] = "\\??\\UNC\\"; +static char const g_szPrefix[] = "\\??\\"; +static char const g_szPrefixNt3xUnc[] = "\\DosDevices\\UNC\\"; +static char const g_szPrefixNt3x[] = "\\DosDevices\\"; + + +/** + * Handles the pass thru case for UTF-8 input. + * Win32 path uses "\\?\" prefix which is converted to "\??\" NT prefix. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +static int rtNtPathFromWinUtf8PassThru(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ + PRTUTF16 pwszPath = NULL; + size_t cwcLen; + int rc = RTStrToUtf16Ex(pszPath, RTSTR_MAX, &pwszPath, 0, &cwcLen); + if (RT_SUCCESS(rc)) + { + if (cwcLen < _32K - 1) + { + *phRootDir = NULL; + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) >= RT_MAKE_U64(0, 4)) + { + pwszPath[0] = '\\'; + pwszPath[1] = '?'; + pwszPath[2] = '?'; + pwszPath[3] = '\\'; + + pNtName->Buffer = pwszPath; + pNtName->Length = (uint16_t)(cwcLen * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + return VINF_SUCCESS; + } + + rc = RTUtf16Realloc(&pwszPath, cwcLen + sizeof(g_szPrefixNt3x)); + if (RT_SUCCESS(rc)) + { + memmove(&pwszPath[sizeof(g_szPrefixNt3x) - 1], &pwszPath[4], (cwcLen - 4 + 1) * sizeof(RTUTF16)); + for (uint32_t i = 0; i < sizeof(g_szPrefixNt3x) - 1; i++) + pwszPath[i] = g_szPrefixNt3x[i]; + + pNtName->Buffer = pwszPath; + pNtName->Length = (uint16_t)((cwcLen - 4 + sizeof(g_szPrefixNt3x) - 1) * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + return VINF_SUCCESS; + } + } + + RTUtf16Free(pwszPath); + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} + + +/** + * Handles the pass thru case for UTF-16 input. + * Win32 path uses "\\?\" prefix which is converted to "\??\" NT prefix. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. + * @param phRootDir Stores NULL here, as we don't use it. + * @param pwszWinPath The UTF-16 windows-style path. + * @param cwcWinPath The length of the windows-style input path. + */ +static int rtNtPathFromWinUtf16PassThru(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, + PCRTUTF16 pwszWinPath, size_t cwcWinPath) +{ + /* Check length and allocate memory for it. */ + int rc; + if (cwcWinPath < _32K - 1) + { + + size_t const cwcExtraPrefix = RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) + >= RT_MAKE_U64(0, 4) + ? 0 : sizeof(g_szPrefixNt3x) - 1 - 4; + PRTUTF16 pwszNtPath = (PRTUTF16)RTUtf16Alloc((cwcExtraPrefix + cwcWinPath + 1) * sizeof(RTUTF16)); + if (pwszNtPath) + { + /* Intialize the path. */ + if (!cwcExtraPrefix) + { + pwszNtPath[0] = '\\'; + pwszNtPath[1] = '?'; + pwszNtPath[2] = '?'; + pwszNtPath[3] = '\\'; + } + else + for (uint32_t i = 0; i < sizeof(g_szPrefixNt3x) - 1; i++) + pwszNtPath[i] = g_szPrefixNt3x[i]; + memcpy(pwszNtPath + cwcExtraPrefix + 4, pwszWinPath + 4, (cwcWinPath - 4) * sizeof(RTUTF16)); + pwszNtPath[cwcExtraPrefix + cwcWinPath] = '\0'; + + /* Initialize the return values. */ + pNtName->Buffer = pwszNtPath; + pNtName->Length = (uint16_t)(cwcExtraPrefix + cwcWinPath * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + *phRootDir = NULL; + + rc = VINF_SUCCESS; + } + else + rc = VERR_NO_UTF16_MEMORY; + } + else + rc = VERR_FILENAME_TOO_LONG; + return rc; +} + + + + + +/** + * Converts the path to UTF-16 and sets all the return values. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +static int rtNtPathUtf8ToUniStr(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ + PRTUTF16 pwszPath = NULL; + size_t cwcLen; + int rc = RTStrToUtf16Ex(pszPath, RTSTR_MAX, &pwszPath, 0, &cwcLen); + if (RT_SUCCESS(rc)) + { + if (cwcLen < _32K - 1) + { + pNtName->Buffer = pwszPath; + pNtName->Length = (uint16_t)(cwcLen * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + *phRootDir = NULL; + return VINF_SUCCESS; + } + + RTUtf16Free(pwszPath); + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} + + +/** + * Converts a windows-style path to NT format and encoding. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * rtTNtPathToNative. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +static int rtNtPathToNative(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ +/** @todo This code sucks a bit performance wise, esp. calling + * generic RTPathAbs. Too many buffers involved, I think. */ + + /* + * Very simple conversion of a win32-like path into an NT path. + */ + const char *pszPrefix; + size_t cchPrefix; + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) >= RT_MAKE_U64(0, 4)) + { + pszPrefix = g_szPrefix; + cchPrefix = sizeof(g_szPrefix) - 1; + } + else + { + pszPrefix = g_szPrefixNt3x; + cchPrefix = sizeof(g_szPrefixNt3x) - 1; + } + + size_t cchSkip = 0; + if ( RTPATH_IS_SLASH(pszPath[0]) + && RTPATH_IS_SLASH(pszPath[1]) + && !RTPATH_IS_SLASH(pszPath[2]) + && pszPath[2]) + { +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + /* + * Special trick: The path starts with RTPATH_NT_PASSTHRU_PREFIX, we will skip past the bang and pass it thru. + */ + if ( pszPath[2] == ':' + && pszPath[3] == 'i' + && pszPath[4] == 'p' + && pszPath[5] == 'r' + && pszPath[6] == 't' + && pszPath[7] == 'n' + && pszPath[8] == 't' + && pszPath[9] == ':' + && RTPATH_IS_SLASH(pszPath[10])) + return rtNtPathUtf8ToUniStr(pNtName, phRootDir, pszPath + 10); +#endif + + if ( pszPath[2] == '?' + && RTPATH_IS_SLASH(pszPath[3])) + return rtNtPathFromWinUtf8PassThru(pNtName, phRootDir, pszPath); + + if ( pszPath[2] == '.' + && RTPATH_IS_SLASH(pszPath[3])) + { + /* + * Device path. + * Note! I suspect \\.\stuff\..\otherstuff may be handled differently by windows. + */ + cchSkip = 4; + } + else + { + /* UNC */ + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) >= RT_MAKE_U64(0, 4)) + { + pszPrefix = g_szPrefixUnc; + cchPrefix = sizeof(g_szPrefixUnc) - 1; + } + else + { + pszPrefix = g_szPrefixNt3xUnc; + cchPrefix = sizeof(g_szPrefixNt3xUnc) - 1; + } + cchSkip = 2; + } + } + + /* + * Straighten out all .. and uncessary . references and convert slashes. + */ + char szAbsPathBuf[RTPATH_MAX]; + size_t cbAbsPath = sizeof(szAbsPathBuf) - (cchPrefix - cchSkip); + char *pszAbsPath = szAbsPathBuf; + char *pszAbsPathFree = NULL; + int rc = RTPathAbsEx(NULL, pszPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc == VERR_BUFFER_OVERFLOW) + { + unsigned cTries = 8; + size_t cbAbsPathBuf = RTPATH_MAX; + for (;;) + { + cbAbsPathBuf = RT_MAX(RT_ALIGN_Z((cchPrefix - cchSkip) + cbAbsPath + 32, 64), cbAbsPathBuf + 256); + if (cTries == 1) + cbAbsPathBuf = RT_MAX(cbAbsPathBuf, RTPATH_BIG_MAX * 2); + pszAbsPathFree = pszAbsPath = (char *)RTMemTmpAlloc(cbAbsPathBuf); + if (!pszAbsPath) + return VERR_NO_TMP_MEMORY; + + cbAbsPath = cbAbsPathBuf - (cchPrefix - cchSkip); + rc = RTPathAbsEx(NULL, pszPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + break; + RTMemTmpFree(pszAbsPathFree); + pszAbsPathFree = NULL; + if (rc != VERR_BUFFER_OVERFLOW) + return rc; + if (--cTries == 0) + return VERR_FILENAME_TOO_LONG; + } + } + else + return rc; + + /* + * Add prefix and convert it to UTF16. + */ + memcpy(pszAbsPath, pszPrefix, cchPrefix); + rc = rtNtPathUtf8ToUniStr(pNtName, phRootDir, pszAbsPath); + + if (pszAbsPathFree) + RTMemTmpFree(pszAbsPathFree); + return rc; +} + + +/** + * Converts a windows-style path to NT format and encoding. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * RTNtPathToNative. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +RTDECL(int) RTNtPathFromWinUtf8(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ + return rtNtPathToNative(pNtName, phRootDir, pszPath); +} + + +/** + * Converts a UTF-16 windows-style path to NT format. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * RTNtPathFree. + * @param phRootDir Where to return the root handle, if applicable. + * @param pwszWinPath The UTF-16 windows-style path. + * @param cwcWinPath The max length of the windows-style path in + * RTUTF16 units. Use RTSTR_MAX if unknown and @a + * pwszWinPath is correctly terminated. + */ +RTDECL(int) RTNtPathFromWinUtf16Ex(struct _UNICODE_STRING *pNtName, HANDLE *phRootDir, PCRTUTF16 pwszWinPath, size_t cwcWinPath) +{ + /* + * Validate the input, calculating the correct length. + */ + if (cwcWinPath == 0 || *pwszWinPath == '\0') + return VERR_INVALID_NAME; + + RTUtf16NLenEx(pwszWinPath, cwcWinPath, &cwcWinPath); + int rc = RTUtf16ValidateEncodingEx(pwszWinPath, cwcWinPath, 0); + if (RT_FAILURE(rc)) + return rc; + + /* + * Very simple conversion of a win32-like path into an NT path. + */ + const char *pszPrefix = g_szPrefix; + size_t cchPrefix = sizeof(g_szPrefix) - 1; + size_t cchSkip = 0; + + if ( RTPATH_IS_SLASH(pwszWinPath[0]) + && cwcWinPath >= 3 + && RTPATH_IS_SLASH(pwszWinPath[1]) + && !RTPATH_IS_SLASH(pwszWinPath[2]) ) + { +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + /* + * Special trick: The path starts with RTPATH_NT_PASSTHRU_PREFIX, we will skip past the bang and pass it thru. + */ + if ( cwcWinPath >= sizeof(RTPATH_NT_PASSTHRU_PREFIX) - 1U + && pwszWinPath[2] == ':' + && pwszWinPath[3] == 'i' + && pwszWinPath[4] == 'p' + && pwszWinPath[5] == 'r' + && pwszWinPath[6] == 't' + && pwszWinPath[7] == 'n' + && pwszWinPath[8] == 't' + && pwszWinPath[9] == ':' + && RTPATH_IS_SLASH(pwszWinPath[10]) ) + { + pwszWinPath += 10; + cwcWinPath -= 10; + if (cwcWinPath < _32K - 1) + { + PRTUTF16 pwszNtPath = RTUtf16Alloc((cwcWinPath + 1) * sizeof(RTUTF16)); + if (pwszNtPath) + { + memcpy(pwszNtPath, pwszWinPath, cwcWinPath * sizeof(RTUTF16)); + pwszNtPath[cwcWinPath] = '\0'; + pNtName->Buffer = pwszNtPath; + pNtName->Length = (uint16_t)(cwcWinPath * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + *phRootDir = NULL; + return VINF_SUCCESS; + } + rc = VERR_NO_UTF16_MEMORY; + } + else + rc = VERR_FILENAME_TOO_LONG; + return rc; + } +#endif + + if ( pwszWinPath[2] == '?' + && cwcWinPath >= 4 + && RTPATH_IS_SLASH(pwszWinPath[3])) + return rtNtPathFromWinUtf16PassThru(pNtName, phRootDir, pwszWinPath, cwcWinPath); + + if ( pwszWinPath[2] == '.' + && cwcWinPath >= 4 + && RTPATH_IS_SLASH(pwszWinPath[3])) + { + /* + * Device path. + * Note! I suspect \\.\stuff\..\otherstuff may be handled differently by windows. + */ + cchSkip = 4; + } + else + { + /* UNC */ + pszPrefix = g_szPrefixUnc; + cchPrefix = sizeof(g_szPrefixUnc) - 1; + cchSkip = 2; + } + } + + /* + * Straighten out all .. and unnecessary . references and convert slashes. + */ + /* UTF-16 -> UTF-8 (relative path) */ + char szRelPath[RTPATH_MAX]; + char *pszRelPathFree = NULL; + char *pszRelPath = szRelPath; + size_t cchRelPath; + rc = RTUtf16ToUtf8Ex(pwszWinPath, cwcWinPath, &pszRelPath, sizeof(szRelPath), &cchRelPath); + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc == VERR_BUFFER_OVERFLOW) + { + pszRelPath = NULL; + rc = RTUtf16ToUtf8Ex(pwszWinPath, cwcWinPath, &pszRelPath, 0, &cchRelPath); + if (RT_SUCCESS(rc)) + pszRelPathFree = pszRelPath; + } + if (RT_SUCCESS(rc)) + { + /* Relative -> Absolute. */ + char szAbsPathBuf[RTPATH_MAX]; + char *pszAbsPathFree = NULL; + char *pszAbsPath = szAbsPathBuf; + size_t cbAbsPath = sizeof(szAbsPathBuf) - (cchPrefix - cchSkip); + rc = RTPathAbsEx(NULL, szRelPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc == VERR_BUFFER_OVERFLOW) + { + unsigned cTries = 8; + size_t cbAbsPathBuf = RTPATH_MAX; + for (;;) + { + cbAbsPathBuf = RT_MAX(RT_ALIGN_Z((cchPrefix - cchSkip) + cbAbsPath + 32, 64), cbAbsPathBuf + 256); + if (cTries == 1) + cbAbsPathBuf = RT_MAX(cbAbsPathBuf, RTPATH_BIG_MAX * 2); + pszAbsPathFree = pszAbsPath = (char *)RTMemTmpAlloc(cbAbsPathBuf); + if (!pszAbsPath) + { + rc = VERR_NO_TMP_MEMORY; + break; + } + + cbAbsPath = cbAbsPathBuf - (cchPrefix - cchSkip); + rc = RTPathAbsEx(NULL, szRelPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + break; + + RTMemTmpFree(pszAbsPathFree); + pszAbsPathFree = NULL; + if (rc != VERR_BUFFER_OVERFLOW) + break; + if (--cTries == 0) + { + rc = VERR_FILENAME_TOO_LONG; + break; + } + } + + } + if (pszRelPathFree) + RTStrFree(pszRelPathFree); + + if (RT_SUCCESS(rc)) + { + /* + * Add prefix + */ + memcpy(pszAbsPath, pszPrefix, cchPrefix); + + /* + * Remove trailing '.' that is used to specify no extension in the Win32/DOS world. + */ + size_t cchAbsPath = strlen(pszAbsPath); + if ( cchAbsPath > 2 + && pszAbsPath[cchAbsPath - 1] == '.') + { + char const ch = pszAbsPath[cchAbsPath - 2]; + if ( ch != '/' + && ch != '\\' + && ch != ':' + && ch != '.') + pszAbsPath[--cchAbsPath] = '\0'; + } + + /* + * Finally convert to UNICODE_STRING. + */ + rc = rtNtPathUtf8ToUniStr(pNtName, phRootDir, pszAbsPath); + + if (pszAbsPathFree) + RTMemTmpFree(pszAbsPathFree); + } + } + return rc; +} + + +/** + * Ensures that the NT string has sufficient storage to hold @a cwcMin RTUTF16 + * chars plus a terminator. + * + * The NT string must have been returned by RTNtPathFromWinUtf8 or + * RTNtPathFromWinUtf16Ex. + * + * @returns IPRT status code. + * @param pNtName The NT path string. + * @param cwcMin The minimum number of RTUTF16 chars. Max 32767. + * @sa RTNtPathFree + */ +RTDECL(int) RTNtPathEnsureSpace(struct _UNICODE_STRING *pNtName, size_t cwcMin) +{ + if (pNtName->MaximumLength / sizeof(RTUTF16) > cwcMin) + return VINF_SUCCESS; + + AssertReturn(cwcMin < _64K / sizeof(RTUTF16), VERR_OUT_OF_RANGE); + + size_t const cbMin = (cwcMin + 1) * sizeof(RTUTF16); + int rc = RTUtf16Realloc(&pNtName->Buffer, cbMin); + if (RT_SUCCESS(rc)) + pNtName->MaximumLength = (uint16_t)cbMin; + return rc; +} + + +/** + * Gets the NT path to the object represented by the given handle. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT path. Free using + * RTNtPathFree. + * @param hHandle The handle. + * @param cwcExtra How much extra space is needed. + */ +RTDECL(int) RTNtPathFromHandle(struct _UNICODE_STRING *pNtName, HANDLE hHandle, size_t cwcExtra) +{ + /* + * Query the name into a buffer. + */ + ULONG cbBuf = _2K; + PUNICODE_STRING pUniStrBuf = (PUNICODE_STRING)RTMemTmpAllocZ(cbBuf); + if (!pUniStrBuf) + return VERR_NO_TMP_MEMORY; + + ULONG cbNameBuf = cbBuf; + NTSTATUS rcNt = NtQueryObject(hHandle, ObjectNameInformation, pUniStrBuf, cbBuf, &cbNameBuf); + while ( rcNt == STATUS_BUFFER_OVERFLOW + || rcNt == STATUS_BUFFER_TOO_SMALL) + { + do + cbBuf *= 2; + while (cbBuf <= cbNameBuf); + RTMemTmpFree(pUniStrBuf); + pUniStrBuf = (PUNICODE_STRING)RTMemTmpAllocZ(cbBuf); + if (!pUniStrBuf) + return VERR_NO_TMP_MEMORY; + + cbNameBuf = cbBuf; + rcNt = NtQueryObject(hHandle, ObjectNameInformation, pUniStrBuf, cbBuf, &cbNameBuf); + } + int rc; + if (NT_SUCCESS(rcNt)) + { + /* + * Copy the result into the return string. + */ + size_t cbNeeded = cwcExtra * sizeof(RTUTF16) + pUniStrBuf->Length + sizeof(RTUTF16); + if (cbNeeded < _64K) + { + pNtName->Length = pUniStrBuf->Length; + pNtName->MaximumLength = (uint16_t)cbNeeded; + pNtName->Buffer = RTUtf16Alloc(cbNeeded); + if (pNtName->Buffer) + { + memcpy(pNtName->Buffer, pUniStrBuf->Buffer, pUniStrBuf->Length); + pNtName->Buffer[pUniStrBuf->Length / sizeof(RTUTF16)] = '\0'; + rc = VINF_SUCCESS; + } + else + rc = VERR_NO_UTF16_MEMORY; + } + else + rc = VERR_FILENAME_TOO_LONG; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + RTMemTmpFree(pUniStrBuf); + return rc; +} + +static int rtNtPathRelativeToAbs(struct _UNICODE_STRING *pNtName, HANDLE *phRootDir) +{ + int rc; + if (pNtName->Length == 0) + { + RTUtf16Free(pNtName->Buffer); + rc = RTNtPathFromHandle(pNtName, *phRootDir, pNtName->Length / sizeof(RTUTF16) + 2); + if (RT_SUCCESS(rc)) + { + *phRootDir = NULL; + return VINF_SUCCESS; + } + } + else + { + + UNICODE_STRING RootDir; + size_t const cwcAppend = pNtName->Length / sizeof(RTUTF16); + rc = RTNtPathFromHandle(&RootDir, *phRootDir, cwcAppend + 2); + if (RT_SUCCESS(rc)) + { + size_t cwcRoot = RootDir.Length / sizeof(RTUTF16); + if (RootDir.Buffer[cwcRoot - 1] != '\\') + RootDir.Buffer[cwcRoot++] = '\\'; + memcpy(&RootDir.Buffer[cwcRoot], pNtName->Buffer, cwcAppend * sizeof(RTUTF16)); + RTUtf16Free(pNtName->Buffer); + pNtName->Length = (uint16_t)((cwcRoot + cwcAppend) * sizeof(RTUTF16)); + pNtName->MaximumLength = RootDir.MaximumLength; + pNtName->Buffer = RootDir.Buffer; + + *phRootDir = NULL; + return VINF_SUCCESS; + } + RTUtf16Free(pNtName->Buffer); + } + pNtName->Length = 0; + pNtName->MaximumLength = 0; + pNtName->Buffer = NULL; + return rc; +} + + +/** + * Rewinds the path back to the start of the previous component. + * + * Will preserve root slash. + * + * @returns Pointer to character after the start-of-component slash, or + * pwszStart. + * @param pwcEnd The current end of the path. + * @param pwszStart The start of the path. + */ +static PRTUTF16 rtNtPathGetPrevComponent(PRTUTF16 pwcEnd, PRTUTF16 pwszStart) +{ + if ((uintptr_t)pwcEnd > (uintptr_t)pwszStart) + { + RTUTF16 wc = pwcEnd[-1]; + if ( (wc == '\\' || wc == '/') + && (uintptr_t)(pwcEnd - pwszStart) != 1) + pwcEnd--; + + while ( (uintptr_t)pwcEnd > (uintptr_t)pwszStart + && (wc = pwcEnd[-1]) != '\\' + && (wc = pwcEnd[-1]) != '/') + pwcEnd--; + } + return pwcEnd; +} + + +/** + * Converts a relative windows-style path to relative NT format and encoding. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * rtTNtPathToNative with phRootDir set to NULL. + * @param phRootDir On input, the handle to the directory the path + * is relative to. On output, the handle to + * specify as root directory in the object + * attributes when accessing the path. If + * enmAscent is kRTNtPathRelativeAscent_Allow, it + * may have been set to NULL. + * @param pszPath The relative UTF-8 path. + * @param enmAscent How to handle ascent. + * @param fMustReturnAbsolute Must convert to an absolute path. This + * is necessary if the root dir is a NT directory + * object (e.g. /Devices) since they cannot parse + * relative paths it seems. + */ +RTDECL(int) RTNtPathRelativeFromUtf8(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath, + RTNTPATHRELATIVEASCENT enmAscent, bool fMustReturnAbsolute) +{ + size_t cwcMax; + int rc = RTStrCalcUtf16LenEx(pszPath, RTSTR_MAX, &cwcMax); + if (RT_FAILURE(rc)) + return rc; + if (cwcMax + 2 >= _32K) + return VERR_FILENAME_TOO_LONG; + + PRTUTF16 pwszDst; + pNtName->Length = 0; + pNtName->MaximumLength = (uint16_t)((cwcMax + 2) * sizeof(RTUTF16)); + pNtName->Buffer = pwszDst = RTUtf16Alloc((cwcMax + 2) * sizeof(RTUTF16)); + if (!pwszDst) + return VERR_NO_UTF16_MEMORY; + + PRTUTF16 pwszDstCur = pwszDst; + PRTUTF16 pwszDstComp = pwszDst; + for (;;) + { + RTUNICP uc; + rc = RTStrGetCpEx(&pszPath, &uc); + if (RT_SUCCESS(rc)) + { + switch (uc) + { + default: + pwszDstCur = RTUtf16PutCp(pwszDstCur, uc); + break; + + case '\\': + case '/': + if (pwszDstCur != pwszDstComp) + pwszDstComp = pwszDstCur = RTUtf16PutCp(pwszDstCur, '\\'); + /* else: only one slash between components. */ + break; + + case '.': + if (pwszDstCur == pwszDstComp) + { + /* + * Single dot changes nothing. + */ + char ch2 = *pszPath; + if (ch2 == '\0') + { + /* Trailing single dot means we need to drop trailing slash. */ + if (pwszDstCur != pwszDst) + pwszDstCur--; + *pwszDstCur = '\0'; + pNtName->Length = (uint16_t)((uintptr_t)pwszDstCur - (uintptr_t)pwszDst); + if (!fMustReturnAbsolute || *phRootDir == NULL) + return VINF_SUCCESS; + return rtNtPathRelativeToAbs(pNtName, phRootDir); + } + + if (ch2 == '\\' || ch2 == '/') + { + pszPath++; /* Ignore lone dot followed but another component. */ + break; + } + + /* + * Two dots drops off the last directory component. This gets complicated + * when we start out without any path and we need to consult enmAscent. + */ + if (ch2 == '.') + { + char ch3 = pszPath[1]; + if ( ch3 == '\\' + || ch3 == '/' + || ch3 == '\0') + { + /* Drop a path component. */ + if (pwszDstComp != pwszDst) + pwszDstComp = pwszDstCur = rtNtPathGetPrevComponent(pwszDstCur, pwszDst); + /* Hit the start, which is a bit complicated. */ + else + switch (enmAscent) + { + case kRTNtPathRelativeAscent_Allow: + if (*phRootDir != NULL) + { + RTUtf16Free(pwszDst); + rc = RTNtPathFromHandle(pNtName, *phRootDir, cwcMax + 2); + if (RT_FAILURE(rc)) + return rc; + + *phRootDir = NULL; + pwszDst = pNtName->Buffer; + pwszDstCur = &pwszDst[pNtName->Length / sizeof(RTUTF16)]; + if ( pwszDst != pwszDstCur + && pwszDstCur[-1] != '\\' + && pwszDstCur[-1] != '/') + *pwszDstCur++ = '\\'; + pwszDstComp = pwszDstCur = rtNtPathGetPrevComponent(pwszDstCur, pwszDst); + } + /* else: ignore attempt to ascend beyond the NT root (won't get here). */ + break; + + case kRTNtPathRelativeAscent_Ignore: + /* nothing to do here */ + break; + + default: + case kRTNtPathRelativeAscent_Fail: + RTUtf16Free(pwszDst); + return VERR_PATH_NOT_FOUND; + } + + if (ch3 == '\0') + { + *pwszDstCur = '\0'; + pNtName->Length = (uint16_t)((uintptr_t)pwszDstCur - (uintptr_t)pwszDst); + if (!fMustReturnAbsolute || *phRootDir == NULL) + return VINF_SUCCESS; + return rtNtPathRelativeToAbs(pNtName, phRootDir); + } + pszPath += 2; + break; + } + } + } + + /* Neither '.' nor '..'. */ + pwszDstCur = RTUtf16PutCp(pwszDstCur, '.'); + break; + + case '\0': + *pwszDstCur = '\0'; + pNtName->Length = (uint16_t)((uintptr_t)pwszDstCur - (uintptr_t)pwszDst); + if (!fMustReturnAbsolute || *phRootDir == NULL) + return VINF_SUCCESS; + return rtNtPathRelativeToAbs(pNtName, phRootDir); + } + } + } +} + + +/** + * Frees the native path and root handle. + * + * @param pNtName The NT path after a successful rtNtPathToNative + * call or RTNtPathRelativeFromUtf8. + * @param phRootDir The root handle variable from rtNtPathToNative, + * but NOT RTNtPathRelativeFromUtf8. + */ +static void rtNtPathFreeNative(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir) +{ + RTUtf16Free(pNtName->Buffer); + pNtName->Buffer = NULL; + + RT_NOREF_PV(phRootDir); /* never returned by rtNtPathToNative, shouldn't be freed in connection with RTNtPathRelativeFromUtf8 */ +} + + +/** + * Frees the native path and root handle. + * + * @param pNtName The NT path after a successful rtNtPathToNative + * call or RTNtPathRelativeFromUtf8. + * @param phRootDir The root handle variable from rtNtPathToNative, + */ +RTDECL(void) RTNtPathFree(struct _UNICODE_STRING *pNtName, HANDLE *phRootDir) +{ + rtNtPathFreeNative(pNtName, phRootDir); +} + + +/** + * Wrapper around NtCreateFile. + * + * @returns IPRT status code. + * @param pszPath The UTF-8 path. + * @param fDesiredAccess See NtCreateFile. + * @param fFileAttribs See NtCreateFile. + * @param fShareAccess See NtCreateFile. + * @param fCreateDisposition See NtCreateFile. + * @param fCreateOptions See NtCreateFile. + * @param fObjAttribs The OBJECT_ATTRIBUTES::Attributes value, see + * NtCreateFile and InitializeObjectAttributes. + * @param phHandle Where to return the handle. + * @param puAction Where to return the action taken. Optional. + */ +RTDECL(int) RTNtPathOpen(const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, ULONG fShareAccess, + ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + PHANDLE phHandle, PULONG_PTR puAction) +{ + *phHandle = RTNT_INVALID_HANDLE_VALUE; + + HANDLE hRootDir; + UNICODE_STRING NtName; + int rc = rtNtPathToNative(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, fObjAttribs, hRootDir, NULL); + + NTSTATUS rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + fFileAttribs, + fShareAccess, + fCreateDisposition, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + if (puAction) + *puAction = Ios.Information; + *phHandle = hFile; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + rtNtPathFreeNative(&NtName, &hRootDir); + } + return rc; +} + + +/** + * Wrapper around NtCreateFile. + * + * @returns IPRT status code. + * @param pszPath The UTF-8 path. + * @param fDesiredAccess See NtCreateFile. + * @param fShareAccess See NtCreateFile. + * @param fCreateOptions See NtCreateFile. + * @param fObjAttribs The OBJECT_ATTRIBUTES::Attributes value, see + * NtCreateFile and InitializeObjectAttributes. + * @param phHandle Where to return the handle. + * @param pfObjDir If not NULL, the variable pointed to will be set + * to @c true if we opened an object directory and + * @c false if we opened an directory file (normal + * directory). + */ +RTDECL(int) RTNtPathOpenDir(const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fShareAccess, ULONG fCreateOptions, + ULONG fObjAttribs, PHANDLE phHandle, bool *pfObjDir) +{ + *phHandle = RTNT_INVALID_HANDLE_VALUE; + + HANDLE hRootDir; + UNICODE_STRING NtName; + int rc = rtNtPathToNative(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + if (pfObjDir) + { + *pfObjDir = false; +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if ( !RTPATH_IS_SLASH(pszPath[0]) + || !RTPATH_IS_SLASH(pszPath[1]) + || pszPath[2] != ':' + || pszPath[3] != 'i' + || pszPath[4] != 'p' + || pszPath[5] != 'r' + || pszPath[6] != 't' + || pszPath[7] != 'n' + || pszPath[8] != 't' + || pszPath[9] != ':' + || !RTPATH_IS_SLASH(pszPath[10]) ) +#endif + pfObjDir = NULL; + } + rc = RTNtPathOpenDirEx(hRootDir, &NtName, fDesiredAccess, fShareAccess, fCreateOptions, fObjAttribs, phHandle, pfObjDir); + rtNtPathFreeNative(&NtName, &hRootDir); + } + return rc; +} + + + +/** + * Wrapper around NtCreateFile, extended version. + * + * @returns IPRT status code. + * @param hRootDir The root director the path is relative to. NULL + * if none. + * @param pNtName The NT path. + * @param fDesiredAccess See NtCreateFile. + * @param fShareAccess See NtCreateFile. + * @param fCreateOptions See NtCreateFile. + * @param fObjAttribs The OBJECT_ATTRIBUTES::Attributes value, see + * NtCreateFile and InitializeObjectAttributes. + * @param phHandle Where to return the handle. + * @param pfObjDir If not NULL, the variable pointed to will be set + * to @c true if we opened an object directory and + * @c false if we opened an directory file (normal + * directory). + */ +RTDECL(int) RTNtPathOpenDirEx(HANDLE hRootDir, struct _UNICODE_STRING *pNtName, ACCESS_MASK fDesiredAccess, ULONG fShareAccess, + ULONG fCreateOptions, ULONG fObjAttribs, PHANDLE phHandle, bool *pfObjDir) +{ + *phHandle = RTNT_INVALID_HANDLE_VALUE; + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, pNtName, fObjAttribs, hRootDir, NULL); + + NTSTATUS rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + fShareAccess, + FILE_OPEN, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + if (pfObjDir) + *pfObjDir = false; + *phHandle = hFile; + return VINF_SUCCESS; + } + + /* + * Try add a slash in case this is a device object with a file system attached. + */ + if ( rcNt == STATUS_INVALID_PARAMETER + && pNtName->Length < _64K - 4 + && ( pNtName->Length == 0 + || pNtName->Buffer[pNtName->Length / sizeof(RTUTF16)] != '\\') ) + { + UNICODE_STRING NtTmp; + NtTmp.Length = pNtName->Length + 2; + NtTmp.MaximumLength = NtTmp.Length + 2; + NtTmp.Buffer = (PRTUTF16)RTMemTmpAlloc(NtTmp.MaximumLength); + if (NtTmp.Buffer) + { + memcpy(NtTmp.Buffer, pNtName->Buffer, pNtName->Length); + NtTmp.Buffer[pNtName->Length / sizeof(RTUTF16)] = '\\'; + NtTmp.Buffer[pNtName->Length / sizeof(RTUTF16) + 1] = '\0'; + + hFile = RTNT_INVALID_HANDLE_VALUE; + Ios.Status = -1; + Ios.Information = 0; + ObjAttr.ObjectName = &NtTmp; + + rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + fShareAccess, + FILE_OPEN, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + RTMemTmpFree(NtTmp.Buffer); + if (NT_SUCCESS(rcNt)) + { + if (pfObjDir) + *pfObjDir = false; + *phHandle = hFile; + return VINF_SUCCESS; + } + ObjAttr.ObjectName = pNtName; + } + } + + /* + * Try open it as a directory object if it makes sense. + */ + if ( pfObjDir + && ( rcNt == STATUS_OBJECT_NAME_INVALID + || rcNt == STATUS_OBJECT_TYPE_MISMATCH )) + { + /* Strip trailing slash. */ + struct _UNICODE_STRING NtName2 = *pNtName; + if ( NtName2.Length > 2 + && RTPATH_IS_SLASH(NtName2.Buffer[(NtName2.Length / 2) - 1])) + NtName2.Length -= 2; + ObjAttr.ObjectName = &NtName2; + + /* Rought conversion of the access flags. */ + ULONG fObjDesiredAccess = 0; + if ( (fDesiredAccess & GENERIC_ALL) + || (fDesiredAccess & STANDARD_RIGHTS_ALL) == STANDARD_RIGHTS_ALL) + fObjDesiredAccess = DIRECTORY_ALL_ACCESS; + else + { + if (fDesiredAccess & (GENERIC_WRITE | STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA)) + fObjDesiredAccess |= DIRECTORY_CREATE_OBJECT | DIRECTORY_CREATE_OBJECT | DIRECTORY_CREATE_SUBDIRECTORY; + + if ( (fDesiredAccess & (GENERIC_READ | STANDARD_RIGHTS_READ | FILE_LIST_DIRECTORY)) + || !fObjDesiredAccess) + fObjDesiredAccess |= DIRECTORY_QUERY; + + if (fDesiredAccess & FILE_TRAVERSE) + fObjDesiredAccess |= DIRECTORY_TRAVERSE; + } + + rcNt = NtOpenDirectoryObject(&hFile, fObjDesiredAccess, &ObjAttr); + if (NT_SUCCESS(rcNt)) + { + *pfObjDir = true; + *phHandle = hFile; + return VINF_SUCCESS; + } + } + + return RTErrConvertFromNtStatus(rcNt); +} + + + +/** + * Closes an handled open by rtNtPathOpen. + * + * @returns IPRT status code + * @param hHandle The handle value. + */ +RTDECL(int) RTNtPathClose(HANDLE hHandle) +{ + NTSTATUS rcNt = NtClose(hHandle); + if (NT_SUCCESS(rcNt)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(rcNt); +} + diff --git a/src/VBox/Runtime/r3/nt/time-nt.cpp b/src/VBox/Runtime/r3/nt/time-nt.cpp new file mode 100644 index 00000000..f60250e6 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/time-nt.cpp @@ -0,0 +1,228 @@ +/* $Id: time-nt.cpp $ */ +/** @file + * IPRT - Time, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#include "internal-r3-nt.h" + +#include <iprt/time.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/ldr.h> +#include <iprt/uint128.h> +#include "internal/time.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Whether we've tried to resolve g_pfnRtlGetSystemTimePrecise or not. */ +static bool g_fInitialized = false; +/** Pointer to RtlGetSystemTimePrecise, added in 6.2 (windows 8). */ +static PFNRTLGETSYSTEMTIMEPRECISE g_pfnRtlGetSystemTimePrecise = NULL; + + +/** + * Initializes globals. + */ +static void rtTimeNtInitialize(void) +{ + /* + * Make sure we don't recurse here when calling into RTLdr. + */ + if (ASMAtomicCmpXchgBool(&g_fInitialized, true, false)) + { + void *pvFunc = RTLdrGetSystemSymbol("ntdll.dll", "RtlGetSystemTimePrecise"); + if (pvFunc) + ASMAtomicWritePtr((void * volatile *)&g_pfnRtlGetSystemTimePrecise, pvFunc); + ASMCompilerBarrier(); + } +} + + +static uint64_t rtTimeGetSystemNanoTS(void) +{ + if (RT_UNLIKELY(!g_fInitialized)) + rtTimeNtInitialize(); + + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; + +#if 1 + /* + * If there is precise time, get the precise system time and calculate the + * interrupt time from it. (Microsoft doesn't expose interrupt time to user + * application, which is very unfortunate as there are a lot place where + * monotonic time is applicable but developer is "forced" to use wall clock.) + */ + if (g_pfnRtlGetSystemTimePrecise) + { + for (;;) + { + uint64_t uUpdateLockBefore; + while ((uUpdateLockBefore = pUserSharedData->TimeUpdateLock) & 1) + ASMNopPause(); + + uint64_t uInterruptTime = *(uint64_t volatile *)&pUserSharedData->InterruptTime; + uint64_t uBaselineInterruptTimeQpc = pUserSharedData->BaselineInterruptTimeQpc; + uint64_t uQpcInterruptTimeIncrement = pUserSharedData->QpcInterruptTimeIncrement; + uint8_t uQpcInterruptTimeIncrementShift = pUserSharedData->QpcInterruptTimeIncrementShift; + LARGE_INTEGER QpcValue; + RtlQueryPerformanceCounter(&QpcValue); + + if (pUserSharedData->TimeUpdateLock == uUpdateLockBefore) + { + uint64_t uQpcValue = QpcValue.QuadPart; + if (uQpcValue <= uBaselineInterruptTimeQpc) + return uInterruptTime * 100; + + /* Calc QPC delta since base line. */ + uQpcValue -= uBaselineInterruptTimeQpc; + uQpcValue--; + + /* Multiply by 10 million. */ + uQpcValue *= UINT32_C(10000000); + + /* Multiply by QPC interrupt time increment value. */ + RTUINT128U Tmp128; + RTUInt128MulU64ByU64(&Tmp128, uQpcValue, uQpcInterruptTimeIncrement); + + /* Shift the upper 64 bits by the increment shift factor. */ + uint64_t uResult = Tmp128.s.Hi >> uQpcInterruptTimeIncrementShift; + + /* Add to base interrupt time value. */ + uResult += uInterruptTime; + + /* Convert from NT unit to nano seconds. */ + return uResult * 100; + } + + ASMNopPause(); + } + } +#endif + + /* + * Just read interrupt time. + */ +#if ARCH_BITS >= 64 + uint64_t uRet = *(uint64_t volatile *)&pUserSharedData->InterruptTime; /* This is what KeQueryInterruptTime does. */ + uRet *= 100; + return uRet; +#else + + LARGE_INTEGER NtTime; + do + { + NtTime.HighPart = pUserSharedData->InterruptTime.High1Time; + NtTime.LowPart = pUserSharedData->InterruptTime.LowPart; + } while (pUserSharedData->InterruptTime.High2Time != NtTime.HighPart); + + return (uint64_t)NtTime.QuadPart * 100; +#endif +} + + +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return rtTimeGetSystemNanoTS(); +} + + +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return rtTimeGetSystemNanoTS() / RT_NS_1MS; +} + + +RTDECL(PRTTIMESPEC) RTTimeNow(PRTTIMESPEC pTime) +{ + /* + * Get the precise time if possible. + */ + if (RT_UNLIKELY(!g_fInitialized)) + rtTimeNtInitialize(); + if (g_pfnRtlGetSystemTimePrecise != NULL) + return RTTimeSpecSetNtTime(pTime, g_pfnRtlGetSystemTimePrecise()); + + /* + * Just read system time. + */ + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; +#ifdef RT_ARCH_AMD64 + uint64_t uRet = *(uint64_t volatile *)&pUserSharedData->SystemTime; /* This is what KeQuerySystemTime does. */ + return RTTimeSpecSetNtTime(pTime, uRet); +#else + + LARGE_INTEGER NtTime; + do + { + NtTime.HighPart = pUserSharedData->SystemTime.High1Time; + NtTime.LowPart = pUserSharedData->SystemTime.LowPart; + } while (pUserSharedData->SystemTime.High2Time != NtTime.HighPart); + return RTTimeSpecSetNtTime(pTime, NtTime.QuadPart); +#endif +} + + +RTDECL(PRTTIMESPEC) RTTimeLocalNow(PRTTIMESPEC pTime) +{ + return RTTimeSpecAddNano(RTTimeNow(pTime), RTTimeLocalDeltaNano()); +} + + +RTDECL(int64_t) RTTimeLocalDeltaNano(void) +{ + /* + * UTC = local + TimeZoneBias; The bias is given in NT units. + */ + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; + LARGE_INTEGER Delta; +#if ARCH_BITS == 64 + Delta.QuadPart = *(int64_t volatile *)&pUserSharedData->TimeZoneBias; +#else + do + { + Delta.HighPart = pUserSharedData->TimeZoneBias.High1Time; + Delta.LowPart = pUserSharedData->TimeZoneBias.LowPart; + } while (pUserSharedData->TimeZoneBias.High2Time != Delta.HighPart); +#endif + return Delta.QuadPart * -100; +} + diff --git a/src/VBox/Runtime/r3/os2/Makefile.kup b/src/VBox/Runtime/r3/os2/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/os2/Makefile.kup diff --git a/src/VBox/Runtime/r3/os2/RTTimeSet-os2.cpp b/src/VBox/Runtime/r3/os2/RTTimeSet-os2.cpp new file mode 100644 index 00000000..fded291b --- /dev/null +++ b/src/VBox/Runtime/r3/os2/RTTimeSet-os2.cpp @@ -0,0 +1,121 @@ +/* $Id: RTTimeSet-os2.cpp $ */ +/** @file + * IPRT - RTTimeSet, OS/2. + */ + +/* + * Contributed by knut st. osmundsen. + * + * Copyright (C) 2018-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + * -------------------------------------------------------------------- + * + * Copyright (c) 2018 knut st. osmundsen <bird-src-spam@anduin.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define INCL_DOSDATETIME +#define INCL_DOSERRORS +#include <os2.h> + +#include <iprt/time.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> + + +RTDECL(int) RTTimeSet(PCRTTIMESPEC pTime) +{ + /* + * Convert to local time and explode it, keeping the distance + * between UTC and local. + */ + int64_t cNsLocalDelta = RTTimeLocalDeltaNanoFor(pTime); + RTTIMESPEC TimeLocal = *pTime; + RTTIME Exploded; + if (RTTimeExplode(&Exploded, RTTimeSpecAddNano(&TimeLocal, cNsLocalDelta))) + { + /* + * Fill in the OS/2 structure and make the call. + */ + DATETIME DateTime; + DateTime.hours = Exploded.u8Hour; + DateTime.minutes = Exploded.u8Minute; + DateTime.seconds = Exploded.u8Second; + DateTime.hundredths = (uint8_t)(Exploded.u32Nanosecond / (RT_NS_1SEC_64 / 100)); + DateTime.day = Exploded.u8MonthDay; + DateTime.month = Exploded.u8Month; + DateTime.year = (uint16_t)Exploded.i32Year; + DateTime.weekday = Exploded.u8WeekDay; + + /* Minutes from UTC. http://www.edm2.com/os2api/Dos/DosSetDateTime.html says + that timezones west of UTC should have a positive value. The kernel fails + the call if we're more than +/-780 min (13h) distant, so clamp it in + case of bogus TZ values. */ + DateTime.timezone = (int16_t)(-cNsLocalDelta / (int64_t)RT_NS_1MIN); + if (DateTime.timezone > 780) + DateTime.timezone = 780; + else if (DateTime.timezone < -780) + DateTime.timezone = -780; + + APIRET rc = DosSetDateTime(&DateTime); + if (rc == NO_ERROR) + return VINF_SUCCESS; + AssertMsgFailed(("rc=%u\n", rc)); + return RTErrConvertFromOS2(rc); + } + return VERR_INVALID_PARAMETER; +} + diff --git a/src/VBox/Runtime/r3/os2/filelock-os2.cpp b/src/VBox/Runtime/r3/os2/filelock-os2.cpp new file mode 100644 index 00000000..9fb93078 --- /dev/null +++ b/src/VBox/Runtime/r3/os2/filelock-os2.cpp @@ -0,0 +1,186 @@ +/* $Id: filelock-os2.cpp $ */ +/** @file + * IPRT - File Locking, OS/2. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE + +#include <errno.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/fcntl.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/time.h> + +#include <iprt/file.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/file.h" +#include "internal/fs.h" + + + + +RTR3DECL(int) RTFileLock(RTFILE File, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* Check arguments. */ + if (fLock & ~RTFILE_LOCK_MASK) + { + AssertMsgFailed(("Invalid fLock=%08X\n", fLock)); + return VERR_INVALID_PARAMETER; + } + + /* + * Validate offset. + */ + if ( sizeof(off_t) < sizeof(cbLock) + && ( (offLock >> 32) != 0 + || (cbLock >> 32) != 0 + || ((offLock + cbLock) >> 32) != 0)) + { + AssertMsgFailed(("64-bit file i/o not supported! offLock=%lld cbLock=%lld\n", offLock, cbLock)); + return VERR_NOT_SUPPORTED; + } + + /* Prepare flock structure. */ + struct flock fl; + Assert(RTFILE_LOCK_WRITE); + fl.l_type = (fLock & RTFILE_LOCK_WRITE) ? F_WRLCK : F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_start = (off_t)offLock; + fl.l_len = (off_t)cbLock; + fl.l_pid = 0; + + Assert(RTFILE_LOCK_WAIT); + if (fcntl(RTFileToNative(File), (fLock & RTFILE_LOCK_WAIT) ? F_SETLKW : F_SETLK, &fl) >= 0) + return VINF_SUCCESS; + + int iErr = errno; + if ( iErr == EAGAIN + || iErr == EACCES) + return VERR_FILE_LOCK_VIOLATION; + + return RTErrConvertFromErrno(iErr); +} + + +RTR3DECL(int) RTFileChangeLock(RTFILE File, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + /** @todo copied from ../win/fileio-win.cpp for now but a proper solution + * would probably be to modify kLIBC so that __fcntl_locking() first + * assumes a change lock request is made (e.g. the same region was + * previously F_RDLCK'ed and now needs to be F_WRLCK'ed or vice versa) and + * tries to use atomic locking, and only if it fails, it does the regular + * lock procedure. The alternative is to use DosSetFileLocks directly here + * which basically means copy-pasting the __fcntl_locking() source + * code :) Note that the first attempt to call RTFileLock() below assumes + * that kLIBC is patched as described above one day and gives it a chance; + * on failure, we fall back to the Win-like unlock-then-lock approach. */ + + int rc = RTFileLock(File, fLock, offLock, cbLock); + if (RT_FAILURE(rc) && rc != VERR_FILE_LOCK_VIOLATION) + return rc; + + /* Check arguments. */ + if (fLock & ~RTFILE_LOCK_MASK) + { + AssertMsgFailed(("Invalid fLock=%08X\n", fLock)); + return VERR_INVALID_PARAMETER; + } + + /* Remove old lock. */ + rc = RTFileUnlock(File, offLock, cbLock); + if (RT_FAILURE(rc)) + return rc; + + /* Set new lock. */ + rc = RTFileLock(File, fLock, offLock, cbLock); + if (RT_SUCCESS(rc)) + return rc; + + /* Try to restore old lock. */ + unsigned fLockOld = (fLock & RTFILE_LOCK_WRITE) ? fLock & ~RTFILE_LOCK_WRITE : fLock | RTFILE_LOCK_WRITE; + rc = RTFileLock(File, fLockOld, offLock, cbLock); + if (RT_SUCCESS(rc)) + return VERR_FILE_LOCK_VIOLATION; + else + return VERR_FILE_LOCK_LOST; +} + + +RTR3DECL(int) RTFileUnlock(RTFILE File, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* + * Validate offset. + */ + if ( sizeof(off_t) < sizeof(cbLock) + && ( (offLock >> 32) != 0 + || (cbLock >> 32) != 0 + || ((offLock + cbLock) >> 32) != 0)) + { + AssertMsgFailed(("64-bit file i/o not supported! offLock=%lld cbLock=%lld\n", offLock, cbLock)); + return VERR_NOT_SUPPORTED; + } + + /* Prepare flock structure. */ + struct flock fl; + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = (off_t)offLock; + fl.l_len = (off_t)cbLock; + fl.l_pid = 0; + + if (fcntl(RTFileToNative(File), F_SETLK, &fl) >= 0) + return VINF_SUCCESS; + + /** @todo check error codes for non existing lock. */ + int iErr = errno; + if ( iErr == EAGAIN + || iErr == EACCES) + return VERR_FILE_LOCK_VIOLATION; + + return RTErrConvertFromErrno(iErr); +} + diff --git a/src/VBox/Runtime/r3/os2/mp-os2.cpp b/src/VBox/Runtime/r3/os2/mp-os2.cpp new file mode 100644 index 00000000..64ae06a8 --- /dev/null +++ b/src/VBox/Runtime/r3/os2/mp-os2.cpp @@ -0,0 +1,125 @@ +/* $Id: mp-os2.cpp $ */ +/** @file + * IPRT - Multiprocessor, OS/2. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_BASE +#define INCL_ERRORS +#include <os2.h> +#undef RT_MAX + +#include <iprt/mp.h> +#include <iprt/cpuset.h> +#include <iprt/assert.h> + +/** @todo RTMpCpuId() */ + +RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu) +{ + return idCpu < RTCPUSET_MAX_CPUS ? (int) idCpu : -1; +} + + +RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu) +{ + return (unsigned)iCpu < RTCPUSET_MAX_CPUS ? iCpu : NIL_RTCPUID; +} + + +RTDECL(RTCPUID) RTMpGetMaxCpuId(void) +{ + return RTCPUSET_MAX_CPUS; +} + + +RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) +{ + RTCPUSET Set; + return RTCpuSetIsMember(RTMpGetSet(&Set), idCpu); +} + + +RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet) +{ + RTCPUID idCpu = RTMpGetCount(); + RTCpuSetEmpty(pSet); + while (idCpu-- > 0) + RTCpuSetAdd(pSet, idCpu); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetCount(void) +{ + ULONG cCpus = 1; + int rc = DosQuerySysInfo(QSV_NUMPROCESSORS, QSV_NUMPROCESSORS, &cCpus, sizeof(cCpus)); + if (rc || !cCpus) + cCpus = 1; + return cCpus; +} + + +RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu) +{ + RTCPUSET Set; + return RTCpuSetIsMember(RTMpGetOnlineSet(&Set), idCpu); +} + + +RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet) +{ + union + { + uint64_t u64; + MPAFFINITY mpaff; + } u; + + int rc = DosQueryThreadAffinity(AFNTY_SYSTEM, &u.mpaff); + if (rc) + u.u64 = 1; + return RTCpuSetFromU64(pSet, u.u64); +} + + +RTDECL(RTCPUID) RTMpGetOnlineCount(void) +{ + RTCPUSET Set; + RTMpGetOnlineSet(&Set); + return RTCpuSetCount(&Set); +} + diff --git a/src/VBox/Runtime/r3/os2/pipe-os2.cpp b/src/VBox/Runtime/r3/os2/pipe-os2.cpp new file mode 100644 index 00000000..6a4792c5 --- /dev/null +++ b/src/VBox/Runtime/r3/os2/pipe-os2.cpp @@ -0,0 +1,1083 @@ +/* $Id: pipe-os2.cpp $ */ +/** @file + * IPRT - Anonymous Pipes, OS/2 Implementation. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_ERRORS +#define INCL_DOSSEMAPHORES +#include <os2.h> + +#include <iprt/pipe.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include "internal/pipe.h" +#include "internal/magics.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The pipe buffer size we prefer. */ +#define RTPIPE_OS2_SIZE _32K + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTPIPEINTERNAL +{ + /** Magic value (RTPIPE_MAGIC). */ + uint32_t u32Magic; + /** The pipe handle. */ + HPIPE hPipe; + /** Set if this is the read end, clear if it's the write end. */ + bool fRead; + /** RTPipeFromNative: Leave open. */ + bool fLeaveOpen; + /** Whether the pipe is in blocking or non-blocking mode. */ + bool fBlocking; + /** Set if the pipe is broken. */ + bool fBrokenPipe; + /** Usage counter. */ + uint32_t cUsers; + + /** The event semaphore associated with the pipe. */ + HEV hev; + /** The handle of the poll set currently polling on this pipe. + * We can only have one poller at the time (lazy bird). */ + RTPOLLSET hPollSet; + /** Critical section protecting the above members. + * (Taking the lazy/simple approach.) */ + RTCRITSECT CritSect; + +} RTPIPEINTERNAL; + + +/** + * Ensures that the pipe has a semaphore associated with it. + * + * @returns VBox status code. + * @param pThis The pipe. + */ +static int rtPipeOs2EnsureSem(RTPIPEINTERNAL *pThis) +{ + if (pThis->hev != NULLHANDLE) + return VINF_SUCCESS; + + HEV hev; + APIRET orc = DosCreateEventSem(NULL, &hev, DC_SEM_SHARED, FALSE); + if (orc == NO_ERROR) + { + orc = DosSetNPipeSem(pThis->hPipe, (HSEM)hev, 1); + if (orc == NO_ERROR) + { + pThis->hev = hev; + return VINF_SUCCESS; + } + + DosCloseEventSem(hev); + } + return RTErrConvertFromOS2(orc); +} + + +RTDECL(int) RTPipeCreate(PRTPIPE phPipeRead, PRTPIPE phPipeWrite, uint32_t fFlags) +{ + AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER); + AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTPIPE_C_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Try create and connect a pipe pair. + */ + APIRET orc; + HPIPE hPipeR; + HFILE hPipeW; + int rc; + for (;;) + { + static volatile uint32_t g_iNextPipe = 0; + char szName[128]; + RTStrPrintf(szName, sizeof(szName), "\\pipe\\iprt-pipe-%u-%u", RTProcSelf(), ASMAtomicIncU32(&g_iNextPipe)); + + /* + * Create the read end of the pipe. + */ + ULONG fPipeMode = 1 /*instance*/ | NP_TYPE_BYTE | NP_READMODE_BYTE | NP_NOWAIT; + ULONG fOpenMode = NP_ACCESS_DUPLEX | NP_WRITEBEHIND; + if (fFlags & RTPIPE_C_INHERIT_READ) + fOpenMode |= NP_INHERIT; + else + fOpenMode |= NP_NOINHERIT; + orc = DosCreateNPipe((PSZ)szName, &hPipeR, fOpenMode, fPipeMode, RTPIPE_OS2_SIZE, RTPIPE_OS2_SIZE, NP_DEFAULT_WAIT); + if (orc == NO_ERROR) + { + orc = DosConnectNPipe(hPipeR); + if (orc == ERROR_PIPE_NOT_CONNECTED || orc == NO_ERROR) + { + /* + * Connect to the pipe (the write end), attach sem below. + */ + ULONG ulAction = 0; + ULONG fOpenW = OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS; + ULONG fModeW = OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYNONE | OPEN_FLAGS_FAIL_ON_ERROR; + if (!(fFlags & RTPIPE_C_INHERIT_WRITE)) + fModeW |= OPEN_FLAGS_NOINHERIT; + orc = DosOpen((PSZ)szName, &hPipeW, &ulAction, 0 /*cbFile*/, FILE_NORMAL, + fOpenW, fModeW, NULL /*peaop2*/); + if (orc == NO_ERROR) + break; + } + DosClose(hPipeR); + } + if ( orc != ERROR_PIPE_BUSY /* already exist - compatible */ + && orc != ERROR_ACCESS_DENIED /* already exist - incompatible (?) */) + return RTErrConvertFromOS2(orc); + /* else: try again with a new name */ + } + + /* + * Create the two handles. + */ + RTPIPEINTERNAL *pThisR = (RTPIPEINTERNAL *)RTMemAllocZ(sizeof(RTPIPEINTERNAL)); + if (pThisR) + { + RTPIPEINTERNAL *pThisW = (RTPIPEINTERNAL *)RTMemAllocZ(sizeof(RTPIPEINTERNAL)); + if (pThisW) + { + /* Crit sects. */ + rc = RTCritSectInit(&pThisR->CritSect); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pThisW->CritSect); + if (RT_SUCCESS(rc)) + { + /* Initialize the structures. */ + pThisR->u32Magic = RTPIPE_MAGIC; + pThisW->u32Magic = RTPIPE_MAGIC; + pThisR->hPipe = hPipeR; + pThisW->hPipe = hPipeW; + pThisR->hev = NULLHANDLE; + pThisW->hev = NULLHANDLE; + pThisR->fRead = true; + pThisW->fRead = false; + pThisR->fLeaveOpen = false; + pThisW->fLeaveOpen = false; + pThisR->fBlocking = false; + pThisW->fBlocking = true; + //pThisR->fBrokenPipe = false; + //pThisW->fBrokenPipe = false; + //pThisR->cUsers = 0; + //pThisW->cUsers = 0; + pThisR->hPollSet = NIL_RTPOLLSET; + pThisW->hPollSet = NIL_RTPOLLSET; + + *phPipeRead = pThisR; + *phPipeWrite = pThisW; + return VINF_SUCCESS; + } + + RTCritSectDelete(&pThisR->CritSect); + } + RTMemFree(pThisW); + } + else + rc = VERR_NO_MEMORY; + RTMemFree(pThisR); + } + else + rc = VERR_NO_MEMORY; + + /* Don't call DosDisConnectNPipe! */ + DosClose(hPipeW); + DosClose(hPipeR); + return rc; +} + + +RTDECL(int) RTPipeCloseEx(RTPIPE hPipe, bool fLeaveOpen) +{ + RTPIPEINTERNAL *pThis = hPipe; + if (pThis == NIL_RTPIPE) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the cleanup. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTPIPE_MAGIC, RTPIPE_MAGIC), VERR_INVALID_HANDLE); + RTCritSectEnter(&pThis->CritSect); + Assert(pThis->cUsers == 0); + + /* Don't call DosDisConnectNPipe! */ + if (!fLeaveOpen && !pThis->fLeaveOpen) + DosClose(pThis->hPipe); + pThis->hPipe = (HPIPE)-1; + + if (pThis->hev != NULLHANDLE) + { + DosCloseEventSem(pThis->hev); + pThis->hev = NULLHANDLE; + } + + RTCritSectLeave(&pThis->CritSect); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTPipeClose(RTPIPE hPipe) +{ + return RTPipeCloseEx(hPipe, false /*fLeaveOpen*/); +} + + +RTDECL(int) RTPipeFromNative(PRTPIPE phPipe, RTHCINTPTR hNativePipe, uint32_t fFlags) +{ + AssertPtrReturn(phPipe, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTPIPE_N_VALID_MASK_FN), VERR_INVALID_PARAMETER); + AssertReturn(!!(fFlags & RTPIPE_N_READ) != !!(fFlags & RTPIPE_N_WRITE), VERR_INVALID_PARAMETER); + + /* + * Get and validate the pipe handle info. + */ + HPIPE hNative = (HPIPE)hNativePipe; + ULONG ulType = 0; + ULONG ulAttr = 0; + APIRET orc = DosQueryHType(hNative, &ulType, &ulAttr); + AssertMsgReturn(orc == NO_ERROR, ("%d\n", orc), RTErrConvertFromOS2(orc)); + AssertReturn((ulType & 0x7) == HANDTYPE_PIPE, VERR_INVALID_HANDLE); + +#if 0 + union + { + PIPEINFO PipeInfo; + uint8_t abPadding[sizeof(PIPEINFO) + 127]; + } Buf; + orc = DosQueryNPipeInfo(hNative, 1, &Buf, sizeof(Buf)); + if (orc != NO_ERROR) + { + /* Sorry, anonymous pips are not supported. */ + AssertMsgFailed(("%d\n", orc)); + return VERR_INVALID_HANDLE; + } + AssertReturn(Buf.PipeInfo.cbMaxInst == 1, VERR_INVALID_HANDLE); +#endif + + ULONG fPipeState = 0; + orc = DosQueryNPHState(hNative, &fPipeState); + if (orc != NO_ERROR) + { + /* Sorry, anonymous pips are not supported. */ + AssertMsgFailed(("%d\n", orc)); + return VERR_INVALID_HANDLE; + } + AssertReturn(!(fPipeState & NP_TYPE_MESSAGE), VERR_INVALID_HANDLE); + AssertReturn(!(fPipeState & NP_READMODE_MESSAGE), VERR_INVALID_HANDLE); + AssertReturn((fPipeState & 0xff) == 1, VERR_INVALID_HANDLE); + + ULONG fFileState = 0; + orc = DosQueryFHState(hNative, &fFileState); + AssertMsgReturn(orc == NO_ERROR, ("%d\n", orc), VERR_INVALID_HANDLE); + AssertMsgReturn( (fFileState & 0x3) == (fFlags & RTPIPE_N_READ ? OPEN_ACCESS_READONLY : OPEN_ACCESS_WRITEONLY) + || (fFileState & 0x3) == OPEN_ACCESS_READWRITE + , ("%#x\n", fFileState), VERR_INVALID_HANDLE); + + /* + * Looks kind of OK. Fix the inherit flag. + */ + orc = DosSetFHState(hNative, (fFileState & (OPEN_FLAGS_WRITE_THROUGH | OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_CACHE)) + | (fFlags & RTPIPE_N_INHERIT ? 0 : OPEN_FLAGS_NOINHERIT)); + AssertMsgReturn(orc == NO_ERROR, ("%d\n", orc), RTErrConvertFromOS2(orc)); + + + /* + * Create a handle so we can try rtPipeQueryInfo on it + * and see if we need to duplicate it to make that call work. + */ + RTPIPEINTERNAL *pThis = (RTPIPEINTERNAL *)RTMemAllocZ(sizeof(RTPIPEINTERNAL)); + if (!pThis) + return VERR_NO_MEMORY; + int rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pThis->u32Magic = RTPIPE_MAGIC; + pThis->hPipe = hNative; + pThis->hev = NULLHANDLE; + pThis->fRead = RT_BOOL(fFlags & RTPIPE_N_READ); + pThis->fLeaveOpen = RT_BOOL(fFlags & RTPIPE_N_LEAVE_OPEN); + pThis->fBlocking = !(fPipeState & NP_NOWAIT); + //pThis->fBrokenPipe = false; + //pThis->cUsers = 0; + pThis->hPollSet = NIL_RTPOLLSET; + + *phPipe = pThis; + return VINF_SUCCESS; + + //RTCritSectDelete(&pThis->CritSect); + } + RTMemFree(pThis); + return rc; +} + +RTDECL(RTHCINTPTR) RTPipeToNative(RTPIPE hPipe) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, -1); + + return (RTHCINTPTR)pThis->hPipe; +} + +/** + * Prepare blocking mode. + * + * @returns IPRT status code. + * @retval VERR_WRONG_ORDER if simultaneous non-blocking and blocking access is + * attempted. + * + * @param pThis The pipe handle. + * + * @remarks Caller owns the critical section. + */ +static int rtPipeTryBlocking(RTPIPEINTERNAL *pThis) +{ + if (!pThis->fBlocking) + { + if (pThis->cUsers != 0) + return VERR_WRONG_ORDER; + + APIRET orc = DosSetNPHState(pThis->hPipe, NP_WAIT | NP_READMODE_BYTE); + if (orc != NO_ERROR) + { + if (orc != ERROR_BROKEN_PIPE && orc != ERROR_PIPE_NOT_CONNECTED) + return RTErrConvertFromOS2(orc); + pThis->fBrokenPipe = true; + } + pThis->fBlocking = true; + } + + pThis->cUsers++; + return VINF_SUCCESS; +} + + +/** + * Prepare non-blocking mode. + * + * @returns IPRT status code. + * @retval VERR_WRONG_ORDER if simultaneous non-blocking and blocking access is + * attempted. + * + * @param pThis The pipe handle. + */ +static int rtPipeTryNonBlocking(RTPIPEINTERNAL *pThis) +{ + if (pThis->fBlocking) + { + if (pThis->cUsers != 0) + return VERR_WRONG_ORDER; + + APIRET orc = DosSetNPHState(pThis->hPipe, NP_NOWAIT | NP_READMODE_BYTE); + if (orc != NO_ERROR) + { + if (orc != ERROR_BROKEN_PIPE && orc != ERROR_PIPE_NOT_CONNECTED) + return RTErrConvertFromOS2(orc); + pThis->fBrokenPipe = true; + } + pThis->fBlocking = false; + } + + pThis->cUsers++; + return VINF_SUCCESS; +} + + +/** + * Checks if the read pipe has been broken. + * + * @returns true if broken, false if no. + * @param pThis The pipe handle (read). + */ +static bool rtPipeOs2IsBroken(RTPIPEINTERNAL *pThis) +{ + Assert(pThis->fRead); + +#if 0 + /* + * Query it via the semaphore. Not sure how fast this is... + */ + PIPESEMSTATE aStates[3]; RT_ZERO(aStates); + APIRET orc = DosQueryNPipeSemState(pThis->hev, &aStates[0], sizeof(aStates)); + if (orc == NO_ERROR) + { + if (aStates[0].fStatus == NPSS_CLOSE) + return true; + if (aStates[0].fStatus == NPSS_RDATA) + return false; + } + AssertMsgFailed(("%d / %d\n", orc, aStates[0].fStatus)); + + /* + * Fall back / alternative method. + */ +#endif + ULONG cbActual = 0; + ULONG ulState = 0; + AVAILDATA Avail = { 0, 0 }; + APIRET orc = DosPeekNPipe(pThis->hPipe, NULL, 0, &cbActual, &Avail, &ulState); + if (orc != NO_ERROR) + { + if (orc != ERROR_PIPE_BUSY) + AssertMsgFailed(("%d\n", orc)); + return false; + } + + return ulState != NP_STATE_CONNECTED; +} + + +RTDECL(int) RTPipeRead(RTPIPE hPipe, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pcbRead); + AssertPtr(pvBuf); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = rtPipeTryNonBlocking(pThis); + if (RT_SUCCESS(rc)) + { + RTCritSectLeave(&pThis->CritSect); + + ULONG cbActual = 0; + APIRET orc = DosRead(pThis->hPipe, pvBuf, cbToRead, &cbActual); + if (orc == NO_ERROR) + { + if (cbActual || !cbToRead || !rtPipeOs2IsBroken(pThis)) + *pcbRead = cbActual; + else + rc = VERR_BROKEN_PIPE; + } + else if (orc == ERROR_NO_DATA) + { + *pcbRead = 0; + rc = VINF_TRY_AGAIN; + } + else + rc = RTErrConvertFromOS2(orc); + + RTCritSectEnter(&pThis->CritSect); + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTPipeReadBlocking(RTPIPE hPipe, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pvBuf); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = rtPipeTryBlocking(pThis); + if (RT_SUCCESS(rc)) + { + RTCritSectLeave(&pThis->CritSect); + + size_t cbTotalRead = 0; + while (cbToRead > 0) + { + ULONG cbActual = 0; + APIRET orc = DosRead(pThis->hPipe, pvBuf, cbToRead, &cbActual); + if (orc != NO_ERROR) + { + rc = RTErrConvertFromOS2(orc); + break; + } + if (!cbActual && rtPipeOs2IsBroken(pThis)) + { + rc = VERR_BROKEN_PIPE; + break; + } + + /* advance */ + pvBuf = (char *)pvBuf + cbActual; + cbTotalRead += cbActual; + cbToRead -= cbActual; + } + + if (pcbRead) + { + *pcbRead = cbTotalRead; + if ( RT_FAILURE(rc) + && cbTotalRead) + rc = VINF_SUCCESS; + } + + RTCritSectEnter(&pThis->CritSect); + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +/** + * Gets the available write buffer size of the pipe. + * + * @returns Number of bytes, 1 on failure. + * @param pThis The pipe handle. + */ +static ULONG rtPipeOs2GetSpace(RTPIPEINTERNAL *pThis) +{ + Assert(!pThis->fRead); + +#if 0 /* Not sure which is more efficient, neither are really optimal, I fear. */ + /* + * Query via semaphore state. + * This will walk the list of active named pipes... + */ + /** @todo Check how hev and hpipe are associated, if complicated, use the + * alternative method below. */ + PIPESEMSTATE aStates[3]; RT_ZERO(aStates); + APIRET orc = DosQueryNPipeSemState((HSEM)pThis->hev, &aStates[0], sizeof(aStates)); + if (orc == NO_ERROR) + { + if (aStates[0].fStatus == NPSS_WSPACE) + return aStates[0].usAvail; + if (aStates[1].fStatus == NPSS_WSPACE) + return aStates[1].usAvail; + return 0; + } + AssertMsgFailed(("%d / %d\n", orc, aStates[0].fStatus)); + +#else + /* + * Query via the pipe info. + * This will have to lookup and store the pipe name. + */ + union + { + PIPEINFO PipeInfo; + uint8_t abPadding[sizeof(PIPEINFO) + 127]; + } Buf; + APIRET orc = DosQueryNPipeInfo(pThis->hPipe, 1, &Buf, sizeof(Buf)); + if (orc == NO_ERROR) + return Buf.PipeInfo.cbOut; + AssertMsgFailed(("%d\n", orc)); +#endif + + return 1; +} + + +RTDECL(int) RTPipeWrite(RTPIPE hPipe, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pcbWritten); + AssertPtr(pvBuf); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = rtPipeTryNonBlocking(pThis); + if (RT_SUCCESS(rc)) + { + if (cbToWrite > 0) + { + ULONG cbActual = 0; + APIRET orc = DosWrite(pThis->hPipe, pvBuf, cbToWrite, &cbActual); + if (orc == NO_ERROR && cbActual == 0) + { + /* Retry with the request adjusted to the available buffer space. */ + ULONG cbAvail = rtPipeOs2GetSpace(pThis); + orc = DosWrite(pThis->hPipe, pvBuf, RT_MIN(cbAvail, cbToWrite), &cbActual); + } + + if (orc == NO_ERROR) + { + *pcbWritten = cbActual; + if (cbActual == 0) + rc = VINF_TRY_AGAIN; + } + else + { + rc = RTErrConvertFromOS2(orc); + if (rc == VERR_PIPE_NOT_CONNECTED) + rc = VERR_BROKEN_PIPE; + } + } + else + *pcbWritten = 0; + + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTPipeWriteBlocking(RTPIPE hPipe, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pvBuf); + AssertPtrNull(pcbWritten); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = rtPipeTryBlocking(pThis); + if (RT_SUCCESS(rc)) + { + RTCritSectLeave(&pThis->CritSect); + + size_t cbTotalWritten = 0; + while (cbToWrite > 0) + { + ULONG cbActual = 0; + APIRET orc = DosWrite(pThis->hPipe, pvBuf, cbToWrite, &cbActual); + if (orc != NO_ERROR) + { + rc = RTErrConvertFromOS2(orc); + if (rc == VERR_PIPE_NOT_CONNECTED) + rc = VERR_BROKEN_PIPE; + break; + } + pvBuf = (char const *)pvBuf + cbActual; + cbToWrite -= cbActual; + cbTotalWritten += cbActual; + } + + if (pcbWritten) + { + *pcbWritten = cbTotalWritten; + if ( RT_FAILURE(rc) + && cbTotalWritten) + rc = VINF_SUCCESS; + } + + RTCritSectEnter(&pThis->CritSect); + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTPipeFlush(RTPIPE hPipe) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + + APIRET orc = DosResetBuffer(pThis->hPipe); + if (orc != NO_ERROR) + { + int rc = RTErrConvertFromOS2(orc); + if (rc == VERR_BROKEN_PIPE) + { + RTCritSectEnter(&pThis->CritSect); + pThis->fBrokenPipe = true; + RTCritSectLeave(&pThis->CritSect); + } + return rc; + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTPipeSelectOne(RTPIPE hPipe, RTMSINTERVAL cMillies) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + uint64_t const StartMsTS = RTTimeMilliTS(); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = rtPipeOs2EnsureSem(pThis); + if (RT_SUCCESS(rc) && cMillies > 0) + { + /* Stop polling attempts if we might block. */ + if (pThis->hPollSet == NIL_RTPOLLSET) + pThis->hPollSet = (RTPOLLSET)(uintptr_t)0xbeef0042; + else + rc = VERR_WRONG_ORDER; + } + if (RT_SUCCESS(rc)) + { + for (unsigned iLoop = 0;; iLoop++) + { + /* + * Check the handle state. + */ + APIRET orc; + if (cMillies > 0) + { + ULONG ulIgnore; + orc = DosResetEventSem(pThis->hev, &ulIgnore); + AssertMsg(orc == NO_ERROR || orc == ERROR_ALREADY_RESET, ("%d\n", orc)); + } + + PIPESEMSTATE aStates[4]; RT_ZERO(aStates); + orc = DosQueryNPipeSemState((HSEM)pThis->hev, &aStates[0], sizeof(aStates)); + if (orc != NO_ERROR) + { + rc = RTErrConvertFromOS2(orc); + break; + } + int i = 0; + if (pThis->fRead) + while (aStates[i].fStatus == NPSS_WSPACE) + i++; + else + while (aStates[i].fStatus == NPSS_RDATA) + i++; + if (aStates[i].fStatus == NPSS_CLOSE) + break; + Assert(aStates[i].fStatus == NPSS_WSPACE || aStates[i].fStatus == NPSS_RDATA || aStates[i].fStatus == NPSS_EOI); + if ( aStates[i].fStatus != NPSS_EOI + && aStates[i].usAvail > 0) + break; + + /* + * Check for timeout. + */ + ULONG cMsMaxWait = SEM_INDEFINITE_WAIT; + if (cMillies != RT_INDEFINITE_WAIT) + { + uint64_t cElapsed = RTTimeMilliTS() - StartMsTS; + if (cElapsed >= cMillies) + { + rc = VERR_TIMEOUT; + break; + } + cMsMaxWait = cMillies - (uint32_t)cElapsed; + } + + /* + * Wait. + */ + RTCritSectLeave(&pThis->CritSect); + orc = DosWaitEventSem(pThis->hev, cMsMaxWait); + RTCritSectEnter(&pThis->CritSect); + if (orc != NO_ERROR && orc != ERROR_TIMEOUT && orc != ERROR_SEM_TIMEOUT ) + { + rc = RTErrConvertFromOS2(orc); + break; + } + } + + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + if (cMillies > 0) + pThis->hPollSet = NIL_RTPOLLSET; + } + + RTCritSectLeave(&pThis->CritSect); + return rc; +} + + +RTDECL(int) RTPipeQueryReadable(RTPIPE hPipe, size_t *pcbReadable) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_PIPE_NOT_READ); + AssertPtrReturn(pcbReadable, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + ULONG cbActual = 0; + ULONG ulState = 0; + AVAILDATA Avail = { 0, 0 }; + APIRET orc = DosPeekNPipe(pThis->hPipe, NULL, 0, &cbActual, &Avail, &ulState); + if (orc == NO_ERROR) + { + if (Avail.cbpipe > 0 || ulState == NP_STATE_CONNECTED) + *pcbReadable = Avail.cbpipe; + else + rc = VERR_PIPE_NOT_CONNECTED; /*??*/ + } + else + rc = RTErrConvertFromOS2(orc); + + RTCritSectLeave(&pThis->CritSect); + return rc; +} + + +RTDECL(int) RTPipeQueryInfo(RTPIPE hPipe, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, 0); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, 0); + + rtPipeFakeQueryInfo(pObjInfo, enmAddAttr, pThis->fRead); + + if (pThis->fRead) + { + ULONG cbActual = 0; + ULONG ulState = 0; + AVAILDATA Avail = { 0, 0 }; + APIRET orc = DosPeekNPipe(pThis->hPipe, NULL, 0, &cbActual, &Avail, &ulState); + if (orc == NO_ERROR && (Avail.cbpipe > 0 || ulState == NP_STATE_CONNECTED)) + pObjInfo->cbObject = Avail.cbpipe; + } + else + pObjInfo->cbObject = rtPipeOs2GetSpace(pThis); + pObjInfo->cbAllocated = RTPIPE_OS2_SIZE; /** @todo this isn't necessarily true if we didn't create it... but, whatever */ + + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; +} + + +int rtPipePollGetHandle(RTPIPE hPipe, uint32_t fEvents, PRTHCINTPTR phNative) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + AssertReturn(!(fEvents & RTPOLL_EVT_READ) || pThis->fRead, VERR_INVALID_PARAMETER); + AssertReturn(!(fEvents & RTPOLL_EVT_WRITE) || !pThis->fRead, VERR_INVALID_PARAMETER); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = rtPipeOs2EnsureSem(pThis); + if (RT_SUCCESS(rc)) + *phNative = (RTHCINTPTR)pThis->hev; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +/** + * Checks for pending events. + * + * @returns Event mask or 0. + * @param pThis The pipe handle. + * @param fEvents The desired events. + * @param fResetEvtSem Whether to reset the event semaphore. + */ +static uint32_t rtPipePollCheck(RTPIPEINTERNAL *pThis, uint32_t fEvents, bool fResetEvtSem) +{ + /* + * Reset the event semaphore if we're gonna wait. + */ + APIRET orc; + ULONG ulIgnore; + if (fResetEvtSem) + { + orc = DosResetEventSem(pThis->hev, &ulIgnore); + AssertMsg(orc == NO_ERROR || orc == ERROR_ALREADY_RESET, ("%d\n", orc)); + } + + /* + * Check for events. + */ + uint32_t fRetEvents = 0; + if (pThis->fBrokenPipe) + fRetEvents |= RTPOLL_EVT_ERROR; + else if (pThis->fRead) + { + ULONG cbActual = 0; + ULONG ulState = 0; + AVAILDATA Avail = { 0, 0 }; + orc = DosPeekNPipe(pThis->hPipe, NULL, 0, &cbActual, &Avail, &ulState); + if (orc != NO_ERROR) + { + fRetEvents |= RTPOLL_EVT_ERROR; + if (orc == ERROR_BROKEN_PIPE || orc == ERROR_PIPE_NOT_CONNECTED) + pThis->fBrokenPipe = true; + } + else if (Avail.cbpipe > 0) + fRetEvents |= RTPOLL_EVT_READ; + else if (ulState != NP_STATE_CONNECTED) + { + fRetEvents |= RTPOLL_EVT_ERROR; + pThis->fBrokenPipe = true; + } + } + else + { + PIPESEMSTATE aStates[4]; RT_ZERO(aStates); + orc = DosQueryNPipeSemState((HSEM)pThis->hev, &aStates[0], sizeof(aStates)); + if (orc == NO_ERROR) + { + int i = 0; + while (aStates[i].fStatus == NPSS_RDATA) + i++; + if (aStates[i].fStatus == NPSS_CLOSE) + { + fRetEvents |= RTPOLL_EVT_ERROR; + pThis->fBrokenPipe = true; + } + else if ( aStates[i].fStatus == NPSS_WSPACE + && aStates[i].usAvail > 0) + fRetEvents |= RTPOLL_EVT_WRITE; + } + else + { + fRetEvents |= RTPOLL_EVT_ERROR; + if (orc == ERROR_BROKEN_PIPE || orc == ERROR_PIPE_NOT_CONNECTED) + pThis->fBrokenPipe = true; + } + } + + return fRetEvents & (fEvents | RTPOLL_EVT_ERROR); +} + + +uint32_t rtPipePollStart(RTPIPE hPipe, RTPOLLSET hPollSet, uint32_t fEvents, bool fFinalEntry, bool fNoWait) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, UINT32_MAX); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, UINT32_MAX); + + /* Check that this is the only current use of this pipe. */ + uint32_t fRetEvents; + if ( pThis->cUsers == 0 + || pThis->hPollSet == NIL_RTPOLLSET) + { + fRetEvents = rtPipePollCheck(pThis, fEvents, fNoWait); + if (!fRetEvents && !fNoWait) + { + /* Mark the set busy while waiting. */ + pThis->cUsers++; + pThis->hPollSet = hPollSet; + } + } + else + { + AssertFailed(); + fRetEvents = UINT32_MAX; + } + + RTCritSectLeave(&pThis->CritSect); + return fRetEvents; +} + + +uint32_t rtPipePollDone(RTPIPE hPipe, uint32_t fEvents, bool fFinalEntry, bool fHarvestEvents) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, 0); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, 0); + + Assert(pThis->cUsers > 0); + + /* harvest events. */ + uint32_t fRetEvents = rtPipePollCheck(pThis, fEvents, false); + + /* update counters. */ + pThis->cUsers--; + pThis->hPollSet = NIL_RTPOLLSET; + + RTCritSectLeave(&pThis->CritSect); + return fRetEvents; +} diff --git a/src/VBox/Runtime/r3/os2/rtProcInitExePath-os2.cpp b/src/VBox/Runtime/r3/os2/rtProcInitExePath-os2.cpp new file mode 100644 index 00000000..bd5e249a --- /dev/null +++ b/src/VBox/Runtime/r3/os2/rtProcInitExePath-os2.cpp @@ -0,0 +1,71 @@ +/* $Id: rtProcInitExePath-os2.cpp $ */ +/** @file + * IPRT - rtProcInitName, OS/2. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include "internal/process.h" +#include "internal/path.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ + /* + * Query the image name from the dynamic linker, convert and return it. + */ + _execname(pszPath, cchPath); + + char const *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszPath, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszLink=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, cchPath, pszPath), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp, pszPath); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/os2/sched-os2.cpp b/src/VBox/Runtime/r3/os2/sched-os2.cpp new file mode 100644 index 00000000..25eb75c3 --- /dev/null +++ b/src/VBox/Runtime/r3/os2/sched-os2.cpp @@ -0,0 +1,244 @@ +/* $Id: sched-os2.cpp $ */ +/** @file + * IPRT - Scheduling, OS/2 + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @def OS2_SCHED_ENABLED + * Enables the priority scheme. */ +#define OS2_SCHED_ENABLED + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#define INCL_BASE +#define INCL_ERRORS +#include <os2.h> + +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include "internal/sched.h" +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Configuration of one priority. + */ +typedef struct +{ + /** The priority. */ + RTPROCPRIORITY enmPriority; + /** The name of this priority. */ + const char *pszName; + /** Array scheduler attributes corresponding to each of the thread types. */ + struct + { + /** For sanity include the array index. */ + RTTHREADTYPE enmType; + /** The OS/2 priority class. */ + ULONG ulClass; + /** The OS/2 priority delta. */ + ULONG ulDelta; + } aTypes[RTTHREADTYPE_END]; +} PROCPRIORITY; + +/** Matches any process priority class. */ +#define ANY_PROCESS_PRIORITY_CLASS (~0U) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Array of static priority configurations. + */ +static const PROCPRIORITY g_aPriorities[] = +{ + { + RTPROCPRIORITY_FLAT, "Flat", + { + { RTTHREADTYPE_INVALID, ~0, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_EMULATION, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_DEFAULT, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_GUI, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_MAIN_WORKER, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_VRDP_IO, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_DEBUGGER, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_MSG_PUMP, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_IO, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_TIMER, PRTYC_REGULAR, 0 } + } + }, + { + RTPROCPRIORITY_LOW, "Low", + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, PRTYC_IDLETIME, 0 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, PRTYC_IDLETIME, 0 }, + { RTTHREADTYPE_EMULATION, PRTYC_IDLETIME, 0 }, + { RTTHREADTYPE_DEFAULT, PRTYC_IDLETIME, 30 }, + { RTTHREADTYPE_GUI, PRTYC_IDLETIME, 30 }, + { RTTHREADTYPE_MAIN_WORKER, PRTYC_IDLETIME, 30 }, + { RTTHREADTYPE_VRDP_IO, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_DEBUGGER, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_MSG_PUMP, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_IO, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_TIMER, PRTYC_REGULAR, 0 } + } + }, + { + RTPROCPRIORITY_NORMAL, "Normal", + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, PRTYC_IDLETIME, 30 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, PRTYC_IDLETIME, 31 }, + { RTTHREADTYPE_EMULATION, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_DEFAULT, PRTYC_REGULAR, 5 }, + { RTTHREADTYPE_GUI, PRTYC_REGULAR, 10 }, + { RTTHREADTYPE_MAIN_WORKER, PRTYC_REGULAR, 12 }, + { RTTHREADTYPE_VRDP_IO, PRTYC_REGULAR, 15 }, + { RTTHREADTYPE_DEBUGGER, PRTYC_REGULAR, 20 }, + { RTTHREADTYPE_MSG_PUMP, PRTYC_REGULAR, 25 }, + { RTTHREADTYPE_IO, PRTYC_FOREGROUNDSERVER, 5 }, + { RTTHREADTYPE_TIMER, PRTYC_TIMECRITICAL, 0 } + } + }, + { + RTPROCPRIORITY_HIGH, "High", + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, PRTYC_IDLETIME, 30 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_EMULATION, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_DEFAULT, PRTYC_REGULAR, 15 }, + { RTTHREADTYPE_GUI, PRTYC_REGULAR, 20 }, + { RTTHREADTYPE_MAIN_WORKER, PRTYC_REGULAR, 25 }, + { RTTHREADTYPE_VRDP_IO, PRTYC_REGULAR, 30 }, + { RTTHREADTYPE_DEBUGGER, PRTYC_TIMECRITICAL, 2 }, + { RTTHREADTYPE_MSG_PUMP, PRTYC_TIMECRITICAL, 3 }, + { RTTHREADTYPE_IO, PRTYC_TIMECRITICAL, 4 }, + { RTTHREADTYPE_TIMER, PRTYC_TIMECRITICAL, 5 } + } + } +}; + +/** + * The dynamic default priority configuration. + * + * This can be recalulated at runtime depending on what the + * system allow us to do. Presently we don't do this as it's + * generally not a bit issue on OS/2 hosts. + */ +static PROCPRIORITY g_aDefaultPriority = +{ + RTPROCPRIORITY_LOW, "Default", + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, PRTYC_IDLETIME, 30 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, PRTYC_IDLETIME, 31 }, + { RTTHREADTYPE_EMULATION, PRTYC_REGULAR, 0 }, + { RTTHREADTYPE_DEFAULT, PRTYC_REGULAR, 5 }, + { RTTHREADTYPE_GUI, PRTYC_REGULAR, 10 }, + { RTTHREADTYPE_MAIN_WORKER, PRTYC_REGULAR, 12 }, + { RTTHREADTYPE_VRDP_IO, PRTYC_REGULAR, 15 }, + { RTTHREADTYPE_DEBUGGER, PRTYC_REGULAR, 20 }, + { RTTHREADTYPE_MSG_PUMP, PRTYC_REGULAR, 25 }, + { RTTHREADTYPE_IO, PRTYC_FOREGROUNDSERVER, 5 }, + { RTTHREADTYPE_TIMER, PRTYC_TIMECRITICAL, 0 } + } +}; + + +/** Pointer to the current priority configuration. */ +static const PROCPRIORITY *g_pProcessPriority = &g_aDefaultPriority; + + +/** + * Calculate the scheduling properties for all the threads in the default + * process priority, assuming the current thread have the type enmType. + * + * @returns iprt status code. + * @param enmType The thread type to be assumed for the current thread. + */ +DECLHIDDEN(int) rtSchedNativeCalcDefaultPriority(RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) rtProcNativeSetPriority(RTPROCPRIORITY enmPriority) +{ + Assert(enmPriority > RTPROCPRIORITY_INVALID && enmPriority < RTPROCPRIORITY_LAST); + + if (enmPriority == RTPROCPRIORITY_DEFAULT) + { + g_pProcessPriority = &g_aDefaultPriority; + return VINF_SUCCESS; + } + + for (size_t i = 0; i < RT_ELEMENTS(g_aPriorities); i++) + if (g_aPriorities[i].enmPriority == enmPriority) + { + g_pProcessPriority = &g_aPriorities[i]; + return VINF_SUCCESS; + } + + AssertFailedReturn(VERR_INTERNAL_ERROR); +} + + +DECLHIDDEN(int) rtThreadNativeSetPriority(PRTTHREADINT pThread, RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + AssertMsg(g_pProcessPriority && g_pProcessPriority->aTypes[enmType].enmType == enmType, + ("enmType=%d entry=%d\n", enmType, g_pProcessPriority->aTypes[enmType].enmType)); + +#ifdef OS2_SCHED_ENABLED + APIRET rc = DosSetPriority(PRTYS_THREAD, g_pProcessPriority->aTypes[enmType].ulClass, g_pProcessPriority->aTypes[enmType].ulDelta, (ULONG)pThread->Core.Key & 0xffff /*tid*/); + AssertMsg(rc == NO_ERROR, ("%d\n", rc)); + return RTErrConvertFromOS2(rc); +#else + return VINF_SUCCESS; +#endif +} + diff --git a/src/VBox/Runtime/r3/os2/sems-os2.cpp b/src/VBox/Runtime/r3/os2/sems-os2.cpp new file mode 100644 index 00000000..d4b8136e --- /dev/null +++ b/src/VBox/Runtime/r3/os2/sems-os2.cpp @@ -0,0 +1,392 @@ +/* $Id: sems-os2.cpp $ */ +/** @file + * IPRT - Semaphores, OS/2. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_DOSSEMAPHORES +#define INCL_ERRORS +#include <os2.h> +#undef RT_MAX + +#include <iprt/semaphore.h> +#include <iprt/assert.h> +#include <iprt/err.h> + + +/** Converts semaphore to OS/2 handle. */ +#define SEM2HND(Sem) ((LHANDLE)(uintptr_t)Sem) + + + +RTDECL(int) RTSemEventCreate(PRTSEMEVENT phEventSem) +{ + return RTSemEventCreateEx(phEventSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventCreateEx(PRTSEMEVENT phEventSem, uint32_t fFlags, RTLOCKVALCLASS hClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~(RTSEMEVENT_FLAGS_NO_LOCK_VAL | RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)), VERR_INVALID_PARAMETER); + Assert(!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) || (fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL)); + + /* + * Create the semaphore. + * (Auto reset, not signaled, private event object.) + */ + HEV hev; + int rc = DosCreateEventSem(NULL, &hev, DCE_AUTORESET | DCE_POSTONE, 0); + if (!rc) + { + *phEventSem = (RTSEMEVENT)(void *)hev; + return VINF_SUCCESS; + } + return RTErrConvertFromOS2(rc); +} + + +RTDECL(int) RTSemEventDestroy(RTSEMEVENT hEventSem) +{ + if (hEventSem == NIL_RTSEMEVENT) + return VINF_SUCCESS; + + /* + * Close semaphore handle. + */ + int rc = DosCloseEventSem(SEM2HND(hEventSem)); + if (!rc) + return VINF_SUCCESS; + AssertMsgFailed(("Destroy hEventSem %p failed, rc=%d\n", hEventSem, rc)); + return RTErrConvertFromOS2(rc); +} + + +RTDECL(int) RTSemEventWaitNoResume(RTSEMEVENT hEventSem, RTMSINTERVAL cMillies) +{ + /* + * Wait for condition. + */ + int rc = DosWaitEventSem(SEM2HND(hEventSem), cMillies == RT_INDEFINITE_WAIT ? SEM_INDEFINITE_WAIT : cMillies); + switch (rc) + { + case NO_ERROR: return VINF_SUCCESS; + case ERROR_SEM_TIMEOUT: + case ERROR_TIMEOUT: return VERR_TIMEOUT; + case ERROR_INTERRUPT: return VERR_INTERRUPTED; + default: + { + AssertMsgFailed(("Wait on hEventSem %p failed, rc=%d\n", hEventSem, rc)); + return RTErrConvertFromOS2(rc); + } + } +} + + +RTDECL(int) RTSemEventSignal(RTSEMEVENT hEventSem) +{ + /* + * Signal the object. + */ + int rc = DosPostEventSem(SEM2HND(hEventSem)); + switch (rc) + { + case NO_ERROR: + case ERROR_ALREADY_POSTED: + case ERROR_TOO_MANY_POSTS: + return VINF_SUCCESS; + default: + return RTErrConvertFromOS2(rc); + } +} + + +RTDECL(void) RTSemEventSetSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +/** @todo implement RTSemEventSetSignaller and friends for OS/2 */ +} + + +RTDECL(void) RTSemEventAddSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ + +} + + +RTDECL(void) RTSemEventRemoveSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ + +} + + + + +RTDECL(int) RTSemEventMultiCreate(PRTSEMEVENTMULTI phEventMultiSem) +{ + return RTSemEventMultiCreateEx(phEventMultiSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventMultiCreateEx(PRTSEMEVENTMULTI phEventMultiSem, uint32_t fFlags, RTLOCKVALCLASS hClass, + const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Create the semaphore. + * (Manual reset, not signaled, private event object.) + */ + HEV hev; + int rc = DosCreateEventSem(NULL, &hev, 0, FALSE); + if (!rc) + { + *phEventMultiSem = (RTSEMEVENTMULTI)(void *)hev; + return VINF_SUCCESS; + } + return RTErrConvertFromOS2(rc); +} + + +RTDECL(int) RTSemEventMultiDestroy(RTSEMEVENTMULTI hEventMultiSem) +{ + if (hEventMultiSem == NIL_RTSEMEVENTMULTI) + return VINF_SUCCESS; + + /* + * Close semaphore handle. + */ + int rc = DosCloseEventSem(SEM2HND(hEventMultiSem)); + if (!rc) + return VINF_SUCCESS; + AssertMsgFailed(("Destroy hEventMultiSem %p failed, rc=%d\n", hEventMultiSem, rc)); + return RTErrConvertFromOS2(rc); +} + + +RTDECL(int) RTSemEventMultiSignal(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Signal the object. + */ + int rc = DosPostEventSem(SEM2HND(hEventMultiSem)); + switch (rc) + { + case NO_ERROR: + case ERROR_ALREADY_POSTED: + case ERROR_TOO_MANY_POSTS: + return VINF_SUCCESS; + default: + return RTErrConvertFromOS2(rc); + } +} + + +RTDECL(int) RTSemEventMultiReset(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Reset the object. + */ + ULONG ulIgnore; + int rc = DosResetEventSem(SEM2HND(hEventMultiSem), &ulIgnore); + switch (rc) + { + case NO_ERROR: + case ERROR_ALREADY_RESET: + return VINF_SUCCESS; + default: + return RTErrConvertFromOS2(rc); + } +} + + +RTDECL(int) RTSemEventMultiWaitNoResume(RTSEMEVENTMULTI hEventMultiSem, RTMSINTERVAL cMillies) +{ + /* + * Wait for condition. + */ + int rc = DosWaitEventSem(SEM2HND(hEventMultiSem), cMillies == RT_INDEFINITE_WAIT ? SEM_INDEFINITE_WAIT : cMillies); + switch (rc) + { + case NO_ERROR: return VINF_SUCCESS; + case ERROR_SEM_TIMEOUT: + case ERROR_TIMEOUT: return VERR_TIMEOUT; + case ERROR_INTERRUPT: return VERR_INTERRUPTED; + default: + { + AssertMsgFailed(("Wait on hEventMultiSem %p failed, rc=%d\n", hEventMultiSem, rc)); + return RTErrConvertFromOS2(rc); + } + } +} + + +RTDECL(void) RTSemEventMultiSetSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ + /** @todo implement RTSemEventMultiSetSignaller on OS/2 */ +} + + +RTDECL(void) RTSemEventMultiAddSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +} + + +RTDECL(void) RTSemEventMultiRemoveSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +} + + + +#undef RTSemMutexCreate +RTDECL(int) RTSemMutexCreate(PRTSEMMUTEX phMutexSem) +{ + return RTSemMutexCreateEx(phMutexSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL); +} + + +RTDECL(int) RTSemMutexCreateEx(PRTSEMMUTEX phMutexSem, uint32_t fFlags, + RTLOCKVALCLASS hClass, uint32_t uSubClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMMUTEX_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Create the semaphore. + */ + HMTX hmtx; + int rc = DosCreateMutexSem(NULL, &hmtx, 0, FALSE); + if (!rc) + { + /** @todo implement lock validation of OS/2 mutex semaphores. */ + *phMutexSem = (RTSEMMUTEX)(void *)hmtx; + return VINF_SUCCESS; + } + + return RTErrConvertFromOS2(rc); +} + + +RTDECL(int) RTSemMutexDestroy(RTSEMMUTEX hMutexSem) +{ + if (hMutexSem == NIL_RTSEMMUTEX) + return VINF_SUCCESS; + + /* + * Close semaphore handle. + */ + int rc = DosCloseMutexSem(SEM2HND(hMutexSem)); + if (!rc) + return VINF_SUCCESS; + AssertMsgFailed(("Destroy hMutexSem %p failed, rc=%d\n", hMutexSem, rc)); + return RTErrConvertFromOS2(rc); +} + + + +RTDECL(uint32_t) RTSemMutexSetSubClass(RTSEMMUTEX hMutexSem, uint32_t uSubClass) +{ +#if 0 /** @todo def RTSEMMUTEX_STRICT */ + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID); + + return RTLockValidatorRecExclSetSubClass(&pThis->ValidatorRec, uSubClass); +#else + return RTLOCKVAL_SUB_CLASS_INVALID; +#endif +} + + +#undef RTSemMutexRequestNoResume +RTDECL(int) RTSemMutexRequestNoResume(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies) +{ + /* + * Lock mutex semaphore. + */ + int rc = DosRequestMutexSem(SEM2HND(hMutexSem), cMillies == RT_INDEFINITE_WAIT ? SEM_INDEFINITE_WAIT : cMillies); + switch (rc) + { + case NO_ERROR: return VINF_SUCCESS; + case ERROR_SEM_TIMEOUT: + case ERROR_TIMEOUT: return VERR_TIMEOUT; + case ERROR_INTERRUPT: return VERR_INTERRUPTED; + case ERROR_SEM_OWNER_DIED: return VERR_SEM_OWNER_DIED; + default: + { + AssertMsgFailed(("Wait on hMutexSem %p failed, rc=%d\n", hMutexSem, rc)); + return RTErrConvertFromOS2(rc); + } + } +} + +RTDECL(int) RTSemMutexRequestNoResumeDebug(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ +// RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); +// return rtSemMutexRequestNoResume(hMutexSem, cMillies, &SrcPos); + return RTSemMutexRequestNoResume(hMutexSem, cMillies); +} + + +RTDECL(int) RTSemMutexRelease(RTSEMMUTEX hMutexSem) +{ + /* + * Unlock mutex semaphore. + */ + int rc = DosReleaseMutexSem(SEM2HND(hMutexSem)); + if (!rc) + return VINF_SUCCESS; + AssertMsgFailed(("Release hMutexSem %p failed, rc=%d\n", hMutexSem, rc)); + return RTErrConvertFromOS2(rc); +} + + +RTDECL(bool) RTSemMutexIsOwned(RTSEMMUTEX hMutexSem) +{ + /* + * Unlock mutex semaphore. + */ + PID pid; + TID tid; + ULONG cRecursions; + int rc = DosQueryMutexSem(SEM2HND(hMutexSem), &pid, &tid, &cRecursions); + if (!rc) + return cRecursions != 0; + AssertMsgFailed(("DosQueryMutexSem %p failed, rc=%d\n", hMutexSem, rc)); + return rc == ERROR_SEM_OWNER_DIED; +} + diff --git a/src/VBox/Runtime/r3/os2/serialport-os2.cpp b/src/VBox/Runtime/r3/os2/serialport-os2.cpp new file mode 100644 index 00000000..95158dea --- /dev/null +++ b/src/VBox/Runtime/r3/os2/serialport-os2.cpp @@ -0,0 +1,755 @@ +/* $Id: serialport-os2.cpp $ */ +/** @file + * IPRT - Serial Port API, OS/2 Implementation. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_BASE +#define INCL_DOSFILEMGR +#define INCL_ERRORS +#define INCL_DOS +#define INCL_DOSDEVIOCTL +#define INCL_DOSDEVICES +#include <os2.h> +#undef RT_MAX + +#include <iprt/serialport.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/cdefs.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include "internal/magics.h" + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Returned data structure for ASYNC_EXTGETBAUDRATE. + */ +typedef struct OS2EXTGETBAUDRATEDATA +{ + /** Current bit rate. */ + ULONG uBitRateCur; + /** Fraction of the current bit rate. */ + BYTE bBitRateCurFrac; + /** Minimum supported bit rate. */ + ULONG uBitRateMin; + /** Fraction of the minimum bit rate. */ + BYTE bBitRateCurMin; + /** Maximum supported bit rate. */ + ULONG uBitRateMax; + /** Fraction of the maximum bit rate. */ + BYTE bBitRateCurMax; +} OS2EXTGETBAUDRATEDATA; +/** Pointer to the get extended baud rate data packet. */ +typedef OS2EXTGETBAUDRATEDATA *POS2EXTGETBAUDRATEDATA; + + +/** + * Data packet for the ASYNC_EXTSETBAUDRATE ioctl. + */ +typedef struct OS2EXTSETBAUDRATEDATA +{ + /** Current bit rate. */ + ULONG uBitRate; + /** Fraction of the current bit rate. */ + BYTE bBitRateFrac; +} OS2EXTSETBAUDRATEDATA; +/** Pointer to the set extended baud rate data packet. */ +typedef OS2EXTSETBAUDRATEDATA *POS2EXTSETBAUDRATEDATA; + + +/** + * Data packet for the ASYNC_GETLINECTRL ioctl. + */ +typedef struct OS2GETLINECTRLDATA +{ + /** Returns the current amount of data bits in a symbol used for the communication. */ + BYTE bDataBits; + /** Current parity setting. */ + BYTE bParity; + /** Current number of stop bits. */ + BYTE bStopBits; + /** Flag whether a break condition is currently transmitted on the line. */ + BYTE bTxBrk; +} OS2GETLINECTRLDATA; +/** Pointer to the get line control data packet. */ +typedef OS2GETLINECTRLDATA *POS2GETLINECTRLDATA; + + +/** + * Data packet for the ASYNC_SETLINECTRL ioctl. + */ +typedef struct OS2SETLINECTRLDATA +{ + /** Returns the current amount of data bits in a symbol used for the communication. */ + BYTE bDataBits; + /** Current parity setting. */ + BYTE bParity; + /** Current number of stop bits. */ + BYTE bStopBits; +} OS2SETLINECTRLDATA; +/** Pointer to the get line control data packet. */ +typedef OS2SETLINECTRLDATA *POS2SETLINECTRLDATA; + + +/** + * Internal serial port state. + */ +typedef struct RTSERIALPORTINTERNAL +{ + /** Magic value (RTSERIALPORT_MAGIC). */ + uint32_t u32Magic; + /** Flags given while opening the serial port. */ + uint32_t fOpenFlags; + /** The file descriptor of the serial port. */ + HFILE hDev; + /** Flag whether blocking mode is currently enabled. */ + bool fBlocking; + /** Flag whether RTSerialPortEvtPoll() was interrupted by RTSerialPortEvtPollInterrupt(). */ + volatile bool fInterrupt; +} RTSERIALPORTINTERNAL; +/** Pointer to the internal serial port state. */ +typedef RTSERIALPORTINTERNAL *PRTSERIALPORTINTERNAL; + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Indicator whether the CTS input is set/clear. */ +#define OS2_GET_MODEM_INPUT_CTS RT_BIT(4) +/** Indicator whether the DSR input is set/clear. */ +#define OS2_GET_MODEM_INPUT_DSR RT_BIT(5) +/** Indicator whether the RI input is set/clear. */ +#define OS2_GET_MODEM_INPUT_RI RT_BIT(6) +/** Indicator whether the DCD input is set/clear. */ +#define OS2_GET_MODEM_INPUT_DCD RT_BIT(7) + +/** There is something to read on the serial port. */ +#define OS2_GET_COMM_EVT_RX RT_BIT(0) +/** A receive timeout interrupt was generated on the serial port during a read request. */ +#define OS2_GET_COMM_EVT_RTI RT_BIT(1) +/** The transmit queue for the serial port is empty. */ +#define OS2_GET_COMM_EVT_TX_EMPTY RT_BIT(2) +/** The CTS signal changes state. */ +#define OS2_GET_COMM_EVT_CTS_CHG RT_BIT(3) +/** The DSR signal changes state. */ +#define OS2_GET_COMM_EVT_DSR_CHG RT_BIT(4) +/** The DCD signal changes state. */ +#define OS2_GET_COMM_EVT_DCD_CHG RT_BIT(5) +/** A break condition was detected on the serial port. */ +#define OS2_GET_COMM_EVT_BRK RT_BIT(6) +/** A parity, framing or receive hardware overrun error occurred. */ +#define OS2_GET_COMM_EVT_COMM_ERR RT_BIT(7) +/** Trailing edge ring indicator was detected. */ +#define OS2_GET_COMM_EVT_RI_TRAIL_EDGE RT_BIT(8) + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ +/** OS/2 parity value to IPRT parity enum. */ +static RTSERIALPORTPARITY s_aParityConvTbl[] = +{ + RTSERIALPORTPARITY_NONE, + RTSERIALPORTPARITY_ODD, + RTSERIALPORTPARITY_EVEN, + RTSERIALPORTPARITY_MARK, + RTSERIALPORTPARITY_SPACE +}; + +/** OS/2 data bits value to IPRT data bits enum. */ +static RTSERIALPORTDATABITS s_aDataBitsConvTbl[] = +{ + RTSERIALPORTDATABITS_INVALID, + RTSERIALPORTDATABITS_INVALID, + RTSERIALPORTDATABITS_INVALID, + RTSERIALPORTDATABITS_INVALID, + RTSERIALPORTDATABITS_INVALID, + RTSERIALPORTDATABITS_5BITS, + RTSERIALPORTDATABITS_6BITS, + RTSERIALPORTDATABITS_7BITS, + RTSERIALPORTDATABITS_8BITS +}; + +/** OS/2 stop bits value to IPRT stop bits enum. */ +static RTSERIALPORTSTOPBITS s_aStopBitsConvTbl[] = +{ + RTSERIALPORTSTOPBITS_ONE, + RTSERIALPORTSTOPBITS_ONEPOINTFIVE, + RTSERIALPORTSTOPBITS_TWO +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * The slow path of rtSerialPortSwitchBlockingMode that does the actual switching. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param fBlocking The desired mode of operation. + * @remarks Do not call directly. + * + * @note Affects only read behavior. + */ +static int rtSerialPortSwitchBlockingModeSlow(PRTSERIALPORTINTERNAL pThis, bool fBlocking) +{ + DCBINFO DcbInfo; + ULONG cbDcbInfo = sizeof(DcbInfo); + ULONG rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_GETDCBINFO, NULL, 0, NULL, &DcbInfo, cbDcbInfo, &cbDcbInfo); + if (!rcOs2) + { + DcbInfo.fbTimeout &= ~0x06; + DcbInfo.fbTimeout |= fBlocking ? 0x04 : 0x06; + rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_SETDCBINFO, &DcbInfo, cbDcbInfo, &cbDcbInfo, NULL, 0, NULL); + if (rcOs2) + return RTErrConvertFromOS2(rcOs2); + } + else + return RTErrConvertFromOS2(rcOs2); + + pThis->fBlocking = fBlocking; + return VINF_SUCCESS; +} + + +/** + * Switches the serial port to the desired blocking mode if necessary. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param fBlocking The desired mode of operation. + * + * @note Affects only read behavior. + */ +DECLINLINE(int) rtSerialPortSwitchBlockingMode(PRTSERIALPORTINTERNAL pThis, bool fBlocking) +{ + if (pThis->fBlocking != fBlocking) + return rtSerialPortSwitchBlockingModeSlow(pThis, fBlocking); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSerialPortOpen(PRTSERIALPORT phSerialPort, const char *pszPortAddress, uint32_t fFlags) +{ + AssertPtrReturn(phSerialPort, VERR_INVALID_POINTER); + AssertPtrReturn(pszPortAddress, VERR_INVALID_POINTER); + AssertReturn(*pszPortAddress != '\0', VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTSERIALPORT_OPEN_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn((fFlags & RTSERIALPORT_OPEN_F_READ) || (fFlags & RTSERIALPORT_OPEN_F_WRITE), + VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PRTSERIALPORTINTERNAL pThis = (PRTSERIALPORTINTERNAL)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + ULONG fOpenMode = OPEN_SHARE_DENYREADWRITE + | OPEN_FLAGS_SEQUENTIAL + | OPEN_FLAGS_NOINHERIT + | OPEN_FLAGS_FAIL_ON_ERROR; + + if ((fFlags & RTSERIALPORT_OPEN_F_READ) && !(fFlags & RTSERIALPORT_OPEN_F_WRITE)) + fOpenMode |= OPEN_ACCESS_READONLY; + else if (!(fFlags & RTSERIALPORT_OPEN_F_READ) && (fFlags & RTSERIALPORT_OPEN_F_WRITE)) + fOpenMode |= OPEN_ACCESS_WRITEONLY; + else + fOpenMode |= OPEN_ACCESS_READWRITE; + + pThis->u32Magic = RTSERIALPORT_MAGIC; + pThis->fOpenFlags = fFlags; + pThis->fInterrupt = false; + pThis->fBlocking = true; + + ULONG uAction = 0; + ULONG rcOs2 = DosOpen((const UCHAR *)pszPortAddress, &pThis->hDev, &uAction, 0, FILE_NORMAL, FILE_OPEN, fOpenMode, NULL); + if (!rcOs2) + { + /* Switch to a known read blocking mode. */ + rc = rtSerialPortSwitchBlockingMode(pThis, false); + if (RT_SUCCESS(rc)) + { + *phSerialPort = pThis; + return VINF_SUCCESS; + } + + DosClose(pThis->hDev); + } + else + rc = RTErrConvertFromOS2(rcOs2); + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTSerialPortClose(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + if (pThis == NIL_RTSERIALPORT) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the cleanup. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTSERIALPORT_MAGIC_DEAD, RTSERIALPORT_MAGIC), VERR_INVALID_HANDLE); + + DosClose(pThis->hDev); + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(RTHCINTPTR) RTSerialPortToNative(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, -1); + + return pThis->hDev; +} + + +RTDECL(int) RTSerialPortRead(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER); + + int rc = rtSerialPortSwitchBlockingMode(pThis, true); + if (RT_SUCCESS(rc)) + { + /* + * Attempt read. + */ + ULONG cbRead = 0; + ULONG rcOs2 = DosRead(pThis->hDev, pvBuf, cbToRead, &cbRead); + if (!rcOs2) + { + if (pcbRead) + /* caller can handle partial read. */ + *pcbRead = cbRead; + else + { + /* Caller expects all to be read. */ + while (cbToRead > cbRead) + { + ULONG cbReadPart = 0; + rcOs2 = DosRead(pThis->hDev, (uint8_t *)pvBuf + cbRead, cbToRead - cbRead, &cbReadPart); + if (rcOs2) + return RTErrConvertFromOS2(rcOs2); + + cbRead += cbReadPart; + } + } + } + else + rc = RTErrConvertFromOS2(rcOs2); + } + + return rc; +} + + +RTDECL(int) RTSerialPortReadNB(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + + *pcbRead = 0; + + int rc = rtSerialPortSwitchBlockingMode(pThis, false); + if (RT_SUCCESS(rc)) + { + ULONG cbThisRead = 0; + ULONG rcOs2 = DosRead(pThis->hDev, pvBuf, cbToRead, &cbThisRead); + if (!rcOs2) + { + *pcbRead = cbThisRead; + + if (cbThisRead == 0) + rc = VINF_TRY_AGAIN; + } + else + rc = RTErrConvertFromOS2(rcOs2); + } + + return rc; +} + + +RTDECL(int) RTSerialPortWrite(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER); + + /* + * Attempt write. + */ + int rc = VINF_SUCCESS; + ULONG cbThisWritten = 0; + ULONG rcOs2 = DosWrite(pThis->hDev, pvBuf, cbToWrite, &cbThisWritten); + if (!rcOs2) + { + if (pcbWritten) + /* caller can handle partial write. */ + *pcbWritten = cbThisWritten; + else + { + /** @todo Wait for TX empty and loop. */ + rc = VERR_NOT_SUPPORTED; + } + } + else + rc = RTErrConvertFromOS2(rcOs2); + + return rc; +} + + +RTDECL(int) RTSerialPortWriteNB(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + + *pcbWritten = 0; + + int rc = VINF_SUCCESS; + ULONG cbThisWritten = 0; + ULONG rcOs2 = DosWrite(pThis->hDev, pvBuf, cbToWrite, &cbThisWritten); + if (!rcOs2) + { + *pcbWritten = cbThisWritten; + if (!cbThisWritten) + rc = VINF_TRY_AGAIN; + } + else + rc = RTErrConvertFromOS2(rcOs2); + + return rc; +} + + +RTDECL(int) RTSerialPortCfgQueryCurrent(RTSERIALPORT hSerialPort, PRTSERIALPORTCFG pCfg) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + OS2EXTGETBAUDRATEDATA ExtBaudRate; + ULONG cbExtBaudRate = sizeof(ExtBaudRate); + ULONG rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_EXTGETBAUDRATE, NULL, 0, NULL, &ExtBaudRate, cbExtBaudRate, &cbExtBaudRate); + if (!rcOs2) + { + OS2GETLINECTRLDATA LineCtrl; + ULONG cbLineCtrl = sizeof(LineCtrl); + rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_GETLINECTRL, NULL, 0, NULL, &LineCtrl, cbLineCtrl, &cbLineCtrl); + if (!rcOs2) + { + pCfg->uBaudRate = ExtBaudRate.uBitRateCur; + if (LineCtrl.bParity < RT_ELEMENTS(s_aParityConvTbl)) + pCfg->enmParity = s_aParityConvTbl[LineCtrl.bParity]; + else + rc = VERR_IPE_UNEXPECTED_STATUS; + + if ( RT_SUCCESS(rc) + && LineCtrl.bDataBits < RT_ELEMENTS(s_aDataBitsConvTbl) + && s_aDataBitsConvTbl[LineCtrl.bDataBits] != RTSERIALPORTDATABITS_INVALID) + pCfg->enmDataBitCount = s_aDataBitsConvTbl[LineCtrl.bDataBits]; + else + rc = VERR_IPE_UNEXPECTED_STATUS; + + if ( RT_SUCCESS(rc) + && LineCtrl.bStopBits < RT_ELEMENTS(s_aStopBitsConvTbl)) + pCfg->enmStopBitCount = s_aStopBitsConvTbl[LineCtrl.bStopBits]; + else + rc = VERR_IPE_UNEXPECTED_STATUS; + } + else + rc = RTErrConvertFromOS2(rcOs2); + } + else + rc = RTErrConvertFromOS2(rcOs2); + + return rc; +} + + +RTDECL(int) RTSerialPortCfgSet(RTSERIALPORT hSerialPort, PCRTSERIALPORTCFG pCfg, PRTERRINFO pErrInfo) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + OS2EXTSETBAUDRATEDATA ExtBaudRate; + OS2SETLINECTRLDATA LineCtrl; + ULONG cbExtBaudRate = sizeof(ExtBaudRate); + ULONG cbLineCtrl = sizeof(LineCtrl); + + ExtBaudRate.uBitRate = pCfg->uBaudRate; + ExtBaudRate.bBitRateFrac = 0; + + BYTE idx = 0; + while (idx < RT_ELEMENTS(s_aParityConvTbl)) + { + if (s_aParityConvTbl[idx] == pCfg->enmParity) + { + LineCtrl.bParity = idx; + break; + } + idx++; + } + AssertReturn(idx < RT_ELEMENTS(s_aParityConvTbl), VERR_INTERNAL_ERROR); + + idx = 0; + while (idx < RT_ELEMENTS(s_aDataBitsConvTbl)) + { + if (s_aDataBitsConvTbl[idx] == pCfg->enmDataBitCount) + { + LineCtrl.bDataBits = idx; + break; + } + idx++; + } + AssertReturn(idx < RT_ELEMENTS(s_aDataBitsConvTbl), VERR_INTERNAL_ERROR); + + idx = 0; + while (idx < RT_ELEMENTS(s_aStopBitsConvTbl)) + { + if (s_aStopBitsConvTbl[idx] == pCfg->enmStopBitCount) + { + LineCtrl.bStopBits = idx; + break; + } + idx++; + } + AssertReturn(idx < RT_ELEMENTS(s_aStopBitsConvTbl), VERR_INTERNAL_ERROR); + + ULONG rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_EXTSETBAUDRATE, &ExtBaudRate, cbExtBaudRate, &cbExtBaudRate, NULL, 0, NULL); + if (!rcOs2) + { + rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_SETLINECTRL, &LineCtrl, cbLineCtrl, &cbLineCtrl, NULL, 0, NULL); + if (rcOs2) + rc = RTErrConvertFromOS2(rcOs2); + } + else + rc = RTErrConvertFromOS2(rcOs2); + + return rc; +} + + +RTDECL(int) RTSerialPortEvtPoll(RTSERIALPORT hSerialPort, uint32_t fEvtMask, uint32_t *pfEvtsRecv, + RTMSINTERVAL msTimeout) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fEvtMask & ~RTSERIALPORT_EVT_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pfEvtsRecv, VERR_INVALID_POINTER); + + *pfEvtsRecv = 0; + + /* + * We need to kind of busy wait here as there is, to my knowledge, no API + * to wait for a COM event. + * + * @todo Adaptive waiting + * @todo Handle rollover after 48days eventually + */ + int rc = VINF_SUCCESS; + uint64_t tsStart = RTTimeSystemMilliTS(); + do + { + if (ASMAtomicXchgBool(&pThis->fInterrupt, false)) + { + rc = VERR_INTERRUPTED; + break; + } + + USHORT fCommEvt = 0; + ULONG cbCommEvt = sizeof(fCommEvt); + ULONG rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_GETCOMMEVENT, NULL, 0, NULL, + &fCommEvt, cbCommEvt, &cbCommEvt); + if (!rcOs2) + { + AssertReturn(cbCommEvt = sizeof(fCommEvt), VERR_IPE_UNEXPECTED_STATUS); + + if ( (fEvtMask & RTSERIALPORT_EVT_F_DATA_RX) + && (fCommEvt & OS2_GET_COMM_EVT_RX)) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_DATA_RX; + + /** @todo Is there something better to indicate that there is room in the queue instead of queue is empty? */ + if ( (fEvtMask & RTSERIALPORT_EVT_F_DATA_TX) + && (fCommEvt & OS2_GET_COMM_EVT_TX_EMPTY)) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_DATA_TX; + + if ( (fEvtMask & RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED) + && (fCommEvt & (OS2_GET_COMM_EVT_CTS_CHG | OS2_GET_COMM_EVT_DSR_CHG | OS2_GET_COMM_EVT_DCD_CHG))) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED; + + if ( (fEvtMask & RTSERIALPORT_EVT_F_BREAK_DETECTED) + && (fCommEvt & OS2_GET_COMM_EVT_BRK)) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_BREAK_DETECTED; + + if (*pfEvtsRecv != 0) + break; + } + else + { + rc = RTErrConvertFromOS2(rcOs2); + break; + } + + uint64_t tsNow = RTTimeSystemMilliTS(); + if ( msTimeout == RT_INDEFINITE_WAIT + || tsNow - tsStart < msTimeout) + DosSleep(1); + else + rc = VERR_TIMEOUT; + } while (RT_SUCCESS(rc)); + + return rc; +} + + +RTDECL(int) RTSerialPortEvtPollInterrupt(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + ASMAtomicXchgBool(&pThis->fInterrupt, true); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSerialPortChgBreakCondition(RTSERIALPORT hSerialPort, bool fSet) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + ULONG rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, fSet ? ASYNC_SETBREAKON : ASYNC_SETBREAKOFF, + NULL, 0, NULL, NULL, 0, NULL); + + return RTErrConvertFromOS2(rcOs2); +} + + +RTDECL(int) RTSerialPortChgStatusLines(RTSERIALPORT hSerialPort, uint32_t fClear, uint32_t fSet) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + MODEMSTATUS MdmSts; + ULONG cbMdmSts = sizeof(MdmSts); + + MdmSts.fbModemOn = (fSet & RTSERIALPORT_CHG_STS_LINES_F_RTS ? 0x02 : 0x00) + | (fSet & RTSERIALPORT_CHG_STS_LINES_F_DTR ? 0x01 : 0x00); + MdmSts.fbModemOff = 0xff; + MdmSts.fbModemOff &= ~( (fClear & RTSERIALPORT_CHG_STS_LINES_F_RTS ? 0x02 : 0x00) + | (fClear & RTSERIALPORT_CHG_STS_LINES_F_DTR ? 0x01 : 0x00)); + + ULONG rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_SETMODEMCTRL, &MdmSts, cbMdmSts, &cbMdmSts, NULL, 0, NULL); + + return RTErrConvertFromOS2(rcOs2); +} + + +RTDECL(int) RTSerialPortQueryStatusLines(RTSERIALPORT hSerialPort, uint32_t *pfStsLines) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfStsLines, VERR_INVALID_POINTER); + + *pfStsLines = 0; + + int rc = VINF_SUCCESS; + BYTE fStsLines = 0; + ULONG cbStsLines = sizeof(fStsLines); + ULONG rcOs2 = DosDevIOCtl(pThis->hDev, IOCTL_ASYNC, ASYNC_GETMODEMINPUT, NULL, 0, NULL, &fStsLines, cbStsLines, &cbStsLines); + if (!rcOs2) + { + AssertReturn(cbStsLines == sizeof(BYTE), VERR_IPE_UNEXPECTED_STATUS); + + *pfStsLines |= (fStsLines & OS2_GET_MODEM_INPUT_DCD) ? RTSERIALPORT_STS_LINE_DCD : 0; + *pfStsLines |= (fStsLines & OS2_GET_MODEM_INPUT_RI) ? RTSERIALPORT_STS_LINE_RI : 0; + *pfStsLines |= (fStsLines & OS2_GET_MODEM_INPUT_DSR) ? RTSERIALPORT_STS_LINE_DSR : 0; + *pfStsLines |= (fStsLines & OS2_GET_MODEM_INPUT_CTS) ? RTSERIALPORT_STS_LINE_CTS : 0; + } + else + rc = RTErrConvertFromOS2(rcOs2); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/os2/systemmem-os2.cpp b/src/VBox/Runtime/r3/os2/systemmem-os2.cpp new file mode 100644 index 00000000..71eb1adf --- /dev/null +++ b/src/VBox/Runtime/r3/os2/systemmem-os2.cpp @@ -0,0 +1,80 @@ +/* $Id: systemmem-os2.cpp $ */ +/** @file + * IPRT - RTSystemQueryTotalRam, OS/2 ring-3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_DOSMISC +#define INCL_ERRORS +#include <os2.h> +#undef RT_MAX + +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> + + +RTDECL(int) RTSystemQueryTotalRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + ULONG cbMem; + APIRET rc = DosQuerySysInfo(QSV_TOTPHYSMEM, QSV_TOTPHYSMEM, &cbMem, sizeof(cbMem)); + if (rc != NO_ERROR) + return RTErrConvertFromOS2(rc); + + *pcb = cbMem; + return VINF_SUCCESS; +} + + +RTDECL(int) RTSystemQueryAvailableRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + ULONG cbAvailMem; + APIRET rc = DosQuerySysInfo(QSV_TOTAVAILMEM, QSV_TOTAVAILMEM, &cbAvailMem, sizeof(cbAvailMem)); + if (rc != NO_ERROR) + return RTErrConvertFromOS2(rc); + + *pcb = cbAvailMem; + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/os2/thread-os2.cpp b/src/VBox/Runtime/r3/os2/thread-os2.cpp new file mode 100644 index 00000000..366f4803 --- /dev/null +++ b/src/VBox/Runtime/r3/os2/thread-os2.cpp @@ -0,0 +1,330 @@ +/* $Id: thread-os2.cpp $ */ +/** @file + * IPRT - Threads, OS/2. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#define INCL_BASE +#include <os2.h> +#undef RT_MAX + +#include <errno.h> +#include <process.h> +#include <stdlib.h> +#include <signal.h> +#include <InnoTekLIBC/FastInfoBlocks.h> +#include <InnoTekLIBC/thread.h> + +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/cpuset.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Pointer to thread local memory which points to the current thread. */ +static PRTTHREADINT *g_ppCurThread; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void rtThreadNativeMain(void *pvArgs); + + +DECLHIDDEN(int) rtThreadNativeInit(void) +{ + /* + * Allocate thread local memory. + */ + PULONG pul; + int rc = DosAllocThreadLocalMemory(1, &pul); + if (rc) + return VERR_NO_TLS_FOR_SELF; + g_ppCurThread = (PRTTHREADINT *)(void *)pul; + return VINF_SUCCESS; +} + + +static void rtThreadOs2BlockSigAlarm(void) +{ + /* + * Block SIGALRM - required for timer-posix.cpp. + * This is done to limit harm done by OSes which doesn't do special SIGALRM scheduling. + * It will not help much if someone creates threads directly using pthread_create. :/ + */ + sigset_t SigSet; + sigemptyset(&SigSet); + sigaddset(&SigSet, SIGALRM); + sigprocmask(SIG_BLOCK, &SigSet, NULL); +} + + +DECLHIDDEN(void) rtThreadNativeReInitObtrusive(void) +{ + rtThreadOs2BlockSigAlarm(); +} + + +DECLHIDDEN(int) rtThreadNativeAdopt(PRTTHREADINT pThread) +{ + + *g_ppCurThread = pThread; + return VINF_SUCCESS; +} + + +DECLHIDDEN(void) rtThreadNativeDestroy(PRTTHREADINT pThread) +{ + if (pThread == *g_ppCurThread) + *g_ppCurThread = NULL; +} + + +/** + * Wrapper which unpacks the params and calls thread function. + */ +static void rtThreadNativeMain(void *pvArgs) +{ + rtThreadOs2BlockSigAlarm(); + + /* + * Call common main. + */ + PRTTHREADINT pThread = (PRTTHREADINT)pvArgs; + *g_ppCurThread = pThread; + +#ifdef fibGetTidPid + rtThreadMain(pThread, fibGetTidPid(), &pThread->szName[0]); +#else + rtThreadMain(pThread, _gettid(), &pThread->szName[0]); +#endif + + *g_ppCurThread = NULL; + _endthread(); +} + + +DECLHIDDEN(int) rtThreadNativeCreate(PRTTHREADINT pThread, PRTNATIVETHREAD pNativeThread) +{ + /* + * Default stack size. + */ + if (!pThread->cbStack) + pThread->cbStack = 512*1024; + + /* + * Create the thread. + */ + int iThreadId = _beginthread(rtThreadNativeMain, NULL, pThread->cbStack, pThread); + if (iThreadId > 0) + { +#ifdef fibGetTidPid + *pNativeThread = iThreadId | (fibGetPid() << 16); +#else + *pNativeThread = iThreadId; +#endif + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +RTDECL(RTTHREAD) RTThreadSelf(void) +{ + PRTTHREADINT pThread = *g_ppCurThread; + if (pThread) + return (RTTHREAD)pThread; + /** @todo import alien threads? */ + return NULL; +} + + +RTDECL(RTNATIVETHREAD) RTThreadNativeSelf(void) +{ +#ifdef fibGetTidPid + return fibGetTidPid(); +#else + return _gettid(); +#endif +} + + +RTDECL(int) RTThreadSleep(RTMSINTERVAL cMillies) +{ + LogFlow(("RTThreadSleep: cMillies=%d\n", cMillies)); + DosSleep(cMillies); + LogFlow(("RTThreadSleep: returning (cMillies=%d)\n", cMillies)); + return VINF_SUCCESS; +} + + +RTDECL(int) RTThreadSleepNoLog(RTMSINTERVAL cMillies) +{ + DosSleep(cMillies); + return VINF_SUCCESS; +} + + +RTDECL(bool) RTThreadYield(void) +{ + uint64_t u64TS = ASMReadTSC(); + DosSleep(0); + u64TS = ASMReadTSC() - u64TS; + bool fRc = u64TS > 1750; + LogFlow(("RTThreadYield: returning %d (%llu ticks)\n", fRc, u64TS)); + return fRc; +} + + +RTR3DECL(int) RTThreadGetAffinity(PRTCPUSET pCpuSet) +{ + union + { + uint64_t u64; + MPAFFINITY mpaff; + } u; + + APIRET rc = DosQueryThreadAffinity(AFNTY_THREAD, &u.mpaff); + if (!rc) + { + RTCpuSetFromU64(pCpuSet, u.u64); + return VINF_SUCCESS; + } + return RTErrConvertFromOS2(rc); +} + + +RTR3DECL(int) RTThreadSetAffinity(PCRTCPUSET pCpuSet) +{ + union + { + uint64_t u64; + MPAFFINITY mpaff; + } u; + u.u64 = pCpuSet ? RTCpuSetToU64(pCpuSet) : UINT64_MAX; + int rc = DosSetThreadAffinity(&u.mpaff); + if (!rc) + return VINF_SUCCESS; + return RTErrConvertFromOS2(rc); +} + + +RTR3DECL(RTTLS) RTTlsAlloc(void) +{ + AssertCompile(NIL_RTTLS == -1); + return __libc_TLSAlloc(); +} + + +RTR3DECL(int) RTTlsAllocEx(PRTTLS piTls, PFNRTTLSDTOR pfnDestructor) +{ + int rc; + int iTls = __libc_TLSAlloc(); + if (iTls != -1) + { + if ( !pfnDestructor + || __libc_TLSDestructor(iTls, (void (*)(void *, int, unsigned))pfnDestructor, 0) != -1) + { + *piTls = iTls; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromErrno(errno); + __libc_TLSFree(iTls); + } + else + rc = RTErrConvertFromErrno(errno); + + *piTls = NIL_RTTLS; + return rc; +} + + +RTR3DECL(int) RTTlsFree(RTTLS iTls) +{ + if (iTls == NIL_RTTLS) + return VINF_SUCCESS; + if (__libc_TLSFree(iTls) != -1) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(void *) RTTlsGet(RTTLS iTls) +{ + return __libc_TLSGet(iTls); +} + + +RTR3DECL(int) RTTlsGetEx(RTTLS iTls, void **ppvValue) +{ + int rc = VINF_SUCCESS; + void *pv = __libc_TLSGet(iTls); + if (RT_UNLIKELY(!pv)) + { + errno = 0; + pv = __libc_TLSGet(iTls); + if (!pv && errno) + rc = RTErrConvertFromErrno(errno); + } + + *ppvValue = pv; + return rc; +} + + +RTR3DECL(int) RTTlsSet(RTTLS iTls, void *pvValue) +{ + if (__libc_TLSSet(iTls, pvValue) != -1) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTThreadGetExecutionTimeMilli(uint64_t *pKernelTime, uint64_t *pUserTime) +{ + return VERR_NOT_IMPLEMENTED; +} diff --git a/src/VBox/Runtime/r3/os2/time-os2.cpp b/src/VBox/Runtime/r3/os2/time-os2.cpp new file mode 100644 index 00000000..e06b9fcd --- /dev/null +++ b/src/VBox/Runtime/r3/os2/time-os2.cpp @@ -0,0 +1,87 @@ +/* $Id: time-os2.cpp $ */ +/** @file + * IPRT - Time, OS/2. + */ + +/* + * Contributed by knut st. osmundsen. + * + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + * -------------------------------------------------------------------- + * + * This code is based on: + * + * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#include <InnoTekLIBC/FastInfoBlocks.h> + +#include <iprt/time.h> +#include "internal/time.h" + +/** @todo mscount will roll over after ~48 days. */ + +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return fibGetMsCount() * UINT64_C(10000000); +} + + +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return fibGetMsCount(); +} + diff --git a/src/VBox/Runtime/r3/path.cpp b/src/VBox/Runtime/r3/path.cpp new file mode 100644 index 00000000..bcd8deb1 --- /dev/null +++ b/src/VBox/Runtime/r3/path.cpp @@ -0,0 +1,163 @@ +/* $Id: path.cpp $ */ +/** @file + * IPRT - Path Manipulation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/path.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include "internal/path.h" +#include "internal/process.h" + + +#ifdef RT_OS_SOLARIS +/** + * Hack to strip of the architecture subdirectory from the exec dir. + * + * @returns See RTPathExecDir. + * @param pszPath See RTPathExecDir. + * @param cchPath See RTPathExecDir. + */ +DECLINLINE(int) rtPathSolarisArchHack(char *pszPath, size_t cchPath) +{ + int rc = RTPathExecDir(pszPath, cchPath); + if (RT_SUCCESS(rc)) + { + const char *pszLast = RTPathFilename(pszPath); + if ( !strcmp(pszLast, "amd64") + || !strcmp(pszLast, "i386")) + RTPathStripFilename(pszPath); + } + return rc; +} +#endif + + +RTDECL(int) RTPathExecDir(char *pszPath, size_t cchPath) +{ + AssertReturn(g_szrtProcExePath[0], VERR_WRONG_ORDER); + + /* + * Calc the length and check if there is space before copying. + */ + size_t cch = g_cchrtProcExeDir; + if (cch < cchPath) + { + memcpy(pszPath, g_szrtProcExePath, cch); + pszPath[cch] = '\0'; + return VINF_SUCCESS; + } + + AssertMsgFailed(("Buffer too small (%zu <= %zu)\n", cchPath, cch)); + return VERR_BUFFER_OVERFLOW; +} + + +RTDECL(int) RTPathAppPrivateNoArch(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE) + return RTStrCopy(pszPath, cchPath, RTPATH_APP_PRIVATE); +#elif defined(RT_OS_SOLARIS) + return rtPathSolarisArchHack(pszPath, cchPath); +#else + return RTPathExecDir(pszPath, cchPath); +#endif +} + + +RTDECL(int) RTPathAppPrivateArch(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE_ARCH) + return RTStrCopy(pszPath, cchPath, RTPATH_APP_PRIVATE_ARCH); +#else + return RTPathExecDir(pszPath, cchPath); +#endif +} + + +RTDECL(int) RTPathAppPrivateArchTop(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE_ARCH_TOP) + return RTStrCopy(pszPath, cchPath, RTPATH_APP_PRIVATE_ARCH_TOP); +#elif !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE_ARCH) + return RTStrCopy(pszPath, cchPath, RTPATH_APP_PRIVATE_ARCH); +#elif defined(RT_OS_SOLARIS) + return rtPathSolarisArchHack(pszPath, cchPath); +#else + int rc = RTPathExecDir(pszPath, cchPath); + return rc; +#endif +} + + +RTDECL(int) RTPathSharedLibs(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_SHARED_LIBS) + return RTStrCopy(pszPath, cchPath, RTPATH_SHARED_LIBS); +#else + return RTPathExecDir(pszPath, cchPath); +#endif +} + + +RTDECL(int) RTPathAppDocs(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_DOCS) + return RTStrCopy(pszPath, cchPath, RTPATH_APP_DOCS); +#elif defined(RT_OS_SOLARIS) + return rtPathSolarisArchHack(pszPath, cchPath); +#else + return RTPathExecDir(pszPath, cchPath); +#endif +} + + +RTR3DECL(int) RTPathGetMode(const char *pszPath, PRTFMODE pfMode) +{ + AssertPtrReturn(pfMode, VERR_INVALID_POINTER); + + RTFSOBJINFO ObjInfo; + int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(rc)) + *pfMode = ObjInfo.Attr.fMode; + + return rc; +} diff --git a/src/VBox/Runtime/r3/poll.cpp b/src/VBox/Runtime/r3/poll.cpp new file mode 100644 index 00000000..51286e0c --- /dev/null +++ b/src/VBox/Runtime/r3/poll.cpp @@ -0,0 +1,1148 @@ +/* $Id: poll.cpp $ */ +/** @file + * IPRT - Polling I/O Handles, Windows+Posix Implementation. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> + +#elif defined(RT_OS_OS2) +# define INCL_BASE +# include <os2.h> +# include <limits.h> +# include <sys/socket.h> + +#else +# include <limits.h> +# include <errno.h> +# include <sys/poll.h> +# if defined(RT_OS_SOLARIS) +# include <sys/socket.h> +# endif +#endif + +#include <iprt/poll.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/pipe.h> +#include <iprt/socket.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "internal/pipe.h" +#define IPRT_INTERNAL_SOCKET_POLLING_ONLY +#include "internal/socket.h" +#include "internal/magics.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The maximum poll set size. + * @remarks To help portability, we set this to the Windows limit. We can lift + * this restriction later if it becomes necessary. */ +#define RTPOLL_SET_MAX 64 + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Handle entry in a poll set. + */ +typedef struct RTPOLLSETHNDENT +{ + /** The handle type. */ + RTHANDLETYPE enmType; + /** The handle ID. */ + uint32_t id; + /** The events we're waiting for here. */ + uint32_t fEvents; + /** Set if this is the final entry for this handle. + * If the handle is entered more than once, this will be clear for all but + * the last entry. */ + bool fFinalEntry; + /** The handle union. */ + RTHANDLEUNION u; +} RTPOLLSETHNDENT; +/** Pointer to a handle entry. */ +typedef RTPOLLSETHNDENT *PRTPOLLSETHNDENT; + + +/** + * Poll set data. + */ +typedef struct RTPOLLSETINTERNAL +{ + /** The magic value (RTPOLLSET_MAGIC). */ + uint32_t u32Magic; + /** Set when someone is polling or making changes. */ + bool volatile fBusy; + + /** The number of allocated handles. */ + uint16_t cHandlesAllocated; + /** The number of valid handles in the set. */ + uint16_t cHandles; + +#ifdef RT_OS_WINDOWS + /** Pointer to an array of native handles. */ + HANDLE *pahNative; +#elif defined(RT_OS_OS2) + /** The semaphore records. */ + PSEMRECORD paSemRecs; + /** The multiple wait semaphore used for non-socket waits. */ + HMUX hmux; + /** os2_select template. */ + int *pafdSelect; + /** The number of sockets to monitor for read. */ + uint16_t cReadSockets; + /** The number of sockets to monitor for write. */ + uint16_t cWriteSockets; + /** The number of sockets to monitor for exceptions. */ + uint16_t cXcptSockets; + /** The number of pipes. */ + uint16_t cPipes; + /** Pointer to an array of native handles. */ + PRTHCINTPTR pahNative; +#else + /** Pointer to an array of pollfd structures. */ + struct pollfd *paPollFds; +#endif + /** Pointer to an array of handles and IDs. */ + PRTPOLLSETHNDENT paHandles; +} RTPOLLSETINTERNAL; + + + +/** + * Common worker for RTPoll and RTPollNoResume + */ +static int rtPollNoResumeWorker(RTPOLLSETINTERNAL *pThis, uint64_t MsStart, RTMSINTERVAL cMillies, + uint32_t *pfEvents, uint32_t *pid) +{ + int rc; + + /* + * Check for special case, RTThreadSleep... + */ + uint32_t const cHandles = pThis->cHandles; + if (cHandles == 0) + { + if (RT_LIKELY(cMillies != RT_INDEFINITE_WAIT)) + { + rc = RTThreadSleep(cMillies); + if (RT_SUCCESS(rc)) + rc = VERR_TIMEOUT; + } + else + rc = VERR_DEADLOCK; + return rc; + } + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + /* + * Check + prepare the handles before waiting. + */ + uint32_t fEvents = 0; + bool const fNoWait = cMillies == 0; + uint32_t i; + for (i = 0; i < cHandles; i++) + { + switch (pThis->paHandles[i].enmType) + { + case RTHANDLETYPE_PIPE: + fEvents = rtPipePollStart(pThis->paHandles[i].u.hPipe, pThis, pThis->paHandles[i].fEvents, + pThis->paHandles[i].fFinalEntry, fNoWait); + break; + + case RTHANDLETYPE_SOCKET: + fEvents = rtSocketPollStart(pThis->paHandles[i].u.hSocket, pThis, pThis->paHandles[i].fEvents, + pThis->paHandles[i].fFinalEntry, fNoWait); + break; + + default: + AssertFailed(); + fEvents = UINT32_MAX; + break; + } + if (fEvents) + break; + } + if ( fEvents + || fNoWait) + { + + if (pid) + *pid = pThis->paHandles[i].id; + if (pfEvents) + *pfEvents = fEvents; + rc = !fEvents + ? VERR_TIMEOUT + : fEvents != UINT32_MAX + ? VINF_SUCCESS + : VERR_INTERNAL_ERROR_4; + + /* clean up */ + if (!fNoWait) + while (i-- > 0) + { + switch (pThis->paHandles[i].enmType) + { + case RTHANDLETYPE_PIPE: + rtPipePollDone(pThis->paHandles[i].u.hPipe, pThis->paHandles[i].fEvents, + pThis->paHandles[i].fFinalEntry, false); + break; + + case RTHANDLETYPE_SOCKET: + rtSocketPollDone(pThis->paHandles[i].u.hSocket, pThis->paHandles[i].fEvents, + pThis->paHandles[i].fFinalEntry, false); + break; + + default: + AssertFailed(); + break; + } + } + + return rc; + } + + + /* + * Wait. + */ +# ifdef RT_OS_WINDOWS + RT_NOREF_PV(MsStart); + + DWORD dwRc = WaitForMultipleObjectsEx(cHandles, pThis->pahNative, + FALSE /*fWaitAll */, + cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies, + TRUE /*fAlertable*/); + AssertCompile(WAIT_OBJECT_0 == 0); + if (dwRc < WAIT_OBJECT_0 + cHandles) + rc = VERR_INTERRUPTED; + else if (dwRc == WAIT_TIMEOUT) + rc = VERR_TIMEOUT; + else if (dwRc == WAIT_IO_COMPLETION) + rc = VERR_INTERRUPTED; + else if (dwRc == WAIT_FAILED) + rc = RTErrConvertFromWin32(GetLastError()); + else + { + AssertMsgFailed(("%u (%#x)\n", dwRc, dwRc)); + rc = VERR_INTERNAL_ERROR_5; + } + +# else /* RT_OS_OS2 */ + APIRET orc; + ULONG ulUser = 0; + uint16_t cSockets = pThis->cReadSockets + pThis->cWriteSockets + pThis->cXcptSockets; + if (cSockets == 0) + { + /* Only pipes. */ + AssertReturn(pThis->cPipes > 0, VERR_INTERNAL_ERROR_2); + orc = DosWaitMuxWaitSem(pThis->hmux, + cMillies == RT_INDEFINITE_WAIT ? SEM_INDEFINITE_WAIT : RT_MIN(cMillies, SEM_INDEFINITE_WAIT - 1), + &ulUser); + rc = RTErrConvertFromOS2(orc); + } + else + { + int *pafdSelect = (int *)alloca(cSockets + 1); + if (pThis->cPipes == 0) + { + /* Only sockets. */ + memcpy(pafdSelect, pThis->pafdSelect, sizeof(pThis->pafdSelect[0]) * (cSockets + 1)); + rc = os2_select(pafdSelect, pThis->cReadSockets, pThis->cWriteSockets, pThis->cXcptSockets, + cMillies == RT_INDEFINITE_WAIT ? -1 : (long)RT_MIN(cMillies, LONG_MAX)); + if (rc > 0) + rc = VINF_SUCCESS; + else if (rc == 0) + rc = VERR_TIMEOUT; + else + rc = RTErrConvertFromErrno(sock_errno()); + } + else + { + /* Mix of both - taking the easy way out, not optimal, but whatever... */ + do + { + orc = DosWaitMuxWaitSem(pThis->hmux, 8, &ulUser); + if (orc != ERROR_TIMEOUT && orc != ERROR_SEM_TIMEOUT) + { + rc = RTErrConvertFromOS2(orc); + break; + } + + memcpy(pafdSelect, pThis->pafdSelect, sizeof(pThis->pafdSelect[0]) * (cSockets + 1)); + rc = os2_select(pafdSelect, pThis->cReadSockets, pThis->cWriteSockets, pThis->cXcptSockets, 8); + if (rc != 0) + { + if (rc > 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(sock_errno()); + break; + } + } while (cMillies == RT_INDEFINITE_WAIT || RTTimeMilliTS() - MsStart < cMillies); + } + } +# endif /* RT_OS_OS2 */ + + /* + * Get event (if pending) and do wait cleanup. + */ + bool fHarvestEvents = true; + for (i = 0; i < cHandles; i++) + { + fEvents = 0; + switch (pThis->paHandles[i].enmType) + { + case RTHANDLETYPE_PIPE: + fEvents = rtPipePollDone(pThis->paHandles[i].u.hPipe, pThis->paHandles[i].fEvents, + pThis->paHandles[i].fFinalEntry, fHarvestEvents); + break; + + case RTHANDLETYPE_SOCKET: + fEvents = rtSocketPollDone(pThis->paHandles[i].u.hSocket, pThis->paHandles[i].fEvents, + pThis->paHandles[i].fFinalEntry, fHarvestEvents); + break; + + default: + AssertFailed(); + break; + } + if ( fEvents + && fHarvestEvents) + { + Assert(fEvents != UINT32_MAX); + fHarvestEvents = false; + if (pfEvents) + *pfEvents = fEvents; + if (pid) + *pid = pThis->paHandles[i].id; + rc = VINF_SUCCESS; + } + } + +#else /* POSIX */ + + RT_NOREF_PV(MsStart); + + /* clear the revents. */ + uint32_t i = pThis->cHandles; + while (i-- > 0) + pThis->paPollFds[i].revents = 0; + + rc = poll(&pThis->paPollFds[0], pThis->cHandles, + cMillies == RT_INDEFINITE_WAIT || cMillies >= INT_MAX + ? -1 + : (int)cMillies); + if (rc == 0) + return VERR_TIMEOUT; + if (rc < 0) + return RTErrConvertFromErrno(errno); + for (i = 0; i < pThis->cHandles; i++) + if (pThis->paPollFds[i].revents) + { + if (pfEvents) + { + *pfEvents = 0; + if (pThis->paPollFds[i].revents & (POLLIN +# ifdef POLLRDNORM + | POLLRDNORM /* just in case */ +# endif +# ifdef POLLRDBAND + | POLLRDBAND /* ditto */ +# endif +# ifdef POLLPRI + | POLLPRI /* ditto */ +# endif +# ifdef POLLMSG + | POLLMSG /* ditto */ +# endif +# ifdef POLLWRITE + | POLLWRITE /* ditto */ +# endif +# ifdef POLLEXTEND + | POLLEXTEND /* ditto */ +# endif + ) + ) + *pfEvents |= RTPOLL_EVT_READ; + + if (pThis->paPollFds[i].revents & (POLLOUT +# ifdef POLLWRNORM + | POLLWRNORM /* just in case */ +# endif +# ifdef POLLWRBAND + | POLLWRBAND /* ditto */ +# endif + ) + ) + *pfEvents |= RTPOLL_EVT_WRITE; + + if (pThis->paPollFds[i].revents & (POLLERR | POLLHUP | POLLNVAL +# ifdef POLLRDHUP + | POLLRDHUP +# endif + ) + ) + *pfEvents |= RTPOLL_EVT_ERROR; + +# if defined(RT_OS_SOLARIS) + /* Solaris does not return POLLHUP for sockets, just POLLIN. Check if a + POLLIN should also have RTPOLL_EVT_ERROR set or not, so we present a + behaviour more in line with linux and BSDs. Note that this will not + help is only RTPOLL_EVT_ERROR was requested, that will require + extending this hack quite a bit further (restart poll): */ + if ( *pfEvents == RTPOLL_EVT_READ + && pThis->paHandles[i].enmType == RTHANDLETYPE_SOCKET) + { + uint8_t abBuf[64]; + ssize_t rcRecv = recv(pThis->paPollFds[i].fd, abBuf, sizeof(abBuf), MSG_PEEK | MSG_DONTWAIT); + if (rcRecv == 0) + *pfEvents |= RTPOLL_EVT_ERROR; + } +# endif + } + if (pid) + *pid = pThis->paHandles[i].id; + return VINF_SUCCESS; + } + + AssertFailed(); + RTThreadYield(); + rc = VERR_INTERRUPTED; + +#endif /* POSIX */ + + return rc; +} + + +RTDECL(int) RTPoll(RTPOLLSET hPollSet, RTMSINTERVAL cMillies, uint32_t *pfEvents, uint32_t *pid) +{ + RTPOLLSETINTERNAL *pThis = hPollSet; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, VERR_INVALID_HANDLE); + AssertPtrNull(pfEvents); + AssertPtrNull(pid); + + /* + * Set the busy flag and do the job. + */ + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), VERR_CONCURRENT_ACCESS); + + int rc; + if (cMillies == RT_INDEFINITE_WAIT || cMillies == 0) + { + do rc = rtPollNoResumeWorker(pThis, 0, cMillies, pfEvents, pid); + while (rc == VERR_INTERRUPTED); + } + else + { + uint64_t MsStart = RTTimeMilliTS(); + rc = rtPollNoResumeWorker(pThis, MsStart, cMillies, pfEvents, pid); + while (RT_UNLIKELY(rc == VERR_INTERRUPTED)) + { + if (RTTimeMilliTS() - MsStart >= cMillies) + { + rc = VERR_TIMEOUT; + break; + } + rc = rtPollNoResumeWorker(pThis, MsStart, cMillies, pfEvents, pid); + } + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + + return rc; +} + + +RTDECL(int) RTPollNoResume(RTPOLLSET hPollSet, RTMSINTERVAL cMillies, uint32_t *pfEvents, uint32_t *pid) +{ + RTPOLLSETINTERNAL *pThis = hPollSet; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, VERR_INVALID_HANDLE); + AssertPtrNull(pfEvents); + AssertPtrNull(pid); + + /* + * Set the busy flag and do the job. + */ + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), VERR_CONCURRENT_ACCESS); + + int rc; + if (cMillies == RT_INDEFINITE_WAIT || cMillies == 0) + rc = rtPollNoResumeWorker(pThis, 0, cMillies, pfEvents, pid); + else + rc = rtPollNoResumeWorker(pThis, RTTimeMilliTS(), cMillies, pfEvents, pid); + + ASMAtomicWriteBool(&pThis->fBusy, false); + + return rc; +} + + +RTDECL(int) RTPollSetCreate(PRTPOLLSET phPollSet) +{ + AssertPtrReturn(phPollSet, VERR_INVALID_POINTER); + RTPOLLSETINTERNAL *pThis = (RTPOLLSETINTERNAL *)RTMemAlloc(sizeof(RTPOLLSETINTERNAL)); + if (!pThis) + return VERR_NO_MEMORY; + + pThis->fBusy = false; + pThis->cHandles = 0; + pThis->cHandlesAllocated = 0; +#ifdef RT_OS_WINDOWS + pThis->pahNative = NULL; +#elif defined(RT_OS_OS2) + pThis->hmux = NULLHANDLE; + APIRET orc = DosCreateMuxWaitSem(NULL, &pThis->hmux, 0, NULL, DCMW_WAIT_ANY); + if (orc != NO_ERROR) + { + RTMemFree(pThis); + return RTErrConvertFromOS2(orc); + } + pThis->pafdSelect = NULL; + pThis->cReadSockets = 0; + pThis->cWriteSockets = 0; + pThis->cXcptSockets = 0; + pThis->cPipes = 0; + pThis->pahNative = NULL; +#else + pThis->paPollFds = NULL; +#endif + pThis->paHandles = NULL; + pThis->u32Magic = RTPOLLSET_MAGIC; + + *phPollSet = pThis; + return VINF_SUCCESS; +} + + +RTDECL(int) RTPollSetDestroy(RTPOLLSET hPollSet) +{ + RTPOLLSETINTERNAL *pThis = hPollSet; + if (pThis == NIL_RTPOLLSET) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), VERR_CONCURRENT_ACCESS); + + ASMAtomicWriteU32(&pThis->u32Magic, ~RTPOLLSET_MAGIC); +#ifdef RT_OS_WINDOWS + RTMemFree(pThis->pahNative); + pThis->pahNative = NULL; +#elif defined(RT_OS_OS2) + DosCloseMuxWaitSem(pThis->hmux); + pThis->hmux = NULLHANDLE; + RTMemFree(pThis->pafdSelect); + pThis->pafdSelect = NULL; + RTMemFree(pThis->pahNative); + pThis->pahNative = NULL; +#else + RTMemFree(pThis->paPollFds); + pThis->paPollFds = NULL; +#endif + RTMemFree(pThis->paHandles); + pThis->paHandles = NULL; + RTMemFree(pThis); + + return VINF_SUCCESS; +} + +#ifdef RT_OS_OS2 + +/** + * Checks if @a fd is in the specific socket subset. + * + * @returns true / false. + * @param pThis The poll set instance. + * @param iStart The index to start at. + * @param cFds The number of sockets to check. + * @param fd The socket to look for. + */ +static bool rtPollSetOs2IsSocketInSet(RTPOLLSETINTERNAL *pThis, uint16_t iStart, uint16_t cFds, int fd) +{ + int const *pfd = pThis->pafdSelect + iStart; + while (cFds-- > 0) + { + if (*pfd == fd) + return true; + pfd++; + } + return false; +} + + +/** + * Removes a socket from a select template subset. + * + * @param pThis The poll set instance. + * @param iStart The index to start at. + * @param pcSubSet The subset counter to decrement. + * @param fd The socket to remove. + */ +static void rtPollSetOs2RemoveSocket(RTPOLLSETINTERNAL *pThis, uint16_t iStart, uint16_t *pcFds, int fd) +{ + uint16_t cFds = *pcFds; + while (cFds-- > 0) + { + if (pThis->pafdSelect[iStart] == fd) + break; + iStart++; + } + AssertReturnVoid(iStart != UINT16_MAX); + + /* Note! We keep a -1 entry at the end of the set, thus the + 1. */ + memmove(&pThis->pafdSelect[iStart], + &pThis->pafdSelect[iStart + 1], + pThis->cReadSockets + pThis->cWriteSockets + pThis->cXcptSockets + 1 - 1 - iStart); + *pcFds -= 1; + + Assert(pThis->pafdSelect[pThis->cReadSockets + pThis->cWriteSockets + pThis->cXcptSockets] == -1); +} + + +/** + * Adds a socket to a select template subset. + * + * @param pThis The poll set instance. + * @param iInsert The insertion point. + * ASSUMED to be at the end of the subset. + * @param pcSubSet The subset counter to increment. + * @param fd The socket to add. + */ +static void rtPollSetOs2AddSocket(RTPOLLSETINTERNAL *pThis, uint16_t iInsert, uint16_t *pcFds, int fd) +{ + Assert(!rtPollSetOs2IsSocketInSet(pThis, iInsert - *pcFds, *pcFds, fd)); + + /* Note! We keep a -1 entry at the end of the set, thus the + 1. */ + memmove(&pThis->pafdSelect[iInsert + 1], + &pThis->pafdSelect[iInsert], + pThis->cReadSockets + pThis->cWriteSockets + pThis->cXcptSockets + 1 - iInsert); + pThis->pafdSelect[iInsert] = fd; + *pcFds += 1; + + Assert(pThis->pafdSelect[pThis->cReadSockets + pThis->cWriteSockets + pThis->cXcptSockets] == -1); +} + + +/** + * OS/2 specific RTPollSetAdd worker. + * + * @returns IPRT status code. + * @param pThis The poll set instance. + * @param i The index of the new handle (not committed). + * @param fEvents The events to poll for. + */ +static int rtPollSetOs2Add(RTPOLLSETINTERNAL *pThis, unsigned i, uint32_t fEvents) +{ + if (pThis->paHandles[i].enmType == RTHANDLETYPE_SOCKET) + { + int const fdSocket = pThis->pahNative[i]; + if ( (fEvents & RTPOLL_EVT_READ) + && rtPollSetOs2IsSocketInSet(pThis, 0, pThis->cReadSockets, fdSocket)) + rtPollSetOs2AddSocket(pThis, pThis->cReadSockets, &pThis->cReadSockets, fdSocket); + + if ( (fEvents & RTPOLL_EVT_WRITE) + && rtPollSetOs2IsSocketInSet(pThis, pThis->cReadSockets, pThis->cWriteSockets, fdSocket)) + rtPollSetOs2AddSocket(pThis, pThis->cReadSockets + pThis->cWriteSockets, &pThis->cWriteSockets, fdSocket); + + if ( (fEvents & RTPOLL_EVT_ERROR) + && rtPollSetOs2IsSocketInSet(pThis, pThis->cReadSockets + pThis->cWriteSockets, pThis->cXcptSockets, fdSocket)) + rtPollSetOs2AddSocket(pThis, pThis->cReadSockets + pThis->cWriteSockets + pThis->cXcptSockets, + &pThis->cXcptSockets, fdSocket); + } + else if (pThis->paHandles[i].enmType == RTHANDLETYPE_PIPE) + { + SEMRECORD Rec = { (HSEM)pThis->pahNative[i], pThis->paHandles[i].id }; + APIRET orc = DosAddMuxWaitSem(pThis->hmux, &Rec); + if (orc != NO_ERROR && orc != ERROR_DUPLICATE_HANDLE) + return RTErrConvertFromOS2(orc); + pThis->cPipes++; + } + else + AssertFailedReturn(VERR_INTERNAL_ERROR_2); + return VINF_SUCCESS; +} + +#endif /* RT_OS_OS2 */ + +/** + * Grows the poll set. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY. + * @param pThis The poll set instance. + * @param cHandlesNew The new poll set size. + */ +static int rtPollSetGrow(RTPOLLSETINTERNAL *pThis, uint32_t cHandlesNew) +{ + Assert(cHandlesNew > pThis->cHandlesAllocated); + + /* The common array. */ + void *pvNew = RTMemRealloc(pThis->paHandles, cHandlesNew * sizeof(pThis->paHandles[0])); + if (!pvNew) + return VERR_NO_MEMORY; + pThis->paHandles = (PRTPOLLSETHNDENT)pvNew; + + + /* OS specific handles */ +#if defined(RT_OS_WINDOWS) + pvNew = RTMemRealloc(pThis->pahNative, cHandlesNew * sizeof(pThis->pahNative[0])); + if (!pvNew) + return VERR_NO_MEMORY; + pThis->pahNative = (HANDLE *)pvNew; + +#elif defined(RT_OS_OS2) + pvNew = RTMemRealloc(pThis->pahNative, cHandlesNew * sizeof(pThis->pahNative[0])); + if (!pvNew) + return VERR_NO_MEMORY; + pThis->pahNative = (PRTHCINTPTR)pvNew; + + pvNew = RTMemRealloc(pThis->pafdSelect, (cHandlesNew * 3 + 1) * sizeof(pThis->pafdSelect[0])); + if (!pvNew) + return VERR_NO_MEMORY; + pThis->pafdSelect = (int *)pvNew; + if (pThis->cHandlesAllocated == 0) + pThis->pafdSelect[0] = -1; + +#else + pvNew = RTMemRealloc(pThis->paPollFds, cHandlesNew * sizeof(pThis->paPollFds[0])); + if (!pvNew) + return VERR_NO_MEMORY; + pThis->paPollFds = (struct pollfd *)pvNew; + +#endif + + pThis->cHandlesAllocated = (uint16_t)cHandlesNew; + return VINF_SUCCESS; +} + + +RTDECL(int) RTPollSetAdd(RTPOLLSET hPollSet, PCRTHANDLE pHandle, uint32_t fEvents, uint32_t id) +{ + /* + * Validate the input (tedious). + */ + RTPOLLSETINTERNAL *pThis = hPollSet; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fEvents & ~RTPOLL_EVT_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(fEvents, VERR_INVALID_PARAMETER); + AssertReturn(id != UINT32_MAX, VERR_INVALID_PARAMETER); + + if (!pHandle) + return VINF_SUCCESS; + AssertPtrReturn(pHandle, VERR_INVALID_POINTER); + AssertReturn(pHandle->enmType > RTHANDLETYPE_INVALID && pHandle->enmType < RTHANDLETYPE_END, VERR_INVALID_PARAMETER); + + /* + * Set the busy flag and do the job. + */ + + int rc = VINF_SUCCESS; + RTHCINTPTR hNative = -1; + RTHANDLEUNION uh; + uh.uInt = 0; + switch (pHandle->enmType) + { + case RTHANDLETYPE_PIPE: + uh.hPipe = pHandle->u.hPipe; + if (uh.hPipe == NIL_RTPIPE) + return VINF_SUCCESS; + rc = rtPipePollGetHandle(uh.hPipe, fEvents, &hNative); + break; + + case RTHANDLETYPE_SOCKET: + uh.hSocket = pHandle->u.hSocket; + if (uh.hSocket == NIL_RTSOCKET) + return VINF_SUCCESS; + rc = rtSocketPollGetHandle(uh.hSocket, fEvents, &hNative); + break; + + case RTHANDLETYPE_FILE: + AssertMsgFailed(("Files are always ready for reading/writing and thus not pollable. Use native APIs for special devices.\n")); + rc = VERR_POLL_HANDLE_NOT_POLLABLE; + break; + + case RTHANDLETYPE_THREAD: + AssertMsgFailed(("Thread handles are currently not pollable\n")); + rc = VERR_POLL_HANDLE_NOT_POLLABLE; + break; + + default: + AssertMsgFailed(("\n")); + rc = VERR_POLL_HANDLE_NOT_POLLABLE; + break; + } + if (RT_SUCCESS(rc)) + { + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), VERR_CONCURRENT_ACCESS); + + uint32_t const i = pThis->cHandles; + + /* Check that the handle ID doesn't exist already. */ + uint32_t iPrev = UINT32_MAX; + uint32_t j = i; + while (j-- > 0) + { + if (pThis->paHandles[j].id == id) + { + rc = VERR_POLL_HANDLE_ID_EXISTS; + break; + } + if ( pThis->paHandles[j].enmType == pHandle->enmType + && pThis->paHandles[j].u.uInt == uh.uInt) + iPrev = j; + } + + /* Check that we won't overflow the poll set now. */ + if ( RT_SUCCESS(rc) + && i + 1 > RTPOLL_SET_MAX) + rc = VERR_POLL_SET_IS_FULL; + + /* Grow the tables if necessary. */ + if (RT_SUCCESS(rc) && i + 1 > pThis->cHandlesAllocated) + rc = rtPollSetGrow(pThis, pThis->cHandlesAllocated + 32); + if (RT_SUCCESS(rc)) + { + /* + * Add the handles to the two parallel arrays. + */ +#ifdef RT_OS_WINDOWS + pThis->pahNative[i] = (HANDLE)hNative; +#elif defined(RT_OS_OS2) + pThis->pahNative[i] = hNative; +#else + pThis->paPollFds[i].fd = (int)hNative; + pThis->paPollFds[i].revents = 0; + pThis->paPollFds[i].events = 0; + if (fEvents & RTPOLL_EVT_READ) + pThis->paPollFds[i].events |= POLLIN; + if (fEvents & RTPOLL_EVT_WRITE) + pThis->paPollFds[i].events |= POLLOUT; + if (fEvents & RTPOLL_EVT_ERROR) +# ifdef RT_OS_DARWIN + pThis->paPollFds[i].events |= POLLERR | POLLHUP; +# else + pThis->paPollFds[i].events |= POLLERR; +# endif +#endif + pThis->paHandles[i].enmType = pHandle->enmType; + pThis->paHandles[i].u = uh; + pThis->paHandles[i].id = id; + pThis->paHandles[i].fEvents = fEvents; + pThis->paHandles[i].fFinalEntry = true; + + if (iPrev != UINT32_MAX) + { + Assert(pThis->paHandles[iPrev].fFinalEntry); + pThis->paHandles[iPrev].fFinalEntry = false; + } + + /* + * Validations and OS specific updates. + */ +#ifdef RT_OS_WINDOWS + /* none */ +#elif defined(RT_OS_OS2) + rc = rtPollSetOs2Add(pThis, i, fEvents); +#else /* POSIX */ + if (poll(&pThis->paPollFds[i], 1, 0) < 0) + { + rc = RTErrConvertFromErrno(errno); + pThis->paPollFds[i].fd = -1; + } +#endif /* POSIX */ + + if (RT_SUCCESS(rc)) + { + /* + * Commit it to the set. + */ + pThis->cHandles++; Assert(pThis->cHandles == i + 1); + rc = VINF_SUCCESS; + } + } + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + return rc; +} + + +RTDECL(int) RTPollSetRemove(RTPOLLSET hPollSet, uint32_t id) +{ + /* + * Validate the input. + */ + RTPOLLSETINTERNAL *pThis = hPollSet; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(id != UINT32_MAX, VERR_INVALID_PARAMETER); + + /* + * Set the busy flag and do the job. + */ + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), VERR_CONCURRENT_ACCESS); + + int rc = VERR_POLL_HANDLE_ID_NOT_FOUND; + uint32_t i = pThis->cHandles; + while (i-- > 0) + if (pThis->paHandles[i].id == id) + { + /* Save some details for the duplicate searching. */ + bool const fFinalEntry = pThis->paHandles[i].fFinalEntry; + RTHANDLETYPE const enmType = pThis->paHandles[i].enmType; + RTHANDLEUNION const uh = pThis->paHandles[i].u; +#ifdef RT_OS_OS2 + uint32_t fRemovedEvents = pThis->paHandles[i].fEvents; + RTHCINTPTR const hNative = pThis->pahNative[i]; +#endif + + /* Remove the entry. */ + pThis->cHandles--; + size_t const cToMove = pThis->cHandles - i; + if (cToMove) + { + memmove(&pThis->paHandles[i], &pThis->paHandles[i + 1], cToMove * sizeof(pThis->paHandles[i])); +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + memmove(&pThis->pahNative[i], &pThis->pahNative[i + 1], cToMove * sizeof(pThis->pahNative[i])); +#else + memmove(&pThis->paPollFds[i], &pThis->paPollFds[i + 1], cToMove * sizeof(pThis->paPollFds[i])); +#endif + } + + /* Check for duplicate and set the fFinalEntry flag. */ + if (fFinalEntry) + while (i-- > 0) + if ( pThis->paHandles[i].u.uInt == uh.uInt + && pThis->paHandles[i].enmType == enmType) + { + Assert(!pThis->paHandles[i].fFinalEntry); + pThis->paHandles[i].fFinalEntry = true; + break; + } + +#ifdef RT_OS_OS2 + /* + * Update OS/2 wait structures. + */ + uint32_t fNewEvents = 0; + i = pThis->cHandles; + while (i-- > 0) + if ( pThis->paHandles[i].u.uInt == uh.uInt + && pThis->paHandles[i].enmType == enmType) + fNewEvents |= pThis->paHandles[i].fEvents; + if (enmType == RTHANDLETYPE_PIPE) + { + pThis->cPipes--; + if (fNewEvents == 0) + { + APIRET orc = DosDeleteMuxWaitSem(pThis->hmux, (HSEM)hNative); + AssertMsg(orc == NO_ERROR, ("%d\n", orc)); + } + } + else if ( fNewEvents != (fNewEvents | fRemovedEvents) + && enmType == RTHANDLETYPE_SOCKET) + { + fRemovedEvents = fNewEvents ^ (fNewEvents | fRemovedEvents); + if (fRemovedEvents & RTPOLL_EVT_ERROR) + rtPollSetOs2RemoveSocket(pThis, pThis->cReadSockets + pThis->cWriteSockets, &pThis->cXcptSockets, (int)hNative); + if (fRemovedEvents & RTPOLL_EVT_WRITE) + rtPollSetOs2RemoveSocket(pThis, pThis->cReadSockets, &pThis->cWriteSockets, (int)hNative); + if (fRemovedEvents & RTPOLL_EVT_READ) + rtPollSetOs2RemoveSocket(pThis, 0, &pThis->cReadSockets, (int)hNative); + } +#endif /* RT_OS_OS2 */ + rc = VINF_SUCCESS; + break; + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + return rc; +} + + +RTDECL(int) RTPollSetQueryHandle(RTPOLLSET hPollSet, uint32_t id, PRTHANDLE pHandle) +{ + /* + * Validate the input. + */ + RTPOLLSETINTERNAL *pThis = hPollSet; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(id != UINT32_MAX, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pHandle, VERR_INVALID_POINTER); + + /* + * Set the busy flag and do the job. + */ + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), VERR_CONCURRENT_ACCESS); + + int rc = VERR_POLL_HANDLE_ID_NOT_FOUND; + uint32_t i = pThis->cHandles; + while (i-- > 0) + if (pThis->paHandles[i].id == id) + { + if (pHandle) + { + pHandle->enmType = pThis->paHandles[i].enmType; + pHandle->u = pThis->paHandles[i].u; + } + rc = VINF_SUCCESS; + break; + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + return rc; +} + + +RTDECL(uint32_t) RTPollSetGetCount(RTPOLLSET hPollSet) +{ + /* + * Validate the input. + */ + RTPOLLSETINTERNAL *pThis = hPollSet; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, UINT32_MAX); + + /* + * Set the busy flag and do the job. + */ + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), UINT32_MAX); + uint32_t cHandles = pThis->cHandles; + ASMAtomicWriteBool(&pThis->fBusy, false); + + return cHandles; +} + +RTDECL(int) RTPollSetEventsChange(RTPOLLSET hPollSet, uint32_t id, uint32_t fEvents) +{ + /* + * Validate the input. + */ + RTPOLLSETINTERNAL *pThis = hPollSet; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPOLLSET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(id != UINT32_MAX, VERR_INVALID_PARAMETER); + AssertReturn(!(fEvents & ~RTPOLL_EVT_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(fEvents, VERR_INVALID_PARAMETER); + + /* + * Set the busy flag and do the job. + */ + AssertReturn(ASMAtomicCmpXchgBool(&pThis->fBusy, true, false), VERR_CONCURRENT_ACCESS); + + int rc = VERR_POLL_HANDLE_ID_NOT_FOUND; + uint32_t i = pThis->cHandles; + while (i-- > 0) + if (pThis->paHandles[i].id == id) + { + if (pThis->paHandles[i].fEvents != fEvents) + { +#if defined(RT_OS_WINDOWS) + /*nothing*/ +#elif defined(RT_OS_OS2) + if (pThis->paHandles[i].enmType == RTHANDLETYPE_SOCKET) + { + uint32_t fOldEvents = 0; + uint32_t j = pThis->cHandles; + while (j-- > 0) + if ( pThis->paHandles[j].enmType == RTHANDLETYPE_SOCKET + && pThis->paHandles[j].u.uInt == pThis->paHandles[i].u.uInt + && j != i) + fOldEvents |= pThis->paHandles[j].fEvents; + uint32_t fNewEvents = fOldEvents | fEvents; + fOldEvents |= pThis->paHandles[i].fEvents; + if (fOldEvents != fEvents) + { + int const fdSocket = pThis->pahNative[i]; + uint32_t const fChangedEvents = fOldEvents ^ fNewEvents; + + if ((fChangedEvents & RTPOLL_EVT_READ) && (fNewEvents & RTPOLL_EVT_READ)) + rtPollSetOs2AddSocket(pThis, pThis->cReadSockets, &pThis->cReadSockets, fdSocket); + else if (fChangedEvents & RTPOLL_EVT_READ) + rtPollSetOs2RemoveSocket(pThis, 0, &pThis->cReadSockets, fdSocket); + + if ((fChangedEvents & RTPOLL_EVT_WRITE) && (fNewEvents & RTPOLL_EVT_WRITE)) + rtPollSetOs2AddSocket(pThis, pThis->cReadSockets + pThis->cWriteSockets, + &pThis->cWriteSockets, fdSocket); + else if (fChangedEvents & RTPOLL_EVT_WRITE) + rtPollSetOs2RemoveSocket(pThis, pThis->cReadSockets, &pThis->cWriteSockets, fdSocket); + + if ((fChangedEvents & RTPOLL_EVT_ERROR) && (fNewEvents & RTPOLL_EVT_ERROR)) + rtPollSetOs2AddSocket(pThis, pThis->cReadSockets + pThis->cWriteSockets + pThis->cXcptSockets, + &pThis->cXcptSockets, fdSocket); + else if (fChangedEvents & RTPOLL_EVT_ERROR) + rtPollSetOs2RemoveSocket(pThis, pThis->cReadSockets + pThis->cWriteSockets, &pThis->cXcptSockets, + fdSocket); + } + } +#else + pThis->paPollFds[i].events = 0; + if (fEvents & RTPOLL_EVT_READ) + pThis->paPollFds[i].events |= POLLIN; + if (fEvents & RTPOLL_EVT_WRITE) + pThis->paPollFds[i].events |= POLLOUT; + if (fEvents & RTPOLL_EVT_ERROR) + pThis->paPollFds[i].events |= POLLERR; +#endif + pThis->paHandles[i].fEvents = fEvents; + } + rc = VINF_SUCCESS; + break; + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/Makefile.kup b/src/VBox/Runtime/r3/posix/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/posix/Makefile.kup diff --git a/src/VBox/Runtime/r3/posix/RTFileQueryFsSizes-posix.cpp b/src/VBox/Runtime/r3/posix/RTFileQueryFsSizes-posix.cpp new file mode 100644 index 00000000..886c3844 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTFileQueryFsSizes-posix.cpp @@ -0,0 +1,78 @@ +/* $Id: RTFileQueryFsSizes-posix.cpp $ */ +/** @file + * IPRT - File I/O, RTFileFsQuerySizes, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE + +#include <errno.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/statvfs.h> + +#include <iprt/file.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include <iprt/string.h> + + +RTR3DECL(int) RTFileQueryFsSizes(RTFILE hFile, PRTFOFF pcbTotal, RTFOFF *pcbFree, + uint32_t *pcbBlock, uint32_t *pcbSector) +{ + struct statvfs StatVFS; + RT_ZERO(StatVFS); + if (fstatvfs(RTFileToNative(hFile), &StatVFS)) + return RTErrConvertFromErrno(errno); + + /* + * Calc the returned values. + */ + if (pcbTotal) + *pcbTotal = (RTFOFF)StatVFS.f_blocks * StatVFS.f_frsize; + if (pcbFree) + *pcbFree = (RTFOFF)StatVFS.f_bavail * StatVFS.f_frsize; + if (pcbBlock) + *pcbBlock = StatVFS.f_frsize; + /* no idea how to get the sector... */ + if (pcbSector) + *pcbSector = 512; + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/posix/RTFileSetAllocationSize-posix.cpp b/src/VBox/Runtime/r3/posix/RTFileSetAllocationSize-posix.cpp new file mode 100644 index 00000000..534754ac --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTFileSetAllocationSize-posix.cpp @@ -0,0 +1,87 @@ +/* $Id: RTFileSetAllocationSize-posix.cpp $ */ +/** @file + * IPRT - RTFileSetAllocationSize, linux implementation. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include <iprt/file.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#include <dlfcn.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> + +/** + * The posix_fallocate() method. + */ +typedef int (*PFNPOSIXFALLOCATE) (int iFd, off_t offStart, off_t cb); + +RTDECL(int) RTFileSetAllocationSize(RTFILE hFile, uint64_t cbSize, uint32_t fFlags) +{ + AssertReturn(hFile != NIL_RTFILE, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTFILE_ALLOC_SIZE_F_VALID), VERR_INVALID_PARAMETER); + AssertMsgReturn(sizeof(off_t) >= sizeof(cbSize) || RT_HIDWORD(cbSize) == 0, + ("64-bit filesize not supported! cbSize=%lld\n", cbSize), + VERR_NOT_SUPPORTED); + + if (fFlags & RTFILE_ALLOC_SIZE_F_KEEP_SIZE) + return VERR_NOT_SUPPORTED; + + int rc = VINF_SUCCESS; + PFNPOSIXFALLOCATE pfnPosixFAllocate = (PFNPOSIXFALLOCATE)(uintptr_t)dlsym(RTLD_DEFAULT, "posix_fallocate"); + if (RT_VALID_PTR(pfnPosixFAllocate)) + { + int rcPosix = pfnPosixFAllocate(RTFileToNative(hFile), 0, cbSize); + if (rcPosix != 0) + { + if (errno == EOPNOTSUPP) + rc = VERR_NOT_SUPPORTED; + else + rc = RTErrConvertFromErrno(errno); + } + } + else + rc = VERR_NOT_SUPPORTED; + + return rc; +} +RT_EXPORT_SYMBOL(RTFileSetAllocationSize); diff --git a/src/VBox/Runtime/r3/posix/RTHandleGetStandard-posix.cpp b/src/VBox/Runtime/r3/posix/RTHandleGetStandard-posix.cpp new file mode 100644 index 00000000..30e54331 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTHandleGetStandard-posix.cpp @@ -0,0 +1,144 @@ +/* $Id: RTHandleGetStandard-posix.cpp $ */ +/** @file + * IPRT - RTHandleGetStandard, POSIX. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#ifdef _MSC_VER +# include <io.h> +#else +# include <unistd.h> +#endif + +#include "internal/iprt.h" +#include <iprt/handle.h> + +#include <iprt/file.h> +#include <iprt/pipe.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/log.h> + +#include "internal/socket.h" + + + +RTDECL(int) RTHandleGetStandard(RTHANDLESTD enmStdHandle, bool fLeaveOpen, PRTHANDLE ph) +{ + /* + * Validate and convert input. + */ + AssertPtrReturn(ph, VERR_INVALID_POINTER); + int fd; + switch (enmStdHandle) + { + case RTHANDLESTD_INPUT: fd = 0; break; + case RTHANDLESTD_OUTPUT: fd = 1; break; + case RTHANDLESTD_ERROR: fd = 2; break; + default: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + + /* + * Is the requested descriptor valid and which IPRT handle type does it + * best map on to? + */ + struct stat st; + int rc = fstat(fd, &st); + if (rc == -1) + return RTErrConvertFromErrno(errno); + + rc = fcntl(fd, F_GETFD, 0); + if (rc == -1) + return RTErrConvertFromErrno(errno); + bool const fInherit = !(rc & FD_CLOEXEC); + + RTHANDLE h; + if (S_ISREG(st.st_mode)) + h.enmType = RTHANDLETYPE_FILE; + else if ( S_ISFIFO(st.st_mode) + || (st.st_mode == 0 && st.st_nlink == 0 /*see bugs on bsd manpage*/)) + h.enmType = RTHANDLETYPE_PIPE; + else if (S_ISSOCK(st.st_mode)) + { + /** @todo check if it's really a socket... IIRC some OSes reports + * anonymouse pips as sockets. */ + h.enmType = RTHANDLETYPE_SOCKET; + } +#if 0 /** @todo re-enable this when the VFS pipe has been coded up. */ + else if (isatty(fd)) + h.enmType = RTHANDLETYPE_PIPE; +#endif + else + h.enmType = RTHANDLETYPE_FILE; + + /* + * Create the IPRT handle. + */ + switch (h.enmType) + { + case RTHANDLETYPE_FILE: + /** @todo fLeaveOpen */ + rc = RTFileFromNative(&h.u.hFile, fd); + break; + + case RTHANDLETYPE_PIPE: + rc = RTPipeFromNative(&h.u.hPipe, fd, + (enmStdHandle == RTHANDLESTD_INPUT ? RTPIPE_N_READ : RTPIPE_N_WRITE) + | (fInherit ? RTPIPE_N_INHERIT : 0) + | (fLeaveOpen ? RTPIPE_N_LEAVE_OPEN : 0)); + break; + + case RTHANDLETYPE_SOCKET: + rc = rtSocketCreateForNative(&h.u.hSocket, fd, fLeaveOpen); + break; + + default: /* shut up gcc */ + return VERR_INTERNAL_ERROR; + } + + if (RT_SUCCESS(rc)) + *ph = h; + + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/RTMemProtect-posix.cpp b/src/VBox/Runtime/r3/posix/RTMemProtect-posix.cpp new file mode 100644 index 00000000..41400a87 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTMemProtect-posix.cpp @@ -0,0 +1,105 @@ +/* $Id: RTMemProtect-posix.cpp $ */ +/** @file + * IPRT - Memory Allocation, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/param.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include <errno.h> +#include <sys/mman.h> + + +RTDECL(int) RTMemProtect(void *pv, size_t cb, unsigned fProtect) RT_NO_THROW_DEF +{ + /* + * Validate input. + */ + if (cb == 0) + { + AssertMsgFailed(("!cb\n")); + return VERR_INVALID_PARAMETER; + } + if (fProtect & ~(RTMEM_PROT_NONE | RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC)) + { + AssertMsgFailed(("fProtect=%#x\n", fProtect)); + return VERR_INVALID_PARAMETER; + } + + /* + * Convert the flags. + */ + int fProt; +#if RTMEM_PROT_NONE == PROT_NONE \ + && RTMEM_PROT_READ == PROT_READ \ + && RTMEM_PROT_WRITE == PROT_WRITE \ + && RTMEM_PROT_EXEC == PROT_EXEC + fProt = fProtect; +#else + Assert(!RTMEM_PROT_NONE); + if (!fProtect) + fProt = PROT_NONE; + else + { + fProt = 0; + if (fProtect & RTMEM_PROT_READ) + fProt |= PROT_READ; + if (fProtect & RTMEM_PROT_WRITE) + fProt |= PROT_WRITE; + if (fProtect & RTMEM_PROT_EXEC) + fProt |= PROT_EXEC; + } +#endif + + /* + * Align the request. + */ + cb += (uintptr_t)pv & PAGE_OFFSET_MASK; + pv = (void *)((uintptr_t)pv & ~PAGE_OFFSET_MASK); + + /* + * Change the page attributes. + */ + int rc = mprotect(pv, cb, fProt); + if (!rc) + return rc; + return RTErrConvertFromErrno(errno); +} diff --git a/src/VBox/Runtime/r3/posix/RTMpGetCount-posix.cpp b/src/VBox/Runtime/r3/posix/RTMpGetCount-posix.cpp new file mode 100644 index 00000000..f48d6f98 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTMpGetCount-posix.cpp @@ -0,0 +1,89 @@ +/* $Id: RTMpGetCount-posix.cpp $ */ +/** @file + * IPRT - RTMpGetCount, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/mp.h> +#include <iprt/assert.h> + +#include <unistd.h> +#if !defined(RT_OS_SOLARIS) +# include <sys/sysctl.h> +#endif + + +RTDECL(RTCPUID) RTMpGetCount(void) +{ + /* + * The sysconf way (linux and others). + */ +#if defined(_SC_NPROCESSORS_MAX) || defined(_SC_NPROCESSORS_CONF) || defined(_SC_NPROCESSORS_ONLN) + int cCpusSC = -1; +# ifdef _SC_NPROCESSORS_MAX + int cMax = sysconf(_SC_NPROCESSORS_MAX); + cCpusSC = RT_MAX(cCpusSC, cMax); +# endif +# ifdef _SC_NPROCESSORS_CONF + int cConf = sysconf(_SC_NPROCESSORS_CONF); + cCpusSC = RT_MAX(cCpusSC, cConf); +# endif +# ifdef _SC_NPROCESSORS_ONLN + int cOnln = sysconf(_SC_NPROCESSORS_ONLN); + cCpusSC = RT_MAX(cCpusSC, cOnln); +# endif + Assert(cCpusSC > 0); + if (cCpusSC > 0) + return cCpusSC; +#endif + + /* + * The BSD 4.4 way. + */ +#if defined(CTL_HW) && defined(HW_NCPU) + int aiMib[2]; + aiMib[0] = CTL_HW; + aiMib[1] = HW_NCPU; + int cCpus = -1; + size_t cb = sizeof(cCpus); + int rc = sysctl(aiMib, RT_ELEMENTS(aiMib), &cCpus, &cb, NULL, 0); + if (rc != -1 && cCpus >= 1) + return cCpus; +#endif + return 1; +} + diff --git a/src/VBox/Runtime/r3/posix/RTPathUserDocuments-posix.cpp b/src/VBox/Runtime/r3/posix/RTPathUserDocuments-posix.cpp new file mode 100644 index 00000000..ee8c8036 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTPathUserDocuments-posix.cpp @@ -0,0 +1,63 @@ +/* $Id: RTPathUserDocuments-posix.cpp $ */ +/** @file + * IPRT - RTPathUserDocuments, posix ring-3. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/path.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> + +RTDECL(int) RTPathUserDocuments(char *pszPath, size_t cchPath) +{ + /* + * Validate input + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cchPath, VERR_INVALID_PARAMETER); + + int rc = RTPathUserHome(pszPath, cchPath); + if (RT_FAILURE(rc)) + return rc; + + rc = RTPathAppend(pszPath, cchPath, "Documents"); + if (RT_FAILURE(rc)) + *pszPath = '\0'; + + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/RTPathUserHome-posix.cpp b/src/VBox/Runtime/r3/posix/RTPathUserHome-posix.cpp new file mode 100644 index 00000000..daa30ea1 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTPathUserHome-posix.cpp @@ -0,0 +1,173 @@ +/* $Id: RTPathUserHome-posix.cpp $ */ +/** @file + * IPRT - Path Manipulation, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PATH +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.h> +#include <sys/types.h> +#include <pwd.h> + +#include <iprt/path.h> +#include <iprt/env.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/path.h" +#include "internal/fs.h" + + +#ifndef RT_OS_L4 +/** + * Worker for RTPathUserHome that looks up the home directory + * using the getpwuid_r api. + * + * @returns IPRT status code. + * @param pszPath The path buffer. + * @param cchPath The size of the buffer. + * @param uid The User ID to query the home directory of. + */ +static int rtPathUserHomeByPasswd(char *pszPath, size_t cchPath, uid_t uid) +{ + /* + * The getpwuid_r function uses the passed in buffer to "allocate" any + * extra memory it needs. On some systems we should probably use the + * sysconf function to find the appropriate buffer size, but since it won't + * work everywhere we'll settle with a 5KB buffer and ASSUME that it'll + * suffice for even the lengthiest user descriptions... + */ + char achBuffer[5120]; + struct passwd Passwd; + struct passwd *pPasswd; + memset(&Passwd, 0, sizeof(Passwd)); + int rc = getpwuid_r(uid, &Passwd, &achBuffer[0], sizeof(achBuffer), &pPasswd); + if (rc != 0) + return RTErrConvertFromErrno(rc); + if (!pPasswd) /* uid not found in /etc/passwd */ + return VERR_PATH_NOT_FOUND; + + /* + * Check that it isn't empty and that it exists. + */ + struct stat st; + if ( !pPasswd->pw_dir + || !*pPasswd->pw_dir + || stat(pPasswd->pw_dir, &st) + || !S_ISDIR(st.st_mode)) + return VERR_PATH_NOT_FOUND; + + /* + * Convert it to UTF-8 and copy it to the return buffer. + */ + return rtPathFromNativeCopy(pszPath, cchPath, pPasswd->pw_dir, NULL); +} +#endif + + +/** + * Worker for RTPathUserHome that looks up the home directory + * using the HOME environment variable. + * + * @returns IPRT status code. + * @param pszPath The path buffer. + * @param cchPath The size of the buffer. + */ +static int rtPathUserHomeByEnv(char *pszPath, size_t cchPath) +{ + /* + * Get HOME env. var it and validate it's existance. + */ + int rc = VERR_PATH_NOT_FOUND; + const char *pszHome = RTEnvGet("HOME"); /** @todo Codeset confusion in RTEnv. */ + if (pszHome) + + { + struct stat st; + if ( !stat(pszHome, &st) + && S_ISDIR(st.st_mode)) + rc = rtPathFromNativeCopy(pszPath, cchPath, pszHome, NULL); + } + return rc; +} + + +RTDECL(int) RTPathUserHome(char *pszPath, size_t cchPath) +{ + int rc; +#ifndef RT_OS_L4 + /* + * We make an exception for the root user and use the system call + * getpwuid_r to determine their initial home path instead of + * reading it from the $HOME variable. This is because the $HOME + * variable does not get changed by sudo (and possibly su and others) + * which can cause root-owned files to appear in user's home folders. + */ + uid_t uid = geteuid(); + if (!uid) + rc = rtPathUserHomeByPasswd(pszPath, cchPath, uid); + else + rc = rtPathUserHomeByEnv(pszPath, cchPath); + + /* + * On failure, retry using the alternative method. + * (Should perhaps restrict the retry cases a bit more here...) + */ + if ( RT_FAILURE(rc) + && rc != VERR_BUFFER_OVERFLOW) + { + if (!uid) + rc = rtPathUserHomeByEnv(pszPath, cchPath); + else + rc = rtPathUserHomeByPasswd(pszPath, cchPath, uid); + } +#else /* RT_OS_L4 */ + rc = rtPathUserHomeByEnv(pszPath, cchPath); +#endif /* RT_OS_L4 */ + + LogFlow(("RTPathUserHome(%p:{%s}, %u): returns %Rrc\n", pszPath, + RT_SUCCESS(rc) ? pszPath : "<failed>", cchPath, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/RTSystemQueryOSInfo-posix.cpp b/src/VBox/Runtime/r3/posix/RTSystemQueryOSInfo-posix.cpp new file mode 100644 index 00000000..6dc2f975 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTSystemQueryOSInfo-posix.cpp @@ -0,0 +1,100 @@ +/* $Id: RTSystemQueryOSInfo-posix.cpp $ */ +/** @file + * IPRT - RTSystemQueryOSInfo, POSIX implementation. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/errcore.h> + +#include <errno.h> +#include <sys/utsname.h> + + +RTDECL(int) RTSystemQueryOSInfo(RTSYSOSINFO enmInfo, char *pszInfo, size_t cchInfo) +{ + /* + * Quick validation. + */ + AssertReturn(enmInfo > RTSYSOSINFO_INVALID && enmInfo < RTSYSOSINFO_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszInfo, VERR_INVALID_POINTER); + if (!cchInfo) + return VERR_BUFFER_OVERFLOW; + + /* + * Handle the request. + */ + switch (enmInfo) + { + case RTSYSOSINFO_PRODUCT: + case RTSYSOSINFO_RELEASE: + case RTSYSOSINFO_VERSION: + { + struct utsname UtsInfo; + if (uname(&UtsInfo) < 0) + return RTErrConvertFromErrno(errno); + const char *pszSrc; + switch (enmInfo) + { + case RTSYSOSINFO_PRODUCT: pszSrc = UtsInfo.sysname; break; + case RTSYSOSINFO_RELEASE: pszSrc = UtsInfo.release; break; + case RTSYSOSINFO_VERSION: pszSrc = UtsInfo.version; break; + default: AssertFatalFailed(); /* screw gcc */ + } + size_t cch = strlen(pszSrc); + if (cch < cchInfo) + { + memcpy(pszInfo, pszSrc, cch + 1); + return VINF_SUCCESS; + } + memcpy(pszInfo, pszSrc, cchInfo - 1); + pszInfo[cchInfo - 1] = '\0'; + return VERR_BUFFER_OVERFLOW; + } + + + case RTSYSOSINFO_SERVICE_PACK: + default: + *pszInfo = '\0'; + return VERR_NOT_SUPPORTED; + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/posix/RTSystemQueryTotalRam-posix.cpp b/src/VBox/Runtime/r3/posix/RTSystemQueryTotalRam-posix.cpp new file mode 100644 index 00000000..151a12f5 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTSystemQueryTotalRam-posix.cpp @@ -0,0 +1,61 @@ +/* $Id: RTSystemQueryTotalRam-posix.cpp $ */ +/** @file + * IPRT - RTSystemQueryTotalRam, windows ring-3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> + + + + +RTDECL(int) RTSystemQueryTotalRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + return VERR_NOT_IMPLEMENTED; +} + +RTDECL(int) RTSystemQueryAvailableRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + return VERR_NOT_IMPLEMENTED; +} diff --git a/src/VBox/Runtime/r3/posix/RTTimeNow-posix.cpp b/src/VBox/Runtime/r3/posix/RTTimeNow-posix.cpp new file mode 100644 index 00000000..b00394c8 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTTimeNow-posix.cpp @@ -0,0 +1,61 @@ +/* $Id: RTTimeNow-posix.cpp $ */ +/** @file + * IPRT - RTTimeNow, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define RTTIME_INCL_TIMEVAL +#include <sys/time.h> +#include <time.h> + +#include <iprt/time.h> + + +/** + * Gets the current system time. + * + * @returns pTime. + * @param pTime Where to store the time. + */ +RTDECL(PRTTIMESPEC) RTTimeNow(PRTTIMESPEC pTime) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return RTTimeSpecSetTimeval(pTime, &tv); +} + diff --git a/src/VBox/Runtime/r3/posix/RTTimeSet-posix.cpp b/src/VBox/Runtime/r3/posix/RTTimeSet-posix.cpp new file mode 100644 index 00000000..8d848b50 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTTimeSet-posix.cpp @@ -0,0 +1,60 @@ +/* $Id: RTTimeSet-posix.cpp $ */ +/** @file + * IPRT - RTTimeSet, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define RTTIME_INCL_TIMEVAL +#include <sys/time.h> +#include <time.h> +#include <errno.h> + +#include <iprt/time.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> + + +RTDECL(int) RTTimeSet(PCRTTIMESPEC pTime) +{ + struct timeval tv; + if (settimeofday(RTTimeSpecGetTimeval(pTime, &tv), NULL) == 0) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + diff --git a/src/VBox/Runtime/r3/posix/RTTimeZoneGetCurrent-posix.cpp b/src/VBox/Runtime/r3/posix/RTTimeZoneGetCurrent-posix.cpp new file mode 100644 index 00000000..6d0d84d0 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/RTTimeZoneGetCurrent-posix.cpp @@ -0,0 +1,262 @@ +/* $Id: RTTimeZoneGetCurrent-posix.cpp $ */ +/** @file + * IPRT - RTTimeZoneGetCurrent, POSIX. + */ + +/* + * Copyright (C) 2020-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/time.h> +#include "internal/iprt.h" + +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/errcore.h> +#include <iprt/types.h> +#include <iprt/symlink.h> +#include <iprt/stream.h> + +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) +# include <tzfile.h> +#else +# define TZDIR "/usr/share/zoneinfo" +# define TZ_MAGIC "TZif" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define PATH_LOCALTIME "/etc/localtime" +#if defined(RT_OS_FREEBSD) +# define PATH_TIMEZONE "/var/db/zoneinfo" +#else +# define PATH_TIMEZONE "/etc/timezone" +#endif +#define PATH_SYSCONFIG_CLOCK "/etc/sysconfig/clock" + + +/** + * Checks if a time zone database file is valid by verifying it begins with + * TZ_MAGIC. + * + * @returns IPRT status code. + * @param pszTimezone The time zone database file relative to + * <tzfile.h>:TZDIR (normally /usr/share/zoneinfo), + * e.g. Europe/London, or Etc/UTC, or UTC, or etc. + * + * @note File format is documented in RFC-8536. + */ +static int rtIsValidTimeZoneFile(const char *pszTimeZone) +{ + if (pszTimeZone == NULL || *pszTimeZone == '\0' || *pszTimeZone == '/') + return VERR_INVALID_PARAMETER; + + int rc = RTStrValidateEncoding(pszTimeZone); + if (RT_SUCCESS(rc)) + { + /* construct full pathname of the time zone file */ + char szTZPath[RTPATH_MAX]; + rc = RTPathJoin(szTZPath, sizeof(szTZPath), TZDIR, pszTimeZone); + if (RT_SUCCESS(rc)) + { + /* open the time zone file and check that it begins with the correct magic number */ + RTFILE hFile = NIL_RTFILE; + rc = RTFileOpen(&hFile, szTZPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + char achTZBuf[sizeof(TZ_MAGIC)]; + rc = RTFileRead(hFile, achTZBuf, sizeof(achTZBuf), NULL); + RTFileClose(hFile); + if (RT_SUCCESS(rc)) + { + if (memcmp(achTZBuf, RT_STR_TUPLE(TZ_MAGIC)) == 0) + rc = VINF_SUCCESS; + else + rc = VERR_INVALID_MAGIC; + } + } + } + } + + return rc; +} + + +/** + * Return the system time zone. + * + * @returns IPRT status code. + * @param pszName The buffer to return the time zone in. + * @param cbName The size of the pszName buffer. + */ +RTDECL(int) RTTimeZoneGetCurrent(char *pszName, size_t cbName) +{ + int rc = RTEnvGetEx(RTENV_DEFAULT, "TZ", pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + { + /* + * $TZ can have two different formats and one of them doesn't specify + * a time zone database file under <tzfile.h>:TZDIR but since all + * current callers of this routine expect a time zone filename we do + * the validation check here so that if it is invalid then we fall back + * to the other mechanisms to return the system's current time zone. + */ + if (*pszName == ':') /* POSIX allows $TZ to begin with a colon (:) so we allow for that here */ + memmove(pszName, pszName + 1, strlen(pszName)); + /** @todo this isn't perfect for absolute paths... Should probably try treat + * it like /etc/localtime. */ + rc = rtIsValidTimeZoneFile(pszName); + if (RT_SUCCESS(rc)) + return rc; + } + else if (rc != VERR_ENV_VAR_NOT_FOUND) + return rc; + + /* + * /etc/localtime is a symbolic link to the system time zone on many OSes + * including Solaris, macOS, Ubuntu, RH/OEL 6 and later, Arch Linux, NetBSD, + * and etc. We extract the time zone pathname relative to TZDIR defined in + * <tzfile.h> which is normally /usr/share/zoneinfo. + * + * N.B. Some OSes have /etc/localtime as a regular file instead of a + * symlink and while we could trawl through all the files under TZDIR + * looking for a match we instead fallback to other popular mechanisms of + * specifying the system-wide time zone for the sake of simplicity. + */ + char szBuf[RTPATH_MAX]; + const char *pszPath = PATH_LOCALTIME; + if (RTSymlinkExists(pszPath)) + { + /* the contents of the symink may contain '..' or other links */ + char szLinkPathReal[RTPATH_MAX]; + rc = RTPathReal(pszPath, szLinkPathReal, sizeof(szLinkPathReal)); + if (RT_SUCCESS(rc)) + { + rc = RTPathReal(TZDIR, szBuf, sizeof(szBuf)); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Assert(RTPathStartsWith(szLinkPathReal, szBuf)); + if (RTPathStartsWith(szLinkPathReal, szBuf)) + { + /* <tzfile.h>:TZDIR doesn't include a trailing slash */ + const char *pszTimeZone = &szLinkPathReal[strlen(szBuf) + 1]; + rc = rtIsValidTimeZoneFile(pszTimeZone); + if (RT_SUCCESS(rc)) + return RTStrCopy(pszName, cbName, pszTimeZone); + } + } + } + } + + /* + * /etc/timezone is a regular file consisting of a single line containing + * the time zone (e.g. Europe/London or Etc/UTC or etc.) and is used by a + * variety of Linux distros such as Ubuntu, Gentoo, Debian, and etc. + * The equivalent on FreeBSD is /var/db/zoneinfo. + */ + pszPath = PATH_TIMEZONE; + if (RTFileExists(pszPath)) + { + RTFILE hFile = NIL_RTFILE; + rc = RTFileOpen(&hFile, PATH_TIMEZONE, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + size_t cbRead = 0; + rc = RTFileRead(hFile, szBuf, sizeof(szBuf), &cbRead); + RTFileClose(hFile); + if (RT_SUCCESS(rc) && cbRead > 0) + { + /* Get the first line and strip it. */ + szBuf[RT_MIN(sizeof(szBuf) - 1, cbRead)] = '\0'; + size_t const offNewLine = RTStrOffCharOrTerm(szBuf, '\n'); + szBuf[offNewLine] = '\0'; + const char *pszTimeZone = RTStrStrip(szBuf); + + rc = rtIsValidTimeZoneFile(pszTimeZone); + if (RT_SUCCESS(rc)) + return RTStrCopy(pszName, cbName, pszTimeZone); + } + } + } + + /* + * Older versions of RedHat / OEL don't have /etc/localtime as a symlink or + * /etc/timezone but instead have /etc/sysconfig/clock which contains a line + * of the syntax ZONE=Europe/London or ZONE="Europe/London" amongst other entries. + */ + pszPath = PATH_SYSCONFIG_CLOCK; + if (RTFileExists(pszPath)) + { + PRTSTREAM pStrm; + rc = RTStrmOpen(pszPath, "r", &pStrm); + if (RT_SUCCESS(rc)) + { + while (RT_SUCCESS(rc = RTStrmGetLine(pStrm, szBuf, sizeof(szBuf)))) + { + static char const s_szVarEq[] = "ZONE="; + char *pszStart = RTStrStrip(szBuf); + if (memcmp(pszStart, RT_STR_TUPLE(s_szVarEq)) == 0) + { + char *pszTimeZone = &pszStart[sizeof(s_szVarEq) - 1]; + + /* Drop any quoting before using the value, assuming it is plain stuff: */ + if (*pszTimeZone == '\"' || *pszTimeZone == '\'') + { + pszTimeZone++; + size_t const cchTimeZone = strlen(pszTimeZone); + if (cchTimeZone && (pszTimeZone[cchTimeZone - 1] == '"' || pszTimeZone[cchTimeZone - 1] == '\'')) + pszTimeZone[cchTimeZone - 1] = '\0'; + } + + rc = rtIsValidTimeZoneFile(pszTimeZone); + if (RT_SUCCESS(rc)) + { + RTStrmClose(pStrm); + return RTStrCopy(pszName, cbName, pszTimeZone); + } + } + } + RTStrmClose(pStrm); + } + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/allocex-r3-posix.cpp b/src/VBox/Runtime/r3/posix/allocex-r3-posix.cpp new file mode 100644 index 00000000..b8f1a1ad --- /dev/null +++ b/src/VBox/Runtime/r3/posix/allocex-r3-posix.cpp @@ -0,0 +1,120 @@ +/* $Id: allocex-r3-posix.cpp $ */ +/** @file + * IPRT - Memory Allocation, Extended Alloc Workers, posix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define RTMEM_NO_WRAP_TO_EF_APIS +#include <iprt/mem.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include "../allocex.h" + +#include <sys/mman.h> + + +DECLHIDDEN(int) rtMemAllocEx16BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv) +{ + AssertReturn(cbAlloc < _64K, VERR_NO_MEMORY); + + /* + * Try with every possible address hint since the possible range is very limited. + */ + int fProt = PROT_READ | PROT_WRITE | (fFlags & RTMEMALLOCEX_FLAGS_EXEC ? PROT_EXEC : 0); + uintptr_t uAddr = 0x1000; + uintptr_t uAddrLast = _64K - uAddr - cbAlloc; + while (uAddr <= uAddrLast) + { + void *pv = mmap((void *)uAddr, cbAlloc, fProt, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pv && (uintptr_t)pv <= uAddrLast) + { + *ppv = pv; + return VINF_SUCCESS; + } + + if (pv) + { + munmap(pv, cbAlloc); + pv = NULL; + } + uAddr += _4K; + } + + return VERR_NO_MEMORY; +} + + +DECLHIDDEN(int) rtMemAllocEx32BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv) +{ + int fProt = PROT_READ | PROT_WRITE | (fFlags & RTMEMALLOCEX_FLAGS_EXEC ? PROT_EXEC : 0); +#if ARCH_BITS == 32 + void *pv = mmap(NULL, cbAlloc, fProt, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pv) + { + *ppv = pv; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; + +#elif defined(RT_OS_LINUX) +# ifdef MAP_32BIT + void *pv = mmap(NULL, cbAlloc, fProt, MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0); + if (pv) + { + *ppv = pv; + return VINF_SUCCESS; + } +# endif + + /** @todo On linux, we need an accurate hint. Since I don't need this branch of + * the code right now, I won't bother starting to parse + * /proc/curproc/mmap right now... */ +#else +#endif + return VERR_NOT_SUPPORTED; +} + + +DECLHIDDEN(void) rtMemFreeExYyBitReach(void *pv, size_t cb, uint32_t fFlags) +{ + RT_NOREF_PV(fFlags); + munmap(pv, cb); +} + diff --git a/src/VBox/Runtime/r3/posix/dir-posix.cpp b/src/VBox/Runtime/r3/posix/dir-posix.cpp new file mode 100644 index 00000000..558085ab --- /dev/null +++ b/src/VBox/Runtime/r3/posix/dir-posix.cpp @@ -0,0 +1,733 @@ +/* $Id: dir-posix.cpp $ */ +/** @file + * IPRT - Directory manipulation, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <dlfcn.h> +#include <stdio.h> + +#include <iprt/dir.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include "internal/dir.h" +#include "internal/fs.h" +#include "internal/path.h" + +#if !defined(RT_OS_SOLARIS) && !defined(RT_OS_HAIKU) +# define HAVE_DIRENT_D_TYPE 1 +#endif + + +RTDECL(bool) RTDirExists(const char *pszPath) +{ + bool fRc = false; + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat s; + fRc = !stat(pszNativePath, &s) + && S_ISDIR(s.st_mode); + + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTDirExists(%p={%s}): returns %RTbool\n", pszPath, pszPath, fRc)); + return fRc; +} + + +RTDECL(int) RTDirCreate(const char *pszPath, RTFMODE fMode, uint32_t fCreate) +{ + RT_NOREF_PV(fCreate); + + int rc; + fMode = rtFsModeNormalize(fMode, pszPath, 0, RTFS_TYPE_DIRECTORY); + if (rtFsModeIsValidPermissions(fMode)) + { + char const *pszNativePath; + rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat st; + if (mkdir(pszNativePath, fMode & RTFS_UNIX_MASK) == 0) + { + /* If requested, we try make use the permission bits are set + correctly when asked. For now, we'll just ignore errors here. */ + if (fCreate & RTDIRCREATE_FLAGS_IGNORE_UMASK) + { + if ( stat(pszNativePath, &st) + || (st.st_mode & 07777) != (fMode & 07777) ) + chmod(pszNativePath, fMode & RTFS_UNIX_MASK); + } + rc = VINF_SUCCESS; + } + else + { + rc = errno; + /* + * Solaris mkdir returns ENOSYS on autofs directories, and also + * did this apparently for NFS mount points in some Nevada + * development builds. It also returned EACCES when it should + * have returned EEXIST, which actually is within the POSIX + * spec (not that I like this interpretation, but it seems + * valid). Check ourselves. + */ + if ( rc == ENOSYS + || rc == EACCES) + { + rc = RTErrConvertFromErrno(rc); + if (!stat(pszNativePath, &st)) + rc = VERR_ALREADY_EXISTS; + } + else + rc = RTErrConvertFromErrno(rc); + } + } + + rtPathFreeNative(pszNativePath, pszPath); + } + else + { + AssertMsgFailed(("Invalid file mode! %RTfmode\n", fMode)); + rc = VERR_INVALID_FMODE; + } + LogFlow(("RTDirCreate(%p={%s}, %RTfmode): returns %Rrc\n", pszPath, pszPath, fMode, rc)); + return rc; +} + + +RTDECL(int) RTDirRemove(const char *pszPath) +{ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + if (rmdir(pszNativePath)) + { + rc = errno; + if (rc == EEXIST) /* Solaris returns this, the rest have ENOTEMPTY. */ + rc = VERR_DIR_NOT_EMPTY; + else if (rc != ENOTDIR) + rc = RTErrConvertFromErrno(rc); + else + { + /* + * This may be a valid path-not-found or it may be a non-directory in + * the final component. FsPerf want us to distinguish between the two, + * and trailing slash shouldn't matter because it doesn't on windows... + */ + char *pszFree = NULL; + const char *pszStat = pszNativePath; + size_t cch = strlen(pszNativePath); + if (cch > 2 && RTPATH_IS_SLASH(pszNativePath[cch - 1])) + { + pszFree = (char *)RTMemTmpAlloc(cch); + if (pszFree) + { + memcpy(pszFree, pszNativePath, cch); + do + pszFree[--cch] = '\0'; + while (cch > 2 && RTPATH_IS_SLASH(pszFree[cch - 1])); + pszStat = pszFree; + } + } + + struct stat st; + if (!stat(pszStat, &st) && !S_ISDIR(st.st_mode)) + rc = VERR_NOT_A_DIRECTORY; + else + rc = VERR_PATH_NOT_FOUND; + + if (pszFree) + RTMemTmpFree(pszFree); + } + } + + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTDirRemove(%p={%s}): returns %Rrc\n", pszPath, pszPath, rc)); + return rc; +} + + +RTDECL(int) RTDirFlush(const char *pszPath) +{ + /* + * Linux: The fsync() man page hints at this being required for ensuring + * consistency between directory and file in case of a crash. + * + * Solaris: No mentioned is made of directories on the fsync man page. + * While rename+fsync will do what we want on ZFS, the code needs more + * careful studying wrt whether the directory entry of a new file is + * implicitly synced when the file is synced (it's very likely for ZFS). + * + * FreeBSD: The FFS fsync code seems to flush the directory entry as well + * in some cases. Don't know exactly what's up with rename, but from the + * look of things fsync(dir) should work. + */ + int rc; +#ifdef O_DIRECTORY + int fd = open(pszPath, O_RDONLY | O_DIRECTORY, 0); +#else + int fd = open(pszPath, O_RDONLY, 0); +#endif + if (fd >= 0) + { + if (fsync(fd) == 0) + rc = VINF_SUCCESS; + else + { + /* Linux fsync(2) man page documents both errors as an indication + * that the file descriptor can't be flushed (seen EINVAL for usual + * directories on CIFS). BSD (OS X) fsync(2) documents only the + * latter, and Solaris fsync(3C) pretends there is no problem. */ + if (errno == EROFS || errno == EINVAL) + rc = VERR_NOT_SUPPORTED; + else + rc = RTErrConvertFromErrno(errno); + } + close(fd); + } + else + rc = RTErrConvertFromErrno(errno); + return rc; +} + + +size_t rtDirNativeGetStructSize(const char *pszPath) +{ + long cbNameMax = pathconf(pszPath, _PC_NAME_MAX); +# ifdef NAME_MAX + if (cbNameMax < NAME_MAX) /* This is plain paranoia, but it doesn't hurt. */ + cbNameMax = NAME_MAX; +# endif +# ifdef _XOPEN_NAME_MAX + if (cbNameMax < _XOPEN_NAME_MAX) /* Ditto. */ + cbNameMax = _XOPEN_NAME_MAX; +# endif + size_t cbDir = RT_UOFFSETOF_DYN(RTDIRINTERNAL, Data.d_name[cbNameMax + 1]); + if (cbDir < sizeof(RTDIRINTERNAL)) /* Ditto. */ + cbDir = sizeof(RTDIRINTERNAL); + cbDir = RT_ALIGN_Z(cbDir, 8); + + return cbDir; +} + + +int rtDirNativeOpen(PRTDIRINTERNAL pDir, uintptr_t hRelativeDir, void *pvNativeRelative) +{ + NOREF(hRelativeDir); + NOREF(pvNativeRelative); + + /* + * Convert to a native path and try opendir. + */ + char *pszSlash = NULL; + char const *pszNativePath; + int rc; + if ( !(pDir->fFlags & RTDIR_F_NO_FOLLOW) + || pDir->fDirSlash + || pDir->cchPath <= 1) + rc = rtPathToNative(&pszNativePath, pDir->pszPath, NULL); + else + { + pszSlash = (char *)&pDir->pszPath[pDir->cchPath - 1]; + *pszSlash = '\0'; + rc = rtPathToNative(&pszNativePath, pDir->pszPath, NULL); + } + if (RT_SUCCESS(rc)) + { + if ( !(pDir->fFlags & RTDIR_F_NO_FOLLOW) + || pDir->fDirSlash) + pDir->pDir = opendir(pszNativePath); + else + { + /* + * If we can get fdopendir() and have both O_NOFOLLOW and O_DIRECTORY, + * we will use open() to safely open the directory without following + * symlinks in the final component, and then use fdopendir to get a DIR + * from the file descriptor. + * + * If we cannot get that, we will use lstat() + opendir() as a fallback. + * + * We ASSUME that support for the O_NOFOLLOW and O_DIRECTORY flags is + * older than fdopendir(). + */ +#if defined(O_NOFOLLOW) && defined(O_DIRECTORY) + /* Need to resolve fdopendir dynamically. */ + typedef DIR * (*PFNFDOPENDIR)(int); + static PFNFDOPENDIR s_pfnFdOpenDir = NULL; + static bool volatile s_fInitalized = false; + + PFNFDOPENDIR pfnFdOpenDir = s_pfnFdOpenDir; + ASMCompilerBarrier(); + if (s_fInitalized) + { /* likely */ } + else + { + pfnFdOpenDir = (PFNFDOPENDIR)(uintptr_t)dlsym(RTLD_DEFAULT, "fdopendir"); + s_pfnFdOpenDir = pfnFdOpenDir; + ASMAtomicWriteBool(&s_fInitalized, true); + } + + if (pfnFdOpenDir) + { + int fd = open(pszNativePath, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0); + if (fd >= 0) + { + pDir->pDir = pfnFdOpenDir(fd); + if (RT_UNLIKELY(!pDir->pDir)) + { + rc = RTErrConvertFromErrno(errno); + close(fd); + } + } + else + { + /* WSL returns ELOOP here, but we take no chances that O_NOFOLLOW + takes precedence over O_DIRECTORY everywhere. */ + int iErr = errno; + if (iErr == ELOOP || iErr == ENOTDIR) + { + struct stat St; + if ( lstat(pszNativePath, &St) == 0 + && S_ISLNK(St.st_mode)) + rc = VERR_IS_A_SYMLINK; + else + rc = RTErrConvertFromErrno(iErr); + } + } + } + else +#endif + { + /* Fallback. This contains a race condition. */ + struct stat St; + if ( lstat(pszNativePath, &St) != 0 + || !S_ISLNK(St.st_mode)) + pDir->pDir = opendir(pszNativePath); + else + rc = VERR_IS_A_SYMLINK; + } + } + if (pDir->pDir) + { + /* + * Init data (allocated as all zeros). + */ + pDir->fDataUnread = false; /* spelling it out */ + } + else if (RT_SUCCESS_NP(rc)) + rc = RTErrConvertFromErrno(errno); + + rtPathFreeNative(pszNativePath, pDir->pszPath); + } + if (pszSlash) + *pszSlash = RTPATH_SLASH; + return rc; +} + + +RTDECL(int) RTDirClose(RTDIR hDir) +{ + PRTDIRINTERNAL pDir = hDir; + + /* + * Validate input. + */ + if (!pDir) + return VERR_INVALID_PARAMETER; + if (pDir->u32Magic != RTDIR_MAGIC) + { + AssertMsgFailed(("Invalid pDir=%p\n", pDir)); + return VERR_INVALID_PARAMETER; + } + + /* + * Close the handle. + */ + int rc = VINF_SUCCESS; + pDir->u32Magic = RTDIR_MAGIC_DEAD; + if (closedir(pDir->pDir)) + { + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("closedir(%p) -> errno=%d (%Rrc)\n", pDir->pDir, errno, rc)); + } + + RTMemFree(pDir); + return rc; +} + + +/** + * Ensure that there is unread data in the buffer + * and that there is a converted filename hanging around. + * + * @returns IPRT status code. + * @param pDir the open directory. Fully validated. + */ +static int rtDirReadMore(PRTDIRINTERNAL pDir) +{ + /** @todo try avoid the rematching on buffer overflow errors. */ + for (;;) + { + /* + * Fetch data? + */ + if (!pDir->fDataUnread) + { + struct dirent *pResult = NULL; +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + int rc = readdir_r(pDir->pDir, &pDir->Data, &pResult); +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic pop +#endif + if (rc) + { + rc = RTErrConvertFromErrno(rc); + /** @todo Consider translating ENOENT (The current + * position of the directory stream is invalid) + * differently. */ + AssertMsg(rc == VERR_FILE_NOT_FOUND, ("%Rrc\n", rc)); + return rc; + } + if (!pResult) + return VERR_NO_MORE_FILES; + } + + /* + * Convert the filename to UTF-8. + */ + if (!pDir->pszName) + { + int rc = rtPathFromNative(&pDir->pszName, pDir->Data.d_name, pDir->pszPath); + if (RT_FAILURE(rc)) + { + pDir->pszName = NULL; + return rc; + } + pDir->cchName = strlen(pDir->pszName); + } + if ( !pDir->pfnFilter + || pDir->pfnFilter(pDir, pDir->pszName)) + break; + rtPathFreeIprt(pDir->pszName, pDir->Data.d_name); + pDir->pszName = NULL; + pDir->fDataUnread = false; + } + + pDir->fDataUnread = true; + return VINF_SUCCESS; +} + + +#ifdef HAVE_DIRENT_D_TYPE +/** + * Converts the d_type field to IPRT directory entry type. + * + * @returns IPRT directory entry type. + * @param Unix + */ +static RTDIRENTRYTYPE rtDirType(int iType) +{ + switch (iType) + { + case DT_UNKNOWN: return RTDIRENTRYTYPE_UNKNOWN; + case DT_FIFO: return RTDIRENTRYTYPE_FIFO; + case DT_CHR: return RTDIRENTRYTYPE_DEV_CHAR; + case DT_DIR: return RTDIRENTRYTYPE_DIRECTORY; + case DT_BLK: return RTDIRENTRYTYPE_DEV_BLOCK; + case DT_REG: return RTDIRENTRYTYPE_FILE; + case DT_LNK: return RTDIRENTRYTYPE_SYMLINK; + case DT_SOCK: return RTDIRENTRYTYPE_SOCKET; + case DT_WHT: return RTDIRENTRYTYPE_WHITEOUT; + default: + AssertMsgFailed(("iType=%d\n", iType)); + return RTDIRENTRYTYPE_UNKNOWN; + } +} +#endif /*HAVE_DIRENT_D_TYPE */ + + +RTDECL(int) RTDirRead(RTDIR hDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry) +{ + PRTDIRINTERNAL pDir = hDir; + + /* + * Validate and digest input. + */ + if (!rtDirValidHandle(pDir)) + return VERR_INVALID_PARAMETER; + AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER); + + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + AssertPtrReturn(pcbDirEntry, VERR_INVALID_POINTER); + cbDirEntry = *pcbDirEntry; + AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRY, szName[2]), + ("Invalid *pcbDirEntry=%d (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])), + VERR_INVALID_PARAMETER); + } + + /* + * Fetch more data if necessary and/or convert the name. + */ + int rc = rtDirReadMore(pDir); + if (RT_SUCCESS(rc)) + { + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRY, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired <= cbDirEntry) + { + /* + * Setup the returned data. + */ + pDirEntry->INodeId = pDir->Data.d_ino; /* may need #ifdefing later */ +#ifdef HAVE_DIRENT_D_TYPE + pDirEntry->enmType = rtDirType(pDir->Data.d_type); +#else + pDirEntry->enmType = RTDIRENTRYTYPE_UNKNOWN; +#endif + pDirEntry->cbName = (uint16_t)cchName; + Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + + /* free cached data */ + pDir->fDataUnread = false; + rtPathFreeIprt(pDir->pszName, pDir->Data.d_name); + pDir->pszName = NULL; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + + LogFlow(("RTDirRead(%p:{%s}, %p:{%s}, %p:{%u}): returns %Rrc\n", + pDir, pDir->pszPath, pDirEntry, RT_SUCCESS(rc) ? pDirEntry->szName : "<failed>", + pcbDirEntry, pcbDirEntry ? *pcbDirEntry : 0, rc)); + return rc; +} + + +/** + * Fills dummy info into the info structure. + * This function is called if we cannot stat the file. + * + * @param pInfo The struct in question. + * @param + */ +static void rtDirSetDummyInfo(PRTFSOBJINFO pInfo, RTDIRENTRYTYPE enmType) +{ + pInfo->cbObject = 0; + pInfo->cbAllocated = 0; + RTTimeSpecSetNano(&pInfo->AccessTime, 0); + RTTimeSpecSetNano(&pInfo->ModificationTime, 0); + RTTimeSpecSetNano(&pInfo->ChangeTime, 0); + RTTimeSpecSetNano(&pInfo->BirthTime, 0); + memset(&pInfo->Attr, 0, sizeof(pInfo->Attr)); + pInfo->Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + switch (enmType) + { + default: + case RTDIRENTRYTYPE_UNKNOWN: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL; break; + case RTDIRENTRYTYPE_FIFO: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FIFO; break; + case RTDIRENTRYTYPE_DEV_CHAR: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_DEV_CHAR; break; + case RTDIRENTRYTYPE_DIRECTORY: pInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY; break; + case RTDIRENTRYTYPE_DEV_BLOCK: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_DEV_BLOCK; break; + case RTDIRENTRYTYPE_FILE: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE; break; + case RTDIRENTRYTYPE_SYMLINK: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_SYMLINK; break; + case RTDIRENTRYTYPE_SOCKET: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_SOCKET; break; + case RTDIRENTRYTYPE_WHITEOUT: pInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_WHITEOUT; break; + } +} + + +RTDECL(int) RTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, + RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + PRTDIRINTERNAL pDir = hDir; + + /* + * Validate and digest input. + */ + if (!rtDirValidHandle(pDir)) + return VERR_INVALID_PARAMETER; + AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER); + AssertMsgReturn( enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING + && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + ("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs), + VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + AssertPtrReturn(pcbDirEntry, VERR_INVALID_POINTER); + cbDirEntry = *pcbDirEntry; + AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]), + ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])), + VERR_INVALID_PARAMETER); + } + + /* + * Fetch more data if necessary and/or convert the name. + */ + int rc = rtDirReadMore(pDir); + if (RT_SUCCESS(rc)) + { + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRYEX, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired <= cbDirEntry) + { + /* + * Setup the returned data. + */ + pDirEntry->cwcShortName = 0; + pDirEntry->wszShortName[0] = 0; + pDirEntry->cbName = (uint16_t)cchName; + Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + + /* get the info data */ + size_t cch = cchName + pDir->cchPath + 1; + char *pszNamePath = (char *)alloca(cch); + if (pszNamePath) + { + memcpy(pszNamePath, pDir->pszPath, pDir->cchPath); + memcpy(pszNamePath + pDir->cchPath, pszName, cchName + 1); + rc = RTPathQueryInfoEx(pszNamePath, &pDirEntry->Info, enmAdditionalAttribs, fFlags); + } + else + rc = VERR_NO_MEMORY; + if (RT_FAILURE(rc)) + { +#ifdef HAVE_DIRENT_D_TYPE + rtDirSetDummyInfo(&pDirEntry->Info, rtDirType(pDir->Data.d_type)); +#else + rtDirSetDummyInfo(&pDirEntry->Info, RTDIRENTRYTYPE_UNKNOWN); +#endif + rc = VWRN_NO_DIRENT_INFO; + } + + /* free cached data */ + pDir->fDataUnread = false; + rtPathFreeIprt(pDir->pszName, pDir->Data.d_name); + pDir->pszName = NULL; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + + return rc; +} + + +RTDECL(int) RTDirRewind(RTDIR hDir) +{ + PRTDIRINTERNAL pDir = hDir; + + /* + * Validate and digest input. + */ + if (!rtDirValidHandle(pDir)) + return VERR_INVALID_PARAMETER; + + /* + * Do the rewinding. + */ + /** @todo OS/2 does not rescan the directory as it should. */ + rewinddir(pDir->pDir); + pDir->fDataUnread = false; + + return VINF_SUCCESS; +} + + +RTDECL(int) RTDirRename(const char *pszSrc, const char *pszDst, unsigned fRename) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + AssertMsgReturn(*pszSrc, ("%p\n", pszSrc), VERR_INVALID_PARAMETER); + AssertMsgReturn(*pszDst, ("%p\n", pszDst), VERR_INVALID_PARAMETER); + AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER); + + /* + * Take common cause with RTPathRename. + */ + int rc = rtPathPosixRename(pszSrc, pszDst, fRename, RTFS_TYPE_DIRECTORY); + + LogFlow(("RTDirRename(%p:{%s}, %p:{%s}): returns %Rrc\n", + pszSrc, pszSrc, pszDst, pszDst, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/env-posix.cpp b/src/VBox/Runtime/r3/posix/env-posix.cpp new file mode 100644 index 00000000..60a9112e --- /dev/null +++ b/src/VBox/Runtime/r3/posix/env-posix.cpp @@ -0,0 +1,179 @@ +/* $Id: env-posix.cpp $ */ +/** @file + * IPRT - Environment, Posix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_DARWIN +/* pick the correct prototype for unsetenv. */ +# define _POSIX_C_SOURCE 1 +#endif +#include <iprt/env.h> + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#if defined(DEBUG) && defined(RT_OS_LINUX) +# include <iprt/asm.h> +#endif +#include <iprt/err.h> +#include <iprt/string.h> + +#include <stdlib.h> +#include <errno.h> + +#include "internal/alignmentchecks.h" + + +RTDECL(bool) RTEnvExistsBad(const char *pszVar) +{ + AssertReturn(strchr(pszVar, '=') == NULL, false); + return RTEnvGetBad(pszVar) != NULL; +} + + +RTDECL(bool) RTEnvExist(const char *pszVar) +{ + return RTEnvExistsBad(pszVar); +} + + +RTDECL(const char *) RTEnvGetBad(const char *pszVar) +{ + AssertReturn(strchr(pszVar, '=') == NULL, NULL); + + IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc causes trouble */ + const char *pszValue = getenv(pszVar); + IPRT_ALIGNMENT_CHECKS_ENABLE(); + return pszValue; +} + + +RTDECL(const char *) RTEnvGet(const char *pszVar) +{ + return RTEnvGetBad(pszVar); +} + + +RTDECL(int) RTEnvPutBad(const char *pszVarEqualValue) +{ + /** @todo putenv is a source memory leaks. deal with this on a per system basis. */ + if (!putenv((char *)pszVarEqualValue)) + return 0; + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTEnvPut(const char *pszVarEqualValue) +{ + return RTEnvPutBad(pszVarEqualValue); +} + + +RTDECL(int) RTEnvSetBad(const char *pszVar, const char *pszValue) +{ + AssertMsgReturn(strchr(pszVar, '=') == NULL, ("'%s'\n", pszVar), VERR_ENV_INVALID_VAR_NAME); + +#if defined(_MSC_VER) + /* make a local copy and feed it to putenv. */ + const size_t cchVar = strlen(pszVar); + const size_t cchValue = strlen(pszValue); + char *pszTmp = (char *)alloca(cchVar + cchValue + 2 + !*pszValue); + memcpy(pszTmp, pszVar, cchVar); + pszTmp[cchVar] = '='; + if (*pszValue) + memcpy(pszTmp + cchVar + 1, pszValue, cchValue + 1); + else + { + pszTmp[cchVar + 1] = ' '; /* wrong, but putenv will remove it otherwise. */ + pszTmp[cchVar + 2] = '\0'; + } + + if (!putenv(pszTmp)) + return 0; + return RTErrConvertFromErrno(errno); + +#else + if (!setenv(pszVar, pszValue, 1)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +#endif +} + + +RTDECL(int) RTEnvSet(const char *pszVar, const char *pszValue) +{ + return RTEnvSetBad(pszVar, pszValue); +} + +RTDECL(int) RTEnvUnsetBad(const char *pszVar) +{ + AssertReturn(strchr(pszVar, '=') == NULL, VERR_ENV_INVALID_VAR_NAME); + + /* + * Check that it exists first. + */ + if (!RTEnvExist(pszVar)) + return VINF_ENV_VAR_NOT_FOUND; + + /* + * Ok, try remove it. + */ +#ifdef RT_OS_WINDOWS + /* Use putenv(var=) since Windows does not have unsetenv(). */ + size_t cchVar = strlen(pszVar); + char *pszBuf = (char *)alloca(cchVar + 2); + memcpy(pszBuf, pszVar, cchVar); + pszBuf[cchVar] = '='; + pszBuf[cchVar + 1] = '\0'; + + if (!putenv(pszBuf)) + return VINF_SUCCESS; + +#else + /* This is the preferred function as putenv() like used above does neither work on Solaris nor on Darwin. */ + if (!unsetenv((char*)pszVar)) + return VINF_SUCCESS; +#endif + + return RTErrConvertFromErrno(errno); +} + +RTDECL(int) RTEnvUnset(const char *pszVar) +{ + return RTEnvUnsetBad(pszVar); +} + diff --git a/src/VBox/Runtime/r3/posix/errvars-posix.cpp b/src/VBox/Runtime/r3/posix/errvars-posix.cpp new file mode 100644 index 00000000..b6a98709 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/errvars-posix.cpp @@ -0,0 +1,88 @@ +/* $Id: errvars-posix.cpp $ */ +/** @file + * IPRT - Save and Restore Error Variables, POSIX Ring-3. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <netdb.h> +#include <errno.h> + +#include <iprt/errcore.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include "internal/magics.h" + + + +RTDECL(PRTERRVARS) RTErrVarsSave(PRTERRVARS pVars) +{ + pVars->ai32Vars[0] = RTERRVARS_MAGIC; + pVars->ai32Vars[1] = errno; + pVars->ai32Vars[2] = h_errno; + return pVars; +} + + +RTDECL(void) RTErrVarsRestore(PCRTERRVARS pVars) +{ + AssertReturnVoid(pVars->ai32Vars[0] == RTERRVARS_MAGIC); + h_errno = pVars->ai32Vars[2]; + errno = pVars->ai32Vars[1]; +} + + +RTDECL(bool) RTErrVarsAreEqual(PCRTERRVARS pVars1, PCRTERRVARS pVars2) +{ + Assert(pVars1->ai32Vars[0] == RTERRVARS_MAGIC); + Assert(pVars2->ai32Vars[0] == RTERRVARS_MAGIC); + + return pVars1->ai32Vars[0] == pVars2->ai32Vars[0] + && pVars1->ai32Vars[1] == pVars2->ai32Vars[1] + && pVars1->ai32Vars[2] == pVars2->ai32Vars[2]; +} + + +RTDECL(bool) RTErrVarsHaveChanged(PCRTERRVARS pVars) +{ + Assert(pVars->ai32Vars[0] == RTERRVARS_MAGIC); + + return pVars->ai32Vars[0] != RTERRVARS_MAGIC + || pVars->ai32Vars[1] != errno + || pVars->ai32Vars[2] != h_errno; +} + diff --git a/src/VBox/Runtime/r3/posix/fileaio-posix.cpp b/src/VBox/Runtime/r3/posix/fileaio-posix.cpp new file mode 100644 index 00000000..befa49fb --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fileaio-posix.cpp @@ -0,0 +1,1071 @@ +/* $Id: fileaio-posix.cpp $ */ +/** @file + * IPRT - File async I/O, native implementation for POSIX compliant host platforms. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/asm.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include "internal/fileaio.h" + +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) +# include <sys/types.h> +# include <sys/sysctl.h> /* for sysctlbyname */ +#endif +#if defined(RT_OS_FREEBSD) +# include <fcntl.h> /* O_SYNC */ +#endif +#include <aio.h> +#include <errno.h> +#include <time.h> + +/* + * Linux does not define this value. + * Just define it with really big + * value. + */ +#ifndef AIO_LISTIO_MAX +# define AIO_LISTIO_MAX UINT32_MAX +#endif + +#if 0 /* Only used for debugging */ +# undef AIO_LISTIO_MAX +# define AIO_LISTIO_MAX 16 +#endif + +/** Invalid entry in the waiting array. */ +#define RTFILEAIOCTX_WAIT_ENTRY_INVALID (~0U) + +/** No-op replacement for rtFileAioCtxDump for non debug builds */ +#ifndef LOG_ENABLED +# define rtFileAioCtxDump(pCtxInt) do {} while (0) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Async I/O request state. + */ +typedef struct RTFILEAIOREQINTERNAL +{ + /** The aio control block. FIRST ELEMENT! */ + struct aiocb AioCB; + /** Next element in the chain. */ + struct RTFILEAIOREQINTERNAL *pNext; + /** Previous element in the chain. */ + struct RTFILEAIOREQINTERNAL *pPrev; + /** Current state the request is in. */ + RTFILEAIOREQSTATE enmState; + /** Flag whether this is a flush request. */ + bool fFlush; + /** Flag indicating if the request was canceled. */ + volatile bool fCanceled; + /** Opaque user data. */ + void *pvUser; + /** Number of bytes actually transferred. */ + size_t cbTransfered; + /** Status code. */ + int Rc; + /** Completion context we are assigned to. */ + struct RTFILEAIOCTXINTERNAL *pCtxInt; + /** Entry in the waiting list the request is in. */ + unsigned iWaitingList; + /** Magic value (RTFILEAIOREQ_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOREQINTERNAL, *PRTFILEAIOREQINTERNAL; + +/** + * Async I/O completion context state. + */ +typedef struct RTFILEAIOCTXINTERNAL +{ + /** Current number of requests active on this context. */ + volatile int32_t cRequests; + /** Maximum number of requests this context can handle. */ + uint32_t cMaxRequests; + /** The ID of the thread which is currently waiting for requests. */ + volatile RTTHREAD hThreadWait; + /** Flag whether the thread was woken up. */ + volatile bool fWokenUp; + /** Flag whether the thread is currently waiting in the syscall. */ + volatile bool fWaiting; + /** Flags given during creation. */ + uint32_t fFlags; + /** Magic value (RTFILEAIOCTX_MAGIC). */ + uint32_t u32Magic; + /** Flag whether the thread was woken up due to a internal event. */ + volatile bool fWokenUpInternal; + /** List of new requests which needs to be inserted into apReqs by the + * waiting thread. */ + volatile PRTFILEAIOREQINTERNAL apReqsNewHead[5]; + /** Special entry for requests which are canceled. Because only one + * request can be canceled at a time and the thread canceling the request + * has to wait we need only one entry. */ + volatile PRTFILEAIOREQINTERNAL pReqToCancel; + /** Event semaphore the canceling thread is waiting for completion of + * the operation. */ + RTSEMEVENT SemEventCancel; + /** Head of submitted elements waiting to get into the array. */ + PRTFILEAIOREQINTERNAL pReqsWaitHead; + /** Tail of submitted elements waiting to get into the array. */ + PRTFILEAIOREQINTERNAL pReqsWaitTail; + /** Maximum number of elements in the waiting array. */ + unsigned cReqsWaitMax; + /** First free slot in the waiting list. */ + unsigned iFirstFree; + /** List of requests we are currently waiting on. + * Size depends on cMaxRequests and AIO_LISTIO_MAX. */ + volatile PRTFILEAIOREQINTERNAL apReqs[1]; +} RTFILEAIOCTXINTERNAL, *PRTFILEAIOCTXINTERNAL; + +/** + * Internal worker for waking up the waiting thread. + */ +static void rtFileAioCtxWakeup(PRTFILEAIOCTXINTERNAL pCtxInt) +{ + /* + * Read the thread handle before the status flag. + * If we read the handle after the flag we might + * end up with an invalid handle because the thread + * waiting in RTFileAioCtxWakeup() might get scheduled + * before we read the flag and returns. + * We can ensure that the handle is valid if fWaiting is true + * when reading the handle before the status flag. + */ + RTTHREAD hThread; + ASMAtomicReadHandle(&pCtxInt->hThreadWait, &hThread); + bool fWaiting = ASMAtomicReadBool(&pCtxInt->fWaiting); + if (fWaiting) + { + /* + * If a thread waits the handle must be valid. + * It is possible that the thread returns from + * aio_suspend() before the signal is send. + * This is no problem because we already set fWokenUp + * to true which will let the thread return VERR_INTERRUPTED + * and the next call to RTFileAioCtxWait() will not + * return VERR_INTERRUPTED because signals are not saved + * and will simply vanish if the destination thread can't + * receive it. + */ + Assert(hThread != NIL_RTTHREAD); + RTThreadPoke(hThread); + } +} + +/** + * Internal worker processing events and inserting new requests into the waiting list. + */ +static int rtFileAioCtxProcessEvents(PRTFILEAIOCTXINTERNAL pCtxInt) +{ + int rc = VINF_SUCCESS; + + /* Process new requests first. */ + bool fWokenUp = ASMAtomicXchgBool(&pCtxInt->fWokenUpInternal, false); + if (fWokenUp) + { + for (unsigned iSlot = 0; iSlot < RT_ELEMENTS(pCtxInt->apReqsNewHead); iSlot++) + { + PRTFILEAIOREQINTERNAL pReqHead = ASMAtomicXchgPtrT(&pCtxInt->apReqsNewHead[iSlot], NULL, PRTFILEAIOREQINTERNAL); + + while ( (pCtxInt->iFirstFree < pCtxInt->cReqsWaitMax) + && pReqHead) + { + RTFIELAIOREQ_ASSERT_STATE(pReqHead, SUBMITTED); + pCtxInt->apReqs[pCtxInt->iFirstFree] = pReqHead; + pReqHead->iWaitingList = pCtxInt->iFirstFree; + pReqHead = pReqHead->pNext; + + /* Clear pointer to next and previous element just for safety. */ + pCtxInt->apReqs[pCtxInt->iFirstFree]->pNext = NULL; + pCtxInt->apReqs[pCtxInt->iFirstFree]->pPrev = NULL; + pCtxInt->iFirstFree++; + + Assert( (pCtxInt->iFirstFree <= pCtxInt->cMaxRequests) + && (pCtxInt->iFirstFree <= pCtxInt->cReqsWaitMax)); + } + + /* Append the rest to the wait list. */ + if (pReqHead) + { + RTFIELAIOREQ_ASSERT_STATE(pReqHead, SUBMITTED); + if (!pCtxInt->pReqsWaitHead) + { + Assert(!pCtxInt->pReqsWaitTail); + pCtxInt->pReqsWaitHead = pReqHead; + pReqHead->pPrev = NULL; + } + else + { + AssertPtr(pCtxInt->pReqsWaitTail); + + pCtxInt->pReqsWaitTail->pNext = pReqHead; + pReqHead->pPrev = pCtxInt->pReqsWaitTail; + } + + /* Update tail. */ + while (pReqHead->pNext) + { + RTFIELAIOREQ_ASSERT_STATE(pReqHead->pNext, SUBMITTED); + pReqHead = pReqHead->pNext; + } + + pCtxInt->pReqsWaitTail = pReqHead; + pCtxInt->pReqsWaitTail->pNext = NULL; + } + } + + /* Check if a request needs to be canceled. */ + PRTFILEAIOREQINTERNAL pReqToCancel = ASMAtomicReadPtrT(&pCtxInt->pReqToCancel, PRTFILEAIOREQINTERNAL); + if (pReqToCancel) + { + /* The request can be in the array waiting for completion or still in the list because it is full. */ + if (pReqToCancel->iWaitingList != RTFILEAIOCTX_WAIT_ENTRY_INVALID) + { + /* Put it out of the waiting list. */ + pCtxInt->apReqs[pReqToCancel->iWaitingList] = pCtxInt->apReqs[--pCtxInt->iFirstFree]; + pCtxInt->apReqs[pReqToCancel->iWaitingList]->iWaitingList = pReqToCancel->iWaitingList; + } + else + { + /* Unlink from the waiting list. */ + PRTFILEAIOREQINTERNAL pPrev = pReqToCancel->pPrev; + PRTFILEAIOREQINTERNAL pNext = pReqToCancel->pNext; + + if (pNext) + pNext->pPrev = pPrev; + else + { + /* We canceled the tail. */ + pCtxInt->pReqsWaitTail = pPrev; + } + + if (pPrev) + pPrev->pNext = pNext; + else + { + /* We canceled the head. */ + pCtxInt->pReqsWaitHead = pNext; + } + } + + ASMAtomicDecS32(&pCtxInt->cRequests); + AssertMsg(pCtxInt->cRequests >= 0, ("Canceled request not which is not in this context\n")); + RTSemEventSignal(pCtxInt->SemEventCancel); + } + } + else + { + if (ASMAtomicXchgBool(&pCtxInt->fWokenUp, false)) + rc = VERR_INTERRUPTED; + } + + return rc; +} + +RTR3DECL(int) RTFileAioGetLimits(PRTFILEAIOLIMITS pAioLimits) +{ + int rcBSD = 0; + AssertPtrReturn(pAioLimits, VERR_INVALID_POINTER); + +#if defined(RT_OS_DARWIN) + int cReqsOutstandingMax = 0; + size_t cbParameter = sizeof(int); + + rcBSD = sysctlbyname("kern.aioprocmax", /* name */ + &cReqsOutstandingMax, /* Where to store the old value. */ + &cbParameter, /* Size of the memory pointed to. */ + NULL, /* Where the new value is located. */ + 0); /* Where the size of the new value is stored. */ + if (rcBSD == -1) + return RTErrConvertFromErrno(errno); + + pAioLimits->cReqsOutstandingMax = cReqsOutstandingMax; + pAioLimits->cbBufferAlignment = 0; +#elif defined(RT_OS_FREEBSD) + /* + * The AIO API is implemented in a kernel module which is not + * loaded by default. + * If it is loaded there are additional sysctl parameters. + */ + int cReqsOutstandingMax = 0; + size_t cbParameter = sizeof(int); + + rcBSD = sysctlbyname("vfs.aio.max_aio_per_proc", /* name */ + &cReqsOutstandingMax, /* Where to store the old value. */ + &cbParameter, /* Size of the memory pointed to. */ + NULL, /* Where the new value is located. */ + 0); /* Where the size of the new value is stored. */ + if (rcBSD == -1) + { + /* ENOENT means the value is unknown thus the module is not loaded. */ + if (errno == ENOENT) + return VERR_NOT_SUPPORTED; + else + return RTErrConvertFromErrno(errno); + } + + pAioLimits->cReqsOutstandingMax = cReqsOutstandingMax; + pAioLimits->cbBufferAlignment = 0; +#else + pAioLimits->cReqsOutstandingMax = RTFILEAIO_UNLIMITED_REQS; + pAioLimits->cbBufferAlignment = 0; +#endif + + return VINF_SUCCESS; +} + +RTR3DECL(int) RTFileAioReqCreate(PRTFILEAIOREQ phReq) +{ + AssertPtrReturn(phReq, VERR_INVALID_POINTER); + + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOREQINTERNAL)); + if (RT_UNLIKELY(!pReqInt)) + return VERR_NO_MEMORY; + + pReqInt->pCtxInt = NULL; + pReqInt->u32Magic = RTFILEAIOREQ_MAGIC; + pReqInt->iWaitingList = RTFILEAIOCTX_WAIT_ENTRY_INVALID; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + *phReq = (RTFILEAIOREQ)pReqInt; + + return VINF_SUCCESS; +} + + +RTDECL(int) RTFileAioReqDestroy(RTFILEAIOREQ hReq) +{ + /* + * Validate the handle and ignore nil. + */ + if (hReq == NIL_RTFILEAIOREQ) + return VINF_SUCCESS; + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + + /* + * Trash the magic and free it. + */ + ASMAtomicUoWriteU32(&pReqInt->u32Magic, ~RTFILEAIOREQ_MAGIC); + RTMemFree(pReqInt); + return VINF_SUCCESS; +} + +/** + * Worker setting up the request. + */ +DECLINLINE(int) rtFileAioReqPrepareTransfer(RTFILEAIOREQ hReq, RTFILE hFile, + unsigned uTransferDirection, + RTFOFF off, void *pvBuf, size_t cbTransfer, + void *pvUser) +{ + /* + * Validate the input. + */ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + Assert(hFile != NIL_RTFILE); + AssertPtr(pvBuf); + Assert(off >= 0); + Assert(cbTransfer > 0); + + memset(&pReqInt->AioCB, 0, sizeof(struct aiocb)); + pReqInt->fFlush = false; + pReqInt->AioCB.aio_lio_opcode = uTransferDirection; + pReqInt->AioCB.aio_fildes = RTFileToNative(hFile); + pReqInt->AioCB.aio_offset = off; + pReqInt->AioCB.aio_nbytes = cbTransfer; + pReqInt->AioCB.aio_buf = pvBuf; + pReqInt->pvUser = pvUser; + pReqInt->pCtxInt = NULL; + pReqInt->Rc = VERR_FILE_AIO_IN_PROGRESS; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTFileAioReqPrepareRead(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void *pvBuf, size_t cbRead, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LIO_READ, + off, pvBuf, cbRead, pvUser); +} + + +RTDECL(int) RTFileAioReqPrepareWrite(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void const *pvBuf, size_t cbWrite, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LIO_WRITE, + off, (void *)pvBuf, cbWrite, pvUser); +} + + +RTDECL(int) RTFileAioReqPrepareFlush(RTFILEAIOREQ hReq, RTFILE hFile, void *pvUser) +{ + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)hReq; + + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + Assert(hFile != NIL_RTFILE); + + pReqInt->fFlush = true; + pReqInt->AioCB.aio_fildes = RTFileToNative(hFile); + pReqInt->AioCB.aio_offset = 0; + pReqInt->AioCB.aio_nbytes = 0; + pReqInt->AioCB.aio_buf = NULL; + pReqInt->pvUser = pvUser; + pReqInt->Rc = VERR_FILE_AIO_IN_PROGRESS; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + + return VINF_SUCCESS; +} + + +RTDECL(void *) RTFileAioReqGetUser(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN_RC(pReqInt, NULL); + + return pReqInt->pvUser; +} + + +RTDECL(int) RTFileAioReqCancel(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_NOT_SUBMITTED); + + ASMAtomicXchgBool(&pReqInt->fCanceled, true); + + int rcPosix = aio_cancel(pReqInt->AioCB.aio_fildes, &pReqInt->AioCB); + + if (rcPosix == AIO_CANCELED) + { + PRTFILEAIOCTXINTERNAL pCtxInt = pReqInt->pCtxInt; + /* + * Notify the waiting thread that the request was canceled. + */ + AssertMsg(RT_VALID_PTR(pCtxInt), ("Invalid state. Request was canceled but wasn't submitted\n")); + + Assert(!pCtxInt->pReqToCancel); + ASMAtomicWritePtr(&pCtxInt->pReqToCancel, pReqInt); + rtFileAioCtxWakeup(pCtxInt); + + /* Wait for acknowledge. */ + int rc = RTSemEventWait(pCtxInt->SemEventCancel, RT_INDEFINITE_WAIT); + AssertRC(rc); + + ASMAtomicWriteNullPtr(&pCtxInt->pReqToCancel); + pReqInt->Rc = VERR_FILE_AIO_CANCELED; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + return VINF_SUCCESS; + } + else if (rcPosix == AIO_ALLDONE) + return VERR_FILE_AIO_COMPLETED; + else if (rcPosix == AIO_NOTCANCELED) + return VERR_FILE_AIO_IN_PROGRESS; + else + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTFileAioReqGetRC(RTFILEAIOREQ hReq, size_t *pcbTransfered) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, PREPARED, VERR_FILE_AIO_NOT_SUBMITTED); + AssertPtrNull(pcbTransfered); + + if ( (RT_SUCCESS(pReqInt->Rc)) + && (pcbTransfered)) + *pcbTransfered = pReqInt->cbTransfered; + + return pReqInt->Rc; +} + + +RTDECL(int) RTFileAioCtxCreate(PRTFILEAIOCTX phAioCtx, uint32_t cAioReqsMax, + uint32_t fFlags) +{ + PRTFILEAIOCTXINTERNAL pCtxInt; + unsigned cReqsWaitMax; + + AssertPtrReturn(phAioCtx, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTFILEAIOCTX_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + if (cAioReqsMax == RTFILEAIO_UNLIMITED_REQS) + return VERR_OUT_OF_RANGE; + + cReqsWaitMax = RT_MIN(cAioReqsMax, AIO_LISTIO_MAX); + + pCtxInt = (PRTFILEAIOCTXINTERNAL)RTMemAllocZ( sizeof(RTFILEAIOCTXINTERNAL) + + cReqsWaitMax * sizeof(PRTFILEAIOREQINTERNAL)); + if (RT_UNLIKELY(!pCtxInt)) + return VERR_NO_MEMORY; + + /* Create event semaphore. */ + int rc = RTSemEventCreate(&pCtxInt->SemEventCancel); + if (RT_FAILURE(rc)) + { + RTMemFree(pCtxInt); + return rc; + } + + pCtxInt->u32Magic = RTFILEAIOCTX_MAGIC; + pCtxInt->cMaxRequests = cAioReqsMax; + pCtxInt->cReqsWaitMax = cReqsWaitMax; + pCtxInt->fFlags = fFlags; + *phAioCtx = (RTFILEAIOCTX)pCtxInt; + + return VINF_SUCCESS; +} + + +RTDECL(int) RTFileAioCtxDestroy(RTFILEAIOCTX hAioCtx) +{ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + + AssertPtrReturn(pCtxInt, VERR_INVALID_HANDLE); + + if (RT_UNLIKELY(pCtxInt->cRequests)) + return VERR_FILE_AIO_BUSY; + + RTSemEventDestroy(pCtxInt->SemEventCancel); + RTMemFree(pCtxInt); + + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTFileAioCtxGetMaxReqCount(RTFILEAIOCTX hAioCtx) +{ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + + if (hAioCtx == NIL_RTFILEAIOCTX) + return RTFILEAIO_UNLIMITED_REQS; + return pCtxInt->cMaxRequests; +} + +RTDECL(int) RTFileAioCtxAssociateWithFile(RTFILEAIOCTX hAioCtx, RTFILE hFile) +{ + NOREF(hAioCtx); NOREF(hFile); + return VINF_SUCCESS; +} + +#ifdef LOG_ENABLED +/** + * Dumps the state of a async I/O context. + */ +static void rtFileAioCtxDump(PRTFILEAIOCTXINTERNAL pCtxInt) +{ + LogFlow(("cRequests=%d\n", pCtxInt->cRequests)); + LogFlow(("cMaxRequests=%u\n", pCtxInt->cMaxRequests)); + LogFlow(("hThreadWait=%#p\n", pCtxInt->hThreadWait)); + LogFlow(("fWokenUp=%RTbool\n", pCtxInt->fWokenUp)); + LogFlow(("fWaiting=%RTbool\n", pCtxInt->fWaiting)); + LogFlow(("fWokenUpInternal=%RTbool\n", pCtxInt->fWokenUpInternal)); + for (unsigned i = 0; i < RT_ELEMENTS(pCtxInt->apReqsNewHead); i++) + LogFlow(("apReqsNewHead[%u]=%#p\n", i, pCtxInt->apReqsNewHead[i])); + LogFlow(("pReqToCancel=%#p\n", pCtxInt->pReqToCancel)); + LogFlow(("pReqsWaitHead=%#p\n", pCtxInt->pReqsWaitHead)); + LogFlow(("pReqsWaitTail=%#p\n", pCtxInt->pReqsWaitTail)); + LogFlow(("cReqsWaitMax=%u\n", pCtxInt->cReqsWaitMax)); + LogFlow(("iFirstFree=%u\n", pCtxInt->iFirstFree)); + for (unsigned i = 0; i < pCtxInt->cReqsWaitMax; i++) + LogFlow(("apReqs[%u]=%#p\n", i, pCtxInt->apReqs[i])); +} +#endif + +RTDECL(int) RTFileAioCtxSubmit(RTFILEAIOCTX hAioCtx, PRTFILEAIOREQ pahReqs, size_t cReqs) +{ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + + /* Parameter checks */ + AssertPtrReturn(pCtxInt, VERR_INVALID_HANDLE); + AssertReturn(cReqs != 0, VERR_INVALID_POINTER); + AssertPtrReturn(pahReqs, VERR_INVALID_PARAMETER); + + rtFileAioCtxDump(pCtxInt); + + /* Check that we don't exceed the limit */ + if (ASMAtomicUoReadS32(&pCtxInt->cRequests) + cReqs > pCtxInt->cMaxRequests) + return VERR_FILE_AIO_LIMIT_EXCEEDED; + + PRTFILEAIOREQINTERNAL pHead = NULL; + + do + { + int rcPosix = 0; + size_t cReqsSubmit = 0; + size_t i = 0; + PRTFILEAIOREQINTERNAL pReqInt; + + while ( (i < cReqs) + && (i < AIO_LISTIO_MAX)) + { + pReqInt = pahReqs[i]; + if (RTFILEAIOREQ_IS_NOT_VALID(pReqInt)) + { + /* Undo everything and stop submitting. */ + for (size_t iUndo = 0; iUndo < i; iUndo++) + { + pReqInt = pahReqs[iUndo]; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + pReqInt->pCtxInt = NULL; + + /* Unlink from the list again. */ + PRTFILEAIOREQINTERNAL pNext, pPrev; + pNext = pReqInt->pNext; + pPrev = pReqInt->pPrev; + if (pNext) + pNext->pPrev = pPrev; + if (pPrev) + pPrev->pNext = pNext; + else + pHead = pNext; + } + rc = VERR_INVALID_HANDLE; + break; + } + + pReqInt->pCtxInt = pCtxInt; + + if (pReqInt->fFlush) + break; + + /* Link them together. */ + pReqInt->pNext = pHead; + if (pHead) + pHead->pPrev = pReqInt; + pReqInt->pPrev = NULL; + pHead = pReqInt; + RTFILEAIOREQ_SET_STATE(pReqInt, SUBMITTED); + + cReqsSubmit++; + i++; + } + + if (cReqsSubmit) + { + rcPosix = lio_listio(LIO_NOWAIT, (struct aiocb **)pahReqs, cReqsSubmit, NULL); + if (RT_UNLIKELY(rcPosix < 0)) + { + size_t cReqsSubmitted = cReqsSubmit; + + if (errno == EAGAIN) + rc = VERR_FILE_AIO_INSUFFICIENT_RESSOURCES; + else + rc = RTErrConvertFromErrno(errno); + + /* Check which ones were not submitted. */ + for (i = 0; i < cReqsSubmit; i++) + { + pReqInt = pahReqs[i]; + + rcPosix = aio_error(&pReqInt->AioCB); + + if ((rcPosix != EINPROGRESS) && (rcPosix != 0)) + { + cReqsSubmitted--; + +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + if (errno == EINVAL) +#else + if (rcPosix == EINVAL) +#endif + { + /* Was not submitted. */ + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + } + else + { + /* An error occurred. */ + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + /* + * Looks like Apple and glibc interpret the standard in different ways. + * glibc returns the error code which would be in errno but Apple returns + * -1 and sets errno to the appropriate value + */ +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + Assert(rcPosix == -1); + pReqInt->Rc = RTErrConvertFromErrno(errno); +#elif defined(RT_OS_LINUX) + pReqInt->Rc = RTErrConvertFromErrno(rcPosix); +#endif + pReqInt->cbTransfered = 0; + } + /* Unlink from the list. */ + PRTFILEAIOREQINTERNAL pNext, pPrev; + pNext = pReqInt->pNext; + pPrev = pReqInt->pPrev; + if (pNext) + pNext->pPrev = pPrev; + if (pPrev) + pPrev->pNext = pNext; + else + pHead = pNext; + + pReqInt->pNext = NULL; + pReqInt->pPrev = NULL; + } + } + ASMAtomicAddS32(&pCtxInt->cRequests, cReqsSubmitted); + AssertMsg(pCtxInt->cRequests >= 0, ("Adding requests resulted in overflow\n")); + break; + } + + ASMAtomicAddS32(&pCtxInt->cRequests, cReqsSubmit); + AssertMsg(pCtxInt->cRequests >= 0, ("Adding requests resulted in overflow\n")); + cReqs -= cReqsSubmit; + pahReqs += cReqsSubmit; + } + + /* + * Check if we have a flush request now. + * If not we hit the AIO_LISTIO_MAX limit + * and will continue submitting requests + * above. + */ + if (cReqs && RT_SUCCESS_NP(rc)) + { + pReqInt = pahReqs[0]; + + if (pReqInt->fFlush) + { + /* + * lio_listio does not work with flush requests so + * we have to use aio_fsync directly. + */ + rcPosix = aio_fsync(O_SYNC, &pReqInt->AioCB); + if (RT_UNLIKELY(rcPosix < 0)) + { + if (errno == EAGAIN) + { + rc = VERR_FILE_AIO_INSUFFICIENT_RESSOURCES; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + } + else + { + rc = RTErrConvertFromErrno(errno); + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + pReqInt->Rc = rc; + } + pReqInt->cbTransfered = 0; + break; + } + + /* Link them together. */ + pReqInt->pNext = pHead; + if (pHead) + pHead->pPrev = pReqInt; + pReqInt->pPrev = NULL; + pHead = pReqInt; + RTFILEAIOREQ_SET_STATE(pReqInt, SUBMITTED); + + ASMAtomicIncS32(&pCtxInt->cRequests); + AssertMsg(pCtxInt->cRequests >= 0, ("Adding requests resulted in overflow\n")); + cReqs--; + pahReqs++; + } + } + } while ( cReqs + && RT_SUCCESS_NP(rc)); + + if (pHead) + { + /* + * Forward successfully submitted requests to the thread waiting for requests. + * We search for a free slot first and if we don't find one + * we will grab the first one and append our list to the existing entries. + */ + unsigned iSlot = 0; + while ( (iSlot < RT_ELEMENTS(pCtxInt->apReqsNewHead)) + && !ASMAtomicCmpXchgPtr(&pCtxInt->apReqsNewHead[iSlot], pHead, NULL)) + iSlot++; + + if (iSlot == RT_ELEMENTS(pCtxInt->apReqsNewHead)) + { + /* Nothing found. */ + PRTFILEAIOREQINTERNAL pOldHead = ASMAtomicXchgPtrT(&pCtxInt->apReqsNewHead[0], NULL, PRTFILEAIOREQINTERNAL); + + /* Find the end of the current head and link the old list to the current. */ + PRTFILEAIOREQINTERNAL pTail = pHead; + while (pTail->pNext) + pTail = pTail->pNext; + + pTail->pNext = pOldHead; + + ASMAtomicWritePtr(&pCtxInt->apReqsNewHead[0], pHead); + } + + /* Set the internal wakeup flag and wakeup the thread if possible. */ + bool fWokenUp = ASMAtomicXchgBool(&pCtxInt->fWokenUpInternal, true); + if (!fWokenUp) + rtFileAioCtxWakeup(pCtxInt); + } + + rtFileAioCtxDump(pCtxInt); + + return rc; +} + + +RTDECL(int) RTFileAioCtxWait(RTFILEAIOCTX hAioCtx, size_t cMinReqs, RTMSINTERVAL cMillies, + PRTFILEAIOREQ pahReqs, size_t cReqs, uint32_t *pcReqs) +{ + int rc = VINF_SUCCESS; + int cRequestsCompleted = 0; + PRTFILEAIOCTXINTERNAL pCtxInt = (PRTFILEAIOCTXINTERNAL)hAioCtx; + struct timespec Timeout; + struct timespec *pTimeout = NULL; + uint64_t StartNanoTS = 0; + + LogFlowFunc(("hAioCtx=%#p cMinReqs=%zu cMillies=%u pahReqs=%#p cReqs=%zu pcbReqs=%#p\n", + hAioCtx, cMinReqs, cMillies, pahReqs, cReqs, pcReqs)); + + /* Check parameters. */ + AssertPtrReturn(pCtxInt, VERR_INVALID_HANDLE); + AssertPtrReturn(pcReqs, VERR_INVALID_POINTER); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + AssertReturn(cReqs != 0, VERR_INVALID_PARAMETER); + AssertReturn(cReqs >= cMinReqs, VERR_OUT_OF_RANGE); + + rtFileAioCtxDump(pCtxInt); + + int32_t cRequestsWaiting = ASMAtomicReadS32(&pCtxInt->cRequests); + + if ( RT_UNLIKELY(cRequestsWaiting <= 0) + && !(pCtxInt->fFlags & RTFILEAIOCTX_FLAGS_WAIT_WITHOUT_PENDING_REQUESTS)) + return VERR_FILE_AIO_NO_REQUEST; + + if (RT_UNLIKELY(cMinReqs > (uint32_t)cRequestsWaiting)) + return VERR_INVALID_PARAMETER; + + if (cMillies != RT_INDEFINITE_WAIT) + { + Timeout.tv_sec = cMillies / 1000; + Timeout.tv_nsec = (cMillies % 1000) * 1000000; + pTimeout = &Timeout; + StartNanoTS = RTTimeNanoTS(); + } + + /* Wait for at least one. */ + if (!cMinReqs) + cMinReqs = 1; + + /* For the wakeup call. */ + Assert(pCtxInt->hThreadWait == NIL_RTTHREAD); + ASMAtomicWriteHandle(&pCtxInt->hThreadWait, RTThreadSelf()); + + /* Update the waiting list once before we enter the loop. */ + rc = rtFileAioCtxProcessEvents(pCtxInt); + + while ( cMinReqs + && RT_SUCCESS_NP(rc)) + { +#ifdef RT_STRICT + if (RT_UNLIKELY(!pCtxInt->iFirstFree)) + { + for (unsigned i = 0; i < pCtxInt->cReqsWaitMax; i++) + RTAssertMsg2Weak("wait[%d] = %#p\n", i, pCtxInt->apReqs[i]); + + AssertMsgFailed(("No request to wait for. pReqsWaitHead=%#p pReqsWaitTail=%#p\n", + pCtxInt->pReqsWaitHead, pCtxInt->pReqsWaitTail)); + } +#endif + + LogFlow(("Waiting for %d requests to complete\n", pCtxInt->iFirstFree)); + rtFileAioCtxDump(pCtxInt); + + ASMAtomicXchgBool(&pCtxInt->fWaiting, true); + int rcPosix = aio_suspend((const struct aiocb * const *)pCtxInt->apReqs, + pCtxInt->iFirstFree, pTimeout); + ASMAtomicXchgBool(&pCtxInt->fWaiting, false); + if (rcPosix < 0) + { + LogFlow(("aio_suspend failed %d nent=%u\n", errno, pCtxInt->iFirstFree)); + /* Check that this is an external wakeup event. */ + if (errno == EINTR) + rc = rtFileAioCtxProcessEvents(pCtxInt); + else + rc = RTErrConvertFromErrno(errno); + } + else + { + /* Requests finished. */ + unsigned iReqCurr = 0; + unsigned cDone = 0; + + /* Remove completed requests from the waiting list. */ + while ( (iReqCurr < pCtxInt->iFirstFree) + && (cDone < cReqs)) + { + PRTFILEAIOREQINTERNAL pReq = pCtxInt->apReqs[iReqCurr]; + int rcReq = aio_error(&pReq->AioCB); + + if (rcReq != EINPROGRESS) + { + /* Completed store the return code. */ + if (rcReq == 0) + { + pReq->Rc = VINF_SUCCESS; + /* Call aio_return() to free resources. */ + pReq->cbTransfered = aio_return(&pReq->AioCB); + } + else + { +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + pReq->Rc = RTErrConvertFromErrno(errno); +#else + pReq->Rc = RTErrConvertFromErrno(rcReq); +#endif + } + + /* Mark the request as finished. */ + RTFILEAIOREQ_SET_STATE(pReq, COMPLETED); + cDone++; + + /* If there are other entries waiting put the head into the now free entry. */ + if (pCtxInt->pReqsWaitHead) + { + PRTFILEAIOREQINTERNAL pReqInsert = pCtxInt->pReqsWaitHead; + + pCtxInt->pReqsWaitHead = pReqInsert->pNext; + if (!pCtxInt->pReqsWaitHead) + { + /* List is empty now. Clear tail too. */ + pCtxInt->pReqsWaitTail = NULL; + } + + pReqInsert->iWaitingList = pReq->iWaitingList; + pCtxInt->apReqs[pReqInsert->iWaitingList] = pReqInsert; + iReqCurr++; + } + else + { + /* + * Move the last entry into the current position to avoid holes + * but only if it is not the last element already. + */ + if (pReq->iWaitingList < pCtxInt->iFirstFree - 1) + { + pCtxInt->apReqs[pReq->iWaitingList] = pCtxInt->apReqs[--pCtxInt->iFirstFree]; + pCtxInt->apReqs[pReq->iWaitingList]->iWaitingList = pReq->iWaitingList; + } + else + pCtxInt->iFirstFree--; + + pCtxInt->apReqs[pCtxInt->iFirstFree] = NULL; + } + + /* Put the request into the completed list. */ + pahReqs[cRequestsCompleted++] = pReq; + pReq->iWaitingList = RTFILEAIOCTX_WAIT_ENTRY_INVALID; + } + else + iReqCurr++; + } + + AssertMsg((cDone <= cReqs), ("Overflow cReqs=%u cMinReqs=%u cDone=%u\n", + cReqs, cDone)); + cReqs -= cDone; + cMinReqs = RT_MAX(cMinReqs, cDone) - cDone; + ASMAtomicSubS32(&pCtxInt->cRequests, cDone); + + AssertMsg(pCtxInt->cRequests >= 0, ("Finished more requests than currently active\n")); + + if (!cMinReqs) + break; + + if (cMillies != RT_INDEFINITE_WAIT) + { + uint64_t TimeDiff; + + /* Recalculate the timeout. */ + TimeDiff = RTTimeSystemNanoTS() - StartNanoTS; + Timeout.tv_sec = Timeout.tv_sec - (TimeDiff / 1000000); + Timeout.tv_nsec = Timeout.tv_nsec - (TimeDiff % 1000000); + } + + /* Check for new elements. */ + rc = rtFileAioCtxProcessEvents(pCtxInt); + } + } + + *pcReqs = cRequestsCompleted; + Assert(pCtxInt->hThreadWait == RTThreadSelf()); + ASMAtomicWriteHandle(&pCtxInt->hThreadWait, NIL_RTTHREAD); + + rtFileAioCtxDump(pCtxInt); + + return rc; +} + + +RTDECL(int) RTFileAioCtxWakeup(RTFILEAIOCTX hAioCtx) +{ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + /** @todo r=bird: Define the protocol for how to resume work after calling + * this function. */ + + bool fWokenUp = ASMAtomicXchgBool(&pCtxInt->fWokenUp, true); + if (!fWokenUp) + rtFileAioCtxWakeup(pCtxInt); + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/posix/fileio-at-posix.cpp b/src/VBox/Runtime/r3/posix/fileio-at-posix.cpp new file mode 100644 index 00000000..72d3cc05 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fileio-at-posix.cpp @@ -0,0 +1,107 @@ +/* $Id: fileio-at-posix.cpp $ */ +/** @file + * IPRT - File I/O, RTFileReadAt and RTFileWriteAt, posix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> + +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/err.h> +#include <iprt/log.h> + + + +RTDECL(int) RTFileReadAt(RTFILE hFile, RTFOFF off, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + ssize_t cbRead = pread(RTFileToNative(hFile), pvBuf, cbToRead, off); + if (cbRead >= 0) + { + if (pcbRead) + /* caller can handle partial read. */ + *pcbRead = cbRead; + else + { + /* Caller expects all to be read. */ + while ((ssize_t)cbToRead > cbRead) + { + ssize_t cbReadPart = pread(RTFileToNative(hFile), (char*)pvBuf + cbRead, cbToRead - cbRead, off + cbRead); + if (cbReadPart <= 0) + { + if (cbReadPart == 0) + return VERR_EOF; + return RTErrConvertFromErrno(errno); + } + cbRead += cbReadPart; + } + } + return VINF_SUCCESS; + } + + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTFileWriteAt(RTFILE hFile, RTFOFF off, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + ssize_t cbWritten = pwrite(RTFileToNative(hFile), pvBuf, cbToWrite, off); + if (cbWritten >= 0) + { + if (pcbWritten) + /* caller can handle partial write. */ + *pcbWritten = cbWritten; + else + { + /* Caller expects all to be write. */ + while ((ssize_t)cbToWrite > cbWritten) + { + ssize_t cbWrittenPart = pwrite(RTFileToNative(hFile), (const char *)pvBuf + cbWritten, cbToWrite - cbWritten, + off + cbWritten); + if (cbWrittenPart < 0) + return cbWrittenPart < 0 ? RTErrConvertFromErrno(errno) : VERR_TRY_AGAIN; + cbWritten += cbWrittenPart; + } + } + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + diff --git a/src/VBox/Runtime/r3/posix/fileio-posix.cpp b/src/VBox/Runtime/r3/posix/fileio-posix.cpp new file mode 100644 index 00000000..cd7d238f --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fileio-posix.cpp @@ -0,0 +1,934 @@ +/* $Id: fileio-posix.cpp $ */ +/** @file + * IPRT - File I/O, POSIX, Part 1. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE + +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#ifdef _MSC_VER +# include <io.h> +# include <stdio.h> +#else +# include <unistd.h> +# include <sys/time.h> +#endif +#ifdef RT_OS_LINUX +# include <sys/file.h> +#endif +#if defined(RT_OS_OS2) && (!defined(__INNOTEK_LIBC__) || __INNOTEK_LIBC__ < 0x006) +# include <io.h> +#endif +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) +# include <sys/disk.h> +#endif +#ifdef RT_OS_SOLARIS +# include <stropts.h> +# include <sys/dkio.h> +# include <sys/vtoc.h> +#endif /* RT_OS_SOLARIS */ + +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/thread.h> +#include "internal/file.h" +#include "internal/fs.h" +#include "internal/path.h" + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Default file permissions for newly created files. */ +#if defined(S_IRUSR) && defined(S_IWUSR) +# define RT_FILE_PERMISSION (S_IRUSR | S_IWUSR) +#else +# define RT_FILE_PERMISSION (00600) +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef O_CLOEXEC +static int volatile g_fHave_O_CLOEXEC = 0; /* {-1,0,1}; since Linux 2.6.23 */ +#endif + + + +RTDECL(bool) RTFileExists(const char *pszPath) +{ + bool fRc = false; + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat s; + fRc = !stat(pszNativePath, &s) + && S_ISREG(s.st_mode); + + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTFileExists(%p={%s}): returns %RTbool\n", pszPath, pszPath, fRc)); + return fRc; +} + + +#ifdef O_CLOEXEC +/** Worker for RTFileOpenEx that detects whether the kernel supports + * O_CLOEXEC or not, setting g_fHave_O_CLOEXEC to 1 or -1 accordingly. */ +static int rtFileOpenExDetectCloExecSupport(void) +{ + /* + * Open /dev/null with O_CLOEXEC and see if FD_CLOEXEC is set or not. + */ + int fHave_O_CLOEXEC = -1; + int fd = open("/dev/null", O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) + { + int fFlags = fcntl(fd, F_GETFD, 0); + fHave_O_CLOEXEC = fFlags > 0 && (fFlags & FD_CLOEXEC) ? 1 : -1; + close(fd); + } + else + AssertMsg(errno == EINVAL, ("%d\n", errno)); + g_fHave_O_CLOEXEC = fHave_O_CLOEXEC; + return fHave_O_CLOEXEC; +} +#endif + + +RTR3DECL(int) RTFileOpen(PRTFILE pFile, const char *pszFilename, uint64_t fOpen) +{ + return RTFileOpenEx(pszFilename, fOpen, pFile, NULL); +} + + +RTDECL(int) RTFileOpenEx(const char *pszFilename, uint64_t fOpen, PRTFILE phFile, PRTFILEACTION penmActionTaken) +{ + /* + * Validate input. + */ + AssertPtrReturn(phFile, VERR_INVALID_POINTER); + *phFile = NIL_RTFILE; + if (penmActionTaken) + *penmActionTaken = RTFILEACTION_INVALID; + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + + /* + * Merge forced open flags and validate them. + */ + int rc = rtFileRecalcAndValidateFlags(&fOpen); + if (RT_FAILURE(rc)) + return rc; +#ifndef O_NONBLOCK + AssertReturn(!(fOpen & RTFILE_O_NON_BLOCK), VERR_INVALID_FLAGS); +#endif +#if defined(RT_OS_OS2) /* Cannot delete open files on OS/2. */ + AssertReturn(!(fOpen & RTFILE_O_TEMP_AUTO_DELETE), VERR_NOT_SUPPORTED); +#endif + + /* + * Calculate open mode flags. + */ + int fOpenMode = 0; +#ifdef O_BINARY + fOpenMode |= O_BINARY; /* (pc) */ +#endif +#ifdef O_LARGEFILE + fOpenMode |= O_LARGEFILE; /* (linux, solaris) */ +#endif +#ifdef O_NOINHERIT + if (!(fOpen & RTFILE_O_INHERIT)) + fOpenMode |= O_NOINHERIT; +#endif +#ifdef O_CLOEXEC + int fHave_O_CLOEXEC = g_fHave_O_CLOEXEC; + if ( !(fOpen & RTFILE_O_INHERIT) + && ( fHave_O_CLOEXEC > 0 + || ( fHave_O_CLOEXEC == 0 + && (fHave_O_CLOEXEC = rtFileOpenExDetectCloExecSupport()) > 0))) + fOpenMode |= O_CLOEXEC; +#endif +#ifdef O_NONBLOCK + if (fOpen & RTFILE_O_NON_BLOCK) + fOpenMode |= O_NONBLOCK; +#endif +#ifdef O_SYNC + if (fOpen & RTFILE_O_WRITE_THROUGH) + fOpenMode |= O_SYNC; +#endif +#if defined(O_DIRECT) && defined(RT_OS_LINUX) + /* O_DIRECT is mandatory to get async I/O working on Linux. */ + if (fOpen & RTFILE_O_ASYNC_IO) + fOpenMode |= O_DIRECT; +#endif +#if defined(O_DIRECT) && (defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD)) + /* Disable the kernel cache. */ + if (fOpen & RTFILE_O_NO_CACHE) + fOpenMode |= O_DIRECT; +#endif + + /* create/truncate file */ + switch (fOpen & RTFILE_O_ACTION_MASK) + { + case RTFILE_O_OPEN: break; + case RTFILE_O_OPEN_CREATE: fOpenMode |= O_CREAT; break; + case RTFILE_O_CREATE: fOpenMode |= O_CREAT | O_EXCL; break; + case RTFILE_O_CREATE_REPLACE: fOpenMode |= O_CREAT | O_TRUNC; break; /** @todo replacing needs fixing, this is *not* a 1:1 mapping! */ + default: + AssertMsgFailed(("fOpen=%#llx\n", fOpen)); + fOpen = (fOpen & ~RTFILE_O_ACTION_MASK) | RTFILE_O_OPEN; + break; + + } + if ( (fOpen & RTFILE_O_TRUNCATE) + && (fOpen & RTFILE_O_ACTION_MASK) != RTFILE_O_CREATE) + fOpenMode |= O_TRUNC; + + switch (fOpen & RTFILE_O_ACCESS_MASK) + { + case RTFILE_O_READ: + fOpenMode |= O_RDONLY; /* RTFILE_O_APPEND is ignored. */ + break; + case RTFILE_O_WRITE: + fOpenMode |= fOpen & RTFILE_O_APPEND ? O_APPEND | O_WRONLY : O_WRONLY; + break; + case RTFILE_O_READWRITE: + fOpenMode |= fOpen & RTFILE_O_APPEND ? O_APPEND | O_RDWR : O_RDWR; + break; + default: + AssertMsgFailedReturn(("RTFileOpen received an invalid RW value, fOpen=%#llx\n", fOpen), VERR_INVALID_FLAGS); + } + + /* File mode. */ + int fMode = (fOpen & RTFILE_O_CREATE_MODE_MASK) + ? (fOpen & RTFILE_O_CREATE_MODE_MASK) >> RTFILE_O_CREATE_MODE_SHIFT + : RT_FILE_PERMISSION; + + /** @todo sharing? */ + + /* + * Open/create the file. + */ + char const *pszNativeFilename; + rc = rtPathToNative(&pszNativeFilename, pszFilename, NULL); + if (RT_FAILURE(rc)) + return (rc); + + int fh; + int iErr; + if (!penmActionTaken) + { + fh = open(pszNativeFilename, fOpenMode, fMode); + iErr = errno; + } + else + { + /* We need to know exactly which action was taken by open, Windows & + OS/2 style. Can be tedious and subject to races: */ + switch (fOpen & RTFILE_O_ACTION_MASK) + { + case RTFILE_O_OPEN: + Assert(!(fOpenMode & O_CREAT)); + Assert(!(fOpenMode & O_EXCL)); + fh = open(pszNativeFilename, fOpenMode, fMode); + iErr = errno; + if (fh >= 0) + *penmActionTaken = fOpenMode & O_TRUNC ? RTFILEACTION_TRUNCATED : RTFILEACTION_OPENED; + break; + + case RTFILE_O_CREATE: + Assert(fOpenMode & O_CREAT); + Assert(fOpenMode & O_EXCL); + fh = open(pszNativeFilename, fOpenMode, fMode); + iErr = errno; + if (fh >= 0) + *penmActionTaken = RTFILEACTION_CREATED; + else if (iErr == EEXIST) + *penmActionTaken = RTFILEACTION_ALREADY_EXISTS; + break; + + case RTFILE_O_OPEN_CREATE: + case RTFILE_O_CREATE_REPLACE: + { + Assert(fOpenMode & O_CREAT); + Assert(!(fOpenMode & O_EXCL)); + int iTries = 64; + while (iTries-- > 0) + { + /* Yield the CPU if we've raced too long. */ + if (iTries < 4) + RTThreadSleep(2 - (iTries & 1)); + + /* Try exclusive creation first: */ + fh = open(pszNativeFilename, fOpenMode | O_EXCL, fMode); + iErr = errno; + if (fh >= 0) + { + *penmActionTaken = RTFILEACTION_CREATED; + break; + } + if (iErr != EEXIST) + break; + + /* If the file exists, try open it: */ + fh = open(pszNativeFilename, fOpenMode & ~O_CREAT, fMode); + iErr = errno; + if (fh >= 0) + { + if ((fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE) + *penmActionTaken = fOpenMode & O_TRUNC ? RTFILEACTION_TRUNCATED : RTFILEACTION_OPENED; + else + *penmActionTaken = RTFILEACTION_REPLACED; + break; + } + if (iErr != ENOENT) + break; + } + Assert(iTries >= 0); + if (iTries < 0) + { + /* Thanks for the race, but we need to get on with things. */ + fh = open(pszNativeFilename, fOpenMode, fMode); + iErr = errno; + if (fh >= 0) + *penmActionTaken = RTFILEACTION_OPENED; + } + break; + } + + default: + AssertMsgFailed(("fOpen=%#llx fOpenMode=%#x\n", fOpen, fOpenMode)); + iErr = EINVAL; + fh = -1; + break; + } + } + + rtPathFreeNative(pszNativeFilename, pszFilename); + if (fh >= 0) + { + iErr = 0; + + /* + * If temporary file, delete it. + */ + if (fOpen & RTFILE_O_TEMP_AUTO_DELETE) + { + /** @todo Use funlinkat/funlink or similar here when available! Or better, + * use O_TMPFILE, only that may require fallback as not supported by + * all file system on linux. */ + iErr = unlink(pszNativeFilename); + Assert(iErr == 0); + } + + /* + * Mark the file handle close on exec, unless inherit is specified. + */ + if ( !(fOpen & RTFILE_O_INHERIT) +#ifdef O_NOINHERIT + && !(fOpenMode & O_NOINHERIT) /* Take care since it might be a zero value dummy. */ +#endif +#ifdef O_CLOEXEC + && fHave_O_CLOEXEC <= 0 +#endif + ) + iErr = fcntl(fh, F_SETFD, FD_CLOEXEC) >= 0 ? 0 : errno; + + /* + * Switch direct I/O on now if requested and required. + */ +#if defined(RT_OS_DARWIN) \ + || (defined(RT_OS_SOLARIS) && !defined(IN_GUEST)) + if (iErr == 0 && (fOpen & RTFILE_O_NO_CACHE)) + { +# if defined(RT_OS_DARWIN) + iErr = fcntl(fh, F_NOCACHE, 1) >= 0 ? 0 : errno; +# else + iErr = directio(fh, DIRECTIO_ON) >= 0 ? 0 : errno; +# endif + } +#endif + + /* + * Implement / emulate file sharing. + * + * We need another mode which allows skipping this stuff completely + * and do things the UNIX way. So for the present this is just a debug + * aid that can be enabled by developers too lazy to test on Windows. + */ +#if 0 && defined(RT_OS_LINUX) + if (iErr == 0) + { + /* This approach doesn't work because only knfsd checks for these + buggers. :-( */ + int iLockOp; + switch (fOpen & RTFILE_O_DENY_MASK) + { + default: + AssertFailed(); + case RTFILE_O_DENY_NONE: + case RTFILE_O_DENY_NOT_DELETE: + iLockOp = LOCK_MAND | LOCK_READ | LOCK_WRITE; + break; + case RTFILE_O_DENY_READ: + case RTFILE_O_DENY_READ | RTFILE_O_DENY_NOT_DELETE: + iLockOp = LOCK_MAND | LOCK_WRITE; + break; + case RTFILE_O_DENY_WRITE: + case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_NOT_DELETE: + iLockOp = LOCK_MAND | LOCK_READ; + break; + case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ: + case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ | RTFILE_O_DENY_NOT_DELETE: + iLockOp = LOCK_MAND; + break; + } + iErr = flock(fh, iLockOp | LOCK_NB); + if (iErr != 0) + iErr = errno == EAGAIN ? ETXTBSY : 0; + } +#endif /* 0 && RT_OS_LINUX */ +#if defined(DEBUG_bird) && !defined(RT_OS_SOLARIS) + if (iErr == 0) + { + /* This emulation is incomplete but useful. */ + switch (fOpen & RTFILE_O_DENY_MASK) + { + default: + AssertFailed(); + case RTFILE_O_DENY_NONE: + case RTFILE_O_DENY_NOT_DELETE: + case RTFILE_O_DENY_READ: + case RTFILE_O_DENY_READ | RTFILE_O_DENY_NOT_DELETE: + break; + case RTFILE_O_DENY_WRITE: + case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_NOT_DELETE: + case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ: + case RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ | RTFILE_O_DENY_NOT_DELETE: + if (fOpen & RTFILE_O_WRITE) + { + iErr = flock(fh, LOCK_EX | LOCK_NB); + if (iErr != 0) + iErr = errno == EAGAIN ? ETXTBSY : 0; + } + break; + } + } +#endif +#ifdef RT_OS_SOLARIS + /** @todo Use fshare_t and associates, it's a perfect match. see sys/fcntl.h */ +#endif + + /* + * We're done. + */ + if (iErr == 0) + { + *phFile = (RTFILE)(uintptr_t)fh; + Assert((intptr_t)*phFile == fh); + LogFlow(("RTFileOpen(%p:{%RTfile}, %p:{%s}, %#llx): returns %Rrc\n", + phFile, *phFile, pszFilename, pszFilename, fOpen, rc)); + return VINF_SUCCESS; + } + + close(fh); + } + return RTErrConvertFromErrno(iErr); +} + + +RTR3DECL(int) RTFileOpenBitBucket(PRTFILE phFile, uint64_t fAccess) +{ + AssertReturn( fAccess == RTFILE_O_READ + || fAccess == RTFILE_O_WRITE + || fAccess == RTFILE_O_READWRITE, + VERR_INVALID_PARAMETER); + return RTFileOpen(phFile, "/dev/null", fAccess | RTFILE_O_DENY_NONE | RTFILE_O_OPEN); +} + + +RTR3DECL(int) RTFileClose(RTFILE hFile) +{ + if (hFile == NIL_RTFILE) + return VINF_SUCCESS; + if (close(RTFileToNative(hFile)) == 0) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileFromNative(PRTFILE pFile, RTHCINTPTR uNative) +{ + AssertCompile(sizeof(uNative) == sizeof(*pFile)); + if (uNative < 0) + { + AssertMsgFailed(("%p\n", uNative)); + *pFile = NIL_RTFILE; + return VERR_INVALID_HANDLE; + } + *pFile = (RTFILE)uNative; + return VINF_SUCCESS; +} + + +RTR3DECL(RTHCINTPTR) RTFileToNative(RTFILE hFile) +{ + AssertReturn(hFile != NIL_RTFILE, -1); + return (intptr_t)hFile; +} + + +RTFILE rtFileGetStandard(RTHANDLESTD enmStdHandle) +{ + int fd; + switch (enmStdHandle) + { + case RTHANDLESTD_INPUT: fd = 0; break; + case RTHANDLESTD_OUTPUT: fd = 1; break; + case RTHANDLESTD_ERROR: fd = 2; break; + default: + AssertFailedReturn(NIL_RTFILE); + } + + struct stat st; + int rc = fstat(fd, &st); + if (rc == -1) + return NIL_RTFILE; + return (RTFILE)(intptr_t)fd; +} + + +RTR3DECL(int) RTFileDelete(const char *pszFilename) +{ + char const *pszNativeFilename; + int rc = rtPathToNative(&pszNativeFilename, pszFilename, NULL); + if (RT_SUCCESS(rc)) + { + if (unlink(pszNativeFilename) != 0) + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativeFilename, pszFilename); + } + return rc; +} + + +RTR3DECL(int) RTFileSeek(RTFILE hFile, int64_t offSeek, unsigned uMethod, uint64_t *poffActual) +{ + static const unsigned aSeekRecode[] = + { + SEEK_SET, + SEEK_CUR, + SEEK_END, + }; + + /* + * Validate input. + */ + if (uMethod > RTFILE_SEEK_END) + { + AssertMsgFailed(("Invalid uMethod=%d\n", uMethod)); + return VERR_INVALID_PARAMETER; + } + + /* check that within off_t range. */ + if ( sizeof(off_t) < sizeof(offSeek) + && ( (offSeek > 0 && (unsigned)(offSeek >> 32) != 0) + || (offSeek < 0 && (unsigned)(-offSeek >> 32) != 0))) + { + AssertMsgFailed(("64-bit search not supported\n")); + return VERR_NOT_SUPPORTED; + } + + off_t offCurrent = lseek(RTFileToNative(hFile), (off_t)offSeek, aSeekRecode[uMethod]); + if (offCurrent != ~0) + { + if (poffActual) + *poffActual = (uint64_t)offCurrent; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileRead(RTFILE hFile, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + if (cbToRead <= 0) + { + if (pcbRead) + *pcbRead = 0; + return VINF_SUCCESS; + } + + /* + * Attempt read. + */ + ssize_t cbRead = read(RTFileToNative(hFile), pvBuf, cbToRead); + if (cbRead >= 0) + { + if (pcbRead) + /* caller can handle partial read. */ + *pcbRead = cbRead; + else + { + /* Caller expects all to be read. */ + while ((ssize_t)cbToRead > cbRead) + { + ssize_t cbReadPart = read(RTFileToNative(hFile), (char*)pvBuf + cbRead, cbToRead - cbRead); + if (cbReadPart <= 0) + { + if (cbReadPart == 0) + return VERR_EOF; + return RTErrConvertFromErrno(errno); + } + cbRead += cbReadPart; + } + } + return VINF_SUCCESS; + } + + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileWrite(RTFILE hFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + if (cbToWrite <= 0) + return VINF_SUCCESS; + + /* + * Attempt write. + */ + ssize_t cbWritten = write(RTFileToNative(hFile), pvBuf, cbToWrite); + if (cbWritten >= 0) + { + if (pcbWritten) + /* caller can handle partial write. */ + *pcbWritten = cbWritten; + else + { + /* Caller expects all to be write. */ + while ((ssize_t)cbToWrite > cbWritten) + { + ssize_t cbWrittenPart = write(RTFileToNative(hFile), (const char *)pvBuf + cbWritten, cbToWrite - cbWritten); + if (cbWrittenPart <= 0) + return cbWrittenPart < 0 ? RTErrConvertFromErrno(errno) : VERR_TRY_AGAIN; + cbWritten += cbWrittenPart; + } + } + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileSetSize(RTFILE hFile, uint64_t cbSize) +{ + /* + * Validate offset. + */ + if ( sizeof(off_t) < sizeof(cbSize) + && (cbSize >> 32) != 0) + { + AssertMsgFailed(("64-bit filesize not supported! cbSize=%lld\n", cbSize)); + return VERR_NOT_SUPPORTED; + } + +#if defined(_MSC_VER) || (defined(RT_OS_OS2) && (!defined(__INNOTEK_LIBC__) || __INNOTEK_LIBC__ < 0x006)) + if (chsize(RTFileToNative(hFile), (off_t)cbSize) == 0) +#else + /* This relies on a non-standard feature of FreeBSD, Linux, and OS/2 + * LIBC v0.6 and higher. (SuS doesn't define ftruncate() and size bigger + * than the file.) + */ + if (ftruncate(RTFileToNative(hFile), (off_t)cbSize) == 0) +#endif + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileQuerySize(RTFILE hFile, uint64_t *pcbSize) +{ + /* + * Ask fstat() first. + */ + struct stat st; + if (!fstat(RTFileToNative(hFile), &st)) + { + *pcbSize = st.st_size; + if ( st.st_size != 0 +#if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN) + || (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) +#elif defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_DARWIN) + || !S_ISCHR(st.st_mode) +#else + || !S_ISBLK(st.st_mode) +#endif + ) + return VINF_SUCCESS; + + /* + * It could be a block device. Try determin the size by I/O control + * query or seek. + */ +#ifdef RT_OS_DARWIN + uint64_t cBlocks; + if (!ioctl(RTFileToNative(hFile), DKIOCGETBLOCKCOUNT, &cBlocks)) + { + uint32_t cbBlock; + if (!ioctl(RTFileToNative(hFile), DKIOCGETBLOCKSIZE, &cbBlock)) + { + *pcbSize = cBlocks * cbBlock; + return VINF_SUCCESS; + } + } + + /* Always fail block devices. Character devices doesn't all need to be + /dev/rdisk* nodes, they should return ENOTTY but /dev/null returns ENODEV + and we include EINVAL just in case. */ + if (!S_ISBLK(st.st_mode) && (errno == ENOTTY || errno == ENODEV || errno == EINVAL)) + return VINF_SUCCESS; + +#elif defined(RT_OS_SOLARIS) + struct dk_minfo MediaInfo; + if (!ioctl(RTFileToNative(hFile), DKIOCGMEDIAINFO, &MediaInfo)) + { + *pcbSize = MediaInfo.dki_capacity * MediaInfo.dki_lbsize; + return VINF_SUCCESS; + } + /* might not be a block device. */ + if (errno == EINVAL || errno == ENOTTY) + return VINF_SUCCESS; + +#elif defined(RT_OS_FREEBSD) + off_t cbMedia = 0; + if (!ioctl(RTFileToNative(hFile), DIOCGMEDIASIZE, &cbMedia)) + { + *pcbSize = cbMedia; + return VINF_SUCCESS; + } + /* might not be a block device. */ + if (errno == EINVAL || errno == ENOTTY) + return VINF_SUCCESS; + +#else + /* PORTME! Avoid this path when possible. */ + uint64_t offSaved = UINT64_MAX; + int rc = RTFileSeek(hFile, 0, RTFILE_SEEK_CURRENT, &offSaved); + if (RT_SUCCESS(rc)) + { + rc = RTFileSeek(hFile, 0, RTFILE_SEEK_END, pcbSize); + int rc2 = RTFileSeek(hFile, offSaved, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + return rc2; + } +#endif + } + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileQueryMaxSizeEx(RTFILE hFile, PRTFOFF pcbMax) +{ + /* + * Save the current location + */ + uint64_t offOld = UINT64_MAX; + int rc = RTFileSeek(hFile, 0, RTFILE_SEEK_CURRENT, &offOld); + if (RT_FAILURE(rc)) + return rc; + + uint64_t offLow = 0; + uint64_t offHigh = INT64_MAX; /* we don't need bigger files */ + /** @todo Unfortunately this does not work for certain file system types, + * for instance cifs mounts. Even worse, statvfs.f_fsid returns 0 for such + * file systems. */ + + /* + * Quickly guess the order of magnitude for offHigh and offLow. + */ + { + uint64_t offHighPrev = offHigh; + while (offHigh >= INT32_MAX) + { + rc = RTFileSeek(hFile, offHigh, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + { + offLow = offHigh; + offHigh = offHighPrev; + break; + } + else + { + offHighPrev = offHigh; + offHigh >>= 8; + } + } + } + + /* + * Sanity: if the seek to the initial offHigh (INT64_MAX) works, then + * this algorithm cannot possibly work. Declare defeat. + */ + if (offLow == offHigh) + { + rc = RTFileSeek(hFile, offOld, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + rc = VERR_NOT_IMPLEMENTED; + + return rc; + } + + /* + * Perform a binary search for the max file size. + */ + while (offLow <= offHigh) + { + uint64_t offMid = offLow + (offHigh - offLow) / 2; + rc = RTFileSeek(hFile, offMid, RTFILE_SEEK_BEGIN, NULL); + if (RT_FAILURE(rc)) + offHigh = offMid - 1; + else + offLow = offMid + 1; + } + + if (pcbMax) + *pcbMax = RT_MIN(offLow, offHigh); + return RTFileSeek(hFile, offOld, RTFILE_SEEK_BEGIN, NULL); +} + + +RTR3DECL(bool) RTFileIsValid(RTFILE hFile) +{ + if (hFile != NIL_RTFILE) + { + int fFlags = fcntl(RTFileToNative(hFile), F_GETFD); + if (fFlags >= 0) + return true; + } + return false; +} + + +RTR3DECL(int) RTFileFlush(RTFILE hFile) +{ + if (!fsync(RTFileToNative(hFile))) + return VINF_SUCCESS; + /* Ignore EINVAL here as that's what returned for pseudo ttys + and other odd handles. */ + if (errno == EINVAL) + return VINF_NOT_SUPPORTED; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileIoCtl(RTFILE hFile, unsigned long ulRequest, void *pvData, unsigned cbData, int *piRet) +{ + NOREF(cbData); + int rc = ioctl(RTFileToNative(hFile), ulRequest, pvData); + if (piRet) + *piRet = rc; + return rc >= 0 ? VINF_SUCCESS : RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTFileSetMode(RTFILE hFile, RTFMODE fMode) +{ + /* + * Normalize the mode and call the API. + */ + fMode = rtFsModeNormalize(fMode, NULL, 0, RTFS_TYPE_FILE); + if (!rtFsModeIsValid(fMode)) + return VERR_INVALID_PARAMETER; + + if (fchmod(RTFileToNative(hFile), fMode & RTFS_UNIX_MASK)) + { + int rc = RTErrConvertFromErrno(errno); + Log(("RTFileSetMode(%RTfile,%RTfmode): returns %Rrc\n", hFile, fMode, rc)); + return rc; + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTFileSetOwner(RTFILE hFile, uint32_t uid, uint32_t gid) +{ + uid_t uidNative = uid != NIL_RTUID ? (uid_t)uid : (uid_t)-1; + AssertReturn(uid == uidNative, VERR_INVALID_PARAMETER); + gid_t gidNative = gid != NIL_RTGID ? (gid_t)gid : (gid_t)-1; + AssertReturn(gid == gidNative, VERR_INVALID_PARAMETER); + + if (fchown(RTFileToNative(hFile), uidNative, gidNative)) + return RTErrConvertFromErrno(errno); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTFileRename(const char *pszSrc, const char *pszDst, unsigned fRename) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + AssertMsgReturn(*pszSrc, ("%p\n", pszSrc), VERR_INVALID_PARAMETER); + AssertMsgReturn(*pszDst, ("%p\n", pszDst), VERR_INVALID_PARAMETER); + AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER); + + /* + * Take common cause with RTPathRename. + */ + int rc = rtPathPosixRename(pszSrc, pszDst, fRename, RTFS_TYPE_FILE); + + LogFlow(("RTDirRename(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", + pszSrc, pszSrc, pszDst, pszDst, fRename, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/fileio-sg-at-posix.cpp b/src/VBox/Runtime/r3/posix/fileio-sg-at-posix.cpp new file mode 100644 index 00000000..5fdea41a --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fileio-sg-at-posix.cpp @@ -0,0 +1,298 @@ +/* $Id: fileio-sg-at-posix.cpp $ */ +/** @file + * IPRT - File I/O, RTFileSgReadAt & RTFileSgWriteAt, posixy. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +/* + * Determin whether we've got preadv and pwritev. + */ +#include <iprt/cdefs.h> +#ifdef RT_OS_LINUX +/* Linux has these since glibc 2.10 and Linux 2.6.30: */ +# include <features.h> +# ifdef __GLIBC_PREREQ +# if __GLIBC_PREREQ(2,10) +# define HAVE_PREADV_AND_PWRITEV 1 +#else +# endif +# endif + +#elif defined(RT_OS_FREEBSD) +/* FreeBSD has these since 6.0: */ +# include <osreldate.h> +# ifdef __FreeBSD_version +# if __FreeBSD_version >= 600000 +# define HAVE_PREADV_AND_PWRITEV 1 +# endif +# endif + +#endif + +#ifndef HAVE_PREADV_AND_PWRITEV + +# include "../../generic/fileio-sg-at-generic.cpp" + +#else /* HAVE_PREADV_AND_PWRITEV - rest of the file */ + +# include <errno.h> +# include <sys/types.h> +# include <sys/uio.h> +# include <unistd.h> +# include <limits.h> +# if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) +# include <sys/syslimits.h> +# endif + +# include "internal/iprt.h" +# include <iprt/file.h> + +# include <iprt/assert.h> +# include <iprt/err.h> +# include <iprt/log.h> + +# ifndef UIO_MAXIOV +# ifdef IOV_MAX +# define UIO_MAXIOV IOV_MAX +# else +# error "UIO_MAXIOV and IOV_MAX are undefined" +# endif +# endif + + +/* These assumptions simplifies things a lot here. */ +AssertCompileMembersSameSizeAndOffset(struct iovec, iov_base, RTSGSEG, pvSeg); +AssertCompileMembersSameSizeAndOffset(struct iovec, iov_len, RTSGSEG, cbSeg); + + +RTDECL(int) RTFileSgReadAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToRead, size_t *pcbRead) +{ + /* + * Make sure we set pcbRead. + */ + if (pcbRead) + *pcbRead = 0; + + /* + * Special case: Zero read == seek. + */ + if (cbToRead == 0) + return RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL); + + /* + * We can use the segment array directly if we're at the start of the + * current S/G segment and cbToRead matches the remainder exactly. + */ + size_t cbTotalRead = 0; + + size_t const cbSgBufLeft = RTSgBufCalcLengthLeft(pSgBuf); + AssertMsgReturn(cbSgBufLeft >= cbToRead, ("%#zx vs %#zx\n", cbSgBufLeft, cbToRead), VERR_INVALID_PARAMETER); + + if (cbToRead == cbSgBufLeft) + while (RTSgBufIsAtStartOfSegment(pSgBuf)) + { + size_t const cSegsLeft = pSgBuf->cSegs - pSgBuf->idxSeg; + ssize_t cbThisRead = preadv(RTFileToNative(hFile), (const struct iovec *)&pSgBuf->paSegs[pSgBuf->idxSeg], + RT_MIN(cSegsLeft, UIO_MAXIOV), off); + if (cbThisRead >= 0) + { + AssertStmt((size_t)cbThisRead <= cbToRead, cbThisRead = cbToRead); + + RTSgBufAdvance(pSgBuf, cbThisRead); + cbTotalRead += cbThisRead; + cbToRead -= cbThisRead; + if (cbToRead == 0) + { + if (pcbRead) + *pcbRead = cbTotalRead; + return VINF_SUCCESS; + } + + if ( pcbRead + && ( cSegsLeft <= UIO_MAXIOV + || cbThisRead == 0 /* typically EOF */ )) + { + *pcbRead = cbTotalRead; + return VINF_SUCCESS; + } + if (cbThisRead == 0) + return VERR_EOF; + + off += cbThisRead; + } + else if (cbTotalRead > 0 && pcbRead) + { + *pcbRead = cbTotalRead; + return VINF_SUCCESS; + } + else + return RTErrConvertFromErrno(errno); + } + + /* + * Unaligned start or not reading the whole buffer. For reasons of + * simplicity, we work the input segment by segment like the generic code. + */ + int rc = VINF_SUCCESS; + while (cbToRead > 0) + { + size_t cbSeg; + void *pvSeg = RTSgBufGetCurrentSegment(pSgBuf, cbToRead, &cbSeg); + size_t cbThisRead = cbSeg; + rc = RTFileReadAt(hFile, off, pvSeg, cbSeg, pcbRead ? &cbThisRead : NULL); + if (RT_SUCCESS(rc)) + { + RTSgBufAdvance(pSgBuf, cbThisRead); + cbTotalRead += cbThisRead; + } + else + break; + if ((size_t)cbThisRead < cbSeg) + { + AssertStmt(pcbRead, rc = VERR_INTERNAL_ERROR_2); + break; + } + + Assert(cbSeg == cbThisRead); + cbToRead -= cbSeg; + off += cbSeg; + } + if (pcbRead) + *pcbRead = cbTotalRead; + return rc; +} + + +RTDECL(int) RTFileSgWriteAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToWrite, size_t *pcbWritten) +{ + /* + * Make sure we set pcbWritten. + */ + if (pcbWritten) + *pcbWritten = 0; + + /* + * Special case: Zero write == seek. + */ + if (cbToWrite == 0) + return RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL); + + /* + * We can use the segment array directly if we're at the start of the + * current S/G segment and cbToWrite matches the remainder exactly. + */ + size_t cbTotalWritten = 0; + + size_t const cbSgBufLeft = RTSgBufCalcLengthLeft(pSgBuf); + AssertMsgReturn(cbSgBufLeft >= cbToWrite, ("%#zx vs %#zx\n", cbSgBufLeft, cbToWrite), VERR_INVALID_PARAMETER); + + if (cbToWrite == cbSgBufLeft) + while (RTSgBufIsAtStartOfSegment(pSgBuf)) + { + size_t const cSegsLeft = pSgBuf->cSegs - pSgBuf->idxSeg; + ssize_t cbThisWritten = pwritev(RTFileToNative(hFile), (const struct iovec *)&pSgBuf->paSegs[pSgBuf->idxSeg], + RT_MIN(cSegsLeft, UIO_MAXIOV), off); + if (cbThisWritten >= 0) + { + AssertStmt((size_t)cbThisWritten <= cbToWrite, cbThisWritten = cbToWrite); + + RTSgBufAdvance(pSgBuf, cbThisWritten); + cbTotalWritten += cbThisWritten; + cbToWrite -= cbThisWritten; + if (cbToWrite == 0) + { + if (pcbWritten) + *pcbWritten = cbTotalWritten; + return VINF_SUCCESS; + } + + if ( pcbWritten + && ( cSegsLeft <= UIO_MAXIOV + || cbThisWritten == 0 /* non-file, full buffer/whatever */ )) + { + *pcbWritten = cbTotalWritten; + return VINF_SUCCESS; + } + if (cbThisWritten == 0) + return VERR_TRY_AGAIN; + + off += cbThisWritten; + } + else if (cbTotalWritten > 0 && pcbWritten) + { + *pcbWritten = cbTotalWritten; + return VINF_SUCCESS; + } + else + return RTErrConvertFromErrno(errno); + } + + /* + * Unaligned start or not writing the whole buffer. For reasons of + * simplicity, we work the input segment by segment like the generic code. + */ + int rc = VINF_SUCCESS; + while (cbToWrite > 0) + { + size_t cbSeg; + void *pvSeg = RTSgBufGetCurrentSegment(pSgBuf, cbToWrite, &cbSeg); + size_t cbThisWritten = cbSeg; + rc = RTFileWriteAt(hFile, off, pvSeg, cbSeg, pcbWritten ? &cbThisWritten : NULL); + if (RT_SUCCESS(rc)) + { + RTSgBufAdvance(pSgBuf, cbThisWritten); + cbTotalWritten += cbThisWritten; + } + else + break; + if ((size_t)cbThisWritten < cbSeg) + { + AssertStmt(pcbWritten, rc = VERR_INTERNAL_ERROR_2); + break; + } + + Assert(cbSeg == cbThisWritten); + cbToWrite -= cbSeg; + off += cbSeg; + } + if (pcbWritten) + *pcbWritten = cbTotalWritten; + return rc; +} + +#endif /* HAVE_PREADV_AND_PWRITEV */ + diff --git a/src/VBox/Runtime/r3/posix/fileio-sg-posix.cpp b/src/VBox/Runtime/r3/posix/fileio-sg-posix.cpp new file mode 100644 index 00000000..c8c95786 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fileio-sg-posix.cpp @@ -0,0 +1,260 @@ +/* $Id: fileio-sg-posix.cpp $ */ +/** @file + * IPRT - File I/O, RTFileSgRead & RTFileSgWrite, posixy. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +#include <limits.h> +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) +# include <sys/syslimits.h> +#endif + +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/log.h> + +#ifndef UIO_MAXIOV +# ifdef IOV_MAX +# define UIO_MAXIOV IOV_MAX +# else +# error "UIO_MAXIOV and IOV_MAX are undefined" +# endif +#endif + + +/* These assumptions simplifies things a lot here. */ +AssertCompileMembersSameSizeAndOffset(struct iovec, iov_base, RTSGSEG, pvSeg); +AssertCompileMembersSameSizeAndOffset(struct iovec, iov_len, RTSGSEG, cbSeg); + + +RTDECL(int) RTFileSgRead(RTFILE hFile, PRTSGBUF pSgBuf, size_t cbToRead, size_t *pcbRead) +{ + /* + * Make sure we set pcbRead. + */ + if (pcbRead) + *pcbRead = 0; + + /* + * Special case: Zero read == nop. + */ + if (cbToRead == 0) + return VINF_SUCCESS; + + /* + * We can use the segment array directly if we're at the start of the + * current S/G segment and cbToRead matches the remainder exactly. + */ + size_t cbTotalRead = 0; + + size_t const cbSgBufLeft = RTSgBufCalcLengthLeft(pSgBuf); + AssertMsgReturn(cbSgBufLeft >= cbToRead, ("%#zx vs %#zx\n", cbSgBufLeft, cbToRead), VERR_INVALID_PARAMETER); + + if (cbToRead == cbSgBufLeft) + while (RTSgBufIsAtStartOfSegment(pSgBuf)) + { + size_t const cSegsLeft = pSgBuf->cSegs - pSgBuf->idxSeg; + ssize_t cbThisRead = readv(RTFileToNative(hFile), (const struct iovec *)&pSgBuf->paSegs[pSgBuf->idxSeg], + RT_MIN(cSegsLeft, UIO_MAXIOV)); + if (cbThisRead >= 0) + { + AssertStmt((size_t)cbThisRead <= cbToRead, cbThisRead = cbToRead); + + RTSgBufAdvance(pSgBuf, cbThisRead); + cbTotalRead += cbThisRead; + cbToRead -= cbThisRead; + if (cbToRead == 0) + { + if (pcbRead) + *pcbRead = cbTotalRead; + return VINF_SUCCESS; + } + + if ( pcbRead + && ( cSegsLeft <= UIO_MAXIOV + || cbThisRead == 0 /* typically EOF */ )) + { + *pcbRead = cbTotalRead; + return VINF_SUCCESS; + } + if (cbThisRead == 0) + return VERR_EOF; + } + else if (cbTotalRead > 0 && pcbRead) + { + *pcbRead = cbTotalRead; + return VINF_SUCCESS; + } + else + return RTErrConvertFromErrno(errno); + } + + /* + * Unaligned start or not reading the whole buffer. For reasons of + * simplicity, we work the input segment by segment like the generic code. + */ + int rc = VINF_SUCCESS; + while (cbToRead > 0) + { + size_t cbSeg; + void *pvSeg = RTSgBufGetCurrentSegment(pSgBuf, cbToRead, &cbSeg); + size_t cbThisRead = cbSeg; + rc = RTFileRead(hFile, pvSeg, cbSeg, pcbRead ? &cbThisRead : NULL); + if (RT_SUCCESS(rc)) + { + RTSgBufAdvance(pSgBuf, cbThisRead); + cbTotalRead += cbThisRead; + } + else + break; + if ((size_t)cbThisRead < cbSeg) + { + AssertStmt(pcbRead, rc = VERR_INTERNAL_ERROR_2); + break; + } + + Assert(cbSeg == cbThisRead); + cbToRead -= cbSeg; + } + if (pcbRead) + *pcbRead = cbTotalRead; + return rc; +} + + +RTDECL(int) RTFileSgWrite(RTFILE hFile, PRTSGBUF pSgBuf, size_t cbToWrite, size_t *pcbWritten) +{ + /* + * Make sure we set pcbWritten. + */ + if (pcbWritten) + *pcbWritten = 0; + + /* + * Special case: Zero write == nop. + */ + if (cbToWrite == 0) + return VINF_SUCCESS; + + /* + * We can use the segment array directly if we're at the start of the + * current S/G segment and cbToWrite matches the remainder exactly. + */ + size_t cbTotalWritten = 0; + + size_t const cbSgBufLeft = RTSgBufCalcLengthLeft(pSgBuf); + AssertMsgReturn(cbSgBufLeft >= cbToWrite, ("%#zx vs %#zx\n", cbSgBufLeft, cbToWrite), VERR_INVALID_PARAMETER); + + if (cbToWrite == cbSgBufLeft) + while (RTSgBufIsAtStartOfSegment(pSgBuf)) + { + size_t const cSegsLeft = pSgBuf->cSegs - pSgBuf->idxSeg; + ssize_t cbThisWritten = writev(RTFileToNative(hFile), (const struct iovec *)&pSgBuf->paSegs[pSgBuf->idxSeg], + RT_MIN(cSegsLeft, UIO_MAXIOV)); + if (cbThisWritten >= 0) + { + AssertStmt((size_t)cbThisWritten <= cbToWrite, cbThisWritten = cbToWrite); + + RTSgBufAdvance(pSgBuf, cbThisWritten); + cbTotalWritten += cbThisWritten; + cbToWrite -= cbThisWritten; + if (cbToWrite == 0) + { + if (pcbWritten) + *pcbWritten = cbTotalWritten; + return VINF_SUCCESS; + } + + if ( pcbWritten + && ( cSegsLeft <= UIO_MAXIOV + || cbThisWritten == 0 /* non-file, full buffer/whatever */ )) + { + *pcbWritten = cbTotalWritten; + return VINF_SUCCESS; + } + if (cbThisWritten == 0) + return VERR_TRY_AGAIN; + } + else if (cbTotalWritten > 0 && pcbWritten) + { + *pcbWritten = cbTotalWritten; + return VINF_SUCCESS; + } + else + return RTErrConvertFromErrno(errno); + } + + /* + * Unaligned start or not writing the whole buffer. For reasons of + * simplicity, we work the input segment by segment like the generic code. + */ + int rc = VINF_SUCCESS; + while (cbToWrite > 0) + { + size_t cbSeg; + void *pvSeg = RTSgBufGetCurrentSegment(pSgBuf, cbToWrite, &cbSeg); + size_t cbThisWritten = cbSeg; + rc = RTFileWrite(hFile, pvSeg, cbSeg, pcbWritten ? &cbThisWritten : NULL); + if (RT_SUCCESS(rc)) + { + RTSgBufAdvance(pSgBuf, cbThisWritten); + cbTotalWritten += cbThisWritten; + } + else + break; + if ((size_t)cbThisWritten < cbSeg) + { + AssertStmt(pcbWritten, rc = VERR_INTERNAL_ERROR_2); + break; + } + + Assert(cbSeg == cbThisWritten); + cbToWrite -= cbSeg; + } + if (pcbWritten) + *pcbWritten = cbTotalWritten; + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/fileio2-posix.cpp b/src/VBox/Runtime/r3/posix/fileio2-posix.cpp new file mode 100644 index 00000000..4879f51e --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fileio2-posix.cpp @@ -0,0 +1,210 @@ +/* $Id: fileio2-posix.cpp $ */ +/** @file + * IPRT - File I/O, POSIX, Part 2. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE + +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#ifdef _MSC_VER +# include <io.h> +# include <stdio.h> +#else +# include <unistd.h> +# include <sys/time.h> +#endif +#ifdef RT_OS_LINUX +# include <sys/file.h> +#endif +#if defined(RT_OS_OS2) && (!defined(__INNOTEK_LIBC__) || __INNOTEK_LIBC__ < 0x006) +# include <io.h> +#endif + +#ifdef RT_OS_SOLARIS +# define futimes(filedes, timeval) futimesat(filedes, NULL, timeval) +#endif + +#ifdef RT_OS_HAIKU +# define USE_FUTIMENS +#endif + +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include "internal/file.h" +#include "internal/fs.h" +#include "internal/path.h" + + + +RTR3DECL(int) RTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + /* + * Validate input. + */ + AssertReturn(hFile != NIL_RTFILE, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_PARAMETER); + if ( enmAdditionalAttribs < RTFSOBJATTRADD_NOTHING + || enmAdditionalAttribs > RTFSOBJATTRADD_LAST) + { + AssertMsgFailed(("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs)); + return VERR_INVALID_PARAMETER; + } + + /* + * Query file info. + */ + struct stat Stat; + if (fstat(RTFileToNative(hFile), &Stat)) + { + int rc = RTErrConvertFromErrno(errno); + Log(("RTFileQueryInfo(%RTfile,,%d): returns %Rrc\n", hFile, enmAdditionalAttribs, rc)); + return rc; + } + + /* + * Setup the returned data. + */ + rtFsConvertStatToObjInfo(pObjInfo, &Stat, NULL, 0); + + /* + * Requested attributes (we cannot provide anything actually). + */ + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_NOTHING: + case RTFSOBJATTRADD_UNIX: + /* done */ + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + rtFsObjInfoAttrSetUnixOwner(pObjInfo, Stat.st_uid); + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + rtFsObjInfoAttrSetUnixGroup(pObjInfo, Stat.st_gid); + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR; + } + + LogFlow(("RTFileQueryInfo(%RTfile,,%d): returns VINF_SUCCESS\n", hFile, enmAdditionalAttribs)); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTFileSetTimes(RTFILE hFile, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + NOREF(pChangeTime); NOREF(pBirthTime); + + /* + * We can only set AccessTime and ModificationTime, so if neither + * are specified we can return immediately. + */ + if (!pAccessTime && !pModificationTime) + return VINF_SUCCESS; + +#ifdef USE_FUTIMENS + struct timespec aTimespecs[2]; + if (pAccessTime && pModificationTime) + { + memcpy(&aTimespecs[0], pAccessTime, sizeof(struct timespec)); + memcpy(&aTimespecs[1], pModificationTime, sizeof(struct timespec)); + } + else + { + RTFSOBJINFO ObjInfo; + int rc = RTFileQueryInfo(hFile, &ObjInfo, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(rc)) + return rc; + memcpy(&aTimespecs[0], pAccessTime ? pAccessTime : &ObjInfo.AccessTime, sizeof(struct timespec)); + memcpy(&aTimespecs[1], pModificationTime ? pModificationTime : &ObjInfo.ModificationTime, sizeof(struct timespec)); + } + + if (futimens(RTFileToNative(hFile), aTimespecs)) + { + int rc = RTErrConvertFromErrno(errno); + Log(("RTFileSetTimes(%RTfile,%p,%p,,): returns %Rrc\n", hFile, pAccessTime, pModificationTime, rc)); + return rc; + } +#else + /* + * Convert the input to timeval, getting the missing one if necessary, + * and call the API which does the change. + */ + struct timeval aTimevals[2]; + if (pAccessTime && pModificationTime) + { + RTTimeSpecGetTimeval(pAccessTime, &aTimevals[0]); + RTTimeSpecGetTimeval(pModificationTime, &aTimevals[1]); + } + else + { + RTFSOBJINFO ObjInfo; + int rc = RTFileQueryInfo(hFile, &ObjInfo, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(rc)) + return rc; + RTTimeSpecGetTimeval(pAccessTime ? pAccessTime : &ObjInfo.AccessTime, &aTimevals[0]); + RTTimeSpecGetTimeval(pModificationTime ? pModificationTime : &ObjInfo.ModificationTime, &aTimevals[1]); + } + + /* XXX this falls back to utimes("/proc/self/fd/...",...) for older kernels/glibcs and this + * will not work for hardened builds where this directory is owned by root.root and mode 0500 */ + if (futimes(RTFileToNative(hFile), aTimevals)) + { + int rc = RTErrConvertFromErrno(errno); + Log(("RTFileSetTimes(%RTfile,%p,%p,,): returns %Rrc\n", hFile, pAccessTime, pModificationTime, rc)); + return rc; + } +#endif + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/posix/filelock-posix.cpp b/src/VBox/Runtime/r3/posix/filelock-posix.cpp new file mode 100644 index 00000000..3a0e230f --- /dev/null +++ b/src/VBox/Runtime/r3/posix/filelock-posix.cpp @@ -0,0 +1,148 @@ +/* $Id: filelock-posix.cpp $ */ +/** @file + * IPRT - File Locking, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE + +#include <errno.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/time.h> + +#include <iprt/file.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/file.h" +#include "internal/fs.h" + + + + +RTR3DECL(int) RTFileLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* Check arguments. */ + if (fLock & ~RTFILE_LOCK_MASK) + { + AssertMsgFailed(("Invalid fLock=%08X\n", fLock)); + return VERR_INVALID_PARAMETER; + } + + /* + * Validate offset. + */ + if ( sizeof(off_t) < sizeof(cbLock) + && ( (offLock >> 32) != 0 + || (cbLock >> 32) != 0 + || ((offLock + cbLock) >> 32) != 0)) + { + AssertMsgFailed(("64-bit file i/o not supported! offLock=%lld cbLock=%lld\n", offLock, cbLock)); + return VERR_NOT_SUPPORTED; + } + + /* Prepare flock structure. */ + struct flock fl; + Assert(RTFILE_LOCK_WRITE); + fl.l_type = (fLock & RTFILE_LOCK_WRITE) ? F_WRLCK : F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_start = (off_t)offLock; + fl.l_len = (off_t)cbLock; + fl.l_pid = 0; + + Assert(RTFILE_LOCK_WAIT); + if (fcntl(RTFileToNative(hFile), (fLock & RTFILE_LOCK_WAIT) ? F_SETLKW : F_SETLK, &fl) >= 0) + return VINF_SUCCESS; + + int iErr = errno; + if ( iErr == EAGAIN + || iErr == EACCES) + return VERR_FILE_LOCK_VIOLATION; + + return RTErrConvertFromErrno(iErr); +} + + +RTR3DECL(int) RTFileChangeLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + /** @todo We never returns VERR_FILE_NOT_LOCKED for now. */ + return RTFileLock(hFile, fLock, offLock, cbLock); +} + + +RTR3DECL(int) RTFileUnlock(RTFILE hFile, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* + * Validate offset. + */ + if ( sizeof(off_t) < sizeof(cbLock) + && ( (offLock >> 32) != 0 + || (cbLock >> 32) != 0 + || ((offLock + cbLock) >> 32) != 0)) + { + AssertMsgFailed(("64-bit file i/o not supported! offLock=%lld cbLock=%lld\n", offLock, cbLock)); + return VERR_NOT_SUPPORTED; + } + + /* Prepare flock structure. */ + struct flock fl; + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = (off_t)offLock; + fl.l_len = (off_t)cbLock; + fl.l_pid = 0; + + if (fcntl(RTFileToNative(hFile), F_SETLK, &fl) >= 0) + return VINF_SUCCESS; + + /** @todo check error codes for non existing lock. */ + int iErr = errno; + if ( iErr == EAGAIN + || iErr == EACCES) + return VERR_FILE_LOCK_VIOLATION; + + return RTErrConvertFromErrno(iErr); +} + diff --git a/src/VBox/Runtime/r3/posix/fs-posix.cpp b/src/VBox/Runtime/r3/posix/fs-posix.cpp new file mode 100644 index 00000000..04b3289a --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fs-posix.cpp @@ -0,0 +1,346 @@ +/* $Id: fs-posix.cpp $ */ +/** @file + * IPRT - File System, Linux. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <sys/statvfs.h> +#include <errno.h> +#include <stdio.h> +#ifdef RT_OS_LINUX +# include <mntent.h> +#endif +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) +# include <sys/mount.h> +#endif + +#include <iprt/fs.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include <iprt/string.h> +#include "internal/fs.h" +#include "internal/path.h" + + + +RTR3DECL(int) RTFsQuerySizes(const char *pszFsPath, RTFOFF *pcbTotal, RTFOFF *pcbFree, + uint32_t *pcbBlock, uint32_t *pcbSector) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath != '\0', VERR_INVALID_PARAMETER); + + /* + * Convert the path and query the information. + */ + char const *pszNativeFsPath; + int rc = rtPathToNative(&pszNativeFsPath, pszFsPath, NULL); + if (RT_SUCCESS(rc)) + { + /** @todo I'm not quite sure if statvfs was properly specified by SuS, I have to check my own + * implementation and FreeBSD before this can eventually be promoted to posix. */ + struct statvfs StatVFS; + RT_ZERO(StatVFS); + if (!statvfs(pszNativeFsPath, &StatVFS)) + { + /* + * Calc the returned values. + */ + if (pcbTotal) + *pcbTotal = (RTFOFF)StatVFS.f_blocks * StatVFS.f_frsize; + if (pcbFree) + *pcbFree = (RTFOFF)StatVFS.f_bavail * StatVFS.f_frsize; + if (pcbBlock) + *pcbBlock = StatVFS.f_frsize; + /* no idea how to get the sector... */ + if (pcbSector) + *pcbSector = 512; + } + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativeFsPath, pszFsPath); + } + + LogFlow(("RTFsQuerySizes(%p:{%s}, %p:{%RTfoff}, %p:{%RTfoff}, %p:{%RX32}, %p:{%RX32}): returns %Rrc\n", + pszFsPath, pszFsPath, pcbTotal, pcbTotal ? *pcbTotal : 0, pcbFree, pcbFree ? *pcbFree : 0, + pcbBlock, pcbBlock ? *pcbBlock : 0, pcbSector, pcbSector ? *pcbSector : 0, rc)); + return rc; +} + + +RTR3DECL(int) RTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(pu32Serial, VERR_INVALID_POINTER); + + /* + * Convert the path and query the stats. + * We're simply return the device id. + */ + char const *pszNativeFsPath; + int rc = rtPathToNative(&pszNativeFsPath, pszFsPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat Stat; + if (!stat(pszNativeFsPath, &Stat)) + { + if (pu32Serial) + *pu32Serial = (uint32_t)Stat.st_dev; + } + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativeFsPath, pszFsPath); + } + LogFlow(("RTFsQuerySerial(%p:{%s}, %p:{%RX32}: returns %Rrc\n", + pszFsPath, pszFsPath, pu32Serial, pu32Serial ? *pu32Serial : 0, rc)); + return rc; +} + + +RTR3DECL(int) RTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties) +{ + /* + * Validate. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(pProperties, VERR_INVALID_POINTER); + + /* + * Convert the path and query the information. + */ + char const *pszNativeFsPath; + int rc = rtPathToNative(&pszNativeFsPath, pszFsPath, NULL); + if (RT_SUCCESS(rc)) + { + struct statvfs StatVFS; + RT_ZERO(StatVFS); + if (!statvfs(pszNativeFsPath, &StatVFS)) + { + /* + * Calc/fake the returned values. + */ + pProperties->cbMaxComponent = StatVFS.f_namemax; +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + pProperties->fCaseSensitive = false; +#else + pProperties->fCaseSensitive = true; +#endif + pProperties->fCompressed = false; + pProperties->fFileCompression = false; + pProperties->fReadOnly = !!(StatVFS.f_flag & ST_RDONLY); + pProperties->fRemote = false; + pProperties->fSupportsUnicode = true; + } + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativeFsPath, pszFsPath); + } + + LogFlow(("RTFsQueryProperties(%p:{%s}, %p:{.cbMaxComponent=%u, .fReadOnly=%RTbool}): returns %Rrc\n", + pszFsPath, pszFsPath, pProperties, pProperties->cbMaxComponent, pProperties->fReadOnly, rc)); + return rc; +} + + +RTR3DECL(bool) RTFsIsCaseSensitive(const char *pszFsPath) +{ + RT_NOREF_PV(pszFsPath); +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + return false; +#else + return true; +#endif +} + + +RTR3DECL(int) RTFsQueryType(const char *pszFsPath, PRTFSTYPE penmType) +{ + *penmType = RTFSTYPE_UNKNOWN; + + /* + * Validate input. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath, VERR_INVALID_PARAMETER); + + /* + * Convert the path and query the stats. + * We're simply return the device id. + */ + char const *pszNativeFsPath; + int rc = rtPathToNative(&pszNativeFsPath, pszFsPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat Stat; + if (!stat(pszNativeFsPath, &Stat)) + { +#if defined(RT_OS_LINUX) + FILE *mounted = setmntent("/proc/mounts", "r"); + if (!mounted) + mounted = setmntent("/etc/mtab", "r"); + if (mounted) + { + char szBuf[1024]; + struct stat mntStat; + struct mntent mntEnt; + while (getmntent_r(mounted, &mntEnt, szBuf, sizeof(szBuf))) + { + if (!stat(mntEnt.mnt_dir, &mntStat)) + { + if (mntStat.st_dev == Stat.st_dev) + { + if (!strcmp("ext4", mntEnt.mnt_type)) + *penmType = RTFSTYPE_EXT4; + else if (!strcmp("ext3", mntEnt.mnt_type)) + *penmType = RTFSTYPE_EXT3; + else if (!strcmp("ext2", mntEnt.mnt_type)) + *penmType = RTFSTYPE_EXT2; + else if (!strcmp("jfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_JFS; + else if (!strcmp("xfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_XFS; + else if (!strcmp("btrfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_BTRFS; + else if ( !strcmp("vfat", mntEnt.mnt_type) + || !strcmp("msdos", mntEnt.mnt_type)) + *penmType = RTFSTYPE_FAT; + else if (!strcmp("ntfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_NTFS; + else if (!strcmp("hpfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_HPFS; + else if (!strcmp("ufs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_UFS; + else if (!strcmp("tmpfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_TMPFS; + else if (!strcmp("hfsplus", mntEnt.mnt_type)) + *penmType = RTFSTYPE_HFS; + else if (!strcmp("udf", mntEnt.mnt_type)) + *penmType = RTFSTYPE_UDF; + else if (!strcmp("iso9660", mntEnt.mnt_type)) + *penmType = RTFSTYPE_ISO9660; + else if (!strcmp("smbfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_SMBFS; + else if (!strcmp("cifs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_CIFS; + else if (!strcmp("nfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_NFS; + else if (!strcmp("nfs4", mntEnt.mnt_type)) + *penmType = RTFSTYPE_NFS; + else if (!strcmp("ocfs2", mntEnt.mnt_type)) + *penmType = RTFSTYPE_OCFS2; + else if (!strcmp("sysfs", mntEnt.mnt_type)) + *penmType = RTFSTYPE_SYSFS; + else if (!strcmp("proc", mntEnt.mnt_type)) + *penmType = RTFSTYPE_PROC; + else if ( !strcmp("fuse", mntEnt.mnt_type) + || !strncmp("fuse.", mntEnt.mnt_type, 5) + || !strcmp("fuseblk", mntEnt.mnt_type)) + *penmType = RTFSTYPE_FUSE; + else + { + /* sometimes there are more than one entry for the same partition */ + continue; + } + break; + } + } + } + endmntent(mounted); + } + +#elif defined(RT_OS_SOLARIS) + /* + * Home directories are normally loopback mounted in Solaris 11 (st_fstype=="lofs") + * so statvfs(2) is needed to get the underlying file system information. + */ + struct statvfs statvfsBuf; + if (!statvfs(pszNativeFsPath, &statvfsBuf)) + { + if (!strcmp("zfs", statvfsBuf.f_basetype)) + *penmType = RTFSTYPE_ZFS; + else if (!strcmp("ufs", statvfsBuf.f_basetype)) + *penmType = RTFSTYPE_UFS; + else if (!strcmp("nfs", statvfsBuf.f_basetype)) + *penmType = RTFSTYPE_NFS; + } + +#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + struct statfs statfsBuf; + if (!statfs(pszNativeFsPath, &statfsBuf)) + { + if (!strcmp("hfs", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_HFS; + else if (!strcmp("apfs", statfsBuf.f_fstypename)) /** @todo verify apfs signature. */ + *penmType = RTFSTYPE_APFS; + else if ( !strcmp("fat", statfsBuf.f_fstypename) + || !strcmp("msdos", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_FAT; + else if (!strcmp("ntfs", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_NTFS; + else if (!strcmp("autofs", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_AUTOFS; + else if (!strcmp("devfs", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_DEVFS; + else if (!strcmp("nfs", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_NFS; + else if (!strcmp("ufs", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_UFS; + else if (!strcmp("zfs", statfsBuf.f_fstypename)) + *penmType = RTFSTYPE_ZFS; + } + else + rc = RTErrConvertFromErrno(errno); +#endif + } + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativeFsPath, pszFsPath); + } + + return rc; +} diff --git a/src/VBox/Runtime/r3/posix/fs2-posix.cpp b/src/VBox/Runtime/r3/posix/fs2-posix.cpp new file mode 100644 index 00000000..adbdecfe --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fs2-posix.cpp @@ -0,0 +1,165 @@ +/* $Id: fs2-posix.cpp $ */ +/** @file + * IPRT - File System Helpers, POSIX, Part 2. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define RTTIME_INCL_TIMESPEC +#include <sys/time.h> +#include <sys/param.h> +#ifndef DEV_BSIZE +# include <sys/stat.h> +# if defined(RT_OS_HAIKU) && !defined(S_BLKSIZE) +# define S_BLKSIZE 512 +# endif +# define DEV_BSIZE S_BLKSIZE /** @todo bird: add DEV_BSIZE to sys/param.h on OS/2. */ +#endif + +#include <iprt/fs.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/time.h> +#include "internal/fs.h" + + +/** + * Internal worker function which setups RTFSOBJINFO based on a UNIX stat struct. + * + * @param pObjInfo The file system object info structure to setup. + * @param pStat The stat structure to use. + * @param pszName The filename which this applies to (exe/hidden check). + * @param cbName The length of that filename. (optional, set 0) + */ +void rtFsConvertStatToObjInfo(PRTFSOBJINFO pObjInfo, const struct stat *pStat, const char *pszName, unsigned cbName) +{ + pObjInfo->cbObject = pStat->st_size; + pObjInfo->cbAllocated = pStat->st_blocks * DEV_BSIZE; + +#ifdef HAVE_STAT_NSEC + RTTimeSpecAddNano(RTTimeSpecSetSeconds(&pObjInfo->AccessTime, pStat->st_atime), pStat->st_atimensec); + RTTimeSpecAddNano(RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, pStat->st_mtime), pStat->st_mtimensec); + RTTimeSpecAddNano(RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, pStat->st_ctime), pStat->st_ctimensec); +# ifdef HAVE_STAT_BIRTHTIME + RTTimeSpecAddNano(RTTimeSpecSetSeconds(&pObjInfo->BirthTime, pStat->st_birthtime), pStat->st_birthtimensec); +# endif + +#elif defined(HAVE_STAT_TIMESPEC_BRIEF) + RTTimeSpecSetTimespec(&pObjInfo->AccessTime, &pStat->st_atim); + RTTimeSpecSetTimespec(&pObjInfo->ModificationTime, &pStat->st_mtim); + RTTimeSpecSetTimespec(&pObjInfo->ChangeTime, &pStat->st_ctim); +# ifdef HAVE_STAT_BIRTHTIME + RTTimeSpecSetTimespec(&pObjInfo->BirthTime, &pStat->st_birthtim); +# endif + +#elif defined(HAVE_STAT_TIMESPEC) + RTTimeSpecSetTimespec(&pObjInfo->AccessTime, pStat->st_atimespec); + RTTimeSpecSetTimespec(&pObjInfo->ModificationTime, pStat->st_mtimespec); + RTTimeSpecSetTimespec(&pObjInfo->ChangeTime, pStat->st_ctimespec); +# ifdef HAVE_STAT_BIRTHTIME + RTTimeSpecSetTimespec(&pObjInfo->BirthTime, pStat->st_birthtimespec); +# endif + +#else /* just the normal stuff */ + RTTimeSpecSetSeconds(&pObjInfo->AccessTime, pStat->st_atime); + RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, pStat->st_mtime); + RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, pStat->st_ctime); +# ifdef HAVE_STAT_BIRTHTIME + RTTimeSpecSetSeconds(&pObjInfo->BirthTime, pStat->st_birthtime); +# endif +#endif +#ifndef HAVE_STAT_BIRTHTIME + pObjInfo->BirthTime = pObjInfo->ChangeTime; +#endif + + /* the file mode */ + RTFMODE fMode = pStat->st_mode & RTFS_UNIX_MASK; + Assert(RTFS_UNIX_ISUID == S_ISUID); + Assert(RTFS_UNIX_ISGID == S_ISGID); +#ifdef S_ISTXT + Assert(RTFS_UNIX_ISTXT == S_ISTXT); +#elif defined(S_ISVTX) + Assert(RTFS_UNIX_ISTXT == S_ISVTX); +#else +#error "S_ISVTX / S_ISTXT isn't defined" +#endif + Assert(RTFS_UNIX_IRWXU == S_IRWXU); + Assert(RTFS_UNIX_IRUSR == S_IRUSR); + Assert(RTFS_UNIX_IWUSR == S_IWUSR); + Assert(RTFS_UNIX_IXUSR == S_IXUSR); + Assert(RTFS_UNIX_IRWXG == S_IRWXG); + Assert(RTFS_UNIX_IRGRP == S_IRGRP); + Assert(RTFS_UNIX_IWGRP == S_IWGRP); + Assert(RTFS_UNIX_IXGRP == S_IXGRP); + Assert(RTFS_UNIX_IRWXO == S_IRWXO); + Assert(RTFS_UNIX_IROTH == S_IROTH); + Assert(RTFS_UNIX_IWOTH == S_IWOTH); + Assert(RTFS_UNIX_IXOTH == S_IXOTH); + Assert(RTFS_TYPE_FIFO == S_IFIFO); + Assert(RTFS_TYPE_DEV_CHAR == S_IFCHR); + Assert(RTFS_TYPE_DIRECTORY == S_IFDIR); + Assert(RTFS_TYPE_DEV_BLOCK == S_IFBLK); + Assert(RTFS_TYPE_FILE == S_IFREG); + Assert(RTFS_TYPE_SYMLINK == S_IFLNK); + Assert(RTFS_TYPE_SOCKET == S_IFSOCK); +#ifdef S_IFWHT + Assert(RTFS_TYPE_WHITEOUT == S_IFWHT); +#endif + Assert(RTFS_TYPE_MASK == S_IFMT); + + pObjInfo->Attr.fMode = rtFsModeFromUnix(fMode, pszName, cbName, 0); + + /* additional unix attribs */ + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pObjInfo->Attr.u.Unix.uid = pStat->st_uid; + pObjInfo->Attr.u.Unix.gid = pStat->st_gid; + pObjInfo->Attr.u.Unix.cHardlinks = pStat->st_nlink; + pObjInfo->Attr.u.Unix.INodeIdDevice = pStat->st_dev; + pObjInfo->Attr.u.Unix.INodeId = pStat->st_ino; +#ifdef HAVE_STAT_FLAGS + pObjInfo->Attr.u.Unix.fFlags = pStat->st_flags; +#else + pObjInfo->Attr.u.Unix.fFlags = 0; +#endif +#ifdef HAVE_STAT_GEN + pObjInfo->Attr.u.Unix.GenerationId = pStat->st_gen; +#else + pObjInfo->Attr.u.Unix.GenerationId = 0; +#endif + pObjInfo->Attr.u.Unix.Device = pStat->st_rdev; +} + diff --git a/src/VBox/Runtime/r3/posix/fs3-posix.cpp b/src/VBox/Runtime/r3/posix/fs3-posix.cpp new file mode 100644 index 00000000..368ae8e3 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/fs3-posix.cpp @@ -0,0 +1,94 @@ +/* $Id: fs3-posix.cpp $ */ +/** @file + * IPRT - File System Helpers, POSIX, Part 3. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fs.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/string.h> +#include "internal/fs.h" + +#include <sys/time.h> +#include <grp.h> +#include <pwd.h> + + +/** + * Set user-owner additional attributes. + * + * @param pObjInfo The object info to fill add attrs for. + * @param uid The user id. + */ +void rtFsObjInfoAttrSetUnixOwner(PRTFSOBJINFO pObjInfo, RTUID uid) +{ + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pObjInfo->Attr.u.UnixOwner.uid = uid; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; + + char achBuf[_4K]; + struct passwd Pwd; + struct passwd *pPwd; + int rc = getpwuid_r(uid, &Pwd, achBuf, sizeof(achBuf), &pPwd); + if (!rc && pPwd) + RTStrCopy(pObjInfo->Attr.u.UnixOwner.szName, sizeof(pObjInfo->Attr.u.UnixOwner.szName), pPwd->pw_name); +} + + +/** + * Set user-group additional attributes. + * + * @param pObjInfo The object info to fill add attrs for. + * @param gid The group id. + */ +void rtFsObjInfoAttrSetUnixGroup(PRTFSOBJINFO pObjInfo, RTUID gid) +{ + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pObjInfo->Attr.u.UnixGroup.gid = gid; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + + char achBuf[_4K]; + struct group Grp; + struct group *pGrp; + + int rc = getgrgid_r(gid, &Grp, achBuf, sizeof(achBuf), &pGrp); + if (!rc && pGrp) + RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName), pGrp->gr_name); +} + diff --git a/src/VBox/Runtime/r3/posix/ldrNative-posix.cpp b/src/VBox/Runtime/r3/posix/ldrNative-posix.cpp new file mode 100644 index 00000000..9b33a0ff --- /dev/null +++ b/src/VBox/Runtime/r3/posix/ldrNative-posix.cpp @@ -0,0 +1,207 @@ +/* $Id: ldrNative-posix.cpp $ */ +/** @file + * IPRT - Binary Image Loader, POSIX native. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include <dlfcn.h> + +#include <iprt/ldr.h> +#include <iprt/assert.h> +#include <iprt/path.h> +#include <iprt/alloca.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/ldr.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) +static const char g_szSuff[] = ".DLL"; +#elif defined(RT_OS_L4) +static const char g_szSuff[] = ".s.so"; +#elif defined(RT_OS_DARWIN) +static const char g_szSuff[] = ".dylib"; +#else +static const char g_szSuff[] = ".so"; +#endif + + +DECLHIDDEN(int) rtldrNativeLoad(const char *pszFilename, uintptr_t *phHandle, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + /* + * Do we need to add an extension? + */ + if (!RTPathHasSuffix(pszFilename) && !(fFlags & RTLDRLOAD_FLAGS_NO_SUFFIX)) + { + size_t cch = strlen(pszFilename); + char *psz = (char *)alloca(cch + sizeof(g_szSuff)); + if (!psz) + return RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "alloca failed"); + memcpy(psz, pszFilename, cch); + memcpy(psz + cch, g_szSuff, sizeof(g_szSuff)); + pszFilename = psz; + } + + /* + * Attempt load. + */ + int fFlagsNative = RTLD_NOW; + if (fFlags & RTLDRLOAD_FLAGS_GLOBAL) + fFlagsNative |= RTLD_GLOBAL; + else + fFlagsNative |= RTLD_LOCAL; + void *pvMod = dlopen(pszFilename, fFlagsNative); + if (pvMod) + { + *phHandle = (uintptr_t)pvMod; + return VINF_SUCCESS; + } + + const char *pszDlError = dlerror(); + RTErrInfoSet(pErrInfo, VERR_FILE_NOT_FOUND, pszDlError); + LogRel(("rtldrNativeLoad: dlopen('%s', RTLD_NOW | RTLD_LOCAL) failed: %s\n", pszFilename, pszDlError)); + return VERR_FILE_NOT_FOUND; +} + + +DECLCALLBACK(int) rtldrNativeGetSymbol(PRTLDRMODINTERNAL pMod, const char *pszSymbol, void **ppvValue) +{ + PRTLDRMODNATIVE pModNative = (PRTLDRMODNATIVE)pMod; +#ifdef RT_OS_OS2 + /* Prefix the symbol with an underscore (assuming __cdecl/gcc-default). */ + size_t cch = strlen(pszSymbol); + char *psz = (char *)alloca(cch + 2); + psz[0] = '_'; + memcpy(psz + 1, pszSymbol, cch + 1); + pszSymbol = psz; +#endif + *ppvValue = dlsym((void *)pModNative->hNative, pszSymbol); + if (*ppvValue) + return VINF_SUCCESS; + return VERR_SYMBOL_NOT_FOUND; +} + + +DECLCALLBACK(int) rtldrNativeClose(PRTLDRMODINTERNAL pMod) +{ + PRTLDRMODNATIVE pModNative = (PRTLDRMODNATIVE)pMod; +#ifdef __SANITIZE_ADDRESS__ + /* If we are compiled with enabled address sanitizer (gcc/llvm), don't + * unload the module to prevent <unknown module> in the stack trace */ + pModNative->fFlags |= RTLDRLOAD_FLAGS_NO_UNLOAD; +#endif + if ( (pModNative->fFlags & RTLDRLOAD_FLAGS_NO_UNLOAD) + || !dlclose((void *)pModNative->hNative)) + { + pModNative->hNative = (uintptr_t)0; + return VINF_SUCCESS; + } + Log(("rtldrNativeFree: dlclose(%p) failed: %s\n", pModNative->hNative, dlerror())); + return VERR_GENERAL_FAILURE; +} + + +DECLHIDDEN(int) rtldrNativeLoadSystem(const char *pszFilename, const char *pszExt, uint32_t fFlags, PRTLDRMOD phLdrMod) +{ + /* + * For the present we ASSUME that we can trust dlopen to load what we want + * when not specifying a path. There seems to be very little we can do to + * restrict the places dlopen will search for library without doing + * auditing (linux) or something like that. + */ + Assert(strchr(pszFilename, '/') == NULL); + + uint32_t const fFlags2 = fFlags & ~(RTLDRLOAD_FLAGS_SO_VER_BEGIN_MASK | RTLDRLOAD_FLAGS_SO_VER_END_MASK); + + /* + * If no suffix is given and we haven't got any RTLDRLOAD_FLAGS_SO_VER_ range to work + * with, we can call RTLdrLoadEx directly. + */ + if (!pszExt) + { +#if !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS) + if ( (fFlags & RTLDRLOAD_FLAGS_SO_VER_BEGIN_MASK) >> RTLDRLOAD_FLAGS_SO_VER_BEGIN_SHIFT + == (fFlags & RTLDRLOAD_FLAGS_SO_VER_END_MASK) >> RTLDRLOAD_FLAGS_SO_VER_END_SHIFT) +#endif + return RTLdrLoadEx(pszFilename, phLdrMod, fFlags2, NULL); + pszExt = ""; + } + + /* + * Combine filename and suffix and then do the loading. + */ + size_t const cchFilename = strlen(pszFilename); + size_t const cchSuffix = strlen(pszExt); + char *pszTmp = (char *)alloca(cchFilename + cchSuffix + 16 + 1); + memcpy(pszTmp, pszFilename, cchFilename); + memcpy(&pszTmp[cchFilename], pszExt, cchSuffix); + pszTmp[cchFilename + cchSuffix] = '\0'; + + int rc = RTLdrLoadEx(pszTmp, phLdrMod, fFlags2, NULL); + +#if !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS) + /* + * If no version was given after the .so and do .so.MAJOR search according + * to the range in the fFlags. + */ + if (RT_FAILURE(rc) && !(fFlags & RTLDRLOAD_FLAGS_NO_SUFFIX)) + { + const char *pszActualSuff = RTPathSuffix(pszTmp); + if (pszActualSuff && strcmp(pszActualSuff, ".so") == 0) + { + int32_t const iBegin = (fFlags & RTLDRLOAD_FLAGS_SO_VER_BEGIN_MASK) >> RTLDRLOAD_FLAGS_SO_VER_BEGIN_SHIFT; + int32_t const iEnd = (fFlags & RTLDRLOAD_FLAGS_SO_VER_END_MASK) >> RTLDRLOAD_FLAGS_SO_VER_END_SHIFT; + int32_t const iIncr = iBegin <= iEnd ? 1 : -1; + for (int32_t iMajorVer = iBegin; iMajorVer != iEnd; iMajorVer += iIncr) + { + RTStrPrintf(&pszTmp[cchFilename + cchSuffix], 16 + 1, ".%d", iMajorVer); + rc = RTLdrLoadEx(pszTmp, phLdrMod, fFlags2, NULL); + if (RT_SUCCESS(rc)) + break; + } + } + } +#endif + + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/localipc-posix.cpp b/src/VBox/Runtime/r3/posix/localipc-posix.cpp new file mode 100644 index 00000000..131d78cf --- /dev/null +++ b/src/VBox/Runtime/r3/posix/localipc-posix.cpp @@ -0,0 +1,1172 @@ +/* $Id: localipc-posix.cpp $ */ +/** @file + * IPRT - Local IPC Server & Client, Posix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LOCALIPC +#include "internal/iprt.h" +#include <iprt/localipc.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/log.h> +#include <iprt/poll.h> +#include <iprt/socket.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/path.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#ifndef RT_OS_OS2 +# include <sys/poll.h> +#endif +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +#include <sys/stat.h> +#ifndef AF_LOCAL +# define AF_LOCAL AF_UNIX +#endif + +#include "internal/magics.h" +#include "internal/path.h" +#include "internal/socket.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Local IPC service instance, POSIX. + */ +typedef struct RTLOCALIPCSERVERINT +{ + /** The magic (RTLOCALIPCSERVER_MAGIC). */ + uint32_t u32Magic; + /** The creation flags. */ + uint32_t fFlags; + /** Critical section protecting the structure. */ + RTCRITSECT CritSect; + /** The number of references to the instance. */ + uint32_t volatile cRefs; + /** Indicates that there is a pending cancel request. */ + bool volatile fCancelled; + /** The server socket. */ + RTSOCKET hSocket; + /** Thread currently listening for clients. */ + RTTHREAD hListenThread; + /** The name we bound the server to (native charset encoding). */ + struct sockaddr_un Name; +} RTLOCALIPCSERVERINT; +/** Pointer to a local IPC server instance (POSIX). */ +typedef RTLOCALIPCSERVERINT *PRTLOCALIPCSERVERINT; + + +/** + * Local IPC session instance, POSIX. + */ +typedef struct RTLOCALIPCSESSIONINT +{ + /** The magic (RTLOCALIPCSESSION_MAGIC). */ + uint32_t u32Magic; + /** Critical section protecting the structure. */ + RTCRITSECT CritSect; + /** The number of references to the instance. */ + uint32_t volatile cRefs; + /** Indicates that there is a pending cancel request. */ + bool volatile fCancelled; + /** Set if this is the server side, clear if the client. */ + bool fServerSide; + /** The client socket. */ + RTSOCKET hSocket; + /** Thread currently doing read related activites. */ + RTTHREAD hWriteThread; + /** Thread currently doing write related activies. */ + RTTHREAD hReadThread; +} RTLOCALIPCSESSIONINT; +/** Pointer to a local IPC session instance (Windows). */ +typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT; + + +/** Local IPC name prefix for portable names. */ +#define RTLOCALIPC_POSIX_NAME_PREFIX "/tmp/.iprt-localipc-" + + +/** + * Validates the user specified name. + * + * @returns IPRT status code. + * @param pszName The name to validate. + * @param fNative Whether it's a native name or a portable name. + */ +static int rtLocalIpcPosixValidateName(const char *pszName, bool fNative) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(*pszName, VERR_INVALID_NAME); + + if (!fNative) + { + for (;;) + { + char ch = *pszName++; + if (!ch) + break; + AssertReturn(!RT_C_IS_CNTRL(ch), VERR_INVALID_NAME); + AssertReturn((unsigned)ch < 0x80, VERR_INVALID_NAME); + AssertReturn(ch != '\\', VERR_INVALID_NAME); + AssertReturn(ch != '/', VERR_INVALID_NAME); + } + } + else + { + int rc = RTStrValidateEncoding(pszName); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + + +/** + * Constructs a local (unix) domain socket name. + * + * @returns IPRT status code. + * @param pAddr The address structure to construct the name in. + * @param pcbAddr Where to return the address size. + * @param pszName The user specified name (valid). + * @param fNative Whether it's a native name or a portable name. + */ +static int rtLocalIpcPosixConstructName(struct sockaddr_un *pAddr, uint8_t *pcbAddr, const char *pszName, bool fNative) +{ + const char *pszNativeName; + int rc = rtPathToNative(&pszNativeName, pszName, NULL /*pszBasePath not support*/); + if (RT_SUCCESS(rc)) + { + size_t cchNativeName = strlen(pszNativeName); + size_t cbFull = !fNative ? cchNativeName + sizeof(RTLOCALIPC_POSIX_NAME_PREFIX) : cchNativeName + 1; + if (cbFull <= sizeof(pAddr->sun_path)) + { + RT_ZERO(*pAddr); +#ifdef RT_OS_OS2 /* Size must be exactly right on OS/2. */ + *pcbAddr = sizeof(*pAddr); +#else + *pcbAddr = RT_UOFFSETOF(struct sockaddr_un, sun_path) + (uint8_t)cbFull; +#endif +#ifdef HAVE_SUN_LEN_MEMBER + pAddr->sun_len = *pcbAddr; +#endif + pAddr->sun_family = AF_LOCAL; + + if (!fNative) + { + memcpy(pAddr->sun_path, RTLOCALIPC_POSIX_NAME_PREFIX, sizeof(RTLOCALIPC_POSIX_NAME_PREFIX) - 1); + memcpy(&pAddr->sun_path[sizeof(RTLOCALIPC_POSIX_NAME_PREFIX) - 1], pszNativeName, cchNativeName + 1); + } + else + memcpy(pAddr->sun_path, pszNativeName, cchNativeName + 1); + } + else + rc = VERR_FILENAME_TOO_LONG; + rtPathFreeNative(pszNativeName, pszName); + } + return rc; +} + + + +RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszName, uint32_t fFlags) +{ + /* + * Parameter validation. + */ + AssertPtrReturn(phServer, VERR_INVALID_POINTER); + *phServer = NIL_RTLOCALIPCSERVER; + AssertReturn(!(fFlags & ~RTLOCALIPC_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + int rc = rtLocalIpcPosixValidateName(pszName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + /* + * Allocate memory for the instance and initialize it. + */ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTLOCALIPCSERVER_MAGIC; + pThis->fFlags = fFlags; + pThis->cRefs = 1; + pThis->fCancelled = false; + pThis->hListenThread = NIL_RTTHREAD; + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Create the local (unix) socket and bind to it. + */ + rc = rtSocketCreate(&pThis->hSocket, AF_LOCAL, SOCK_STREAM, 0 /*iProtocol*/, false /*fInheritable*/); + if (RT_SUCCESS(rc)) + { + signal(SIGPIPE, SIG_IGN); /* Required on solaris, at least. */ + + uint8_t cbAddr; + rc = rtLocalIpcPosixConstructName(&pThis->Name, &cbAddr, pszName, + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + rc = rtSocketBindRawAddr(pThis->hSocket, &pThis->Name, cbAddr); + if (rc == VERR_NET_ADDRESS_IN_USE) + { + unlink(pThis->Name.sun_path); + rc = rtSocketBindRawAddr(pThis->hSocket, &pThis->Name, cbAddr); + } + if (RT_SUCCESS(rc)) + { + rc = rtSocketListen(pThis->hSocket, 16); + if (RT_SUCCESS(rc)) + { + LogFlow(("RTLocalIpcServerCreate: Created %p (%s)\n", pThis, pThis->Name.sun_path)); + *phServer = pThis; + return VINF_SUCCESS; + } + unlink(pThis->Name.sun_path); + } + } + RTSocketRelease(pThis->hSocket); + } + RTCritSectDelete(&pThis->CritSect); + } + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + Log(("RTLocalIpcServerCreate: failed, rc=%Rrc\n", rc)); + return rc; +} + + +RTDECL(int) RTLocalIpcServerGrantGroupAccess(RTLOCALIPCSERVER hServer, RTGID gid) +{ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->Name.sun_path[0] != '\0', VERR_INVALID_STATE); + + if (chown(pThis->Name.sun_path, (uid_t)-1, gid) == 0) + { + if (chmod(pThis->Name.sun_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == 0) + { + LogRel2(("RTLocalIpcServerGrantGroupAccess: IPC socket %s access has been granted to group %RTgid\n", + pThis->Name.sun_path, gid)); + return VINF_SUCCESS; + } + LogRel(("RTLocalIpcServerGrantGroupAccess: cannot grant IPC socket %s write permission to group %RTgid: errno=%d\n", + pThis->Name.sun_path, gid, errno)); + } + else + LogRel(("RTLocalIpcServerGrantGroupAccess: cannot change IPC socket %s group ownership to %RTgid: errno=%d\n", + pThis->Name.sun_path, gid, errno)); + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTLocalIpcServerSetAccessMode(RTLOCALIPCSERVER hServer, RTFMODE fMode) +{ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->Name.sun_path[0] != '\0', VERR_INVALID_STATE); + + if (chmod(pThis->Name.sun_path, fMode & RTFS_UNIX_ALL_ACCESS_PERMS) == 0) + return VINF_SUCCESS; + + return RTErrConvertFromErrno(errno); +} + + +/** + * Retains a reference to the server instance. + * + * @returns + * @param pThis The server instance. + */ +DECLINLINE(void) rtLocalIpcServerRetain(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); RT_NOREF_PV(cRefs); +} + + +/** + * Server instance destructor. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The server instance. + */ +static int rtLocalIpcServerDtor(PRTLOCALIPCSERVERINT pThis) +{ + pThis->u32Magic = ~RTLOCALIPCSERVER_MAGIC; + if (RTSocketRelease(pThis->hSocket) == 0) + Log(("rtLocalIpcServerDtor: Released socket\n")); + else + Log(("rtLocalIpcServerDtor: Socket still has references (impossible?)\n")); + RTCritSectDelete(&pThis->CritSect); + unlink(pThis->Name.sun_path); + RTMemFree(pThis); + return VINF_OBJECT_DESTROYED; +} + + +/** + * Releases a reference to the server instance. + * + * @returns VINF_SUCCESS if only release, VINF_OBJECT_DESTROYED if destroyed. + * @param pThis The server instance. + */ +DECLINLINE(int) rtLocalIpcServerRelease(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcServerDtor(pThis); + return VINF_SUCCESS; +} + + +/** + * The core of RTLocalIpcServerCancel, used by both the destroy and cancel APIs. + * + * @returns IPRT status code + * @param pThis The server instance. + */ +static int rtLocalIpcServerCancel(PRTLOCALIPCSERVERINT pThis) +{ + RTCritSectEnter(&pThis->CritSect); + pThis->fCancelled = true; + Log(("rtLocalIpcServerCancel:\n")); + if (pThis->hListenThread != NIL_RTTHREAD) + RTThreadPoke(pThis->hListenThread); + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; +} + + + +RTDECL(int) RTLocalIpcServerDestroy(RTLOCALIPCSERVER hServer) +{ + /* + * Validate input. + */ + if (hServer == NIL_RTLOCALIPCSERVER) + return VINF_SUCCESS; + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the server, releasing the caller's reference to the instance + * data and making sure any other thread in the listen API will wake up. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTLOCALIPCSERVER_MAGIC, RTLOCALIPCSERVER_MAGIC), VERR_WRONG_ORDER); + + rtLocalIpcServerCancel(pThis); + return rtLocalIpcServerRelease(pThis); +} + + +RTDECL(int) RTLocalIpcServerCancel(RTLOCALIPCSERVER hServer) +{ + /* + * Validate input. + */ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the job. + */ + rtLocalIpcServerRetain(pThis); + rtLocalIpcServerCancel(pThis); + rtLocalIpcServerRelease(pThis); + return VINF_SUCCESS; +} + + +RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION phClientSession) +{ + /* + * Validate input. + */ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + + /* + * Begin listening. + */ + rtLocalIpcServerRetain(pThis); + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->hListenThread == NIL_RTTHREAD) + { + pThis->hListenThread = RTThreadSelf(); + + /* + * The listening retry loop. + */ + for (;;) + { + if (!pThis->fCancelled) + { + rc = RTCritSectLeave(&pThis->CritSect); + AssertRCBreak(rc); + + struct sockaddr_un Addr; + size_t cbAddr = sizeof(Addr); + RTSOCKET hClient; + Log(("RTLocalIpcServerListen: Calling rtSocketAccept...\n")); + rc = rtSocketAccept(pThis->hSocket, &hClient, (struct sockaddr *)&Addr, &cbAddr); + Log(("RTLocalIpcServerListen: rtSocketAccept returns %Rrc.\n", rc)); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRCBreakStmt(rc2, rc = RT_SUCCESS(rc) ? rc2 : rc); + + if (RT_SUCCESS(rc)) + { + /* + * Create a client session. + */ + PRTLOCALIPCSESSIONINT pSession = (PRTLOCALIPCSESSIONINT)RTMemAllocZ(sizeof(*pSession)); + if (pSession) + { + pSession->u32Magic = RTLOCALIPCSESSION_MAGIC; + pSession->cRefs = 1; + pSession->fCancelled = false; + pSession->fServerSide = true; + pSession->hSocket = hClient; + pSession->hReadThread = NIL_RTTHREAD; + pSession->hWriteThread = NIL_RTTHREAD; + rc = RTCritSectInit(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + Log(("RTLocalIpcServerListen: Returning new client session: %p\n", pSession)); + *phClientSession = pSession; + break; + } + + RTMemFree(pSession); + } + else + rc = VERR_NO_MEMORY; + } + else if ( rc != VERR_INTERRUPTED + && rc != VERR_TRY_AGAIN) + break; + } + else + { + rc = VERR_CANCELLED; + break; + } + } + + pThis->hListenThread = NIL_RTTHREAD; + } + else + { + AssertFailed(); + rc = VERR_RESOURCE_BUSY; + } + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + rtLocalIpcServerRelease(pThis); + + Log(("RTLocalIpcServerListen: returns %Rrc\n", rc)); + return rc; +} + + +RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *pszName, uint32_t fFlags) +{ + /* + * Parameter validation. + */ + AssertPtrReturn(phSession, VERR_INVALID_POINTER); + *phSession = NIL_RTLOCALIPCSESSION; + + AssertReturn(!(fFlags & ~RTLOCALIPC_C_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + + int rc = rtLocalIpcPosixValidateName(pszName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + /* + * Allocate memory for the instance and initialize it. + */ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTLOCALIPCSESSION_MAGIC; + pThis->cRefs = 1; + pThis->fCancelled = false; + pThis->fServerSide = false; + pThis->hSocket = NIL_RTSOCKET; + pThis->hReadThread = NIL_RTTHREAD; + pThis->hWriteThread = NIL_RTTHREAD; + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Create the local (unix) socket and try connect to the server. + */ + rc = rtSocketCreate(&pThis->hSocket, AF_LOCAL, SOCK_STREAM, 0 /*iProtocol*/, false /*fInheritable*/); + if (RT_SUCCESS(rc)) + { + signal(SIGPIPE, SIG_IGN); /* Required on solaris, at least. */ + + struct sockaddr_un Addr; + uint8_t cbAddr; + rc = rtLocalIpcPosixConstructName(&Addr, &cbAddr, pszName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + rc = rtSocketConnectRaw(pThis->hSocket, &Addr, cbAddr); + if (RT_SUCCESS(rc)) + { + *phSession = pThis; + Log(("RTLocalIpcSessionConnect: Returns new session %p\n", pThis)); + return VINF_SUCCESS; + } + } + RTSocketRelease(pThis->hSocket); + } + RTCritSectDelete(&pThis->CritSect); + } + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + Log(("RTLocalIpcSessionConnect: returns %Rrc\n", rc)); + return rc; +} + + +/** + * Retains a reference to the session instance. + * + * @param pThis The server instance. + */ +DECLINLINE(void) rtLocalIpcSessionRetain(PRTLOCALIPCSESSIONINT pThis) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); RT_NOREF_PV(cRefs); +} + + +RTDECL(uint32_t) RTLocalIpcSessionRetain(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); + return cRefs; +} + + +/** + * Session instance destructor. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The server instance. + */ +static int rtLocalIpcSessionDtor(PRTLOCALIPCSESSIONINT pThis) +{ + pThis->u32Magic = ~RTLOCALIPCSESSION_MAGIC; + if (RTSocketRelease(pThis->hSocket) == 0) + Log(("rtLocalIpcSessionDtor: Released socket\n")); + else + Log(("rtLocalIpcSessionDtor: Socket still has references (impossible?)\n")); + RTCritSectDelete(&pThis->CritSect); + RTMemFree(pThis); + return VINF_OBJECT_DESTROYED; +} + + +/** + * Releases a reference to the session instance. + * + * @returns VINF_SUCCESS or VINF_OBJECT_DESTROYED as appropriate. + * @param pThis The session instance. + */ +DECLINLINE(int) rtLocalIpcSessionRelease(PRTLOCALIPCSESSIONINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcSessionDtor(pThis); + Log(("rtLocalIpcSessionRelease: %u refs left\n", cRefs)); + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTLocalIpcSessionRelease(RTLOCALIPCSESSION hSession) +{ + if (hSession == NIL_RTLOCALIPCSESSION) + return 0; + + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (cRefs) + Log(("RTLocalIpcSessionRelease: %u refs left\n", cRefs)); + else + rtLocalIpcSessionDtor(pThis); + return cRefs; +} + + +/** + * The core of RTLocalIpcSessionCancel, used by both the destroy and cancel APIs. + * + * @returns IPRT status code + * @param pThis The session instance. + */ +static int rtLocalIpcSessionCancel(PRTLOCALIPCSESSIONINT pThis) +{ + RTCritSectEnter(&pThis->CritSect); + pThis->fCancelled = true; + Log(("rtLocalIpcSessionCancel:\n")); + if (pThis->hReadThread != NIL_RTTHREAD) + RTThreadPoke(pThis->hReadThread); + if (pThis->hWriteThread != NIL_RTTHREAD) + RTThreadPoke(pThis->hWriteThread); + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; +} + + +RTDECL(int) RTLocalIpcSessionClose(RTLOCALIPCSESSION hSession) +{ + /* + * Validate input. + */ + if (hSession == NIL_RTLOCALIPCSESSION) + return VINF_SUCCESS; + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the session, releasing the caller's reference to the instance + * data and making sure any other thread in the listen API will wake up. + */ + Log(("RTLocalIpcSessionClose:\n")); + + rtLocalIpcSessionCancel(pThis); + return rtLocalIpcSessionRelease(pThis); +} + + +RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession) +{ + /* + * Validate input. + */ + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the job. + */ + rtLocalIpcSessionRetain(pThis); + rtLocalIpcSessionCancel(pThis); + rtLocalIpcSessionRelease(pThis); + return VINF_SUCCESS; +} + + +/** + * Checks if the socket has has a HUP condition after reading zero bytes. + * + * @returns true if HUP, false if no. + * @param pThis The IPC session handle. + */ +static bool rtLocalIpcPosixHasHup(PRTLOCALIPCSESSIONINT pThis) +{ + int fdNative = RTSocketToNative(pThis->hSocket); + +#if !defined(RT_OS_OS2) && !defined(RT_OS_SOLARIS) + struct pollfd PollFd; + RT_ZERO(PollFd); + PollFd.fd = fdNative; + PollFd.events = POLLHUP | POLLERR; + if (poll(&PollFd, 1, 0) <= 0) + return false; + if (!(PollFd.revents & (POLLHUP | POLLERR))) + return false; +#else /* RT_OS_OS2 || RT_OS_SOLARIS */ + /* + * OS/2: No native poll, do zero byte send to check for EPIPE. + * Solaris: We don't get POLLHUP. + */ + uint8_t bDummy; + ssize_t rcSend = send(fdNative, &bDummy, 0, 0); + if (rcSend >= 0 || (errno != EPIPE && errno != ECONNRESET)) + return false; +#endif /* RT_OS_OS2 || RT_OS_SOLARIS */ + + /* + * We've established EPIPE. Now make sure there aren't any last bytes to + * read that came in between the recv made by the caller and the disconnect. + */ + uint8_t bPeek; + ssize_t rcRecv = recv(fdNative, &bPeek, 1, MSG_DONTWAIT | MSG_PEEK); + return rcRecv <= 0; +} + + +RTDECL(int) RTLocalIpcSessionRead(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + /* + * Validate input. + */ + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the job. + */ + rtLocalIpcSessionRetain(pThis); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->hReadThread == NIL_RTTHREAD) + { + pThis->hReadThread = RTThreadSelf(); + + for (;;) + { + if (!pThis->fCancelled) + { + rc = RTCritSectLeave(&pThis->CritSect); + AssertRCBreak(rc); + + rc = RTSocketRead(pThis->hSocket, pvBuf, cbToRead, pcbRead); + + /* Detect broken pipe. */ + if (rc == VINF_SUCCESS) + { + if (!pcbRead || *pcbRead) + { /* likely */ } + else if (rtLocalIpcPosixHasHup(pThis)) + rc = VERR_BROKEN_PIPE; + } + else if (rc == VERR_NET_CONNECTION_RESET_BY_PEER || rc == VERR_NET_SHUTDOWN) + rc = VERR_BROKEN_PIPE; + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRCBreakStmt(rc2, rc = RT_SUCCESS(rc) ? rc2 : rc); + + if ( rc == VERR_INTERRUPTED + || rc == VERR_TRY_AGAIN) + continue; + } + else + rc = VERR_CANCELLED; + break; + } + + pThis->hReadThread = NIL_RTTHREAD; + } + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + + rtLocalIpcSessionRelease(pThis); + return rc; +} + + +RTDECL(int) RTLocalIpcSessionReadNB(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + /* + * Validate input. + */ + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the job. + */ + rtLocalIpcSessionRetain(pThis); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->hReadThread == NIL_RTTHREAD) + { + pThis->hReadThread = RTThreadSelf(); /* not really required, but whatever. */ + + for (;;) + { + if (!pThis->fCancelled) + { + rc = RTSocketReadNB(pThis->hSocket, pvBuf, cbToRead, pcbRead); + + /* Detect broken pipe. */ + if (rc == VINF_SUCCESS) + { + if (!pcbRead || *pcbRead) + { /* likely */ } + else if (rtLocalIpcPosixHasHup(pThis)) + rc = VERR_BROKEN_PIPE; + } + else if (rc == VERR_NET_CONNECTION_RESET_BY_PEER || rc == VERR_NET_SHUTDOWN) + rc = VERR_BROKEN_PIPE; + + if (rc == VERR_INTERRUPTED) + continue; + } + else + rc = VERR_CANCELLED; + break; + } + + pThis->hReadThread = NIL_RTTHREAD; + } + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + + rtLocalIpcSessionRelease(pThis); + return rc; +} + + +RTDECL(int) RTLocalIpcSessionWrite(RTLOCALIPCSESSION hSession, const void *pvBuf, size_t cbToWrite) +{ + /* + * Validate input. + */ + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the job. + */ + rtLocalIpcSessionRetain(pThis); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->hWriteThread == NIL_RTTHREAD) + { + pThis->hWriteThread = RTThreadSelf(); + + for (;;) + { + if (!pThis->fCancelled) + { + rc = RTCritSectLeave(&pThis->CritSect); + AssertRCBreak(rc); + + rc = RTSocketWrite(pThis->hSocket, pvBuf, cbToWrite); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRCBreakStmt(rc2, rc = RT_SUCCESS(rc) ? rc2 : rc); + + if ( rc == VERR_INTERRUPTED + || rc == VERR_TRY_AGAIN) + continue; + } + else + rc = VERR_CANCELLED; + break; + } + + pThis->hWriteThread = NIL_RTTHREAD; + } + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + + rtLocalIpcSessionRelease(pThis); + return rc; +} + + +RTDECL(int) RTLocalIpcSessionFlush(RTLOCALIPCSESSION hSession) +{ + /* + * Validate input. + */ + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * This is a no-op because apparently write doesn't return until the + * result is read. At least that's what the reply to a 2003-04-08 LKML + * posting title "fsync() on unix domain sockets?" indicates. + * + * For conformity, make sure there isn't any active writes concurrent to this call. + */ + rtLocalIpcSessionRetain(pThis); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->hWriteThread == NIL_RTTHREAD) + rc = RTCritSectLeave(&pThis->CritSect); + else + { + rc = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = VERR_RESOURCE_BUSY; + } + } + + rtLocalIpcSessionRelease(pThis); + return rc; +} + + +RTDECL(int) RTLocalIpcSessionWaitForData(RTLOCALIPCSESSION hSession, uint32_t cMillies) +{ + /* + * Validate input. + */ + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the job. + */ + rtLocalIpcSessionRetain(pThis); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->hReadThread == NIL_RTTHREAD) + { + pThis->hReadThread = RTThreadSelf(); + uint64_t const msStart = RTTimeMilliTS(); + RTMSINTERVAL const cMsOriginalTimeout = cMillies; + + for (;;) + { + if (!pThis->fCancelled) + { + rc = RTCritSectLeave(&pThis->CritSect); + AssertRCBreak(rc); + + uint32_t fEvents = 0; +#ifdef RT_OS_OS2 + /* This doesn't give us any error condition on hangup, so use HUP check. */ + Log(("RTLocalIpcSessionWaitForData: Calling RTSocketSelectOneEx...\n")); + rc = RTSocketSelectOneEx(pThis->hSocket, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, &fEvents, cMillies); + Log(("RTLocalIpcSessionWaitForData: RTSocketSelectOneEx returns %Rrc, fEvents=%#x\n", rc, fEvents)); + if (RT_SUCCESS(rc) && fEvents == RTPOLL_EVT_READ && rtLocalIpcPosixHasHup(pThis)) + rc = VERR_BROKEN_PIPE; +#else +/** @todo RTSocketPoll? */ + /* POLLHUP will be set on hangup. */ + struct pollfd PollFd; + RT_ZERO(PollFd); + PollFd.fd = RTSocketToNative(pThis->hSocket); + PollFd.events = POLLHUP | POLLERR | POLLIN; + Log(("RTLocalIpcSessionWaitForData: Calling poll...\n")); + int cFds = poll(&PollFd, 1, cMillies == RT_INDEFINITE_WAIT ? -1 : (int)cMillies); + if (cFds >= 1) + { + /* Linux & Darwin sets both POLLIN and POLLHUP when the pipe is + broken and but no more data to read. Google hints at NetBSD + returning more sane values (POLLIN till no more data, then + POLLHUP). Solairs OTOH, doesn't ever seem to return POLLHUP. */ + fEvents = RTPOLL_EVT_READ; + if ( (PollFd.revents & (POLLHUP | POLLERR)) + && !(PollFd.revents & POLLIN)) + fEvents = RTPOLL_EVT_ERROR; +# if defined(RT_OS_SOLARIS) + else if (PollFd.revents & POLLIN) +# else + else if ((PollFd.revents & (POLLIN | POLLHUP)) == (POLLIN | POLLHUP)) +# endif + { + /* Check if there is actually data available. */ + uint8_t bPeek; + ssize_t rcRecv = recv(PollFd.fd, &bPeek, 1, MSG_DONTWAIT | MSG_PEEK); + if (rcRecv <= 0) + fEvents = RTPOLL_EVT_ERROR; + } + rc = VINF_SUCCESS; + } + else if (rc == 0) + rc = VERR_TIMEOUT; + else + rc = RTErrConvertFromErrno(errno); + Log(("RTLocalIpcSessionWaitForData: poll returns %u (rc=%d), revents=%#x\n", cFds, rc, PollFd.revents)); +#endif + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRCBreakStmt(rc2, rc = RT_SUCCESS(rc) ? rc2 : rc); + + if (RT_SUCCESS(rc)) + { + if (pThis->fCancelled) + rc = VERR_CANCELLED; + else if (fEvents & RTPOLL_EVT_ERROR) + rc = VERR_BROKEN_PIPE; + } + else if ( rc == VERR_INTERRUPTED + || rc == VERR_TRY_AGAIN) + { + /* Recalc cMillies. */ + if (cMsOriginalTimeout != RT_INDEFINITE_WAIT) + { + uint64_t cMsElapsed = RTTimeMilliTS() - msStart; + cMillies = cMsElapsed >= cMsOriginalTimeout ? 0 : cMsOriginalTimeout - (RTMSINTERVAL)cMsElapsed; + } + continue; + } + } + else + rc = VERR_CANCELLED; + break; + } + + pThis->hReadThread = NIL_RTTHREAD; + } + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + + rtLocalIpcSessionRelease(pThis); + return rc; +} + + +/** + * Get IPC session socket peer credentials. + * + * @returns IPRT status code. + * @param hSession IPC session handle. + * @param pProcess Where to return the remote peer's PID (can be NULL). + * @param pUid Where to return the remote peer's UID (can be NULL). + * @param pGid Where to return the remote peer's GID (can be NULL). + */ +static int rtLocalIpcSessionQueryUcred(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess, PRTUID pUid, PRTGID pGid) +{ + PRTLOCALIPCSESSIONINT pThis = hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + +#if defined(RT_OS_LINUX) + struct ucred PeerCred = { (pid_t)NIL_RTPROCESS, (uid_t)NIL_RTUID, (gid_t)NIL_RTGID }; + socklen_t cbPeerCred = sizeof(PeerCred); + + rtLocalIpcSessionRetain(pThis); + + int rc = RTCritSectEnter(&pThis->CritSect);; + if (RT_SUCCESS(rc)) + { + if (getsockopt(RTSocketToNative(pThis->hSocket), SOL_SOCKET, SO_PEERCRED, &PeerCred, &cbPeerCred) >= 0) + { + if (pProcess) + *pProcess = PeerCred.pid; + if (pUid) + *pUid = PeerCred.uid; + if (pGid) + *pGid = PeerCred.gid; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + + rtLocalIpcSessionRelease(pThis); + + return rc; + +#else + /** @todo Implement on other platforms too (mostly platform specific this). + * Solaris: getpeerucred? Darwin: LOCALPEERCRED or getpeereid? */ + RT_NOREF(pProcess, pUid, pGid); + return VERR_NOT_SUPPORTED; +#endif +} + + +RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess) +{ + return rtLocalIpcSessionQueryUcred(hSession, pProcess, NULL, NULL); +} + + +RTDECL(int) RTLocalIpcSessionQueryUserId(RTLOCALIPCSESSION hSession, PRTUID pUid) +{ + return rtLocalIpcSessionQueryUcred(hSession, NULL, pUid, NULL); +} + +RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGid) +{ + return rtLocalIpcSessionQueryUcred(hSession, NULL, NULL, pGid); +} + diff --git a/src/VBox/Runtime/r3/posix/path-posix.cpp b/src/VBox/Runtime/r3/posix/path-posix.cpp new file mode 100644 index 00000000..04462315 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/path-posix.cpp @@ -0,0 +1,418 @@ +/* $Id: path-posix.cpp $ */ +/** @file + * IPRT - Path Manipulation, POSIX, Part 1. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PATH +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.h> +#include <sys/types.h> +#include <pwd.h> + +#include <iprt/path.h> +#include <iprt/env.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/path.h" +#include "internal/process.h" +#include "internal/fs.h" + + + +RTDECL(int) RTPathReal(const char *pszPath, char *pszRealPath, size_t cchRealPath) +{ + /* + * Convert input. + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + /* + * On POSIX platforms the API doesn't take a length parameter, which makes it + * a little bit more work. + */ + char szTmpPath[PATH_MAX + 1]; + const char *psz = realpath(pszNativePath, szTmpPath); + if (psz) + rc = rtPathFromNativeCopy(pszRealPath, cchRealPath, szTmpPath, NULL); + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTPathReal(%p:{%s}, %p:{%s}, %u): returns %Rrc\n", pszPath, pszPath, + pszRealPath, RT_SUCCESS(rc) ? pszRealPath : "<failed>", cchRealPath, rc)); + return rc; +} + + +RTR3DECL(int) RTPathSetMode(const char *pszPath, RTFMODE fMode) +{ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + + int rc; + fMode = rtFsModeNormalize(fMode, pszPath, 0, 0); + if (rtFsModeIsValidPermissions(fMode)) + { + char const *pszNativePath; + rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + if (chmod(pszNativePath, fMode & RTFS_UNIX_MASK) != 0) + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativePath, pszPath); + } + } + else + { + AssertMsgFailed(("Invalid file mode! %RTfmode\n", fMode)); + rc = VERR_INVALID_FMODE; + } + return rc; +} + + +/** + * Checks if two files are the one and same file. + */ +static bool rtPathSame(const char *pszNativeSrc, const char *pszNativeDst) +{ + struct stat SrcStat; + if (lstat(pszNativeSrc, &SrcStat)) + return false; + struct stat DstStat; + if (lstat(pszNativeDst, &DstStat)) + return false; + Assert(SrcStat.st_dev && DstStat.st_dev); + Assert(SrcStat.st_ino && DstStat.st_ino); + if ( SrcStat.st_dev == DstStat.st_dev + && SrcStat.st_ino == DstStat.st_ino + && (SrcStat.st_mode & S_IFMT) == (DstStat.st_mode & S_IFMT)) + return true; + return false; +} + + +/** + * Worker for RTPathRename, RTDirRename, RTFileRename. + * + * @returns IPRT status code. + * @param pszSrc The source path. + * @param pszDst The destination path. + * @param fRename The rename flags. + * @param fFileType The filetype. We use the RTFMODE filetypes here. If it's 0, + * anything goes. If it's RTFS_TYPE_DIRECTORY we'll check that the + * source is a directory. If Its RTFS_TYPE_FILE we'll check that it's + * not a directory (we are NOT checking whether it's a file). + */ +DECLHIDDEN(int) rtPathPosixRename(const char *pszSrc, const char *pszDst, unsigned fRename, RTFMODE fFileType) +{ + /* + * Convert the paths. + */ + char const *pszNativeSrc; + int rc = rtPathToNative(&pszNativeSrc, pszSrc, NULL); + if (RT_SUCCESS(rc)) + { + char const *pszNativeDst; + rc = rtPathToNative(&pszNativeDst, pszDst, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Check that the source exists and that any types that's specified matches. + * We have to check this first to avoid getting errnous VERR_ALREADY_EXISTS + * errors from the next step. + * + * There are race conditions here (perhaps unlikely ones, but still), but I'm + * afraid there is little with can do to fix that. + */ + struct stat SrcStat; + if (lstat(pszNativeSrc, &SrcStat)) + rc = RTErrConvertFromErrno(errno); + else if (!fFileType) + rc = VINF_SUCCESS; + else if (RTFS_IS_DIRECTORY(fFileType)) + rc = S_ISDIR(SrcStat.st_mode) ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY; + else + rc = S_ISDIR(SrcStat.st_mode) ? VERR_IS_A_DIRECTORY : VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { + bool fSameFile = false; + + /* + * Check if the target exists, rename is rather destructive. + * We'll have to make sure we don't overwrite the source! + * Another race condition btw. + */ + struct stat DstStat; + if (lstat(pszNativeDst, &DstStat)) + rc = errno == ENOENT ? VINF_SUCCESS : RTErrConvertFromErrno(errno); + else + { + Assert(SrcStat.st_dev && DstStat.st_dev); + Assert(SrcStat.st_ino && DstStat.st_ino); + if ( SrcStat.st_dev == DstStat.st_dev + && SrcStat.st_ino == DstStat.st_ino + && (SrcStat.st_mode & S_IFMT) == (DstStat.st_mode & S_IFMT)) + { + /* + * It's likely that we're talking about the same file here. + * We should probably check paths or whatever, but for now this'll have to be enough. + */ + fSameFile = true; + } + if (fSameFile) + rc = VINF_SUCCESS; + else if (S_ISDIR(DstStat.st_mode) || !(fRename & RTPATHRENAME_FLAGS_REPLACE)) + rc = VERR_ALREADY_EXISTS; + else + rc = VINF_SUCCESS; + + } + if (RT_SUCCESS(rc)) + { + if (!rename(pszNativeSrc, pszNativeDst)) + rc = VINF_SUCCESS; + else if ( (fRename & RTPATHRENAME_FLAGS_REPLACE) + && (errno == ENOTDIR || errno == EEXIST)) + { + /* + * Check that the destination isn't a directory. + * Yet another race condition. + */ + if (rtPathSame(pszNativeSrc, pszNativeDst)) + { + rc = VINF_SUCCESS; + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): appears to be the same file... (errno=%d)\n", + pszSrc, pszDst, fRename, fFileType, errno)); + } + else + { + if (lstat(pszNativeDst, &DstStat)) + rc = errno != ENOENT ? RTErrConvertFromErrno(errno) : VINF_SUCCESS; + else if (S_ISDIR(DstStat.st_mode)) + rc = VERR_ALREADY_EXISTS; + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { + if (!unlink(pszNativeDst)) + { + if (!rename(pszNativeSrc, pszNativeDst)) + rc = VINF_SUCCESS; + else + { + rc = RTErrConvertFromErrno(errno); + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + } + else + { + rc = RTErrConvertFromErrno(errno); + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): failed to unlink dst rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + } + else + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): dst !dir check failed rc=%Rrc\n", + pszSrc, pszDst, fRename, fFileType, rc)); + } + } + else + { + rc = RTErrConvertFromErrno(errno); + if (errno == ENOTDIR) + rc = VERR_ALREADY_EXISTS; /* unless somebody is racing us, this is the right interpretation */ + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + } + else + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): destination check failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + else + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): source type check failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + + rtPathFreeNative(pszNativeDst, pszDst); + } + rtPathFreeNative(pszNativeSrc, pszSrc); + } + return rc; +} + + +RTR3DECL(int) RTPathRename(const char *pszSrc, const char *pszDst, unsigned fRename) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + AssertMsgReturn(*pszSrc, ("%p\n", pszSrc), VERR_INVALID_PARAMETER); + AssertMsgReturn(*pszDst, ("%p\n", pszDst), VERR_INVALID_PARAMETER); + AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER); + + /* + * Hand it to the worker. + */ + int rc = rtPathPosixRename(pszSrc, pszDst, fRename, 0); + + Log(("RTPathRename(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", pszSrc, pszSrc, pszDst, pszDst, fRename, rc)); + return rc; +} + + +RTR3DECL(int) RTPathUnlink(const char *pszPath, uint32_t fUnlink) +{ + RT_NOREF_PV(pszPath); RT_NOREF_PV(fUnlink); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(bool) RTPathExists(const char *pszPath) +{ + return RTPathExistsEx(pszPath, RTPATH_F_FOLLOW_LINK); +} + + +RTDECL(bool) RTPathExistsEx(const char *pszPath, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, false); + AssertReturn(*pszPath, false); + Assert(RTPATH_F_IS_VALID(fFlags, 0)); + + /* + * Convert the path and check if it exists using stat(). + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat Stat; + if (fFlags & RTPATH_F_FOLLOW_LINK) + rc = stat(pszNativePath, &Stat); + else + rc = lstat(pszNativePath, &Stat); + if (!rc) + rc = VINF_SUCCESS; + else + rc = VERR_GENERAL_FAILURE; + rtPathFreeNative(pszNativePath, pszPath); + } + return RT_SUCCESS(rc); +} + + +RTDECL(int) RTPathGetCurrent(char *pszPath, size_t cchPath) +{ + /* + * Try with a reasonably sized buffer first. + */ + char szNativeCurDir[RTPATH_MAX]; + if (getcwd(szNativeCurDir, sizeof(szNativeCurDir)) != NULL) + return rtPathFromNativeCopy(pszPath, cchPath, szNativeCurDir, NULL); + + /* + * Retry a few times with really big buffers if we failed because CWD is unreasonably long. + */ + int iErr = errno; + if (iErr != ERANGE) + return RTErrConvertFromErrno(iErr); + + size_t cbNativeTmp = RTPATH_BIG_MAX; + for (;;) + { + char *pszNativeTmp = (char *)RTMemTmpAlloc(cbNativeTmp); + if (!pszNativeTmp) + return VERR_NO_TMP_MEMORY; + if (getcwd(pszNativeTmp, cbNativeTmp) != NULL) + { + int rc = rtPathFromNativeCopy(pszPath, cchPath, pszNativeTmp, NULL); + RTMemTmpFree(pszNativeTmp); + return rc; + } + iErr = errno; + RTMemTmpFree(pszNativeTmp); + if (iErr != ERANGE) + return RTErrConvertFromErrno(iErr); + + cbNativeTmp += RTPATH_BIG_MAX; + if (cbNativeTmp > RTPATH_BIG_MAX * 4) + return VERR_FILENAME_TOO_LONG; + } +} + + +RTDECL(int) RTPathSetCurrent(const char *pszPath) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + + /* + * Change the directory. + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + if (chdir(pszNativePath)) + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativePath, pszPath); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/path2-posix.cpp b/src/VBox/Runtime/r3/posix/path2-posix.cpp new file mode 100644 index 00000000..9af9512f --- /dev/null +++ b/src/VBox/Runtime/r3/posix/path2-posix.cpp @@ -0,0 +1,316 @@ +/* $Id: path2-posix.cpp $ */ +/** @file + * IPRT - Path Manipulation, POSIX, Part 2 - RTPathQueryInfo. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PATH +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.h> +#include <sys/types.h> + +#include <iprt/path.h> +#include <iprt/env.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/path.h" +#include "internal/process.h" +#include "internal/fs.h" + + +RTR3DECL(int) RTPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + return RTPathQueryInfoEx(pszPath, pObjInfo, enmAdditionalAttribs, RTPATH_F_ON_LINK); +} + + +RTR3DECL(int) RTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + AssertMsgReturn( enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING + && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + ("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs), + VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Convert the filename. + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat Stat; + if (fFlags & RTPATH_F_FOLLOW_LINK) + rc = stat(pszNativePath, &Stat); + else + rc = lstat(pszNativePath, &Stat); /** @todo how doesn't have lstat again? */ + if (!rc) + { + rtFsConvertStatToObjInfo(pObjInfo, &Stat, pszPath, 0); + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_NOTHING: + case RTFSOBJATTRADD_UNIX: + Assert(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX); + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + rtFsObjInfoAttrSetUnixOwner(pObjInfo, Stat.st_uid); + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + rtFsObjInfoAttrSetUnixGroup(pObjInfo, Stat.st_gid); + break; + + case RTFSOBJATTRADD_EASIZE: + /** @todo Use SGI extended attribute interface to query EA info. */ + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR; + } + } + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTPathQueryInfoEx(%p:{%s}, pObjInfo=%p, %d): returns %Rrc\n", + pszPath, pszPath, pObjInfo, enmAdditionalAttribs, rc)); + return rc; +} + + +RTR3DECL(int) RTPathSetTimes(const char *pszPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + return RTPathSetTimesEx(pszPath, pAccessTime, pModificationTime, pChangeTime, pBirthTime, RTPATH_F_ON_LINK); +} + + +RTR3DECL(int) RTPathSetTimesEx(const char *pszPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pAccessTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pModificationTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pChangeTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pBirthTime, VERR_INVALID_POINTER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Convert the paths. + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO ObjInfo; + + /* + * If it's a no-op, we'll only verify the existance of the file. + */ + if (!pAccessTime && !pModificationTime) + rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, fFlags); + else + { + /* + * Convert the input to timeval, getting the missing one if necessary, + * and call the API which does the change. + */ + struct timeval aTimevals[2]; + if (pAccessTime && pModificationTime) + { + RTTimeSpecGetTimeval(pAccessTime, &aTimevals[0]); + RTTimeSpecGetTimeval(pModificationTime, &aTimevals[1]); + } + else + { + rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, fFlags); + if (RT_SUCCESS(rc)) + { + RTTimeSpecGetTimeval(pAccessTime ? pAccessTime : &ObjInfo.AccessTime, &aTimevals[0]); + RTTimeSpecGetTimeval(pModificationTime ? pModificationTime : &ObjInfo.ModificationTime, &aTimevals[1]); + } + else + Log(("RTPathSetTimes('%s',%p,%p,,): RTPathQueryInfo failed with %Rrc\n", + pszPath, pAccessTime, pModificationTime, rc)); + } + if (RT_SUCCESS(rc)) + { + if (fFlags & RTPATH_F_FOLLOW_LINK) + { + if (utimes(pszNativePath, aTimevals)) + rc = RTErrConvertFromErrno(errno); + } +#if (defined(RT_OS_DARWIN) && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050) \ + || defined(RT_OS_FREEBSD) \ + || defined(RT_OS_LINUX) \ + || defined(RT_OS_OS2) /** @todo who really has lutimes? */ + else + { + if (lutimes(pszNativePath, aTimevals)) + { + /* If lutimes is not supported (e.g. linux < 2.6.22), try fall back on utimes: */ + if (errno != ENOSYS) + rc = RTErrConvertFromErrno(errno); + else + { + if (pAccessTime && pModificationTime) + rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, fFlags); + if (RT_SUCCESS(rc) && !RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) + { + if (utimes(pszNativePath, aTimevals)) + rc = RTErrConvertFromErrno(errno); + } + else + rc = VERR_NOT_SUPPORTED; + } + } + } +#else + else + { + if (pAccessTime && pModificationTime) + rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, fFlags); + if (RT_SUCCESS(rc) && RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) + rc = VERR_NS_SYMLINK_SET_TIME; + else if (RT_SUCCESS(rc)) + { + if (utimes(pszNativePath, aTimevals)) + rc = RTErrConvertFromErrno(errno); + } + } +#endif + if (RT_FAILURE(rc)) + Log(("RTPathSetTimes('%s',%p,%p,,): failed with %Rrc and errno=%d\n", + pszPath, pAccessTime, pModificationTime, rc, errno)); + } + } + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTPathSetTimes(%p:{%s}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}): return %Rrc\n", + pszPath, pszPath, pAccessTime, pAccessTime, pModificationTime, pModificationTime, + pChangeTime, pChangeTime, pBirthTime, pBirthTime, rc)); + return rc; +} + + +RTR3DECL(int) RTPathSetOwner(const char *pszPath, uint32_t uid, uint32_t gid) +{ + return RTPathSetOwnerEx(pszPath, uid, gid, RTPATH_F_ON_LINK); +} + + +RTR3DECL(int) RTPathSetOwnerEx(const char *pszPath, uint32_t uid, uint32_t gid, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + uid_t uidNative = uid != NIL_RTUID ? (uid_t)uid : (uid_t)-1; + AssertReturn(uid == uidNative, VERR_INVALID_PARAMETER); + gid_t gidNative = gid != NIL_RTGID ? (gid_t)gid : (uid_t)-1; + AssertReturn(gid == gidNative, VERR_INVALID_PARAMETER); + + /* + * Convert the path. + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + if (fFlags & RTPATH_F_FOLLOW_LINK) + { + if (chown(pszNativePath, uidNative, gidNative)) + rc = RTErrConvertFromErrno(errno); + } +#if 1 + else + { + if (lchown(pszNativePath, uidNative, gidNative)) + rc = RTErrConvertFromErrno(errno); + } +#else + else + { + RTFSOBJINFO ObjInfo; + rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, fFlags); + if (RT_SUCCESS(rc) && RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) + rc = VERR_NS_SYMLINK_CHANGE_OWNER; + else if (RT_SUCCESS(rc)) + { + if (lchown(pszNativePath, uidNative, gidNative)) + rc = RTErrConvertFromErrno(errno); + } + } +#endif + if (RT_FAILURE(rc)) + Log(("RTPathSetOwnerEx('%s',%d,%d): failed with %Rrc and errno=%d\n", + pszPath, uid, gid, rc, errno)); + + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTPathSetOwnerEx(%p:{%s}, uid=%d, gid=%d): return %Rrc\n", + pszPath, pszPath, uid, gid, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/pathhost-posix.cpp b/src/VBox/Runtime/r3/posix/pathhost-posix.cpp new file mode 100644 index 00000000..b0c4fe46 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/pathhost-posix.cpp @@ -0,0 +1,294 @@ +/* $Id: pathhost-posix.cpp $ */ +/** @file + * IPRT - Path Conversions, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PATH +#include "internal/iprt.h" +#include "internal/path.h" +#include "internal/string.h" +#include "internal/thread.h" + +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/once.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Initialize once object. */ +static RTONCE g_OnceInitPathConv = RTONCE_INITIALIZER; +/** If set, then we can pass UTF-8 thru directly. */ +static bool g_fPassthruUtf8 = false; +/** The UTF-8 to FS iconv cache entry. */ +static RTSTRICONV g_enmUtf8ToFsIdx = RTSTRICONV_UTF8_TO_LOCALE; +/** The FS to UTF-8 iconv cache entry. */ +static RTSTRICONV g_enmFsToUtf8Idx = RTSTRICONV_LOCALE_TO_UTF8; +/** The codeset we're using. */ +static char g_szFsCodeset[32]; + + +/** + * Do a case insensitive compare where the 2nd string is known and can be case + * folded when writing the code. + * + * @returns see strcmp. + * @param pszStr1 The string to compare against pszLower and + * pszUpper. + * @param pszUpper The upper case edition of the 2nd string. + * @param pszLower The lower case edition of the 2nd string. + */ +static int rtPathStrICmp(const char *pszStr1, const char *pszUpper, const char *pszLower) +{ + Assert(strlen(pszLower) == strlen(pszUpper)); + for (;;) + { + char ch1 = *pszStr1++; + char ch2Upper = *pszUpper++; + char ch2Lower = *pszLower++; + if ( ch1 != ch2Upper + && ch1 != ch2Lower) + return ch1 < ch2Upper ? -1 : 1; + if (!ch1) + return 0; + } +} + +/** + * Is the specified codeset something we can treat as UTF-8. + * + * @returns true if we can do UTF-8 passthru, false if not. + * @param pszCodeset The codeset in question. + */ +static bool rtPathConvInitIsUtf8(const char *pszCodeset) +{ + /* Paranoia. */ + if (!pszCodeset) + return false; + + /* + * Avoid RTStrICmp at this point. + */ + static struct + { + const char *pszUpper; + const char *pszLower; + } const s_aUtf8Compatible[] = + { + /* The default locale. */ + { "C" , "c" }, + { "POSIX" , "posix" }, + /* 7-bit ASCII. */ + { "ANSI_X3.4-1968" , "ansi_x3.4-1968" }, + { "ANSI_X3.4-1986" , "ansi_x3.4-1986" }, + { "US-ASCII" , "us-ascii" }, + { "ISO646-US" , "iso646-us" }, + { "ISO_646.IRV:1991" , "iso_646.irv:1991" }, + { "ISO-IR-6" , "iso-ir-6" }, + { "IBM367" , "ibm367" }, + /* UTF-8 */ + { "UTF-8" , "utf-8" }, + { "UTF8" , "utf8" }, + { "ISO-10646/UTF-8" , "iso-10646/utf-8" }, + { "ISO-10646/UTF8" , "iso-10646/utf8" } + }; + + for (size_t i = 0; i < RT_ELEMENTS(s_aUtf8Compatible); i++) + if (!rtPathStrICmp(pszCodeset, s_aUtf8Compatible[i].pszUpper, s_aUtf8Compatible[i].pszLower)) + return true; + + return false; +} + + +/** + * Init once for the path conversion code. + * + * @returns IPRT status code. + * @param pvUser1 Unused. + * @param pvUser2 Unused. + */ +static DECLCALLBACK(int32_t) rtPathConvInitOnce(void *pvUser) +{ + /* + * Read the environment variable, no mercy on misconfigs here except that + * empty values are quietly ignored. (We use a temp buffer for stripping.) + */ + char *pszEnvValue = NULL; + char szEnvValue[sizeof(g_szFsCodeset)]; + int rc = RTEnvGetEx(RTENV_DEFAULT, RTPATH_CODESET_ENV_VAR, szEnvValue, sizeof(szEnvValue), NULL); + if (rc != VERR_ENV_VAR_NOT_FOUND && RT_FAILURE(rc)) + return rc; + if (RT_SUCCESS(rc)) + pszEnvValue = RTStrStrip(szEnvValue); + + if (pszEnvValue && *pszEnvValue) + { + g_fPassthruUtf8 = rtPathConvInitIsUtf8(pszEnvValue); + g_enmFsToUtf8Idx = RTSTRICONV_FS_TO_UTF8; + g_enmUtf8ToFsIdx = RTSTRICONV_UTF8_TO_FS; + strcpy(g_szFsCodeset, pszEnvValue); + } + else + { + const char *pszCodeset = rtStrGetLocaleCodeset(); + size_t cchCodeset = pszCodeset ? strlen(pszCodeset) : sizeof(g_szFsCodeset); + if (cchCodeset >= sizeof(g_szFsCodeset)) + /* This shouldn't happen, but we'll manage. */ + g_szFsCodeset[0] = '\0'; + else + { + memcpy(g_szFsCodeset, pszCodeset, cchCodeset + 1); + pszCodeset = g_szFsCodeset; + } + g_fPassthruUtf8 = rtPathConvInitIsUtf8(pszCodeset); + g_enmFsToUtf8Idx = RTSTRICONV_LOCALE_TO_UTF8; + g_enmUtf8ToFsIdx = RTSTRICONV_UTF8_TO_LOCALE; + } + + NOREF(pvUser); + return VINF_SUCCESS; +} + + +int rtPathToNative(char const **ppszNativePath, const char *pszPath, const char *pszBasePath) +{ + *ppszNativePath = NULL; + + int rc = RTOnce(&g_OnceInitPathConv, rtPathConvInitOnce, NULL); + if (RT_SUCCESS(rc)) + { + if (g_fPassthruUtf8 || !*pszPath) + *ppszNativePath = pszPath; + else + rc = rtStrConvert(pszPath, strlen(pszPath), "UTF-8", + (char **)ppszNativePath, 0, g_szFsCodeset, + 2, g_enmUtf8ToFsIdx); + } + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return rc; +} + + +void rtPathFreeNative(char const *pszNativePath, const char *pszPath) +{ + if ( pszNativePath != pszPath + && pszNativePath) + RTStrFree((char *)pszNativePath); +} + + +int rtPathFromNative(const char **ppszPath, const char *pszNativePath, const char *pszBasePath) +{ + *ppszPath = NULL; + + int rc = RTOnce(&g_OnceInitPathConv, rtPathConvInitOnce, NULL); + if (RT_SUCCESS(rc)) + { + if (g_fPassthruUtf8 || !*pszNativePath) + { + size_t cCpsIgnored; + size_t cchNativePath; + rc = rtUtf8Length(pszNativePath, RTSTR_MAX, &cCpsIgnored, &cchNativePath); + if (RT_SUCCESS(rc)) + { + char *pszPath; + *ppszPath = pszPath = RTStrAlloc(cchNativePath + 1); + if (pszPath) + memcpy(pszPath, pszNativePath, cchNativePath + 1); + else + rc = VERR_NO_STR_MEMORY; + } + } + else + rc = rtStrConvert(pszNativePath, strlen(pszNativePath), g_szFsCodeset, + (char **)ppszPath, 0, "UTF-8", + 2, g_enmFsToUtf8Idx); + } + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return rc; +} + + +void rtPathFreeIprt(const char *pszPath, const char *pszNativePath) +{ + if ( pszPath != pszNativePath + && pszPath) + RTStrFree((char *)pszPath); +} + + +int rtPathFromNativeCopy(char *pszPath, size_t cbPath, const char *pszNativePath, const char *pszBasePath) +{ + int rc = RTOnce(&g_OnceInitPathConv, rtPathConvInitOnce, NULL); + if (RT_SUCCESS(rc)) + { + if (g_fPassthruUtf8 || !*pszNativePath) + rc = RTStrCopy(pszPath, cbPath, pszNativePath); + else if (cbPath) + rc = rtStrConvert(pszNativePath, strlen(pszNativePath), g_szFsCodeset, + &pszPath, cbPath, "UTF-8", + 2, g_enmFsToUtf8Idx); + else + rc = VERR_BUFFER_OVERFLOW; + } + + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return rc; +} + + +int rtPathFromNativeDup(char **ppszPath, const char *pszNativePath, const char *pszBasePath) +{ + int rc = RTOnce(&g_OnceInitPathConv, rtPathConvInitOnce, NULL); + if (RT_SUCCESS(rc)) + { + if (g_fPassthruUtf8 || !*pszNativePath) + rc = RTStrDupEx(ppszPath, pszNativePath); + else + rc = rtStrConvert(pszNativePath, strlen(pszNativePath), g_szFsCodeset, + ppszPath, 0, "UTF-8", + 2, g_enmFsToUtf8Idx); + } + + NOREF(pszBasePath); /* We don't query the FS for codeset preferences. */ + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/pipe-posix.cpp b/src/VBox/Runtime/r3/posix/pipe-posix.cpp new file mode 100644 index 00000000..dc673bd3 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/pipe-posix.cpp @@ -0,0 +1,754 @@ +/* $Id: pipe-posix.cpp $ */ +/** @file + * IPRT - Anonymous Pipes, POSIX Implementation. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/pipe.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include "internal/magics.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <sys/stat.h> +#include <signal.h> +#ifdef RT_OS_LINUX +# include <sys/syscall.h> +#endif +#ifdef RT_OS_SOLARIS +# include <sys/filio.h> +#endif + +#include "internal/pipe.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTPIPEINTERNAL +{ + /** Magic value (RTPIPE_MAGIC). */ + uint32_t u32Magic; + /** The file descriptor. */ + int fd; + /** Set if this is the read end, clear if it's the write end. */ + bool fRead; + /** RTPipeFromNative: Leave it open on RTPipeClose. */ + bool fLeaveOpen; + /** Atomically operated state variable. + * + * - Bits 0 thru 29 - Users of the new mode. + * - Bit 30 - The pipe mode, set indicates blocking. + * - Bit 31 - Set when we're switching the mode. + */ + uint32_t volatile u32State; +} RTPIPEINTERNAL; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @name RTPIPEINTERNAL::u32State defines + * @{ */ +#define RTPIPE_POSIX_BLOCKING UINT32_C(0x40000000) +#define RTPIPE_POSIX_SWITCHING UINT32_C(0x80000000) +#define RTPIPE_POSIX_SWITCHING_BIT 31 +#define RTPIPE_POSIX_USERS_MASK UINT32_C(0x3fffffff) +/** @} */ + + + +/** + * Wrapper for calling pipe2() or pipe(). + * + * When using pipe2() the returned handles are marked close-on-exec and does + * not risk racing process creation calls on other threads. + * + * @returns See pipe(). + * @param paFds See pipe(). + * @param piNewPipeSyscall Where to cache which call we should used. -1 if + * pipe(), 1 if pipe2(), 0 if not yet decided. + */ +static int my_pipe_wrapper(int *paFds, int *piNewPipeSyscall) +{ + if (*piNewPipeSyscall >= 0) + { +#if defined(RT_OS_LINUX) && defined(__NR_pipe2) && defined(O_CLOEXEC) + long rc = syscall(__NR_pipe2, paFds, O_CLOEXEC); + if (rc >= 0) + { + if (*piNewPipeSyscall == 0) + *piNewPipeSyscall = 1; + return (int)rc; + } +#endif + *piNewPipeSyscall = -1; + } + + return pipe(paFds); +} + + +RTDECL(int) RTPipeCreate(PRTPIPE phPipeRead, PRTPIPE phPipeWrite, uint32_t fFlags) +{ + AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER); + AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTPIPE_C_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Create the pipe and clear/set the close-on-exec flag as required. + */ + int aFds[2] = {-1, -1}; + static int s_iNewPipeSyscall = 0; + if (my_pipe_wrapper(aFds, &s_iNewPipeSyscall)) + return RTErrConvertFromErrno(errno); + + int rc = VINF_SUCCESS; + if (s_iNewPipeSyscall > 0) + { + /* created with close-on-exec set. */ + if (fFlags & RTPIPE_C_INHERIT_READ) + { + if (fcntl(aFds[0], F_SETFD, 0)) + rc = RTErrConvertFromErrno(errno); + } + + if (fFlags & RTPIPE_C_INHERIT_WRITE) + { + if (fcntl(aFds[1], F_SETFD, 0)) + rc = RTErrConvertFromErrno(errno); + } + } + else + { + /* created with close-on-exec cleared. */ + if (!(fFlags & RTPIPE_C_INHERIT_READ)) + { + if (fcntl(aFds[0], F_SETFD, FD_CLOEXEC)) + rc = RTErrConvertFromErrno(errno); + } + + if (!(fFlags & RTPIPE_C_INHERIT_WRITE)) + { + if (fcntl(aFds[1], F_SETFD, FD_CLOEXEC)) + rc = RTErrConvertFromErrno(errno); + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Create the two handles. + */ + RTPIPEINTERNAL *pThisR = (RTPIPEINTERNAL *)RTMemAlloc(sizeof(RTPIPEINTERNAL)); + if (pThisR) + { + RTPIPEINTERNAL *pThisW = (RTPIPEINTERNAL *)RTMemAlloc(sizeof(RTPIPEINTERNAL)); + if (pThisW) + { + pThisR->u32Magic = RTPIPE_MAGIC; + pThisW->u32Magic = RTPIPE_MAGIC; + pThisR->fd = aFds[0]; + pThisW->fd = aFds[1]; + pThisR->fRead = true; + pThisW->fRead = false; + pThisR->fLeaveOpen = false; + pThisW->fLeaveOpen = false; + pThisR->u32State = RTPIPE_POSIX_BLOCKING; + pThisW->u32State = RTPIPE_POSIX_BLOCKING; + + *phPipeRead = pThisR; + *phPipeWrite = pThisW; + + /* + * Before we leave, make sure to shut up SIGPIPE. + */ + signal(SIGPIPE, SIG_IGN); + return VINF_SUCCESS; + } + + RTMemFree(pThisR); + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NO_MEMORY; + } + + close(aFds[0]); + close(aFds[1]); + return rc; +} + + +RTDECL(int) RTPipeCloseEx(RTPIPE hPipe, bool fLeaveOpen) +{ + RTPIPEINTERNAL *pThis = hPipe; + if (pThis == NIL_RTPIPE) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the cleanup. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTPIPE_MAGIC, RTPIPE_MAGIC), VERR_INVALID_HANDLE); + + int fd = pThis->fd; + pThis->fd = -1; + if (!fLeaveOpen && !pThis->fLeaveOpen) + close(fd); + + if (ASMAtomicReadU32(&pThis->u32State) & RTPIPE_POSIX_USERS_MASK) + { + AssertFailed(); + RTThreadSleep(1); + } + + RTMemFree(pThis); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTPipeClose(RTPIPE hPipe) +{ + return RTPipeCloseEx(hPipe, false /*fLeaveOpen*/); +} + + +RTDECL(int) RTPipeFromNative(PRTPIPE phPipe, RTHCINTPTR hNativePipe, uint32_t fFlags) +{ + AssertPtrReturn(phPipe, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTPIPE_N_VALID_MASK_FN), VERR_INVALID_PARAMETER); + AssertReturn(!!(fFlags & RTPIPE_N_READ) != !!(fFlags & RTPIPE_N_WRITE), VERR_INVALID_PARAMETER); + + /* + * Get and validate the pipe handle info. + */ + int hNative = (int)hNativePipe; + struct stat st; + AssertReturn(fstat(hNative, &st) == 0, RTErrConvertFromErrno(errno)); + AssertMsgReturn(S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode), ("%#x (%o)\n", st.st_mode, st.st_mode), VERR_INVALID_HANDLE); + + int fFd = fcntl(hNative, F_GETFL, 0); + AssertReturn(fFd != -1, VERR_INVALID_HANDLE); + AssertMsgReturn( (fFd & O_ACCMODE) == (fFlags & RTPIPE_N_READ ? O_RDONLY : O_WRONLY) + || (fFd & O_ACCMODE) == O_RDWR /* Solaris creates bi-directional pipes. */ + , ("%#x\n", fFd), VERR_INVALID_HANDLE); + + /* + * Create the handle. + */ + RTPIPEINTERNAL *pThis = (RTPIPEINTERNAL *)RTMemAlloc(sizeof(RTPIPEINTERNAL)); + if (!pThis) + return VERR_NO_MEMORY; + + pThis->u32Magic = RTPIPE_MAGIC; + pThis->fd = hNative; + pThis->fRead = RT_BOOL(fFlags & RTPIPE_N_READ); + pThis->fLeaveOpen = RT_BOOL(fFlags & RTPIPE_N_LEAVE_OPEN); + pThis->u32State = fFd & O_NONBLOCK ? 0 : RTPIPE_POSIX_BLOCKING; + + /* + * Fix up inheritability and shut up SIGPIPE and we're done. + */ + if (fcntl(hNative, F_SETFD, fFlags & RTPIPE_N_INHERIT ? 0 : FD_CLOEXEC) == 0) + { + signal(SIGPIPE, SIG_IGN); + *phPipe = pThis; + return VINF_SUCCESS; + } + + int rc = RTErrConvertFromErrno(errno); + RTMemFree(pThis); + return rc; +} + + +RTDECL(RTHCINTPTR) RTPipeToNative(RTPIPE hPipe) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, -1); + + return pThis->fd; +} + + +/** + * Prepare blocking mode. + * + * @returns VINF_SUCCESS + * @retval VERR_WRONG_ORDER + * @retval VERR_INTERNAL_ERROR_4 + * + * @param pThis The pipe handle. + */ +static int rtPipeTryBlocking(RTPIPEINTERNAL *pThis) +{ + /* + * Update the state. + */ + for (;;) + { + uint32_t u32State = ASMAtomicReadU32(&pThis->u32State); + uint32_t const u32StateOld = u32State; + uint32_t const cUsers = (u32State & RTPIPE_POSIX_USERS_MASK); + + if (u32State & RTPIPE_POSIX_BLOCKING) + { + AssertReturn(cUsers < RTPIPE_POSIX_USERS_MASK / 2, VERR_INTERNAL_ERROR_4); + u32State &= ~RTPIPE_POSIX_USERS_MASK; + u32State |= cUsers + 1; + if (ASMAtomicCmpXchgU32(&pThis->u32State, u32State, u32StateOld)) + { + if (u32State & RTPIPE_POSIX_SWITCHING) + break; + return VINF_SUCCESS; + } + } + else if (cUsers == 0) + { + u32State = 1 | RTPIPE_POSIX_SWITCHING | RTPIPE_POSIX_BLOCKING; + if (ASMAtomicCmpXchgU32(&pThis->u32State, u32State, u32StateOld)) + break; + } + else + return VERR_WRONG_ORDER; + ASMNopPause(); + } + + /* + * Do the switching. + */ + int fFlags = fcntl(pThis->fd, F_GETFL, 0); + if (fFlags != -1) + { + if ( !(fFlags & O_NONBLOCK) + || fcntl(pThis->fd, F_SETFL, fFlags & ~O_NONBLOCK) != -1) + { + ASMAtomicBitClear(&pThis->u32State, RTPIPE_POSIX_SWITCHING_BIT); + return VINF_SUCCESS; + } + } + + ASMAtomicDecU32(&pThis->u32State); + return RTErrConvertFromErrno(errno); +} + + +/** + * Prepare non-blocking mode. + * + * @returns VINF_SUCCESS + * @retval VERR_WRONG_ORDER + * @retval VERR_INTERNAL_ERROR_4 + * + * @param pThis The pipe handle. + */ +static int rtPipeTryNonBlocking(RTPIPEINTERNAL *pThis) +{ + /* + * Update the state. + */ + for (;;) + { + uint32_t u32State = ASMAtomicReadU32(&pThis->u32State); + uint32_t const u32StateOld = u32State; + uint32_t const cUsers = (u32State & RTPIPE_POSIX_USERS_MASK); + + if (!(u32State & RTPIPE_POSIX_BLOCKING)) + { + AssertReturn(cUsers < RTPIPE_POSIX_USERS_MASK / 2, VERR_INTERNAL_ERROR_4); + u32State &= ~RTPIPE_POSIX_USERS_MASK; + u32State |= cUsers + 1; + if (ASMAtomicCmpXchgU32(&pThis->u32State, u32State, u32StateOld)) + { + if (u32State & RTPIPE_POSIX_SWITCHING) + break; + return VINF_SUCCESS; + } + } + else if (cUsers == 0) + { + u32State = 1 | RTPIPE_POSIX_SWITCHING; + if (ASMAtomicCmpXchgU32(&pThis->u32State, u32State, u32StateOld)) + break; + } + else + return VERR_WRONG_ORDER; + ASMNopPause(); + } + + /* + * Do the switching. + */ + int fFlags = fcntl(pThis->fd, F_GETFL, 0); + if (fFlags != -1) + { + if ( (fFlags & O_NONBLOCK) + || fcntl(pThis->fd, F_SETFL, fFlags | O_NONBLOCK) != -1) + { + ASMAtomicBitClear(&pThis->u32State, RTPIPE_POSIX_SWITCHING_BIT); + return VINF_SUCCESS; + } + } + + ASMAtomicDecU32(&pThis->u32State); + return RTErrConvertFromErrno(errno); +} + + +/** + * Checks if the read pipe has a HUP condition. + * + * @returns true if HUP, false if no. + * @param pThis The pipe handle (read). + */ +static bool rtPipePosixHasHup(RTPIPEINTERNAL *pThis) +{ + Assert(pThis->fRead); + + struct pollfd PollFd; + RT_ZERO(PollFd); + PollFd.fd = pThis->fd; + PollFd.events = POLLHUP; + return poll(&PollFd, 1, 0) >= 1 + && (PollFd.revents & POLLHUP); +} + + +RTDECL(int) RTPipeRead(RTPIPE hPipe, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pcbRead); + AssertPtr(pvBuf); + + int rc = rtPipeTryNonBlocking(pThis); + if (RT_SUCCESS(rc)) + { + ssize_t cbRead = read(pThis->fd, pvBuf, RT_MIN(cbToRead, SSIZE_MAX)); + if (cbRead >= 0) + { + if (cbRead || !cbToRead || !rtPipePosixHasHup(pThis)) + *pcbRead = cbRead; + else + rc = VERR_BROKEN_PIPE; + } + else if (errno == EAGAIN) + { + *pcbRead = 0; + rc = VINF_TRY_AGAIN; + } + else + rc = RTErrConvertFromErrno(errno); + + ASMAtomicDecU32(&pThis->u32State); + } + return rc; +} + + +RTDECL(int) RTPipeReadBlocking(RTPIPE hPipe, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pvBuf); + + int rc = rtPipeTryBlocking(pThis); + if (RT_SUCCESS(rc)) + { + size_t cbTotalRead = 0; + while (cbToRead > 0) + { + ssize_t cbRead = read(pThis->fd, pvBuf, RT_MIN(cbToRead, SSIZE_MAX)); + if (cbRead < 0) + { + rc = RTErrConvertFromErrno(errno); + break; + } + if (!cbRead && rtPipePosixHasHup(pThis)) + { + rc = VERR_BROKEN_PIPE; + break; + } + + /* advance */ + pvBuf = (char *)pvBuf + cbRead; + cbTotalRead += cbRead; + cbToRead -= cbRead; + } + + if (pcbRead) + { + *pcbRead = cbTotalRead; + if ( RT_FAILURE(rc) + && cbTotalRead + && rc != VERR_INVALID_POINTER) + rc = VINF_SUCCESS; + } + + ASMAtomicDecU32(&pThis->u32State); + } + return rc; +} + + +RTDECL(int) RTPipeWrite(RTPIPE hPipe, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pcbWritten); + AssertPtr(pvBuf); + + int rc = rtPipeTryNonBlocking(pThis); + if (RT_SUCCESS(rc)) + { + if (cbToWrite) + { + ssize_t cbWritten = write(pThis->fd, pvBuf, RT_MIN(cbToWrite, SSIZE_MAX)); + if (cbWritten >= 0) + *pcbWritten = cbWritten; + else if (errno == EAGAIN) + { + *pcbWritten = 0; + rc = VINF_TRY_AGAIN; + } + else + rc = RTErrConvertFromErrno(errno); + } + else + *pcbWritten = 0; + + ASMAtomicDecU32(&pThis->u32State); + } + return rc; +} + + +RTDECL(int) RTPipeWriteBlocking(RTPIPE hPipe, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pvBuf); + AssertPtrNull(pcbWritten); + + int rc = rtPipeTryBlocking(pThis); + if (RT_SUCCESS(rc)) + { + size_t cbTotalWritten = 0; + while (cbToWrite > 0) + { + ssize_t cbWritten = write(pThis->fd, pvBuf, RT_MIN(cbToWrite, SSIZE_MAX)); + if (cbWritten < 0) + { + rc = RTErrConvertFromErrno(errno); + break; + } + + /* advance */ + pvBuf = (char const *)pvBuf + cbWritten; + cbTotalWritten += cbWritten; + cbToWrite -= cbWritten; + } + + if (pcbWritten) + { + *pcbWritten = cbTotalWritten; + if ( RT_FAILURE(rc) + && cbTotalWritten + && rc != VERR_INVALID_POINTER) + rc = VINF_SUCCESS; + } + + ASMAtomicDecU32(&pThis->u32State); + } + return rc; +} + + +RTDECL(int) RTPipeFlush(RTPIPE hPipe) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + + if (fsync(pThis->fd)) + { + if (errno == EINVAL || errno == ENOTSUP) + return VERR_NOT_SUPPORTED; + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTPipeSelectOne(RTPIPE hPipe, RTMSINTERVAL cMillies) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + struct pollfd PollFd; + RT_ZERO(PollFd); + PollFd.fd = pThis->fd; + PollFd.events = POLLHUP | POLLERR; + if (pThis->fRead) + PollFd.events |= POLLIN | POLLPRI; + else + PollFd.events |= POLLOUT; + + int timeout; + if ( cMillies == RT_INDEFINITE_WAIT + || cMillies >= INT_MAX /* lazy bird */) + timeout = -1; + else + timeout = cMillies; + + int rc = poll(&PollFd, 1, timeout); + if (rc == -1) + return RTErrConvertFromErrno(errno); + return rc > 0 ? VINF_SUCCESS : VERR_TIMEOUT; +} + + +RTDECL(int) RTPipeQueryReadable(RTPIPE hPipe, size_t *pcbReadable) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_PIPE_NOT_READ); + AssertPtrReturn(pcbReadable, VERR_INVALID_POINTER); + + int cb = 0; + int rc = ioctl(pThis->fd, FIONREAD, &cb); + if (rc != -1) + { + AssertStmt(cb >= 0, cb = 0); + *pcbReadable = cb; + return VINF_SUCCESS; + } + + rc = errno; + if (rc == ENOTTY) + rc = VERR_NOT_SUPPORTED; + else + rc = RTErrConvertFromErrno(rc); + return rc; +} + + +RTDECL(int) RTPipeQueryInfo(RTPIPE hPipe, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, 0); + + rtPipeFakeQueryInfo(pObjInfo, enmAddAttr, pThis->fRead); + + if (pThis->fRead) + { + int cb = 0; + int rc = ioctl(pThis->fd, FIONREAD, &cb); + if (rc >= 0) + pObjInfo->cbObject = cb; + } +#ifdef FIONSPACE + else + { + int cb = 0; + int rc = ioctl(pThis->fd, FIONSPACE, &cb); + if (rc >= 0) + pObjInfo->cbObject = cb; + } +#endif + + /** @todo Check this out on linux, solaris and darwin... (Currently going by a + * FreeBSD manpage.) */ + struct stat St; + if (fstat(pThis->fd, &St)) + { + pObjInfo->cbAllocated = St.st_blksize; + if ( enmAddAttr == RTFSOBJATTRADD_NOTHING + || enmAddAttr == RTFSOBJATTRADD_UNIX) + { + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pObjInfo->Attr.u.Unix.INodeId = St.st_ino; + pObjInfo->Attr.u.Unix.INodeIdDevice = St.st_dev; + } + } + /** @todo error handling? */ + + return VINF_SUCCESS; +} + + +int rtPipePollGetHandle(RTPIPE hPipe, uint32_t fEvents, PRTHCINTPTR phNative) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + AssertReturn(!(fEvents & RTPOLL_EVT_READ) || pThis->fRead, VERR_INVALID_PARAMETER); + AssertReturn(!(fEvents & RTPOLL_EVT_WRITE) || !pThis->fRead, VERR_INVALID_PARAMETER); + + *phNative = pThis->fd; + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/posix/process-creation-posix.cpp b/src/VBox/Runtime/r3/posix/process-creation-posix.cpp new file mode 100644 index 00000000..6cf7be73 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/process-creation-posix.cpp @@ -0,0 +1,2408 @@ +/* $Id: process-creation-posix.cpp $ */ +/** @file + * IPRT - Process Creation, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <iprt/cdefs.h> +#ifdef RT_OS_LINUX +# define IPRT_WITH_DYNAMIC_CRYPT_R +#endif +#if (defined(RT_OS_LINUX) || defined(RT_OS_OS2)) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +#endif +#if defined(RT_OS_LINUX) && !defined(_XOPEN_SOURCE) +# define _XOPEN_SOURCE 700 /* for newlocale */ +#endif + +#ifdef RT_OS_OS2 +# define crypt unistd_crypt +# define setkey unistd_setkey +# define encrypt unistd_encrypt +# include <unistd.h> +# undef crypt +# undef setkey +# undef encrypt +#else +# include <unistd.h> +#endif +#include <stdlib.h> +#include <errno.h> +#include <langinfo.h> +#include <locale.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <signal.h> +#include <grp.h> +#include <pwd.h> +#if defined(RT_OS_LINUX) || defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) +# include <crypt.h> +#endif +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) +# include <shadow.h> +#endif +#if defined(RT_OS_DARWIN) +# include <xlocale.h> /* for newlocale() */ +#endif + +#if defined(RT_OS_LINUX) || defined(RT_OS_OS2) +/* While Solaris has posix_spawn() of course we don't want to use it as + * we need to have the child in a different process contract, no matter + * whether it is started detached or not. */ +# define HAVE_POSIX_SPAWN 1 +#endif +#if defined(RT_OS_DARWIN) && defined(MAC_OS_X_VERSION_MIN_REQUIRED) +# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +# define HAVE_POSIX_SPAWN 1 +# endif +#endif +#ifdef HAVE_POSIX_SPAWN +# include <spawn.h> +#endif + +#if !defined(IPRT_USE_PAM) \ + && !defined(IPRT_WITHOUT_PAM) \ + && ( defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS) ) +# define IPRT_USE_PAM +#endif +#ifdef IPRT_USE_PAM +# include <security/pam_appl.h> +# include <stdlib.h> +# include <dlfcn.h> +# include <iprt/asm.h> +#endif + +#ifdef RT_OS_SOLARIS +# include <limits.h> +# include <sys/ctfs.h> +# include <sys/contract/process.h> +# include <libcontract.h> +#endif + +#ifndef RT_OS_SOLARIS +# include <paths.h> +#else +# define _PATH_MAILDIR "/var/mail" +# define _PATH_DEFPATH "/usr/bin:/bin" +# define _PATH_STDPATH "/sbin:/usr/sbin:/bin:/usr/bin" +#endif +#ifndef _PATH_BSHELL +# define _PATH_BSHELL "/bin/sh" +#endif + + +#include <iprt/process.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#if defined(IPRT_WITH_DYNAMIC_CRYPT_R) || defined(IPRT_USE_PAM) +# include <iprt/ldr.h> +#endif +#include <iprt/log.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/socket.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include "internal/process.h" +#include "internal/path.h" +#include "internal/string.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef IPRT_USE_PAM +/* + * The PAM library names and version ranges to try. + */ +# ifdef RT_OS_DARWIN +# include <mach-o/dyld.h> +/** @node libpam.2.dylib was introduced with 10.6.x (OpenPAM); we use + * libpam.dylib as that's a symlink to the latest and greatest. */ +# define IPRT_LIBPAM_FILE_1 "libpam.dylib" +# define IPRT_LIBPAM_FILE_1_FIRST_VER 0 +# define IPRT_LIBPAM_FILE_1_END_VER 0 +# define IPRT_LIBPAM_FILE_2 "libpam.2.dylib" +# define IPRT_LIBPAM_FILE_2_FIRST_VER 0 +# define IPRT_LIBPAM_FILE_2_END_VER 0 +# define IPRT_LIBPAM_FILE_3 "libpam.1.dylib" +# define IPRT_LIBPAM_FILE_3_FIRST_VER 0 +# define IPRT_LIBPAM_FILE_3_END_VER 0 +# elif RT_OS_LINUX +# define IPRT_LIBPAM_FILE_1 "libpam.so.0" +# define IPRT_LIBPAM_FILE_1_FIRST_VER 0 +# define IPRT_LIBPAM_FILE_1_END_VER 0 +# define IPRT_LIBPAM_FILE_2 "libpam.so" +# define IPRT_LIBPAM_FILE_2_FIRST_VER 16 +# define IPRT_LIBPAM_FILE_2_END_VER 1 +# else +# define IPRT_LIBPAM_FILE_1 "libpam.so" +# define IPRT_LIBPAM_FILE_1_FIRST_VER 16 +# define IPRT_LIBPAM_FILE_1_END_VER 0 +# endif +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef IPRT_USE_PAM +/** For passing info between rtCheckCredentials and rtPamConv. */ +typedef struct RTPROCPAMARGS +{ + const char *pszUser; + const char *pszPassword; +} RTPROCPAMARGS; +/** Pointer to rtPamConv argument package. */ +typedef RTPROCPAMARGS *PRTPROCPAMARGS; +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Environment dump marker used with CSH. */ +static const char g_szEnvMarkerBegin[] = "IPRT_EnvEnvEnv_Begin_EnvEnvEnv"; +/** Environment dump marker used with CSH. */ +static const char g_szEnvMarkerEnd[] = "IPRT_EnvEnvEnv_End_EnvEnvEnv"; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int rtProcPosixCreateInner(const char *pszExec, const char * const *papszArgs, RTENV hEnv, RTENV hEnvToUse, + uint32_t fFlags, const char *pszAsUser, uid_t uid, gid_t gid, + unsigned cRedirFds, int *paRedirFds, PRTPROCESS phProcess); + + +#ifdef IPRT_USE_PAM +/** + * Worker for rtCheckCredentials that feeds password and maybe username to PAM. + * + * @returns PAM status. + * @param cMessages Number of messages. + * @param papMessages Message vector. + * @param ppaResponses Where to put our responses. + * @param pvAppData Pointer to RTPROCPAMARGS. + */ +#if defined(RT_OS_SOLARIS) +static int rtPamConv(int cMessages, struct pam_message **papMessages, struct pam_response **ppaResponses, void *pvAppData) +#else +static int rtPamConv(int cMessages, const struct pam_message **papMessages, struct pam_response **ppaResponses, void *pvAppData) +#endif +{ + LogFlow(("rtPamConv: cMessages=%d\n", cMessages)); + PRTPROCPAMARGS pArgs = (PRTPROCPAMARGS)pvAppData; + AssertPtrReturn(pArgs, PAM_CONV_ERR); + + struct pam_response *paResponses = (struct pam_response *)calloc(cMessages, sizeof(paResponses[0])); + AssertReturn(paResponses, PAM_CONV_ERR); + for (int i = 0; i < cMessages; i++) + { + LogFlow(("rtPamConv: #%d: msg_style=%d msg=%s\n", i, papMessages[i]->msg_style, papMessages[i]->msg)); + + paResponses[i].resp_retcode = 0; + if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_OFF) + paResponses[i].resp = strdup(pArgs->pszPassword); + else if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_ON) + paResponses[i].resp = strdup(pArgs->pszUser); + else + { + paResponses[i].resp = NULL; + continue; + } + if (paResponses[i].resp == NULL) + { + while (i-- > 0) + free(paResponses[i].resp); + free(paResponses); + LogFlow(("rtPamConv: out of memory\n")); + return PAM_CONV_ERR; + } + } + + *ppaResponses = paResponses; + return PAM_SUCCESS; +} + + +/** + * Common PAM driver for rtCheckCredentials and the case where pszAsUser is NULL + * but RTPROC_FLAGS_PROFILE is set. + * + * @returns IPRT status code. + * @param pszPamService The PAM service to use for the run. + * @param pszUser The user. + * @param pszPassword The password. + * @param ppapszEnv Where to return PAM environment variables, NULL is + * fine if no variables to return. Call + * rtProcPosixFreePamEnv to free. Optional, so NULL + * can be passed in. + * @param pfMayFallBack Where to return whether a fallback to crypt is + * acceptable or if the failure result is due to + * authentication failing. Optional. + */ +static int rtProcPosixAuthenticateUsingPam(const char *pszPamService, const char *pszUser, const char *pszPassword, + char ***ppapszEnv, bool *pfMayFallBack) +{ + if (pfMayFallBack) + *pfMayFallBack = true; + + /* + * Dynamically load pam the first time we go thru here. + */ + static int (*s_pfnPamStart)(const char *, const char *, struct pam_conv *, pam_handle_t **); + static int (*s_pfnPamAuthenticate)(pam_handle_t *, int); + static int (*s_pfnPamAcctMgmt)(pam_handle_t *, int); + static int (*s_pfnPamSetItem)(pam_handle_t *, int, const void *); + static int (*s_pfnPamSetCred)(pam_handle_t *, int); + static char ** (*s_pfnPamGetEnvList)(pam_handle_t *); + static int (*s_pfnPamOpenSession)(pam_handle_t *, int); + static int (*s_pfnPamCloseSession)(pam_handle_t *, int); + static int (*s_pfnPamEnd)(pam_handle_t *, int); + if ( s_pfnPamStart == NULL + || s_pfnPamAuthenticate == NULL + || s_pfnPamAcctMgmt == NULL + || s_pfnPamSetItem == NULL + || s_pfnPamEnd == NULL) + { + RTLDRMOD hModPam = NIL_RTLDRMOD; + const char *pszLast; + int rc = RTLdrLoadSystemEx(pszLast = IPRT_LIBPAM_FILE_1, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD + | RTLDRLOAD_FLAGS_SO_VER_RANGE(IPRT_LIBPAM_FILE_1_FIRST_VER, IPRT_LIBPAM_FILE_1_END_VER), + &hModPam); +# ifdef IPRT_LIBPAM_FILE_2 + if (RT_FAILURE(rc)) + rc = RTLdrLoadSystemEx(pszLast = IPRT_LIBPAM_FILE_2, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD + | RTLDRLOAD_FLAGS_SO_VER_RANGE(IPRT_LIBPAM_FILE_2_FIRST_VER, IPRT_LIBPAM_FILE_2_END_VER), + &hModPam); +# endif +# ifdef IPRT_LIBPAM_FILE_3 + if (RT_FAILURE(rc)) + rc = RTLdrLoadSystemEx(pszLast = IPRT_LIBPAM_FILE_3, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD + | RTLDRLOAD_FLAGS_SO_VER_RANGE(IPRT_LIBPAM_FILE_3_FIRST_VER, IPRT_LIBPAM_FILE_3_END_VER), + &hModPam); +# endif + if (RT_FAILURE(rc)) + { + LogRelMax(10, ("failed to load %s: %Rrc\n", pszLast, rc)); + return VERR_AUTHENTICATION_FAILURE; + } + + *(uintptr_t *)&s_pfnPamStart = (uintptr_t)RTLdrGetFunction(hModPam, "pam_start"); + *(uintptr_t *)&s_pfnPamAuthenticate = (uintptr_t)RTLdrGetFunction(hModPam, "pam_authenticate"); + *(uintptr_t *)&s_pfnPamAcctMgmt = (uintptr_t)RTLdrGetFunction(hModPam, "pam_acct_mgmt"); + *(uintptr_t *)&s_pfnPamSetItem = (uintptr_t)RTLdrGetFunction(hModPam, "pam_set_item"); + *(uintptr_t *)&s_pfnPamSetCred = (uintptr_t)RTLdrGetFunction(hModPam, "pam_setcred"); + *(uintptr_t *)&s_pfnPamGetEnvList = (uintptr_t)RTLdrGetFunction(hModPam, "pam_getenvlist"); + *(uintptr_t *)&s_pfnPamOpenSession = (uintptr_t)RTLdrGetFunction(hModPam, "pam_open_session"); + *(uintptr_t *)&s_pfnPamCloseSession = (uintptr_t)RTLdrGetFunction(hModPam, "pam_close_session"); + *(uintptr_t *)&s_pfnPamEnd = (uintptr_t)RTLdrGetFunction(hModPam, "pam_end"); + ASMCompilerBarrier(); + + RTLdrClose(hModPam); + + if ( s_pfnPamStart == NULL + || s_pfnPamAuthenticate == NULL + || s_pfnPamAcctMgmt == NULL + || s_pfnPamSetItem == NULL + || s_pfnPamEnd == NULL) + { + LogRelMax(10, ("failed to resolve symbols: %p %p %p %p %p\n", + s_pfnPamStart, s_pfnPamAuthenticate, s_pfnPamAcctMgmt, s_pfnPamSetItem, s_pfnPamEnd)); + return VERR_AUTHENTICATION_FAILURE; + } + } + +# define pam_start s_pfnPamStart +# define pam_authenticate s_pfnPamAuthenticate +# define pam_acct_mgmt s_pfnPamAcctMgmt +# define pam_set_item s_pfnPamSetItem +# define pam_setcred s_pfnPamSetCred +# define pam_getenvlist s_pfnPamGetEnvList +# define pam_open_session s_pfnPamOpenSession +# define pam_close_session s_pfnPamCloseSession +# define pam_end s_pfnPamEnd + + /* + * Do the PAM stuff. + */ + pam_handle_t *hPam = NULL; + RTPROCPAMARGS PamConvArgs = { pszUser, pszPassword }; + struct pam_conv PamConversation; + RT_ZERO(PamConversation); + PamConversation.appdata_ptr = &PamConvArgs; + PamConversation.conv = rtPamConv; + int rc = pam_start(pszPamService, pszUser, &PamConversation, &hPam); + if (rc == PAM_SUCCESS) + { + rc = pam_set_item(hPam, PAM_RUSER, pszUser); + LogRel2(("rtProcPosixAuthenticateUsingPam(%s): pam_setitem/PAM_RUSER: %s\n", pszPamService, pszUser)); + if (rc == PAM_SUCCESS) + { + /* + * Secure TTY fun ahead (for pam_securetty). + * + * We need to set PAM_TTY (if available) to make PAM stacks work which + * require a secure TTY via pam_securetty (Debian 10 + 11, for example). This + * is typically an issue when launching as 'root'. See @bugref{10225}. + * + * Note! We only can try (or better: guess) to a certain amount, as it really + * depends on the distribution or Administrator which has set up the + * system which (and how) things are allowed (see /etc/securetty). + * + * Note! We don't acctually try or guess anything about the distro like + * suggested by the above note, we just try determine the TTY of + * the _parent_ process and hope for the best. (bird) + */ + char szTTY[64]; + int rc2 = RTEnvGetEx(RTENV_DEFAULT, "DISPLAY", szTTY, sizeof(szTTY), NULL); + if (RT_FAILURE(rc2)) + { + /* Virtual terminal hint given? */ + static char const s_szPrefix[] = "tty"; + memcpy(szTTY, s_szPrefix, sizeof(s_szPrefix)); + rc2 = RTEnvGetEx(RTENV_DEFAULT, "XDG_VTNR", &szTTY[sizeof(s_szPrefix) - 1], sizeof(s_szPrefix) - 1, NULL); + } + + /** @todo Should we - distinguished from the login service - also set the hostname as PAM_TTY? + * The pam_access and pam_systemd talk about this. Similarly, SSH and cron use "ssh" and "cron" for PAM_TTY + * (see PAM_TTY_KLUDGE). */ +#ifdef IPRT_WITH_PAM_TTY_KLUDGE + if (RT_FAILURE(rc2)) + if (!RTStrICmp(pszPamService, "access")) /* Access management needed? */ + { + int err = gethostname(szTTY, sizeof(szTTY)); + if (err == 0) + rc2 = VINF_SUCCESS; + } +#endif + /* As a last resort, try stdin's TTY name instead (if any). */ + if (RT_FAILURE(rc2)) + { + rc2 = ttyname_r(0 /*stdin*/, szTTY, sizeof(szTTY)); + if (rc2 != 0) + rc2 = RTErrConvertFromErrno(rc2); + } + + LogRel2(("rtProcPosixAuthenticateUsingPam(%s): pam_setitem/PAM_TTY: %s, rc2=%Rrc\n", pszPamService, szTTY, rc2)); + if (szTTY[0] == '\0') + LogRel2(("rtProcPosixAuthenticateUsingPam(%s): Hint: Looks like running as a non-interactive user (no TTY/PTY).\n" + "Authentication requiring a secure terminal might fail.\n", pszPamService)); + + if ( RT_SUCCESS(rc2) + && szTTY[0] != '\0') /* Only try using PAM_TTY if we have something to set. */ + rc = pam_set_item(hPam, PAM_TTY, szTTY); + + if (rc == PAM_SUCCESS) + { + /* From this point on we don't allow falling back to other auth methods. */ + if (pfMayFallBack) + *pfMayFallBack = false; + + rc = pam_authenticate(hPam, 0); + if (rc == PAM_SUCCESS) + { + rc = pam_acct_mgmt(hPam, 0); + if ( rc == PAM_SUCCESS + || rc == PAM_AUTHINFO_UNAVAIL /*??*/) + { + if ( ppapszEnv + && s_pfnPamGetEnvList + && s_pfnPamSetCred) + { + /* pam_env.so creates the environment when pam_setcred is called,. */ + int rcSetCred = pam_setcred(hPam, PAM_ESTABLISH_CRED | PAM_SILENT); + /** @todo check pam_setcred status code? */ + + /* Unless it does it during session opening (Ubuntu 21.10). This + unfortunately means we might mount user dir and other crap: */ + /** @todo do session handling properly */ + int rcOpenSession = PAM_ABORT; + if ( s_pfnPamOpenSession + && s_pfnPamCloseSession) + rcOpenSession = pam_open_session(hPam, PAM_SILENT); + + *ppapszEnv = pam_getenvlist(hPam); + LogFlowFunc(("pam_getenvlist -> %p ([0]=%p); rcSetCred=%d rcOpenSession=%d\n", + *ppapszEnv, *ppapszEnv ? **ppapszEnv : NULL, rcSetCred, rcOpenSession)); RT_NOREF(rcSetCred); + + if (rcOpenSession == PAM_SUCCESS) + pam_close_session(hPam, PAM_SILENT); + pam_setcred(hPam, PAM_DELETE_CRED); + } + + pam_end(hPam, PAM_SUCCESS); + LogFlowFunc(("pam auth (for %s) successful\n", pszPamService)); + return VINF_SUCCESS; + } + LogFunc(("pam_acct_mgmt -> %d\n", rc)); + } + else + LogFunc(("pam_authenticate -> %d\n", rc)); + } + else + LogFunc(("pam_setitem/PAM_TTY -> %d\n", rc)); + } + else + LogFunc(("pam_set_item/PAM_RUSER -> %d\n", rc)); + pam_end(hPam, rc); + } + else + LogFunc(("pam_start(%s) -> %d\n", pszPamService, rc)); + + LogRel2(("rtProcPosixAuthenticateUsingPam(%s): Failed authenticating user '%s' with %d\n", pszPamService, pszUser, rc)); + return VERR_AUTHENTICATION_FAILURE; +} + + +/** + * Checks if the given service file is present in any of the pam.d directories. + */ +static bool rtProcPosixPamServiceExists(const char *pszService) +{ + char szPath[256]; + + /* PAM_CONFIG_D: */ + int rc = RTPathJoin(szPath, sizeof(szPath), "/etc/pam.d/", pszService); AssertRC(rc); + if (RTFileExists(szPath)) + return true; + + /* PAM_CONFIG_DIST_D: */ + rc = RTPathJoin(szPath, sizeof(szPath), "/usr/lib/pam.d/", pszService); AssertRC(rc); + if (RTFileExists(szPath)) + return true; + + /* No support for PAM_CONFIG_DIST2_D. */ + return false; +} + +#endif /* IPRT_USE_PAM */ + + +#if defined(IPRT_WITH_DYNAMIC_CRYPT_R) +/** Pointer to crypt_r(). */ +typedef char *(*PFNCRYPTR)(const char *, const char *, struct crypt_data *); + +/** + * Wrapper for resolving and calling crypt_r dynamically. + * + * The reason for this is that fedora 30+ wants to use libxcrypt rather than the + * glibc libcrypt. The two libraries has different crypt_data sizes and layout, + * so we allocate a 256KB data block to be on the safe size (caller does this). + */ +static char *rtProcDynamicCryptR(const char *pszKey, const char *pszSalt, struct crypt_data *pData) +{ + static PFNCRYPTR volatile s_pfnCryptR = NULL; + PFNCRYPTR pfnCryptR = s_pfnCryptR; + if (pfnCryptR) + return pfnCryptR(pszKey, pszSalt, pData); + + pfnCryptR = (PFNCRYPTR)(uintptr_t)RTLdrGetSystemSymbolEx("libcrypt.so", "crypt_r", RTLDRLOAD_FLAGS_SO_VER_RANGE(1, 6)); + if (!pfnCryptR) + pfnCryptR = (PFNCRYPTR)(uintptr_t)RTLdrGetSystemSymbolEx("libxcrypt.so", "crypt_r", RTLDRLOAD_FLAGS_SO_VER_RANGE(1, 32)); + if (pfnCryptR) + { + s_pfnCryptR = pfnCryptR; + return pfnCryptR(pszKey, pszSalt, pData); + } + + LogRel(("IPRT/RTProc: Unable to locate crypt_r!\n")); + return NULL; +} +#endif /* IPRT_WITH_DYNAMIC_CRYPT_R */ + + +/** Free the environment list returned by rtCheckCredentials. */ +static void rtProcPosixFreePamEnv(char **papszEnv) +{ + if (papszEnv) + { + for (size_t i = 0; papszEnv[i] != NULL; i++) + free(papszEnv[i]); + free(papszEnv); + } +} + + +/** + * Check the credentials and return the gid/uid of user. + * + * @param pszUser The username. + * @param pszPasswd The password to authenticate with. + * @param gid Where to store the GID of the user. + * @param uid Where to store the UID of the user. + * @param ppapszEnv Where to return PAM environment variables, NULL is fine + * if no variables to return. Call rtProcPosixFreePamEnv to + * free. Optional, so NULL can be passed in. + * @returns IPRT status code + */ +static int rtCheckCredentials(const char *pszUser, const char *pszPasswd, gid_t *pGid, uid_t *pUid, char ***ppapszEnv) +{ + Log(("rtCheckCredentials: pszUser=%s\n", pszUser)); + int rc; + + if (ppapszEnv) + *ppapszEnv = NULL; + + /* + * Resolve user to UID and GID. + */ + char achBuf[_4K]; + struct passwd Pw; + struct passwd *pPw; + if (getpwnam_r(pszUser, &Pw, achBuf, sizeof(achBuf), &pPw) != 0) + return VERR_AUTHENTICATION_FAILURE; + if (!pPw) + return VERR_AUTHENTICATION_FAILURE; + + *pUid = pPw->pw_uid; + *pGid = pPw->pw_gid; + +#ifdef IPRT_USE_PAM + /* + * Try authenticate using PAM, and falling back on crypto if allowed. + */ + const char *pszService = "iprt-as-user"; + if (!rtProcPosixPamServiceExists("iprt-as-user")) +# ifdef IPRT_PAM_NATIVE_SERVICE_NAME_AS_USER + pszService = IPRT_PAM_NATIVE_SERVICE_NAME_AS_USER; +# else + pszService = "login"; +# endif + bool fMayFallBack = false; + rc = rtProcPosixAuthenticateUsingPam(pszService, pszUser, pszPasswd, ppapszEnv, &fMayFallBack); + if (RT_SUCCESS(rc) || !fMayFallBack) + { + RTMemWipeThoroughly(achBuf, sizeof(achBuf), 3); + return rc; + } +#endif + +#if !defined(IPRT_USE_PAM) || defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) || defined(RT_OS_OS2) +# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) + /* + * Ditto for /etc/shadow and replace pw_passwd from above if we can access it: + * + * Note! On FreeBSD and OS/2 the root user will open /etc/shadow above, so + * this getspnam_r step is not necessary. + */ + struct spwd ShwPwd; + char achBuf2[_4K]; +# if defined(RT_OS_LINUX) + struct spwd *pShwPwd = NULL; + if (getspnam_r(pszUser, &ShwPwd, achBuf2, sizeof(achBuf2), &pShwPwd) != 0) + pShwPwd = NULL; +# else + struct spwd *pShwPwd = getspnam_r(pszUser, &ShwPwd, achBuf2, sizeof(achBuf2)); +# endif + if (pShwPwd != NULL) + pPw->pw_passwd = pShwPwd->sp_pwdp; +# endif + + /* + * Encrypt the passed in password and see if it matches. + */ +# if defined(RT_OS_LINUX) + /* Default fCorrect=true if no password specified. In that case, pPw->pw_passwd + must be NULL (no password set for this user). Fail if a password is specified + but the user does not have one assigned. */ + rc = !pszPasswd || !*pszPasswd ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE; + if (pPw->pw_passwd && *pPw->pw_passwd) +# endif + { +# if defined(RT_OS_LINUX) || defined(RT_OS_OS2) +# ifdef IPRT_WITH_DYNAMIC_CRYPT_R + size_t const cbCryptData = RT_MAX(sizeof(struct crypt_data) * 2, _256K); +# else + size_t const cbCryptData = sizeof(struct crypt_data); +# endif + struct crypt_data *pCryptData = (struct crypt_data *)RTMemTmpAllocZ(cbCryptData); + if (pCryptData) + { +# ifdef IPRT_WITH_DYNAMIC_CRYPT_R + char *pszEncPasswd = rtProcDynamicCryptR(pszPasswd, pPw->pw_passwd, pCryptData); +# else + char *pszEncPasswd = crypt_r(pszPasswd, pPw->pw_passwd, pCryptData); +# endif + rc = pszEncPasswd && !strcmp(pszEncPasswd, pPw->pw_passwd) ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE; + RTMemWipeThoroughly(pCryptData, cbCryptData, 3); + RTMemTmpFree(pCryptData); + } + else + rc = VERR_NO_TMP_MEMORY; +# else + char *pszEncPasswd = crypt(pszPasswd, pPw->pw_passwd); + rc = strcmp(pszEncPasswd, pPw->pw_passwd) == 0 ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE; +# endif + } + + /* + * Return GID and UID on success. Always wipe stack buffers. + */ + if (RT_SUCCESS(rc)) + { + *pGid = pPw->pw_gid; + *pUid = pPw->pw_uid; + } +# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) + RTMemWipeThoroughly(achBuf2, sizeof(achBuf2), 3); +# endif +#endif + RTMemWipeThoroughly(achBuf, sizeof(achBuf), 3); + return rc; +} + +#ifdef RT_OS_SOLARIS + +/** @todo the error reporting of the Solaris process contract code could be + * a lot better, but essentially it is not meant to run into errors after + * the debugging phase. */ +static int rtSolarisContractPreFork(void) +{ + int templateFd = open64(CTFS_ROOT "/process/template", O_RDWR); + if (templateFd < 0) + return -1; + + /* Set template parameters and event sets. */ + if (ct_pr_tmpl_set_param(templateFd, CT_PR_PGRPONLY)) + { + close(templateFd); + return -1; + } + if (ct_pr_tmpl_set_fatal(templateFd, CT_PR_EV_HWERR)) + { + close(templateFd); + return -1; + } + if (ct_tmpl_set_critical(templateFd, 0)) + { + close(templateFd); + return -1; + } + if (ct_tmpl_set_informative(templateFd, CT_PR_EV_HWERR)) + { + close(templateFd); + return -1; + } + + /* Make this the active template for the process. */ + if (ct_tmpl_activate(templateFd)) + { + close(templateFd); + return -1; + } + + return templateFd; +} + +static void rtSolarisContractPostForkChild(int templateFd) +{ + if (templateFd == -1) + return; + + /* Clear the active template. */ + ct_tmpl_clear(templateFd); + close(templateFd); +} + +static void rtSolarisContractPostForkParent(int templateFd, pid_t pid) +{ + if (templateFd == -1) + return; + + /* Clear the active template. */ + int cleared = ct_tmpl_clear(templateFd); + close(templateFd); + + /* If the clearing failed or the fork failed there's nothing more to do. */ + if (cleared || pid <= 0) + return; + + /* Look up the contract which was created by this thread. */ + int statFd = open64(CTFS_ROOT "/process/latest", O_RDONLY); + if (statFd == -1) + return; + ct_stathdl_t statHdl; + if (ct_status_read(statFd, CTD_COMMON, &statHdl)) + { + close(statFd); + return; + } + ctid_t ctId = ct_status_get_id(statHdl); + ct_status_free(statHdl); + close(statFd); + if (ctId < 0) + return; + + /* Abandon this contract we just created. */ + char ctlPath[PATH_MAX]; + size_t len = snprintf(ctlPath, sizeof(ctlPath), + CTFS_ROOT "/process/%ld/ctl", (long)ctId); + if (len >= sizeof(ctlPath)) + return; + int ctlFd = open64(ctlPath, O_WRONLY); + if (statFd == -1) + return; + if (ct_ctl_abandon(ctlFd) < 0) + { + close(ctlFd); + return; + } + close(ctlFd); +} + +#endif /* RT_OS_SOLARIS */ + + +RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess) +{ + return RTProcCreateEx(pszExec, papszArgs, Env, fFlags, + NULL, NULL, NULL, /* standard handles */ + NULL /*pszAsUser*/, NULL /* pszPassword*/, NULL /*pvExtraData*/, + pProcess); +} + + +/** + * Adjust the profile environment after forking the child process and changing + * the UID. + * + * @returns IRPT status code. + * @param hEnvToUse The environment we're going to use with execve. + * @param fFlags The process creation flags. + * @param hEnv The environment passed in by the user. + */ +static int rtProcPosixAdjustProfileEnvFromChild(RTENV hEnvToUse, uint32_t fFlags, RTENV hEnv) +{ + int rc = VINF_SUCCESS; +#ifdef RT_OS_DARWIN + if ( RT_SUCCESS(rc) + && (!(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || RTEnvExistEx(hEnv, "TMPDIR")) ) + { + char szValue[RTPATH_MAX]; + size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szValue, sizeof(szValue)); + if (cbNeeded > 0 && cbNeeded < sizeof(szValue)) + { + char *pszTmp; + rc = RTStrCurrentCPToUtf8(&pszTmp, szValue); + if (RT_SUCCESS(rc)) + { + rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp); + RTStrFree(pszTmp); + } + } + else + rc = VERR_BUFFER_OVERFLOW; + } +#else + RT_NOREF_PV(hEnvToUse); RT_NOREF_PV(fFlags); RT_NOREF_PV(hEnv); +#endif + return rc; +} + + +/** + * Undos quoting and escape sequences and looks for stop characters. + * + * @returns Where to continue scanning in @a pszString. This points to the + * next character after the stop character, but for the zero terminator + * it points to the terminator character. + * @param pszString The string to undo quoting and escaping for. + * This is both input and output as the work is + * done in place. + * @param pfStoppedOnEqual Where to return whether we stopped work on a + * plain equal characater or not. If this is NULL, + * then the equal character is not a stop + * character, then only newline and the zero + * terminator are. + */ +static char *rtProcPosixProfileEnvUnquoteAndUnescapeString(char *pszString, bool *pfStoppedOnEqual) +{ + if (pfStoppedOnEqual) + *pfStoppedOnEqual = false; + + enum { kPlain, kSingleQ, kDoubleQ } enmState = kPlain; + char *pszDst = pszString; + for (;;) + { + char ch = *pszString++; + switch (ch) + { + default: + *pszDst++ = ch; + break; + + case '\\': + { + char ch2; + if ( enmState == kSingleQ + || (ch2 = *pszString) == '\0' + || (enmState == kDoubleQ && strchr("\\$`\"\n", ch2) == NULL) ) + *pszDst++ = ch; + else + { + *pszDst++ = ch2; + pszString++; + } + break; + } + + case '"': + if (enmState == kSingleQ) + *pszDst++ = ch; + else + enmState = enmState == kPlain ? kDoubleQ : kPlain; + break; + + case '\'': + if (enmState == kDoubleQ) + *pszDst++ = ch; + else + enmState = enmState == kPlain ? kSingleQ : kPlain; + break; + + case '\n': + if (enmState == kPlain) + { + *pszDst = '\0'; + return pszString; + } + *pszDst++ = ch; + break; + + case '=': + if (enmState == kPlain && pfStoppedOnEqual) + { + *pszDst = '\0'; + *pfStoppedOnEqual = true; + return pszString; + } + *pszDst++ = ch; + break; + + case '\0': + Assert(enmState == kPlain); + *pszDst = '\0'; + return pszString - 1; + } + } +} + + +/** + * Worker for rtProcPosixProfileEnvRunAndHarvest that parses the environment + * dump and loads it into hEnvToUse. + * + * @note This isn't entirely correct should any of the profile setup scripts + * unset any of the environment variables in the basic initial + * enviornment, but since that's unlikely and it's very convenient to + * have something half sensible as a basis if don't don't grok the dump + * entirely and would skip central stuff like PATH or HOME. + * + * @returns IPRT status code. + * @retval -VERR_PARSE_ERROR (positive, e.g. warning) if we run into trouble. + * @retval -VERR_INVALID_UTF8_ENCODING (positive, e.g. warning) if there are + * invalid UTF-8 in the environment. This isn't unlikely if the + * profile doesn't use UTF-8. This is unfortunately not something we + * can guess to accurately up front, so we don't do any guessing and + * hope everyone is sensible and use UTF-8. + * + * @param hEnvToUse The basic environment to extend with what we manage + * to parse here. + * @param pszEnvDump The environment dump to parse. Nominally in Bourne + * shell 'export -p' format. + * @param fWithMarkers Whether there are markers around the dump (C shell, + * tmux) or not. + */ +static int rtProcPosixProfileEnvHarvest(RTENV hEnvToUse, char *pszEnvDump, bool fWithMarkers) +{ + LogRel3(("**** pszEnvDump start ****\n%s**** pszEnvDump end ****\n", pszEnvDump)); + if (!LogIs3Enabled()) + LogFunc(("**** pszEnvDump start ****\n%s**** pszEnvDump end ****\n", pszEnvDump)); + + /* + * Clip dump at markers if we're using them (C shell). + */ + if (fWithMarkers) + { + char *pszStart = strstr(pszEnvDump, g_szEnvMarkerBegin); + AssertReturn(pszStart, -VERR_PARSE_ERROR); + pszStart += sizeof(g_szEnvMarkerBegin) - 1; + if (*pszStart == '\n') + pszStart++; + pszEnvDump = pszStart; + + char *pszEnd = strstr(pszStart, g_szEnvMarkerEnd); + AssertReturn(pszEnd, -VERR_PARSE_ERROR); + *pszEnd = '\0'; + } + + /* + * Since we're using /bin/sh -c "export -p" for all the dumping, we should + * always get lines on the format: + * export VAR1="Value 1" + * export VAR2=Value2 + * + * However, just in case something goes wrong, like bash doesn't think it + * needs to be posixly correct, try deal with the alternative where + * "declare -x " replaces the "export". + */ + const char *pszPrefix; + if ( strncmp(pszEnvDump, RT_STR_TUPLE("export")) == 0 + && RT_C_IS_BLANK(pszEnvDump[6])) + pszPrefix = "export "; + else if ( strncmp(pszEnvDump, RT_STR_TUPLE("declare")) == 0 + && RT_C_IS_BLANK(pszEnvDump[7]) + && pszEnvDump[8] == '-') + pszPrefix = "declare -x "; /* We only need to care about the non-array, non-function lines. */ + else + AssertFailedReturn(-VERR_PARSE_ERROR); + size_t const cchPrefix = strlen(pszPrefix); + + /* + * Process the lines, ignoring stuff which we don't grok. + * The shell should quote problematic characters. Bash double quotes stuff + * by default, whereas almquist's shell does it as needed and only the value + * side. + */ + int rc = VINF_SUCCESS; + while (pszEnvDump && *pszEnvDump != '\0') + { + /* + * Skip the prefixing command. + */ + if ( cchPrefix == 0 + || strncmp(pszEnvDump, pszPrefix, cchPrefix) == 0) + { + pszEnvDump += cchPrefix; + while (RT_C_IS_BLANK(*pszEnvDump)) + pszEnvDump++; + } + else + { + /* Oops, must find our bearings for some reason... */ + pszEnvDump = strchr(pszEnvDump, '\n'); + rc = -VERR_PARSE_ERROR; + continue; + } + + /* + * Parse out the variable name using typical bourne shell escaping + * and quoting rules. + */ + /** @todo We should throw away lines that aren't propertly quoted, now we + * just continue and use what we found. */ + const char *pszVar = pszEnvDump; + bool fStoppedOnPlainEqual = false; + pszEnvDump = rtProcPosixProfileEnvUnquoteAndUnescapeString(pszEnvDump, &fStoppedOnPlainEqual); + const char *pszValue = pszEnvDump; + if (fStoppedOnPlainEqual) + pszEnvDump = rtProcPosixProfileEnvUnquoteAndUnescapeString(pszEnvDump, NULL /*pfStoppedOnPlainEqual*/); + else + pszValue = ""; + + /* + * Add them if valid UTF-8, otherwise we simply drop them for now. + * The whole codeset stuff goes seriously wonky here as the environment + * we're harvesting probably contains it's own LC_CTYPE or LANG variables, + * so ignore the problem for now. + */ + if ( RTStrIsValidEncoding(pszVar) + && RTStrIsValidEncoding(pszValue)) + { + int rc2 = RTEnvSetEx(hEnvToUse, pszVar, pszValue); + AssertRCReturn(rc2, rc2); + } + else if (rc == VINF_SUCCESS) + rc = -VERR_INVALID_UTF8_ENCODING; + } + + return rc; +} + + +/** + * Runs the user's shell in login mode with some environment dumping logic and + * harvests the dump, putting it into hEnvToUse. + * + * This is a bit hairy, esp. with regards to codesets. + * + * @returns IPRT status code. Not all error statuses will be returned and the + * caller should just continue with whatever is in hEnvToUse. + * + * @param hEnvToUse On input this is the basic user environment, on success + * in is fleshed out with stuff from the login shell dump. + * @param pszAsUser The user name for the profile. + * @param uid The UID corrsponding to @a pszAsUser, ~0 if current user. + * @param gid The GID corrsponding to @a pszAsUser, ~0 if current user. + * @param pszShell The login shell. This is a writable string to avoid + * needing to make a copy of it when examining the path + * part, instead we make a temporary change to it which is + * always reverted before returning. + */ +static int rtProcPosixProfileEnvRunAndHarvest(RTENV hEnvToUse, const char *pszAsUser, uid_t uid, gid_t gid, char *pszShell) +{ + LogFlowFunc(("pszAsUser=%s uid=%u gid=%u pszShell=%s; hEnvToUse contains %u variables on entry\n", + pszAsUser, uid, gid, pszShell, RTEnvCountEx(hEnvToUse) )); + + /* + * The three standard handles should be pointed to /dev/null, the 3rd handle + * is used to dump the environment. + */ + RTPIPE hPipeR, hPipeW; + int rc = RTPipeCreate(&hPipeR, &hPipeW, 0); + if (RT_SUCCESS(rc)) + { + RTFILE hFileNull; + rc = RTFileOpenBitBucket(&hFileNull, RTFILE_O_READWRITE); + if (RT_SUCCESS(rc)) + { + int aRedirFds[4]; + aRedirFds[0] = aRedirFds[1] = aRedirFds[2] = RTFileToNative(hFileNull); + aRedirFds[3] = RTPipeToNative(hPipeW); + + /* + * Allocate a buffer for receiving the environment dump. + * + * This is fixed sized for simplicity and safety (creative user script + * shouldn't be allowed to exhaust our memory or such, after all we're + * most likely running with root privileges in this code path). + */ + size_t const cbEnvDump = _64K; + char * const pszEnvDump = (char *)RTMemTmpAllocZ(cbEnvDump); + if (pszEnvDump) + { + /* + * Our default approach is using /bin/sh: + */ + const char *pszExec = _PATH_BSHELL; + const char *apszArgs[8]; + apszArgs[0] = "-sh"; /* First arg must start with a dash for login shells. */ + apszArgs[1] = "-c"; + apszArgs[2] = "POSIXLY_CORRECT=1;export -p >&3"; + apszArgs[3] = NULL; + + /* + * But see if we can trust the shell to be a real usable shell. + * This would be great as different shell typically has different profile setup + * files and we'll endup with the wrong enviornment if we use a different shell. + */ + char szDashShell[32]; + char szExportArg[128]; + bool fWithMarkers = false; + const char *pszShellNm = RTPathFilename(pszShell); + if ( pszShellNm + && access(pszShellNm, X_OK)) + { + /* + * First the check that it's a known bin directory: + */ + size_t const cchShellPath = pszShellNm - pszShell; + char const chSaved = pszShell[cchShellPath - 1]; + pszShell[cchShellPath - 1] = '\0'; + if ( RTPathCompare(pszShell, "/bin") == 0 + || RTPathCompare(pszShell, "/usr/bin") == 0 + || RTPathCompare(pszShell, "/usr/local/bin") == 0) + { + /* + * Then see if we recognize the shell name. + */ + RTStrCopy(&szDashShell[1], sizeof(szDashShell) - 1, pszShellNm); + szDashShell[0] = '-'; + if ( strcmp(pszShellNm, "bash") == 0 + || strcmp(pszShellNm, "ksh") == 0 + || strcmp(pszShellNm, "ksh93") == 0 + || strcmp(pszShellNm, "zsh") == 0 + || strcmp(pszShellNm, "fish") == 0) + { + pszExec = pszShell; + apszArgs[0] = szDashShell; + + /* Use /bin/sh for doing the environment dumping so we get the same kind + of output from everyone and can limit our parsing + testing efforts. */ + RTStrPrintf(szExportArg, sizeof(szExportArg), + "%s -c 'POSIXLY_CORRECT=1;export -p >&3'", _PATH_BSHELL); + apszArgs[2] = szExportArg; + } + /* C shell is very annoying in that it closes fd 3 without regard to what + we might have put there, so we must use stdout here but with markers so + we can find the dump. + Seems tmux have similar issues as it doesn't work above, but works fine here. */ + else if ( strcmp(pszShellNm, "csh") == 0 + || strcmp(pszShellNm, "tcsh") == 0 + || strcmp(pszShellNm, "tmux") == 0) + { + pszExec = pszShell; + apszArgs[0] = szDashShell; + + fWithMarkers = true; + size_t cch = RTStrPrintf(szExportArg, sizeof(szExportArg), + "%s -c 'set -e;POSIXLY_CORRECT=1;echo %s;export -p;echo %s'", + _PATH_BSHELL, g_szEnvMarkerBegin, g_szEnvMarkerEnd); + Assert(cch < sizeof(szExportArg) - 1); RT_NOREF(cch); + apszArgs[2] = szExportArg; + + aRedirFds[1] = aRedirFds[3]; + aRedirFds[3] = -1; + } + } + pszShell[cchShellPath - 1] = chSaved; + } + + /* + * Create the process and wait for the output. + */ + LogFunc(("Executing '%s': '%s', '%s', '%s'\n", pszExec, apszArgs[0], apszArgs[1], apszArgs[2])); + RTPROCESS hProcess = NIL_RTPROCESS; + rc = rtProcPosixCreateInner(pszExec, apszArgs, hEnvToUse, hEnvToUse, 0 /*fFlags*/, + pszAsUser, uid, gid, RT_ELEMENTS(aRedirFds), aRedirFds, &hProcess); + if (RT_SUCCESS(rc)) + { + RTPipeClose(hPipeW); + hPipeW = NIL_RTPIPE; + + size_t offEnvDump = 0; + uint64_t const msStart = RTTimeMilliTS(); + for (;;) + { + size_t cbRead = 0; + if (offEnvDump < cbEnvDump - 1) + { + rc = RTPipeRead(hPipeR, &pszEnvDump[offEnvDump], cbEnvDump - 1 - offEnvDump, &cbRead); + if (RT_SUCCESS(rc)) + offEnvDump += cbRead; + else + { + LogFlowFunc(("Breaking out of read loop: %Rrc\n", rc)); + if (rc == VERR_BROKEN_PIPE) + rc = VINF_SUCCESS; + break; + } + pszEnvDump[offEnvDump] = '\0'; + } + else + { + LogFunc(("Too much data in environment dump, dropping it\n")); + rc = VERR_TOO_MUCH_DATA; + break; + } + + /* Do the timout check. */ + uint64_t const cMsElapsed = RTTimeMilliTS() - msStart; + if (cMsElapsed >= RT_MS_15SEC) + { + LogFunc(("Timed out after %RU64 ms\n", cMsElapsed)); + rc = VERR_TIMEOUT; + break; + } + + /* If we got no data in above wait for more to become ready. */ + if (!cbRead) + RTPipeSelectOne(hPipeR, RT_MS_15SEC - cMsElapsed); + } + + /* + * Kill the process and wait for it to avoid leaving zombies behind. + */ + /** @todo do we check the exit code? */ + int rc2 = RTProcWait(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, NULL); + if (RT_SUCCESS(rc2)) + LogFlowFunc(("First RTProcWait succeeded\n")); + else + { + LogFunc(("First RTProcWait failed (%Rrc), terminating and doing a blocking wait\n", rc2)); + RTProcTerminate(hProcess); + RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, NULL); + } + + /* + * Parse the result. + */ + if (RT_SUCCESS(rc)) + rc = rtProcPosixProfileEnvHarvest(hEnvToUse, pszEnvDump, fWithMarkers); + else + { + LogFunc(("Ignoring rc=%Rrc from the pipe read loop and continues with basic environment\n", rc)); + rc = -rc; + } + } + else + LogFunc(("Failed to create process '%s': %Rrc\n", pszExec, rc)); + RTMemTmpFree(pszEnvDump); + } + else + { + LogFunc(("Failed to allocate %#zx bytes for the dump\n", cbEnvDump)); + rc = VERR_NO_TMP_MEMORY; + } + RTFileClose(hFileNull); + } + else + LogFunc(("Failed to open /dev/null: %Rrc\n", rc)); + RTPipeClose(hPipeR); + RTPipeClose(hPipeW); + } + else + LogFunc(("Failed to create pipe: %Rrc\n", rc)); + LogFlowFunc(("returns %Rrc (hEnvToUse contains %u variables now)\n", rc, RTEnvCountEx(hEnvToUse))); + return rc; +} + + +/** + * Create an environment for the given user. + * + * This starts by creating a very basic environment and then tries to do it + * properly by running the user's shell in login mode with some environment + * dumping attached. The latter may fail and we'll ignore that for now and move + * ahead with the very basic environment. + * + * @returns IPRT status code. + * @param phEnvToUse Where to return the created environment. + * @param pszAsUser The user name for the profile. NULL if the current + * user. + * @param uid The UID corrsponding to @a pszAsUser, ~0 if NULL. + * @param gid The GID corrsponding to @a pszAsUser, ~0 if NULL. + * @param fFlags RTPROC_FLAGS_XXX + * @param papszPamEnv Array of environment variables returned by PAM, if + * it was used for authentication and produced anything. + * Otherwise NULL. + */ +static int rtProcPosixCreateProfileEnv(PRTENV phEnvToUse, const char *pszAsUser, uid_t uid, gid_t gid, + uint32_t fFlags, char **papszPamEnv) +{ + /* + * Get the passwd entry for the user. + */ + struct passwd Pwd; + struct passwd *pPwd = NULL; + char achBuf[_4K]; + int rc; + errno = 0; + if (pszAsUser) + rc = getpwnam_r(pszAsUser, &Pwd, achBuf, sizeof(achBuf), &pPwd); + else + rc = getpwuid_r(getuid(), &Pwd, achBuf, sizeof(achBuf), &pPwd); + if (rc == 0 && pPwd) + { + /* + * Convert stuff to UTF-8 since the environment is UTF-8. + */ + char *pszDir; + rc = RTStrCurrentCPToUtf8(&pszDir, pPwd->pw_dir); + if (RT_SUCCESS(rc)) + { +#if 0 /* Enable and modify this to test shells other that your login shell. */ + pPwd->pw_shell = (char *)"/bin/tmux"; +#endif + char *pszShell; + rc = RTStrCurrentCPToUtf8(&pszShell, pPwd->pw_shell); + if (RT_SUCCESS(rc)) + { + char *pszAsUserFree = NULL; + if (!pszAsUser) + { + rc = RTStrCurrentCPToUtf8(&pszAsUserFree, pPwd->pw_name); + if (RT_SUCCESS(rc)) + pszAsUser = pszAsUserFree; + } + if (RT_SUCCESS(rc)) + { + /* + * Create and populate the environment. + */ + rc = RTEnvCreate(phEnvToUse); + if (RT_SUCCESS(rc)) + { + RTENV hEnvToUse = *phEnvToUse; + rc = RTEnvSetEx(hEnvToUse, "HOME", pszDir); + if (RT_SUCCESS(rc)) + rc = RTEnvSetEx(hEnvToUse, "SHELL", pszShell); + if (RT_SUCCESS(rc)) + rc = RTEnvSetEx(hEnvToUse, "USER", pszAsUser); + if (RT_SUCCESS(rc)) + rc = RTEnvSetEx(hEnvToUse, "LOGNAME", pszAsUser); + if (RT_SUCCESS(rc)) + rc = RTEnvSetEx(hEnvToUse, "PATH", pPwd->pw_uid == 0 ? _PATH_STDPATH : _PATH_DEFPATH); + char szTmpPath[RTPATH_MAX]; + if (RT_SUCCESS(rc)) + { + RTStrPrintf(szTmpPath, sizeof(szTmpPath), "%s/%s", _PATH_MAILDIR, pszAsUser); + rc = RTEnvSetEx(hEnvToUse, "MAIL", szTmpPath); + } +#ifdef RT_OS_DARWIN + if (RT_SUCCESS(rc)) + { + /* TMPDIR is some unique per user directory under /var/folders on darwin, + so get the one for the current user. If we're launching the process as + a different user, rtProcPosixAdjustProfileEnvFromChild will update it + again for the actual child process user (provided we set it here). See + https://opensource.apple.com/source/Libc/Libc-997.1.1/darwin/_dirhelper.c + for the implementation of this query. */ + size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szTmpPath, sizeof(szTmpPath)); + if (cbNeeded > 0 && cbNeeded < sizeof(szTmpPath)) + { + char *pszTmp; + rc = RTStrCurrentCPToUtf8(&pszTmp, szTmpPath); + if (RT_SUCCESS(rc)) + { + rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp); + RTStrFree(pszTmp); + } + } + else + rc = VERR_BUFFER_OVERFLOW; + } +#endif + /* + * Add everything from the PAM environment. + */ + if (RT_SUCCESS(rc) && papszPamEnv != NULL) + for (size_t i = 0; papszPamEnv[i] != NULL && RT_SUCCESS(rc); i++) + { + char *pszEnvVar; + rc = RTStrCurrentCPToUtf8(&pszEnvVar, papszPamEnv[i]); + if (RT_SUCCESS(rc)) + { + char *pszValue = strchr(pszEnvVar, '='); + if (pszValue) + *pszValue++ = '\0'; + rc = RTEnvSetEx(hEnvToUse, pszEnvVar, pszValue ? pszValue : ""); + RTStrFree(pszEnvVar); + } + /* Ignore conversion issue, though LogRel them. */ + else if (rc != VERR_NO_STR_MEMORY && rc != VERR_NO_MEMORY) + { + LogRelMax(256, ("RTStrCurrentCPToUtf8(,%.*Rhxs) -> %Rrc\n", strlen(pszEnvVar), pszEnvVar, rc)); + rc = -rc; + } + } + if (RT_SUCCESS(rc)) + { + /* + * Now comes the fun part where we need to try run a shell in login mode + * and harvest its final environment to get the proper environment for + * the user. We ignore some failures here so buggy login scrips and + * other weird stuff won't trip us up too badly. + */ + if (!(fFlags & RTPROC_FLAGS_ONLY_BASIC_PROFILE)) + rc = rtProcPosixProfileEnvRunAndHarvest(hEnvToUse, pszAsUser, uid, gid, pszShell); + } + + if (RT_FAILURE(rc)) + RTEnvDestroy(hEnvToUse); + } + RTStrFree(pszAsUserFree); + } + RTStrFree(pszShell); + } + RTStrFree(pszDir); + } + } + else + rc = errno ? RTErrConvertFromErrno(errno) : VERR_ACCESS_DENIED; + return rc; +} + + +/** + * Converts the arguments to the child's LC_CTYPE charset if necessary. + * + * @returns IPRT status code. + * @param papszArgs The arguments (UTF-8). + * @param hEnvToUse The child process environment. + * @param ppapszArgs Where to return the converted arguments. The array + * entries must be freed by RTStrFree and the array itself + * by RTMemFree. + */ +static int rtProcPosixConvertArgv(const char * const *papszArgs, RTENV hEnvToUse, char ***ppapszArgs) +{ + *ppapszArgs = (char **)papszArgs; + + /* + * The first thing we need to do here is to try guess the codeset of the + * child process and check if it's UTF-8 or not. + */ + const char *pszEncoding; + char szEncoding[512]; + if (hEnvToUse == RTENV_DEFAULT) + { + /* Same environment as us, assume setlocale is up to date: */ + pszEncoding = rtStrGetLocaleCodeset(); + } + else + { + /* + * LC_ALL overrides everything else. The LC_* environment variables are often set + * to the empty string so move on the next variable if that is the case (that's + * what setlocale in glibc does). + */ + const char *pszVar; + int rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_ALL", szEncoding, sizeof(szEncoding), NULL); + if (rc == VERR_ENV_VAR_NOT_FOUND || (RT_SUCCESS(rc) && szEncoding[0] == '\0')) + rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_CTYPE", szEncoding, sizeof(szEncoding), NULL); + if (rc == VERR_ENV_VAR_NOT_FOUND || (RT_SUCCESS(rc) && szEncoding[0] == '\0')) + rc = RTEnvGetEx(hEnvToUse, pszVar = "LANG", szEncoding, sizeof(szEncoding), NULL); + if (RT_SUCCESS(rc) && szEncoding[0] != '\0') + { + /* + * LC_ALL can contain a composite locale consisting of the locales of each of the + * categories in two different formats depending on the OS. On Solaris, macOS, and + * *BSD composite locale names use slash ('/') as the separator and the following + * order for the categories: + * LC_CTYPE/LC_NUMERIC/LC_TIME/LC_COLLATE/LC_MONETARY/LC_MESSAGES + * e.g.: + * en_US.UTF-8/POSIX/el_GR.UTF-8/el_CY.UTF-8/en_GB.UTF-8/es_ES.UTF-8 + * + * On Solaris there is also a leading slash. + * + * On Linux and OS/2 the composite locale format is made up of key-value pairs + * of category names and locales of the form 'name=value' with each element + * separated by a semicolon in the same order as above with following additional + * categories included as well: + * LC_PAPER/LC_NAME/LC_ADDRESS/LC_TELEPHONE/LC_MEASUREMENT/LC_IDENTIFICATION + * e.g. + * LC_CTYPE=fr_BE;LC_NUMERIC=fr_BE@euro;LC_TIME=fr_BE.utf8;LC_COLLATE=fr_CA;\ + * LC_MONETARY=fr_CA.utf8;LC_MESSAGES=fr_CH;LC_PAPER=fr_CH.utf8;LC_NAME=fr_FR;\ + * LC_ADDRESS=fr_FR.utf8;LC_TELEPHONE=fr_LU;LC_MEASUREMENT=fr_LU@euro;\ + * LC_IDENTIFICATION=fr_LU.utf8 + */ + char *pszEncodingStart = szEncoding; +#if !defined(RT_OS_LINUX) && !defined(RT_OS_OS2) + if (*pszEncodingStart == '/') + pszEncodingStart++; + char *pszSlash = strchr(pszEncodingStart, '/'); + if (pszSlash) + *pszSlash = '\0'; /* This ASSUMES the first one is LC_CTYPE! */ +#else + char *pszCType = strstr(pszEncodingStart, "LC_CTYPE="); + if (pszCType) + { + pszEncodingStart = pszCType + sizeof("LC_CTYPE=") - 1; + + char *pszSemiColon = strchr(pszEncodingStart, ';'); + if (pszSemiColon) + *pszSemiColon = '\0'; + } +#endif + + /* + * Use newlocale and nl_langinfo_l to determine the default codeset for the locale + * specified in the child's environment. These routines have been around since + * ancient days on Linux and for quite a long time on macOS, Solaris, and *BSD but + * to ensure their availability check that LC_CTYPE_MASK is defined. + * + * Note! The macOS nl_langinfo(3)/nl_langinfo_l(3) routines return a pointer to an + * empty string for "short" locale names like en_NZ, it_IT, el_GR, etc. so use + * UTF-8 in those cases as it is the default for short name locales on macOS + * (see also rtStrGetLocaleCodeset). + */ +#ifdef LC_CTYPE_MASK + locale_t hLocale = newlocale(LC_CTYPE_MASK, pszEncodingStart, (locale_t)0); + if (hLocale != (locale_t)0) + { + const char *pszCodeset = nl_langinfo_l(CODESET, hLocale); + Log2Func(("nl_langinfo_l(CODESET, %s=%s) -> %s\n", pszVar, pszEncodingStart, pszCodeset)); + if (!pszCodeset || *pszCodeset == '\0') +# ifdef RT_OS_DARWIN + pszEncoding = "UTF-8"; +# else + pszEncoding = "ASCII"; +# endif + else + { + rc = RTStrCopy(szEncoding, sizeof(szEncoding), pszCodeset); + AssertRC(rc); /* cannot possibly overflow */ + } + + freelocale(hLocale); + pszEncoding = szEncoding; + } + else +#endif + { + /* If there is something that ought to be a character set encoding, try use it: */ + const char *pszDot = strchr(pszEncodingStart, '.'); + if (pszDot) + pszDot = RTStrStripL(pszDot + 1); + if (pszDot && *pszDot != '\0') + { + pszEncoding = pszDot; + Log2Func(("%s=%s -> %s (simple)\n", pszVar, szEncoding, pszEncoding)); + } + else + { + /* This is mostly wrong, but I cannot think of anything better now: */ + pszEncoding = rtStrGetLocaleCodeset(); + LogFunc(("No newlocale or it failed (on '%s=%s', errno=%d), falling back on %s that we're using...\n", + pszVar, pszEncodingStart, errno, pszEncoding)); + } + } + RT_NOREF_PV(pszVar); + } + else +#ifdef RT_OS_DARWIN /* @bugref{10153}: Darwin defaults to UTF-8. */ + pszEncoding = "UTF-8"; +#else + pszEncoding = "ASCII"; +#endif + } + + /* + * Do nothing if it's UTF-8. + */ + if (rtStrIsCodesetUtf8(pszEncoding)) + { + LogFlowFunc(("No conversion needed (%s)\n", pszEncoding)); + return VINF_SUCCESS; + } + + + /* + * Do the conversion. + */ + size_t cArgs = 0; + while (papszArgs[cArgs] != NULL) + cArgs++; + LogFunc(("Converting #%u arguments to %s...\n", cArgs, pszEncoding)); + + char **papszArgsConverted = (char **)RTMemAllocZ(sizeof(papszArgsConverted[0]) * (cArgs + 2)); + AssertReturn(papszArgsConverted, VERR_NO_MEMORY); + + void *pvConversionCache = NULL; + rtStrLocalCacheInit(&pvConversionCache); + for (size_t i = 0; i < cArgs; i++) + { + int rc = rtStrLocalCacheConvert(papszArgs[i], strlen(papszArgs[i]), "UTF-8", + &papszArgsConverted[i], 0, pszEncoding, &pvConversionCache); + if (RT_SUCCESS(rc) && rc != VWRN_NO_TRANSLATION) + { /* likely */ } + else + { + LogRelMax(100, ("Failed to convert argument #%u '%s' to '%s': %Rrc\n", i, papszArgs[i], pszEncoding, rc)); + while (i-- > 0) + RTStrFree(papszArgsConverted[i]); + RTMemFree(papszArgsConverted); + rtStrLocalCacheDelete(&pvConversionCache); + return rc == VWRN_NO_TRANSLATION || rc == VERR_NO_TRANSLATION ? VERR_PROC_NO_ARG_TRANSLATION : rc; + } + } + + rtStrLocalCacheDelete(&pvConversionCache); + *ppapszArgs = papszArgsConverted; + return VINF_SUCCESS; +} + + +/** + * The result structure for rtPathFindExec/RTPathTraverseList. + * @todo move to common path code? + */ +typedef struct RTPATHINTSEARCH +{ + /** For EACCES or EPERM errors that we continued on. + * @note Must be initialized to VINF_SUCCESS. */ + int rcSticky; + /** Buffer containing the filename. */ + char szFound[RTPATH_MAX]; +} RTPATHINTSEARCH; +/** Pointer to a rtPathFindExec/RTPathTraverseList result. */ +typedef RTPATHINTSEARCH *PRTPATHINTSEARCH; + + +/** + * RTPathTraverseList callback used by RTProcCreateEx to locate the executable. + */ +static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2) +{ + const char *pszExec = (const char *)pvUser1; + PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)pvUser2; + int rc = RTPathJoinEx(pResult->szFound, sizeof(pResult->szFound), pchPath, cchPath, pszExec, RTSTR_MAX, + RTPATH_STR_F_STYLE_HOST); + if (RT_SUCCESS(rc)) + { + const char *pszNativeExec = NULL; + rc = rtPathToNative(&pszNativeExec, pResult->szFound, NULL); + if (RT_SUCCESS(rc)) + { + if (!access(pszNativeExec, X_OK)) + rc = VINF_SUCCESS; + else + { + if ( errno == EACCES + || errno == EPERM) + pResult->rcSticky = RTErrConvertFromErrno(errno); + rc = VERR_TRY_AGAIN; + } + rtPathFreeNative(pszNativeExec, pResult->szFound); + } + else + AssertRCStmt(rc, rc = VERR_TRY_AGAIN /* don't stop on this, whatever it is */); + } + return rc; +} + + +RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, + PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser, + const char *pszPassword, void *pvExtraData, PRTPROCESS phProcess) +{ + int rc; + LogFlow(("RTProcCreateEx: pszExec=%s pszAsUser=%s fFlags=%#x phStdIn=%p phStdOut=%p phStdErr=%p\n", + pszExec, pszAsUser, fFlags, phStdIn, phStdOut, phStdErr)); + + /* + * Input validation + */ + AssertPtrReturn(pszExec, VERR_INVALID_POINTER); + AssertReturn(*pszExec, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER); + AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER); + AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER); + AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER); + AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER); +#if defined(RT_OS_OS2) + if (fFlags & RTPROC_FLAGS_DETACHED) + return VERR_PROC_DETACH_NOT_SUPPORTED; +#endif + AssertReturn(pvExtraData == NULL || (fFlags & RTPROC_FLAGS_DESIRED_SESSION_ID), VERR_INVALID_PARAMETER); + + /* + * Get the file descriptors for the handles we've been passed. + */ + PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr }; + int aStdFds[3] = { -1, -1, -1 }; + for (int i = 0; i < 3; i++) + { + if (paHandles[i]) + { + AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER); + switch (paHandles[i]->enmType) + { + case RTHANDLETYPE_FILE: + aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE + ? (int)RTFileToNative(paHandles[i]->u.hFile) + : -2 /* close it */; + break; + + case RTHANDLETYPE_PIPE: + aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE + ? (int)RTPipeToNative(paHandles[i]->u.hPipe) + : -2 /* close it */; + break; + + case RTHANDLETYPE_SOCKET: + aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET + ? (int)RTSocketToNative(paHandles[i]->u.hSocket) + : -2 /* close it */; + break; + + default: + AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER); + } + /** @todo check the close-on-execness of these handles? */ + } + } + + for (int i = 0; i < 3; i++) + if (aStdFds[i] == i) + aStdFds[i] = -1; + LogFlowFunc(("aStdFds={%d, %d, %d}\n", aStdFds[0], aStdFds[1], aStdFds[2])); + + for (int i = 0; i < 3; i++) + AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i, + ("%i := %i not possible because we're lazy\n", i, aStdFds[i]), + VERR_NOT_SUPPORTED); + + /* + * Validate the credentials if a user is specified. + */ + bool const fNeedLoginEnv = (fFlags & RTPROC_FLAGS_PROFILE) + && ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || hEnv == RTENV_DEFAULT); + uid_t uid = ~(uid_t)0; + gid_t gid = ~(gid_t)0; + char **papszPamEnv = NULL; + if (pszAsUser) + { + rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid, fNeedLoginEnv ? &papszPamEnv : NULL); + if (RT_FAILURE(rc)) + return rc; + } +#ifdef IPRT_USE_PAM + /* + * User unchanged, but if PROFILE is request we must try get the PAM + * environmnet variables. + * + * For this to work, we'll need a special PAM service profile which doesn't + * actually do any authentication, only concerns itself with the enviornment + * setup. gdm-launch-environment is such one, and we use it if we haven't + * got an IPRT specific one there. + */ + else if (fNeedLoginEnv) + { + const char *pszService; + if (rtProcPosixPamServiceExists("iprt-environment")) + pszService = "iprt-environment"; +# ifdef IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT + else if (rtProcPosixPamServiceExists(IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT)) + pszService = IPRT_PAM_NATIVE_SERVICE_NAME_ENVIRONMENT; +# endif + else if (rtProcPosixPamServiceExists("gdm-launch-environment")) + pszService = "gdm-launch-environment"; + else + pszService = NULL; + if (pszService) + { + char szLoginName[512]; + rc = getlogin_r(szLoginName, sizeof(szLoginName)); + if (rc == 0) + rc = rtProcPosixAuthenticateUsingPam(pszService, szLoginName, "xxx", &papszPamEnv, NULL); + } + } +#endif + + /* + * Create the child environment if either RTPROC_FLAGS_PROFILE or + * RTPROC_FLAGS_ENV_CHANGE_RECORD are in effect. + */ + RTENV hEnvToUse = hEnv; + if ( (fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_PROFILE)) + && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) + || hEnv == RTENV_DEFAULT) ) + { + if (fFlags & RTPROC_FLAGS_PROFILE) + rc = rtProcPosixCreateProfileEnv(&hEnvToUse, pszAsUser, uid, gid, fFlags, papszPamEnv); + else + rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT); + rtProcPosixFreePamEnv(papszPamEnv); + papszPamEnv = NULL; + if (RT_FAILURE(rc)) + return rc; + + if ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) && hEnv != RTENV_DEFAULT) + { + rc = RTEnvApplyChanges(hEnvToUse, hEnv); + if (RT_FAILURE(rc)) + { + RTEnvDestroy(hEnvToUse); + return rc; + } + } + } + Assert(papszPamEnv == NULL); + + /* + * Check for execute access to the file, searching the PATH if needed. + */ + const char *pszNativeExec = NULL; + rc = rtPathToNative(&pszNativeExec, pszExec, NULL); + if (RT_SUCCESS(rc)) + { + if (access(pszNativeExec, X_OK) == 0) + rc = VINF_SUCCESS; + else + { + rc = errno; + rtPathFreeNative(pszNativeExec, pszExec); + + if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH) + || rc != ENOENT + || RTPathHavePath(pszExec) ) + rc = RTErrConvertFromErrno(rc); + else + { + /* Search the PATH for it: */ + char *pszPath = RTEnvDupEx(hEnvToUse, "PATH"); + if (pszPath) + { + PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)alloca(sizeof(*pResult)); + pResult->rcSticky = VINF_SUCCESS; + rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, pResult); + RTStrFree(pszPath); + if (RT_SUCCESS(rc)) + { + /* Found it. Now, convert to native path: */ + pszExec = pResult->szFound; + rc = rtPathToNative(&pszNativeExec, pszExec, NULL); + } + else + rc = rc != VERR_END_OF_STRING ? rc + : pResult->rcSticky == VINF_SUCCESS ? VERR_FILE_NOT_FOUND : pResult->rcSticky; + } + else + rc = VERR_NO_STR_MEMORY; + } + } + if (RT_SUCCESS(rc)) + { + /* + * Convert arguments to child codeset if necessary. + */ + char **papszArgsConverted = (char **)papszArgs; + if (!(fFlags & RTPROC_FLAGS_UTF8_ARGV)) + rc = rtProcPosixConvertArgv(papszArgs, hEnvToUse, &papszArgsConverted); + if (RT_SUCCESS(rc)) + { + /* + * The rest of the process creation is reused internally by rtProcPosixCreateProfileEnv. + */ + rc = rtProcPosixCreateInner(pszNativeExec, papszArgsConverted, hEnv, hEnvToUse, fFlags, pszAsUser, uid, gid, + RT_ELEMENTS(aStdFds), aStdFds, phProcess); + + } + + /* Free the translated argv copy, if needed. */ + if (papszArgsConverted != (char **)papszArgs) + { + for (size_t i = 0; papszArgsConverted[i] != NULL; i++) + RTStrFree(papszArgsConverted[i]); + RTMemFree(papszArgsConverted); + } + rtPathFreeNative(pszNativeExec, pszExec); + } + } + if (hEnvToUse != hEnv) + RTEnvDestroy(hEnvToUse); + return rc; +} + + +/** + * The inner 2nd half of RTProcCreateEx. + * + * This is also used by rtProcPosixCreateProfileEnv(). + * + * @returns IPRT status code. + * @param pszNativeExec The executable to run (absolute path, X_OK). + * Native path. + * @param papszArgs The arguments. Caller has done codeset conversions. + * @param hEnv The original enviornment request, needed for + * adjustments if starting as different user. + * @param hEnvToUse The environment we should use. + * @param fFlags The process creation flags, RTPROC_FLAGS_XXX. + * @param pszAsUser The user to start the process as, if requested. + * @param uid The UID corrsponding to @a pszAsUser, ~0 if NULL. + * @param gid The GID corrsponding to @a pszAsUser, ~0 if NULL. + * @param cRedirFds Number of redirection file descriptors. + * @param paRedirFds Pointer to redirection file descriptors. Entries + * containing -1 are not modified (inherit from parent), + * -2 indicates that the descriptor should be closed in the + * child. + * @param phProcess Where to return the process ID on success. + */ +static int rtProcPosixCreateInner(const char *pszNativeExec, const char * const *papszArgs, RTENV hEnv, RTENV hEnvToUse, + uint32_t fFlags, const char *pszAsUser, uid_t uid, gid_t gid, + unsigned cRedirFds, int *paRedirFds, PRTPROCESS phProcess) +{ + /* + * Get the environment block. + */ + const char * const *papszEnv = RTEnvGetExecEnvP(hEnvToUse); + AssertPtrReturn(papszEnv, VERR_INVALID_HANDLE); + + /* + * Optimize the redirections. + */ + while (cRedirFds > 0 && paRedirFds[cRedirFds - 1] == -1) + cRedirFds--; + + /* + * Child PID. + */ + pid_t pid = -1; + + /* + * Take care of detaching the process. + * + * HACK ALERT! Put the process into a new process group with pgid = pid + * to make sure it differs from that of the parent process to ensure that + * the IPRT waitpid call doesn't race anyone (read XPCOM) doing group wide + * waits. setsid() includes the setpgid() functionality. + * 2010-10-11 XPCOM no longer waits for anything, but it cannot hurt. + */ +#ifndef RT_OS_OS2 + if (fFlags & RTPROC_FLAGS_DETACHED) + { +# ifdef RT_OS_SOLARIS + int templateFd = -1; + if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT)) + { + templateFd = rtSolarisContractPreFork(); + if (templateFd == -1) + return VERR_OPEN_FAILED; + } +# endif /* RT_OS_SOLARIS */ + pid = fork(); + if (!pid) + { +# ifdef RT_OS_SOLARIS + if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT)) + rtSolarisContractPostForkChild(templateFd); +# endif + setsid(); /* see comment above */ + + pid = -1; + /* Child falls through to the actual spawn code below. */ + } + else + { +# ifdef RT_OS_SOLARIS + if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT)) + rtSolarisContractPostForkParent(templateFd, pid); +# endif + if (pid > 0) + { + /* Must wait for the temporary process to avoid a zombie. */ + int status = 0; + pid_t pidChild = 0; + + /* Restart if we get interrupted. */ + do + { + pidChild = waitpid(pid, &status, 0); + } while ( pidChild == -1 + && errno == EINTR); + + /* Assume that something wasn't found. No detailed info. */ + if (status) + return VERR_PROCESS_NOT_FOUND; + if (phProcess) + *phProcess = 0; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); + } + } +#endif + + /* + * Spawn the child. + * + * Any spawn code MUST not execute any atexit functions if it is for a + * detached process. It would lead to running the atexit functions which + * make only sense for the parent. libORBit e.g. gets confused by multiple + * execution. Remember, there was only a fork() so far, and until exec() + * is successfully run there is nothing which would prevent doing anything + * silly with the (duplicated) file descriptors. + */ + int rc; +#ifdef HAVE_POSIX_SPAWN + /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */ + if ( uid == ~(uid_t)0 + && gid == ~(gid_t)0) + { + /* Spawn attributes. */ + posix_spawnattr_t Attr; + rc = posix_spawnattr_init(&Attr); + if (!rc) + { + /* Indicate that process group and signal mask are to be changed, + and that the child should use default signal actions. */ + rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF); + Assert(rc == 0); + + /* The child starts in its own process group. */ + if (!rc) + { + rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */); + Assert(rc == 0); + } + + /* Unmask all signals. */ + if (!rc) + { + sigset_t SigMask; + sigemptyset(&SigMask); + rc = posix_spawnattr_setsigmask(&Attr, &SigMask); Assert(rc == 0); + } + + /* File changes. */ + posix_spawn_file_actions_t FileActions; + posix_spawn_file_actions_t *pFileActions = NULL; + if (!rc && cRedirFds > 0) + { + rc = posix_spawn_file_actions_init(&FileActions); + if (!rc) + { + pFileActions = &FileActions; + for (unsigned i = 0; i < cRedirFds; i++) + { + int fd = paRedirFds[i]; + if (fd == -2) + rc = posix_spawn_file_actions_addclose(&FileActions, i); + else if (fd >= 0 && fd != (int)i) + { + rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i); + if (!rc) + { + for (unsigned j = i + 1; j < cRedirFds; j++) + if (paRedirFds[j] == fd) + { + fd = -1; + break; + } + if (fd >= 0) + rc = posix_spawn_file_actions_addclose(&FileActions, fd); + } + } + if (rc) + break; + } + } + } + + if (!rc) + rc = posix_spawn(&pid, pszNativeExec, pFileActions, &Attr, (char * const *)papszArgs, + (char * const *)papszEnv); + + /* cleanup */ + int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2); + if (pFileActions) + { + rc2 = posix_spawn_file_actions_destroy(pFileActions); + Assert(rc2 == 0); + } + + /* return on success.*/ + if (!rc) + { + /* For a detached process this happens in the temp process, so + * it's not worth doing anything as this process must exit. */ + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(0); + if (phProcess) + *phProcess = pid; + return VINF_SUCCESS; + } + } + /* For a detached process this happens in the temp process, so + * it's not worth doing anything as this process must exit. */ + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(124); + } + else +#endif + { +#ifdef RT_OS_SOLARIS + int templateFd = -1; + if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT)) + { + templateFd = rtSolarisContractPreFork(); + if (templateFd == -1) + return VERR_OPEN_FAILED; + } +#endif /* RT_OS_SOLARIS */ + pid = fork(); + if (!pid) + { +#ifdef RT_OS_SOLARIS + if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT)) + rtSolarisContractPostForkChild(templateFd); +#endif /* RT_OS_SOLARIS */ + if (!(fFlags & RTPROC_FLAGS_DETACHED)) + setpgid(0, 0); /* see comment above */ + + /* + * Change group and user if requested. + */ +#if 1 /** @todo This needs more work, see suplib/hardening. */ + if (pszAsUser) + { + int ret = initgroups(pszAsUser, gid); + if (ret) + { + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(126); + else + exit(126); + } + } + if (gid != ~(gid_t)0) + { + if (setgid(gid)) + { + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(126); + else + exit(126); + } + } + + if (uid != ~(uid_t)0) + { + if (setuid(uid)) + { + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(126); + else + exit(126); + } + } +#endif + + /* + * Some final profile environment tweaks, if running as user. + */ + if ( (fFlags & RTPROC_FLAGS_PROFILE) + && pszAsUser + && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) + || hEnv == RTENV_DEFAULT) ) + { + rc = rtProcPosixAdjustProfileEnvFromChild(hEnvToUse, fFlags, hEnv); + papszEnv = RTEnvGetExecEnvP(hEnvToUse); + if (RT_FAILURE(rc) || !papszEnv) + { + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(126); + else + exit(126); + } + } + + /* + * Unset the signal mask. + */ + sigset_t SigMask; + sigemptyset(&SigMask); + rc = sigprocmask(SIG_SETMASK, &SigMask, NULL); + Assert(rc == 0); + + /* + * Apply changes to the standard file descriptor and stuff. + */ + for (unsigned i = 0; i < cRedirFds; i++) + { + int fd = paRedirFds[i]; + if (fd == -2) + close(i); + else if (fd >= 0) + { + int rc2 = dup2(fd, i); + if (rc2 != (int)i) + { + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(125); + else + exit(125); + } + for (unsigned j = i + 1; j < cRedirFds; j++) + if (paRedirFds[j] == fd) + { + fd = -1; + break; + } + if (fd >= 0) + close(fd); + } + } + + /* + * Finally, execute the requested program. + */ + rc = execve(pszNativeExec, (char * const *)papszArgs, (char * const *)papszEnv); + if (errno == ENOEXEC) + { + /* This can happen when trying to start a shell script without the magic #!/bin/sh */ + RTAssertMsg2Weak("Cannot execute this binary format!\n"); + } + else + RTAssertMsg2Weak("execve returns %d errno=%d (%s)\n", rc, errno, pszNativeExec); + RTAssertReleasePanic(); + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(127); + else + exit(127); + } +#ifdef RT_OS_SOLARIS + if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT)) + rtSolarisContractPostForkParent(templateFd, pid); +#endif /* RT_OS_SOLARIS */ + if (pid > 0) + { + /* For a detached process this happens in the temp process, so + * it's not worth doing anything as this process must exit. */ + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(0); + if (phProcess) + *phProcess = pid; + return VINF_SUCCESS; + } + /* For a detached process this happens in the temp process, so + * it's not worth doing anything as this process must exit. */ + if (fFlags & RTPROC_FLAGS_DETACHED) + _Exit(124); + return RTErrConvertFromErrno(errno); + } + + return VERR_NOT_IMPLEMENTED; +} + + +RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile) +{ + /* + * Fork the child process in a new session and quit the parent. + * + * - fork once and create a new session (setsid). This will detach us + * from the controlling tty meaning that we won't receive the SIGHUP + * (or any other signal) sent to that session. + * - The SIGHUP signal is ignored because the session/parent may throw + * us one before we get to the setsid. + * - When the parent exit(0) we will become an orphan and re-parented to + * the init process. + * - Because of the sometimes unexpected semantics of assigning the + * controlling tty automagically when a session leader first opens a tty, + * we will fork() once more to get rid of the session leadership role. + */ + + /* We start off by opening the pidfile, so that we can fail straight away + * if it already exists. */ + int fdPidfile = -1; + if (pszPidfile != NULL) + { + /* @note the exclusive create is not guaranteed on all file + * systems (e.g. NFSv2) */ + if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1) + return RTErrConvertFromErrno(errno); + } + + /* Ignore SIGHUP straight away. */ + struct sigaction OldSigAct; + struct sigaction SigAct; + memset(&SigAct, 0, sizeof(SigAct)); + SigAct.sa_handler = SIG_IGN; + int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct); + + /* First fork, to become independent process. */ + pid_t pid = fork(); + if (pid == -1) + { + if (fdPidfile != -1) + close(fdPidfile); + return RTErrConvertFromErrno(errno); + } + if (pid != 0) + { + /* Parent exits, no longer necessary. The child gets reparented + * to the init process. */ + exit(0); + } + + /* Create new session, fix up the standard file descriptors and the + * current working directory. */ + /** @todo r=klaus the webservice uses this function and assumes that the + * contract id of the daemon is the same as that of the original process. + * Whenever this code is changed this must still remain possible. */ + pid_t newpgid = setsid(); + int SavedErrno = errno; + if (rcSigAct != -1) + sigaction(SIGHUP, &OldSigAct, NULL); + if (newpgid == -1) + { + if (fdPidfile != -1) + close(fdPidfile); + return RTErrConvertFromErrno(SavedErrno); + } + + if (!fNoClose) + { + /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */ + int fd = open("/dev/null", O_RDWR); + if (fd == -1) /* paranoia */ + { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + fd = open("/dev/null", O_RDWR); + } + if (fd != -1) + { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } + } + + if (!fNoChDir) + { + int rcIgnored = chdir("/"); + NOREF(rcIgnored); + } + + /* Second fork to lose session leader status. */ + pid = fork(); + if (pid == -1) + { + if (fdPidfile != -1) + close(fdPidfile); + return RTErrConvertFromErrno(errno); + } + + if (pid != 0) + { + /* Write the pid file, this is done in the parent, before exiting. */ + if (fdPidfile != -1) + { + char szBuf[256]; + size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid); + ssize_t cbIgnored = write(fdPidfile, szBuf, cbPid); NOREF(cbIgnored); + close(fdPidfile); + } + exit(0); + } + + if (fdPidfile != -1) + close(fdPidfile); + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/posix/process-posix.cpp b/src/VBox/Runtime/r3/posix/process-posix.cpp new file mode 100644 index 00000000..b0074f1c --- /dev/null +++ b/src/VBox/Runtime/r3/posix/process-posix.cpp @@ -0,0 +1,279 @@ +/* $Id: process-posix.cpp $ */ +/** @file + * IPRT - Process, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <signal.h> +#include <pwd.h> + +#include <iprt/process.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/pipe.h> +#include <iprt/socket.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include "internal/process.h" + + +RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus) +{ + int rc; + do rc = RTProcWaitNoResume(Process, fFlags, pProcStatus); + while (rc == VERR_INTERRUPTED); + return rc; +} + + +RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus) +{ + /* + * Validate input. + */ + if (Process <= 0) + { + AssertMsgFailed(("Invalid Process=%d\n", Process)); + return VERR_INVALID_PARAMETER; + } + if (fFlags & ~(RTPROCWAIT_FLAGS_NOBLOCK | RTPROCWAIT_FLAGS_BLOCK)) + { + AssertMsgFailed(("Invalid flags %#x\n", fFlags)); + return VERR_INVALID_PARAMETER; + } + + /* + * Perform the wait. + */ + int iStatus = 0; + int rc = waitpid(Process, &iStatus, fFlags & RTPROCWAIT_FLAGS_NOBLOCK ? WNOHANG : 0); + if (rc > 0) + { + /* + * Fill in the status structure. + */ + if (pProcStatus) + { + if (WIFEXITED(iStatus)) + { + pProcStatus->enmReason = RTPROCEXITREASON_NORMAL; + pProcStatus->iStatus = WEXITSTATUS(iStatus); + } + else if (WIFSIGNALED(iStatus)) + { + pProcStatus->enmReason = RTPROCEXITREASON_SIGNAL; + pProcStatus->iStatus = WTERMSIG(iStatus); + } + else + { + Assert(!WIFSTOPPED(iStatus)); + pProcStatus->enmReason = RTPROCEXITREASON_ABEND; + pProcStatus->iStatus = iStatus; + } + } + return VINF_SUCCESS; + } + + /* + * Child running? + */ + if (!rc) + { + Assert(fFlags & RTPROCWAIT_FLAGS_NOBLOCK); + return VERR_PROCESS_RUNNING; + } + + /* + * Figure out which error to return. + */ + int iErr = errno; + if (iErr == ECHILD) + return VERR_PROCESS_NOT_FOUND; + return RTErrConvertFromErrno(iErr); +} + + +RTR3DECL(int) RTProcTerminate(RTPROCESS Process) +{ + if (Process == NIL_RTPROCESS) + return VINF_SUCCESS; + + if (!kill(Process, SIGKILL)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(uint64_t) RTProcGetAffinityMask(void) +{ + /// @todo + return 1; +} + + +RTR3DECL(int) RTProcQueryParent(RTPROCESS hProcess, PRTPROCESS phParent) +{ + if (hProcess == RTProcSelf()) + { + *phParent = getppid(); + return VINF_SUCCESS; + } + return VERR_NOT_SUPPORTED; +} + + +RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, size_t *pcbUser) +{ + AssertReturn( (pszUser && cbUser > 0) + || (!pszUser && !cbUser), VERR_INVALID_PARAMETER); + AssertReturn(pcbUser || pszUser, VERR_INVALID_PARAMETER); + + int rc; + if ( hProcess == NIL_RTPROCESS + || hProcess == RTProcSelf()) + { + /* + * Figure a good buffer estimate. + */ + int32_t cbPwdMax = sysconf(_SC_GETPW_R_SIZE_MAX); + if (cbPwdMax <= _1K) + cbPwdMax = _1K; + else + AssertStmt(cbPwdMax <= 32*_1M, cbPwdMax = 32*_1M); + char *pchBuf = (char *)RTMemTmpAllocZ(cbPwdMax); + if (pchBuf) + { + /* + * Get the password file entry. + */ + struct passwd Pwd; + struct passwd *pPwd = NULL; + rc = getpwuid_r(geteuid(), &Pwd, pchBuf, cbPwdMax, &pPwd); + if (!rc) + { + /* + * Convert the name to UTF-8, assuming that we're getting it in the local codeset. + */ + /** @todo This isn't exactly optimal... the current codeset/page conversion + * stuff never was. Should optimize that for UTF-8 and ASCII one day. + * And also optimize for avoiding heap. */ + char *pszTmp = NULL; + rc = RTStrCurrentCPToUtf8(&pszTmp, pPwd->pw_name); + if (RT_SUCCESS(rc)) + { + size_t cbTmp = strlen(pszTmp) + 1; + if (pcbUser) + *pcbUser = cbTmp; + if (cbTmp <= cbUser) + { + memcpy(pszUser, pszTmp, cbTmp); + rc = VINF_SUCCESS; + } + else + rc = VERR_BUFFER_OVERFLOW; + RTStrFree(pszTmp); + } + } + else + rc = RTErrConvertFromErrno(rc); + RTMemFree(pchBuf); + } + else + rc = VERR_NO_TMP_MEMORY; + } + else + rc = VERR_NOT_SUPPORTED; + return rc; +} + + +RTR3DECL(int) RTProcQueryUsernameA(RTPROCESS hProcess, char **ppszUser) +{ + AssertPtrReturn(ppszUser, VERR_INVALID_POINTER); + + int rc; + if ( hProcess == NIL_RTPROCESS + || hProcess == RTProcSelf()) + { + /* + * Figure a good buffer estimate. + */ + int32_t cbPwdMax = sysconf(_SC_GETPW_R_SIZE_MAX); + if (cbPwdMax <= _1K) + cbPwdMax = _1K; + else + AssertStmt(cbPwdMax <= 32*_1M, cbPwdMax = 32*_1M); + char *pchBuf = (char *)RTMemTmpAllocZ(cbPwdMax); + if (pchBuf) + { + /* + * Get the password file entry. + */ + struct passwd Pwd; + struct passwd *pPwd = NULL; + rc = getpwuid_r(geteuid(), &Pwd, pchBuf, cbPwdMax, &pPwd); + if (!rc) + { + /* + * Convert the name to UTF-8, assuming that we're getting it in the local codeset. + */ + rc = RTStrCurrentCPToUtf8(ppszUser, pPwd->pw_name); + } + else + rc = RTErrConvertFromErrno(rc); + RTMemFree(pchBuf); + } + else + rc = VERR_NO_TMP_MEMORY; + } + else + rc = VERR_NOT_SUPPORTED; + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/rand-posix.cpp b/src/VBox/Runtime/r3/posix/rand-posix.cpp new file mode 100644 index 00000000..191e3db8 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/rand-posix.cpp @@ -0,0 +1,148 @@ +/* $Id: rand-posix.cpp $ */ +/** @file + * IPRT - Random Numbers and Byte Streams, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#ifdef _MSC_VER +# include <io.h> +# include <stdio.h> +#else +# include <unistd.h> +# include <sys/time.h> +#endif + +#include <iprt/rand.h> +#include <iprt/mem.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include "internal/rand.h" +#include "internal/magics.h" + + + +/** @copydoc RTRANDINT::pfnGetBytes */ +static DECLCALLBACK(void) rtRandAdvPosixGetBytes(PRTRANDINT pThis, uint8_t *pb, size_t cb) +{ + ssize_t cbRead = read(pThis->u.File.hFile, pb, cb); + if ((size_t)cbRead != cb) + { + /* S10 has been observed returning 1040 bytes at the time from /dev/urandom. + Which means we need to do than 256 rounds to reach 668171 bytes if + that's what demanded by the caller (like tstRTMemWipe.cpp). */ + ssize_t cTries = RT_MAX(256, cb / 64); + do + { + if (cbRead > 0) + { + cb -= cbRead; + pb += cbRead; + } + cbRead = read(pThis->u.File.hFile, pb, cb); + } while ( (size_t)cbRead != cb + && cTries-- > 0); + AssertReleaseMsg((size_t)cbRead == cb, ("%zu != %zu, cTries=%zd errno=%d\n", cbRead, cb, cTries, errno)); + } +} + + +/** @copydoc RTRANDINT::pfnDestroy */ +static DECLCALLBACK(int) rtRandAdvPosixDestroy(PRTRANDINT pThis) +{ + pThis->u32Magic = ~RTRANDINT_MAGIC; + int fd = pThis->u.File.hFile; + pThis->u.File.hFile = -1; + RTMemFree(pThis); + close(fd); + return VINF_SUCCESS; +} + + +static int rtRandAdvPosixCreateSystem(PRTRAND phRand, const char *pszDev) RT_NO_THROW_DEF +{ + /* + * Try open it first and then setup the handle structure. + */ + int fd = open(pszDev, O_RDONLY); + if (fd < 0) + return RTErrConvertFromErrno(errno); + int rc; + if (fcntl(fd, F_SETFD, FD_CLOEXEC) != -1) + { + PRTRANDINT pThis = (PRTRANDINT)RTMemAlloc(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTRANDINT_MAGIC; + pThis->pfnGetBytes = rtRandAdvPosixGetBytes; + pThis->pfnGetU32 = rtRandAdvSynthesizeU32FromBytes; + pThis->pfnGetU64 = rtRandAdvSynthesizeU64FromBytes; + pThis->pfnSeed = rtRandAdvStubSeed; + pThis->pfnSaveState = rtRandAdvStubSaveState; + pThis->pfnRestoreState = rtRandAdvStubRestoreState; + pThis->pfnDestroy = rtRandAdvPosixDestroy; + pThis->u.File.hFile = fd; + + *phRand = pThis; + return VINF_SUCCESS; + } + + /* bail out */ + rc = VERR_NO_MEMORY; + } + else + rc = RTErrConvertFromErrno(errno); + close(fd); + return rc; +} + + +RTDECL(int) RTRandAdvCreateSystemFaster(PRTRAND phRand) RT_NO_THROW_DEF +{ + return rtRandAdvPosixCreateSystem(phRand, "/dev/urandom"); +} + + +RTDECL(int) RTRandAdvCreateSystemTruer(PRTRAND phRand) RT_NO_THROW_DEF +{ + return rtRandAdvPosixCreateSystem(phRand, "/dev/random"); +} + diff --git a/src/VBox/Runtime/r3/posix/rtmempage-exec-mmap-heap-posix.cpp b/src/VBox/Runtime/r3/posix/rtmempage-exec-mmap-heap-posix.cpp new file mode 100644 index 00000000..2bb60178 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/rtmempage-exec-mmap-heap-posix.cpp @@ -0,0 +1,797 @@ +/* $Id: rtmempage-exec-mmap-heap-posix.cpp $ */ +/** @file + * IPRT - RTMemPage*, POSIX with heap. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/mem.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/critsect.h> +#include <iprt/errcore.h> +#include <iprt/once.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include "internal/mem.h" +#include "../alloc-ef.h" + +#include <stdlib.h> +#include <errno.h> +#include <sys/mman.h> +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Threshold at which to we switch to simply calling mmap. */ +#define RTMEMPAGEPOSIX_MMAP_THRESHOLD _128K +/** The size of a heap block (power of two) - in bytes. */ +#define RTMEMPAGEPOSIX_BLOCK_SIZE _2M +AssertCompile(RTMEMPAGEPOSIX_BLOCK_SIZE == (RTMEMPAGEPOSIX_BLOCK_SIZE / PAGE_SIZE) * PAGE_SIZE); +/** The number of pages per heap block. */ +#define RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT (RTMEMPAGEPOSIX_BLOCK_SIZE / PAGE_SIZE) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a page heap block. */ +typedef struct RTHEAPPAGEBLOCK *PRTHEAPPAGEBLOCK; + +/** + * A simple page heap. + */ +typedef struct RTHEAPPAGE +{ + /** Magic number (RTHEAPPAGE_MAGIC). */ + uint32_t u32Magic; + /** The number of pages in the heap (in BlockTree). */ + uint32_t cHeapPages; + /** The number of currently free pages. */ + uint32_t cFreePages; + /** Number of successful calls. */ + uint32_t cAllocCalls; + /** Number of successful free calls. */ + uint32_t cFreeCalls; + /** The free call number at which we last tried to minimize the heap. */ + uint32_t uLastMinimizeCall; + /** Tree of heap blocks. */ + AVLRPVTREE BlockTree; + /** Allocation hint no 1 (last freed). */ + PRTHEAPPAGEBLOCK pHint1; + /** Allocation hint no 2 (last alloc). */ + PRTHEAPPAGEBLOCK pHint2; + /** Critical section protecting the heap. */ + RTCRITSECT CritSect; + /** Set if the memory must allocated with execute access. */ + bool fExec; +} RTHEAPPAGE; +#define RTHEAPPAGE_MAGIC UINT32_C(0xfeedface) +/** Pointer to a page heap. */ +typedef RTHEAPPAGE *PRTHEAPPAGE; + + +/** + * Describes a page heap block. + */ +typedef struct RTHEAPPAGEBLOCK +{ + /** The AVL tree node core (void pointer range). */ + AVLRPVNODECORE Core; + /** Allocation bitmap. Set bits marks allocated pages. */ + uint32_t bmAlloc[RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT / 32]; + /** Allocation boundrary bitmap. Set bits marks the start of + * allocations. */ + uint32_t bmFirst[RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT / 32]; + /** The number of free pages. */ + uint32_t cFreePages; + /** Pointer back to the heap. */ + PRTHEAPPAGE pHeap; +} RTHEAPPAGEBLOCK; + + +/** + * Argument package for rtHeapPageAllocCallback. + */ +typedef struct RTHEAPPAGEALLOCARGS +{ + /** The number of pages to allocate. */ + size_t cPages; + /** Non-null on success. */ + void *pvAlloc; + /** RTMEMPAGEALLOC_F_XXX. */ + uint32_t fFlags; +} RTHEAPPAGEALLOCARGS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Initialize once structure. */ +static RTONCE g_MemPagePosixInitOnce = RTONCE_INITIALIZER; +/** The page heap. */ +static RTHEAPPAGE g_MemPagePosixHeap; +/** The exec page heap. */ +static RTHEAPPAGE g_MemExecPosixHeap; + + +#ifdef RT_OS_OS2 +/* + * A quick mmap/munmap mockup for avoid duplicating lots of good code. + */ +# define INCL_BASE +# include <os2.h> +# undef MAP_PRIVATE +# define MAP_PRIVATE 0 +# undef MAP_ANONYMOUS +# define MAP_ANONYMOUS 0 +# undef MAP_FAILED +# define MAP_FAILED (void *)-1 +# undef mmap +# define mmap iprt_mmap +# undef munmap +# define munmap iprt_munmap + +static void *mmap(void *pvWhere, size_t cb, int fProt, int fFlags, int fd, off_t off) +{ + NOREF(pvWhere); NOREF(fd); NOREF(off); + void *pv = NULL; + ULONG fAlloc = OBJ_ANY | PAG_COMMIT; + if (fProt & PROT_EXEC) + fAlloc |= PAG_EXECUTE; + if (fProt & PROT_READ) + fAlloc |= PAG_READ; + if (fProt & PROT_WRITE) + fAlloc |= PAG_WRITE; + APIRET rc = DosAllocMem(&pv, cb, fAlloc); + if (rc == NO_ERROR) + return pv; + errno = ENOMEM; + return MAP_FAILED; +} + +static int munmap(void *pv, size_t cb) +{ + APIRET rc = DosFreeMem(pv); + if (rc == NO_ERROR) + return 0; + errno = EINVAL; + return -1; +} + +#endif + +/** + * Initializes the heap. + * + * @returns IPRT status code. + * @param pHeap The page heap to initialize. + * @param fExec Whether the heap memory should be marked as + * executable or not. + */ +int RTHeapPageInit(PRTHEAPPAGE pHeap, bool fExec) +{ + int rc = RTCritSectInitEx(&pHeap->CritSect, + RTCRITSECT_FLAGS_NO_LOCK_VAL | RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_BOOTSTRAP_HACK, + NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL); + if (RT_SUCCESS(rc)) + { + pHeap->cHeapPages = 0; + pHeap->cFreePages = 0; + pHeap->cAllocCalls = 0; + pHeap->cFreeCalls = 0; + pHeap->uLastMinimizeCall = 0; + pHeap->BlockTree = NULL; + pHeap->fExec = fExec; + pHeap->u32Magic = RTHEAPPAGE_MAGIC; + } + return rc; +} + + +/** + * Deletes the heap and all the memory it tracks. + * + * @returns IPRT status code. + * @param pHeap The page heap to delete. + */ +int RTHeapPageDelete(PRTHEAPPAGE pHeap) +{ + NOREF(pHeap); + return VERR_NOT_IMPLEMENTED; +} + + +/** + * Applies flags to an allocation. + * + * @param pv The allocation. + * @param cb The size of the allocation (page aligned). + * @param fFlags RTMEMPAGEALLOC_F_XXX. + */ +DECLINLINE(void) rtMemPagePosixApplyFlags(void *pv, size_t cb, uint32_t fFlags) +{ +#ifndef RT_OS_OS2 + if (fFlags & RTMEMPAGEALLOC_F_ADVISE_LOCKED) + { + int rc = mlock(pv, cb); +# ifndef RT_OS_SOLARIS /* mlock(3C) on Solaris requires the priv_lock_memory privilege */ + AssertMsg(rc == 0, ("mlock %p LB %#zx -> %d errno=%d\n", pv, cb, rc, errno)); +# endif + NOREF(rc); + } + +# ifdef MADV_DONTDUMP + if (fFlags & RTMEMPAGEALLOC_F_ADVISE_NO_DUMP) + { + int rc = madvise(pv, cb, MADV_DONTDUMP); + AssertMsg(rc == 0, ("madvice %p LB %#zx MADV_DONTDUMP -> %d errno=%d\n", pv, cb, rc, errno)); + NOREF(rc); + } +# endif +#endif + + if (fFlags & RTMEMPAGEALLOC_F_ZERO) + RT_BZERO(pv, cb); +} + + +/** + * Avoids some gotos in rtHeapPageAllocFromBlock. + * + * @returns VINF_SUCCESS. + * @param pBlock The block. + * @param iPage The page to start allocating at. + * @param cPages The number of pages. + * @param fFlags RTMEMPAGEALLOC_F_XXX. + * @param ppv Where to return the allocation address. + */ +DECLINLINE(int) rtHeapPageAllocFromBlockSuccess(PRTHEAPPAGEBLOCK pBlock, uint32_t iPage, size_t cPages, uint32_t fFlags, void **ppv) +{ + PRTHEAPPAGE pHeap = pBlock->pHeap; + + ASMBitSet(&pBlock->bmFirst[0], iPage); + pBlock->cFreePages -= cPages; + pHeap->cFreePages -= cPages; + if (!pHeap->pHint2 || pHeap->pHint2->cFreePages < pBlock->cFreePages) + pHeap->pHint2 = pBlock; + pHeap->cAllocCalls++; + + void *pv = (uint8_t *)pBlock->Core.Key + (iPage << PAGE_SHIFT); + *ppv = pv; + + if (fFlags) + rtMemPagePosixApplyFlags(pv, cPages << PAGE_SHIFT, fFlags); + + return VINF_SUCCESS; +} + + +/** + * Checks if a page range is free in the specified block. + * + * @returns @c true if the range is free, @c false if not. + * @param pBlock The block. + * @param iFirst The first page to check. + * @param cPages The number of pages to check. + */ +DECLINLINE(bool) rtHeapPageIsPageRangeFree(PRTHEAPPAGEBLOCK pBlock, uint32_t iFirst, uint32_t cPages) +{ + uint32_t i = iFirst + cPages; + while (i-- > iFirst) + { + if (ASMBitTest(&pBlock->bmAlloc[0], i)) + return false; + Assert(!ASMBitTest(&pBlock->bmFirst[0], i)); + } + return true; +} + + +/** + * Tries to allocate a chunk of pages from a heap block. + * + * @retval VINF_SUCCESS on success. + * @retval VERR_NO_MEMORY if the allocation failed. + * @param pBlock The block to allocate from. + * @param cPages The size of the allocation. + * @param fFlags RTMEMPAGEALLOC_F_XXX. + * @param ppv Where to return the allocation address on success. + */ +DECLINLINE(int) rtHeapPageAllocFromBlock(PRTHEAPPAGEBLOCK pBlock, size_t cPages, uint32_t fFlags, void **ppv) +{ + if (pBlock->cFreePages >= cPages) + { + int iPage = ASMBitFirstClear(&pBlock->bmAlloc[0], RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT); + Assert(iPage >= 0); + + /* special case: single page. */ + if (cPages == 1) + { + ASMBitSet(&pBlock->bmAlloc[0], iPage); + return rtHeapPageAllocFromBlockSuccess(pBlock, iPage, cPages, fFlags, ppv); + } + + while ( iPage >= 0 + && (unsigned)iPage <= RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT - cPages) + { + if (rtHeapPageIsPageRangeFree(pBlock, iPage + 1, cPages - 1)) + { + ASMBitSetRange(&pBlock->bmAlloc[0], iPage, iPage + cPages); + return rtHeapPageAllocFromBlockSuccess(pBlock, iPage, cPages, fFlags, ppv); + } + + /* next */ + iPage = ASMBitNextSet(&pBlock->bmAlloc[0], RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT, iPage); + if (iPage < 0 || iPage >= RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT - 1) + break; + iPage = ASMBitNextClear(&pBlock->bmAlloc[0], RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT, iPage); + } + } + + return VERR_NO_MEMORY; +} + + +/** + * RTAvlrPVDoWithAll callback. + * + * @returns 0 to continue the enum, non-zero to quit it. + * @param pNode The node. + * @param pvUser The user argument. + */ +static DECLCALLBACK(int) rtHeapPageAllocCallback(PAVLRPVNODECORE pNode, void *pvUser) +{ + PRTHEAPPAGEBLOCK pBlock = RT_FROM_MEMBER(pNode, RTHEAPPAGEBLOCK, Core); + RTHEAPPAGEALLOCARGS *pArgs = (RTHEAPPAGEALLOCARGS *)pvUser; + int rc = rtHeapPageAllocFromBlock(pBlock, pArgs->cPages, pArgs->fFlags, &pArgs->pvAlloc); + return RT_SUCCESS(rc) ? 1 : 0; +} + + +/** + * Worker for RTHeapPageAlloc. + * + * @returns IPRT status code + * @param pHeap The heap - locked. + * @param cPages The page count. + * @param pszTag The tag. + * @param fFlags RTMEMPAGEALLOC_F_XXX. + * @param ppv Where to return the address of the allocation + * on success. + */ +static int rtHeapPageAllocLocked(PRTHEAPPAGE pHeap, size_t cPages, const char *pszTag, uint32_t fFlags, void **ppv) +{ + int rc; + NOREF(pszTag); + + /* + * Use the hints first. + */ + if (pHeap->pHint1) + { + rc = rtHeapPageAllocFromBlock(pHeap->pHint1, cPages, fFlags, ppv); + if (rc != VERR_NO_MEMORY) + return rc; + } + if (pHeap->pHint2) + { + rc = rtHeapPageAllocFromBlock(pHeap->pHint2, cPages, fFlags, ppv); + if (rc != VERR_NO_MEMORY) + return rc; + } + + /* + * Search the heap for a block with enough free space. + * + * N.B. This search algorithm is not optimal at all. What (hopefully) saves + * it are the two hints above. + */ + if (pHeap->cFreePages >= cPages) + { + RTHEAPPAGEALLOCARGS Args; + Args.cPages = cPages; + Args.pvAlloc = NULL; + Args.fFlags = fFlags; + RTAvlrPVDoWithAll(&pHeap->BlockTree, true /*fFromLeft*/, rtHeapPageAllocCallback, &Args); + if (Args.pvAlloc) + { + *ppv = Args.pvAlloc; + return VINF_SUCCESS; + } + } + + /* + * Didn't find anytyhing, so expand the heap with a new block. + */ + RTCritSectLeave(&pHeap->CritSect); + void *pvPages; + pvPages = mmap(NULL, RTMEMPAGEPOSIX_BLOCK_SIZE, + PROT_READ | PROT_WRITE | (pHeap->fExec ? PROT_EXEC : 0), + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (pvPages == MAP_FAILED) + { + RTCritSectEnter(&pHeap->CritSect); + return RTErrConvertFromErrno(errno); + + } + /** @todo Eliminate this rtMemBaseAlloc dependency! */ + PRTHEAPPAGEBLOCK pBlock; +#ifdef RTALLOC_REPLACE_MALLOC + if (g_pfnOrgMalloc) + pBlock = (PRTHEAPPAGEBLOCK)g_pfnOrgMalloc(sizeof(*pBlock)); + else +#endif + pBlock = (PRTHEAPPAGEBLOCK)rtMemBaseAlloc(sizeof(*pBlock)); + if (!pBlock) + { + munmap(pvPages, RTMEMPAGEPOSIX_BLOCK_SIZE); + RTCritSectEnter(&pHeap->CritSect); + return VERR_NO_MEMORY; + } + + RT_ZERO(*pBlock); + pBlock->Core.Key = pvPages; + pBlock->Core.KeyLast = (uint8_t *)pvPages + RTMEMPAGEPOSIX_BLOCK_SIZE - 1; + pBlock->cFreePages = RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT; + pBlock->pHeap = pHeap; + + RTCritSectEnter(&pHeap->CritSect); + + bool fRc = RTAvlrPVInsert(&pHeap->BlockTree, &pBlock->Core); Assert(fRc); NOREF(fRc); + pHeap->cFreePages += RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT; + pHeap->cHeapPages += RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT; + + /* + * Grab memory from the new block (cannot fail). + */ + rc = rtHeapPageAllocFromBlock(pBlock, cPages, fFlags, ppv); + Assert(rc == VINF_SUCCESS); + + return rc; +} + + +/** + * Allocates one or more pages off the heap. + * + * @returns IPRT status code. + * @param pHeap The page heap. + * @param cPages The number of pages to allocate. + * @param pszTag The allocation tag. + * @param fFlags RTMEMPAGEALLOC_F_XXX. + * @param ppv Where to return the pointer to the pages. + */ +int RTHeapPageAlloc(PRTHEAPPAGE pHeap, size_t cPages, const char *pszTag, uint32_t fFlags, void **ppv) +{ + /* + * Validate input. + */ + AssertPtr(ppv); + *ppv = NULL; + AssertPtrReturn(pHeap, VERR_INVALID_HANDLE); + AssertReturn(pHeap->u32Magic == RTHEAPPAGE_MAGIC, VERR_INVALID_HANDLE); + AssertMsgReturn(cPages < RTMEMPAGEPOSIX_BLOCK_SIZE, ("%#zx\n", cPages), VERR_OUT_OF_RANGE); + + /* + * Grab the lock and call a worker with many returns. + */ + int rc = RTCritSectEnter(&pHeap->CritSect); + if (RT_SUCCESS(rc)) + { + rc = rtHeapPageAllocLocked(pHeap, cPages, pszTag, fFlags, ppv); + RTCritSectLeave(&pHeap->CritSect); + } + + return rc; +} + + +/** + * RTAvlrPVDoWithAll callback. + * + * @returns 0 to continue the enum, non-zero to quit it. + * @param pNode The node. + * @param pvUser Pointer to a block pointer variable. For returning + * the address of the block to be freed. + */ +static DECLCALLBACK(int) rtHeapPageFindUnusedBlockCallback(PAVLRPVNODECORE pNode, void *pvUser) +{ + PRTHEAPPAGEBLOCK pBlock = RT_FROM_MEMBER(pNode, RTHEAPPAGEBLOCK, Core); + if (pBlock->cFreePages == RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT) + { + *(PRTHEAPPAGEBLOCK *)pvUser = pBlock; + return 1; + } + return 0; +} + + +/** + * Allocates one or more pages off the heap. + * + * @returns IPRT status code. + * @param pHeap The page heap. + * @param pv Pointer to what RTHeapPageAlloc returned. + * @param cPages The number of pages that was allocated. + */ +int RTHeapPageFree(PRTHEAPPAGE pHeap, void *pv, size_t cPages) +{ + /* + * Validate input. + */ + if (!pv) + return VINF_SUCCESS; + AssertPtrReturn(pHeap, VERR_INVALID_HANDLE); + AssertReturn(pHeap->u32Magic == RTHEAPPAGE_MAGIC, VERR_INVALID_HANDLE); + + /* + * Grab the lock and look up the page. + */ + int rc = RTCritSectEnter(&pHeap->CritSect); + if (RT_SUCCESS(rc)) + { + PRTHEAPPAGEBLOCK pBlock = (PRTHEAPPAGEBLOCK)RTAvlrPVRangeGet(&pHeap->BlockTree, pv); + if (pBlock) + { + /* + * Validate the specified address range. + */ + uint32_t const iPage = (uint32_t)(((uintptr_t)pv - (uintptr_t)pBlock->Core.Key) >> PAGE_SHIFT); + /* Check the range is within the block. */ + bool fOk = iPage + cPages <= RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT; + /* Check that it's the start of an allocation. */ + fOk = fOk && ASMBitTest(&pBlock->bmFirst[0], iPage); + /* Check that the range ends at an allocation boundrary. */ + fOk = fOk && ( iPage + cPages == RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT + || ASMBitTest(&pBlock->bmFirst[0], iPage + cPages) + || !ASMBitTest(&pBlock->bmAlloc[0], iPage + cPages)); + /* Check the other pages. */ + uint32_t const iLastPage = iPage + cPages - 1; + for (uint32_t i = iPage + 1; i < iLastPage && fOk; i++) + fOk = ASMBitTest(&pBlock->bmAlloc[0], i) + && !ASMBitTest(&pBlock->bmFirst[0], i); + if (fOk) + { + /* + * Free the memory. + */ + ASMBitClearRange(&pBlock->bmAlloc[0], iPage, iPage + cPages); + ASMBitClear(&pBlock->bmFirst[0], iPage); + pBlock->cFreePages += cPages; + pHeap->cFreePages += cPages; + pHeap->cFreeCalls++; + if (!pHeap->pHint1 || pHeap->pHint1->cFreePages < pBlock->cFreePages) + pHeap->pHint1 = pBlock; + + /** @todo Add bitmaps for tracking madvice and mlock so we can undo those. */ + + /* + * Shrink the heap. Not very efficient because of the AVL tree. + */ + if ( pHeap->cFreePages >= RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT * 3 + && pHeap->cFreePages >= pHeap->cHeapPages / 2 /* 50% free */ + && pHeap->cFreeCalls - pHeap->uLastMinimizeCall > RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT + ) + { + uint32_t cFreePageTarget = pHeap->cHeapPages / 4; /* 25% free */ + while (pHeap->cFreePages > cFreePageTarget) + { + pHeap->uLastMinimizeCall = pHeap->cFreeCalls; + + pBlock = NULL; + RTAvlrPVDoWithAll(&pHeap->BlockTree, false /*fFromLeft*/, + rtHeapPageFindUnusedBlockCallback, &pBlock); + if (!pBlock) + break; + + void *pv2 = RTAvlrPVRemove(&pHeap->BlockTree, pBlock->Core.Key); Assert(pv2); NOREF(pv2); + pHeap->cHeapPages -= RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT; + pHeap->cFreePages -= RTMEMPAGEPOSIX_BLOCK_PAGE_COUNT; + pHeap->pHint1 = NULL; + pHeap->pHint2 = NULL; + RTCritSectLeave(&pHeap->CritSect); + + munmap(pBlock->Core.Key, RTMEMPAGEPOSIX_BLOCK_SIZE); + pBlock->Core.Key = pBlock->Core.KeyLast = NULL; + pBlock->cFreePages = 0; +#ifdef RTALLOC_REPLACE_MALLOC + if (g_pfnOrgFree) + g_pfnOrgFree(pBlock); + else +#endif + rtMemBaseFree(pBlock); + + RTCritSectEnter(&pHeap->CritSect); + } + } + } + else + rc = VERR_INVALID_POINTER; + } + else + rc = VERR_INVALID_POINTER; + + RTCritSectLeave(&pHeap->CritSect); + } + + return rc; +} + + +/** + * Initializes the heap. + * + * @returns IPRT status code + * @param pvUser Unused. + */ +static DECLCALLBACK(int) rtMemPagePosixInitOnce(void *pvUser) +{ + NOREF(pvUser); + int rc = RTHeapPageInit(&g_MemPagePosixHeap, false /*fExec*/); + if (RT_SUCCESS(rc)) + { + rc = RTHeapPageInit(&g_MemExecPosixHeap, true /*fExec*/); + if (RT_SUCCESS(rc)) + return rc; + RTHeapPageDelete(&g_MemPagePosixHeap); + } + return rc; +} + + +/** + * Allocates memory from the specified heap. + * + * @returns Address of the allocated memory. + * @param cb The number of bytes to allocate. + * @param pszTag The tag. + * @param fFlags RTMEMPAGEALLOC_F_XXX. + * @param pHeap The heap to use. + */ +static void *rtMemPagePosixAlloc(size_t cb, const char *pszTag, uint32_t fFlags, PRTHEAPPAGE pHeap) +{ + /* + * Validate & adjust the input. + */ + Assert(cb > 0); + NOREF(pszTag); + cb = RT_ALIGN_Z(cb, PAGE_SIZE); + + /* + * If the allocation is relatively large, we use mmap/munmap directly. + */ + void *pv; + if (cb >= RTMEMPAGEPOSIX_MMAP_THRESHOLD) + { + + pv = mmap(NULL, cb, + PROT_READ | PROT_WRITE | (pHeap == &g_MemExecPosixHeap ? PROT_EXEC : 0), + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (pv != MAP_FAILED) + { + AssertPtr(pv); + + if (fFlags) + rtMemPagePosixApplyFlags(pv, cb, fFlags); + } + else + pv = NULL; + } + else + { + int rc = RTOnce(&g_MemPagePosixInitOnce, rtMemPagePosixInitOnce, NULL); + if (RT_SUCCESS(rc)) + rc = RTHeapPageAlloc(pHeap, cb >> PAGE_SHIFT, pszTag, fFlags, &pv); + if (RT_FAILURE(rc)) + pv = NULL; + } + + return pv; +} + + +/** + * Free memory allocated by rtMemPagePosixAlloc. + * + * @param pv The address of the memory to free. + * @param cb The size. + * @param pHeap The heap. + */ +static void rtMemPagePosixFree(void *pv, size_t cb, PRTHEAPPAGE pHeap) +{ + /* + * Validate & adjust the input. + */ + if (!pv) + return; + AssertPtr(pv); + Assert(cb > 0); + Assert(!((uintptr_t)pv & PAGE_OFFSET_MASK)); + cb = RT_ALIGN_Z(cb, PAGE_SIZE); + + /* + * If the allocation is relatively large, we use mmap/munmap directly. + */ + if (cb >= RTMEMPAGEPOSIX_MMAP_THRESHOLD) + { + int rc = munmap(pv, cb); + AssertMsg(rc == 0, ("rc=%d pv=%p cb=%#zx\n", rc, pv, cb)); NOREF(rc); + } + else + { + int rc = RTHeapPageFree(pHeap, pv, cb >> PAGE_SHIFT); + AssertRC(rc); + } +} + + + + + +RTDECL(void *) RTMemPageAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtMemPagePosixAlloc(cb, pszTag, 0, &g_MemPagePosixHeap); +} + + +RTDECL(void *) RTMemPageAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtMemPagePosixAlloc(cb, pszTag, RTMEMPAGEALLOC_F_ZERO, &g_MemPagePosixHeap); +} + + +RTDECL(void *) RTMemPageAllocExTag(size_t cb, uint32_t fFlags, const char *pszTag) RT_NO_THROW_DEF +{ + AssertReturn(!(fFlags & ~RTMEMPAGEALLOC_F_VALID_MASK), NULL); + return rtMemPagePosixAlloc(cb, pszTag, fFlags, &g_MemPagePosixHeap); +} + + +RTDECL(void) RTMemPageFree(void *pv, size_t cb) RT_NO_THROW_DEF +{ + return rtMemPagePosixFree(pv, cb, &g_MemPagePosixHeap); +} + diff --git a/src/VBox/Runtime/r3/posix/rtmempage-exec-mmap-posix.cpp b/src/VBox/Runtime/r3/posix/rtmempage-exec-mmap-posix.cpp new file mode 100644 index 00000000..abe285a2 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/rtmempage-exec-mmap-posix.cpp @@ -0,0 +1,182 @@ +/* $Id: rtmempage-exec-mmap-posix.cpp $ */ +/** @file + * IPRT - RTMemPage*, POSIX with mmap only. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/mem.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/param.h> +#include <iprt/string.h> + +#include <stdlib.h> +#include <errno.h> +#include <sys/mman.h> +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +#endif + + +/** + * Applies flags to an allocation. + * + * @param pv The allocation. + * @param cb The size of the allocation (page aligned). + * @param fFlags RTMEMPAGEALLOC_F_XXX. + */ +DECLINLINE(void) rtMemPagePosixApplyFlags(void *pv, size_t cb, uint32_t fFlags) +{ +#ifndef RT_OS_OS2 + if (fFlags & RTMEMPAGEALLOC_F_ADVISE_LOCKED) + { + int rc = mlock(pv, cb); +# ifndef RT_OS_SOLARIS /* mlock(3C) on Solaris requires the priv_lock_memory privilege */ + AssertMsg(rc == 0, ("mlock %p LB %#zx -> %d errno=%d\n", pv, cb, rc, errno)); +# endif + NOREF(rc); + } + +# ifdef MADV_DONTDUMP + if (fFlags & RTMEMPAGEALLOC_F_ADVISE_NO_DUMP) + { + int rc = madvise(pv, cb, MADV_DONTDUMP); + AssertMsg(rc == 0, ("madvice %p LB %#zx MADV_DONTDUMP -> %d errno=%d\n", pv, cb, rc, errno)); + NOREF(rc); + } +# endif +#endif + + if (fFlags & RTMEMPAGEALLOC_F_ZERO) + RT_BZERO(pv, cb); +} + + +/** + * Allocates memory from the specified heap. + * + * @returns Address of the allocated memory. + * @param cb The number of bytes to allocate. + * @param pszTag The tag. + * @param fFlags RTMEMPAGEALLOC_F_XXX. + * @param fProtExec PROT_EXEC or 0. + */ +static void *rtMemPagePosixAlloc(size_t cb, const char *pszTag, uint32_t fFlags, int fProtExec) +{ + /* + * Validate & adjust the input. + */ + Assert(cb > 0); + NOREF(pszTag); + cb = RT_ALIGN_Z(cb, PAGE_SIZE); + + /* + * Do the allocation. + */ + void *pv = mmap(NULL, cb, + PROT_READ | PROT_WRITE | fProtExec, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pv != MAP_FAILED) + { + AssertPtr(pv); + + if (fFlags) + rtMemPagePosixApplyFlags(pv, cb, fFlags); + } + else + pv = NULL; + + return pv; +} + + +/** + * Free memory allocated by rtMemPagePosixAlloc. + * + * @param pv The address of the memory to free. + * @param cb The size. + */ +static void rtMemPagePosixFree(void *pv, size_t cb) +{ + /* + * Validate & adjust the input. + */ + if (!pv) + return; + AssertPtr(pv); + Assert(cb > 0); + Assert(!((uintptr_t)pv & PAGE_OFFSET_MASK)); + cb = RT_ALIGN_Z(cb, PAGE_SIZE); + + /* + * Free the memory. + */ + int rc = munmap(pv, cb); + AssertMsg(rc == 0, ("rc=%d pv=%p cb=%#zx\n", rc, pv, cb)); NOREF(rc); +} + + + + + +RTDECL(void *) RTMemPageAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtMemPagePosixAlloc(cb, pszTag, 0, 0); +} + + +RTDECL(void *) RTMemPageAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return rtMemPagePosixAlloc(cb, pszTag, RTMEMPAGEALLOC_F_ZERO, 0); +} + + +RTDECL(void *) RTMemPageAllocExTag(size_t cb, uint32_t fFlags, const char *pszTag) RT_NO_THROW_DEF +{ + AssertReturn(!(fFlags & ~RTMEMPAGEALLOC_F_VALID_MASK), NULL); + return rtMemPagePosixAlloc(cb, pszTag, fFlags, 0); +} + + +RTDECL(void) RTMemPageFree(void *pv, size_t cb) RT_NO_THROW_DEF +{ + return rtMemPagePosixFree(pv, cb); +} + diff --git a/src/VBox/Runtime/r3/posix/sched-posix.cpp b/src/VBox/Runtime/r3/posix/sched-posix.cpp new file mode 100644 index 00000000..b30b9c67 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/sched-posix.cpp @@ -0,0 +1,849 @@ +/* $Id: sched-posix.cpp $ */ +/** @file + * IPRT - Scheduling, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * !WARNING! + * + * When talking about lowering and raising priority, we do *NOT* refer to + * the common direction priority values takes on unix systems (lower means + * higher). So, when we raise the priority of a linux thread the nice + * value will decrease, and when we lower the priority the nice value + * will increase. Confusing, right? + * + * !WARNING! + */ + + + +/** @def THREAD_LOGGING + * Be very careful with enabling this, it may cause deadlocks when combined + * with the 'thread' logging prefix. + */ +#ifdef DOXYGEN_RUNNING +#define THREAD_LOGGING +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <errno.h> +#include <pthread.h> +#include <sched.h> +#include <unistd.h> +#include <sys/resource.h> + +#include <iprt/thread.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/log.h> +#include <iprt/err.h> +#include "internal/sched.h" +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Array scheduler attributes corresponding to each of the thread types. */ +typedef struct PROCPRIORITYTYPE +{ + /** For sanity include the array index. */ + RTTHREADTYPE enmType; + /** The thread priority or nice delta - depends on which priority type. */ + int iPriority; +} PROCPRIORITYTYPE; + + +/** + * Configuration of one priority. + */ +typedef struct +{ + /** The priority. */ + RTPROCPRIORITY enmPriority; + /** The name of this priority. */ + const char *pszName; + /** The process nice value. */ + int iNice; + /** The delta applied to the iPriority value. */ + int iDelta; + /** Array scheduler attributes corresponding to each of the thread types. */ + const PROCPRIORITYTYPE *paTypes; +} PROCPRIORITY; + + +/** + * Saved priority settings + */ +typedef struct +{ + /** Process priority. */ + int iPriority; + /** Process level. */ + struct sched_param SchedParam; + /** Process level. */ + int iPolicy; + /** pthread level. */ + struct sched_param PthreadSchedParam; + /** pthread level. */ + int iPthreadPolicy; +} SAVEDPRIORITY, *PSAVEDPRIORITY; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Thread level priorities based on a 0..31 priority range + * as specified as the minimum for SCHED_RR/FIFO. FreeBSD + * seems to be using this (needs more research to be + * certain). + */ +static const PROCPRIORITYTYPE g_aTypesThread[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, -999999999 }, + { RTTHREADTYPE_INFREQUENT_POLLER, 5 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 12 }, + { RTTHREADTYPE_EMULATION, 14 }, + { RTTHREADTYPE_DEFAULT, 15 }, + { RTTHREADTYPE_GUI, 16 }, + { RTTHREADTYPE_MAIN_WORKER, 18 }, + { RTTHREADTYPE_VRDP_IO, 24 }, + { RTTHREADTYPE_DEBUGGER, 28 }, + { RTTHREADTYPE_MSG_PUMP, 29 }, + { RTTHREADTYPE_IO, 30 }, + { RTTHREADTYPE_TIMER, 31 } +}; + +static const PROCPRIORITYTYPE g_aTypesThreadFlat[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, 15 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 15 }, + { RTTHREADTYPE_EMULATION, 15 }, + { RTTHREADTYPE_DEFAULT, 15 }, + { RTTHREADTYPE_GUI, 15 }, + { RTTHREADTYPE_MAIN_WORKER, 15 }, + { RTTHREADTYPE_VRDP_IO, 15 }, + { RTTHREADTYPE_DEBUGGER, 15 }, + { RTTHREADTYPE_MSG_PUMP, 15 }, + { RTTHREADTYPE_IO, 15 }, + { RTTHREADTYPE_TIMER, 15 } +}; + +/** + * Process and thread level priority, full access at thread level. + */ +static const PROCPRIORITY g_aProcessAndThread[] = +{ + { RTPROCPRIORITY_FLAT, "Flat", 0, 0, g_aTypesThreadFlat }, + { RTPROCPRIORITY_LOW, "Low", 9, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 11, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 15, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 17, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 19, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 7, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 5, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 3, 0, g_aTypesThread }, + { RTPROCPRIORITY_LOW, "Low", 1, 0, g_aTypesThread }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesThread }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesThreadFlat }, + { RTPROCPRIORITY_HIGH, "High", -9, 0, g_aTypesThread }, + { RTPROCPRIORITY_HIGH, "High", -7, 0, g_aTypesThread }, + { RTPROCPRIORITY_HIGH, "High", -5, 0, g_aTypesThread }, + { RTPROCPRIORITY_HIGH, "High", -3, 0, g_aTypesThread }, + { RTPROCPRIORITY_HIGH, "High", -1, 0, g_aTypesThread }, + { RTPROCPRIORITY_HIGH, "High", -9, 0, g_aTypesThreadFlat }, + { RTPROCPRIORITY_HIGH, "High", -1, 0, g_aTypesThreadFlat } +}; + +/** + * Deltas for a process in which we are not restricted + * to only be lowering the priority. + */ +static const PROCPRIORITYTYPE g_aTypesUnixFree[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, -999999999 }, + { RTTHREADTYPE_INFREQUENT_POLLER, +3 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, +2 }, + { RTTHREADTYPE_EMULATION, +1 }, + { RTTHREADTYPE_DEFAULT, 0 }, + { RTTHREADTYPE_GUI, 0 }, + { RTTHREADTYPE_MAIN_WORKER, 0 }, + { RTTHREADTYPE_VRDP_IO, -1 }, + { RTTHREADTYPE_DEBUGGER, -1 }, + { RTTHREADTYPE_MSG_PUMP, -2 }, + { RTTHREADTYPE_IO, -3 }, + { RTTHREADTYPE_TIMER, -4 } +}; + +/** + * Deltas for a process in which we are restricted + * to only be lowering the priority. + */ +static const PROCPRIORITYTYPE g_aTypesUnixRestricted[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, -999999999 }, + { RTTHREADTYPE_INFREQUENT_POLLER, +3 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, +2 }, + { RTTHREADTYPE_EMULATION, +1 }, + { RTTHREADTYPE_DEFAULT, 0 }, + { RTTHREADTYPE_GUI, 0 }, + { RTTHREADTYPE_MAIN_WORKER, 0 }, + { RTTHREADTYPE_VRDP_IO, 0 }, + { RTTHREADTYPE_DEBUGGER, 0 }, + { RTTHREADTYPE_MSG_PUMP, 0 }, + { RTTHREADTYPE_IO, 0 }, + { RTTHREADTYPE_TIMER, 0 } +}; + +/** + * Deltas for a process in which we are restricted + * to only be lowering the priority. + */ +static const PROCPRIORITYTYPE g_aTypesUnixFlat[RTTHREADTYPE_END] = +{ + { RTTHREADTYPE_INVALID, -999999999 }, + { RTTHREADTYPE_INFREQUENT_POLLER, 0 }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, 0 }, + { RTTHREADTYPE_EMULATION, 0 }, + { RTTHREADTYPE_DEFAULT, 0 }, + { RTTHREADTYPE_GUI, 0 }, + { RTTHREADTYPE_MAIN_WORKER, 0 }, + { RTTHREADTYPE_VRDP_IO, 0 }, + { RTTHREADTYPE_DEBUGGER, 0 }, + { RTTHREADTYPE_MSG_PUMP, 0 }, + { RTTHREADTYPE_IO, 0 }, + { RTTHREADTYPE_TIMER, 0 } +}; + +/** + * Process and thread level priority, full access at thread level. + */ +static const PROCPRIORITY g_aUnixConfigs[] = +{ + { RTPROCPRIORITY_FLAT, "Flat", 0, 0, g_aTypesUnixFlat }, + { RTPROCPRIORITY_LOW, "Low", 9, 9, g_aTypesUnixFree }, + { RTPROCPRIORITY_LOW, "Low", 9, 9, g_aTypesUnixFlat }, + { RTPROCPRIORITY_LOW, "Low", 15, 15, g_aTypesUnixFree }, + { RTPROCPRIORITY_LOW, "Low", 15, 15, g_aTypesUnixFlat }, + { RTPROCPRIORITY_LOW, "Low", 17, 17, g_aTypesUnixFree }, + { RTPROCPRIORITY_LOW, "Low", 17, 17, g_aTypesUnixFlat }, + { RTPROCPRIORITY_LOW, "Low", 19, 19, g_aTypesUnixFlat }, + { RTPROCPRIORITY_LOW, "Low", 9, 9, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_LOW, "Low", 15, 15, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_LOW, "Low", 17, 17, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesUnixFree }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_NORMAL, "Normal", 0, 0, g_aTypesUnixFlat }, + { RTPROCPRIORITY_HIGH, "High", -9, -9, g_aTypesUnixFree }, + { RTPROCPRIORITY_HIGH, "High", -7, -7, g_aTypesUnixFree }, + { RTPROCPRIORITY_HIGH, "High", -5, -5, g_aTypesUnixFree }, + { RTPROCPRIORITY_HIGH, "High", -3, -3, g_aTypesUnixFree }, + { RTPROCPRIORITY_HIGH, "High", -1, -1, g_aTypesUnixFree }, + { RTPROCPRIORITY_HIGH, "High", -9, -9, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_HIGH, "High", -7, -7, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_HIGH, "High", -5, -5, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_HIGH, "High", -3, -3, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_HIGH, "High", -1, -1, g_aTypesUnixRestricted }, + { RTPROCPRIORITY_HIGH, "High", -9, -9, g_aTypesUnixFlat }, + { RTPROCPRIORITY_HIGH, "High", -7, -7, g_aTypesUnixFlat }, + { RTPROCPRIORITY_HIGH, "High", -5, -5, g_aTypesUnixFlat }, + { RTPROCPRIORITY_HIGH, "High", -3, -3, g_aTypesUnixFlat }, + { RTPROCPRIORITY_HIGH, "High", -1, -1, g_aTypesUnixFlat } +}; + +/** + * The dynamic default priority configuration. + * + * This will be recalulated at runtime depending on what the + * system allow us to do and what the current priority is. + */ +static PROCPRIORITY g_aDefaultPriority = +{ + RTPROCPRIORITY_LOW, "Default", 0, 0, g_aTypesUnixRestricted +}; + +/** Pointer to the current priority configuration. */ +static const PROCPRIORITY *g_pProcessPriority = &g_aDefaultPriority; + + +/** Set to what kind of scheduling priority support the host + * OS seems to be offering. Determined at runtime. + */ +static enum +{ + OSPRIOSUP_UNDETERMINED = 0, + /** An excellent combination of process and thread level + * I.e. setpriority() works on process level, one have to be supervisor + * to raise priority as is the custom in unix. While pthread_setschedparam() + * works on thread level and we can raise the priority just like we want. + * + * I think this is what FreeBSD offers. (It is certainly analogous to what + * NT offers if you wondered.) Linux on the other hand doesn't provide this + * for processes with SCHED_OTHER policy, and I'm not sure if we want to + * play around with using the real-time SCHED_RR and SCHED_FIFO which would + * require special privileges anyway. + */ + OSPRIOSUP_PROCESS_AND_THREAD_LEVEL, + /** A rough thread level priority only. + * setpriority() is the only real game in town, and it works on thread level. + */ + OSPRIOSUP_THREAD_LEVEL +} volatile g_enmOsPrioSup = OSPRIOSUP_UNDETERMINED; + +/** Set if we figure we have nice capability, meaning we can use setpriority + * to raise the priority. */ +static bool g_fCanNice = false; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Saves all the scheduling attributes we can think of. + */ +static void rtSchedNativeSave(PSAVEDPRIORITY pSave) +{ + memset(pSave, 0xff, sizeof(*pSave)); + + errno = 0; + pSave->iPriority = getpriority(PRIO_PROCESS, 0 /* current process */); + Assert(errno == 0); + + errno = 0; + sched_getparam(0 /* current process */, &pSave->SchedParam); + Assert(errno == 0); + + errno = 0; + pSave->iPolicy = sched_getscheduler(0 /* current process */); + Assert(errno == 0); + + int rc = pthread_getschedparam(pthread_self(), &pSave->iPthreadPolicy, &pSave->PthreadSchedParam); + Assert(rc == 0); NOREF(rc); +} + + +/** + * Restores scheduling attributes. + * Most of this won't work right, but anyway... + */ +static void rtSchedNativeRestore(PSAVEDPRIORITY pSave) +{ + setpriority(PRIO_PROCESS, 0, pSave->iPriority); + sched_setscheduler(0, pSave->iPolicy, &pSave->SchedParam); + sched_setparam(0, &pSave->SchedParam); + pthread_setschedparam(pthread_self(), pSave->iPthreadPolicy, &pSave->PthreadSchedParam); +} + + +/** + * Starts a worker thread and wait for it to complete. + * We cannot use RTThreadCreate since we're already owner of the RW lock. + */ +static int rtSchedCreateThread(void *(*pfnThread)(void *pvArg), void *pvArg) +{ + /* + * Setup thread attributes. + */ + pthread_attr_t ThreadAttr; + int rc = pthread_attr_init(&ThreadAttr); + if (!rc) + { + rc = pthread_attr_setdetachstate(&ThreadAttr, PTHREAD_CREATE_JOINABLE); + if (!rc) + { + rc = pthread_attr_setstacksize(&ThreadAttr, 128*1024); + if (!rc) + { + /* + * Create the thread. + */ + pthread_t Thread; + rc = pthread_create(&Thread, &ThreadAttr, pfnThread, pvArg); + if (!rc) + { + pthread_attr_destroy(&ThreadAttr); + /* + * Wait for the thread to finish. + */ + void *pvRet = (void *)-1; + do + { + rc = pthread_join(Thread, &pvRet); + } while (rc == EINTR); + if (rc) + return RTErrConvertFromErrno(rc); + return (int)(uintptr_t)pvRet; + } + } + } + pthread_attr_destroy(&ThreadAttr); + } + return RTErrConvertFromErrno(rc); +} + + +static void rtSchedDumpPriority(void) +{ +#ifdef THREAD_LOGGING + Log(("Priority: g_fCanNice=%d g_enmOsPrioSup=%d\n", g_fCanNice, g_enmOsPrioSup)); + Log(("Priority: enmPriority=%d \"%s\" iNice=%d iDelta=%d\n", + g_pProcessPriority->enmPriority, + g_pProcessPriority->pszName, + g_pProcessPriority->iNice, + g_pProcessPriority->iDelta)); + Log(("Priority: %2d INFREQUENT_POLLER = %d\n", RTTHREADTYPE_INFREQUENT_POLLER, g_pProcessPriority->paTypes[RTTHREADTYPE_INFREQUENT_POLLER].iPriority)); + Log(("Priority: %2d MAIN_HEAVY_WORKER = %d\n", RTTHREADTYPE_MAIN_HEAVY_WORKER, g_pProcessPriority->paTypes[RTTHREADTYPE_MAIN_HEAVY_WORKER].iPriority)); + Log(("Priority: %2d EMULATION = %d\n", RTTHREADTYPE_EMULATION , g_pProcessPriority->paTypes[RTTHREADTYPE_EMULATION ].iPriority)); + Log(("Priority: %2d DEFAULT = %d\n", RTTHREADTYPE_DEFAULT , g_pProcessPriority->paTypes[RTTHREADTYPE_DEFAULT ].iPriority)); + Log(("Priority: %2d GUI = %d\n", RTTHREADTYPE_GUI , g_pProcessPriority->paTypes[RTTHREADTYPE_GUI ].iPriority)); + Log(("Priority: %2d MAIN_WORKER = %d\n", RTTHREADTYPE_MAIN_WORKER , g_pProcessPriority->paTypes[RTTHREADTYPE_MAIN_WORKER ].iPriority)); + Log(("Priority: %2d VRDP_IO = %d\n", RTTHREADTYPE_VRDP_IO , g_pProcessPriority->paTypes[RTTHREADTYPE_VRDP_IO ].iPriority)); + Log(("Priority: %2d DEBUGGER = %d\n", RTTHREADTYPE_DEBUGGER , g_pProcessPriority->paTypes[RTTHREADTYPE_DEBUGGER ].iPriority)); + Log(("Priority: %2d MSG_PUMP = %d\n", RTTHREADTYPE_MSG_PUMP , g_pProcessPriority->paTypes[RTTHREADTYPE_MSG_PUMP ].iPriority)); + Log(("Priority: %2d IO = %d\n", RTTHREADTYPE_IO , g_pProcessPriority->paTypes[RTTHREADTYPE_IO ].iPriority)); + Log(("Priority: %2d TIMER = %d\n", RTTHREADTYPE_TIMER , g_pProcessPriority->paTypes[RTTHREADTYPE_TIMER ].iPriority)); +#endif +} + + +/** + * The prober thread. + * We don't want to mess with the priority of the calling thread. + * + * @remark This is pretty presumptive stuff, but if it works on Linux and + * FreeBSD it does what I want. + */ +static void *rtSchedNativeProberThread(void *pvUser) +{ + SAVEDPRIORITY SavedPriority; + rtSchedNativeSave(&SavedPriority); + + /* + * Let's first try and see what we get on a thread level. + */ + int iMax = sched_get_priority_max(SavedPriority.iPthreadPolicy); + int iMin = sched_get_priority_min(SavedPriority.iPthreadPolicy); + if (iMax - iMin >= 32) + { + pthread_t Self = pthread_self(); + int i = iMin; + while (i <= iMax) + { + struct sched_param SchedParam = SavedPriority.PthreadSchedParam; + SchedParam.sched_priority = i; + if (pthread_setschedparam(Self, SavedPriority.iPthreadPolicy, &SchedParam)) + break; + i++; + } + if (i == iMax) + g_enmOsPrioSup = OSPRIOSUP_PROCESS_AND_THREAD_LEVEL; + } + + /* + * Ok, we didn't have the good stuff, so let's fall back on the unix stuff. + */ + if (g_enmOsPrioSup == OSPRIOSUP_UNDETERMINED) + g_enmOsPrioSup = OSPRIOSUP_THREAD_LEVEL; + + /* + * Check if we can get higher priority (typically only root can do this). + * (Won't work right if our priority is -19 to start with, but what the heck.) + * + * We assume that the unix priority is -19 to 19. I know there are defines + * for this, but I don't remember which and if I'm awake enough to make sense + * of them from any SuS spec. + */ + int iStart = getpriority(PRIO_PROCESS, 0); + int i = iStart; + while (i-- > -19) + { + if (setpriority(PRIO_PROCESS, 0, i)) + break; + } + if (getpriority(PRIO_PROCESS, 0) != iStart) + g_fCanNice = true; + else + g_fCanNice = false; + + /* done */ + rtSchedNativeRestore(&SavedPriority); + RT_NOREF(pvUser); + return (void *)VINF_SUCCESS; +} + + +/** + * Calculate the scheduling properties for all the threads in the default + * process priority, assuming the current thread have the type enmType. + * + * @returns iprt status code. + * @param enmType The thread type to be assumed for the current thread. + */ +DECLHIDDEN(int) rtSchedNativeCalcDefaultPriority(RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + + /* + * First figure out what's supported by the OS. + */ + if (g_enmOsPrioSup == OSPRIOSUP_UNDETERMINED) + { + int iPriority = getpriority(PRIO_PROCESS, 0); + int rc = rtSchedCreateThread(rtSchedNativeProberThread, NULL); + if (RT_FAILURE(rc)) + return rc; + if (g_enmOsPrioSup == OSPRIOSUP_UNDETERMINED) + g_enmOsPrioSup = OSPRIOSUP_THREAD_LEVEL; + Assert(getpriority(PRIO_PROCESS, 0) == iPriority); NOREF(iPriority); + } + + /* + * Now let's see what we can do... + */ + int iPriority = getpriority(PRIO_PROCESS, 0); + switch (g_enmOsPrioSup) + { + case OSPRIOSUP_PROCESS_AND_THREAD_LEVEL: + { + g_aDefaultPriority.iNice = iPriority; + g_aDefaultPriority.iDelta = 0; + g_aDefaultPriority.paTypes = g_aTypesThread; + Assert(enmType == g_aDefaultPriority.paTypes[enmType].enmType); + break; + } + + case OSPRIOSUP_THREAD_LEVEL: + { + if (g_fCanNice) + g_aDefaultPriority.paTypes = g_aTypesUnixFree; + else + g_aDefaultPriority.paTypes = g_aTypesUnixRestricted; + Assert(enmType == g_aDefaultPriority.paTypes[enmType].enmType); + g_aDefaultPriority.iNice = iPriority - g_aDefaultPriority.paTypes[enmType].iPriority; + g_aDefaultPriority.iDelta = g_aDefaultPriority.iNice; + break; + } + + default: + AssertFailed(); + break; + } + rtSchedDumpPriority(); + return VINF_SUCCESS; +} + + +/** + * The validator thread. + * We don't want to mess with the priority of the calling thread. + * + * @remark This is pretty presumptive stuff, but if it works on Linux and + * FreeBSD it does what I want. + */ +static void *rtSchedNativeValidatorThread(void *pvUser) +{ + const PROCPRIORITY *pCfg = (const PROCPRIORITY *)pvUser; + SAVEDPRIORITY SavedPriority; + rtSchedNativeSave(&SavedPriority); + + int rc = VINF_SUCCESS; + switch (g_enmOsPrioSup) + { + /* + * Try set the specified process priority and then try + * out all the thread priorities which are used. + */ + case OSPRIOSUP_PROCESS_AND_THREAD_LEVEL: + { + if (!setpriority(PRIO_PROCESS, 0, pCfg->iNice)) + { + int iMin = sched_get_priority_min(SavedPriority.iPolicy); + pthread_t Self = pthread_self(); + for (int i = RTTHREADTYPE_INVALID + 1; i < RTTHREADTYPE_END; i++) + { + struct sched_param SchedParam = SavedPriority.PthreadSchedParam; + SchedParam.sched_priority = pCfg->paTypes[i].iPriority + + pCfg->iDelta + iMin; + rc = pthread_setschedparam(Self, SavedPriority.iPthreadPolicy, &SchedParam); + if (rc) + { + rc = RTErrConvertFromErrno(rc); + break; + } + } + } + else + rc = RTErrConvertFromErrno(errno); + break; + } + + /* + * Try out the priorities from the top and down. + */ + case OSPRIOSUP_THREAD_LEVEL: + { + int i = RTTHREADTYPE_END; + while (--i > RTTHREADTYPE_INVALID) + { + int iPriority = pCfg->paTypes[i].iPriority + pCfg->iDelta; + if (setpriority(PRIO_PROCESS, 0, iPriority)) + { + rc = RTErrConvertFromErrno(errno); + break; + } + } + break; + } + + default: + AssertFailed(); + break; + } + + /* done */ + rtSchedNativeRestore(&SavedPriority); + return (void *)(intptr_t)rc; +} + + +DECLHIDDEN(int) rtProcNativeSetPriority(RTPROCPRIORITY enmPriority) +{ + Assert(enmPriority > RTPROCPRIORITY_INVALID && enmPriority < RTPROCPRIORITY_LAST); + +#ifdef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY + /* + * Make sure the proxy creation thread is started so we don't 'lose' our + * initial priority if it's lowered. + */ + rtThreadPosixPriorityProxyStart(); +#endif + + /* + * Nothing to validate for the default priority (assuming no external renice). + */ + int rc = VINF_SUCCESS; + if (enmPriority == RTPROCPRIORITY_DEFAULT) + g_pProcessPriority = &g_aDefaultPriority; + else + { + /* + * Select the array to search. + */ + const PROCPRIORITY *pa; + unsigned c; + switch (g_enmOsPrioSup) + { + case OSPRIOSUP_PROCESS_AND_THREAD_LEVEL: + pa = g_aProcessAndThread; + c = RT_ELEMENTS(g_aProcessAndThread); + break; + case OSPRIOSUP_THREAD_LEVEL: + pa = g_aUnixConfigs; + c = RT_ELEMENTS(g_aUnixConfigs); + break; + default: + pa = NULL; + c = 0; + break; + } + + /* + * Search the array. + */ + rc = VERR_FILE_NOT_FOUND; + unsigned i; + for (i = 0; i < c; i++) + { + if (pa[i].enmPriority == enmPriority) + { + /* + * Validate it. + */ + int iPriority = getpriority(PRIO_PROCESS, 0); + int rc3 = rtSchedCreateThread(rtSchedNativeValidatorThread, (void *)&pa[i]); + Assert(getpriority(PRIO_PROCESS, 0) == iPriority); NOREF(iPriority); + if (RT_SUCCESS(rc)) + rc = rc3; + if (RT_SUCCESS(rc)) + break; + } + } + + /* + * Did we get lucky? + * If so update process priority and globals. + */ + if (RT_SUCCESS(rc)) + { + switch (g_enmOsPrioSup) + { + case OSPRIOSUP_PROCESS_AND_THREAD_LEVEL: + if (setpriority(PRIO_PROCESS, 0, pa[i].iNice)) + { + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("setpriority(,,%d) -> errno=%d rc=%Rrc\n", pa[i].iNice, errno, rc)); + } + break; + + default: + break; + } + + if (RT_SUCCESS(rc)) + g_pProcessPriority = &pa[i]; + } + } + +#ifdef THREAD_LOGGING + LogFlow(("rtProcNativeSetPriority: returns %Rrc enmPriority=%d\n", rc, enmPriority)); + rtSchedDumpPriority(); +#endif + return rc; +} + + +/** + * Worker for rtThreadNativeSetPriority/OSPRIOSUP_PROCESS_AND_THREAD_LEVEL + * that's either called on the priority proxy thread or directly if no proxy. + */ +static DECLCALLBACK(int) rtThreadPosixSetPriorityOnProcAndThrdCallback(PRTTHREADINT pThread, RTTHREADTYPE enmType) +{ + struct sched_param SchedParam = {-9999999}; + int iPolicy = -7777777; + int rc = pthread_getschedparam((pthread_t)pThread->Core.Key, &iPolicy, &SchedParam); + if (!rc) + { + SchedParam.sched_priority = g_pProcessPriority->paTypes[enmType].iPriority + + g_pProcessPriority->iDelta + + sched_get_priority_min(iPolicy); + + rc = pthread_setschedparam((pthread_t)pThread->Core.Key, iPolicy, &SchedParam); + if (!rc) + { +#ifdef THREAD_LOGGING + Log(("rtThreadNativeSetPriority: Thread=%p enmType=%d iPolicy=%d sched_priority=%d pid=%d\n", + pThread->Core.Key, enmType, iPolicy, SchedParam.sched_priority, getpid())); +#endif + return VINF_SUCCESS; + } + } + + int rcNative = rc; + rc = RTErrConvertFromErrno(rc); + AssertMsgFailed(("pthread_[gs]etschedparam(%p, %d, {%d}) -> rcNative=%d rc=%Rrc\n", + (void *)pThread->Core.Key, iPolicy, SchedParam.sched_priority, rcNative, rc)); NOREF(rcNative); + return rc; +} + + +DECLHIDDEN(int) rtThreadNativeSetPriority(PRTTHREADINT pThread, RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + Assert(enmType == g_pProcessPriority->paTypes[enmType].enmType); + + int rc = VINF_SUCCESS; + switch (g_enmOsPrioSup) + { + case OSPRIOSUP_PROCESS_AND_THREAD_LEVEL: + { +#ifdef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY + if (rtThreadPosixPriorityProxyStart()) + rc = rtThreadPosixPriorityProxyCall(pThread, (PFNRT)rtThreadPosixSetPriorityOnProcAndThrdCallback, + 2, pThread, enmType); + else +#endif + rc = rtThreadPosixSetPriorityOnProcAndThrdCallback(pThread, enmType); + break; + } + + case OSPRIOSUP_THREAD_LEVEL: + { + /* No cross platform way of getting the 'who' parameter value for + arbitrary threads, so this is restricted to the calling thread only. */ + AssertReturn((pthread_t)pThread->Core.Key == pthread_self(), VERR_NOT_SUPPORTED); + + int iPriority = g_pProcessPriority->paTypes[enmType].iPriority + g_pProcessPriority->iDelta; + if (!setpriority(PRIO_PROCESS, 0, iPriority)) + { + AssertMsg(iPriority == getpriority(PRIO_PROCESS, 0), ("iPriority=%d getpriority()=%d\n", iPriority, getpriority(PRIO_PROCESS, 0))); +#ifdef THREAD_LOGGING + Log(("rtThreadNativeSetPriority: Thread=%p enmType=%d iPriority=%d pid=%d\n", pThread->Core.Key, enmType, iPriority, getpid())); +#endif + } + else + { +#if 0 + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("setpriority(,, %d) -> errno=%d rc=%Rrc\n", iPriority, errno, rc)); +#else + /** @todo + * Just keep quiet about failures now - we'll fail here because we're not + * allowed to raise our own priority. This is a problem when starting the + * threads with higher priority from EMT (i.e. most threads it starts). + * This is apparently inherited from the parent in some cases and not + * in other cases. I guess this would come down to which kind of pthread + * implementation is actually in use, and how many sensible patches which + * are installed. + * I need to find a system where this problem shows up in order to come up + * with a proper fix. There's an pthread_create attribute for not inheriting + * scheduler stuff I think... + */ + rc = VINF_SUCCESS; +#endif + } + break; + } + + /* + * Any thread created before we determine the default config, remains unchanged! + * The prober thread above is one of those. + */ + default: + break; + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/semevent-posix.cpp b/src/VBox/Runtime/r3/posix/semevent-posix.cpp new file mode 100644 index 00000000..96d01bac --- /dev/null +++ b/src/VBox/Runtime/r3/posix/semevent-posix.cpp @@ -0,0 +1,654 @@ +/* $Id: semevent-posix.cpp $ */ +/** @file + * IPRT - Event Semaphore, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/lockvalidator.h> +#include <iprt/time.h> + +#include "internal/mem.h" +#include "internal/strict.h" + +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/time.h> +#include <sched.h> + +#include "semwait.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Internal representation of the POSIX implementation of an Event semaphore. + * The POSIX implementation uses a mutex and a condition variable to implement + * the automatic reset event semaphore semantics. + */ +struct RTSEMEVENTINTERNAL +{ + /** pthread condition. */ + pthread_cond_t Cond; + /** pthread mutex which protects the condition and the event state. */ + pthread_mutex_t Mutex; + /** The state of the semaphore. + * This is operated while owning mutex and using atomic updating. */ + volatile uint32_t u32State; + /** Number of waiters. */ + volatile uint32_t cWaiters; +#ifdef RTSEMEVENT_STRICT + /** Signallers. */ + RTLOCKVALRECSHRD Signallers; + /** Indicates that lock validation should be performed. */ + bool volatile fEverHadSignallers; +#endif + /** The creation flags. */ + uint32_t fFlags; + /** Set if we're using the monotonic clock. */ + bool fMonotonicClock; +}; + +/** The values of the u32State variable in a RTSEMEVENTINTERNAL. + * @{ */ +/** The object isn't initialized. */ +#define EVENT_STATE_UNINITIALIZED 0 +/** The semaphore is signaled. */ +#define EVENT_STATE_SIGNALED 0xff00ff00 +/** The semaphore is not signaled. */ +#define EVENT_STATE_NOT_SIGNALED 0x00ff00ff +/** @} */ + + +RTDECL(int) RTSemEventCreate(PRTSEMEVENT phEventSem) +{ + return RTSemEventCreateEx(phEventSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventCreateEx(PRTSEMEVENT phEventSem, uint32_t fFlags, RTLOCKVALCLASS hClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~(RTSEMEVENT_FLAGS_NO_LOCK_VAL | RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)), VERR_INVALID_PARAMETER); + Assert(!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) || (fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL)); + + /* + * Allocate semaphore handle. + */ + int rc; + struct RTSEMEVENTINTERNAL *pThis; + if (!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + pThis = (struct RTSEMEVENTINTERNAL *)RTMemAlloc(sizeof(*pThis)); + else + pThis = (struct RTSEMEVENTINTERNAL *)rtMemBaseAlloc(sizeof(*pThis)); + if (pThis) + { + /* + * Create the condition variable. + */ + pthread_condattr_t CondAttr; + rc = pthread_condattr_init(&CondAttr); + if (!rc) + { +#if defined(CLOCK_MONOTONIC) && defined(IPRT_HAVE_PTHREAD_CONDATTR_SETCLOCK) + /* ASSUMES RTTimeSystemNanoTS() == RTTimeNanoTS() == clock_gettime(CLOCK_MONOTONIC). */ + rc = pthread_condattr_setclock(&CondAttr, CLOCK_MONOTONIC); + pThis->fMonotonicClock = rc == 0; +#else + pThis->fMonotonicClock = false; +#endif + rc = pthread_cond_init(&pThis->Cond, &CondAttr); + if (!rc) + { + /* + * Create the semaphore. + */ + rc = pthread_mutex_init(&pThis->Mutex, NULL); + if (!rc) + { + pthread_condattr_destroy(&CondAttr); + + ASMAtomicWriteU32(&pThis->u32State, EVENT_STATE_NOT_SIGNALED); + ASMAtomicWriteU32(&pThis->cWaiters, 0); + pThis->fFlags = fFlags; +#ifdef RTSEMEVENT_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iSemEventAnon = 0; + RTLockValidatorRecSharedInit(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL), + "RTSemEvent-%u", ASMAtomicIncU32(&s_iSemEventAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL), + pszNameFmt, va); + va_end(va); + } + pThis->fEverHadSignallers = false; +#else + RT_NOREF_PV(hClass); RT_NOREF_PV(pszNameFmt); +#endif + + *phEventSem = pThis; + return VINF_SUCCESS; + } + pthread_cond_destroy(&pThis->Cond); + } + pthread_condattr_destroy(&CondAttr); + } + + rc = RTErrConvertFromErrno(rc); + if (!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + RTMemFree(pThis); + else + rtMemBaseFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTSemEventDestroy(RTSEMEVENT hEventSem) +{ + /* + * Validate handle. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + if (pThis == NIL_RTSEMEVENT) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + uint32_t u32 = pThis->u32State; + AssertReturn(u32 == EVENT_STATE_NOT_SIGNALED || u32 == EVENT_STATE_SIGNALED, VERR_INVALID_HANDLE); + + /* + * Abort all waiters forcing them to return failure. + */ + int rc; + for (int i = 30; i > 0; i--) + { + ASMAtomicWriteU32(&pThis->u32State, EVENT_STATE_UNINITIALIZED); + rc = pthread_cond_destroy(&pThis->Cond); + if (rc != EBUSY) + break; + pthread_cond_broadcast(&pThis->Cond); + usleep(1000); + } + if (rc) + { + AssertMsgFailed(("Failed to destroy event sem %p, rc=%d.\n", pThis, rc)); + return RTErrConvertFromErrno(rc); + } + + /* + * Destroy the semaphore + * If it's busy we'll wait a bit to give the threads a chance to be scheduled. + */ + for (int i = 30; i > 0; i--) + { + rc = pthread_mutex_destroy(&pThis->Mutex); + if (rc != EBUSY) + break; + usleep(1000); + } + if (rc) + { + AssertMsgFailed(("Failed to destroy event sem %p, rc=%d. (mutex)\n", pThis, rc)); + return RTErrConvertFromErrno(rc); + } + + /* + * Free the semaphore memory and be gone. + */ +#ifdef RTSEMEVENT_STRICT + RTLockValidatorRecSharedDelete(&pThis->Signallers); +#endif + if (!(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + RTMemFree(pThis); + else + rtMemBaseFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSemEventSignal(RTSEMEVENT hEventSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + uint32_t u32 = pThis->u32State; + AssertReturn(u32 == EVENT_STATE_NOT_SIGNALED || u32 == EVENT_STATE_SIGNALED, VERR_INVALID_HANDLE); + +#ifdef RTSEMEVENT_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckSignaller(&pThis->Signallers, NIL_RTTHREAD); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif + + /* + * Lock the mutex semaphore. + */ + int rc = pthread_mutex_lock(&pThis->Mutex); + if (rc) + { + AssertMsgFailed(("Failed to lock event sem %p, rc=%d.\n", hEventSem, rc)); + return RTErrConvertFromErrno(rc); + } + + /* + * Check the state. + */ + if (pThis->u32State == EVENT_STATE_NOT_SIGNALED) + { + ASMAtomicWriteU32(&pThis->u32State, EVENT_STATE_SIGNALED); + rc = pthread_cond_signal(&pThis->Cond); + AssertMsg(!rc, ("Failed to signal event sem %p, rc=%d.\n", hEventSem, rc)); + } + else if (pThis->u32State == EVENT_STATE_SIGNALED) + { + rc = pthread_cond_signal(&pThis->Cond); /* give'm another kick... */ + AssertMsg(!rc, ("Failed to signal event sem %p, rc=%d. (2)\n", hEventSem, rc)); + } + else + rc = VERR_SEM_DESTROYED; + + /* + * Release the mutex and return. + */ + int rc2 = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc2, ("Failed to unlock event sem %p, rc=%d.\n", hEventSem, rc)); + if (rc) + return RTErrConvertFromErrno(rc); + if (rc2) + return RTErrConvertFromErrno(rc2); + + return VINF_SUCCESS; +} + + +/** + * Handle polling (timeout already expired at the time of the call). + * + * @returns VINF_SUCCESS, VERR_TIMEOUT, VERR_SEM_DESTROYED. + * @param pThis The semaphore. + */ +DECLINLINE(int) rtSemEventPosixWaitPoll(struct RTSEMEVENTINTERNAL *pThis) +{ + int rc = pthread_mutex_lock(&pThis->Mutex); + AssertMsgReturn(!rc, ("Failed to lock event sem %p, rc=%d.\n", pThis, rc), RTErrConvertFromErrno(rc)); + + uint32_t u32OldState; + bool fSuccess = ASMAtomicCmpXchgExU32(&pThis->u32State, EVENT_STATE_NOT_SIGNALED, EVENT_STATE_SIGNALED, &u32OldState); + + rc = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc, ("Failed to unlock event sem %p, rc=%d.\n", pThis, rc)); NOREF(rc); + + return fSuccess + ? VINF_SUCCESS + : u32OldState != EVENT_STATE_UNINITIALIZED + ? VERR_TIMEOUT + : VERR_SEM_DESTROYED; +} + + +/** + * Performs an indefinite wait on the event. + */ +static int rtSemEventPosixWaitIndefinite(struct RTSEMEVENTINTERNAL *pThis, uint32_t fFlags, PCRTLOCKVALSRCPOS pSrcPos) +{ + RT_NOREF_PV(pSrcPos); + + /* for fairness, yield before going to sleep. */ + if ( ASMAtomicIncU32(&pThis->cWaiters) > 1 + && pThis->u32State == EVENT_STATE_SIGNALED) + sched_yield(); + + /* take mutex */ + int rc = pthread_mutex_lock(&pThis->Mutex); + if (rc) + { + ASMAtomicDecU32(&pThis->cWaiters); + AssertMsgFailed(("Failed to lock event sem %p, rc=%d.\n", pThis, rc)); + return RTErrConvertFromErrno(rc); + } + + for (;;) + { + /* check state. */ + if (pThis->u32State == EVENT_STATE_SIGNALED) + { + ASMAtomicWriteU32(&pThis->u32State, EVENT_STATE_NOT_SIGNALED); + ASMAtomicDecU32(&pThis->cWaiters); + rc = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc, ("Failed to unlock event sem %p, rc=%d.\n", pThis, rc)); NOREF(rc); + return VINF_SUCCESS; + } + if (pThis->u32State == EVENT_STATE_UNINITIALIZED) + { + rc = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc, ("Failed to unlock event sem %p, rc=%d.\n", pThis, rc)); NOREF(rc); + return VERR_SEM_DESTROYED; + } + + /* wait */ +#ifdef RTSEMEVENT_STRICT + RTTHREAD hThreadSelf = !(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) + ? RTThreadSelfAutoAdopt() + : RTThreadSelf(); + if (pThis->fEverHadSignallers) + { + rc = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + RT_INDEFINITE_WAIT, RTTHREADSTATE_EVENT, true); + if (RT_FAILURE(rc)) + { + ASMAtomicDecU32(&pThis->cWaiters); + pthread_mutex_unlock(&pThis->Mutex); + return rc; + } + } +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT, true); + RT_NOREF_PV(fFlags); /** @todo interruptible wait is not implementable... */ + rc = pthread_cond_wait(&pThis->Cond, &pThis->Mutex); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT); + if (rc) + { + AssertMsgFailed(("Failed to wait on event sem %p, rc=%d.\n", pThis, rc)); + ASMAtomicDecU32(&pThis->cWaiters); + int rc2 = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc2, ("Failed to unlock event sem %p, rc=%d.\n", pThis, rc2)); NOREF(rc2); + return RTErrConvertFromErrno(rc); + } + } +} + + +/** + * Performs an timed wait on the event. + */ +static int rtSemEventPosixWaitTimed(struct RTSEMEVENTINTERNAL *pThis, uint32_t fFlags, uint64_t uTimeout, + PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Convert the timeout specification to absolute and relative deadlines, + * divierting polling and infinite waits to the appropriate workers. + */ + struct timespec AbsDeadline = { 0, 0 }; + uint64_t const cNsRelativeDeadline = rtSemPosixCalcDeadline(fFlags, uTimeout, pThis->fMonotonicClock, &AbsDeadline); + if (cNsRelativeDeadline == 0) + return rtSemEventPosixWaitPoll(pThis); + if (cNsRelativeDeadline == UINT64_MAX) + return rtSemEventPosixWaitIndefinite(pThis, fFlags, pSrcPos); + + /* + * Now to the business of waiting... + */ + + /* for fairness, yield before going to sleep. */ + if (ASMAtomicIncU32(&pThis->cWaiters) > 1) + sched_yield(); + + /* take mutex */ + int rc = pthread_mutex_lock(&pThis->Mutex); + if (rc) + { + ASMAtomicDecU32(&pThis->cWaiters); + AssertMsg(rc == ETIMEDOUT, ("Failed to lock event sem %p, rc=%d.\n", pThis, rc)); + return RTErrConvertFromErrno(rc); + } + + for (;;) + { + /* check state. */ + uint32_t const u32State = pThis->u32State; + if (u32State != EVENT_STATE_NOT_SIGNALED) + { + if (u32State == EVENT_STATE_SIGNALED) + { + ASMAtomicWriteU32(&pThis->u32State, EVENT_STATE_NOT_SIGNALED); + ASMAtomicDecU32(&pThis->cWaiters); + rc = VINF_SUCCESS; + } + else + { + Assert(u32State == EVENT_STATE_UNINITIALIZED); + rc = VERR_SEM_DESTROYED; + } + int rc2 = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc2, ("Failed to unlock event sem %p, rc2=%d.\n", pThis, rc2)); RT_NOREF(rc2); + return rc; + } + + /* wait */ +#ifdef RTSEMEVENT_STRICT + RTTHREAD hThreadSelf = !(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) + ? RTThreadSelfAutoAdopt() + : RTThreadSelf(); + if (pThis->fEverHadSignallers) + { + rc = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + (cNsRelativeDeadline + RT_NS_1MS - 1) / RT_NS_1MS, + RTTHREADSTATE_EVENT, true); + if (RT_FAILURE(rc)) + { + ASMAtomicDecU32(&pThis->cWaiters); + pthread_mutex_unlock(&pThis->Mutex); + return rc; + } + } +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT, true); + rc = pthread_cond_timedwait(&pThis->Cond, &pThis->Mutex, &AbsDeadline); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT); + + /* According to SuS this function shall not return EINTR, but linux man page might have said differently at some point... */ + if ( rc != 0 + && ( rc != EINTR + || !(fFlags & RTSEMWAIT_FLAGS_NORESUME))) + { + AssertMsg(rc == ETIMEDOUT, ("Failed to wait on event sem %p, rc=%d.\n", pThis, rc)); + ASMAtomicDecU32(&pThis->cWaiters); + int rc2 = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc2, ("Failed to unlock event sem %p, rc2=%d.\n", pThis, rc2)); NOREF(rc2); + return RTErrConvertFromErrno(rc); + } + } /* for (;;) */ +} + + +/** + * Internal wait worker function. + */ +DECLINLINE(int) rtSemEventPosixWait(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + uint32_t u32 = pThis->u32State; + AssertReturn(u32 == EVENT_STATE_NOT_SIGNALED || u32 == EVENT_STATE_SIGNALED, VERR_INVALID_HANDLE); + AssertReturn(RTSEMWAIT_FLAGS_ARE_VALID(fFlags), VERR_INVALID_PARAMETER); + + /* + * Timed or indefinite wait? + */ + if (fFlags & RTSEMWAIT_FLAGS_INDEFINITE) + return rtSemEventPosixWaitIndefinite(pThis, fFlags, pSrcPos); + return rtSemEventPosixWaitTimed(hEventSem, fFlags, uTimeout, pSrcPos); +} + + +RTDECL(int) RTSemEventWait(RTSEMEVENT hEventSem, RTMSINTERVAL cMillies) +{ + int rc; +#ifndef RTSEMEVENT_STRICT + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, NULL); + else + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, &SrcPos); + else + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_RESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, &SrcPos); +#endif + Assert(rc != VERR_INTERRUPTED); + return rc; +} + + +RTDECL(int) RTSemEventWaitNoResume(RTSEMEVENT hEventSem, RTMSINTERVAL cMillies) +{ + int rc; +#ifndef RTSEMEVENT_STRICT + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, NULL); + else + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + if (cMillies == RT_INDEFINITE_WAIT) + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_INDEFINITE, 0, &SrcPos); + else + rc = rtSemEventPosixWait(hEventSem, RTSEMWAIT_FLAGS_NORESUME | RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS, + cMillies, &SrcPos); +#endif + Assert(rc != VERR_INTERRUPTED); + return rc; +} + + +RTDECL(int) RTSemEventWaitEx(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout) +{ +#ifndef RTSEMEVENT_STRICT + return rtSemEventPosixWait(hEventSem, fFlags, uTimeout, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemEventPosixWait(hEventSem, fFlags, uTimeout, &SrcPos); +#endif +} + + +RTDECL(int) RTSemEventWaitExDebug(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout, + RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemEventPosixWait(hEventSem, fFlags, uTimeout, &SrcPos); +} + + +RTDECL(uint32_t) RTSemEventGetResolution(void) +{ + /** @todo we have 1ns parameter resolution, but we need to check each host + * what the actual resolution might be once the parameter makes it to the + * kernel and is processed there. */ + return 1; +} + + +RTDECL(void) RTSemEventSetSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + uint32_t u32 = pThis->u32State; + AssertReturnVoid(u32 == EVENT_STATE_NOT_SIGNALED || u32 == EVENT_STATE_SIGNALED); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedResetOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventAddSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + uint32_t u32 = pThis->u32State; + AssertReturnVoid(u32 == EVENT_STATE_NOT_SIGNALED || u32 == EVENT_STATE_SIGNALED); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedAddOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventRemoveSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + uint32_t u32 = pThis->u32State; + AssertReturnVoid(u32 == EVENT_STATE_NOT_SIGNALED || u32 == EVENT_STATE_SIGNALED); + + RTLockValidatorRecSharedRemoveOwner(&pThis->Signallers, hThread); +#else + RT_NOREF_PV(hEventSem); RT_NOREF_PV(hThread); +#endif +} + diff --git a/src/VBox/Runtime/r3/posix/semeventmulti-posix.cpp b/src/VBox/Runtime/r3/posix/semeventmulti-posix.cpp new file mode 100644 index 00000000..52171b4a --- /dev/null +++ b/src/VBox/Runtime/r3/posix/semeventmulti-posix.cpp @@ -0,0 +1,613 @@ +/* $Id: semeventmulti-posix.cpp $ */ +/** @file + * IPRT - Multiple Release Event Semaphore, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/mem.h> +#include <iprt/time.h> + +#include "internal/strict.h" + +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/time.h> + +#include "semwait.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Posix internal representation of a Mutex Multi semaphore. + * The POSIX implementation uses a mutex and a condition variable to implement + * the automatic reset event semaphore semantics. */ +struct RTSEMEVENTMULTIINTERNAL +{ + /** pthread condition. */ + pthread_cond_t Cond; + /** pthread mutex which protects the condition and the event state. */ + pthread_mutex_t Mutex; + /** The state of the semaphore. + * This is operated while owning mutex and using atomic updating. */ + volatile uint32_t u32State; + /** Number of waiters. */ + volatile uint32_t cWaiters; +#ifdef RTSEMEVENTMULTI_STRICT + /** Signallers. */ + RTLOCKVALRECSHRD Signallers; + /** Indicates that lock validation should be performed. */ + bool volatile fEverHadSignallers; +#endif + /** Set if we're using the monotonic clock. */ + bool fMonotonicClock; +}; + +/** The values of the u32State variable in RTSEMEVENTMULTIINTERNAL. + * @{ */ +/** The object isn't initialized. */ +#define EVENTMULTI_STATE_UNINITIALIZED 0 +/** The semaphore is signaled. */ +#define EVENTMULTI_STATE_SIGNALED 0xff00ff00 +/** The semaphore is not signaled. */ +#define EVENTMULTI_STATE_NOT_SIGNALED 0x00ff00ff +/** @} */ + + + +RTDECL(int) RTSemEventMultiCreate(PRTSEMEVENTMULTI phEventMultiSem) +{ + return RTSemEventMultiCreateEx(phEventMultiSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventMultiCreateEx(PRTSEMEVENTMULTI phEventMultiSem, uint32_t fFlags, RTLOCKVALCLASS hClass, + const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Allocate semaphore handle. + */ + int rc; + struct RTSEMEVENTMULTIINTERNAL *pThis = (struct RTSEMEVENTMULTIINTERNAL *)RTMemAlloc(sizeof(struct RTSEMEVENTMULTIINTERNAL)); + if (pThis) + { + /* + * Create the condition variable. + */ + pthread_condattr_t CondAttr; + rc = pthread_condattr_init(&CondAttr); + if (!rc) + { +#if defined(CLOCK_MONOTONIC) && defined(IPRT_HAVE_PTHREAD_CONDATTR_SETCLOCK) + /* ASSUMES RTTimeSystemNanoTS() == RTTimeNanoTS() == clock_gettime(CLOCK_MONOTONIC). */ + rc = pthread_condattr_setclock(&CondAttr, CLOCK_MONOTONIC); + pThis->fMonotonicClock = rc == 0; +#else + pThis->fMonotonicClock = false; +#endif + rc = pthread_cond_init(&pThis->Cond, &CondAttr); + if (!rc) + { + /* + * Create the semaphore. + */ + rc = pthread_mutex_init(&pThis->Mutex, NULL); + if (!rc) + { + pthread_condattr_destroy(&CondAttr); + + ASMAtomicWriteU32(&pThis->u32State, EVENTMULTI_STATE_NOT_SIGNALED); + ASMAtomicWriteU32(&pThis->cWaiters, 0); +#ifdef RTSEMEVENTMULTI_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iSemEventMultiAnon = 0; + RTLockValidatorRecSharedInit(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), + "RTSemEventMulti-%u", ASMAtomicIncU32(&s_iSemEventMultiAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), + pszNameFmt, va); + va_end(va); + } + pThis->fEverHadSignallers = false; +#else + RT_NOREF_PV(hClass); RT_NOREF_PV(pszNameFmt); +#endif + + *phEventMultiSem = pThis; + return VINF_SUCCESS; + } + + pthread_cond_destroy(&pThis->Cond); + } + pthread_condattr_destroy(&CondAttr); + } + rc = RTErrConvertFromErrno(rc); + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; + +} + + +RTDECL(int) RTSemEventMultiDestroy(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate handle. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + if (pThis == NIL_RTSEMEVENTMULTI) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + uint32_t u32 = pThis->u32State; + AssertReturn(u32 == EVENTMULTI_STATE_NOT_SIGNALED || u32 == EVENTMULTI_STATE_SIGNALED, VERR_INVALID_HANDLE); + + /* + * Abort all waiters forcing them to return failure. + */ + int rc; + for (int i = 30; i > 0; i--) + { + ASMAtomicXchgU32(&pThis->u32State, EVENTMULTI_STATE_UNINITIALIZED); + rc = pthread_cond_destroy(&pThis->Cond); + if (rc != EBUSY) + break; + pthread_cond_broadcast(&pThis->Cond); + usleep(1000); + } + if (rc) + { + AssertMsgFailed(("Failed to destroy event sem %p, rc=%d.\n", hEventMultiSem, rc)); + return RTErrConvertFromErrno(rc); + } + + /* + * Destroy the semaphore + * If it's busy we'll wait a bit to give the threads a chance to be scheduled. + */ + for (int i = 30; i > 0; i--) + { + rc = pthread_mutex_destroy(&pThis->Mutex); + if (rc != EBUSY) + break; + usleep(1000); + } + if (rc) + { + AssertMsgFailed(("Failed to destroy event sem %p, rc=%d. (mutex)\n", hEventMultiSem, rc)); + return RTErrConvertFromErrno(rc); + } + + /* + * Free the semaphore memory and be gone. + */ +#ifdef RTSEMEVENTMULTI_STRICT + RTLockValidatorRecSharedDelete(&pThis->Signallers); +#endif + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSemEventMultiSignal(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + uint32_t u32 = pThis->u32State; + AssertReturn(u32 == EVENTMULTI_STATE_NOT_SIGNALED || u32 == EVENTMULTI_STATE_SIGNALED, VERR_INVALID_HANDLE); + +#ifdef RTSEMEVENTMULTI_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckSignaller(&pThis->Signallers, NIL_RTTHREAD); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif + + /* + * Lock the mutex semaphore. + */ + int rc = pthread_mutex_lock(&pThis->Mutex); + if (rc) + { + AssertMsgFailed(("Failed to lock event sem %p, rc=%d.\n", hEventMultiSem, rc)); + return RTErrConvertFromErrno(rc); + } + + /* + * Check the state. + */ + if (pThis->u32State == EVENTMULTI_STATE_NOT_SIGNALED) + { + ASMAtomicXchgU32(&pThis->u32State, EVENTMULTI_STATE_SIGNALED); + rc = pthread_cond_broadcast(&pThis->Cond); + AssertMsg(!rc, ("Failed to signal event sem %p, rc=%d.\n", hEventMultiSem, rc)); + } + else if (pThis->u32State == EVENTMULTI_STATE_SIGNALED) + { + rc = pthread_cond_broadcast(&pThis->Cond); /* give'm another kick... */ + AssertMsg(!rc, ("Failed to signal event sem %p, rc=%d. (2)\n", hEventMultiSem, rc)); + } + else + rc = VERR_SEM_DESTROYED; + + /* + * Release the mutex and return. + */ + int rc2 = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc2, ("Failed to unlock event sem %p, rc=%d.\n", hEventMultiSem, rc)); + if (rc) + return RTErrConvertFromErrno(rc); + if (rc2) + return RTErrConvertFromErrno(rc2); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTSemEventMultiReset(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate input. + */ + int rc = VINF_SUCCESS; + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + uint32_t u32 = pThis->u32State; + AssertReturn(u32 == EVENTMULTI_STATE_NOT_SIGNALED || u32 == EVENTMULTI_STATE_SIGNALED, VERR_INVALID_HANDLE); + + /* + * Lock the mutex semaphore. + */ + int rcPosix = pthread_mutex_lock(&pThis->Mutex); + if (RT_UNLIKELY(rcPosix)) + { + AssertMsgFailed(("Failed to lock event multi sem %p, rc=%d.\n", hEventMultiSem, rcPosix)); + return RTErrConvertFromErrno(rcPosix); + } + + /* + * Check the state. + */ + if (pThis->u32State == EVENTMULTI_STATE_SIGNALED) + ASMAtomicXchgU32(&pThis->u32State, EVENTMULTI_STATE_NOT_SIGNALED); + else if (pThis->u32State != EVENTMULTI_STATE_NOT_SIGNALED) + rc = VERR_SEM_DESTROYED; + + /* + * Release the mutex and return. + */ + rcPosix = pthread_mutex_unlock(&pThis->Mutex); + if (RT_UNLIKELY(rcPosix)) + { + AssertMsgFailed(("Failed to unlock event multi sem %p, rc=%d.\n", hEventMultiSem, rcPosix)); + return RTErrConvertFromErrno(rcPosix); + } + + return rc; +} + + +/** + * Handle polling (timeout already expired at the time of the call). + * + * @returns VINF_SUCCESS, VERR_TIMEOUT, VERR_SEM_DESTROYED. + * @param pThis The semaphore. + */ +DECLINLINE(int) rtSemEventMultiPosixWaitPoll(struct RTSEMEVENTMULTIINTERNAL *pThis) +{ + int rc = pthread_mutex_lock(&pThis->Mutex); + AssertMsgReturn(!rc, ("Failed to lock event multi sem %p, rc=%d.\n", pThis, rc), RTErrConvertFromErrno(rc)); + + uint32_t const u32State = pThis->u32State; + + rc = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc, ("Failed to unlock event multi sem %p, rc=%d.\n", pThis, rc)); NOREF(rc); + + return u32State == EVENTMULTI_STATE_SIGNALED + ? VINF_SUCCESS + : u32State != EVENTMULTI_STATE_UNINITIALIZED + ? VERR_TIMEOUT + : VERR_SEM_DESTROYED; +} + + + +/** + * Implements the indefinite wait. + * + * @returns See RTSemEventMultiWaitEx. + * @param pThis The semaphore. + * @param fFlags See RTSemEventMultiWaitEx. + * @param pSrcPos The source position, can be NULL. + */ +static int rtSemEventMultiPosixWaitIndefinite(struct RTSEMEVENTMULTIINTERNAL *pThis, uint32_t fFlags, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* take mutex */ + int rc = pthread_mutex_lock(&pThis->Mutex); + AssertMsgReturn(!rc, ("Failed to lock event multi sem %p, rc=%d.\n", pThis, rc), RTErrConvertFromErrno(rc)); + ASMAtomicIncU32(&pThis->cWaiters); + + for (;;) + { + /* check state. */ + uint32_t const u32State = pThis->u32State; + if (u32State != EVENTMULTI_STATE_NOT_SIGNALED) + { + ASMAtomicDecU32(&pThis->cWaiters); + rc = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc, ("Failed to unlock event multi sem %p, rc=%d.\n", pThis, rc)); + return u32State == EVENTMULTI_STATE_SIGNALED + ? VINF_SUCCESS + : VERR_SEM_DESTROYED; + } + + /* wait */ +#ifdef RTSEMEVENTMULTI_STRICT + RTTHREAD hThreadSelf = RTThreadSelfAutoAdopt(); + if (pThis->fEverHadSignallers) + { + rc = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + RT_INDEFINITE_WAIT, RTTHREADSTATE_EVENT_MULTI, true); + if (RT_FAILURE(rc)) + { + ASMAtomicDecU32(&pThis->cWaiters); + pthread_mutex_unlock(&pThis->Mutex); + return rc; + } + } +#else + RTTHREAD hThreadSelf = RTThreadSelf(); + RT_NOREF_PV(pSrcPos); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT_MULTI, true); + /** @todo interruptible wait is not implementable... */ NOREF(fFlags); + rc = pthread_cond_wait(&pThis->Cond, &pThis->Mutex); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT_MULTI); + if (RT_UNLIKELY(rc)) + { + AssertMsgFailed(("Failed to wait on event multi sem %p, rc=%d.\n", pThis, rc)); + ASMAtomicDecU32(&pThis->cWaiters); + int rc2 = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc2, ("Failed to unlock event multi sem %p, rc=%d.\n", pThis, rc2)); NOREF(rc2); + return RTErrConvertFromErrno(rc); + } + } +} + + +/** + * Implements the timed wait. + * + * @returns See RTSemEventMultiWaitEx + * @param pThis The semaphore. + * @param fFlags See RTSemEventMultiWaitEx. + * @param uTimeout See RTSemEventMultiWaitEx. + * @param pSrcPos The source position, can be NULL. + */ +static int rtSemEventMultiPosixWaitTimed(struct RTSEMEVENTMULTIINTERNAL *pThis, uint32_t fFlags, uint64_t uTimeout, + PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Convert the timeout specification to absolute and relative deadlines, + * divierting polling and infinite waits to the appropriate workers. + */ + struct timespec AbsDeadline = { 0, 0 }; + uint64_t const cNsRelativeDeadline = rtSemPosixCalcDeadline(fFlags, uTimeout, pThis->fMonotonicClock, &AbsDeadline); + if (cNsRelativeDeadline == 0) + return rtSemEventMultiPosixWaitPoll(pThis); + if (cNsRelativeDeadline == UINT64_MAX) + return rtSemEventMultiPosixWaitIndefinite(pThis, fFlags, pSrcPos); + + /* + * To business! + */ + /* take mutex */ + int rc = pthread_mutex_lock(&pThis->Mutex); + AssertMsgReturn(rc == 0, ("rc=%d pThis=%p\n", rc, pThis), RTErrConvertFromErrno(rc)); NOREF(rc); + ASMAtomicIncU32(&pThis->cWaiters); + + for (;;) + { + /* check state. */ + uint32_t const u32State = pThis->u32State; + if (u32State != EVENTMULTI_STATE_NOT_SIGNALED) + { + ASMAtomicDecU32(&pThis->cWaiters); + rc = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc, ("Failed to unlock event multi sem %p, rc=%d.\n", pThis, rc)); + return u32State == EVENTMULTI_STATE_SIGNALED + ? VINF_SUCCESS + : VERR_SEM_DESTROYED; + } + + /* wait */ +#ifdef RTSEMEVENTMULTI_STRICT + RTTHREAD hThreadSelf = RTThreadSelfAutoAdopt(); + if (pThis->fEverHadSignallers) + { + rc = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + (uTimeout + RT_NS_1MS - 1)/ RT_NS_1MS, RTTHREADSTATE_EVENT_MULTI, true); + if (RT_FAILURE(rc)) + { + ASMAtomicDecU32(&pThis->cWaiters); + pthread_mutex_unlock(&pThis->Mutex); + return rc; + } + } +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT_MULTI, true); + rc = pthread_cond_timedwait(&pThis->Cond, &pThis->Mutex, &AbsDeadline); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT_MULTI); + + /* According to SuS this function shall not return EINTR, but linux man page might have said differently at some point... */ + if ( rc != 0 + && ( rc != EINTR + || (fFlags & RTSEMWAIT_FLAGS_NORESUME)) ) + { + AssertMsg(rc == ETIMEDOUT, ("Failed to wait on event multi sem %p, rc=%d.\n", pThis, rc)); + ASMAtomicDecU32(&pThis->cWaiters); + int rc2 = pthread_mutex_unlock(&pThis->Mutex); + AssertMsg(!rc2, ("Failed to unlock event multi sem %p, rc=%d.\n", pThis, rc2)); NOREF(rc2); + return RTErrConvertFromErrno(rc); + } + } +} + + +DECLINLINE(int) rtSemEventMultiPosixWait(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, + PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + uint32_t u32 = pThis->u32State; + AssertReturn(u32 == EVENTMULTI_STATE_NOT_SIGNALED || u32 == EVENTMULTI_STATE_SIGNALED, VERR_INVALID_HANDLE); + AssertReturn(RTSEMWAIT_FLAGS_ARE_VALID(fFlags), VERR_INVALID_PARAMETER); + + /* + * Optimize the case where the event is signalled. + */ + if (ASMAtomicUoReadU32(&pThis->u32State) == EVENTMULTI_STATE_SIGNALED) + { + int rc = rtSemEventMultiPosixWaitPoll(pThis); + if (RT_LIKELY(rc != VERR_TIMEOUT)) + return rc; + } + + /* + * Indefinite or timed wait? + */ + if (fFlags & RTSEMWAIT_FLAGS_INDEFINITE) + return rtSemEventMultiPosixWaitIndefinite(pThis, fFlags, pSrcPos); + return rtSemEventMultiPosixWaitTimed(pThis, fFlags, uTimeout, pSrcPos); +} + + +#undef RTSemEventMultiWaitEx +RTDECL(int) RTSemEventMultiWaitEx(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout) +{ +#ifndef RTSEMEVENT_STRICT + return rtSemEventMultiPosixWait(hEventMultiSem, fFlags, uTimeout, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemEventMultiPosixWait(hEventMultiSem, fFlags, uTimeout, &SrcPos); +#endif +} + + +RTDECL(int) RTSemEventMultiWaitExDebug(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, + RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemEventMultiPosixWait(hEventMultiSem, fFlags, uTimeout, &SrcPos); +} + + +RTDECL(void) RTSemEventMultiSetSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENTMULTI_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + uint32_t u32 = pThis->u32State; + AssertReturnVoid(u32 == EVENTMULTI_STATE_NOT_SIGNALED || u32 == EVENTMULTI_STATE_SIGNALED); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedResetOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventMultiSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventMultiAddSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENTMULTI_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + uint32_t u32 = pThis->u32State; + AssertReturnVoid(u32 == EVENTMULTI_STATE_NOT_SIGNALED || u32 == EVENTMULTI_STATE_SIGNALED); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedAddOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventMultiSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventMultiRemoveSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENTMULTI_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + uint32_t u32 = pThis->u32State; + AssertReturnVoid(u32 == EVENTMULTI_STATE_NOT_SIGNALED || u32 == EVENTMULTI_STATE_SIGNALED); + + RTLockValidatorRecSharedRemoveOwner(&pThis->Signallers, hThread); +#else + RT_NOREF_PV(hEventMultiSem); RT_NOREF_PV(hThread); +#endif +} + diff --git a/src/VBox/Runtime/r3/posix/semmutex-posix.cpp b/src/VBox/Runtime/r3/posix/semmutex-posix.cpp new file mode 100644 index 00000000..f0df48ef --- /dev/null +++ b/src/VBox/Runtime/r3/posix/semmutex-posix.cpp @@ -0,0 +1,467 @@ +/* $Id: semmutex-posix.cpp $ */ +/** @file + * IPRT - Mutex Semaphore, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/alloc.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/thread.h> +#include "internal/magics.h" +#include "internal/strict.h" + +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/time.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Posix internal representation of a Mutex semaphore. */ +struct RTSEMMUTEXINTERNAL +{ + /** pthread mutex. */ + pthread_mutex_t Mutex; + /** The owner of the mutex. */ + volatile pthread_t Owner; + /** Nesting count. */ + volatile uint32_t cNesting; + /** Magic value (RTSEMMUTEX_MAGIC). */ + uint32_t u32Magic; +#ifdef RTSEMMUTEX_STRICT + /** Lock validator record associated with this mutex. */ + RTLOCKVALRECEXCL ValidatorRec; +#endif +}; + +#if defined(RT_OS_DARWIN) || defined(RT_OS_NETBSD) +/** + * This function is a crude approximation of pthread_mutex_timedlock. + */ +int rtSemFallbackPthreadMutexTimedlock(pthread_mutex_t *mutex, RTMSINTERVAL cMillies) +{ + struct timespec ts; + int rc; + + rc = pthread_mutex_trylock(mutex); + if (rc != EBUSY) + return rc; + + ts.tv_sec = cMillies / 1000; + ts.tv_nsec = (cMillies % 1000) * 1000000; + + while (ts.tv_sec > 0 || ts.tv_nsec > 0) + { + struct timespec delta, remaining; + + if (ts.tv_sec > 0) + { + delta.tv_sec = 1; + delta.tv_nsec = 0; + ts.tv_sec--; + } + else + { + delta.tv_sec = 0; + delta.tv_nsec = ts.tv_nsec; + ts.tv_nsec = 0; + } + + nanosleep(&delta, &remaining); + + rc = pthread_mutex_trylock(mutex); + if (rc != EBUSY) + return rc; + + if (RT_UNLIKELY(remaining.tv_nsec > 0 || remaining.tv_sec > 0)) + { + ts.tv_sec += remaining.tv_sec; + ts.tv_nsec += remaining.tv_nsec; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + } + } + + return ETIMEDOUT; +} +#endif + + +#undef RTSemMutexCreate +RTDECL(int) RTSemMutexCreate(PRTSEMMUTEX phMutexSem) +{ + return RTSemMutexCreateEx(phMutexSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL); +} + + +RTDECL(int) RTSemMutexCreateEx(PRTSEMMUTEX phMutexSem, uint32_t fFlags, + RTLOCKVALCLASS hClass, uint32_t uSubClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMMUTEX_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Allocate semaphore handle. + */ + int rc; + struct RTSEMMUTEXINTERNAL *pThis = (struct RTSEMMUTEXINTERNAL *)RTMemAlloc(sizeof(struct RTSEMMUTEXINTERNAL)); + if (pThis) + { + /* + * Create the semaphore. + */ + rc = pthread_mutex_init(&pThis->Mutex, NULL); + if (!rc) + { + pThis->Owner = (pthread_t)-1; + pThis->cNesting = 0; + pThis->u32Magic = RTSEMMUTEX_MAGIC; +#ifdef RTSEMMUTEX_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iMutexAnon = 0; + RTLockValidatorRecExclInit(&pThis->ValidatorRec, hClass, uSubClass, pThis, + !(fFlags & RTSEMMUTEX_FLAGS_NO_LOCK_VAL), + "RTSemMutex-%u", ASMAtomicIncU32(&s_iMutexAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecExclInitV(&pThis->ValidatorRec, hClass, uSubClass, pThis, + !(fFlags & RTSEMMUTEX_FLAGS_NO_LOCK_VAL), pszNameFmt, va); + va_end(va); + } +#else + RT_NOREF_PV(hClass); RT_NOREF_PV(uSubClass); RT_NOREF_PV(pszNameFmt); +#endif + + *phMutexSem = pThis; + return VINF_SUCCESS; + } + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTSemMutexDestroy(RTSEMMUTEX hMutexSem) +{ + /* + * Validate input. + */ + if (hMutexSem == NIL_RTSEMMUTEX) + return VINF_SUCCESS; + struct RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + + /* + * Try destroy it. + */ + int rc = pthread_mutex_destroy(&pThis->Mutex); + if (rc) + { + AssertMsgFailed(("Failed to destroy mutex sem %p, rc=%d.\n", hMutexSem, rc)); + return RTErrConvertFromErrno(rc); + } + + /* + * Free the memory and be gone. + */ + ASMAtomicWriteU32(&pThis->u32Magic, RTSEMMUTEX_MAGIC_DEAD); + pThis->Owner = (pthread_t)-1; + pThis->cNesting = UINT32_MAX; +#ifdef RTSEMMUTEX_STRICT + RTLockValidatorRecExclDelete(&pThis->ValidatorRec); +#endif + RTMemTmpFree(pThis); + + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTSemMutexSetSubClass(RTSEMMUTEX hMutexSem, uint32_t uSubClass) +{ +#ifdef RTSEMMUTEX_STRICT + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID); + + return RTLockValidatorRecExclSetSubClass(&pThis->ValidatorRec, uSubClass); +#else + RT_NOREF_PV(hMutexSem); RT_NOREF_PV(uSubClass); + return RTLOCKVAL_SUB_CLASS_INVALID; +#endif +} + + +DECL_FORCE_INLINE(int) rtSemMutexRequest(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + + /* + * Check if nested request. + */ + pthread_t Self = pthread_self(); + if ( pThis->Owner == Self + && pThis->cNesting > 0) + { +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclRecursion(&pThis->ValidatorRec, pSrcPos); + if (RT_FAILURE(rc9)) + return rc9; +#endif + ASMAtomicIncU32(&pThis->cNesting); + return VINF_SUCCESS; + } + + /* + * Lock it. + */ + RTTHREAD hThreadSelf = NIL_RTTHREAD; + if (cMillies != 0) + { +#ifdef RTSEMMUTEX_STRICT + hThreadSelf = RTThreadSelfAutoAdopt(); + int rc9 = RTLockValidatorRecExclCheckOrderAndBlocking(&pThis->ValidatorRec, hThreadSelf, pSrcPos, true, + cMillies, RTTHREADSTATE_MUTEX, true); + if (RT_FAILURE(rc9)) + return rc9; +#else + hThreadSelf = RTThreadSelf(); + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_MUTEX, true); + RT_NOREF_PV(pSrcPos); +#endif + } + + if (cMillies == RT_INDEFINITE_WAIT) + { + /* take mutex */ + int rc = pthread_mutex_lock(&pThis->Mutex); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_MUTEX); + if (rc) + { + AssertMsgFailed(("Failed to lock mutex sem %p, rc=%d.\n", hMutexSem, rc)); NOREF(rc); + return RTErrConvertFromErrno(rc); + } + } + else + { + int rc; +#if !defined(RT_OS_DARWIN) && !defined(RT_OS_NETBSD) + struct timespec ts = {0,0}; +# if defined(RT_OS_HAIKU) + struct timeval tv = {0,0}; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; +# else + clock_gettime(CLOCK_REALTIME, &ts); +# endif + if (cMillies != 0) + { + ts.tv_nsec += (cMillies % 1000) * 1000000; + ts.tv_sec += cMillies / 1000; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + } + + /* take mutex */ + rc = pthread_mutex_timedlock(&pThis->Mutex, &ts); +#else + /* + * When there's no pthread_mutex_timedlock() use a crude sleep + * and retry approximation. Since the sleep interval is + * relative, we don't need to convert to the absolute time + * here only to convert back to relative in the fallback + * function. + */ + rc = rtSemFallbackPthreadMutexTimedlock(&pThis->Mutex, cMillies); +#endif + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_MUTEX); + if (rc) + { + AssertMsg(rc == ETIMEDOUT, ("Failed to lock mutex sem %p, rc=%d.\n", hMutexSem, rc)); NOREF(rc); + return RTErrConvertFromErrno(rc); + } + } + + /* + * Set the owner and nesting. + */ + pThis->Owner = Self; + ASMAtomicWriteU32(&pThis->cNesting, 1); +#ifdef RTSEMMUTEX_STRICT + RTLockValidatorRecExclSetOwner(&pThis->ValidatorRec, hThreadSelf, pSrcPos, true); +#endif + + return VINF_SUCCESS; +} + + +#undef RTSemMutexRequest +RTDECL(int) RTSemMutexRequest(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies) +{ +#ifndef RTSEMMUTEX_STRICT + return rtSemMutexRequest(hMutexSem, cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemMutexRequest(hMutexSem, cMillies, &SrcPos); +#endif +} + + +RTDECL(int) RTSemMutexRequestDebug(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemMutexRequest(hMutexSem, cMillies, &SrcPos); +} + + +#undef RTSemMutexRequestNoResume +RTDECL(int) RTSemMutexRequestNoResume(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies) +{ + /* (EINTR isn't returned by the wait functions we're using.) */ +#ifndef RTSEMMUTEX_STRICT + return rtSemMutexRequest(hMutexSem, cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemMutexRequest(hMutexSem, cMillies, &SrcPos); +#endif +} + + +RTDECL(int) RTSemMutexRequestNoResumeDebug(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemMutexRequest(hMutexSem, cMillies, &SrcPos); +} + + +RTDECL(int) RTSemMutexRelease(RTSEMMUTEX hMutexSem) +{ + /* + * Validate input. + */ + struct RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclReleaseOwner(&pThis->ValidatorRec, pThis->cNesting == 1); + if (RT_FAILURE(rc9)) + return rc9; +#endif + + /* + * Check if nested. + */ + pthread_t Self = pthread_self(); + if (RT_UNLIKELY( pThis->Owner != Self + || pThis->cNesting == 0)) + { + AssertMsgFailed(("Not owner of mutex %p!! Self=%08x Owner=%08x cNesting=%d\n", + pThis, Self, pThis->Owner, pThis->cNesting)); + return VERR_NOT_OWNER; + } + + /* + * If nested we'll just pop a nesting. + */ + if (pThis->cNesting > 1) + { + ASMAtomicDecU32(&pThis->cNesting); + return VINF_SUCCESS; + } + + /* + * Clear the state. (cNesting == 1) + */ + pThis->Owner = (pthread_t)-1; + ASMAtomicWriteU32(&pThis->cNesting, 0); + + /* + * Unlock mutex semaphore. + */ + int rc = pthread_mutex_unlock(&pThis->Mutex); + if (RT_UNLIKELY(rc)) + { + AssertMsgFailed(("Failed to unlock mutex sem %p, rc=%d.\n", hMutexSem, rc)); NOREF(rc); + return RTErrConvertFromErrno(rc); + } + + return VINF_SUCCESS; +} + + +RTDECL(bool) RTSemMutexIsOwned(RTSEMMUTEX hMutexSem) +{ + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, false); + + return pThis->Owner != (pthread_t)-1; +} + diff --git a/src/VBox/Runtime/r3/posix/semrw-posix.cpp b/src/VBox/Runtime/r3/posix/semrw-posix.cpp new file mode 100644 index 00000000..ab657566 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/semrw-posix.cpp @@ -0,0 +1,741 @@ +/* $Id: semrw-posix.cpp $ */ +/** @file + * IPRT - Read-Write Semaphore, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/mem.h> +#include <iprt/thread.h> + +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/time.h> + +#include "internal/magics.h" +#include "internal/strict.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @todo move this to r3/posix/something.h. */ +#ifdef RT_OS_SOLARIS +# define ATOMIC_GET_PTHREAD_T(ppvVar, pThread) ASMAtomicReadSize(ppvVar, pThread) +# define ATOMIC_SET_PTHREAD_T(ppvVar, pThread) ASMAtomicWriteSize(ppvVar, pThread) +#else +AssertCompileSize(pthread_t, sizeof(void *)); +# define ATOMIC_GET_PTHREAD_T(ppvVar, pThread) do { *(pThread) = (pthread_t)ASMAtomicReadPtr((void * volatile *)ppvVar); } while (0) +# define ATOMIC_SET_PTHREAD_T(ppvVar, pThread) ASMAtomicWritePtr((void * volatile *)ppvVar, (void *)pThread) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Posix internal representation of a read-write semaphore. */ +struct RTSEMRWINTERNAL +{ + /** The usual magic. (RTSEMRW_MAGIC) */ + uint32_t u32Magic; + /** The number of readers. + * (For preventing screwing up the lock on linux). */ + uint32_t volatile cReaders; + /** Number of write recursions. */ + uint32_t cWrites; + /** Number of read recursions by the writer. */ + uint32_t cWriterReads; + /** The write owner of the lock. */ + volatile pthread_t Writer; + /** pthread rwlock. */ + pthread_rwlock_t RWLock; +#ifdef RTSEMRW_STRICT + /** The validator record for the writer. */ + RTLOCKVALRECEXCL ValidatorWrite; + /** The validator record for the readers. */ + RTLOCKVALRECSHRD ValidatorRead; +#endif +}; + + + +#undef RTSemRWCreate +RTDECL(int) RTSemRWCreate(PRTSEMRW phRWSem) +{ + return RTSemRWCreateEx(phRWSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "RTSemRW"); +} + + +RTDECL(int) RTSemRWCreateEx(PRTSEMRW phRWSem, uint32_t fFlags, + RTLOCKVALCLASS hClass, uint32_t uSubClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMRW_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Allocate handle. + */ + int rc; + struct RTSEMRWINTERNAL *pThis = (struct RTSEMRWINTERNAL *)RTMemAlloc(sizeof(struct RTSEMRWINTERNAL)); + if (pThis) + { + /* + * Create the rwlock. + */ + rc = pthread_rwlock_init(&pThis->RWLock, NULL); + if (!rc) + { + pThis->u32Magic = RTSEMRW_MAGIC; + pThis->cReaders = 0; + pThis->cWrites = 0; + pThis->cWriterReads = 0; + pThis->Writer = (pthread_t)-1; +#ifdef RTSEMRW_STRICT + bool const fLVEnabled = !(fFlags & RTSEMRW_FLAGS_NO_LOCK_VAL); + if (!pszNameFmt) + { + static uint32_t volatile s_iSemRWAnon = 0; + uint32_t i = ASMAtomicIncU32(&s_iSemRWAnon) - 1; + RTLockValidatorRecExclInit(&pThis->ValidatorWrite, hClass, uSubClass, pThis, + fLVEnabled, "RTSemRW-%u", i); + RTLockValidatorRecSharedInit(&pThis->ValidatorRead, hClass, uSubClass, pThis, + false /*fSignaller*/, fLVEnabled, "RTSemRW-%u", i); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecExclInitV(&pThis->ValidatorWrite, hClass, uSubClass, pThis, + fLVEnabled, pszNameFmt, va); + va_end(va); + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(&pThis->ValidatorRead, hClass, uSubClass, pThis, + false /*fSignaller*/, fLVEnabled, pszNameFmt, va); + va_end(va); + } + RTLockValidatorRecMakeSiblings(&pThis->ValidatorWrite.Core, &pThis->ValidatorRead.Core); +#else + RT_NOREF_PV(hClass); RT_NOREF_PV(uSubClass); RT_NOREF_PV(pszNameFmt); +#endif + *phRWSem = pThis; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromErrno(rc); + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTSemRWDestroy(RTSEMRW hRWSem) +{ + /* + * Validate input, nil handle is fine. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + if (pThis == NIL_RTSEMRW) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + VERR_INVALID_HANDLE); + Assert(pThis->Writer == (pthread_t)-1); + Assert(!pThis->cReaders); + Assert(!pThis->cWrites); + Assert(!pThis->cWriterReads); + + /* + * Try destroy it. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTSEMRW_MAGIC, RTSEMRW_MAGIC), VERR_INVALID_HANDLE); + int rc = pthread_rwlock_destroy(&pThis->RWLock); + if (!rc) + { +#ifdef RTSEMRW_STRICT + RTLockValidatorRecSharedDelete(&pThis->ValidatorRead); + RTLockValidatorRecExclDelete(&pThis->ValidatorWrite); +#endif + RTMemFree(pThis); + rc = VINF_SUCCESS; + } + else + { + ASMAtomicWriteU32(&pThis->u32Magic, RTSEMRW_MAGIC); + AssertMsgFailed(("Failed to destroy read-write sem %p, rc=%d.\n", hRWSem, rc)); + rc = RTErrConvertFromErrno(rc); + } + + return rc; +} + + +RTDECL(uint32_t) RTSemRWSetSubClass(RTSEMRW hRWSem, uint32_t uSubClass) +{ +#ifdef RTSEMRW_STRICT + /* + * Validate handle. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn(pThis->u32Magic == RTSEMRW_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID); + + RTLockValidatorRecSharedSetSubClass(&pThis->ValidatorRead, uSubClass); + return RTLockValidatorRecExclSetSubClass(&pThis->ValidatorWrite, uSubClass); +#else + RT_NOREF_PV(hRWSem); RT_NOREF_PV(uSubClass); + return RTLOCKVAL_SUB_CLASS_INVALID; +#endif +} + + +DECL_FORCE_INLINE(int) rtSemRWRequestRead(RTSEMRW hRWSem, RTMSINTERVAL cMillies, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + VERR_INVALID_HANDLE); + + /* + * Check if it's the writer (implement write+read recursion). + */ + pthread_t Self = pthread_self(); + pthread_t Writer; + ATOMIC_GET_PTHREAD_T(&pThis->Writer, &Writer); + if (Writer == Self) + { +#ifdef RTSEMRW_STRICT + int rc9 = RTLockValidatorRecExclRecursionMixed(&pThis->ValidatorWrite, &pThis->ValidatorRead.Core, pSrcPos); + if (RT_FAILURE(rc9)) + return rc9; +#endif + Assert(pThis->cWriterReads < INT32_MAX); + pThis->cWriterReads++; + return VINF_SUCCESS; + } + + /* + * Try lock it. + */ + RTTHREAD hThreadSelf = NIL_RTTHREAD; + if (cMillies > 0) + { +#ifdef RTSEMRW_STRICT + hThreadSelf = RTThreadSelfAutoAdopt(); + int rc9 = RTLockValidatorRecSharedCheckOrderAndBlocking(&pThis->ValidatorRead, hThreadSelf, pSrcPos, true, + cMillies, RTTHREADSTATE_RW_READ, true); + if (RT_FAILURE(rc9)) + return rc9; +#else + hThreadSelf = RTThreadSelf(); + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_RW_READ, true); + RT_NOREF_PV(pSrcPos); +#endif + } + + if (cMillies == RT_INDEFINITE_WAIT) + { + /* take rwlock */ + int rc = pthread_rwlock_rdlock(&pThis->RWLock); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_RW_READ); + if (rc) + { + AssertMsgFailed(("Failed read lock read-write sem %p, rc=%d.\n", hRWSem, rc)); + return RTErrConvertFromErrno(rc); + } + } + else + { +#ifdef RT_OS_DARWIN + AssertMsgFailed(("Not implemented on Darwin yet because of incomplete pthreads API.")); + return VERR_NOT_IMPLEMENTED; + +#else /* !RT_OS_DARWIN */ + /* + * Get current time and calc end of wait time. + */ + struct timespec ts = {0,0}; + clock_gettime(CLOCK_REALTIME, &ts); + if (cMillies != 0) + { + ts.tv_nsec += (cMillies % 1000) * 1000000; + ts.tv_sec += cMillies / 1000; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + } + + /* take rwlock */ + int rc = pthread_rwlock_timedrdlock(&pThis->RWLock, &ts); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_RW_READ); + if (rc) + { + AssertMsg(rc == ETIMEDOUT, ("Failed read lock read-write sem %p, rc=%d.\n", hRWSem, rc)); + return RTErrConvertFromErrno(rc); + } +#endif /* !RT_OS_DARWIN */ + } + + ASMAtomicIncU32(&pThis->cReaders); +#ifdef RTSEMRW_STRICT + RTLockValidatorRecSharedAddOwner(&pThis->ValidatorRead, hThreadSelf, pSrcPos); +#endif + return VINF_SUCCESS; +} + + +#undef RTSemRWRequestRead +RTDECL(int) RTSemRWRequestRead(RTSEMRW hRWSem, RTMSINTERVAL cMillies) +{ +#ifndef RTSEMRW_STRICT + return rtSemRWRequestRead(hRWSem, cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemRWRequestRead(hRWSem, cMillies, &SrcPos); +#endif +} + + +RTDECL(int) RTSemRWRequestReadDebug(RTSEMRW hRWSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemRWRequestRead(hRWSem, cMillies, &SrcPos); +} + + +#undef RTSemRWRequestReadNoResume +RTDECL(int) RTSemRWRequestReadNoResume(RTSEMRW hRWSem, RTMSINTERVAL cMillies) +{ + /* EINTR isn't returned by the wait functions we're using. */ +#ifndef RTSEMRW_STRICT + return rtSemRWRequestRead(hRWSem, cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemRWRequestRead(hRWSem, cMillies, &SrcPos); +#endif +} + + +RTDECL(int) RTSemRWRequestReadNoResumeDebug(RTSEMRW hRWSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemRWRequestRead(hRWSem, cMillies, &SrcPos); +} + + +RTDECL(int) RTSemRWReleaseRead(RTSEMRW hRWSem) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + VERR_INVALID_HANDLE); + + /* + * Check if it's the writer. + */ + pthread_t Self = pthread_self(); + pthread_t Writer; + ATOMIC_GET_PTHREAD_T(&pThis->Writer, &Writer); + if (Writer == Self) + { + AssertMsgReturn(pThis->cWriterReads > 0, ("pThis=%p\n", pThis), VERR_NOT_OWNER); +#ifdef RTSEMRW_STRICT + int rc9 = RTLockValidatorRecExclUnwindMixed(&pThis->ValidatorWrite, &pThis->ValidatorRead.Core); + if (RT_FAILURE(rc9)) + return rc9; +#endif + pThis->cWriterReads--; + return VINF_SUCCESS; + } + + /* + * Try unlock it. + */ +#ifdef RTSEMRW_STRICT + int rc9 = RTLockValidatorRecSharedCheckAndRelease(&pThis->ValidatorRead, RTThreadSelf()); + if (RT_FAILURE(rc9)) + return rc9; +#endif +#ifdef RT_OS_LINUX /* glibc (at least 2.8) may screw up when unlocking a lock we don't own. */ + if (ASMAtomicReadU32(&pThis->cReaders) == 0) + { + AssertMsgFailed(("Not owner of %p\n", pThis)); + return VERR_NOT_OWNER; + } +#endif + ASMAtomicDecU32(&pThis->cReaders); + int rc = pthread_rwlock_unlock(&pThis->RWLock); + if (rc) + { + ASMAtomicIncU32(&pThis->cReaders); + AssertMsgFailed(("Failed read unlock read-write sem %p, rc=%d.\n", hRWSem, rc)); + return RTErrConvertFromErrno(rc); + } + return VINF_SUCCESS; +} + + +DECL_FORCE_INLINE(int) rtSemRWRequestWrite(RTSEMRW hRWSem, RTMSINTERVAL cMillies, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + VERR_INVALID_HANDLE); + + /* + * Recursion? + */ + pthread_t Self = pthread_self(); + pthread_t Writer; + ATOMIC_GET_PTHREAD_T(&pThis->Writer, &Writer); + if (Writer == Self) + { +#ifdef RTSEMRW_STRICT + int rc9 = RTLockValidatorRecExclRecursion(&pThis->ValidatorWrite, pSrcPos); + if (RT_FAILURE(rc9)) + return rc9; +#endif + Assert(pThis->cWrites < INT32_MAX); + pThis->cWrites++; + return VINF_SUCCESS; + } + + /* + * Try lock it. + */ + RTTHREAD hThreadSelf = NIL_RTTHREAD; + if (cMillies) + { +#ifdef RTSEMRW_STRICT + hThreadSelf = RTThreadSelfAutoAdopt(); + int rc9 = RTLockValidatorRecExclCheckOrderAndBlocking(&pThis->ValidatorWrite, hThreadSelf, pSrcPos, true, + cMillies, RTTHREADSTATE_RW_WRITE, true); + if (RT_FAILURE(rc9)) + return rc9; +#else + hThreadSelf = RTThreadSelf(); + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_RW_WRITE, true); + RT_NOREF_PV(pSrcPos); +#endif + } + + if (cMillies == RT_INDEFINITE_WAIT) + { + /* take rwlock */ + int rc = pthread_rwlock_wrlock(&pThis->RWLock); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_RW_WRITE); + if (rc) + { + AssertMsgFailed(("Failed write lock read-write sem %p, rc=%d.\n", hRWSem, rc)); + return RTErrConvertFromErrno(rc); + } + } + else + { +#ifdef RT_OS_DARWIN + AssertMsgFailed(("Not implemented on Darwin yet because of incomplete pthreads API.")); + return VERR_NOT_IMPLEMENTED; +#else /* !RT_OS_DARWIN */ + /* + * Get current time and calc end of wait time. + */ + struct timespec ts = {0,0}; + clock_gettime(CLOCK_REALTIME, &ts); + if (cMillies != 0) + { + ts.tv_nsec += (cMillies % 1000) * 1000000; + ts.tv_sec += cMillies / 1000; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + } + + /* take rwlock */ + int rc = pthread_rwlock_timedwrlock(&pThis->RWLock, &ts); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_RW_WRITE); + if (rc) + { + AssertMsg(rc == ETIMEDOUT, ("Failed read lock read-write sem %p, rc=%d.\n", hRWSem, rc)); + return RTErrConvertFromErrno(rc); + } +#endif /* !RT_OS_DARWIN */ + } + + ATOMIC_SET_PTHREAD_T(&pThis->Writer, Self); + pThis->cWrites = 1; + Assert(!pThis->cReaders); +#ifdef RTSEMRW_STRICT + RTLockValidatorRecExclSetOwner(&pThis->ValidatorWrite, hThreadSelf, pSrcPos, true); +#endif + return VINF_SUCCESS; +} + + +#undef RTSemRWRequestWrite +RTDECL(int) RTSemRWRequestWrite(RTSEMRW hRWSem, RTMSINTERVAL cMillies) +{ +#ifndef RTSEMRW_STRICT + return rtSemRWRequestWrite(hRWSem, cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemRWRequestWrite(hRWSem, cMillies, &SrcPos); +#endif +} + + +RTDECL(int) RTSemRWRequestWriteDebug(RTSEMRW hRWSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemRWRequestWrite(hRWSem, cMillies, &SrcPos); +} + + +#undef RTSemRWRequestWriteNoResume +RTDECL(int) RTSemRWRequestWriteNoResume(RTSEMRW hRWSem, RTMSINTERVAL cMillies) +{ + /* EINTR isn't returned by the wait functions we're using. */ +#ifndef RTSEMRW_STRICT + return rtSemRWRequestWrite(hRWSem, cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemRWRequestWrite(hRWSem, cMillies, &SrcPos); +#endif +} + + +RTDECL(int) RTSemRWRequestWriteNoResumeDebug(RTSEMRW hRWSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + /* EINTR isn't returned by the wait functions we're using. */ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemRWRequestWrite(hRWSem, cMillies, &SrcPos); +} + + +RTDECL(int) RTSemRWReleaseWrite(RTSEMRW hRWSem) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + VERR_INVALID_HANDLE); + + /* + * Verify ownership and implement recursion. + */ + pthread_t Self = pthread_self(); + pthread_t Writer; + ATOMIC_GET_PTHREAD_T(&pThis->Writer, &Writer); + AssertMsgReturn(Writer == Self, ("pThis=%p\n", pThis), VERR_NOT_OWNER); + AssertReturn(pThis->cWriterReads == 0 || pThis->cWrites > 1, VERR_WRONG_ORDER); + + if (pThis->cWrites > 1) + { +#ifdef RTSEMRW_STRICT + int rc9 = RTLockValidatorRecExclUnwind(&pThis->ValidatorWrite); + if (RT_FAILURE(rc9)) + return rc9; +#endif + pThis->cWrites--; + return VINF_SUCCESS; + } + + /* + * Try unlock it. + */ +#ifdef RTSEMRW_STRICT + int rc9 = RTLockValidatorRecExclReleaseOwner(&pThis->ValidatorWrite, true); + if (RT_FAILURE(rc9)) + return rc9; +#endif + + pThis->cWrites--; + ATOMIC_SET_PTHREAD_T(&pThis->Writer, (pthread_t)-1); + int rc = pthread_rwlock_unlock(&pThis->RWLock); + if (rc) + { + AssertMsgFailed(("Failed write unlock read-write sem %p, rc=%d.\n", hRWSem, rc)); + return RTErrConvertFromErrno(rc); + } + + return VINF_SUCCESS; +} + + +RTDECL(bool) RTSemRWIsWriteOwner(RTSEMRW hRWSem) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, false); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + false); + + /* + * Check ownership. + */ + pthread_t Self = pthread_self(); + pthread_t Writer; + ATOMIC_GET_PTHREAD_T(&pThis->Writer, &Writer); + return Writer == Self; +} + + +RTDECL(bool) RTSemRWIsReadOwner(RTSEMRW hRWSem, bool fWannaHear) +{ + /* + * Validate handle. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTSEMRW_MAGIC, false); + + /* + * Check write ownership. The writer is also a valid reader. + */ + pthread_t Self = pthread_self(); + pthread_t Writer; + ATOMIC_GET_PTHREAD_T(&pThis->Writer, &Writer); + if (Writer == Self) + return true; + if (Writer != (pthread_t)-1) + return false; + + /* + * If there are no readers, we cannot be one of them, can we? + */ + if (ASMAtomicReadU32(&pThis->cReaders) == 0) + return false; + +#ifdef RTSEMRW_STRICT + /* + * Ask the lock validator. + */ + NOREF(fWannaHear); + return RTLockValidatorRecSharedIsOwner(&pThis->ValidatorRead, NIL_RTTHREAD); +#else + /* + * Just tell the caller what he want to hear. + */ + return fWannaHear; +#endif +} +RT_EXPORT_SYMBOL(RTSemRWIsReadOwner); + + +RTDECL(uint32_t) RTSemRWGetWriteRecursion(RTSEMRW hRWSem) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, 0); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + 0); + + /* + * Return the requested data. + */ + return pThis->cWrites; +} + + +RTDECL(uint32_t) RTSemRWGetWriterReadRecursion(RTSEMRW hRWSem) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, 0); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + 0); + + /* + * Return the requested data. + */ + return pThis->cWriterReads; +} + + +RTDECL(uint32_t) RTSemRWGetReadCount(RTSEMRW hRWSem) +{ + /* + * Validate input. + */ + struct RTSEMRWINTERNAL *pThis = hRWSem; + AssertPtrReturn(pThis, 0); + AssertMsgReturn(pThis->u32Magic == RTSEMRW_MAGIC, + ("pThis=%p u32Magic=%#x\n", pThis, pThis->u32Magic), + 0); + + /* + * Return the requested data. + */ + return pThis->cReaders; +} + diff --git a/src/VBox/Runtime/r3/posix/semwait.h b/src/VBox/Runtime/r3/posix/semwait.h new file mode 100644 index 00000000..ade8c4a7 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/semwait.h @@ -0,0 +1,162 @@ +/* $Id: semwait.h $ */ +/** @file + * IPRT - Common semaphore wait code. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_posix_semwait_h +#define IPRT_INCLUDED_SRC_r3_posix_semwait_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +/** @def IPRT_HAVE_PTHREAD_CONDATTR_SETCLOCK + * Set if the platform implements pthread_condattr_setclock(). + * Enables the use of the monotonic clock for waiting on condition variables. */ +#ifndef IPRT_HAVE_PTHREAD_CONDATTR_SETCLOCK +/* Linux detection */ +# if defined(RT_OS_LINUX) && defined(__USE_XOPEN2K) +# include <features.h> +# if __GLIBC_PREREQ(2,6) /** @todo figure the exact version where this was added */ +# define IPRT_HAVE_PTHREAD_CONDATTR_SETCLOCK +# endif +# endif +/** @todo check other platforms */ +#endif + + +/** + * Converts a extended wait timeout specification to an absolute timespec and a + * relative nanosecond count. + * + * @note This does not check for RTSEMWAIT_FLAGS_INDEFINITE, caller should've + * done that already. + * + * @returns The relative wait in nanoseconds. 0 for a poll call, UINT64_MAX for + * an effectively indefinite wait. + * @param fFlags RTSEMWAIT_FLAGS_XXX. + * @param fMonotonicClock Whether the timeout is in monotonic (true) or real + * (false) time. + * @param uTimeout The timeout. + * @param pAbsDeadline Where to return the absolute deadline. + */ +DECLINLINE(uint64_t) rtSemPosixCalcDeadline(uint32_t fFlags, uint64_t uTimeout, bool fMonotonicClock, + struct timespec *pAbsDeadline) +{ + Assert(!(fFlags & RTSEMWAIT_FLAGS_INDEFINITE)); + + /* + * Convert uTimeout to a relative value in nanoseconds. + */ + if (fFlags & RTSEMWAIT_FLAGS_MILLISECS) + { + if (uTimeout < UINT64_MAX / RT_NS_1MS) + uTimeout = uTimeout * RT_NS_1MS; + else + return UINT64_MAX; + } + else if (uTimeout == UINT64_MAX) /* unofficial way of indicating an indefinite wait */ + return UINT64_MAX; + + /* + * Make uTimeout relative and check for polling (zero timeout) calls. + */ + uint64_t uAbsTimeout = uTimeout; + if (fFlags & RTSEMWAIT_FLAGS_ABSOLUTE) + { + uint64_t const u64Now = RTTimeSystemNanoTS(); + if (uTimeout > u64Now) + uTimeout -= u64Now; + else + return 0; + } + else if (uTimeout == 0) + return 0; + + /* + * Calculate the deadline according to the clock we're using. + */ + if (!fMonotonicClock) + { +#if defined(RT_OS_DARWIN) || defined(RT_OS_HAIKU) + struct timeval tv = {0,0}; + gettimeofday(&tv, NULL); + pAbsDeadline->tv_sec = tv.tv_sec; + pAbsDeadline->tv_nsec = tv.tv_usec * 1000; +#else + clock_gettime(CLOCK_REALTIME, pAbsDeadline); +#endif + struct timespec TsAdd; + TsAdd.tv_nsec = uTimeout % RT_NS_1SEC; + TsAdd.tv_sec = uTimeout / RT_NS_1SEC; + + /* Check for 32-bit tv_sec overflows: */ + if ( sizeof(pAbsDeadline->tv_sec) < sizeof(uint64_t) + && ( uTimeout >= (uint64_t)RT_NS_1SEC * UINT32_MAX + || (uint64_t)pAbsDeadline->tv_sec + pAbsDeadline->tv_sec >= UINT32_MAX) ) + return UINT64_MAX; + + pAbsDeadline->tv_sec += TsAdd.tv_sec; + pAbsDeadline->tv_nsec += TsAdd.tv_nsec; + if ((uint32_t)pAbsDeadline->tv_nsec >= RT_NS_1SEC) + { + pAbsDeadline->tv_nsec -= RT_NS_1SEC; + pAbsDeadline->tv_sec++; + } + } + else + { + /* ASSUMES RTTimeSystemNanoTS() == RTTimeNanoTS() == clock_gettime(CLOCK_MONOTONIC). */ + if (fFlags & RTSEMWAIT_FLAGS_RELATIVE) + { + uint64_t const nsNow = RTTimeSystemNanoTS(); + uAbsTimeout += nsNow; + if (uAbsTimeout < nsNow) + return UINT64_MAX; + } + + /* Check for 32-bit tv_sec overflows: */ + if ( sizeof(pAbsDeadline->tv_sec) < sizeof(uint64_t) + && uAbsTimeout >= (uint64_t)RT_NS_1SEC * UINT32_MAX) + return UINT64_MAX; + + pAbsDeadline->tv_nsec = uAbsTimeout % RT_NS_1SEC; + pAbsDeadline->tv_sec = uAbsTimeout / RT_NS_1SEC; + } + + return uTimeout; +} + +#endif /* !IPRT_INCLUDED_SRC_r3_posix_semwait_h */ + diff --git a/src/VBox/Runtime/r3/posix/serialport-posix.cpp b/src/VBox/Runtime/r3/posix/serialport-posix.cpp new file mode 100644 index 00000000..0afbb676 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/serialport-posix.cpp @@ -0,0 +1,1269 @@ +/* $Id: serialport-posix.cpp $ */ +/** @file + * IPRT - Serial Port API, POSIX Implementation. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/serialport.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/cdefs.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include "internal/magics.h" + +#include <errno.h> +#ifdef RT_OS_SOLARIS +# include <sys/termios.h> +#else +# include <termios.h> +#endif +#include <sys/types.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#ifdef RT_OS_DARWIN +# include <sys/poll.h> +#else +# include <sys/poll.h> +#endif +#include <sys/ioctl.h> +#include <pthread.h> + +#ifdef RT_OS_LINUX +/* + * TIOCM_LOOP is not defined in the above header files for some reason but in asm/termios.h. + * But inclusion of this file however leads to compilation errors because of redefinition of some + * structs. That's why it is defined here until a better solution is found. + */ +# ifndef TIOCM_LOOP +# define TIOCM_LOOP 0x8000 +# endif +/* For linux custom baudrate code we also need serial_struct */ +# include <linux/serial.h> +#endif /* linux */ + +/** Define fallback if not supported. */ +#if !defined(CMSPAR) +# define CMSPAR 0 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal serial port state. + */ +typedef struct RTSERIALPORTINTERNAL +{ + /** Magic value (RTSERIALPORT_MAGIC). */ + uint32_t u32Magic; + /** Flags given while opening the serial port. */ + uint32_t fOpenFlags; + /** The file descriptor of the serial port. */ + int iFd; + /** The status line monitor thread if enabled. */ + RTTHREAD hMonThrd; + /** Flag whether the monitoring thread should shutdown. */ + volatile bool fMonThrdShutdown; + /** Reading end of wakeup pipe. */ + int iFdPipeR; + /** Writing end of wakeup pipe. */ + int iFdPipeW; + /** Event pending mask. */ + volatile uint32_t fEvtsPending; + /** Flag whether we are in blocking or non blocking mode. */ + bool fBlocking; + /** The current active config (we assume no one changes this behind our back). */ + struct termios PortCfg; + /** Flag whether a custom baud rate was chosen (for hosts supporting this.). */ + bool fBaudrateCust; + /** The custom baud rate. */ + uint32_t uBaudRateCust; +} RTSERIALPORTINTERNAL; +/** Pointer to the internal serial port state. */ +typedef RTSERIALPORTINTERNAL *PRTSERIALPORTINTERNAL; + + +/** + * Baud rate conversion table descriptor. + */ +typedef struct RTSERIALPORTBRATECONVDESC +{ + /** The platform independent baud rate used by the RTSerialPort* API. */ + uint32_t uBaudRateCfg; + /** The speed identifier used in the termios structure. */ + speed_t iSpeedTermios; +} RTSERIALPORTBRATECONVDESC; +/** Pointer to a baud rate converions table descriptor. */ +typedef RTSERIALPORTBRATECONVDESC *PRTSERIALPORTBRATECONVDESC; +/** Pointer to a const baud rate conversion table descriptor. */ +typedef const RTSERIALPORTBRATECONVDESC *PCRTSERIALPORTBRATECONVDESC; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** The event poller was woken up due to an external interrupt. */ +#define RTSERIALPORT_WAKEUP_PIPE_REASON_INTERRUPT 0x0 +/** The event poller was woken up due to a change in the monitored status lines. */ +#define RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED 0x1 +/** The monitor thread encoutnered repeating errors querying the status lines and terminated. */ +#define RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_MONITOR_FAILED 0x2 + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +/** The baud rate conversion table. */ +static const RTSERIALPORTBRATECONVDESC s_rtSerialPortBaudrateConv[] = +{ + { 50, B50 }, + { 75, B75 }, + { 110, B110 }, + { 134, B134 }, + { 150, B150 }, + { 200, B200 }, + { 300, B300 }, + { 600, B600 }, + { 1200, B1200 }, + { 1800, B1800 }, + { 2400, B2400 }, + { 4800, B4800 }, + { 9600, B9600 }, + { 19200, B19200 }, + { 38400, B38400 }, + { 57600, B57600 }, + { 115200, B115200 } +}; + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Converts the given termios speed identifier to the baud rate used in the API. + * + * @returns Baud rate or 0 if not a standard baud rate + */ +DECLINLINE(uint32_t) rtSerialPortGetBaudrateFromTermiosSpeed(speed_t enmSpeed) +{ + for (unsigned i = 0; i < RT_ELEMENTS(s_rtSerialPortBaudrateConv); i++) + { + if (s_rtSerialPortBaudrateConv[i].iSpeedTermios == enmSpeed) + return s_rtSerialPortBaudrateConv[i].uBaudRateCfg; + } + + return 0; +} + + +/** + * Converts the given baud rate to proper termios speed identifier. + * + * @returns Speed identifier if available or B0 if no matching speed for the baud rate + * could be found. + * @param uBaudRate The baud rate to convert. + * @param pfBaudrateCust Where to store the flag whether a custom baudrate was selected. + */ +DECLINLINE(speed_t) rtSerialPortGetTermiosSpeedFromBaudrate(uint32_t uBaudRate, bool *pfBaudrateCust) +{ + *pfBaudrateCust = false; + + for (unsigned i = 0; i < RT_ELEMENTS(s_rtSerialPortBaudrateConv); i++) + { + if (s_rtSerialPortBaudrateConv[i].uBaudRateCfg == uBaudRate) + return s_rtSerialPortBaudrateConv[i].iSpeedTermios; + } + +#ifdef RT_OS_LINUX + *pfBaudrateCust = true; + return B38400; +#else + return B0; +#endif +} + + +/** + * Tries to set the default config on the given serial port. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + */ +static int rtSerialPortSetDefaultCfg(PRTSERIALPORTINTERNAL pThis) +{ + pThis->fBaudrateCust = false; + pThis->uBaudRateCust = 0; + pThis->PortCfg.c_iflag = INPCK; /* Input parity checking. */ + cfsetispeed(&pThis->PortCfg, B9600); + cfsetospeed(&pThis->PortCfg, B9600); + pThis->PortCfg.c_cflag |= CS8 | CLOCAL; /* 8 data bits, ignore modem control lines. */ + if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_READ) + pThis->PortCfg.c_cflag |= CREAD; /* Enable receiver. */ + + /* Set to raw input mode. */ + pThis->PortCfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ECHOK | ISIG | IEXTEN); + pThis->PortCfg.c_cc[VMIN] = 0; /* Achieve non-blocking behavior. */ + pThis->PortCfg.c_cc[VTIME] = 0; + + int rc = VINF_SUCCESS; + int rcPsx = tcflush(pThis->iFd, TCIOFLUSH); + if (!rcPsx) + { + rcPsx = tcsetattr(pThis->iFd, TCSANOW, &pThis->PortCfg); + if (rcPsx == -1) + rc = RTErrConvertFromErrno(errno); + + if (RT_SUCCESS(rc)) + { +#ifdef RT_OS_LINUX + if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_ENABLE_LOOPBACK) + { + int fTiocmSet = TIOCM_LOOP; + rcPsx = ioctl(pThis->iFd, TIOCMBIS, &fTiocmSet); + if (rcPsx == -1) + rc = RTErrConvertFromErrno(errno); + } + else + { + /* Make sure it is clear. */ + int fTiocmClear = TIOCM_LOOP; + rcPsx = ioctl(pThis->iFd, TIOCMBIC, &fTiocmClear); + if (rcPsx == -1 && errno != EINVAL) /* Pseudo terminals don't support loopback mode so ignore an error here. */ + rc = RTErrConvertFromErrno(errno); + } +#else + if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_ENABLE_LOOPBACK) + return VERR_NOT_SUPPORTED; +#endif + } + } + else + rc = RTErrConvertFromErrno(errno); + + return rc; +} + + +/** + * Converts the given serial port config to the appropriate termios counterpart. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param pCfg Pointer to the serial port config descriptor. + * @param pTermios Pointer to the termios structure to fill. + * @param pfBaudrateCust Where to store the flag whether a custom baudrate was selected. + * @param pErrInfo Additional error to be set when the conversion fails. + */ +static int rtSerialPortCfg2Termios(PRTSERIALPORTINTERNAL pThis, PCRTSERIALPORTCFG pCfg, struct termios *pTermios, + bool *pfBaudrateCust, PRTERRINFO pErrInfo) +{ + RT_NOREF(pErrInfo); /** @todo Make use of the error info. */ + speed_t enmSpeed = rtSerialPortGetTermiosSpeedFromBaudrate(pCfg->uBaudRate, pfBaudrateCust); + if (enmSpeed != B0) + { + tcflag_t const fCFlagMask = (CS5 | CS6 | CS7 | CS8 | CSTOPB | PARENB | PARODD | CMSPAR); + tcflag_t fCFlagNew = CLOCAL; + + switch (pCfg->enmDataBitCount) + { + case RTSERIALPORTDATABITS_5BITS: + fCFlagNew |= CS5; + break; + case RTSERIALPORTDATABITS_6BITS: + fCFlagNew |= CS6; + break; + case RTSERIALPORTDATABITS_7BITS: + fCFlagNew |= CS7; + break; + case RTSERIALPORTDATABITS_8BITS: + fCFlagNew |= CS8; + break; + default: + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + switch (pCfg->enmParity) + { + case RTSERIALPORTPARITY_NONE: + break; + case RTSERIALPORTPARITY_EVEN: + fCFlagNew |= PARENB; + break; + case RTSERIALPORTPARITY_ODD: + fCFlagNew |= PARENB | PARODD; + break; +#if CMSPAR != 0 + case RTSERIALPORTPARITY_MARK: + fCFlagNew |= PARENB | CMSPAR | PARODD; + break; + case RTSERIALPORTPARITY_SPACE: + fCFlagNew |= PARENB | CMSPAR; + break; +#else + case RTSERIALPORTPARITY_MARK: + case RTSERIALPORTPARITY_SPACE: + return VERR_NOT_SUPPORTED; +#endif + default: + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + switch (pCfg->enmStopBitCount) + { + case RTSERIALPORTSTOPBITS_ONE: + break; + case RTSERIALPORTSTOPBITS_ONEPOINTFIVE: + if (pCfg->enmDataBitCount == RTSERIALPORTDATABITS_5BITS) + fCFlagNew |= CSTOPB; + else + return VERR_NOT_SUPPORTED; + break; + case RTSERIALPORTSTOPBITS_TWO: + if (pCfg->enmDataBitCount != RTSERIALPORTDATABITS_5BITS) + fCFlagNew |= CSTOPB; + else + return VERR_NOT_SUPPORTED; + break; + default: + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + /* Assign new flags. */ + if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_READ) + pTermios->c_cflag |= CREAD; /* Enable receiver. */ + pTermios->c_cflag = (pTermios->c_cflag & ~fCFlagMask) | fCFlagNew; + pTermios->c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ECHOK | ISIG | IEXTEN); + pTermios->c_iflag = INPCK; /* Input parity checking. */ + pTermios->c_cc[VMIN] = 0; /* Achieve non-blocking behavior. */ + pTermios->c_cc[VTIME] = 0; + cfsetispeed(pTermios, enmSpeed); + cfsetospeed(pTermios, enmSpeed); + } + else + return VERR_SERIALPORT_INVALID_BAUDRATE; + + return VINF_SUCCESS; +} + + +/** + * Converts the given termios structure to an appropriate serial port config. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param pTermios The termios structure to convert. + * @param pCfg The serial port config to fill in. + */ +static int rtSerialPortTermios2Cfg(PRTSERIALPORTINTERNAL pThis, struct termios *pTermios, PRTSERIALPORTCFG pCfg) +{ + int rc = VINF_SUCCESS; + bool f5DataBits = false; + speed_t enmSpeedIn = cfgetispeed(pTermios); + Assert(enmSpeedIn == cfgetospeed(pTermios)); /* Should always be the same. */ + + if (!pThis->fBaudrateCust) + { + pCfg->uBaudRate = rtSerialPortGetBaudrateFromTermiosSpeed(enmSpeedIn); + if (!pCfg->uBaudRate) + rc = VERR_SERIALPORT_INVALID_BAUDRATE; + } + else + pCfg->uBaudRate = pThis->uBaudRateCust; + + switch (pTermios->c_cflag & CSIZE) + { + case CS5: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_5BITS; + f5DataBits = true; + break; + case CS6: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_6BITS; + break; + case CS7: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_7BITS; + break; + case CS8: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_8BITS; + break; + default: + AssertFailed(); /* Should not happen. */ + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_INVALID; + rc = RT_FAILURE(rc) ? rc : VERR_INVALID_PARAMETER; + } + + /* Convert parity. */ + if (pTermios->c_cflag & PARENB) + { + /* + * CMSPAR is not supported on all systems, especially OS X. As configuring + * mark/space parity there is not supported and we start from a known config + * when opening the serial port it is not required to check for this here. + */ +#if CMSPAR == 0 + bool fCmsParSet = RT_BOOL(pTermios->c_cflag & CMSPAR); +#else + bool fCmsParSet = false; +#endif + if (pTermios->c_cflag & PARODD) + pCfg->enmParity = fCmsParSet ? RTSERIALPORTPARITY_MARK : RTSERIALPORTPARITY_ODD; + else + pCfg->enmParity = fCmsParSet ? RTSERIALPORTPARITY_SPACE: RTSERIALPORTPARITY_EVEN; + } + else + pCfg->enmParity = RTSERIALPORTPARITY_NONE; + + /* + * 1.5 stop bits are used with a data count of 5 bits when a UART derived from the 8250 + * is used. + */ + if (pTermios->c_cflag & CSTOPB) + pCfg->enmStopBitCount = f5DataBits ? RTSERIALPORTSTOPBITS_ONEPOINTFIVE : RTSERIALPORTSTOPBITS_TWO; + else + pCfg->enmStopBitCount = RTSERIALPORTSTOPBITS_ONE; + + return rc; +} + + +/** + * Wakes up any thread polling for a serial port event with the given reason. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param bWakeupReason The wakeup reason to pass to the event poller. + */ +DECLINLINE(int) rtSerialPortWakeupEvtPoller(PRTSERIALPORTINTERNAL pThis, uint8_t bWakeupReason) +{ + int rcPsx = write(pThis->iFdPipeW, &bWakeupReason, 1); + if (rcPsx != 1) + return RTErrConvertFromErrno(errno); + + return VINF_SUCCESS; +} + + +/** + * The status line monitor thread worker. + * + * @returns IPRT status code. + * @param ThreadSelf Thread handle to this thread. + * @param pvUser User argument. + */ +static DECLCALLBACK(int) rtSerialPortStsLineMonitorThrd(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + PRTSERIALPORTINTERNAL pThis = (PRTSERIALPORTINTERNAL)pvUser; + unsigned long const fStsLinesChk = TIOCM_CAR | TIOCM_RNG | TIOCM_DSR | TIOCM_CTS; + int rc = VINF_SUCCESS; + uint32_t fStsLinesOld = 0; + uint32_t cStsLineGetErrors = 0; +#ifdef RT_OS_LINUX + bool fPoll = false; +#endif + + RTThreadUserSignal(hThreadSelf); + + int rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLinesOld); + if (rcPsx == -1) + { + ASMAtomicXchgBool(&pThis->fMonThrdShutdown, true); + return RTErrConvertFromErrno(errno); + } + + while ( !pThis->fMonThrdShutdown + && RT_SUCCESS(rc)) + { +# ifdef RT_OS_LINUX + /* + * Wait for status line change. + * + * XXX In Linux, if a thread calls tcsetattr while the monitor thread is + * waiting in ioctl for a modem status change then 8250.c wrongly disables + * modem irqs and so the monitor thread never gets released. The workaround + * is to send a signal after each tcsetattr. + * + * TIOCMIWAIT doesn't work for the DSR line with TIOCM_DSR set + * (see http://lxr.linux.no/#linux+v4.7/drivers/usb/class/cdc-acm.c#L949) + * However as it is possible to query the line state we will not just clear + * the TIOCM_DSR bit from the lines to check but resort to the polling + * approach just like on other hosts. + */ + if (!fPoll) + { + rcPsx = ioctl(pThis->iFd, TIOCMIWAIT, fStsLinesChk); + if (!rcPsx) + { + rc = rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED); + if (RT_FAILURE(rc)) + break; + } + else if (rcPsx == -1 && errno != EINTR) + fPoll = true; + } + else +#endif + { + uint32_t fStsLines = 0; + rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLines); + if (!rcPsx) + { + cStsLineGetErrors = 0; /* Reset the error counter once we had one successful query. */ + + if (((fStsLines ^ fStsLinesOld) & fStsLinesChk)) + { + rc = rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED); + if (RT_FAILURE(rc)) + break; + + fStsLinesOld = fStsLines; + } + else /* No change, sleep for a bit. */ + RTThreadSleep(100 /*ms*/); + } + else if (rcPsx == -1 && errno != EINTR) + { + /* + * If querying the status line fails too often we have to shut down the + * thread and notify the user of the serial port. + */ + if (cStsLineGetErrors++ >= 10) + { + rc = RTErrConvertFromErrno(errno); + rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_MONITOR_FAILED); + break; + } + + RTThreadSleep(100 /*ms*/); + } + } + } + + ASMAtomicXchgBool(&pThis->fMonThrdShutdown, true); + return rc; +} + + +/** + * Creates the status line monitoring thread. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + */ +static int rtSerialPortMonitorThreadCreate(PRTSERIALPORTINTERNAL pThis) +{ + int rc = VINF_SUCCESS; + + /* + * Check whether querying the status lines is supported at all, pseudo terminals + * don't support it so an error returned in that case. + */ + uint32_t fStsLines = 0; + int rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLines); + if (!rcPsx) + { + pThis->fMonThrdShutdown = false; + rc = RTThreadCreate(&pThis->hMonThrd, rtSerialPortStsLineMonitorThrd, pThis, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IPRT-SerPortMon"); + if (RT_SUCCESS(rc)) + { + /* Wait for the thread to start up. */ + rc = RTThreadUserWait(pThis->hMonThrd, 20*RT_MS_1SEC); + if ( rc == VERR_TIMEOUT + || pThis->fMonThrdShutdown) + { + /* Startup failed, try to reap the thread. */ + int rcThrd; + rc = RTThreadWait(pThis->hMonThrd, 20*RT_MS_1SEC, &rcThrd); + if (RT_SUCCESS(rc)) + rc = rcThrd; + else + rc = VERR_INTERNAL_ERROR; + /* The thread is lost otherwise. */ + } + } + } + else if (errno == ENOTTY || errno == EINVAL) + rc = VERR_NOT_SUPPORTED; + else + rc = RTErrConvertFromErrno(errno); + + return rc; +} + + +/** + * Shuts down the status line monitor thread. + * + * @param pThis The internal serial port instance data. + */ +static void rtSerialPortMonitorThreadShutdown(PRTSERIALPORTINTERNAL pThis) +{ + bool fShutDown = ASMAtomicXchgBool(&pThis->fMonThrdShutdown, true); + if (!fShutDown) + { + int rc = RTThreadPoke(pThis->hMonThrd); + AssertRC(rc); + } + + int rcThrd = VINF_SUCCESS; + int rc = RTThreadWait(pThis->hMonThrd, 20*RT_MS_1SEC, &rcThrd); + AssertRC(rc); + AssertRC(rcThrd); +} + + +/** + * The slow path of rtSerialPortSwitchBlockingMode that does the actual switching. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param fBlocking The desired mode of operation. + * @remarks Do not call directly. + */ +static int rtSerialPortSwitchBlockingModeSlow(PRTSERIALPORTINTERNAL pThis, bool fBlocking) +{ + int fFlags = fcntl(pThis->iFd, F_GETFL, 0); + if (fFlags == -1) + return RTErrConvertFromErrno(errno); + + if (fBlocking) + fFlags &= ~O_NONBLOCK; + else + fFlags |= O_NONBLOCK; + if (fcntl(pThis->iFd, F_SETFL, fFlags) == -1) + return RTErrConvertFromErrno(errno); + + pThis->fBlocking = fBlocking; + return VINF_SUCCESS; +} + + +/** + * Switches the serial port to the desired blocking mode if necessary. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param fBlocking The desired mode of operation. + */ +DECLINLINE(int) rtSerialPortSwitchBlockingMode(PRTSERIALPORTINTERNAL pThis, bool fBlocking) +{ + if (pThis->fBlocking != fBlocking) + return rtSerialPortSwitchBlockingModeSlow(pThis, fBlocking); + return VINF_SUCCESS; +} + + +RTDECL(int) RTSerialPortOpen(PRTSERIALPORT phSerialPort, const char *pszPortAddress, uint32_t fFlags) +{ + AssertPtrReturn(phSerialPort, VERR_INVALID_POINTER); + AssertPtrReturn(pszPortAddress, VERR_INVALID_POINTER); + AssertReturn(*pszPortAddress != '\0', VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTSERIALPORT_OPEN_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn((fFlags & RTSERIALPORT_OPEN_F_READ) || (fFlags & RTSERIALPORT_OPEN_F_WRITE), + VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PRTSERIALPORTINTERNAL pThis = (PRTSERIALPORTINTERNAL)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + int fPsxFlags = O_NOCTTY | O_NONBLOCK; + + if ((fFlags & RTSERIALPORT_OPEN_F_READ) && !(fFlags & RTSERIALPORT_OPEN_F_WRITE)) + fPsxFlags |= O_RDONLY; + else if (!(fFlags & RTSERIALPORT_OPEN_F_READ) && (fFlags & RTSERIALPORT_OPEN_F_WRITE)) + fPsxFlags |= O_WRONLY; + else + fPsxFlags |= O_RDWR; + + pThis->u32Magic = RTSERIALPORT_MAGIC; + pThis->fOpenFlags = fFlags; + pThis->fEvtsPending = 0; + pThis->iFd = open(pszPortAddress, fPsxFlags); + pThis->fBlocking = false; + if (pThis->iFd != -1) + { + /* Create wakeup pipe for the event API. */ + int aPipeFds[2]; + int rcPsx = pipe(&aPipeFds[0]); + if (!rcPsx) + { + /* Make the pipes close on exec. */ + pThis->iFdPipeR = aPipeFds[0]; + pThis->iFdPipeW = aPipeFds[1]; + + if (fcntl(pThis->iFdPipeR, F_SETFD, FD_CLOEXEC)) + rc = RTErrConvertFromErrno(errno); + + if ( RT_SUCCESS(rc) + && fcntl(pThis->iFdPipeW, F_SETFD, FD_CLOEXEC)) + rc = RTErrConvertFromErrno(errno); + + if (RT_SUCCESS(rc)) + { + rc = rtSerialPortSetDefaultCfg(pThis); + if ( RT_SUCCESS(rc) + && (fFlags & RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING)) + rc = rtSerialPortMonitorThreadCreate(pThis); + + if (RT_SUCCESS(rc)) + { + *phSerialPort = pThis; + return VINF_SUCCESS; + } + } + + close(pThis->iFdPipeR); + close(pThis->iFdPipeW); + } + else + rc = RTErrConvertFromErrno(errno); + + close(pThis->iFd); + } + else + rc = RTErrConvertFromErrno(errno); + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTSerialPortClose(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + if (pThis == NIL_RTSERIALPORT) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the cleanup. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTSERIALPORT_MAGIC_DEAD, RTSERIALPORT_MAGIC), VERR_INVALID_HANDLE); + + if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING) + rtSerialPortMonitorThreadShutdown(pThis); + + close(pThis->iFd); + close(pThis->iFdPipeR); + close(pThis->iFdPipeW); + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(RTHCINTPTR) RTSerialPortToNative(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, -1); + + return pThis->iFd; +} + + +RTDECL(int) RTSerialPortRead(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER); + + int rc = rtSerialPortSwitchBlockingMode(pThis, true); + if (RT_SUCCESS(rc)) + { + /* + * Attempt read. + */ + ssize_t cbRead = read(pThis->iFd, pvBuf, cbToRead); + if (cbRead > 0) + { + if (pcbRead) + /* caller can handle partial read. */ + *pcbRead = cbRead; + else + { + /* Caller expects all to be read. */ + while ((ssize_t)cbToRead > cbRead) + { + ssize_t cbReadPart = read(pThis->iFd, (uint8_t *)pvBuf + cbRead, cbToRead - cbRead); + if (cbReadPart < 0) + return RTErrConvertFromErrno(errno); + else if (cbReadPart == 0) + return VERR_DEV_IO_ERROR; + + cbRead += cbReadPart; + } + } + } + else if (cbRead == 0) + rc = VERR_DEV_IO_ERROR; + else + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + + +RTDECL(int) RTSerialPortReadNB(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + + *pcbRead = 0; + + int rc = rtSerialPortSwitchBlockingMode(pThis, false); + if (RT_SUCCESS(rc)) + { + ssize_t cbThisRead = read(pThis->iFd, pvBuf, cbToRead); + if (cbThisRead > 0) + { + /* + * The read data needs to be scanned for the BREAK condition marker encoded in the data stream, + * if break detection was enabled during open. + */ + if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_DETECT_BREAK_CONDITION) + { /** @todo */ } + + *pcbRead = cbThisRead; + } + else if (cbThisRead == 0) + rc = VERR_DEV_IO_ERROR; + else if ( errno == EAGAIN +# ifdef EWOULDBLOCK +# if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +# endif +# endif + ) + rc = VINF_TRY_AGAIN; + else + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + + +RTDECL(int) RTSerialPortWrite(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER); + + int rc = rtSerialPortSwitchBlockingMode(pThis, true); + if (RT_SUCCESS(rc)) + { + /* + * Attempt write. + */ + ssize_t cbWritten = write(pThis->iFd, pvBuf, cbToWrite); + if (cbWritten > 0) + { + if (pcbWritten) + /* caller can handle partial write. */ + *pcbWritten = cbWritten; + else + { + /* Caller expects all to be written. */ + while ((ssize_t)cbToWrite > cbWritten) + { + ssize_t cbWrittenPart = write(pThis->iFd, (const uint8_t *)pvBuf + cbWritten, cbToWrite - cbWritten); + if (cbWrittenPart < 0) + return RTErrConvertFromErrno(errno); + else if (cbWrittenPart == 0) + return VERR_DEV_IO_ERROR; + cbWritten += cbWrittenPart; + } + } + } + else if (cbWritten == 0) + rc = VERR_DEV_IO_ERROR; + else + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + + +RTDECL(int) RTSerialPortWriteNB(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + + *pcbWritten = 0; + + int rc = rtSerialPortSwitchBlockingMode(pThis, false); + if (RT_SUCCESS(rc)) + { + ssize_t cbThisWrite = write(pThis->iFd, pvBuf, cbToWrite); + if (cbThisWrite > 0) + *pcbWritten = cbThisWrite; + else if (cbThisWrite == 0) + rc = VERR_DEV_IO_ERROR; + else if ( errno == EAGAIN +# ifdef EWOULDBLOCK +# if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +# endif +# endif + ) + rc = VINF_TRY_AGAIN; + else + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + + +RTDECL(int) RTSerialPortCfgQueryCurrent(RTSERIALPORT hSerialPort, PRTSERIALPORTCFG pCfg) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + return rtSerialPortTermios2Cfg(pThis, &pThis->PortCfg, pCfg); +} + + +RTDECL(int) RTSerialPortCfgSet(RTSERIALPORT hSerialPort, PCRTSERIALPORTCFG pCfg, PRTERRINFO pErrInfo) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + struct termios PortCfgNew; RT_ZERO(PortCfgNew); + bool fBaudrateCust = false; + int rc = rtSerialPortCfg2Termios(pThis, pCfg, &PortCfgNew, &fBaudrateCust, pErrInfo); + if (RT_SUCCESS(rc)) + { + int rcPsx = tcflush(pThis->iFd, TCIOFLUSH); + if (!rcPsx) + { +#ifdef RT_OS_LINUX + if (fBaudrateCust) + { + struct serial_struct SerLnx; + rcPsx = ioctl(pThis->iFd, TIOCGSERIAL, &SerLnx); + if (!rcPsx) + { + SerLnx.custom_divisor = SerLnx.baud_base / pCfg->uBaudRate; + if (!SerLnx.custom_divisor) + SerLnx.custom_divisor = 1; + SerLnx.flags &= ~ASYNC_SPD_MASK; + SerLnx.flags |= ASYNC_SPD_CUST; + rcPsx = ioctl(pThis->iFd, TIOCSSERIAL, &SerLnx); + } + } +#else /* !RT_OS_LINUX */ + /* Hosts not supporting custom baud rates should already fail in rtSerialPortCfg2Termios(). */ + AssertMsgFailed(("Should not get here!\n")); +#endif /* !RT_OS_LINUX */ + pThis->fBaudrateCust = fBaudrateCust; + pThis->uBaudRateCust = pCfg->uBaudRate; + + if (!rcPsx) + rcPsx = tcsetattr(pThis->iFd, TCSANOW, &PortCfgNew); + if (rcPsx == -1) + rc = RTErrConvertFromErrno(errno); + else + memcpy(&pThis->PortCfg, &PortCfgNew, sizeof(struct termios)); + +#ifdef RT_OS_LINUX + /* + * XXX In Linux, if a thread calls tcsetattr while the monitor thread is + * waiting in ioctl for a modem status change then 8250.c wrongly disables + * modem irqs and so the monitor thread never gets released. The workaround + * is to send a signal after each tcsetattr. + */ + if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING) + RTThreadPoke(pThis->hMonThrd); +#endif + } + else + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + + +RTDECL(int) RTSerialPortEvtPoll(RTSERIALPORT hSerialPort, uint32_t fEvtMask, uint32_t *pfEvtsRecv, + RTMSINTERVAL msTimeout) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fEvtMask & ~RTSERIALPORT_EVT_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pfEvtsRecv, VERR_INVALID_POINTER); + + *pfEvtsRecv = 0; + + fEvtMask |= RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED; /* This will be reported always, no matter what the caller wants. */ + + /* Return early if there are events pending from previous calls which weren't fetched yet. */ + for (;;) + { + uint32_t fEvtsPending = ASMAtomicReadU32(&pThis->fEvtsPending); + if (fEvtsPending & fEvtMask) + { + *pfEvtsRecv = fEvtsPending & fEvtMask; + /* Write back, repeat the whole procedure if someone else raced us. */ + if (ASMAtomicCmpXchgU32(&pThis->fEvtsPending, fEvtsPending & ~fEvtMask, fEvtsPending)) + return VINF_SUCCESS; + } + else + break; + } + + int rc = rtSerialPortSwitchBlockingMode(pThis, false); + if (RT_SUCCESS(rc)) + { + struct pollfd aPollFds[2]; RT_ZERO(aPollFds); + aPollFds[0].fd = pThis->iFd; + aPollFds[0].events = POLLERR | POLLHUP; + aPollFds[0].revents = 0; + if ( (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_READ) + && (fEvtMask & RTSERIALPORT_EVT_F_DATA_RX)) + aPollFds[0].events |= POLLIN; + if ( (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_WRITE) + && (fEvtMask & RTSERIALPORT_EVT_F_DATA_TX)) + aPollFds[0].events |= POLLOUT; + + aPollFds[1].fd = pThis->iFdPipeR; + aPollFds[1].events = POLLIN | POLLERR | POLLHUP; + aPollFds[1].revents = 0; + + int rcPsx = 0; + int msTimeoutLeft = msTimeout == RT_INDEFINITE_WAIT ? -1 : (int)msTimeout; + while (msTimeoutLeft != 0) + { + uint64_t tsPollStart = RTTimeMilliTS(); + + rcPsx = poll(&aPollFds[0], RT_ELEMENTS(aPollFds), msTimeoutLeft); + if (rcPsx != -1 || errno != EINTR) + break; + /* Restart when getting interrupted. */ + if (msTimeoutLeft > -1) + { + uint64_t tsPollEnd = RTTimeMilliTS(); + uint64_t tsPollSpan = tsPollEnd - tsPollStart; + msTimeoutLeft -= RT_MIN(tsPollSpan, (uint32_t)msTimeoutLeft); + } + } + + uint32_t fEvtsPending = 0; + if (rcPsx < 0 && errno != EINTR) + rc = RTErrConvertFromErrno(errno); + else if (rcPsx > 0) + { + if (aPollFds[0].revents != 0) + { + if (aPollFds[0].revents & POLLERR) + rc = VERR_DEV_IO_ERROR; + else + { + fEvtsPending |= (aPollFds[0].revents & POLLIN) ? RTSERIALPORT_EVT_F_DATA_RX : 0; + fEvtsPending |= (aPollFds[0].revents & POLLOUT) ? RTSERIALPORT_EVT_F_DATA_TX : 0; + /** @todo BREAK condition detection. */ + } + } + + if (aPollFds[1].revents != 0) + { + AssertReturn(!(aPollFds[1].revents & (POLLHUP | POLLERR | POLLNVAL)), VERR_INTERNAL_ERROR); + Assert(aPollFds[1].revents & POLLIN); + + uint8_t bWakeupReason = 0; + ssize_t cbRead = read(pThis->iFdPipeR, &bWakeupReason, 1); + if (cbRead == 1) + { + switch (bWakeupReason) + { + case RTSERIALPORT_WAKEUP_PIPE_REASON_INTERRUPT: + rc = VERR_INTERRUPTED; + break; + case RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED: + fEvtsPending |= RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED; + break; + case RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_MONITOR_FAILED: + fEvtsPending |= RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED; + break; + default: + AssertFailed(); + rc = VERR_INTERNAL_ERROR; + } + } + else + rc = VERR_INTERNAL_ERROR; + } + } + else + rc = VERR_TIMEOUT; + + *pfEvtsRecv = fEvtsPending & fEvtMask; + fEvtsPending &= ~fEvtMask; + ASMAtomicOrU32(&pThis->fEvtsPending, fEvtsPending); + } + + return rc; +} + + +RTDECL(int) RTSerialPortEvtPollInterrupt(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + return rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_INTERRUPT); +} + + +RTDECL(int) RTSerialPortChgBreakCondition(RTSERIALPORT hSerialPort, bool fSet) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + int rcPsx = ioctl(pThis->iFd, fSet ? TIOCSBRK : TIOCCBRK); + if (rcPsx == -1) + rc = RTErrConvertFromErrno(errno); + + return rc; +} + + +RTDECL(int) RTSerialPortChgStatusLines(RTSERIALPORT hSerialPort, uint32_t fClear, uint32_t fSet) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + int fTiocmSet = 0; + int fTiocmClear = 0; + + if (fClear & RTSERIALPORT_CHG_STS_LINES_F_RTS) + fTiocmClear |= TIOCM_RTS; + if (fClear & RTSERIALPORT_CHG_STS_LINES_F_DTR) + fTiocmClear |= TIOCM_DTR; + + if (fSet & RTSERIALPORT_CHG_STS_LINES_F_RTS) + fTiocmSet |= TIOCM_RTS; + if (fSet & RTSERIALPORT_CHG_STS_LINES_F_DTR) + fTiocmSet |= TIOCM_DTR; + + int rcPsx = ioctl(pThis->iFd, TIOCMBIS, &fTiocmSet); + if (!rcPsx) + { + rcPsx = ioctl(pThis->iFd, TIOCMBIC, &fTiocmClear); + if (rcPsx == -1) + rc = RTErrConvertFromErrno(errno); + } + return rc; +} + + +RTDECL(int) RTSerialPortQueryStatusLines(RTSERIALPORT hSerialPort, uint32_t *pfStsLines) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfStsLines, VERR_INVALID_POINTER); + + *pfStsLines = 0; + + int rc = VINF_SUCCESS; + int fStsLines = 0; + int rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLines); + if (!rcPsx) + { + /* This resets the status line event pending flag. */ + for (;;) + { + uint32_t fEvtsPending = ASMAtomicReadU32(&pThis->fEvtsPending); + if (ASMAtomicCmpXchgU32(&pThis->fEvtsPending, fEvtsPending & ~RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED, fEvtsPending)) + break; + } + + *pfStsLines |= (fStsLines & TIOCM_CAR) ? RTSERIALPORT_STS_LINE_DCD : 0; + *pfStsLines |= (fStsLines & TIOCM_RNG) ? RTSERIALPORT_STS_LINE_RI : 0; + *pfStsLines |= (fStsLines & TIOCM_DSR) ? RTSERIALPORT_STS_LINE_DSR : 0; + *pfStsLines |= (fStsLines & TIOCM_CTS) ? RTSERIALPORT_STS_LINE_CTS : 0; + } + else + rc = RTErrConvertFromErrno(errno); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/shmem-posix.cpp b/src/VBox/Runtime/r3/posix/shmem-posix.cpp new file mode 100644 index 00000000..5e21f26b --- /dev/null +++ b/src/VBox/Runtime/r3/posix/shmem-posix.cpp @@ -0,0 +1,419 @@ +/* $Id: shmem-posix.cpp $ */ +/** @file + * IPRT - Named shared memory object, POSIX Implementation. + */ + +/* + * Copyright (C) 2018-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/shmem.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/cdefs.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include "internal/magics.h" + +#include <errno.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> + +/* Workaround on systems which do not provide this. */ +#ifndef NAME_MAX +# define NAME_MAX 255 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Shared memory object mapping descriptor. + */ +typedef struct RTSHMEMMAPPINGDESC +{ + /** Number of references held to this mapping, 0 if the descriptor is free. */ + volatile uint32_t cMappings; + /** Pointer to the region mapping. */ + void *pvMapping; + /** Start offset */ + size_t offRegion; + /** Size of the region. */ + size_t cbRegion; + /** Access flags for this region .*/ + uint32_t fFlags; +} RTSHMEMMAPPINGDESC; +/** Pointer to a shared memory object mapping descriptor. */ +typedef RTSHMEMMAPPINGDESC *PRTSHMEMMAPPINGDESC; +/** Pointer to a constant shared memory object mapping descriptor. */ +typedef const RTSHMEMMAPPINGDESC *PCRTSHMEMMAPPINGDESC; + + +/** + * Internal shared memory object state. + */ +typedef struct RTSHMEMINT +{ + /** Magic value (RTSHMEM_MAGIC). */ + uint32_t u32Magic; + /** File descriptor for the underlying shared memory object. */ + int iFdShm; + /** Pointer to the shared memory object name. */ + char *pszName; + /** Flag whether this instance created the named shared memory object. */ + bool fCreate; + /** Overall number of mappings active for this shared memory object. */ + volatile uint32_t cMappings; + /** Maximum number of mapping descriptors allocated. */ + uint32_t cMappingDescsMax; + /** Number of mapping descriptors used. */ + volatile uint32_t cMappingDescsUsed; + /** Array of mapping descriptors - variable in size. */ + RTSHMEMMAPPINGDESC aMappingDescs[1]; +} RTSHMEMINT; +/** Pointer to the internal shared memory object state. */ +typedef RTSHMEMINT *PRTSHMEMINT; + + + +/** + * Returns a mapping descriptor matching the given region properties or NULL if none was found. + * + * @returns Pointer to the matching mapping descriptor or NULL if not found. + * @param pThis Pointer to the shared memory object instance. + * @param offRegion Offset into the shared memory object to start mapping at. + * @param cbRegion Size of the region to map. + * @param fFlags Desired properties of the mapped region, combination of RTSHMEM_MAP_F_* defines. + */ +DECLINLINE(PRTSHMEMMAPPINGDESC) rtShMemMappingDescFindByProp(PRTSHMEMINT pThis, size_t offRegion, size_t cbRegion, uint32_t fFlags) +{ + for (uint32_t i = 0; i < pThis->cMappingDescsMax; i++) + { + if ( pThis->aMappingDescs[i].offRegion == offRegion + && pThis->aMappingDescs[i].cbRegion == cbRegion + && pThis->aMappingDescs[i].fFlags == fFlags) + return &pThis->aMappingDescs[i]; + } + + return NULL; +} + + +RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, size_t cbMax, uint32_t cMappingsHint) +{ + AssertPtrReturn(phShMem, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTSHMEM_O_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(cMappingsHint < 64, VERR_OUT_OF_RANGE); + + size_t cchName = strlen(pszName); + AssertReturn(cchName, VERR_INVALID_PARAMETER); + AssertReturn(cchName < NAME_MAX - 1, VERR_INVALID_PARAMETER); /* account for the / we add later on. */ + cMappingsHint = cMappingsHint == 0 ? 5 : cMappingsHint; + int rc = VINF_SUCCESS; + PRTSHMEMINT pThis = (PRTSHMEMINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTSHMEMINT, aMappingDescs[cMappingsHint]) + cchName + 2); /* '/' + terminator. */ + if (RT_LIKELY(pThis)) + { + pThis->u32Magic = RTSHMEM_MAGIC; + pThis->pszName = (char *)&pThis->aMappingDescs[cMappingsHint]; + /*pThis->fCreate = false; */ + /*pThis->cMappings = 0; */ + pThis->cMappingDescsMax = cMappingsHint; + /*pThis->cMappingDescsUsed = 0; */ + pThis->pszName[0] = '/'; + memcpy(&pThis->pszName[1], pszName, cchName); + int fShmFlags = 0; + if (fFlags & RTSHMEM_O_F_CREATE) + { + fShmFlags |= O_CREAT; + pThis->fCreate = true; + } + if ((fFlags & RTSHMEM_O_F_CREATE_EXCL) == RTSHMEM_O_F_CREATE_EXCL) + fShmFlags |= O_EXCL; + if ( (fFlags & RTSHMEM_O_F_READWRITE) == RTSHMEM_O_F_READWRITE + || (fFlags & RTSHMEM_O_F_WRITE)) + fShmFlags |= O_RDWR; + else + fShmFlags |= O_RDONLY; + if (fFlags & RTSHMEM_O_F_TRUNCATE) + fShmFlags |= O_TRUNC; + pThis->iFdShm = shm_open(pThis->pszName, fShmFlags , 0600); + if (pThis->iFdShm > 0) + { + if (cbMax) + rc = RTShMemSetSize(pThis, cbMax); + if (RT_SUCCESS(rc)) + { + *phShMem = pThis; + return VINF_SUCCESS; + } + + close(pThis->iFdShm); + } + else + rc = RTErrConvertFromErrno(errno); + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTShMemClose(RTSHMEM hShMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->cMappings, VERR_INVALID_STATE); + + int rc = VINF_SUCCESS; + if (!close(pThis->iFdShm)) + { + if (pThis->fCreate) + shm_unlink(pThis->pszName); /* Ignore any error here. */ + pThis->u32Magic = RTSHMEM_MAGIC_DEAD; + RTMemFree(pThis); + } + else + rc = RTErrConvertFromErrno(errno); + + return rc; +} + + +RTDECL(int) RTShMemDelete(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + + size_t cchName = strlen(pszName); + AssertReturn(cchName, VERR_INVALID_PARAMETER); + AssertReturn(cchName < NAME_MAX - 1, VERR_INVALID_PARAMETER); /* account for the / we add later on. */ + char *psz = NULL; + + int rc = RTStrAllocEx(&psz, cchName + 2); /* '/' + terminator */ + if (RT_SUCCESS(rc)) + { + psz[0] = '/'; + memcpy(&psz[1], pszName, cchName + 1); + if (shm_unlink(psz)) + rc = RTErrConvertFromErrno(errno); + RTStrFree(psz); + } + + return rc; +} + + +RTDECL(uint32_t) RTShMemRefCount(RTSHMEM hShMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, 0); + + return pThis->cMappings; +} + + +RTDECL(int) RTShMemSetSize(RTSHMEM hShMem, size_t cbMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->cMappings, VERR_INVALID_STATE); + + int rc = VINF_SUCCESS; + if (ftruncate(pThis->iFdShm, (off_t)cbMem)) + rc = RTErrConvertFromErrno(errno); + + return rc; +} + + +RTDECL(int) RTShMemQuerySize(RTSHMEM hShMem, size_t *pcbMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pcbMem, VERR_INVALID_PARAMETER); + + struct stat st; + if (!fstat(pThis->iFdShm, &st)) + { + *pcbMem = st.st_size; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +RTDECL(int) RTShMemMapRegion(RTSHMEM hShMem, size_t offRegion, size_t cbRegion, uint32_t fFlags, void **ppv) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(ppv, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTSHMEM_MAP_F_VALID_MASK), VERR_INVALID_PARAMETER); + + /* Try to find a mapping with compatible parameters first. */ + PRTSHMEMMAPPINGDESC pMappingDesc = NULL; + for (uint32_t iTry = 0; iTry < 10; iTry++) + { + pMappingDesc = rtShMemMappingDescFindByProp(pThis, offRegion, cbRegion, fFlags); + if (!pMappingDesc) + break; + + /* Increase the mapping count and check that the region is still accessible by us. */ + if ( ASMAtomicIncU32(&pMappingDesc->cMappings) > 1 + && pMappingDesc->offRegion == offRegion + && pMappingDesc->cbRegion == cbRegion + && pMappingDesc->fFlags == fFlags) + break; + /* Mapping was freed inbetween, next round. */ + } + + int rc = VINF_SUCCESS; + if (!pMappingDesc) + { + /* Find an empty region descriptor and map the region. */ + for (uint32_t i = 0; i < pThis->cMappingDescsMax && !pMappingDesc; i++) + { + if (!pThis->aMappingDescs[i].cMappings) + { + pMappingDesc = &pThis->aMappingDescs[i]; + + /* Try to grab this one. */ + if (ASMAtomicIncU32(&pMappingDesc->cMappings) == 1) + break; + + /* Somebody raced us, drop reference and continue. */ + ASMAtomicDecU32(&pMappingDesc->cMappings); + pMappingDesc = NULL; + } + } + + if (RT_LIKELY(pMappingDesc)) + { + /* Try to map it. */ + int fMmapFlags = 0; + int fProt = 0; + if (fFlags & RTSHMEM_MAP_F_READ) + fProt |= PROT_READ; + if (fFlags & RTSHMEM_MAP_F_WRITE) + fProt |= PROT_WRITE; + if (fFlags & RTSHMEM_MAP_F_EXEC) + fProt |= PROT_EXEC; + if (fFlags & RTSHMEM_MAP_F_COW) + fMmapFlags |= MAP_PRIVATE; + else + fMmapFlags |= MAP_SHARED; + + void *pv = mmap(NULL, cbRegion, fProt, fMmapFlags, pThis->iFdShm, (off_t)offRegion); + if (pv != MAP_FAILED) + { + pMappingDesc->pvMapping = pv; + pMappingDesc->offRegion = offRegion; + pMappingDesc->cbRegion = cbRegion; + pMappingDesc->fFlags = fFlags; + } + else + { + rc = RTErrConvertFromErrno(errno); + ASMAtomicDecU32(&pMappingDesc->cMappings); + } + } + else + rc = VERR_SHMEM_MAXIMUM_MAPPINGS_REACHED; + } + + if (RT_SUCCESS(rc)) + { + *ppv = pMappingDesc->pvMapping; + ASMAtomicIncU32(&pThis->cMappings); + } + + return rc; +} + + +RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pv, VERR_INVALID_PARAMETER); + + /* Find the mapping descriptor by the given region address. */ + PRTSHMEMMAPPINGDESC pMappingDesc = NULL; + for (uint32_t i = 0; i < pThis->cMappingDescsMax && !pMappingDesc; i++) + { + if (pThis->aMappingDescs[i].pvMapping == pv) + { + pMappingDesc = &pThis->aMappingDescs[i]; + break; + } + } + + AssertPtrReturn(pMappingDesc, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + size_t cbRegion = pMappingDesc->cMappings; + if (!ASMAtomicDecU32(&pMappingDesc->cMappings)) + { + /* Last mapping of this region was unmapped, so do the real unmapping now. */ + if (munmap(pv, cbRegion)) + { + ASMAtomicIncU32(&pMappingDesc->cMappings); + rc = RTErrConvertFromErrno(errno); + } + else + { + ASMAtomicDecU32(&pThis->cMappingDescsUsed); + ASMAtomicDecU32(&pThis->cMappings); + } + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/symlink-posix.cpp b/src/VBox/Runtime/r3/posix/symlink-posix.cpp new file mode 100644 index 00000000..e43f5b36 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/symlink-posix.cpp @@ -0,0 +1,247 @@ +/* $Id: symlink-posix.cpp $ */ +/** @file + * IPRT - Symbolic Links, POSIX. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYMLINK + +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <iprt/symlink.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include "internal/path.h" + + + +RTDECL(bool) RTSymlinkExists(const char *pszSymlink) +{ + bool fRc = false; + char const *pszNativeSymlink; + int rc = rtPathToNative(&pszNativeSymlink, pszSymlink, NULL); + if (RT_SUCCESS(rc)) + { + struct stat s; + fRc = !lstat(pszNativeSymlink, &s) + && S_ISLNK(s.st_mode); + + rtPathFreeNative(pszNativeSymlink, pszSymlink); + } + + LogFlow(("RTSymlinkExists(%p={%s}): returns %RTbool\n", pszSymlink, pszSymlink, fRc)); + return fRc; +} + + +RTDECL(bool) RTSymlinkIsDangling(const char *pszSymlink) +{ + bool fRc = false; + char const *pszNativeSymlink; + int rc = rtPathToNative(&pszNativeSymlink, pszSymlink, NULL); + if (RT_SUCCESS(rc)) + { + struct stat s; + fRc = !lstat(pszNativeSymlink, &s) + && S_ISLNK(s.st_mode); + if (fRc) + { + errno = 0; + fRc = stat(pszNativeSymlink, &s) != 0 + && ( errno == ENOENT + || errno == ENOTDIR + || errno == ELOOP); + } + + rtPathFreeNative(pszNativeSymlink, pszSymlink); + } + + LogFlow(("RTSymlinkIsDangling(%p={%s}): returns %RTbool\n", pszSymlink, pszSymlink, fRc)); + return fRc; +} + + +RTDECL(int) RTSymlinkCreate(const char *pszSymlink, const char *pszTarget, RTSYMLINKTYPE enmType, uint32_t fCreate) +{ + RT_NOREF_PV(fCreate); + + /* + * Validate the input. + */ + AssertReturn(enmType > RTSYMLINKTYPE_INVALID && enmType < RTSYMLINKTYPE_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszSymlink, VERR_INVALID_POINTER); + AssertPtrReturn(pszTarget, VERR_INVALID_POINTER); + + /* + * Convert the paths. + */ + char const *pszNativeSymlink; + int rc = rtPathToNative(&pszNativeSymlink, pszSymlink, NULL); + if (RT_SUCCESS(rc)) + { + const char *pszNativeTarget; + rc = rtPathToNative(&pszNativeTarget, pszTarget, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Create the link. + */ + if (symlink(pszNativeTarget, pszNativeSymlink) == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + + rtPathFreeNative(pszNativeTarget, pszTarget); + } + rtPathFreeNative(pszNativeSymlink, pszSymlink); + } + + LogFlow(("RTSymlinkCreate(%p={%s}, %p={%s}, %d, %#x): returns %Rrc\n", pszSymlink, pszSymlink, pszTarget, pszTarget, enmType, fCreate, rc)); + return rc; +} + + +RTDECL(int) RTSymlinkDelete(const char *pszSymlink, uint32_t fDelete) +{ + RT_NOREF_PV(fDelete); + + char const *pszNativeSymlink; + int rc = rtPathToNative(&pszNativeSymlink, pszSymlink, NULL); + if (RT_SUCCESS(rc)) + { + struct stat s; + if (!lstat(pszNativeSymlink, &s)) + { + if (S_ISLNK(s.st_mode)) + { + if (unlink(pszNativeSymlink) == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + } + else + rc = VERR_NOT_SYMLINK; + } + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativeSymlink, pszSymlink); + } + + LogFlow(("RTSymlinkDelete(%p={%s}, #%x): returns %Rrc\n", pszSymlink, pszSymlink, fDelete, rc)); + return rc; +} + + +RTDECL(int) RTSymlinkRead(const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead) +{ + RT_NOREF_PV(fRead); + + char *pszMyTarget; + int rc = RTSymlinkReadA(pszSymlink, &pszMyTarget); + if (RT_SUCCESS(rc)) + { + rc = RTStrCopy(pszTarget, cbTarget, pszMyTarget); + RTStrFree(pszMyTarget); + } + LogFlow(("RTSymlinkRead(%p={%s}): returns %Rrc\n", pszSymlink, pszSymlink, rc)); + return rc; +} + + +RTDECL(int) RTSymlinkReadA(const char *pszSymlink, char **ppszTarget) +{ + AssertPtr(ppszTarget); + char const *pszNativeSymlink; + int rc = rtPathToNative(&pszNativeSymlink, pszSymlink, NULL); + if (RT_SUCCESS(rc)) + { + /* Guess the initial buffer size. */ + ssize_t cbBuf; + struct stat s; + if (!lstat(pszNativeSymlink, &s)) + cbBuf = RT_MAX(RT_ALIGN_Z(s.st_size, 64), 64); + else + cbBuf = 1024; + + /* Read loop that grows the buffer. */ + char *pszBuf = NULL; + for (;;) + { + RTMemTmpFree(pszBuf); + pszBuf = (char *)RTMemTmpAlloc(cbBuf); + if (pszBuf) + { + ssize_t cbReturned = readlink(pszNativeSymlink, pszBuf, cbBuf); + if (cbReturned >= cbBuf) + { + /* Increase the buffer size and try again */ + cbBuf *= 2; + continue; + } + + if (cbReturned > 0) + { + pszBuf[cbReturned] = '\0'; + rc = rtPathFromNativeDup(ppszTarget, pszBuf, pszSymlink); + } + else if (errno == EINVAL) + rc = VERR_NOT_SYMLINK; + else + rc = RTErrConvertFromErrno(errno); + } + else + rc = VERR_NO_TMP_MEMORY; + break; + } /* for loop */ + + RTMemTmpFree(pszBuf); + rtPathFreeNative(pszNativeSymlink, pszSymlink); + } + + if (RT_SUCCESS(rc)) + LogFlow(("RTSymlinkReadA(%p={%s},%p): returns %Rrc *ppszTarget=%p:{%s}\n", pszSymlink, pszSymlink, ppszTarget, rc, *ppszTarget, *ppszTarget)); + else + LogFlow(("RTSymlinkReadA(%p={%s},%p): returns %Rrc\n", pszSymlink, pszSymlink, ppszTarget, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/posix/thread-posix.cpp b/src/VBox/Runtime/r3/posix/thread-posix.cpp new file mode 100644 index 00000000..cd621ce0 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/thread-posix.cpp @@ -0,0 +1,780 @@ +/* $Id: thread-posix.cpp $ */ +/** @file + * IPRT - Threads, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <errno.h> +#include <limits.h> +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#if defined(RT_OS_LINUX) +# include <unistd.h> +# include <sys/syscall.h> +#endif +#if defined(RT_OS_SOLARIS) +# include <sched.h> +# include <sys/resource.h> +#endif +#if defined(RT_OS_DARWIN) +# include <mach/thread_act.h> +# include <mach/thread_info.h> +# include <mach/host_info.h> +# include <mach/mach_init.h> +# include <mach/mach_host.h> +#endif +#if defined(RT_OS_DARWIN) /*|| defined(RT_OS_FREEBSD) - later */ \ + || (defined(RT_OS_LINUX) && !defined(IN_RT_STATIC) /* static + dlsym = trouble */) \ + || defined(IPRT_MAY_HAVE_PTHREAD_SET_NAME_NP) +# define IPRT_MAY_HAVE_PTHREAD_SET_NAME_NP +# include <dlfcn.h> +#endif +#if defined(RT_OS_HAIKU) +# include <OS.h> +#endif +#if defined(RT_OS_DARWIN) +# define sigprocmask pthread_sigmask /* On xnu sigprocmask works on the process, not the calling thread as elsewhere. */ +#endif + +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/initterm.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/list.h> +#include <iprt/once.h> +#include <iprt/critsect.h> +#include <iprt/req.h> +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/*#ifndef IN_GUEST - shouldn't need to exclude this now with the non-obtrusive init option. */ +/** Includes RTThreadPoke. */ +# define RTTHREAD_POSIX_WITH_POKE +/*#endif*/ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The pthread key in which we store the pointer to our own PRTTHREAD structure. + * @note There is a defined NIL value here, nor can we really assume this is an + * integer. However, zero is a valid key on Linux, so we get into trouble + * if we accidentally use it uninitialized. + * + * So, we ASSUME it's a integer value and the valid range is in approx 0 + * to PTHREAD_KEYS_MAX. Solaris has at least one negative value (-1) + * defined. Thus, we go for 16 MAX values below zero and keep our fingers + * cross that it will always be an invalid key value everywhere... + * + * See also NIL_RTTLS, which is -1. + */ +static pthread_key_t g_SelfKey = (pthread_key_t)(-PTHREAD_KEYS_MAX * 16); +#ifdef RTTHREAD_POSIX_WITH_POKE +/** The signal we use for poking threads. + * This is set to -1 if no available signal was found. */ +static int volatile g_iSigPokeThread = -1; +#endif + +#ifdef IPRT_MAY_HAVE_PTHREAD_SET_NAME_NP +# if defined(RT_OS_DARWIN) +/** + * The Mac OS X (10.6 and later) variant of pthread_setname_np. + * + * @returns errno.h + * @param pszName The new thread name. + */ +typedef int (*PFNPTHREADSETNAME)(const char *pszName); +# else +/** + * The variant of pthread_setname_np most other unix-like systems implement. + * + * @returns errno.h + * @param hThread The thread. + * @param pszName The new thread name. + */ +typedef int (*PFNPTHREADSETNAME)(pthread_t hThread, const char *pszName); +# endif + +/** Pointer to pthread_setname_np if found. */ +static PFNPTHREADSETNAME g_pfnThreadSetName = NULL; +#endif /* IPRT_MAY_HAVE_PTHREAD_SET_NAME_NP */ + +#ifdef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY +/** Atomic indicator of whether the priority proxy thread has been (attempted) started. + * + * The priority proxy thread is started under these circumstances: + * - RTThreadCreate + * - RTThreadSetType + * - RTProcSetPriority + * + * Which means that we'll be single threaded when this is modified. + * + * Speical values: + * - VERR_TRY_AGAIN: Not yet started. + * - VERR_WRONG_ORDER: Starting. + * - VINF_SUCCESS: Started successfully. + * - VERR_PROCESS_NOT_FOUND: Stopping or stopped + * - Other error status if failed to start. + * + * @note We could potentially optimize this by only start it when we lower the + * priority of ourselves, the process, or a newly created thread. But + * that would means we would need to take multi-threading into account, so + * let's not do that for now. + */ +static int32_t volatile g_rcPriorityProxyThreadStart = VERR_TRY_AGAIN; +/** The IPRT thread handle for the priority proxy. */ +static RTTHREAD g_hRTThreadPosixPriorityProxyThread = NIL_RTTHREAD; +/** The priority proxy queue. */ +static RTREQQUEUE g_hRTThreadPosixPriorityProxyQueue = NIL_RTREQQUEUE; +#endif /* RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void *rtThreadNativeMain(void *pvArgs); +static void rtThreadKeyDestruct(void *pvValue); +#ifdef RTTHREAD_POSIX_WITH_POKE +static void rtThreadPosixPokeSignal(int iSignal); +#endif + + +#ifdef RTTHREAD_POSIX_WITH_POKE +/** + * Try register the dummy signal handler for RTThreadPoke. + */ +static void rtThreadPosixSelectPokeSignal(void) +{ + /* + * Note! Avoid SIGRTMIN thru SIGRTMIN+2 because of LinuxThreads. + */ +# if !defined(RT_OS_LINUX) && !defined(RT_OS_SOLARIS) /* glibc defines SIGRTMAX to __libc_current_sigrtmax() and Solaris libc defines it relying on _sysconf(), causing compiler to deploy serialization here. */ + static +# endif + const int s_aiSigCandidates[] = + { +# ifdef SIGRTMAX + SIGRTMAX-3, + SIGRTMAX-2, + SIGRTMAX-1, +# endif +# ifndef RT_OS_SOLARIS + SIGUSR2, +# endif + SIGWINCH + }; + + g_iSigPokeThread = -1; + if (!RTR3InitIsUnobtrusive()) + { + for (unsigned iSig = 0; iSig < RT_ELEMENTS(s_aiSigCandidates); iSig++) + { + struct sigaction SigActOld; + if (!sigaction(s_aiSigCandidates[iSig], NULL, &SigActOld)) + { + if ( SigActOld.sa_handler == SIG_DFL + || SigActOld.sa_handler == rtThreadPosixPokeSignal) + { + struct sigaction SigAct; + RT_ZERO(SigAct); + SigAct.sa_handler = rtThreadPosixPokeSignal; + SigAct.sa_flags = 0; /* no SA_RESTART! */ + sigfillset(&SigAct.sa_mask); + + /* ASSUMES no sigaction race... (lazy bird) */ + if (!sigaction(s_aiSigCandidates[iSig], &SigAct, NULL)) + { + g_iSigPokeThread = s_aiSigCandidates[iSig]; + break; + } + AssertMsgFailed(("rc=%Rrc errno=%d\n", RTErrConvertFromErrno(errno), errno)); + } + } + else + AssertMsgFailed(("rc=%Rrc errno=%d\n", RTErrConvertFromErrno(errno), errno)); + } + } +} +#endif /* RTTHREAD_POSIX_WITH_POKE */ + + +DECLHIDDEN(int) rtThreadNativeInit(void) +{ + /* + * Allocate the TLS (key in posix terms) where we store the pointer to + * a threads RTTHREADINT structure. + */ + int rc = pthread_key_create(&g_SelfKey, rtThreadKeyDestruct); + if (rc) + return VERR_NO_TLS_FOR_SELF; + +#ifdef RTTHREAD_POSIX_WITH_POKE + rtThreadPosixSelectPokeSignal(); +#endif + +#ifdef IPRT_MAY_HAVE_PTHREAD_SET_NAME_NP + if (RT_SUCCESS(rc)) + g_pfnThreadSetName = (PFNPTHREADSETNAME)(uintptr_t)dlsym(RTLD_DEFAULT, "pthread_setname_np"); +#endif + return rc; +} + +static void rtThreadPosixBlockSignals(PRTTHREADINT pThread) +{ + /* + * Mask all signals, including the poke one, if requested. + */ + if ( pThread + && (pThread->fFlags & RTTHREADFLAGS_NO_SIGNALS)) + { + sigset_t SigSet; + sigfillset(&SigSet); + sigdelset(&SigSet, SIGILL); /* On the m1 we end up spinning on UDF ... */ + sigdelset(&SigSet, SIGTRAP); /* ... and BRK instruction if these signals are masked. */ + sigdelset(&SigSet, SIGFPE); /* Just adding the rest here to be on the safe side. */ + sigdelset(&SigSet, SIGBUS); + sigdelset(&SigSet, SIGSEGV); + int rc = sigprocmask(SIG_BLOCK, &SigSet, NULL); + AssertMsg(rc == 0, ("rc=%Rrc errno=%d\n", RTErrConvertFromErrno(errno), errno)); RT_NOREF(rc); + } + /* + * Block SIGALRM - required for timer-posix.cpp. + * This is done to limit harm done by OSes which doesn't do special SIGALRM scheduling. + * It will not help much if someone creates threads directly using pthread_create. :/ + */ + else if (!RTR3InitIsUnobtrusive()) + { + sigset_t SigSet; + sigemptyset(&SigSet); + sigaddset(&SigSet, SIGALRM); + sigprocmask(SIG_BLOCK, &SigSet, NULL); + } + +#ifdef RTTHREAD_POSIX_WITH_POKE + /* + * bird 2020-10-28: Not entirely sure why we do this, but it makes sure the signal works + * on the new thread. Probably some pre-NPTL linux reasons. + */ + if (g_iSigPokeThread != -1) + { +# if 1 /* siginterrupt() is typically implemented as two sigaction calls, this should be faster and w/o deprecations: */ + struct sigaction SigActOld; + RT_ZERO(SigActOld); + + struct sigaction SigAct; + RT_ZERO(SigAct); + SigAct.sa_handler = rtThreadPosixPokeSignal; + SigAct.sa_flags = 0; /* no SA_RESTART! */ + sigfillset(&SigAct.sa_mask); + + int rc = sigaction(g_iSigPokeThread, &SigAct, &SigActOld); + AssertMsg(rc == 0, ("rc=%Rrc errno=%d\n", RTErrConvertFromErrno(errno), errno)); RT_NOREF(rc); + AssertMsg(rc || SigActOld.sa_handler == rtThreadPosixPokeSignal, ("%p\n", SigActOld.sa_handler)); +# else + siginterrupt(g_iSigPokeThread, 1); +# endif + } +#endif +} + +DECLHIDDEN(void) rtThreadNativeReInitObtrusive(void) +{ +#ifdef RTTHREAD_POSIX_WITH_POKE + Assert(!RTR3InitIsUnobtrusive()); + rtThreadPosixSelectPokeSignal(); +#endif + rtThreadPosixBlockSignals(NULL); +} + + +/** + * Destructor called when a thread terminates. + * @param pvValue The key value. PRTTHREAD in our case. + */ +static void rtThreadKeyDestruct(void *pvValue) +{ + /* + * Deal with alien threads. + */ + PRTTHREADINT pThread = (PRTTHREADINT)pvValue; + if (pThread->fIntFlags & RTTHREADINT_FLAGS_ALIEN) + { + pthread_setspecific(g_SelfKey, pThread); + rtThreadTerminate(pThread, 0); + pthread_setspecific(g_SelfKey, NULL); + } +} + + +#ifdef RTTHREAD_POSIX_WITH_POKE +/** + * Dummy signal handler for the poke signal. + * + * @param iSignal The signal number. + */ +static void rtThreadPosixPokeSignal(int iSignal) +{ + Assert(iSignal == g_iSigPokeThread); + NOREF(iSignal); +} +#endif + + +/** + * Adopts a thread, this is called immediately after allocating the + * thread structure. + * + * @param pThread Pointer to the thread structure. + */ +DECLHIDDEN(int) rtThreadNativeAdopt(PRTTHREADINT pThread) +{ + rtThreadPosixBlockSignals(pThread); + + int rc = pthread_setspecific(g_SelfKey, pThread); + if (!rc) + return VINF_SUCCESS; + return VERR_FAILED_TO_SET_SELF_TLS; +} + + +DECLHIDDEN(void) rtThreadNativeDestroy(PRTTHREADINT pThread) +{ + if (pThread == (PRTTHREADINT)pthread_getspecific(g_SelfKey)) + pthread_setspecific(g_SelfKey, NULL); +} + + +/** + * Wrapper which unpacks the params and calls thread function. + */ +static void *rtThreadNativeMain(void *pvArgs) +{ + PRTTHREADINT pThread = (PRTTHREADINT)pvArgs; + pthread_t Self = pthread_self(); +#if !defined(RT_OS_SOLARIS) /* On Solaris sizeof(pthread_t) = 4 and sizeof(NIL_RTNATIVETHREAD) = 8 */ + Assert((uintptr_t)Self != NIL_RTNATIVETHREAD); +#endif + Assert(Self == (pthread_t)(RTNATIVETHREAD)Self); + +#if defined(RT_OS_LINUX) + /* + * Set the TID. + */ + pThread->tid = syscall(__NR_gettid); + ASMMemoryFence(); +#endif + + rtThreadPosixBlockSignals(pThread); + + /* + * Set the TLS entry and, if possible, the thread name. + */ + int rc = pthread_setspecific(g_SelfKey, pThread); + AssertReleaseMsg(!rc, ("failed to set self TLS. rc=%d thread '%s'\n", rc, pThread->szName)); + +#ifdef IPRT_MAY_HAVE_PTHREAD_SET_NAME_NP + if (g_pfnThreadSetName) +# ifdef RT_OS_DARWIN + g_pfnThreadSetName(pThread->szName); +# else + g_pfnThreadSetName(Self, pThread->szName); +# endif +#endif + + /* + * Call common main. + */ + rc = rtThreadMain(pThread, (uintptr_t)Self, &pThread->szName[0]); + + pthread_setspecific(g_SelfKey, NULL); + pthread_exit((void *)(intptr_t)rc); + return (void *)(intptr_t)rc; +} + +#ifdef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY + +/** + * @callback_method_impl{FNRTTHREAD, + * Priority proxy thread that services g_hRTThreadPosixPriorityProxyQueue.} + */ +static DECLCALLBACK(int) rtThreadPosixPriorityProxyThread(PRTTHREADINT, void *) +{ + for (;;) + { + RTREQQUEUE hReqQueue = g_hRTThreadPosixPriorityProxyQueue; + if (hReqQueue != NIL_RTREQQUEUE) + RTReqQueueProcess(hReqQueue, RT_INDEFINITE_WAIT); + else + break; + + int32_t rc = ASMAtomicUoReadS32(&g_rcPriorityProxyThreadStart); + if (rc != VINF_SUCCESS && rc != VERR_WRONG_ORDER) + break; + } + + return VINF_SUCCESS; +} + + +/** + * Just returns a non-success status codes to force the thread to re-evaluate + * the global shutdown variable. + */ +static DECLCALLBACK(int) rtThreadPosixPriorityProxyStopper(void) +{ + return VERR_CANCELLED; +} + + +/** + * An atexit() callback that stops the proxy creation/priority thread. + */ +static void rtThreadStopProxyThread(void) +{ + /* + * Signal to the thread that it's time to shut down. + */ + int32_t rc = ASMAtomicXchgS32(&g_rcPriorityProxyThreadStart, VERR_PROCESS_NOT_FOUND); + if (RT_SUCCESS(rc)) + { + /* + * Grab the associated handles. + */ + RTTHREAD hThread = g_hRTThreadPosixPriorityProxyThread; + RTREQQUEUE hQueue = g_hRTThreadPosixPriorityProxyQueue; + g_hRTThreadPosixPriorityProxyQueue = NIL_RTREQQUEUE; + g_hRTThreadPosixPriorityProxyThread = NIL_RTTHREAD; + ASMCompilerBarrier(); /* paranoia */ + + AssertReturnVoid(hThread != NIL_RTTHREAD); + AssertReturnVoid(hQueue != NIL_RTREQQUEUE); + + /* + * Kick the thread so it gets out of any pending RTReqQueueProcess call ASAP. + */ + rc = RTReqQueueCallEx(hQueue, NULL, 0 /*cMillies*/, RTREQFLAGS_IPRT_STATUS | RTREQFLAGS_NO_WAIT, + (PFNRT)rtThreadPosixPriorityProxyStopper, 0); + + /* + * Wait for the thread to complete. + */ + rc = RTThreadWait(hThread, RT_SUCCESS(rc) ? RT_MS_1SEC * 5 : 32, NULL); + if (RT_SUCCESS(rc)) + RTReqQueueDestroy(hQueue); + /* else: just leak the stuff, we're exitting, so nobody cares... */ + } +} + + +/** + * Ensure that the proxy priority proxy thread has been started. + * + * Since we will always start a proxy thread when asked to create a thread, + * there is no need for serialization here. + * + * @retval true if started + * @retval false if it failed to start (caller must handle this scenario). + */ +DECLHIDDEN(bool) rtThreadPosixPriorityProxyStart(void) +{ + /* + * Read the result. + */ + int rc = ASMAtomicUoReadS32(&g_rcPriorityProxyThreadStart); + if (rc != VERR_TRY_AGAIN) + return RT_SUCCESS(rc); + + /* If this triggers then there is a very unexpected race somewhere. It + should be harmless though. */ + AssertReturn(ASMAtomicCmpXchgS32(&g_rcPriorityProxyThreadStart, VERR_WRONG_ORDER, VERR_TRY_AGAIN), false); + + /* + * Not yet started, so do that. + */ + rc = RTReqQueueCreate(&g_hRTThreadPosixPriorityProxyQueue); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&g_hRTThreadPosixPriorityProxyThread, rtThreadPosixPriorityProxyThread, NULL, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "RTThrdPP"); + if (RT_SUCCESS(rc)) + { + ASMAtomicWriteS32(&g_rcPriorityProxyThreadStart, VINF_SUCCESS); + + atexit(rtThreadStopProxyThread); + return true; + } + RTReqQueueCreate(&g_hRTThreadPosixPriorityProxyQueue); + } + ASMAtomicWriteS32(&g_rcPriorityProxyThreadStart, rc != VERR_WRONG_ORDER ? rc : VERR_PROCESS_NOT_FOUND); + return false; +} + + +/** + * Calls @a pfnFunction from the priority proxy thread. + * + * Caller must have called rtThreadPosixStartProxy() to check that the priority + * proxy thread is running. + * + * @returns + * @param pTargetThread The target thread, NULL if not applicable. This is + * so we can skip calls pertaining to the priority + * proxy thread itself. + * @param pfnFunction The function to call. Must return IPRT status code. + * @param cArgs Number of arguments (see also RTReqQueueCall). + * @param ... Arguments (see also RTReqQueueCall). + */ +DECLHIDDEN(int) rtThreadPosixPriorityProxyCall(PRTTHREADINT pTargetThread, PFNRT pfnFunction, int cArgs, ...) +{ + int rc; + if ( !pTargetThread + || pTargetThread->pfnThread != rtThreadPosixPriorityProxyThread) + { + va_list va; + va_start(va, cArgs); + PRTREQ pReq; + rc = RTReqQueueCallV(g_hRTThreadPosixPriorityProxyQueue, &pReq, RT_INDEFINITE_WAIT, RTREQFLAGS_IPRT_STATUS, + pfnFunction, cArgs, va); + va_end(va); + RTReqRelease(pReq); + } + else + rc = VINF_SUCCESS; + return rc; +} + +#endif /* !RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY */ + +/** + * Worker for rtThreadNativeCreate that's either called on the priority proxy + * thread or directly on the calling thread depending on the proxy state. + */ +static DECLCALLBACK(int) rtThreadNativeInternalCreate(PRTTHREADINT pThread, PRTNATIVETHREAD pNativeThread) +{ + /* + * Set the default stack size. + */ + if (!pThread->cbStack) + pThread->cbStack = 512*1024; + +#ifdef RT_OS_LINUX + pThread->tid = -1; +#endif + + /* + * Setup thread attributes. + */ + pthread_attr_t ThreadAttr; + int rc = pthread_attr_init(&ThreadAttr); + if (!rc) + { + rc = pthread_attr_setdetachstate(&ThreadAttr, PTHREAD_CREATE_DETACHED); + if (!rc) + { + rc = pthread_attr_setstacksize(&ThreadAttr, pThread->cbStack); + if (!rc) + { + /* + * Create the thread. + */ + pthread_t ThreadId; + rc = pthread_create(&ThreadId, &ThreadAttr, rtThreadNativeMain, pThread); + if (!rc) + { + pthread_attr_destroy(&ThreadAttr); + *pNativeThread = (uintptr_t)ThreadId; + return VINF_SUCCESS; + } + } + } + pthread_attr_destroy(&ThreadAttr); + } + return RTErrConvertFromErrno(rc); +} + + +DECLHIDDEN(int) rtThreadNativeCreate(PRTTHREADINT pThread, PRTNATIVETHREAD pNativeThread) +{ +#ifdef RTTHREAD_POSIX_WITH_CREATE_PRIORITY_PROXY + /* + * If we have a priority proxy thread, use it. Make sure to ignore the + * staring of the proxy thread itself. + */ + if ( pThread->pfnThread != rtThreadPosixPriorityProxyThread + && rtThreadPosixPriorityProxyStart()) + { + PRTREQ pReq; + int rc = RTReqQueueCall(g_hRTThreadPosixPriorityProxyQueue, &pReq, RT_INDEFINITE_WAIT, + (PFNRT)rtThreadNativeInternalCreate, 2, pThread, pNativeThread); + RTReqRelease(pReq); + return rc; + } + + /* + * Fall back on creating it directly without regard to priority proxying. + */ +#endif + return rtThreadNativeInternalCreate(pThread, pNativeThread); +} + + +RTDECL(RTTHREAD) RTThreadSelf(void) +{ + /** @todo import alien threads? */ +#if defined(RT_OS_DARWIN) + /* On darwin, there seems to be input checking with pthread_getspecific. + So, we must prevent using g_SelfKey before rtThreadNativeInit has run, + otherwise we might crash or starting working with total garbage pointer + values here (see _os_tsd_get_direct in znu/libsyscall/os/tsd.h). + + Now, since the init value is a "negative" one, we just have to check + that it's positive or zero before calling the API. */ + if (RT_LIKELY((intptr_t)g_SelfKey >= 0)) + return (PRTTHREADINT)pthread_getspecific(g_SelfKey); + return NIL_RTTHREAD; +#else + return (PRTTHREADINT)pthread_getspecific(g_SelfKey); +#endif +} + + +#ifdef RTTHREAD_POSIX_WITH_POKE + +RTDECL(int) RTThreadPoke(RTTHREAD hThread) +{ + AssertReturn(hThread != RTThreadSelf(), VERR_INVALID_PARAMETER); + PRTTHREADINT pThread = rtThreadGet(hThread); + AssertReturn(pThread, VERR_INVALID_HANDLE); + + int rc; + if (g_iSigPokeThread != -1) + { + rc = pthread_kill((pthread_t)(uintptr_t)pThread->Core.Key, g_iSigPokeThread); + rc = RTErrConvertFromErrno(rc); + } + else + rc = VERR_NOT_SUPPORTED; + + rtThreadRelease(pThread); + return rc; +} + + +RTDECL(int) RTThreadControlPokeSignal(RTTHREAD hThread, bool fEnable) +{ + AssertReturn(hThread == RTThreadSelf() && hThread != NIL_RTTHREAD, VERR_INVALID_PARAMETER); + int rc; + if (g_iSigPokeThread != -1) + { + sigset_t SigSet; + sigemptyset(&SigSet); + sigaddset(&SigSet, g_iSigPokeThread); + + int rc2 = sigprocmask(fEnable ? SIG_UNBLOCK : SIG_BLOCK, &SigSet, NULL); + if (rc2 == 0) + rc = VINF_SUCCESS; + else + { + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("rc=%Rrc errno=%d (rc2=%d)\n", rc, errno, rc2)); + } + } + else + rc = VERR_NOT_SUPPORTED; + return rc; +} + + +#endif + +/** @todo move this into platform specific files. */ +RTR3DECL(int) RTThreadGetExecutionTimeMilli(uint64_t *pKernelTime, uint64_t *pUserTime) +{ +#if defined(RT_OS_SOLARIS) + struct rusage ts; + int rc = getrusage(RUSAGE_LWP, &ts); + if (rc) + return RTErrConvertFromErrno(rc); + + *pKernelTime = ts.ru_stime.tv_sec * 1000 + ts.ru_stime.tv_usec / 1000; + *pUserTime = ts.ru_utime.tv_sec * 1000 + ts.ru_utime.tv_usec / 1000; + return VINF_SUCCESS; + +#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) + /* on Linux, getrusage(RUSAGE_THREAD, ...) is available since 2.6.26 */ + struct timespec ts; + int rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); + if (rc) + return RTErrConvertFromErrno(rc); + + *pKernelTime = 0; + *pUserTime = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + return VINF_SUCCESS; + +#elif defined(RT_OS_DARWIN) + thread_basic_info ThreadInfo; + mach_msg_type_number_t Count = THREAD_BASIC_INFO_COUNT; + kern_return_t krc = thread_info(mach_thread_self(), THREAD_BASIC_INFO, (thread_info_t)&ThreadInfo, &Count); + AssertReturn(krc == KERN_SUCCESS, RTErrConvertFromDarwinKern(krc)); + + *pKernelTime = ThreadInfo.system_time.seconds * 1000 + ThreadInfo.system_time.microseconds / 1000; + *pUserTime = ThreadInfo.user_time.seconds * 1000 + ThreadInfo.user_time.microseconds / 1000; + + return VINF_SUCCESS; +#elif defined(RT_OS_HAIKU) + thread_info ThreadInfo; + status_t status = get_thread_info(find_thread(NULL), &ThreadInfo); + AssertReturn(status == B_OK, RTErrConvertFromErrno(status)); + + *pKernelTime = ThreadInfo.kernel_time / 1000; + *pUserTime = ThreadInfo.user_time / 1000; + + return VINF_SUCCESS; +#else + return VERR_NOT_IMPLEMENTED; +#endif +} + diff --git a/src/VBox/Runtime/r3/posix/thread2-posix.cpp b/src/VBox/Runtime/r3/posix/thread2-posix.cpp new file mode 100644 index 00000000..6eaee22e --- /dev/null +++ b/src/VBox/Runtime/r3/posix/thread2-posix.cpp @@ -0,0 +1,133 @@ +/* $Id: thread2-posix.cpp $ */ +/** @file + * IPRT - Threads part 2, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sched.h> + +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/asm.h> +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include <iprt/asm-amd64-x86.h> +#endif +#include <iprt/errcore.h> +#include "internal/thread.h" + + +RTDECL(RTNATIVETHREAD) RTThreadNativeSelf(void) +{ + return (RTNATIVETHREAD)pthread_self(); +} + + +RTDECL(int) RTThreadSleep(RTMSINTERVAL cMillies) +{ + LogFlow(("RTThreadSleep: cMillies=%d\n", cMillies)); + if (!cMillies) + { + if (!sched_yield()) + { + LogFlow(("RTThreadSleep: returning %Rrc (cMillies=%d)\n", VINF_SUCCESS, cMillies)); + return VINF_SUCCESS; + } + } + else + { + struct timespec ts; + struct timespec tsrem = {0,0}; + + ts.tv_nsec = (cMillies % 1000) * 1000000; + ts.tv_sec = cMillies / 1000; + if (!nanosleep(&ts, &tsrem)) + { + LogFlow(("RTThreadSleep: returning %Rrc (cMillies=%d)\n", VINF_SUCCESS, cMillies)); + return VINF_SUCCESS; + } + } + + int rc = RTErrConvertFromErrno(errno); + LogFlow(("RTThreadSleep: returning %Rrc (cMillies=%d)\n", rc, cMillies)); + return rc; +} + + +RTDECL(int) RTThreadSleepNoLog(RTMSINTERVAL cMillies) +{ + if (!cMillies) + { + if (!sched_yield()) + return VINF_SUCCESS; + } + else + { + struct timespec ts; + struct timespec tsrem = {0,0}; + + ts.tv_nsec = (cMillies % 1000) * 1000000; + ts.tv_sec = cMillies / 1000; + if (!nanosleep(&ts, &tsrem)) + return VINF_SUCCESS; + } + + return RTErrConvertFromErrno(errno); +} + + +RTDECL(bool) RTThreadYield(void) +{ +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + uint64_t u64TS = ASMReadTSC(); +#endif + + sched_yield(); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + u64TS = ASMReadTSC() - u64TS; + bool fRc = u64TS > 1500; + LogFlow(("RTThreadYield: returning %d (%llu ticks)\n", fRc, u64TS)); +#else + bool fRc = true; /* PORTME: Add heuristics for determining whether the cpus was yielded. */ +#endif + return fRc; +} + diff --git a/src/VBox/Runtime/r3/posix/time-posix.cpp b/src/VBox/Runtime/r3/posix/time-posix.cpp new file mode 100644 index 00000000..3c028b73 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/time-posix.cpp @@ -0,0 +1,99 @@ +/* $Id: time-posix.cpp $ */ +/** @file + * IPRT - Time, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define RTTIME_INCL_TIMEVAL +#include <sys/time.h> +#include <time.h> + +#include <iprt/time.h> +#include "internal/time.h" + + +DECLINLINE(uint64_t) rtTimeGetSystemNanoTS(void) +{ +#if defined(CLOCK_MONOTONIC) && !defined(RT_OS_L4) && !defined(RT_OS_OS2) + /* check monotonic clock first. */ + static bool s_fMonoClock = true; + if (s_fMonoClock) + { + struct timespec ts; + if (!clock_gettime(CLOCK_MONOTONIC, &ts)) + return (uint64_t)ts.tv_sec * RT_NS_1SEC_64 + + ts.tv_nsec; + s_fMonoClock = false; + } +#endif + + /* fallback to gettimeofday(). */ + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * RT_NS_1SEC_64 + + (uint64_t)(tv.tv_usec * RT_NS_1US); +} + + +/** + * Gets the current nanosecond timestamp. + * + * This differs from RTTimeNanoTS in that it will use system APIs and not do any + * resolution or performance optimizations. + * + * @returns nanosecond timestamp. + */ +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return rtTimeGetSystemNanoTS(); +} + + +/** + * Gets the current millisecond timestamp. + * + * This differs from RTTimeNanoTS in that it will use system APIs and not do any + * resolution or performance optimizations. + * + * @returns millisecond timestamp. + */ +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return rtTimeGetSystemNanoTS() / RT_NS_1MS; +} + diff --git a/src/VBox/Runtime/r3/posix/timelocal-posix.cpp b/src/VBox/Runtime/r3/posix/timelocal-posix.cpp new file mode 100644 index 00000000..40e2fc16 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/timelocal-posix.cpp @@ -0,0 +1,215 @@ +/* $Id $ */ +/** @file + * IPRT - Local Time, Posix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#define RTTIME_INCL_TIMEVAL +#include <iprt/types.h> +#include <iprt/assert.h> + +#include <sys/time.h> +#include <time.h> + +#include <iprt/time.h> + + +/** + * This tries to find the UTC offset for a given timespec. + * + * It does probably not take into account changes in daylight + * saving over the years or similar stuff. + * + * @returns UTC offset in nanoseconds. + * @param pTime The time. + * @param fCurrentTime Whether the input is current time or not. + * This is for avoid infinit recursion on errors in the fallback path. + */ +static int64_t rtTimeLocalUTCOffset(PCRTTIMESPEC pTime, bool fCurrentTime) +{ + RTTIMESPEC Fallback; + + /* + * Convert to time_t. + */ + int64_t i64UnixTime = RTTimeSpecGetSeconds(pTime); + time_t UnixTime = i64UnixTime; + if (UnixTime != i64UnixTime) + return fCurrentTime ? 0 : rtTimeLocalUTCOffset(RTTimeNow(&Fallback), true); + + /* + * Explode it as both local and UTC time. + */ + struct tm TmLocal; + if ( !localtime_r(&UnixTime, &TmLocal) + || !TmLocal.tm_year) + return fCurrentTime ? 0 : rtTimeLocalUTCOffset(RTTimeNow(&Fallback), true); + struct tm TmUtc; + if (!gmtime_r(&UnixTime, &TmUtc)) + return fCurrentTime ? 0 : rtTimeLocalUTCOffset(RTTimeNow(&Fallback), true); + + /* + * Calc the difference (if any). + * We ASSUME that the difference is less that 24 hours. + */ + if ( TmLocal.tm_hour == TmUtc.tm_hour + && TmLocal.tm_min == TmUtc.tm_min + && TmLocal.tm_sec == TmUtc.tm_sec + && TmLocal.tm_mday == TmUtc.tm_mday) + return 0; + + int cLocalSecs = TmLocal.tm_hour * 3600 + + TmLocal.tm_min * 60 + + TmLocal.tm_sec; + int cUtcSecs = TmUtc.tm_hour * 3600 + + TmUtc.tm_min * 60 + + TmUtc.tm_sec; + if (TmLocal.tm_mday != TmUtc.tm_mday) + { + /* + * Must add 24 hours to the value that is ahead of the other. + * + * To determine which is ahead was busted for a long long time (bugref:9078), + * so here are some examples and two different approaches. + * + * TmLocal TmUtc => Add 24:00 to => Diff + * 2007-04-02 01:00 2007-04-01 23:00 => TmLocal => +02:00 + * 2007-04-01 01:00 2007-03-31 23:00 => TmLocal => +02:00 + * 2007-03-31 01:00 2007-03-30 23:00 => TmLocal => +02:00 + * + * 2007-04-01 01:00 2007-04-02 23:00 => TmUtc => -02:00 + * 2007-03-31 23:00 2007-04-01 01:00 => TmUtc => -02:00 + * 2007-03-30 23:00 2007-03-31 01:00 => TmUtc => -02:00 + * + */ +#if 0 + /* Using day of month turned out to be a little complicated. */ + if ( ( TmLocal.tm_mday > TmUtc.tm_mday + && (TmUtc.tm_mday != 1 || TmLocal.tm_mday < 28) ) + || (TmLocal.tm_mday == 1 && TmUtc.tm_mday >= 28) ) + { + cLocalSecs += 24*60*60; + Assert( TmLocal.tm_yday - TmUtc.tm_yday == 1 + || (TmLocal.tm_yday == 0 && TmUtc.tm_yday >= 364 && TmLocal.tm_year == TmUtc.tm_year + 1)); + } + else + { + cUtcSecs += 24*60*60; + Assert( TmUtc.tm_yday - TmLocal.tm_yday == 1 + || (TmUtc.tm_yday == 0 && TmLocal.tm_yday >= 364 && TmUtc.tm_year == TmLocal.tm_year + 1)); + } +#else + /* Using day of year and year is simpler. */ + if ( ( TmLocal.tm_year == TmUtc.tm_year + && TmLocal.tm_yday > TmUtc.tm_yday) + || TmLocal.tm_year > TmUtc.tm_year) + { + cLocalSecs += 24*60*60; + Assert( TmLocal.tm_yday - TmUtc.tm_yday == 1 + || (TmLocal.tm_yday == 0 && TmUtc.tm_yday >= 364 && TmLocal.tm_year == TmUtc.tm_year + 1)); + } + else + { + cUtcSecs += 24*60*60; + Assert( TmUtc.tm_yday - TmLocal.tm_yday == 1 + || (TmUtc.tm_yday == 0 && TmLocal.tm_yday >= 364 && TmUtc.tm_year == TmLocal.tm_year + 1)); + } +#endif + } + + return (cLocalSecs - cUtcSecs) * INT64_C(1000000000); +} + + +/** + * Gets the current delta between UTC and local time. + * + * @code + * RTTIMESPEC LocalTime; + * RTTimeSpecAddNano(RTTimeNow(&LocalTime), RTTimeLocalDeltaNano()); + * @endcode + * + * @returns Returns the nanosecond delta between UTC and local time. + */ +RTDECL(int64_t) RTTimeLocalDeltaNano(void) +{ + RTTIMESPEC Time; + return rtTimeLocalUTCOffset(RTTimeNow(&Time), true /* current time, skip fallback */); +} + + +/** + * Gets the delta between UTC and local time at the given time. + * + * @code + * RTTIMESPEC LocalTime; + * RTTimeNow(&LocalTime); + * RTTimeSpecAddNano(&LocalTime, RTTimeLocalDeltaNanoFor(&LocalTime)); + * @endcode + * + * @param pTimeSpec The time spec giving the time to get the delta for. + * @returns Returns the nanosecond delta between UTC and local time. + */ +RTDECL(int64_t) RTTimeLocalDeltaNanoFor(PCRTTIMESPEC pTimeSpec) +{ + AssertPtr(pTimeSpec); + return rtTimeLocalUTCOffset(pTimeSpec, false /* current time, skip fallback */); +} + + +/** + * Explodes a time spec to the localized timezone. + * + * @returns pTime. + * @param pTime Where to store the exploded time. + * @param pTimeSpec The time spec to exploded. (UTC) + */ +RTDECL(PRTTIME) RTTimeLocalExplode(PRTTIME pTime, PCRTTIMESPEC pTimeSpec) +{ + RTTIMESPEC LocalTime = *pTimeSpec; + int64_t cNsUtcOffset = rtTimeLocalUTCOffset(&LocalTime, true /* current time, skip fallback */); + RTTimeSpecAddNano(&LocalTime, cNsUtcOffset); + pTime = RTTimeExplode(pTime, &LocalTime); + if (pTime) + { + pTime->fFlags = (pTime->fFlags & ~RTTIME_FLAGS_TYPE_MASK) | RTTIME_FLAGS_TYPE_LOCAL; + pTime->offUTC = cNsUtcOffset / RT_NS_1MIN; + } + return pTime; +} + diff --git a/src/VBox/Runtime/r3/posix/timer-posix.cpp b/src/VBox/Runtime/r3/posix/timer-posix.cpp new file mode 100644 index 00000000..59c905b9 --- /dev/null +++ b/src/VBox/Runtime/r3/posix/timer-posix.cpp @@ -0,0 +1,847 @@ +/* $Id: timer-posix.cpp $ */ +/** @file + * IPRT - Timer, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Enables the use of POSIX RT timers. */ +#ifndef RT_OS_SOLARIS /* Solaris 10 doesn't have SIGEV_THREAD */ +# define IPRT_WITH_POSIX_TIMERS +#endif /* !RT_OS_SOLARIS */ + +/** @def RT_TIMER_SIGNAL + * The signal number that the timers use. + * We currently use SIGALRM for both setitimer and posix real time timers + * out of simplicity, but we might want change this later for the posix ones. */ +#ifdef IPRT_WITH_POSIX_TIMERS +# define RT_TIMER_SIGNAL SIGALRM +#else +# define RT_TIMER_SIGNAL SIGALRM +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIMER +#include <iprt/timer.h> +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/asm.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/once.h> +#include <iprt/err.h> +#include <iprt/initterm.h> +#include <iprt/critsect.h> +#include "internal/magics.h" + +#include <unistd.h> +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#ifdef RT_OS_LINUX +# include <linux/rtc.h> +#endif +#include <sys/time.h> +#include <signal.h> +#include <errno.h> +#include <pthread.h> +#if defined(RT_OS_DARWIN) +# define sigprocmask pthread_sigmask /* On xnu sigprocmask works on the process, not the calling thread as elsewhere. */ +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef IPRT_WITH_POSIX_TIMERS +/** Init the critsect on first call. */ +static RTONCE g_TimerOnce = RTONCE_INITIALIZER; +/** Global critsect that serializes timer creation and destruction. + * This is lazily created on the first RTTimerCreateEx call and will not be + * freed up (I'm afraid). */ +static RTCRITSECT g_TimerCritSect; +/** + * Global counter of RTTimer instances. The signal thread is + * started when it changes from 0 to 1. The signal thread + * terminates when it becomes 0 again. + */ +static uint32_t volatile g_cTimerInstances; +/** The signal handling thread. */ +static RTTHREAD g_TimerThread; +#endif /* IPRT_WITH_POSIX_TIMERS */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The internal representation of a timer handle. + */ +typedef struct RTTIMER +{ + /** Magic. + * This is RTTIMER_MAGIC, but changes to something else before the timer + * is destroyed to indicate clearly that thread should exit. */ + uint32_t volatile u32Magic; + /** Flag indicating the timer is suspended. */ + uint8_t volatile fSuspended; + /** Flag indicating that the timer has been destroyed. */ + uint8_t volatile fDestroyed; +#ifndef IPRT_WITH_POSIX_TIMERS /** @todo We have to take the signals on a dedicated timer thread as + * we (might) have code assuming that signals doesn't screw around + * on existing threads. (It would be sufficient to have one thread + * per signal of course since the signal will be masked while it's + * running, however, it may just cause more complications than its + * worth - sigwait/sigwaitinfo work atomically anyway...) + * Also, must block the signal in the thread main procedure too. */ + /** The timer thread. */ + RTTHREAD Thread; + /** Event semaphore on which the thread is blocked. */ + RTSEMEVENT Event; +#endif /* !IPRT_WITH_POSIX_TIMERS */ + /** User argument. */ + void *pvUser; + /** Callback. */ + PFNRTTIMER pfnTimer; + /** The timer interval. 0 if one-shot. */ + uint64_t u64NanoInterval; +#ifndef IPRT_WITH_POSIX_TIMERS + /** The first shot interval. 0 if ASAP. */ + uint64_t volatile u64NanoFirst; +#endif /* !IPRT_WITH_POSIX_TIMERS */ + /** The current timer tick. */ + uint64_t volatile iTick; +#ifndef IPRT_WITH_POSIX_TIMERS + /** The error/status of the timer. + * Initially -1, set to 0 when the timer have been successfully started, and + * to errno on failure in starting the timer. */ + int volatile iError; +#else /* IPRT_WITH_POSIX_TIMERS */ + timer_t NativeTimer; +#endif /* IPRT_WITH_POSIX_TIMERS */ + +} RTTIMER; + + + +#ifdef IPRT_WITH_POSIX_TIMERS + +/** + * RTOnce callback that initializes the critical section. + * + * @returns RTCritSectInit return code. + * @param pvUser NULL, ignored. + * + */ +static DECLCALLBACK(int) rtTimerOnce(void *pvUser) +{ + NOREF(pvUser); + return RTCritSectInit(&g_TimerCritSect); +} +#endif + + +/** + * Signal handler which ignore everything it gets. + * + * @param iSignal The signal number. + */ +static void rttimerSignalIgnore(int iSignal) +{ + //AssertBreakpoint(); + NOREF(iSignal); +} + + +/** + * RT_TIMER_SIGNAL wait thread. + */ +static DECLCALLBACK(int) rttimerThread(RTTHREAD hThreadSelf, void *pvArg) +{ + NOREF(hThreadSelf); NOREF(pvArg); +#ifndef IPRT_WITH_POSIX_TIMERS + PRTTIMER pTimer = (PRTTIMER)pvArg; + RTTIMER Timer = *pTimer; + Assert(pTimer->u32Magic == RTTIMER_MAGIC); +#endif /* !IPRT_WITH_POSIX_TIMERS */ + + /* + * Install signal handler. + */ + struct sigaction SigAct; + memset(&SigAct, 0, sizeof(SigAct)); + SigAct.sa_flags = SA_RESTART; + sigemptyset(&SigAct.sa_mask); + SigAct.sa_handler = rttimerSignalIgnore; + if (sigaction(RT_TIMER_SIGNAL, &SigAct, NULL)) + { + SigAct.sa_flags &= ~SA_RESTART; + if (sigaction(RT_TIMER_SIGNAL, &SigAct, NULL)) + AssertMsgFailed(("sigaction failed, errno=%d\n", errno)); + } + + /* + * Mask most signals except those which might be used by the pthread implementation (linux). + */ + sigset_t SigSet; + sigfillset(&SigSet); + sigdelset(&SigSet, SIGTERM); + sigdelset(&SigSet, SIGHUP); + sigdelset(&SigSet, SIGINT); + sigdelset(&SigSet, SIGABRT); + sigdelset(&SigSet, SIGKILL); +#ifdef SIGRTMIN + for (int iSig = SIGRTMIN; iSig < SIGRTMAX; iSig++) + sigdelset(&SigSet, iSig); +#endif + if (sigprocmask(SIG_SETMASK, &SigSet, NULL)) + { +#ifdef IPRT_WITH_POSIX_TIMERS + int rc = RTErrConvertFromErrno(errno); +#else + int rc = pTimer->iError = RTErrConvertFromErrno(errno); +#endif + AssertMsgFailed(("sigprocmask -> errno=%d\n", errno)); + return rc; + } + + /* + * The work loop. + */ + RTThreadUserSignal(hThreadSelf); + +#ifndef IPRT_WITH_POSIX_TIMERS + while ( !pTimer->fDestroyed + && pTimer->u32Magic == RTTIMER_MAGIC) + { + /* + * Wait for a start or destroy event. + */ + if (pTimer->fSuspended) + { + int rc = RTSemEventWait(pTimer->Event, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc) && rc != VERR_INTERRUPTED) + { + AssertRC(rc); + if (pTimer->fDestroyed) + continue; + RTThreadSleep(1000); /* Don't cause trouble! */ + } + if ( pTimer->fSuspended + || pTimer->fDestroyed) + continue; + } + + /* + * Start the timer. + * + * For some SunOS (/SysV?) threading compatibility Linux will only + * deliver the RT_TIMER_SIGNAL to the thread calling setitimer(). Therefore + * we have to call it here. + * + * It turns out this might not always be the case, see RT_TIMER_SIGNAL killing + * processes on RH 2.4.21. + */ + struct itimerval TimerVal; + if (pTimer->u64NanoFirst) + { + uint64_t u64 = RT_MAX(1000, pTimer->u64NanoFirst); + TimerVal.it_value.tv_sec = u64 / 1000000000; + TimerVal.it_value.tv_usec = (u64 % 1000000000) / 1000; + } + else + { + TimerVal.it_value.tv_sec = 0; + TimerVal.it_value.tv_usec = 10; + } + if (pTimer->u64NanoInterval) + { + uint64_t u64 = RT_MAX(1000, pTimer->u64NanoInterval); + TimerVal.it_interval.tv_sec = u64 / 1000000000; + TimerVal.it_interval.tv_usec = (u64 % 1000000000) / 1000; + } + else + { + TimerVal.it_interval.tv_sec = 0; + TimerVal.it_interval.tv_usec = 0; + } + + if (setitimer(ITIMER_REAL, &TimerVal, NULL)) + { + ASMAtomicXchgU8(&pTimer->fSuspended, true); + pTimer->iError = RTErrConvertFromErrno(errno); + RTThreadUserSignal(hThreadSelf); + continue; /* back to suspended mode. */ + } + pTimer->iError = 0; + RTThreadUserSignal(hThreadSelf); + + /* + * Timer Service Loop. + */ + sigemptyset(&SigSet); + sigaddset(&SigSet, RT_TIMER_SIGNAL); + do + { + siginfo_t SigInfo; + RT_ZERO(SigInfo); +#ifdef RT_OS_DARWIN + if (RT_LIKELY(sigwait(&SigSet, &SigInfo.si_signo) >= 0)) + { +#else + if (RT_LIKELY(sigwaitinfo(&SigSet, &SigInfo) >= 0)) + { + if (RT_LIKELY(SigInfo.si_signo == RT_TIMER_SIGNAL)) +#endif + { + if (RT_UNLIKELY( pTimer->fSuspended + || pTimer->fDestroyed + || pTimer->u32Magic != RTTIMER_MAGIC)) + break; + + pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick); + + /* auto suspend one-shot timers. */ + if (RT_UNLIKELY(!pTimer->u64NanoInterval)) + { + ASMAtomicWriteU8(&pTimer->fSuspended, true); + break; + } + } + } + else if (errno != EINTR) + AssertMsgFailed(("sigwaitinfo -> errno=%d\n", errno)); + } while (RT_LIKELY( !pTimer->fSuspended + && !pTimer->fDestroyed + && pTimer->u32Magic == RTTIMER_MAGIC)); + + /* + * Disable the timer. + */ + struct itimerval TimerVal2 = {{0,0}, {0,0}}; + if (setitimer(ITIMER_REAL, &TimerVal2, NULL)) + AssertMsgFailed(("setitimer(ITIMER_REAL,&{0}, NULL) failed, errno=%d\n", errno)); + + /* + * ACK any pending suspend request. + */ + if (!pTimer->fDestroyed) + { + pTimer->iError = 0; + RTThreadUserSignal(hThreadSelf); + } + } + + /* + * Exit. + */ + pTimer->iError = 0; + RTThreadUserSignal(hThreadSelf); + +#else /* IPRT_WITH_POSIX_TIMERS */ + + sigemptyset(&SigSet); + sigaddset(&SigSet, RT_TIMER_SIGNAL); + while (g_cTimerInstances) + { + siginfo_t SigInfo; + RT_ZERO(SigInfo); + if (RT_LIKELY(sigwaitinfo(&SigSet, &SigInfo) >= 0)) + { + LogFlow(("rttimerThread: signo=%d pTimer=%p\n", SigInfo.si_signo, SigInfo.si_value.sival_ptr)); + if (RT_LIKELY( SigInfo.si_signo == RT_TIMER_SIGNAL + && SigInfo.si_code == SI_TIMER)) /* The SI_TIMER check is *essential* because of the pthread_kill. */ + { + PRTTIMER pTimer = (PRTTIMER)SigInfo.si_value.sival_ptr; + AssertPtr(pTimer); + if (RT_UNLIKELY( !RT_VALID_PTR(pTimer) + || ASMAtomicUoReadU8(&pTimer->fSuspended) + || ASMAtomicUoReadU8(&pTimer->fDestroyed) + || pTimer->u32Magic != RTTIMER_MAGIC)) + continue; + + pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick); + + /* auto suspend one-shot timers. */ + if (RT_UNLIKELY(!pTimer->u64NanoInterval)) + ASMAtomicWriteU8(&pTimer->fSuspended, true); + } + } + } +#endif /* IPRT_WITH_POSIX_TIMERS */ + + return VINF_SUCCESS; +} + + +RTDECL(int) RTTimerCreateEx(PRTTIMER *ppTimer, uint64_t u64NanoInterval, uint32_t fFlags, PFNRTTIMER pfnTimer, void *pvUser) +{ + /* + * We don't support the fancy MP features. + */ + if (fFlags & RTTIMER_FLAGS_CPU_SPECIFIC) + return VERR_NOT_SUPPORTED; + + /* + * We need the signal masks to be set correctly, which they won't be in + * unobtrusive mode. + */ + if (RTR3InitIsUnobtrusive()) + return VERR_NOT_SUPPORTED; + +#ifndef IPRT_WITH_POSIX_TIMERS + /* + * Check if timer is busy. + */ + struct itimerval TimerVal; + if (getitimer(ITIMER_REAL, &TimerVal)) + { + AssertMsgFailed(("getitimer() -> errno=%d\n", errno)); + return VERR_NOT_IMPLEMENTED; + } + if ( TimerVal.it_value.tv_usec + || TimerVal.it_value.tv_sec + || TimerVal.it_interval.tv_usec + || TimerVal.it_interval.tv_sec) + { + AssertMsgFailed(("A timer is running. System limit is one timer per process!\n")); + return VERR_TIMER_BUSY; + } +#endif /* !IPRT_WITH_POSIX_TIMERS */ + + /* + * Block RT_TIMER_SIGNAL from calling thread. + */ + sigset_t SigSet; + sigemptyset(&SigSet); + sigaddset(&SigSet, RT_TIMER_SIGNAL); + sigprocmask(SIG_BLOCK, &SigSet, NULL); + +#ifndef IPRT_WITH_POSIX_TIMERS /** @todo combine more of the setitimer/timer_create code. setitimer could also use the global thread. */ + /** @todo Move this RTC hack else where... */ + static bool fDoneRTC; + if (!fDoneRTC) + { + fDoneRTC = true; + /* check resolution. */ + TimerVal.it_interval.tv_sec = 0; + TimerVal.it_interval.tv_usec = 1000; + TimerVal.it_value = TimerVal.it_interval; + if ( setitimer(ITIMER_REAL, &TimerVal, NULL) + || getitimer(ITIMER_REAL, &TimerVal) + || TimerVal.it_interval.tv_usec > 1000) + { + /* + * Try open /dev/rtc to set the irq rate to 1024 and + * turn periodic + */ + Log(("RTTimerCreate: interval={%ld,%ld} trying to adjust /dev/rtc!\n", TimerVal.it_interval.tv_sec, TimerVal.it_interval.tv_usec)); +# ifdef RT_OS_LINUX + int fh = open("/dev/rtc", O_RDONLY); + if (fh >= 0) + { + if ( ioctl(fh, RTC_IRQP_SET, 1024) < 0 + || ioctl(fh, RTC_PIE_ON, 0) < 0) + Log(("RTTimerCreate: couldn't configure rtc! errno=%d\n", errno)); + ioctl(fh, F_SETFL, O_ASYNC); + ioctl(fh, F_SETOWN, getpid()); + /* not so sure if closing it is a good idea... */ + //close(fh); + } + else + Log(("RTTimerCreate: couldn't configure rtc! open failed with errno=%d\n", errno)); +# endif + } + /* disable it */ + TimerVal.it_interval.tv_sec = 0; + TimerVal.it_interval.tv_usec = 0; + TimerVal.it_value = TimerVal.it_interval; + setitimer(ITIMER_REAL, &TimerVal, NULL); + } + + /* + * Create a new timer. + */ + int rc; + PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer)); + if (pTimer) + { + pTimer->u32Magic = RTTIMER_MAGIC; + pTimer->fSuspended = true; + pTimer->fDestroyed = false; + pTimer->Thread = NIL_RTTHREAD; + pTimer->Event = NIL_RTSEMEVENT; + pTimer->pfnTimer = pfnTimer; + pTimer->pvUser = pvUser; + pTimer->u64NanoInterval = u64NanoInterval; + pTimer->u64NanoFirst = 0; + pTimer->iTick = 0; + pTimer->iError = 0; + rc = RTSemEventCreate(&pTimer->Event); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&pTimer->Thread, rttimerThread, pTimer, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "Timer"); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /* + * Wait for the timer thread to initialize it self. + * This might take a little while... + */ + rc = RTThreadUserWait(pTimer->Thread, 45*1000); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserReset(pTimer->Thread); AssertRC(rc); + rc = pTimer->iError; + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RTThreadYield(); /* <-- Horrible hack to make tstTimer work. (linux 2.6.12) */ + *ppTimer = pTimer; + return VINF_SUCCESS; + } + } + + /* bail out */ + ASMAtomicXchgU8(&pTimer->fDestroyed, true); + ASMAtomicXchgU32(&pTimer->u32Magic, ~RTTIMER_MAGIC); + RTThreadWait(pTimer->Thread, 45*1000, NULL); + } + RTSemEventDestroy(pTimer->Event); + pTimer->Event = NIL_RTSEMEVENT; + } + RTMemFree(pTimer); + } + else + rc = VERR_NO_MEMORY; + +#else /* IPRT_WITH_POSIX_TIMERS */ + + /* + * Do the global init first. + */ + int rc = RTOnce(&g_TimerOnce, rtTimerOnce, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Create a new timer structure. + */ + LogFlow(("RTTimerCreateEx: u64NanoInterval=%llu fFlags=%lu\n", u64NanoInterval, fFlags)); + PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer)); + if (pTimer) + { + /* Initialize timer structure. */ + pTimer->u32Magic = RTTIMER_MAGIC; + pTimer->fSuspended = true; + pTimer->fDestroyed = false; + pTimer->pfnTimer = pfnTimer; + pTimer->pvUser = pvUser; + pTimer->u64NanoInterval = u64NanoInterval; + pTimer->iTick = 0; + + /* + * Create a timer that deliver RT_TIMER_SIGNAL upon timer expiration. + */ + struct sigevent SigEvt; + SigEvt.sigev_notify = SIGEV_SIGNAL; + SigEvt.sigev_signo = RT_TIMER_SIGNAL; + SigEvt.sigev_value.sival_ptr = pTimer; /* sigev_value gets copied to siginfo. */ + int err = timer_create(CLOCK_REALTIME, &SigEvt, &pTimer->NativeTimer); + if (!err) + { + /* + * Increment the timer count, do this behind the critsect to avoid races. + */ + RTCritSectEnter(&g_TimerCritSect); + + if (ASMAtomicIncU32(&g_cTimerInstances) != 1) + { + Assert(g_cTimerInstances > 1); + RTCritSectLeave(&g_TimerCritSect); + + LogFlow(("RTTimerCreateEx: rc=%Rrc pTimer=%p (thread already running)\n", rc, pTimer)); + *ppTimer = pTimer; + return VINF_SUCCESS; + } + + /* + * Create the signal handling thread. It will wait for the signal + * and execute the timer functions. + */ + rc = RTThreadCreate(&g_TimerThread, rttimerThread, NULL, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "Timer"); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserWait(g_TimerThread, 45*1000); /* this better not fail... */ + if (RT_SUCCESS(rc)) + { + RTCritSectLeave(&g_TimerCritSect); + + LogFlow(("RTTimerCreateEx: rc=%Rrc pTimer=%p (thread already running)\n", rc, pTimer)); + *ppTimer = pTimer; + return VINF_SUCCESS; + } + /* darn, what do we do here? */ + } + + /* bail out */ + ASMAtomicDecU32(&g_cTimerInstances); + Assert(!g_cTimerInstances); + + RTCritSectLeave(&g_TimerCritSect); + + timer_delete(pTimer->NativeTimer); + } + else + { + rc = RTErrConvertFromErrno(err); + Log(("RTTimerCreateEx: err=%d (%Rrc)\n", err, rc)); + } + + RTMemFree(pTimer); + } + else + rc = VERR_NO_MEMORY; + +#endif /* IPRT_WITH_POSIX_TIMERS */ + return rc; +} + + +RTR3DECL(int) RTTimerDestroy(PRTTIMER pTimer) +{ + LogFlow(("RTTimerDestroy: pTimer=%p\n", pTimer)); + + /* + * Validate input. + */ + /* NULL is ok. */ + if (!pTimer) + return VINF_SUCCESS; + int rc = VINF_SUCCESS; + AssertPtrReturn(pTimer, VERR_INVALID_POINTER); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); +#ifdef IPRT_WITH_POSIX_TIMERS + AssertReturn(g_TimerThread != RTThreadSelf(), VERR_INTERNAL_ERROR); +#else + AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR); +#endif + + /* + * Mark the semaphore as destroyed. + */ + ASMAtomicWriteU8(&pTimer->fDestroyed, true); + ASMAtomicWriteU32(&pTimer->u32Magic, ~RTTIMER_MAGIC); + +#ifdef IPRT_WITH_POSIX_TIMERS + /* + * Suspend the timer if it's running. + */ + if (!pTimer->fSuspended) + { + struct itimerspec TimerSpec; + TimerSpec.it_value.tv_sec = 0; + TimerSpec.it_value.tv_nsec = 0; + TimerSpec.it_interval.tv_sec = 0; + TimerSpec.it_interval.tv_nsec = 0; + int err = timer_settime(pTimer->NativeTimer, 0, &TimerSpec, NULL); NOREF(err); + AssertMsg(!err, ("%d / %d\n", err, errno)); + } +#endif + + /* + * Poke the thread and wait for it to finish. + * This is only done for the last timer when using posix timers. + */ +#ifdef IPRT_WITH_POSIX_TIMERS + RTTHREAD Thread = NIL_RTTHREAD; + RTCritSectEnter(&g_TimerCritSect); + if (ASMAtomicDecU32(&g_cTimerInstances) == 0) + { + Thread = g_TimerThread; + g_TimerThread = NIL_RTTHREAD; + } + RTCritSectLeave(&g_TimerCritSect); +#else /* IPRT_WITH_POSIX_TIMERS */ + RTTHREAD Thread = pTimer->Thread; + rc = RTSemEventSignal(pTimer->Event); + AssertRC(rc); +#endif /* IPRT_WITH_POSIX_TIMERS */ + if (Thread != NIL_RTTHREAD) + { + /* Signal it so it gets out of the sigwait if it's stuck there... */ + pthread_kill((pthread_t)RTThreadGetNative(Thread), RT_TIMER_SIGNAL); + + /* + * Wait for the thread to complete. + */ + rc = RTThreadWait(Thread, 30 * 1000, NULL); + AssertRC(rc); + } + + + /* + * Free up the resources associated with the timer. + */ +#ifdef IPRT_WITH_POSIX_TIMERS + timer_delete(pTimer->NativeTimer); +#else + RTSemEventDestroy(pTimer->Event); + pTimer->Event = NIL_RTSEMEVENT; +#endif /* !IPRT_WITH_POSIX_TIMERS */ + if (RT_SUCCESS(rc)) + RTMemFree(pTimer); + return rc; +} + + +RTDECL(int) RTTimerStart(PRTTIMER pTimer, uint64_t u64First) +{ + /* + * Validate input. + */ + AssertPtrReturn(pTimer, VERR_INVALID_POINTER); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); +#ifndef IPRT_WITH_POSIX_TIMERS + AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR); +#endif + + /* + * Already running? + */ + if (!ASMAtomicXchgU8(&pTimer->fSuspended, false)) + return VERR_TIMER_ACTIVE; + LogFlow(("RTTimerStart: pTimer=%p u64First=%llu u64NanoInterval=%llu\n", pTimer, u64First, pTimer->u64NanoInterval)); + +#ifndef IPRT_WITH_POSIX_TIMERS + /* + * Tell the thread to start servicing the timer. + * Wait for it to ACK the request to avoid reset races. + */ + RTThreadUserReset(pTimer->Thread); + ASMAtomicUoWriteU64(&pTimer->u64NanoFirst, u64First); + ASMAtomicUoWriteU64(&pTimer->iTick, 0); + ASMAtomicWriteU8(&pTimer->fSuspended, false); + int rc = RTSemEventSignal(pTimer->Event); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserWait(pTimer->Thread, 45*1000); + AssertRC(rc); + RTThreadUserReset(pTimer->Thread); + } + else + AssertRC(rc); + +#else /* IPRT_WITH_POSIX_TIMERS */ + /* + * Start the timer. + */ + struct itimerspec TimerSpec; + TimerSpec.it_value.tv_sec = u64First / 1000000000; /* nanosec => sec */ + TimerSpec.it_value.tv_nsec = u64First ? u64First % 1000000000 : 10; /* 0 means disable, replace it with 10. */ + TimerSpec.it_interval.tv_sec = pTimer->u64NanoInterval / 1000000000; + TimerSpec.it_interval.tv_nsec = pTimer->u64NanoInterval % 1000000000; + int err = timer_settime(pTimer->NativeTimer, 0, &TimerSpec, NULL); + int rc = err == 0 ? VINF_SUCCESS : RTErrConvertFromErrno(errno); +#endif /* IPRT_WITH_POSIX_TIMERS */ + + if (RT_FAILURE(rc)) + ASMAtomicXchgU8(&pTimer->fSuspended, false); + return rc; +} + + +RTDECL(int) RTTimerStop(PRTTIMER pTimer) +{ + /* + * Validate input. + */ + AssertPtrReturn(pTimer, VERR_INVALID_POINTER); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); + + /* + * Already running? + */ + if (ASMAtomicXchgU8(&pTimer->fSuspended, true)) + return VERR_TIMER_SUSPENDED; + LogFlow(("RTTimerStop: pTimer=%p\n", pTimer)); + +#ifndef IPRT_WITH_POSIX_TIMERS + /* + * Tell the thread to stop servicing the timer. + */ + RTThreadUserReset(pTimer->Thread); + ASMAtomicXchgU8(&pTimer->fSuspended, true); + int rc = VINF_SUCCESS; + if (RTThreadSelf() != pTimer->Thread) + { + pthread_kill((pthread_t)RTThreadGetNative(pTimer->Thread), RT_TIMER_SIGNAL); + rc = RTThreadUserWait(pTimer->Thread, 45*1000); + AssertRC(rc); + RTThreadUserReset(pTimer->Thread); + } + +#else /* IPRT_WITH_POSIX_TIMERS */ + /* + * Stop the timer. + */ + struct itimerspec TimerSpec; + TimerSpec.it_value.tv_sec = 0; + TimerSpec.it_value.tv_nsec = 0; + TimerSpec.it_interval.tv_sec = 0; + TimerSpec.it_interval.tv_nsec = 0; + int err = timer_settime(pTimer->NativeTimer, 0, &TimerSpec, NULL); + int rc = err == 0 ? VINF_SUCCESS : RTErrConvertFromErrno(errno); +#endif /* IPRT_WITH_POSIX_TIMERS */ + + return rc; +} + + +RTDECL(int) RTTimerChangeInterval(PRTTIMER pTimer, uint64_t u64NanoInterval) +{ + AssertPtrReturn(pTimer, VERR_INVALID_POINTER); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); + NOREF(u64NanoInterval); + return VERR_NOT_SUPPORTED; +} + diff --git a/src/VBox/Runtime/r3/posix/tls-posix.cpp b/src/VBox/Runtime/r3/posix/tls-posix.cpp new file mode 100644 index 00000000..22ef28ab --- /dev/null +++ b/src/VBox/Runtime/r3/posix/tls-posix.cpp @@ -0,0 +1,119 @@ +/* $Id: tls-posix.cpp $ */ +/** @file + * IPRT - Thread Local Storage (TLS), POSIX. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <errno.h> +#include <pthread.h> + +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> + + +AssertCompile(sizeof(pthread_key_t) <= sizeof(RTTLS)); + + +RTR3DECL(RTTLS) RTTlsAlloc(void) +{ + pthread_key_t iTls = (pthread_key_t)NIL_RTTLS; + int rc = pthread_key_create(&iTls, NULL); + if (!rc) + { + Assert(iTls != (pthread_key_t)NIL_RTTLS); + return iTls; + } + return NIL_RTTLS; +} + + +RTR3DECL(int) RTTlsAllocEx(PRTTLS piTls, PFNRTTLSDTOR pfnDestructor) +{ + pthread_key_t iTls = (pthread_key_t)NIL_RTTLS; +#if defined(__GNUC__) && defined(RT_ARCH_X86) + int rc = pthread_key_create(&iTls, (void (*)(void*))pfnDestructor); +#else + int rc = pthread_key_create(&iTls, pfnDestructor); +#endif + if (!rc) + { + *piTls = iTls; + Assert((pthread_key_t)*piTls == iTls); + Assert(*piTls != NIL_RTTLS); + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(rc); +} + + +RTR3DECL(int) RTTlsFree(RTTLS iTls) +{ + if (iTls == NIL_RTTLS) + return VINF_SUCCESS; + int rc = pthread_key_delete(iTls); + if (!rc) + return VINF_SUCCESS; + return RTErrConvertFromErrno(rc); +} + + +RTR3DECL(void *) RTTlsGet(RTTLS iTls) +{ + return pthread_getspecific(iTls); +} + + +RTR3DECL(int) RTTlsGetEx(RTTLS iTls, void **ppvValue) +{ + if (RT_UNLIKELY(iTls == NIL_RTTLS)) + return VERR_INVALID_PARAMETER; + *ppvValue = pthread_getspecific(iTls); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTlsSet(RTTLS iTls, void *pvValue) +{ + int rc = pthread_setspecific(iTls, pvValue); + if (RT_UNLIKELY(rc != 0)) + return RTErrConvertFromErrno(rc); + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/posix/utf8-posix.cpp b/src/VBox/Runtime/r3/posix/utf8-posix.cpp new file mode 100644 index 00000000..5f4c947e --- /dev/null +++ b/src/VBox/Runtime/r3/posix/utf8-posix.cpp @@ -0,0 +1,709 @@ +/* $Id: utf8-posix.cpp $ */ +/** @file + * IPRT - UTF-8 helpers, POSIX. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/string.h> +#include "internal/iprt.h" + +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/string.h> + +#include <errno.h> +#include <locale.h> +#ifdef RT_OS_DARWIN +# include <stdlib.h> +#endif + +/* iconv prototype changed with 165+ (thanks to PSARC/2010/160 Bugster 7037400) */ +#if defined(RT_OS_SOLARIS) +# if !defined(_XPG6) +# define IPRT_XPG6_TMP_DEF +# define _XPG6 +# endif +# if defined(__USE_LEGACY_PROTOTYPES__) +# define IPRT_LEGACY_PROTO_TMP_DEF +# undef __USE_LEGACY_PROTOTYPES__ +# endif +#endif /* RT_OS_SOLARIS */ + +# include <iconv.h> + +#if defined(RT_OS_SOLARIS) +# if defined(IPRT_XPG6_TMP_DEF) +# undef _XPG6 +# undef IPRT_XPG6_TMP_DEF +# endif +# if defined(IPRT_LEGACY_PROTO_TMP_DEF) +# define __USE_LEGACY_PROTOTYPES__ +# undef IPRT_LEGACY_PROTO_TMP_DEF +# endif +#endif /* RT_OS_SOLARIS */ + +#include <wctype.h> + +#include <langinfo.h> + +#include "internal/alignmentchecks.h" +#include "internal/string.h" +#ifdef RT_WITH_ICONV_CACHE +# include "internal/thread.h" +AssertCompile(sizeof(iconv_t) <= sizeof(void *)); +#endif + + +/* There are different opinions about the constness of the input buffer. */ +#if defined(RT_OS_LINUX) || defined(RT_OS_HAIKU) || defined(RT_OS_SOLARIS) \ + || (defined(RT_OS_DARWIN) && defined(_DARWIN_FEATURE_UNIX_CONFORMANCE)) +# define NON_CONST_ICONV_INPUT +#endif +#ifdef RT_OS_FREEBSD +# include <sys/param.h> +# if __FreeBSD_version >= 1002000 /* Changed around 10.2.2 (https://svnweb.freebsd.org/base?view=revision&revision=281550) */ +# define NON_CONST_ICONV_INPUT +# else +# error __FreeBSD_version__ +# endif +#endif +#ifdef RT_OS_NETBSD +/* iconv constness was changed on 2019-10-24, shortly after 9.99.17 */ +# include <sys/param.h> +# if __NetBSD_Prereq__(9,99,18) +# define NON_CONST_ICONV_INPUT +# endif +#endif + + +/** + * Gets the codeset of the current locale (LC_CTYPE). + * + * @returns Pointer to read-only string with the codeset name. + */ +DECLHIDDEN(const char *) rtStrGetLocaleCodeset(void) +{ +#ifdef RT_OS_DARWIN + /* + * @bugref{10153}: If no locale specified in the environment (typically the + * case when launched via Finder, LaunchPad or similar) default to UTF-8. + */ + static int8_t volatile s_fIsUtf8 = -1; + int8_t fIsUtf8 = s_fIsUtf8; + if (fIsUtf8) + { + if (fIsUtf8 == true) + return "UTF-8"; + + /* Initialize: */ + fIsUtf8 = true; + static const char * const s_papszVariables[] = { "LC_ALL", "LC_CTYPE", "LANG" }; + for (size_t i = 0; i < RT_ELEMENTS(s_papszVariables); i++) + { + const char *pszValue = getenv(s_papszVariables[i]); + if (pszValue && *pszValue) + { + fIsUtf8 = false; + break; + } + } + s_fIsUtf8 = fIsUtf8; + if (fIsUtf8 == true) + return "UTF-8"; + } +#endif + return nl_langinfo(CODESET); +} + + +/** + * Checks if the codeset specified by current locale (LC_CTYPE) is UTF-8. + * + * @returns true if UTF-8, false if not. + */ +DECLHIDDEN(bool) rtStrIsLocaleCodesetUtf8(void) +{ + return rtStrIsCodesetUtf8(rtStrGetLocaleCodeset()); +} + + +/** + * Checks if @a pszCodeset specified UTF-8. + * + * @returns true if UTF-8, false if not. + * @param pszCodeset Codeset to test. + */ +DECLHIDDEN(bool) rtStrIsCodesetUtf8(const char *pszCodeset) +{ + if (pszCodeset) + { + /* Skip leading spaces just in case: */ + while (RT_C_IS_SPACE(*pszCodeset)) + pszCodeset++; + + /* If prefixed by 'ISO-10646/' skip that (iconv access this, dunno about + LC_CTYPE et al., but play it safe): */ + if ( strncmp(pszCodeset, RT_STR_TUPLE("ISO-10646/")) == 0 + || strncmp(pszCodeset, RT_STR_TUPLE("iso-10646/")) == 0) + pszCodeset += sizeof("ISO-10646/") - 1; + + /* Match 'utf': */ + if ( (pszCodeset[0] == 'u' || pszCodeset[0] == 'U') + && (pszCodeset[1] == 't' || pszCodeset[1] == 'T') + && (pszCodeset[2] == 'f' || pszCodeset[2] == 'F')) + { + pszCodeset += 3; + + /* Treat the dash as optional: */ + if (*pszCodeset == '-') + pszCodeset++; + + /* Match '8': */ + if (*pszCodeset == '8') + { + do + pszCodeset++; + while (RT_C_IS_SPACE(*pszCodeset)); + + /* We ignore modifiers here (e.g. "[be_BY.]utf8@latin"). */ + if (!*pszCodeset || *pszCodeset == '@') + return true; + } + } + } + return false; +} + + + +#ifdef RT_WITH_ICONV_CACHE + +/** + * Initializes the iconv handle cache associated with a thread. + * + * @param pThread The thread in question. + */ +DECLHIDDEN(void) rtStrIconvCacheInit(PRTTHREADINT pThread) +{ + for (size_t i = 0; i < RT_ELEMENTS(pThread->ahIconvs); i++) + pThread->ahIconvs[i] = (iconv_t)-1; +} + +/** + * Destroys the iconv handle cache associated with a thread. + * + * @param pThread The thread in question. + */ +DECLHIDDEN(void) rtStrIconvCacheDestroy(PRTTHREADINT pThread) +{ + for (size_t i = 0; i < RT_ELEMENTS(pThread->ahIconvs); i++) + { + iconv_t hIconv = (iconv_t)pThread->ahIconvs[i]; + pThread->ahIconvs[i] = (iconv_t)-1; + if (hIconv != (iconv_t)-1) + iconv_close(hIconv); + } +} + + +/** + * Converts a string from one charset to another. + * + * @returns iprt status code. + * @param pvInput Pointer to intput string. + * @param cbInput Size (in bytes) of input string. Excludes any terminators. + * @param pszInputCS Codeset of the input string. + * @param ppvOutput Pointer to pointer to output buffer if cbOutput > 0. + * If cbOutput is 0 this is where the pointer to the allocated + * buffer is stored. + * @param cbOutput Size of the passed in buffer. + * @param pszOutputCS Codeset of the input string. + * @param cFactor Input vs. output size factor. + * @param phIconv Pointer to the cache entry. + */ +static int rtstrConvertCached(const void *pvInput, size_t cbInput, const char *pszInputCS, + void **ppvOutput, size_t cbOutput, const char *pszOutputCS, + unsigned cFactor, iconv_t *phIconv) +{ + /* + * Allocate buffer + */ + bool fUcs2Term; + void *pvOutput; + size_t cbOutput2; + if (!cbOutput) + { + cbOutput2 = cbInput * cFactor; + pvOutput = RTMemTmpAlloc(cbOutput2 + sizeof(RTUTF16)); + if (!pvOutput) + return VERR_NO_TMP_MEMORY; + fUcs2Term = true; + } + else + { + pvOutput = *ppvOutput; + fUcs2Term = !strcmp(pszOutputCS, "UCS-2") + || !strcmp(pszOutputCS, "UTF-16") + || !strcmp(pszOutputCS, "ucs-2") + || !strcmp(pszOutputCS, "utf-16"); + cbOutput2 = cbOutput - (fUcs2Term ? sizeof(RTUTF16) : 1); + if (cbOutput2 > cbOutput) + return VERR_BUFFER_OVERFLOW; + } + + /* + * Use a loop here to retry with bigger buffers. + */ + for (unsigned cTries = 10; cTries > 0; cTries--) + { + /* + * Create conversion object if necessary. + */ + iconv_t hIconv = (iconv_t)*phIconv; + if (hIconv == (iconv_t)-1) + { +#if defined(RT_OS_SOLARIS) || defined(RT_OS_NETBSD) || /* @bugref{10153}: Default to UTF-8: */ defined(RT_OS_DARWIN) + /* Some systems don't grok empty codeset strings, so help them find the current codeset. */ + if (!*pszInputCS) + pszInputCS = rtStrGetLocaleCodeset(); + if (!*pszOutputCS) + pszOutputCS = rtStrGetLocaleCodeset(); +#endif + IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc causes trouble */ + *phIconv = hIconv = iconv_open(pszOutputCS, pszInputCS); + IPRT_ALIGNMENT_CHECKS_ENABLE(); + } + if (hIconv != (iconv_t)-1) + { + /* + * Do the conversion. + */ + size_t cbInLeft = cbInput; + size_t cbOutLeft = cbOutput2; + const void *pvInputLeft = pvInput; + void *pvOutputLeft = pvOutput; + size_t cchNonRev; +#ifdef NON_CONST_ICONV_INPUT + cchNonRev = iconv(hIconv, (char **)&pvInputLeft, &cbInLeft, (char **)&pvOutputLeft, &cbOutLeft); +#else + cchNonRev = iconv(hIconv, (const char **)&pvInputLeft, &cbInLeft, (char **)&pvOutputLeft, &cbOutLeft); +#endif + if (cchNonRev != (size_t)-1) + { + if (!cbInLeft) + { + /* + * We're done, just add the terminator and return. + * (Two terminators to support UCS-2 output, too.) + */ + ((char *)pvOutputLeft)[0] = '\0'; + if (fUcs2Term) + ((char *)pvOutputLeft)[1] = '\0'; + *ppvOutput = pvOutput; + if (cchNonRev == 0) + return VINF_SUCCESS; + return VWRN_NO_TRANSLATION; + } + errno = E2BIG; + } + + /* + * If we failed because of output buffer space we'll + * increase the output buffer size and retry. + */ + if (errno == E2BIG) + { + if (!cbOutput) + { + RTMemTmpFree(pvOutput); + cbOutput2 *= 2; + pvOutput = RTMemTmpAlloc(cbOutput2 + sizeof(RTUTF16)); + if (!pvOutput) + return VERR_NO_TMP_MEMORY; + continue; + } + return VERR_BUFFER_OVERFLOW; + } + + /* + * Close the handle on all other errors to make sure we won't carry + * any bad state with us. + */ + *phIconv = (iconv_t)-1; + iconv_close(hIconv); + } + break; + } + + /* failure */ + if (!cbOutput) + RTMemTmpFree(pvOutput); + return VERR_NO_TRANSLATION; +} + +#endif /* RT_WITH_ICONV_CACHE */ + +/** + * Converts a string from one charset to another without using the handle cache. + * + * @returns IPRT status code. + * + * @param pvInput Pointer to intput string. + * @param cbInput Size (in bytes) of input string. Excludes any terminators. + * @param pszInputCS Codeset of the input string. + * @param ppvOutput Pointer to pointer to output buffer if cbOutput > 0. + * If cbOutput is 0 this is where the pointer to the allocated + * buffer is stored. + * @param cbOutput Size of the passed in buffer. + * @param pszOutputCS Codeset of the input string. + * @param cFactor Input vs. output size factor. + */ +static int rtStrConvertUncached(const void *pvInput, size_t cbInput, const char *pszInputCS, + void **ppvOutput, size_t cbOutput, const char *pszOutputCS, + unsigned cFactor) +{ + /* + * Allocate buffer + */ + bool fUcs2Term; + void *pvOutput; + size_t cbOutput2; + if (!cbOutput) + { + cbOutput2 = cbInput * cFactor; + pvOutput = RTMemTmpAlloc(cbOutput2 + sizeof(RTUTF16)); + if (!pvOutput) + return VERR_NO_TMP_MEMORY; + fUcs2Term = true; + } + else + { + pvOutput = *ppvOutput; + fUcs2Term = !strcmp(pszOutputCS, "UCS-2"); + cbOutput2 = cbOutput - (fUcs2Term ? sizeof(RTUTF16) : 1); + if (cbOutput2 > cbOutput) + return VERR_BUFFER_OVERFLOW; + } + + /* + * Use a loop here to retry with bigger buffers. + */ + for (unsigned cTries = 10; cTries > 0; cTries--) + { + /* + * Create conversion object. + */ +#if defined(RT_OS_SOLARIS) || defined(RT_OS_NETBSD) || /* @bugref{10153}: Default to UTF-8: */ defined(RT_OS_DARWIN) + /* Some systems don't grok empty codeset strings, so help them find the current codeset. */ + if (!*pszInputCS) + pszInputCS = rtStrGetLocaleCodeset(); + if (!*pszOutputCS) + pszOutputCS = rtStrGetLocaleCodeset(); +#endif + IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc causes trouble */ + iconv_t icHandle = iconv_open(pszOutputCS, pszInputCS); + IPRT_ALIGNMENT_CHECKS_ENABLE(); + if (icHandle != (iconv_t)-1) + { + /* + * Do the conversion. + */ + size_t cbInLeft = cbInput; + size_t cbOutLeft = cbOutput2; + const void *pvInputLeft = pvInput; + void *pvOutputLeft = pvOutput; + size_t cchNonRev; +#ifdef NON_CONST_ICONV_INPUT + cchNonRev = iconv(icHandle, (char **)&pvInputLeft, &cbInLeft, (char **)&pvOutputLeft, &cbOutLeft); +#else + cchNonRev = iconv(icHandle, (const char **)&pvInputLeft, &cbInLeft, (char **)&pvOutputLeft, &cbOutLeft); +#endif + if (cchNonRev != (size_t)-1) + { + if (!cbInLeft) + { + /* + * We're done, just add the terminator and return. + * (Two terminators to support UCS-2 output, too.) + */ + iconv_close(icHandle); + ((char *)pvOutputLeft)[0] = '\0'; + if (fUcs2Term) + ((char *)pvOutputLeft)[1] = '\0'; + *ppvOutput = pvOutput; + if (cchNonRev == 0) + return VINF_SUCCESS; + return VWRN_NO_TRANSLATION; + } + errno = E2BIG; + } + iconv_close(icHandle); + + /* + * If we failed because of output buffer space we'll + * increase the output buffer size and retry. + */ + if (errno == E2BIG) + { + if (!cbOutput) + { + RTMemTmpFree(pvOutput); + cbOutput2 *= 2; + pvOutput = RTMemTmpAlloc(cbOutput2 + sizeof(RTUTF16)); + if (!pvOutput) + return VERR_NO_TMP_MEMORY; + continue; + } + return VERR_BUFFER_OVERFLOW; + } + } + break; + } + + /* failure */ + if (!cbOutput) + RTMemTmpFree(pvOutput); + return VERR_NO_TRANSLATION; +} + + +/** + * Wrapper that selects rtStrConvertCached or rtStrConvertUncached. + * + * @returns IPRT status code. + * + * @param pszInput Pointer to intput string. + * @param cchInput Size (in bytes) of input string. Excludes any + * terminators. + * @param pszInputCS Codeset of the input string. + * @param ppszOutput Pointer to pointer to output buffer if cbOutput > 0. + * If cbOutput is 0 this is where the pointer to the + * allocated buffer is stored. + * @param cbOutput Size of the passed in buffer. + * @param pszOutputCS Codeset of the input string. + * @param cFactor Input vs. output size factor. + * @param enmCacheIdx The iconv cache index. + */ +DECLINLINE(int) rtStrConvertWrapper(const char *pchInput, size_t cchInput, const char *pszInputCS, + char **ppszOutput, size_t cbOutput, const char *pszOutputCS, + unsigned cFactor, RTSTRICONV enmCacheIdx) +{ +#ifdef RT_WITH_ICONV_CACHE + RTTHREAD hSelf = RTThreadSelf(); + if (hSelf != NIL_RTTHREAD) + { + PRTTHREADINT pThread = rtThreadGet(hSelf); + if (pThread) + { + if ((pThread->fIntFlags & (RTTHREADINT_FLAGS_ALIEN | RTTHREADINT_FLAGS_MAIN)) != RTTHREADINT_FLAGS_ALIEN) + { + int rc = rtstrConvertCached(pchInput, cchInput, pszInputCS, + (void **)ppszOutput, cbOutput, pszOutputCS, + cFactor, (iconv_t *)&pThread->ahIconvs[enmCacheIdx]); + rtThreadRelease(pThread); + return rc; + } + rtThreadRelease(pThread); + } + } +#endif + return rtStrConvertUncached(pchInput, cchInput, pszInputCS, + (void **)ppszOutput, cbOutput, pszOutputCS, + cFactor); +} + + +/** + * Internal API for use by the path conversion code. + * + * @returns IPRT status code. + * + * @param pszInput Pointer to intput string. + * @param cchInput Size (in bytes) of input string. Excludes any + * terminators. + * @param pszInputCS Codeset of the input string. + * @param ppszOutput Pointer to pointer to output buffer if cbOutput > 0. + * If cbOutput is 0 this is where the pointer to the + * allocated buffer is stored. + * @param cbOutput Size of the passed in buffer. + * @param pszOutputCS Codeset of the input string. + * @param cFactor Input vs. output size factor. + * @param enmCacheIdx The iconv cache index. + */ +DECLHIDDEN(int) rtStrConvert(const char *pchInput, size_t cchInput, const char *pszInputCS, + char **ppszOutput, size_t cbOutput, const char *pszOutputCS, + unsigned cFactor, RTSTRICONV enmCacheIdx) +{ + Assert(enmCacheIdx >= 0 && enmCacheIdx < RTSTRICONV_END); + return rtStrConvertWrapper(pchInput, cchInput, pszInputCS, + ppszOutput, cbOutput, pszOutputCS, + cFactor, enmCacheIdx); +} + + +/** + * Initializes a local conversion cache for use with rtStrLocalCacheConvert. + * + * Call rtStrLocalCacheDelete when done. + */ +DECLHIDDEN(void) rtStrLocalCacheInit(void **ppvTmpCache) +{ + *ppvTmpCache = (iconv_t)-1; +} + + +/** + * Cleans up a local conversion cache. + */ +DECLHIDDEN(void) rtStrLocalCacheDelete(void **ppvTmpCache) +{ +#ifdef RT_WITH_ICONV_CACHE + iconv_t icHandle = (iconv_t)*ppvTmpCache; + if (icHandle != (iconv_t)-1) + iconv_close(icHandle); +#endif + *ppvTmpCache = (iconv_t)-1; +} + + +/** + * Internal API for use by the process creation conversion code. + * + * @returns IPRT status code. + * + * @param pszInput Pointer to intput string. + * @param cchInput Size (in bytes) of input string. Excludes any + * terminators. + * @param pszInputCS Codeset of the input string. + * @param ppszOutput Pointer to pointer to output buffer if cbOutput > 0. + * If cbOutput is 0 this is where the pointer to the + * allocated buffer is stored. + * @param cbOutput Size of the passed in buffer. + * @param pszOutputCS Codeset of the input string. + * @param ppvTmpCache Pointer to local temporary cache. Must be + * initialized by calling rtStrLocalCacheInit and + * cleaned up afterwards by rtStrLocalCacheDelete. + * Optional. + */ +DECLHIDDEN(int) rtStrLocalCacheConvert(const char *pchInput, size_t cchInput, const char *pszInputCS, + char **ppszOutput, size_t cbOutput, const char *pszOutputCS, + void **ppvTmpCache) +{ +#ifdef RT_WITH_ICONV_CACHE + if (ppvTmpCache) + return rtstrConvertCached(pchInput, cchInput, pszInputCS, (void **)ppszOutput, cbOutput, pszOutputCS, + 1 /*cFactor*/, (iconv_t *)ppvTmpCache); +#else + RT_NOREF(ppvTmpCache); +#endif + + return rtStrConvertUncached(pchInput, cchInput, pszInputCS, (void **)ppszOutput, cbOutput, pszOutputCS, 1 /*cFactor*/); +} + + +RTR3DECL(int) RTStrUtf8ToCurrentCPTag(char **ppszString, const char *pszString, const char *pszTag) +{ + Assert(ppszString); + Assert(pszString); + *ppszString = NULL; + + /* + * Assume result string length is not longer than UTF-8 string. + */ + size_t cch = strlen(pszString); + if (cch <= 0) + { + /* zero length string passed. */ + *ppszString = (char *)RTMemTmpAllocZTag(sizeof(char), pszTag); + if (*ppszString) + return VINF_SUCCESS; + return VERR_NO_TMP_MEMORY; + } + return rtStrConvertWrapper(pszString, cch, "UTF-8", ppszString, 0, "", 1, RTSTRICONV_UTF8_TO_LOCALE); +} + + +RTR3DECL(int) RTStrUtf8ToCurrentCPExTag(char **ppszString, const char *pszString, size_t cchString, const char *pszTag) +{ + Assert(ppszString); + Assert(pszString); + *ppszString = NULL; + + /* + * Assume result string length is not longer than UTF-8 string. + */ + cchString = RTStrNLen(pszString, cchString); + if (cchString < 1) + { + /* zero length string passed. */ + *ppszString = (char *)RTMemTmpAllocZTag(sizeof(char), pszTag); + if (*ppszString) + return VINF_SUCCESS; + return VERR_NO_TMP_MEMORY; + } + return rtStrConvertWrapper(pszString, cchString, "UTF-8", ppszString, 0, "", 1, RTSTRICONV_UTF8_TO_LOCALE); +} + + +RTR3DECL(int) RTStrCurrentCPToUtf8Tag(char **ppszString, const char *pszString, const char *pszTag) +{ + Assert(ppszString); + Assert(pszString); + *ppszString = NULL; + + /* + * Attempt with UTF-8 length of 2x the native length. + */ + size_t cch = strlen(pszString); + if (cch <= 0) + { + /* zero length string passed. */ + *ppszString = (char *)RTMemTmpAllocZTag(sizeof(char), pszTag); + if (*ppszString) + return VINF_SUCCESS; + return VERR_NO_TMP_MEMORY; + } + return rtStrConvertWrapper(pszString, cch, "", ppszString, 0, "UTF-8", 2, RTSTRICONV_LOCALE_TO_UTF8); +} + + +RTR3DECL(int) RTStrConsoleCPToUtf8Tag(char **ppszString, const char *pszString, const char *pszTag) +{ + return RTStrCurrentCPToUtf8Tag(ppszString, pszString, pszTag); +} diff --git a/src/VBox/Runtime/r3/process-data.cpp b/src/VBox/Runtime/r3/process-data.cpp new file mode 100644 index 00000000..1255d781 --- /dev/null +++ b/src/VBox/Runtime/r3/process-data.cpp @@ -0,0 +1,67 @@ +/* $Id: process-data.cpp $ */ +/** @file + * IPRT - Process Data Ring-3. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include "internal/process.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * The process identifier of the running process. + */ +DECL_HIDDEN_DATA(RTPROCESS) g_ProcessSelf = NIL_RTPROCESS; + +/** + * The current process priority. + */ +DECL_HIDDEN_DATA(RTPROCPRIORITY) g_enmProcessPriority = RTPROCPRIORITY_DEFAULT; + +/** The process path. + * This is used by RTPathExecDir and RTProcGetExecutablePath and set by rtProcInitName. */ +DECL_HIDDEN_DATA(char) g_szrtProcExePath[RTPATH_MAX]; +/** The length of g_szrtProcExePath. */ +DECL_HIDDEN_DATA(size_t) g_cchrtProcExePath; +/** The offset of the process name into g_szrtProcExePath. */ +DECL_HIDDEN_DATA(size_t) g_offrtProcName; +/** The length of directory path component of g_szrtProcExePath. */ +DECL_HIDDEN_DATA(size_t) g_cchrtProcExeDir; + diff --git a/src/VBox/Runtime/r3/process.cpp b/src/VBox/Runtime/r3/process.cpp new file mode 100644 index 00000000..f9d1ecf2 --- /dev/null +++ b/src/VBox/Runtime/r3/process.cpp @@ -0,0 +1,135 @@ +/* $Id: process.cpp $ */ +/** @file + * IPRT - Process, Common. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/process.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include "internal/process.h" +#include "internal/thread.h" + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#else +# include <unistd.h> +#endif + + +/** + * Get the identifier for the current process. + * + * @returns Process identifier. + */ +RTDECL(RTPROCESS) RTProcSelf(void) +{ + RTPROCESS Self = g_ProcessSelf; + if (Self != NIL_RTPROCESS) + return Self; + + /* lazy init. */ +#ifdef _MSC_VER + Self = GetCurrentProcessId(); /* since NT 3.1, not 3.51+ as listed on geoffchappell.com */ +#else + Self = getpid(); +#endif + g_ProcessSelf = Self; + return Self; +} + + +/** + * Attempts to alter the priority of the current process. + * + * @returns iprt status code. + * @param enmPriority The new priority. + */ +RTR3DECL(int) RTProcSetPriority(RTPROCPRIORITY enmPriority) +{ + if (enmPriority > RTPROCPRIORITY_INVALID && enmPriority < RTPROCPRIORITY_LAST) + return rtThreadDoSetProcPriority(enmPriority); + AssertMsgFailed(("enmPriority=%d\n", enmPriority)); + return VERR_INVALID_PARAMETER; +} + + +/** + * Gets the current priority of this process. + * + * @returns The priority (see RTPROCPRIORITY). + */ +RTR3DECL(RTPROCPRIORITY) RTProcGetPriority(void) +{ + return g_enmProcessPriority; +} + + +RTR3DECL(char *) RTProcGetExecutablePath(char *pszExecPath, size_t cbExecPath) +{ + if (RT_UNLIKELY(g_szrtProcExePath[0] == '\0')) + return NULL; + + /* + * Calc the length and check if there is space before copying. + */ + size_t cch = g_cchrtProcExePath; + if (cch < cbExecPath) + { + memcpy(pszExecPath, g_szrtProcExePath, cch); + pszExecPath[cch] = '\0'; + return pszExecPath; + } + + AssertMsgFailed(("Buffer too small (%zu <= %zu)\n", cbExecPath, cch)); + return NULL; +} + + +RTR3DECL(const char *) RTProcExecutablePath(void) +{ + return g_szrtProcExePath; +} + + +RTR3DECL(const char *) RTProcShortName(void) +{ + return &g_szrtProcExePath[g_offrtProcName]; +} + diff --git a/src/VBox/Runtime/r3/socket.cpp b/src/VBox/Runtime/r3/socket.cpp new file mode 100644 index 00000000..4acfe73e --- /dev/null +++ b/src/VBox/Runtime/r3/socket.cpp @@ -0,0 +1,3186 @@ +/* $Id: socket.cpp $ */ +/** @file + * IPRT - Network Sockets. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/winsock2.h> +# include <iprt/win/ws2tcpip.h> +#else /* !RT_OS_WINDOWS */ +# include <errno.h> +# include <sys/select.h> +# include <sys/stat.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <netinet/tcp.h> +# include <arpa/inet.h> +# ifdef IPRT_WITH_TCPIP_V6 +# include <netinet6/in6.h> +# endif +# include <sys/un.h> +# include <netdb.h> +# include <unistd.h> +# include <fcntl.h> +# include <sys/uio.h> +# ifndef AF_LOCAL +# define AF_LOCAL AF_UNIX +# endif +#endif /* !RT_OS_WINDOWS */ +#include <limits.h> + +#include "internal/iprt.h" +#include <iprt/socket.h> + +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mempool.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/mem.h> +#include <iprt/sg.h> +#include <iprt/log.h> + +#include "internal/magics.h" +#include "internal/socket.h" +#include "internal/string.h" +#ifdef RT_OS_WINDOWS +# include "win/internal-r3-win.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* non-standard linux stuff (it seems). */ +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + +/* Windows has different names for SHUT_XXX. */ +#ifndef SHUT_RDWR +# ifdef SD_BOTH +# define SHUT_RDWR SD_BOTH +# else +# define SHUT_RDWR 2 +# endif +#endif +#ifndef SHUT_WR +# ifdef SD_SEND +# define SHUT_WR SD_SEND +# else +# define SHUT_WR 1 +# endif +#endif +#ifndef SHUT_RD +# ifdef SD_RECEIVE +# define SHUT_RD SD_RECEIVE +# else +# define SHUT_RD 0 +# endif +#endif + +/* fixup backlevel OSes. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) +# define socklen_t int +#endif + +/** How many pending connection. */ +#define RTTCP_SERVER_BACKLOG 10 + +/* Limit read and write sizes on Windows and OS/2. */ +#ifdef RT_OS_WINDOWS +# define RTSOCKET_MAX_WRITE (INT_MAX / 2) +# define RTSOCKET_MAX_READ (INT_MAX / 2) +#elif defined(RT_OS_OS2) +# define RTSOCKET_MAX_WRITE 0x10000 +# define RTSOCKET_MAX_READ 0x10000 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Socket handle data. + * + * This is mainly required for implementing RTPollSet on Windows. + */ +typedef struct RTSOCKETINT +{ + /** Magic number (RTSOCKET_MAGIC). */ + uint32_t u32Magic; + /** Exclusive user count. + * This is used to prevent two threads from accessing the handle concurrently. + * It can be higher than 1 if this handle is reference multiple times in a + * polling set (Windows). */ + uint32_t volatile cUsers; + /** The native socket handle. */ + RTSOCKETNATIVE hNative; + /** Indicates whether the handle has been closed or not. */ + bool volatile fClosed; + /** Indicates whether the socket is operating in blocking or non-blocking mode + * currently. */ + bool fBlocking; + /** Whether to leave the native socket open rather than closing it (for + * RTHandleGetStandard). */ + bool fLeaveOpen; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + /** The pollset currently polling this socket. This is NIL if no one is + * polling. */ + RTPOLLSET hPollSet; +#endif +#ifdef RT_OS_WINDOWS + /** The event semaphore we've associated with the socket handle. + * This is WSA_INVALID_EVENT if not done. */ + WSAEVENT hEvent; + /** The events we're polling for. */ + uint32_t fPollEvts; + /** The events we're currently subscribing to with WSAEventSelect. + * This is ZERO if we're currently not subscribing to anything. */ + uint32_t fSubscribedEvts; + /** Saved events which are only posted once and events harvested for + * sockets entered multiple times into to a poll set. Imagine a scenario where + * you have a RTPOLL_EVT_READ entry and RTPOLL_EVT_ERROR entry. The READ + * condition can be triggered between checking the READ entry and the ERROR + * entry, and we don't want to drop the READ, so we store it here and make sure + * the event is signalled. + * + * The RTPOLL_EVT_ERROR is inconsistenly sticky at the momemnt... */ + uint32_t fEventsSaved; + /** Set if fEventsSaved contains harvested events (used to avoid multiple + * calls to rtSocketPollCheck on the same socket during rtSocketPollDone). */ + bool fHarvestedEvents; + /** Set if we're using the polling fallback. */ + bool fPollFallback; + /** Set if the fallback polling is active (event not set). */ + bool volatile fPollFallbackActive; + /** Set to shut down the fallback polling thread. */ + bool volatile fPollFallbackShutdown; + /** Socket use to wake up the select thread. */ + RTSOCKETNATIVE hPollFallbackNotifyW; + /** Socket the select thread always waits on. */ + RTSOCKETNATIVE hPollFallbackNotifyR; + /** The fallback polling thread. */ + RTTHREAD hPollFallbackThread; +#endif /* RT_OS_WINDOWS */ +} RTSOCKETINT; + + +/** + * Address union used internally for things like getpeername and getsockname. + */ +typedef union RTSOCKADDRUNION +{ + struct sockaddr Addr; + struct sockaddr_in IPv4; +#ifdef IPRT_WITH_TCPIP_V6 + struct sockaddr_in6 IPv6; +#endif +} RTSOCKADDRUNION; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +/** Indicates that we've successfully initialized winsock. */ +static uint32_t volatile g_uWinSockInitedVersion = 0; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +static void rtSocketPokePollFallbackThread(RTSOCKETINT *pThis); +#endif + + + +#ifdef RT_OS_WINDOWS +/** + * Initializes winsock for the process. + * + * @returns IPRT status code. + */ +static int rtSocketInitWinsock(void) +{ + if (g_uWinSockInitedVersion != 0) + return VINF_SUCCESS; + + if ( !g_pfnWSAGetLastError + || !g_pfnWSAStartup + || !g_pfnsocket + || !g_pfnclosesocket) + return VERR_NET_INIT_FAILED; + + /* + * Initialize winsock. Try with 2.2 and back down till we get something that works. + */ + static const WORD s_awVersions[] = + { + MAKEWORD(2, 2), + MAKEWORD(2, 1), + MAKEWORD(2, 0), + MAKEWORD(1, 1), + MAKEWORD(1, 0), + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_awVersions); i++) + { + WSADATA wsaData; + RT_ZERO(wsaData); + int rcWsa = g_pfnWSAStartup(s_awVersions[i], &wsaData); + if (rcWsa == 0) + { + /* AssertMsg(wsaData.wVersion >= s_awVersions[i]); - triggers with winsock 1.1 */ + ASMAtomicWriteU32(&g_uWinSockInitedVersion, wsaData.wVersion); + return VINF_SUCCESS; + } + AssertLogRelMsg(rcWsa == WSAVERNOTSUPPORTED, ("rcWsa=%d (winsock version %#x)\n", rcWsa, s_awVersions[i])); + } + LogRel(("Failed to init winsock!\n")); + return VERR_NET_INIT_FAILED; +} +#endif + + +/** + * Get the last error as an iprt status code. + * + * @returns IPRT status code. + */ +DECLINLINE(int) rtSocketError(void) +{ +#ifdef RT_OS_WINDOWS + if (g_pfnWSAGetLastError) + return RTErrConvertFromWin32(g_pfnWSAGetLastError()); + return VERR_NET_IO_ERROR; +#else + return RTErrConvertFromErrno(errno); +#endif +} + + +/** + * Resets the last error. + */ +DECLINLINE(void) rtSocketErrorReset(void) +{ +#ifdef RT_OS_WINDOWS + if (g_pfnWSASetLastError) + g_pfnWSASetLastError(0); +#else + errno = 0; +#endif +} + + +/** + * Get the last resolver error as an iprt status code. + * + * @returns iprt status code. + */ +DECLHIDDEN(int) rtSocketResolverError(void) +{ +#ifdef RT_OS_WINDOWS + if (g_pfnWSAGetLastError) + return RTErrConvertFromWin32(g_pfnWSAGetLastError()); + return VERR_UNRESOLVED_ERROR; +#else + switch (h_errno) + { + case HOST_NOT_FOUND: + return VERR_NET_HOST_NOT_FOUND; + case NO_DATA: + return VERR_NET_ADDRESS_NOT_AVAILABLE; + case NO_RECOVERY: + return VERR_IO_GEN_FAILURE; + case TRY_AGAIN: + return VERR_TRY_AGAIN; + + default: + AssertLogRelMsgFailed(("Unhandled error %u\n", h_errno)); + return VERR_UNRESOLVED_ERROR; + } +#endif +} + + +/** + * Converts from a native socket address to a generic IPRT network address. + * + * @returns IPRT status code. + * @param pSrc The source address. + * @param cbSrc The size of the source address. + * @param pAddr Where to return the generic IPRT network + * address. + */ +static int rtSocketNetAddrFromAddr(RTSOCKADDRUNION const *pSrc, size_t cbSrc, PRTNETADDR pAddr) +{ + /* + * Convert the address. + */ + if ( cbSrc == sizeof(struct sockaddr_in) + && pSrc->Addr.sa_family == AF_INET) + { + RT_ZERO(*pAddr); + pAddr->enmType = RTNETADDRTYPE_IPV4; + pAddr->uPort = RT_N2H_U16(pSrc->IPv4.sin_port); + pAddr->uAddr.IPv4.u = pSrc->IPv4.sin_addr.s_addr; + } +#ifdef IPRT_WITH_TCPIP_V6 + else if ( cbSrc == sizeof(struct sockaddr_in6) + && pSrc->Addr.sa_family == AF_INET6) + { + RT_ZERO(*pAddr); + pAddr->enmType = RTNETADDRTYPE_IPV6; + pAddr->uPort = RT_N2H_U16(pSrc->IPv6.sin6_port); + pAddr->uAddr.IPv6.au32[0] = pSrc->IPv6.sin6_addr.s6_addr32[0]; + pAddr->uAddr.IPv6.au32[1] = pSrc->IPv6.sin6_addr.s6_addr32[1]; + pAddr->uAddr.IPv6.au32[2] = pSrc->IPv6.sin6_addr.s6_addr32[2]; + pAddr->uAddr.IPv6.au32[3] = pSrc->IPv6.sin6_addr.s6_addr32[3]; + } +#endif + else + return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED; + return VINF_SUCCESS; +} + + +/** + * Converts from a generic IPRT network address to a native socket address. + * + * @returns IPRT status code. + * @param pAddr Pointer to the generic IPRT network address. + * @param pDst The source address. + * @param cbDst The size of the source address. + * @param pcbAddr Where to store the size of the returned address. + * Optional + */ +static int rtSocketAddrFromNetAddr(PCRTNETADDR pAddr, RTSOCKADDRUNION *pDst, size_t cbDst, int *pcbAddr) +{ + RT_BZERO(pDst, cbDst); + if (pAddr->enmType == RTNETADDRTYPE_IPV4) + { + if (cbDst < sizeof(struct sockaddr_in)) + return VERR_BUFFER_OVERFLOW; + + pDst->Addr.sa_family = AF_INET; + pDst->IPv4.sin_port = RT_H2N_U16(pAddr->uPort); + pDst->IPv4.sin_addr.s_addr = pAddr->uAddr.IPv4.u; + if (pcbAddr) + *pcbAddr = sizeof(pDst->IPv4); + } +#ifdef IPRT_WITH_TCPIP_V6 + else if (pAddr->enmType == RTNETADDRTYPE_IPV6) + { + if (cbDst < sizeof(struct sockaddr_in6)) + return VERR_BUFFER_OVERFLOW; + + pDst->Addr.sa_family = AF_INET6; + pDst->IPv6.sin6_port = RT_H2N_U16(pAddr->uPort); + pSrc->IPv6.sin6_addr.s6_addr32[0] = pAddr->uAddr.IPv6.au32[0]; + pSrc->IPv6.sin6_addr.s6_addr32[1] = pAddr->uAddr.IPv6.au32[1]; + pSrc->IPv6.sin6_addr.s6_addr32[2] = pAddr->uAddr.IPv6.au32[2]; + pSrc->IPv6.sin6_addr.s6_addr32[3] = pAddr->uAddr.IPv6.au32[3]; + if (pcbAddr) + *pcbAddr = sizeof(pDst->IPv6); + } +#endif + else + return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED; + return VINF_SUCCESS; +} + + +/** + * Tries to lock the socket for exclusive usage by the calling thread. + * + * Call rtSocketUnlock() to unlock. + * + * @returns @c true if locked, @c false if not. + * @param pThis The socket structure. + */ +DECLINLINE(bool) rtSocketTryLock(RTSOCKETINT *pThis) +{ + return ASMAtomicCmpXchgU32(&pThis->cUsers, 1, 0); +} + + +/** + * Unlocks the socket. + * + * @param pThis The socket structure. + */ +DECLINLINE(void) rtSocketUnlock(RTSOCKETINT *pThis) +{ + ASMAtomicCmpXchgU32(&pThis->cUsers, 0, 1); +} + + +/** + * The slow path of rtSocketSwitchBlockingMode that does the actual switching. + * + * @returns IPRT status code. + * @param pThis The socket structure. + * @param fBlocking The desired mode of operation. + * @remarks Do not call directly. + */ +static int rtSocketSwitchBlockingModeSlow(RTSOCKETINT *pThis, bool fBlocking) +{ +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnioctlsocket, VERR_NET_NOT_UNSUPPORTED); + u_long uBlocking = fBlocking ? 0 : 1; + if (g_pfnioctlsocket(pThis->hNative, FIONBIO, &uBlocking)) + return rtSocketError(); + +#else + int fFlags = fcntl(pThis->hNative, F_GETFL, 0); + if (fFlags == -1) + return rtSocketError(); + + if (fBlocking) + fFlags &= ~O_NONBLOCK; + else + fFlags |= O_NONBLOCK; + if (fcntl(pThis->hNative, F_SETFL, fFlags) == -1) + return rtSocketError(); +#endif + + pThis->fBlocking = fBlocking; + return VINF_SUCCESS; +} + + +/** + * Switches the socket to the desired blocking mode if necessary. + * + * The socket must be locked. + * + * @returns IPRT status code. + * @param pThis The socket structure. + * @param fBlocking The desired mode of operation. + */ +DECLINLINE(int) rtSocketSwitchBlockingMode(RTSOCKETINT *pThis, bool fBlocking) +{ + if (pThis->fBlocking != fBlocking) + return rtSocketSwitchBlockingModeSlow(pThis, fBlocking); + return VINF_SUCCESS; +} + + +/** + * Creates an IPRT socket handle for a native one. + * + * @returns IPRT status code. + * @param ppSocket Where to return the IPRT socket handle. + * @param hNative The native handle. + * @param fLeaveOpen Whether to leave the native socket handle open when + * closed. + */ +DECLHIDDEN(int) rtSocketCreateForNative(RTSOCKETINT **ppSocket, RTSOCKETNATIVE hNative, bool fLeaveOpen) +{ + RTSOCKETINT *pThis = (RTSOCKETINT *)RTMemPoolAlloc(RTMEMPOOL_DEFAULT, sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + pThis->u32Magic = RTSOCKET_MAGIC; + pThis->cUsers = 0; + pThis->hNative = hNative; + pThis->fClosed = false; + pThis->fLeaveOpen = fLeaveOpen; + pThis->fBlocking = true; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + pThis->hPollSet = NIL_RTPOLLSET; +#endif +#ifdef RT_OS_WINDOWS + pThis->hEvent = WSA_INVALID_EVENT; + pThis->fPollEvts = 0; + pThis->fSubscribedEvts = 0; + pThis->fEventsSaved = 0; + pThis->fHarvestedEvents = false; + pThis->fPollFallback = g_uWinSockInitedVersion < MAKEWORD(2, 0) + || g_pfnWSACreateEvent == NULL + || g_pfnWSACloseEvent == NULL + || g_pfnWSAEventSelect == NULL + || g_pfnWSAEnumNetworkEvents == NULL; + pThis->fPollFallbackActive = false; + pThis->fPollFallbackShutdown = false; + pThis->hPollFallbackNotifyR = NIL_RTSOCKETNATIVE; + pThis->hPollFallbackNotifyW = NIL_RTSOCKETNATIVE; + pThis->hPollFallbackThread = NIL_RTTHREAD; +#endif + *ppSocket = pThis; + return VINF_SUCCESS; +} + + +RTDECL(int) RTSocketFromNative(PRTSOCKET phSocket, RTHCINTPTR uNative) +{ + AssertReturn(uNative != NIL_RTSOCKETNATIVE, VERR_INVALID_PARAMETER); +#ifndef RT_OS_WINDOWS + AssertReturn(uNative >= 0, VERR_INVALID_PARAMETER); +#endif + AssertPtrReturn(phSocket, VERR_INVALID_POINTER); + return rtSocketCreateForNative(phSocket, uNative, false /*fLeaveOpen*/); +} + + +/** + * Wrapper around socket(). + * + * @returns IPRT status code. + * @param phSocket Where to store the handle to the socket on + * success. + * @param iDomain The protocol family (PF_XXX). + * @param iType The socket type (SOCK_XXX). + * @param iProtocol Socket parameter, usually 0. + * @param fInheritable Set to true if the socket should be inherted by + * child processes, false if not inheritable. + */ +DECLHIDDEN(int) rtSocketCreate(PRTSOCKET phSocket, int iDomain, int iType, int iProtocol, bool fInheritable) +{ +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnsocket, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnclosesocket, VERR_NET_NOT_UNSUPPORTED); + + /* Initialize WinSock. */ + int rc2 = rtSocketInitWinsock(); + if (RT_FAILURE(rc2)) + return rc2; +#endif + + /* + * Create the socket. + * + * The RTSocketSetInheritance operation isn't necessarily reliable on windows, + * so try use WSA_FLAG_NO_HANDLE_INHERIT with WSASocketW when possible. + */ +#ifdef RT_OS_WINDOWS + bool fCallSetInheritance = true; + RTSOCKETNATIVE hNative; + if (g_pfnWSASocketW) + { + DWORD fWsaFlags = WSA_FLAG_OVERLAPPED | (!fInheritable ? WSA_FLAG_NO_HANDLE_INHERIT : 0); + hNative = g_pfnWSASocketW(iDomain, iType, iProtocol, NULL, 0 /*Group*/, fWsaFlags); + if (hNative != NIL_RTSOCKETNATIVE) + fCallSetInheritance = false; + else + { + if (!fInheritable) + hNative = g_pfnsocket(iDomain, iType, iProtocol); + if (hNative == NIL_RTSOCKETNATIVE) + return rtSocketError(); + } + } + else + { + hNative = g_pfnsocket(iDomain, iType, iProtocol); + if (hNative == NIL_RTSOCKETNATIVE) + return rtSocketError(); + } +#else + RTSOCKETNATIVE hNative = socket(iDomain, iType, iProtocol); + if (hNative == NIL_RTSOCKETNATIVE) + return rtSocketError(); +#endif + + /* + * Wrap it. + */ + int rc = rtSocketCreateForNative(phSocket, hNative, false /*fLeaveOpen*/); + if (RT_SUCCESS(rc)) + { +#ifdef RT_OS_WINDOWS + if (fCallSetInheritance) +#endif + RTSocketSetInheritance(*phSocket, fInheritable); + } + else + { +#ifdef RT_OS_WINDOWS + g_pfnclosesocket(hNative); +#else + close(hNative); +#endif + } + return rc; +} + + +/** + * Wrapper around socketpair() for creating a local TCP connection. + * + * @returns IPRT status code. + * @param phServer Where to return the first native socket. + * @param phClient Where to return the second native socket. + */ +static int rtSocketCreateNativeTcpPair(RTSOCKETNATIVE *phServer, RTSOCKETNATIVE *phClient) +{ +#ifdef RT_OS_WINDOWS + /* + * Initialize WinSock and make sure we got the necessary APIs. + */ + int rc = rtSocketInitWinsock(); + if (RT_FAILURE(rc)) + return rc; + AssertReturn(g_pfnsocket, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnclosesocket, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnsetsockopt, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnbind, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfngetsockname, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnlisten, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnaccept, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnconnect, VERR_NET_NOT_UNSUPPORTED); + + /* + * Create the "server" listen socket and the "client" socket. + */ + RTSOCKETNATIVE hListener = g_pfnsocket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (hListener == NIL_RTSOCKETNATIVE) + return rtSocketError(); + RTSOCKETNATIVE hClient = g_pfnsocket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (hClient != NIL_RTSOCKETNATIVE) + { + + /* + * We let WinSock choose a port number when we bind. + */ + union + { + struct sockaddr_in Ip; + struct sockaddr Generic; + } uAddr; + RT_ZERO(uAddr); + uAddr.Ip.sin_family = AF_INET; + uAddr.Ip.sin_addr.s_addr = RT_H2N_U32_C(INADDR_LOOPBACK); + //uAddr.Ip.sin_port = 0; + int fReuse = 1; + rc = g_pfnsetsockopt(hListener, SOL_SOCKET, SO_REUSEADDR, (const char *)&fReuse, sizeof(fReuse)); + if (rc == 0) + { + rc = g_pfnbind(hListener, &uAddr.Generic, sizeof(uAddr.Ip)); + if (rc == 0) + { + /* + * Get the address the client should connect to. According to the docs, + * we cannot assume that getsockname sets the IP and family. + */ + RT_ZERO(uAddr); + int cbAddr = sizeof(uAddr.Ip); + rc = g_pfngetsockname(hListener, &uAddr.Generic, &cbAddr); + if (rc == 0) + { + uAddr.Ip.sin_family = AF_INET; + uAddr.Ip.sin_addr.s_addr = RT_H2N_U32_C(INADDR_LOOPBACK); + + /* + * Listen, connect and accept. + */ + rc = g_pfnlisten(hListener, 1 /*cBacklog*/); + if (rc == 0) + { + rc = g_pfnconnect(hClient, &uAddr.Generic, sizeof(uAddr.Ip)); + if (rc == 0) + { + RTSOCKETNATIVE hServer = g_pfnaccept(hListener, NULL, NULL); + if (hServer != NIL_RTSOCKETNATIVE) + { + g_pfnclosesocket(hListener); + + /* + * Done! + */ + *phServer = hServer; + *phClient = hClient; + return VINF_SUCCESS; + } + } + } + } + } + } + rc = rtSocketError(); + g_pfnclosesocket(hClient); + } + else + rc = rtSocketError(); + g_pfnclosesocket(hListener); + return rc; + +#else + /* + * Got socket pair, so use it. + * Note! This isn't TCP per se, but it should fool the users. + */ + int aSockets[2] = { -1, -1 }; + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, aSockets) == 0) + { + *phServer = aSockets[0]; + *phClient = aSockets[1]; + return VINF_SUCCESS; + } + return rtSocketError(); +#endif +} + + +/** + * Worker for RTTcpCreatePair. + * + * @returns IPRT status code. + * @param phServer Where to return the "server" side of the pair. + * @param phClient Where to return the "client" side of the pair. + * @note There is no server or client side, but we gotta call it something. + */ +DECLHIDDEN(int) rtSocketCreateTcpPair(RTSOCKET *phServer, RTSOCKET *phClient) +{ + RTSOCKETNATIVE hServer = NIL_RTSOCKETNATIVE; + RTSOCKETNATIVE hClient = NIL_RTSOCKETNATIVE; + int rc = rtSocketCreateNativeTcpPair(&hServer, &hClient); + if (RT_SUCCESS(rc)) + { + rc = rtSocketCreateForNative(phServer, hServer, false /*fLeaveOpen*/); + if (RT_SUCCESS(rc)) + { + rc = rtSocketCreateForNative(phClient, hClient, false /*fLeaveOpen*/); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + RTSocketRelease(*phServer); + } + else + { +#ifdef RT_OS_WINDOWS + g_pfnclosesocket(hServer); +#else + close(hServer); +#endif + } +#ifdef RT_OS_WINDOWS + g_pfnclosesocket(hClient); +#else + close(hClient); +#endif + } + + *phServer = NIL_RTSOCKET; + *phClient = NIL_RTSOCKET; + return rc; +} + + +RTDECL(uint32_t) RTSocketRetain(RTSOCKET hSocket) +{ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, UINT32_MAX); + return RTMemPoolRetain(pThis); +} + + +/** + * Worker for RTSocketRelease and RTSocketClose. + * + * @returns IPRT status code. + * @param pThis The socket handle instance data. + * @param fDestroy Whether we're reaching ref count zero. + */ +static int rtSocketCloseIt(RTSOCKETINT *pThis, bool fDestroy) +{ + /* + * Invalidate the handle structure on destroy. + */ + if (fDestroy) + { + Assert(ASMAtomicReadU32(&pThis->u32Magic) == RTSOCKET_MAGIC); + ASMAtomicWriteU32(&pThis->u32Magic, RTSOCKET_MAGIC_DEAD); + } + + int rc = VINF_SUCCESS; + if (ASMAtomicCmpXchgBool(&pThis->fClosed, true, false)) + { +#ifdef RT_OS_WINDOWS + /* + * Poke the polling thread if active and give it a small chance to stop. + */ + if ( pThis->fPollFallback + && pThis->hPollFallbackThread != NIL_RTTHREAD) + { + ASMAtomicWriteBool(&pThis->fPollFallbackShutdown, true); + rtSocketPokePollFallbackThread(pThis); + int rc2 = RTThreadWait(pThis->hPollFallbackThread, RT_MS_1SEC, NULL); + if (RT_SUCCESS(rc2)) + pThis->hPollFallbackThread = NIL_RTTHREAD; + } +#endif + + /* + * Close the native handle. + */ + RTSOCKETNATIVE hNative = pThis->hNative; + if (hNative != NIL_RTSOCKETNATIVE) + { + pThis->hNative = NIL_RTSOCKETNATIVE; + + if (!pThis->fLeaveOpen) + { +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnclosesocket, VERR_NET_NOT_UNSUPPORTED); + if (g_pfnclosesocket(hNative)) +#else + if (close(hNative)) +#endif + { + rc = rtSocketError(); +#ifdef RT_OS_WINDOWS + AssertMsgFailed(("closesocket(%p) -> %Rrc\n", (uintptr_t)hNative, rc)); +#else + AssertMsgFailed(("close(%d) -> %Rrc\n", hNative, rc)); +#endif + } + } + } + +#ifdef RT_OS_WINDOWS + /* + * Windows specific polling cleanup. + */ + WSAEVENT hEvent = pThis->hEvent; + if (hEvent != WSA_INVALID_EVENT) + { + pThis->hEvent = WSA_INVALID_EVENT; + if (!pThis->fPollFallback) + { + Assert(g_pfnWSACloseEvent); + if (g_pfnWSACloseEvent) + g_pfnWSACloseEvent(hEvent); + } + else + CloseHandle(hEvent); + } + + if (pThis->fPollFallback) + { + if (pThis->hPollFallbackNotifyW != NIL_RTSOCKETNATIVE) + { + g_pfnclosesocket(pThis->hPollFallbackNotifyW); + pThis->hPollFallbackNotifyW = NIL_RTSOCKETNATIVE; + } + + if (pThis->hPollFallbackThread != NIL_RTTHREAD) + { + int rc2 = RTThreadWait(pThis->hPollFallbackThread, RT_MS_1MIN / 2, NULL); + AssertRC(rc2); + pThis->hPollFallbackThread = NIL_RTTHREAD; + } + + if (pThis->hPollFallbackNotifyR != NIL_RTSOCKETNATIVE) + { + g_pfnclosesocket(pThis->hPollFallbackNotifyR); + pThis->hPollFallbackNotifyR = NIL_RTSOCKETNATIVE; + } + } +#endif + } + + return rc; +} + + +RTDECL(uint32_t) RTSocketRelease(RTSOCKET hSocket) +{ + RTSOCKETINT *pThis = hSocket; + if (pThis == NIL_RTSOCKET) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, UINT32_MAX); + + /* get the refcount without killing it... */ + uint32_t cRefs = RTMemPoolRefCount(pThis); + AssertReturn(cRefs != UINT32_MAX, UINT32_MAX); + if (cRefs == 1) + rtSocketCloseIt(pThis, true); + + return RTMemPoolRelease(RTMEMPOOL_DEFAULT, pThis); +} + + +RTDECL(int) RTSocketClose(RTSOCKET hSocket) +{ + RTSOCKETINT *pThis = hSocket; + if (pThis == NIL_RTSOCKET) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + + uint32_t cRefs = RTMemPoolRefCount(pThis); + AssertReturn(cRefs != UINT32_MAX, UINT32_MAX); + + int rc = rtSocketCloseIt(pThis, cRefs == 1); + + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pThis); + return rc; +} + + +RTDECL(RTHCUINTPTR) RTSocketToNative(RTSOCKET hSocket) +{ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, RTHCUINTPTR_MAX); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, RTHCUINTPTR_MAX); + return (RTHCUINTPTR)pThis->hNative; +} + + +RTDECL(int) RTSocketSetInheritance(RTSOCKET hSocket, bool fInheritable) +{ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRefCount(pThis) >= (pThis->cUsers ? 2U : 1U), VERR_CALLER_NO_REFERENCE); + +#ifndef RT_OS_WINDOWS + if (fcntl(pThis->hNative, F_SETFD, fInheritable ? 0 : FD_CLOEXEC) < 0) + return RTErrConvertFromErrno(errno); + return VINF_SUCCESS; +#else + /* Windows is more complicated as sockets are complicated wrt inheritance + (see stackoverflow for details). In general, though we cannot hope to + make a socket really non-inheritable before vista as other layers in + the winsock maze may have additional handles associated with the socket. */ + if (g_pfnGetHandleInformation) + { + /* Check if the handle is already in what seems to be the right state + before we try doing anything. */ + DWORD fFlags; + if (g_pfnGetHandleInformation((HANDLE)pThis->hNative, &fFlags)) + { + if (RT_BOOL(fFlags & HANDLE_FLAG_INHERIT) == fInheritable) + return VINF_SUCCESS; + } + } + + if (!g_pfnSetHandleInformation) + return VERR_NET_NOT_UNSUPPORTED; + + if (!g_pfnSetHandleInformation((HANDLE)pThis->hNative, HANDLE_FLAG_INHERIT, fInheritable ? HANDLE_FLAG_INHERIT : 0)) + return RTErrConvertFromWin32(GetLastError()); + /** @todo Need we do something related to WS_SIO_ASSOCIATE_HANDLE or + * WS_SIO_TRANSLATE_HANDLE? Or what other handles could be associated + * with the socket? that we need to modify? */ + + return VINF_SUCCESS; +#endif +} + + +static bool rtSocketIsIPv4Numerical(const char *pszAddress, PRTNETADDRIPV4 pAddr) +{ + + /* Empty address resolves to the INADDR_ANY address (good for bind). */ + if (!pszAddress || !*pszAddress) + { + pAddr->u = INADDR_ANY; + return true; + } + + /* Four quads? */ + char *psz = (char *)pszAddress; + for (int i = 0; i < 4; i++) + { + uint8_t u8; + int rc = RTStrToUInt8Ex(psz, &psz, 0, &u8); + if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) + return false; + if (*psz != (i < 3 ? '.' : '\0')) + return false; + psz++; + + pAddr->au8[i] = u8; /* big endian */ + } + + return true; +} + +RTDECL(int) RTSocketParseInetAddress(const char *pszAddress, unsigned uPort, PRTNETADDR pAddr) +{ + int rc; + + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszAddress, VERR_INVALID_POINTER); + + /* + * Resolve the address. Pretty crude at the moment, but we have to make + * sure to not ask the NT 4 gethostbyname about an IPv4 address as it may + * give a wrong answer. + */ + /** @todo this only supports IPv4, and IPv6 support needs to be added. + * It probably needs to be converted to getaddrinfo(). */ + RTNETADDRIPV4 IPv4Quad; + if (rtSocketIsIPv4Numerical(pszAddress, &IPv4Quad)) + { + Log3(("rtSocketIsIPv4Numerical: %s -> %#x (%RTnaipv4)\n", pszAddress, IPv4Quad.u, IPv4Quad)); + RT_ZERO(*pAddr); + pAddr->enmType = RTNETADDRTYPE_IPV4; + pAddr->uPort = uPort; + pAddr->uAddr.IPv4 = IPv4Quad; + return VINF_SUCCESS; + } + +#ifdef RT_OS_WINDOWS + /* Initialize WinSock and check version before we call gethostbyname. */ + if (!g_pfngethostbyname) + return VERR_NET_NOT_UNSUPPORTED; + + int rc2 = rtSocketInitWinsock(); + if (RT_FAILURE(rc2)) + return rc2; + +# define gethostbyname g_pfngethostbyname +#endif + + struct hostent *pHostEnt; + pHostEnt = gethostbyname(pszAddress); + if (!pHostEnt) + { + rc = rtSocketResolverError(); + AssertMsg(rc == VERR_NET_HOST_NOT_FOUND, + ("Could not resolve '%s', rc=%Rrc\n", pszAddress, rc)); + return rc; + } + + if (pHostEnt->h_addrtype == AF_INET) + { + RT_ZERO(*pAddr); + pAddr->enmType = RTNETADDRTYPE_IPV4; + pAddr->uPort = uPort; + pAddr->uAddr.IPv4.u = ((struct in_addr *)pHostEnt->h_addr)->s_addr; + Log3(("gethostbyname: %s -> %#x (%RTnaipv4)\n", pszAddress, pAddr->uAddr.IPv4.u, pAddr->uAddr.IPv4)); + } + else + return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED; + +#ifdef RT_OS_WINDOWS +# undef gethostbyname +#endif + return VINF_SUCCESS; +} + + +/* + * New function to allow both ipv4 and ipv6 addresses to be resolved. + * Breaks compatibility with windows before 2000. + */ +RTDECL(int) RTSocketQueryAddressStr(const char *pszHost, char *pszResult, size_t *pcbResult, PRTNETADDRTYPE penmAddrType) +{ + AssertPtrReturn(pszHost, VERR_INVALID_POINTER); + AssertPtrReturn(pcbResult, VERR_INVALID_POINTER); + AssertPtrNullReturn(penmAddrType, VERR_INVALID_POINTER); + AssertPtrNullReturn(pszResult, VERR_INVALID_POINTER); + +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) /** @todo dynamically resolve the APIs not present in NT4! */ + return VERR_NOT_SUPPORTED; + +#else + int rc; + if (*pcbResult < 16) + return VERR_NET_ADDRESS_NOT_AVAILABLE; + + /* Setup the hint. */ + struct addrinfo grHints; + RT_ZERO(grHints); + grHints.ai_socktype = 0; + grHints.ai_flags = 0; + grHints.ai_protocol = 0; + grHints.ai_family = AF_UNSPEC; + if (penmAddrType) + { + switch (*penmAddrType) + { + case RTNETADDRTYPE_INVALID: + /*grHints.ai_family = AF_UNSPEC;*/ + break; + case RTNETADDRTYPE_IPV4: + grHints.ai_family = AF_INET; + break; + case RTNETADDRTYPE_IPV6: + grHints.ai_family = AF_INET6; + break; + default: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + } + +# ifdef RT_OS_WINDOWS + /* + * Winsock2 init + */ + if ( !g_pfngetaddrinfo + || !g_pfnfreeaddrinfo) + return VERR_NET_NOT_UNSUPPORTED; + + int rc2 = rtSocketInitWinsock(); + if (RT_FAILURE(rc2)) + return rc2; + +# define getaddrinfo g_pfngetaddrinfo +# define freeaddrinfo g_pfnfreeaddrinfo +# endif + + /** @todo r=bird: getaddrinfo and freeaddrinfo breaks the additions on NT4. */ + struct addrinfo *pgrResults = NULL; + rc = getaddrinfo(pszHost, "", &grHints, &pgrResults); + if (rc != 0) + return VERR_NET_ADDRESS_NOT_AVAILABLE; + + // return data + // on multiple matches return only the first one + + if (!pgrResults) + return VERR_NET_ADDRESS_NOT_AVAILABLE; + + struct addrinfo const *pgrResult = pgrResults->ai_next; + if (!pgrResult) + { + freeaddrinfo(pgrResults); + return VERR_NET_ADDRESS_NOT_AVAILABLE; + } + + RTNETADDRTYPE enmAddrType = RTNETADDRTYPE_INVALID; + size_t cchIpAddress; + char szIpAddress[48]; + if (pgrResult->ai_family == AF_INET) + { + struct sockaddr_in const *pgrSa = (struct sockaddr_in const *)pgrResult->ai_addr; + cchIpAddress = RTStrPrintf(szIpAddress, sizeof(szIpAddress), + "%RTnaipv4", pgrSa->sin_addr.s_addr); + Assert(cchIpAddress >= 7 && cchIpAddress < sizeof(szIpAddress) - 1); + enmAddrType = RTNETADDRTYPE_IPV4; + rc = VINF_SUCCESS; + } + else if (pgrResult->ai_family == AF_INET6) + { + struct sockaddr_in6 const *pgrSa6 = (struct sockaddr_in6 const *)pgrResult->ai_addr; + cchIpAddress = RTStrPrintf(szIpAddress, sizeof(szIpAddress), + "%RTnaipv6", (PRTNETADDRIPV6)&pgrSa6->sin6_addr); + enmAddrType = RTNETADDRTYPE_IPV6; + rc = VINF_SUCCESS; + } + else + { + rc = VERR_NET_ADDRESS_NOT_AVAILABLE; + szIpAddress[0] = '\0'; + cchIpAddress = 0; + } + freeaddrinfo(pgrResults); + + /* + * Copy out the result. + */ + size_t const cbResult = *pcbResult; + *pcbResult = cchIpAddress + 1; + if (cchIpAddress < cbResult) + memcpy(pszResult, szIpAddress, cchIpAddress + 1); + else + { + RT_BZERO(pszResult, cbResult); + if (RT_SUCCESS(rc)) + rc = VERR_BUFFER_OVERFLOW; + } + if (penmAddrType && RT_SUCCESS(rc)) + *penmAddrType = enmAddrType; + return rc; + +# ifdef RT_OS_WINDOWS +# undef getaddrinfo +# undef freeaddrinfo +# endif +#endif /* !RT_OS_OS2 */ +} + + +RTDECL(int) RTSocketRead(RTSOCKET hSocket, void *pvBuffer, size_t cbBuffer, size_t *pcbRead) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(cbBuffer > 0, VERR_INVALID_PARAMETER); + AssertPtr(pvBuffer); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnrecv, VERR_NET_NOT_UNSUPPORTED); +# define recv g_pfnrecv +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = rtSocketSwitchBlockingMode(pThis, true /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + /* + * Read loop. + * If pcbRead is NULL we have to fill the entire buffer! + */ + size_t cbRead = 0; + size_t cbToRead = cbBuffer; + for (;;) + { + rtSocketErrorReset(); +#ifdef RTSOCKET_MAX_READ + int cbNow = cbToRead >= RTSOCKET_MAX_READ ? RTSOCKET_MAX_READ : (int)cbToRead; +#else + size_t cbNow = cbToRead; +#endif + ssize_t cbBytesRead = recv(pThis->hNative, (char *)pvBuffer + cbRead, cbNow, MSG_NOSIGNAL); + if (cbBytesRead <= 0) + { + rc = rtSocketError(); + Assert(RT_FAILURE_NP(rc) || cbBytesRead == 0); + if (RT_SUCCESS_NP(rc)) + { + if (!pcbRead) + rc = VERR_NET_SHUTDOWN; + else + { + *pcbRead = 0; + rc = VINF_SUCCESS; + } + } + break; + } + if (pcbRead) + { + /* return partial data */ + *pcbRead = cbBytesRead; + break; + } + + /* read more? */ + cbRead += cbBytesRead; + if (cbRead == cbBuffer) + break; + + /* next */ + cbToRead = cbBuffer - cbRead; + } + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef recv +#endif + return rc; +} + + +RTDECL(int) RTSocketReadFrom(RTSOCKET hSocket, void *pvBuffer, size_t cbBuffer, size_t *pcbRead, PRTNETADDR pSrcAddr) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(cbBuffer > 0, VERR_INVALID_PARAMETER); + AssertPtr(pvBuffer); + AssertPtr(pcbRead); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnrecvfrom, VERR_NET_NOT_UNSUPPORTED); +# define recvfrom g_pfnrecvfrom +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = rtSocketSwitchBlockingMode(pThis, true /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + /* + * Read data. + */ + size_t cbRead = 0; + size_t cbToRead = cbBuffer; + rtSocketErrorReset(); + RTSOCKADDRUNION u; +#ifdef RTSOCKET_MAX_READ + int cbNow = cbToRead >= RTSOCKET_MAX_READ ? RTSOCKET_MAX_READ : (int)cbToRead; + int cbAddr = sizeof(u); +#else + size_t cbNow = cbToRead; + socklen_t cbAddr = sizeof(u); +#endif + ssize_t cbBytesRead = recvfrom(pThis->hNative, (char *)pvBuffer + cbRead, cbNow, MSG_NOSIGNAL, &u.Addr, &cbAddr); + if (cbBytesRead <= 0) + { + rc = rtSocketError(); + Assert(RT_FAILURE_NP(rc) || cbBytesRead == 0); + if (RT_SUCCESS_NP(rc)) + { + *pcbRead = 0; + rc = VINF_SUCCESS; + } + } + else + { + if (pSrcAddr) + rc = rtSocketNetAddrFromAddr(&u, cbAddr, pSrcAddr); + *pcbRead = cbBytesRead; + } + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef recvfrom +#endif + return rc; +} + + +RTDECL(int) RTSocketWrite(RTSOCKET hSocket, const void *pvBuffer, size_t cbBuffer) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnsend, VERR_NET_NOT_UNSUPPORTED); +# define send g_pfnsend +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = rtSocketSwitchBlockingMode(pThis, true /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + /* + * Try write all at once. + */ +#ifdef RTSOCKET_MAX_WRITE + int cbNow = cbBuffer >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbBuffer; +#else + size_t cbNow = cbBuffer >= SSIZE_MAX ? SSIZE_MAX : cbBuffer; +#endif + ssize_t cbWritten = send(pThis->hNative, (const char *)pvBuffer, cbNow, MSG_NOSIGNAL); + if (RT_LIKELY((size_t)cbWritten == cbBuffer && cbWritten >= 0)) + rc = VINF_SUCCESS; + else if (cbWritten < 0) + rc = rtSocketError(); + else + { + /* + * Unfinished business, write the remainder of the request. Must ignore + * VERR_INTERRUPTED here if we've managed to send something. + */ + size_t cbSentSoFar = 0; + for (;;) + { + /* advance */ + cbBuffer -= (size_t)cbWritten; + if (!cbBuffer) + break; + cbSentSoFar += (size_t)cbWritten; + pvBuffer = (char const *)pvBuffer + cbWritten; + + /* send */ +#ifdef RTSOCKET_MAX_WRITE + cbNow = cbBuffer >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbBuffer; +#else + cbNow = cbBuffer >= SSIZE_MAX ? SSIZE_MAX : cbBuffer; +#endif + cbWritten = send(pThis->hNative, (const char *)pvBuffer, cbNow, MSG_NOSIGNAL); + if (cbWritten >= 0) + AssertMsg(cbBuffer >= (size_t)cbWritten, ("Wrote more than we requested!!! cbWritten=%zu cbBuffer=%zu rtSocketError()=%d\n", + cbWritten, cbBuffer, rtSocketError())); + else + { + rc = rtSocketError(); + if (rc != VERR_INTERNAL_ERROR || cbSentSoFar == 0) + break; + cbWritten = 0; + rc = VINF_SUCCESS; + } + } + } + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef send +#endif + return rc; +} + + +RTDECL(int) RTSocketWriteTo(RTSOCKET hSocket, const void *pvBuffer, size_t cbBuffer, PCRTNETADDR pAddr) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnsendto, VERR_NET_NOT_UNSUPPORTED); +# define sendto g_pfnsendto +#endif + + /* no locking since UDP reads may be done concurrently to writes, and + * this is the normal use case of this code. */ + + int rc = rtSocketSwitchBlockingMode(pThis, true /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + /* Figure out destination address. */ + struct sockaddr *pSA = NULL; +#ifdef RT_OS_WINDOWS + int cbSA = 0; +#else + socklen_t cbSA = 0; +#endif + RTSOCKADDRUNION u; + if (pAddr) + { + rc = rtSocketAddrFromNetAddr(pAddr, &u, sizeof(u), NULL); + if (RT_FAILURE(rc)) + return rc; + pSA = &u.Addr; + cbSA = sizeof(u); + } + + /* + * Must write all at once, otherwise it is a failure. + */ +#ifdef RT_OS_WINDOWS + int cbNow = cbBuffer >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbBuffer; +#else + size_t cbNow = cbBuffer >= SSIZE_MAX ? SSIZE_MAX : cbBuffer; +#endif + ssize_t cbWritten = sendto(pThis->hNative, (const char *)pvBuffer, cbNow, MSG_NOSIGNAL, pSA, cbSA); + if (RT_LIKELY((size_t)cbWritten == cbBuffer && cbWritten >= 0)) + rc = VINF_SUCCESS; + else if (cbWritten < 0) + rc = rtSocketError(); + else + rc = VERR_TOO_MUCH_DATA; + + /// @todo rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef sendto +#endif + return rc; +} + + +RTDECL(int) RTSocketWriteToNB(RTSOCKET hSocket, const void *pvBuffer, size_t cbBuffer, PCRTNETADDR pAddr) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnsendto, VERR_NET_NOT_UNSUPPORTED); +# define sendto g_pfnsendto +#endif + + /* no locking since UDP reads may be done concurrently to writes, and + * this is the normal use case of this code. */ + + int rc = rtSocketSwitchBlockingMode(pThis, false /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + /* Figure out destination address. */ + struct sockaddr *pSA = NULL; +#ifdef RT_OS_WINDOWS + int cbSA = 0; +#else + socklen_t cbSA = 0; +#endif + RTSOCKADDRUNION u; + if (pAddr) + { + rc = rtSocketAddrFromNetAddr(pAddr, &u, sizeof(u), NULL); + if (RT_FAILURE(rc)) + return rc; + pSA = &u.Addr; + cbSA = sizeof(u); + } + + /* + * Must write all at once, otherwise it is a failure. + */ +#ifdef RT_OS_WINDOWS + int cbNow = cbBuffer >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbBuffer; +#else + size_t cbNow = cbBuffer >= SSIZE_MAX ? SSIZE_MAX : cbBuffer; +#endif + ssize_t cbWritten = sendto(pThis->hNative, (const char *)pvBuffer, cbNow, MSG_NOSIGNAL, pSA, cbSA); + if (RT_LIKELY((size_t)cbWritten == cbBuffer && cbWritten >= 0)) + rc = VINF_SUCCESS; + else if (cbWritten < 0) + rc = rtSocketError(); + else + rc = VERR_TOO_MUCH_DATA; + + /// @todo rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef sendto +#endif + return rc; +} + + +RTDECL(int) RTSocketSgWrite(RTSOCKET hSocket, PCRTSGBUF pSgBuf) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pSgBuf, VERR_INVALID_PARAMETER); + AssertReturn(pSgBuf->cSegs > 0, VERR_INVALID_PARAMETER); + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = rtSocketSwitchBlockingMode(pThis, true /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + /* + * Construct message descriptor (translate pSgBuf) and send it. + */ + rc = VERR_NO_TMP_MEMORY; +#ifdef RT_OS_WINDOWS + if (g_pfnWSASend) + { + AssertCompileSize(WSABUF, sizeof(RTSGSEG)); + AssertCompileMemberSize(WSABUF, buf, RT_SIZEOFMEMB(RTSGSEG, pvSeg)); + + LPWSABUF paMsg = (LPWSABUF)RTMemTmpAllocZ(pSgBuf->cSegs * sizeof(WSABUF)); + if (paMsg) + { + for (unsigned i = 0; i < pSgBuf->cSegs; i++) + { + paMsg[i].buf = (char *)pSgBuf->paSegs[i].pvSeg; + paMsg[i].len = (u_long)pSgBuf->paSegs[i].cbSeg; + } + + DWORD dwSent; + int hrc = g_pfnWSASend(pThis->hNative, paMsg, pSgBuf->cSegs, &dwSent, MSG_NOSIGNAL, NULL, NULL); + if (!hrc) + rc = VINF_SUCCESS; + /** @todo check for incomplete writes */ + else + rc = rtSocketError(); + + RTMemTmpFree(paMsg); + } + } + else if (g_pfnsend) + { + rc = VINF_SUCCESS; + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + uint8_t const *pbSeg = (uint8_t const *)pSgBuf->paSegs[iSeg].pvSeg; + size_t cbSeg = pSgBuf->paSegs[iSeg].cbSeg; + int cbNow; + ssize_t cbWritten; + for (;;) + { + cbNow = cbSeg >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbSeg; + cbWritten = g_pfnsend(pThis->hNative, (const char *)pbSeg, cbNow, MSG_NOSIGNAL); + if ((size_t)cbWritten >= cbSeg || cbWritten < 0) + break; + pbSeg += cbWritten; + cbSeg -= cbWritten; + } + if (cbWritten < 0) + { + rc = rtSocketError(); + break; + } + } + } + else + rc = VERR_NET_NOT_UNSUPPORTED; + +#else /* !RT_OS_WINDOWS */ + AssertCompileSize(struct iovec, sizeof(RTSGSEG)); + AssertCompileMemberSize(struct iovec, iov_base, RT_SIZEOFMEMB(RTSGSEG, pvSeg)); + AssertCompileMemberSize(struct iovec, iov_len, RT_SIZEOFMEMB(RTSGSEG, cbSeg)); + + struct iovec *paMsg = (struct iovec *)RTMemTmpAllocZ(pSgBuf->cSegs * sizeof(struct iovec)); + if (paMsg) + { + for (unsigned i = 0; i < pSgBuf->cSegs; i++) + { + paMsg[i].iov_base = pSgBuf->paSegs[i].pvSeg; + paMsg[i].iov_len = pSgBuf->paSegs[i].cbSeg; + } + + struct msghdr msgHdr; + RT_ZERO(msgHdr); + msgHdr.msg_iov = paMsg; + msgHdr.msg_iovlen = pSgBuf->cSegs; + ssize_t cbWritten = sendmsg(pThis->hNative, &msgHdr, MSG_NOSIGNAL); + if (RT_LIKELY(cbWritten >= 0)) + rc = VINF_SUCCESS; +/** @todo check for incomplete writes */ + else + rc = rtSocketError(); + + RTMemTmpFree(paMsg); + } +#endif /* !RT_OS_WINDOWS */ + + rtSocketUnlock(pThis); + return rc; +} + + +RTDECL(int) RTSocketSgWriteL(RTSOCKET hSocket, size_t cSegs, ...) +{ + va_list va; + va_start(va, cSegs); + int rc = RTSocketSgWriteLV(hSocket, cSegs, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTSocketSgWriteLV(RTSOCKET hSocket, size_t cSegs, va_list va) +{ + /* + * Set up a S/G segment array + buffer on the stack and pass it + * on to RTSocketSgWrite. + */ + Assert(cSegs <= 16); + PRTSGSEG paSegs = (PRTSGSEG)alloca(cSegs * sizeof(RTSGSEG)); + AssertReturn(paSegs, VERR_NO_TMP_MEMORY); + for (size_t i = 0; i < cSegs; i++) + { + paSegs[i].pvSeg = va_arg(va, void *); + paSegs[i].cbSeg = va_arg(va, size_t); + } + + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, paSegs, cSegs); + return RTSocketSgWrite(hSocket, &SgBuf); +} + + +RTDECL(int) RTSocketReadNB(RTSOCKET hSocket, void *pvBuffer, size_t cbBuffer, size_t *pcbRead) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(cbBuffer > 0, VERR_INVALID_PARAMETER); + AssertPtr(pvBuffer); + AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnrecv, VERR_NET_NOT_UNSUPPORTED); +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = rtSocketSwitchBlockingMode(pThis, false /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + rtSocketErrorReset(); +#ifdef RTSOCKET_MAX_READ + int cbNow = cbBuffer >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbBuffer; +#else + size_t cbNow = cbBuffer; +#endif + +#ifdef RT_OS_WINDOWS + int cbRead = g_pfnrecv(pThis->hNative, (char *)pvBuffer, cbNow, MSG_NOSIGNAL); + if (cbRead >= 0) + { + *pcbRead = cbRead; + rc = VINF_SUCCESS; + } + else + { + rc = rtSocketError(); + if (rc == VERR_TRY_AGAIN) + { + *pcbRead = 0; + rc = VINF_TRY_AGAIN; + } + } + +#else + ssize_t cbRead = recv(pThis->hNative, pvBuffer, cbNow, MSG_NOSIGNAL); + if (cbRead >= 0) + *pcbRead = cbRead; + else if ( errno == EAGAIN +# ifdef EWOULDBLOCK +# if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +# endif +# endif + ) + { + *pcbRead = 0; + rc = VINF_TRY_AGAIN; + } + else + rc = rtSocketError(); +#endif + + rtSocketUnlock(pThis); + return rc; +} + + +RTDECL(int) RTSocketWriteNB(RTSOCKET hSocket, const void *pvBuffer, size_t cbBuffer, size_t *pcbWritten) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnsend, VERR_NET_NOT_UNSUPPORTED); +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = rtSocketSwitchBlockingMode(pThis, false /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + rtSocketErrorReset(); +#ifdef RT_OS_WINDOWS +# ifdef RTSOCKET_MAX_WRITE + int cbNow = cbBuffer >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbBuffer; +# else + size_t cbNow = cbBuffer; +# endif + int cbWritten = g_pfnsend(pThis->hNative, (const char *)pvBuffer, cbNow, MSG_NOSIGNAL); + if (cbWritten >= 0) + { + *pcbWritten = cbWritten; + rc = VINF_SUCCESS; + } + else + { + rc = rtSocketError(); + if (rc == VERR_TRY_AGAIN) + { + *pcbWritten = 0; + rc = VINF_TRY_AGAIN; + } + } +#else + ssize_t cbWritten = send(pThis->hNative, pvBuffer, cbBuffer, MSG_NOSIGNAL); + if (cbWritten >= 0) + *pcbWritten = cbWritten; + else if ( errno == EAGAIN +# ifdef EWOULDBLOCK +# if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +# endif +# endif + ) + { + *pcbWritten = 0; + rc = VINF_TRY_AGAIN; + } + else + rc = rtSocketError(); +#endif + + rtSocketUnlock(pThis); + return rc; +} + + +RTDECL(int) RTSocketSgWriteNB(RTSOCKET hSocket, PCRTSGBUF pSgBuf, size_t *pcbWritten) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pSgBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER); + AssertReturn(pSgBuf->cSegs > 0, VERR_INVALID_PARAMETER); + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = rtSocketSwitchBlockingMode(pThis, false /* fBlocking */); + if (RT_FAILURE(rc)) + return rc; + + unsigned cSegsToSend = 0; + rc = VERR_NO_TMP_MEMORY; +#ifdef RT_OS_WINDOWS + if (g_pfnWSASend) + { + LPWSABUF paMsg = NULL; + RTSgBufMapToNative(paMsg, pSgBuf, WSABUF, buf, char *, len, u_long, cSegsToSend); + if (paMsg) + { + DWORD dwSent = 0; + int hrc = g_pfnWSASend(pThis->hNative, paMsg, cSegsToSend, &dwSent, MSG_NOSIGNAL, NULL, NULL); + if (!hrc) + rc = VINF_SUCCESS; + else + rc = rtSocketError(); + + *pcbWritten = dwSent; + + RTMemTmpFree(paMsg); + } + } + else if (g_pfnsend) + { + size_t cbWrittenTotal = 0; + rc = VINF_SUCCESS; + for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + { + uint8_t const *pbSeg = (uint8_t const *)pSgBuf->paSegs[iSeg].pvSeg; + size_t cbSeg = pSgBuf->paSegs[iSeg].cbSeg; + int cbNow; + ssize_t cbWritten; + for (;;) + { + cbNow = cbSeg >= RTSOCKET_MAX_WRITE ? RTSOCKET_MAX_WRITE : (int)cbSeg; + cbWritten = g_pfnsend(pThis->hNative, (const char *)pbSeg, cbNow, MSG_NOSIGNAL); + if ((size_t)cbWritten >= cbSeg || cbWritten < 0) + break; + cbWrittenTotal += cbWrittenTotal; + pbSeg += cbWritten; + cbSeg -= cbWritten; + } + if (cbWritten < 0) + { + rc = rtSocketError(); + break; + } + if (cbWritten != cbNow) + break; + } + *pcbWritten = cbWrittenTotal; + } + else + rc = VERR_NET_NOT_UNSUPPORTED; + +#else /* !RT_OS_WINDOWS */ + struct iovec *paMsg = NULL; + + RTSgBufMapToNative(paMsg, pSgBuf, struct iovec, iov_base, void *, iov_len, size_t, cSegsToSend); + if (paMsg) + { + struct msghdr msgHdr; + RT_ZERO(msgHdr); + msgHdr.msg_iov = paMsg; + msgHdr.msg_iovlen = cSegsToSend; + ssize_t cbWritten = sendmsg(pThis->hNative, &msgHdr, MSG_NOSIGNAL); + if (RT_LIKELY(cbWritten >= 0)) + { + rc = VINF_SUCCESS; + *pcbWritten = cbWritten; + } + else + rc = rtSocketError(); + + RTMemTmpFree(paMsg); + } +#endif /* !RT_OS_WINDOWS */ + + rtSocketUnlock(pThis); + return rc; +} + + +RTDECL(int) RTSocketSgWriteLNB(RTSOCKET hSocket, size_t cSegs, size_t *pcbWritten, ...) +{ + va_list va; + va_start(va, pcbWritten); + int rc = RTSocketSgWriteLVNB(hSocket, cSegs, pcbWritten, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTSocketSgWriteLVNB(RTSOCKET hSocket, size_t cSegs, size_t *pcbWritten, va_list va) +{ + /* + * Set up a S/G segment array + buffer on the stack and pass it + * on to RTSocketSgWrite. + */ + Assert(cSegs <= 16); + PRTSGSEG paSegs = (PRTSGSEG)alloca(cSegs * sizeof(RTSGSEG)); + AssertReturn(paSegs, VERR_NO_TMP_MEMORY); + for (size_t i = 0; i < cSegs; i++) + { + paSegs[i].pvSeg = va_arg(va, void *); + paSegs[i].cbSeg = va_arg(va, size_t); + } + + RTSGBUF SgBuf; + RTSgBufInit(&SgBuf, paSegs, cSegs); + return RTSocketSgWriteNB(hSocket, &SgBuf, pcbWritten); +} + + +RTDECL(int) RTSocketSelectOne(RTSOCKET hSocket, RTMSINTERVAL cMillies) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRefCount(pThis) >= (pThis->cUsers ? 2U : 1U), VERR_CALLER_NO_REFERENCE); + int const fdMax = (int)pThis->hNative + 1; + AssertReturn((RTSOCKETNATIVE)(fdMax - 1) == pThis->hNative, VERR_INTERNAL_ERROR_5); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnselect, VERR_NET_NOT_UNSUPPORTED); +# define select g_pfnselect +#endif + + /* + * Set up the file descriptor sets and do the select. + */ + fd_set fdsetR; + FD_ZERO(&fdsetR); + FD_SET(pThis->hNative, &fdsetR); + + fd_set fdsetE = fdsetR; + + int rc; + if (cMillies == RT_INDEFINITE_WAIT) + rc = select(fdMax, &fdsetR, NULL, &fdsetE, NULL); + else + { + struct timeval timeout; + timeout.tv_sec = cMillies / 1000; + timeout.tv_usec = (cMillies % 1000) * 1000; + rc = select(fdMax, &fdsetR, NULL, &fdsetE, &timeout); + } + if (rc > 0) + rc = VINF_SUCCESS; + else if (rc == 0) + rc = VERR_TIMEOUT; + else + rc = rtSocketError(); + +#ifdef RT_OS_WINDOWS +# undef select +#endif + return rc; +} + + +/** + * Internal worker for RTSocketSelectOneEx and rtSocketPollCheck (fallback) + * + * @returns IPRT status code + * @param pThis The socket (valid). + * @param fEvents The events to select for. + * @param pfEvents Where to return the events. + * @param cMillies How long to select for, in milliseconds. + */ +static int rtSocketSelectOneEx(RTSOCKET pThis, uint32_t fEvents, uint32_t *pfEvents, RTMSINTERVAL cMillies) +{ + RTSOCKETNATIVE hNative = pThis->hNative; + if (hNative == NIL_RTSOCKETNATIVE) + { + /* Socket is already closed? Possible we raced someone calling rtSocketCloseIt. + Should we return a different status code? */ + *pfEvents = RTSOCKET_EVT_ERROR; + return VINF_SUCCESS; + } + + int const fdMax = (int)hNative + 1; + AssertReturn((RTSOCKETNATIVE)(fdMax - 1) == hNative, VERR_INTERNAL_ERROR_5); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnselect, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfn__WSAFDIsSet, VERR_NET_NOT_UNSUPPORTED); +# define select g_pfnselect +# define __WSAFDIsSet g_pfn__WSAFDIsSet +#endif + + *pfEvents = 0; + + /* + * Set up the file descriptor sets and do the select. + */ + fd_set fdsetR; + fd_set fdsetW; + fd_set fdsetE; + FD_ZERO(&fdsetR); + FD_ZERO(&fdsetW); + FD_ZERO(&fdsetE); + + if (fEvents & RTSOCKET_EVT_READ) + FD_SET(hNative, &fdsetR); + if (fEvents & RTSOCKET_EVT_WRITE) + FD_SET(hNative, &fdsetW); + if (fEvents & RTSOCKET_EVT_ERROR) + FD_SET(hNative, &fdsetE); + + int rc; + if (cMillies == RT_INDEFINITE_WAIT) + rc = select(fdMax, &fdsetR, &fdsetW, &fdsetE, NULL); + else + { + struct timeval timeout; + timeout.tv_sec = cMillies / 1000; + timeout.tv_usec = (cMillies % 1000) * 1000; + rc = select(fdMax, &fdsetR, &fdsetW, &fdsetE, &timeout); + } + if (rc > 0) + { + if (pThis->hNative == hNative) + { + if (FD_ISSET(hNative, &fdsetR)) + *pfEvents |= RTSOCKET_EVT_READ; + if (FD_ISSET(hNative, &fdsetW)) + *pfEvents |= RTSOCKET_EVT_WRITE; + if (FD_ISSET(hNative, &fdsetE)) + *pfEvents |= RTSOCKET_EVT_ERROR; + rc = VINF_SUCCESS; + } + else + { + /* Socket was closed while we waited (rtSocketCloseIt). Different status code? */ + *pfEvents = RTSOCKET_EVT_ERROR; + rc = VINF_SUCCESS; + } + } + else if (rc == 0) + rc = VERR_TIMEOUT; + else + rc = rtSocketError(); + +#ifdef RT_OS_WINDOWS +# undef select +# undef __WSAFDIsSet +#endif + return rc; +} + + +RTDECL(int) RTSocketSelectOneEx(RTSOCKET hSocket, uint32_t fEvents, uint32_t *pfEvents, RTMSINTERVAL cMillies) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfEvents, VERR_INVALID_PARAMETER); + AssertReturn(!(fEvents & ~RTSOCKET_EVT_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(RTMemPoolRefCount(pThis) >= (pThis->cUsers ? 2U : 1U), VERR_CALLER_NO_REFERENCE); + + return rtSocketSelectOneEx(pThis, fEvents, pfEvents, cMillies); +} + + +RTDECL(int) RTSocketShutdown(RTSOCKET hSocket, bool fRead, bool fWrite) +{ + /* + * Validate input, don't lock it because we might want to interrupt a call + * active on a different thread. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRefCount(pThis) >= (pThis->cUsers ? 2U : 1U), VERR_CALLER_NO_REFERENCE); + AssertReturn(fRead || fWrite, VERR_INVALID_PARAMETER); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnshutdown, VERR_NET_NOT_UNSUPPORTED); +# define shutdown g_pfnshutdown +#endif + + /* + * Do the job. + */ + int rc = VINF_SUCCESS; + int fHow; + if (fRead && fWrite) + fHow = SHUT_RDWR; + else if (fRead) + fHow = SHUT_RD; + else + fHow = SHUT_WR; + if (shutdown(pThis->hNative, fHow) == -1) + rc = rtSocketError(); + +#ifdef RT_OS_WINDOWS +# undef shutdown +#endif + return rc; +} + + +RTDECL(int) RTSocketGetLocalAddress(RTSOCKET hSocket, PRTNETADDR pAddr) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRefCount(pThis) >= (pThis->cUsers ? 2U : 1U), VERR_CALLER_NO_REFERENCE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfngetsockname, VERR_NET_NOT_UNSUPPORTED); +# define getsockname g_pfngetsockname +#endif + + /* + * Get the address and convert it. + */ + int rc; + RTSOCKADDRUNION u; +#ifdef RT_OS_WINDOWS + int cbAddr = sizeof(u); +#else + socklen_t cbAddr = sizeof(u); +#endif + RT_ZERO(u); + if (getsockname(pThis->hNative, &u.Addr, &cbAddr) == 0) + rc = rtSocketNetAddrFromAddr(&u, cbAddr, pAddr); + else + rc = rtSocketError(); + +#ifdef RT_OS_WINDOWS +# undef getsockname +#endif + return rc; +} + + +RTDECL(int) RTSocketGetPeerAddress(RTSOCKET hSocket, PRTNETADDR pAddr) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRefCount(pThis) >= (pThis->cUsers ? 2U : 1U), VERR_CALLER_NO_REFERENCE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfngetpeername, VERR_NET_NOT_UNSUPPORTED); +# define getpeername g_pfngetpeername +#endif + + /* + * Get the address and convert it. + */ + int rc; + RTSOCKADDRUNION u; +#ifdef RT_OS_WINDOWS + int cbAddr = sizeof(u); +#else + socklen_t cbAddr = sizeof(u); +#endif + RT_ZERO(u); + if (getpeername(pThis->hNative, &u.Addr, &cbAddr) == 0) + rc = rtSocketNetAddrFromAddr(&u, cbAddr, pAddr); + else + rc = rtSocketError(); + +#ifdef RT_OS_WINDOWS +# undef getpeername +#endif + return rc; +} + + + +/** + * Wrapper around bind. + * + * @returns IPRT status code. + * @param hSocket The socket handle. + * @param pAddr The address to bind to. + */ +DECLHIDDEN(int) rtSocketBind(RTSOCKET hSocket, PCRTNETADDR pAddr) +{ + RTSOCKADDRUNION u; + int cbAddr; + int rc = rtSocketAddrFromNetAddr(pAddr, &u, sizeof(u), &cbAddr); + if (RT_SUCCESS(rc)) + rc = rtSocketBindRawAddr(hSocket, &u.Addr, cbAddr); + return rc; +} + + +/** + * Very thin wrapper around bind. + * + * @returns IPRT status code. + * @param hSocket The socket handle. + * @param pvAddr The address to bind to (struct sockaddr and + * friends). + * @param cbAddr The size of the address. + */ +DECLHIDDEN(int) rtSocketBindRawAddr(RTSOCKET hSocket, void const *pvAddr, size_t cbAddr) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvAddr, VERR_INVALID_POINTER); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnbind, VERR_NET_NOT_UNSUPPORTED); +# define bind g_pfnbind +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc; + if (bind(pThis->hNative, (struct sockaddr const *)pvAddr, (int)cbAddr) == 0) + rc = VINF_SUCCESS; + else + rc = rtSocketError(); + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef bind +#endif + return rc; +} + + + +/** + * Wrapper around listen. + * + * @returns IPRT status code. + * @param hSocket The socket handle. + * @param cMaxPending The max number of pending connections. + */ +DECLHIDDEN(int) rtSocketListen(RTSOCKET hSocket, int cMaxPending) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnlisten, VERR_NET_NOT_UNSUPPORTED); +# define listen g_pfnlisten +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = VINF_SUCCESS; + if (listen(pThis->hNative, cMaxPending) != 0) + rc = rtSocketError(); + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef listen +#endif + return rc; +} + + +/** + * Wrapper around accept. + * + * @returns IPRT status code. + * @param hSocket The socket handle. + * @param phClient Where to return the client socket handle on + * success. + * @param pAddr Where to return the client address. + * @param pcbAddr On input this gives the size buffer size of what + * @a pAddr point to. On return this contains the + * size of what's stored at @a pAddr. + */ +DECLHIDDEN(int) rtSocketAccept(RTSOCKET hSocket, PRTSOCKET phClient, struct sockaddr *pAddr, size_t *pcbAddr) +{ + /* + * Validate input. + * Only lock the socket temporarily while we get the native handle, so that + * we can safely shutdown and destroy the socket from a different thread. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnaccept, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnclosesocket, VERR_NET_NOT_UNSUPPORTED); +# define accept g_pfnaccept +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + /* + * Call accept(). + */ + rtSocketErrorReset(); + int rc = VINF_SUCCESS; +#ifdef RT_OS_WINDOWS + int cbAddr = (int)*pcbAddr; +#else + socklen_t cbAddr = *pcbAddr; +#endif + RTSOCKETNATIVE hNativeClient = accept(pThis->hNative, pAddr, &cbAddr); + if (hNativeClient != NIL_RTSOCKETNATIVE) + { + *pcbAddr = cbAddr; + + /* + * Wrap the client socket. + */ + rc = rtSocketCreateForNative(phClient, hNativeClient, false /*fLeaveOpen*/); + if (RT_FAILURE(rc)) + { +#ifdef RT_OS_WINDOWS + g_pfnclosesocket(hNativeClient); +#else + close(hNativeClient); +#endif + } + } + else + rc = rtSocketError(); + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef accept +#endif + return rc; +} + + +/** + * Wrapper around connect. + * + * @returns IPRT status code. + * @param hSocket The socket handle. + * @param pAddr The socket address to connect to. + * @param cMillies Number of milliseconds to wait for the connect attempt to complete. + * Use RT_INDEFINITE_WAIT to wait for ever. + * Use RT_TCPCLIENTCONNECT_DEFAULT_WAIT to wait for the default time + * configured on the running system. + */ +DECLHIDDEN(int) rtSocketConnect(RTSOCKET hSocket, PCRTNETADDR pAddr, RTMSINTERVAL cMillies) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnconnect, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfnselect, VERR_NET_NOT_UNSUPPORTED); + AssertReturn(g_pfngetsockopt, VERR_NET_NOT_UNSUPPORTED); +# define connect g_pfnconnect +# define select g_pfnselect +# define getsockopt g_pfngetsockopt +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + RTSOCKADDRUNION u; + int cbAddr; + int rc = rtSocketAddrFromNetAddr(pAddr, &u, sizeof(u), &cbAddr); + if (RT_SUCCESS(rc)) + { + if (cMillies == RT_SOCKETCONNECT_DEFAULT_WAIT) + { + if (connect(pThis->hNative, &u.Addr, cbAddr) != 0) + rc = rtSocketError(); + } + else + { + /* + * Switch the socket to nonblocking mode, initiate the connect + * and wait for the socket to become writable or until the timeout + * expires. + */ + rc = rtSocketSwitchBlockingMode(pThis, false /* fBlocking */); + if (RT_SUCCESS(rc)) + { + if (connect(pThis->hNative, &u.Addr, cbAddr) != 0) + { + rc = rtSocketError(); + if (rc == VERR_TRY_AGAIN || rc == VERR_NET_IN_PROGRESS) + { + int rcSock = 0; + fd_set FdSetWriteable; + struct timeval TvTimeout; + + TvTimeout.tv_sec = cMillies / RT_MS_1SEC; + TvTimeout.tv_usec = (cMillies % RT_MS_1SEC) * RT_US_1MS; + + FD_ZERO(&FdSetWriteable); + FD_SET(pThis->hNative, &FdSetWriteable); + do + { + rcSock = select(pThis->hNative + 1, NULL, &FdSetWriteable, NULL, + cMillies == RT_INDEFINITE_WAIT || cMillies >= INT_MAX + ? NULL + : &TvTimeout); + if (rcSock > 0) + { + int iSockError = 0; + socklen_t cbSockOpt = sizeof(iSockError); + rcSock = getsockopt(pThis->hNative, SOL_SOCKET, SO_ERROR, (char *)&iSockError, &cbSockOpt); + if (rcSock == 0) + { + if (iSockError == 0) + rc = VINF_SUCCESS; + else + { +#ifdef RT_OS_WINDOWS + rc = RTErrConvertFromWin32(iSockError); +#else + rc = RTErrConvertFromErrno(iSockError); +#endif + } + } + else + rc = rtSocketError(); + } + else if (rcSock == 0) + rc = VERR_TIMEOUT; + else + rc = rtSocketError(); + } while (rc == VERR_INTERRUPTED); + } + } + + rtSocketSwitchBlockingMode(pThis, true /* fBlocking */); + } + } + } + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef connect +# undef select +# undef getsockopt +#endif + return rc; +} + + +/** + * Wrapper around connect, raw address, no timeout. + * + * @returns IPRT status code. + * @param hSocket The socket handle. + * @param pvAddr The raw socket address to connect to. + * @param cbAddr The size of the raw address. + */ +DECLHIDDEN(int) rtSocketConnectRaw(RTSOCKET hSocket, void const *pvAddr, size_t cbAddr) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnconnect, VERR_NET_NOT_UNSUPPORTED); +# define connect g_pfnconnect +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc; + if (connect(pThis->hNative, (const struct sockaddr *)pvAddr, (int)cbAddr) == 0) + rc = VINF_SUCCESS; + else + rc = rtSocketError(); + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef connect +#endif + return rc; +} + + +/** + * Wrapper around setsockopt. + * + * @returns IPRT status code. + * @param hSocket The socket handle. + * @param iLevel The protocol level, e.g. IPPORTO_TCP. + * @param iOption The option, e.g. TCP_NODELAY. + * @param pvValue The value buffer. + * @param cbValue The size of the value pointed to by pvValue. + */ +DECLHIDDEN(int) rtSocketSetOpt(RTSOCKET hSocket, int iLevel, int iOption, void const *pvValue, int cbValue) +{ + /* + * Validate input. + */ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(g_pfnsetsockopt, VERR_NET_NOT_UNSUPPORTED); +# define setsockopt g_pfnsetsockopt +#endif + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = VINF_SUCCESS; + if (setsockopt(pThis->hNative, iLevel, iOption, (const char *)pvValue, cbValue) != 0) + rc = rtSocketError(); + + rtSocketUnlock(pThis); +#ifdef RT_OS_WINDOWS +# undef setsockopt +#endif + return rc; +} + + +/** + * Internal RTPollSetAdd helper that returns the handle that should be added to + * the pollset. + * + * @returns Valid handle on success, INVALID_HANDLE_VALUE on failure. + * @param hSocket The socket handle. + * @param fEvents The events we're polling for. + * @param phNative Where to put the primary handle. + */ +DECLHIDDEN(int) rtSocketPollGetHandle(RTSOCKET hSocket, uint32_t fEvents, PRTHCINTPTR phNative) +{ + RTSOCKETINT *pThis = hSocket; + RT_NOREF_PV(fEvents); + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, VERR_INVALID_HANDLE); +#ifdef RT_OS_WINDOWS + AssertReturn(rtSocketTryLock(pThis), VERR_CONCURRENT_ACCESS); + + int rc = VINF_SUCCESS; + if (pThis->hEvent != WSA_INVALID_EVENT) + *phNative = (RTHCINTPTR)pThis->hEvent; + else if (g_pfnWSACreateEvent) + { + pThis->hEvent = g_pfnWSACreateEvent(); + *phNative = (RTHCINTPTR)pThis->hEvent; + if (pThis->hEvent == WSA_INVALID_EVENT) + rc = rtSocketError(); + } + else + { + AssertCompile(WSA_INVALID_EVENT == (WSAEVENT)NULL); + pThis->hEvent = CreateEventW(NULL, TRUE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/); + *phNative = (RTHCINTPTR)pThis->hEvent; + if (pThis->hEvent == WSA_INVALID_EVENT) + rc = RTErrConvertFromWin32(GetLastError()); + } + + rtSocketUnlock(pThis); + return rc; + +#else /* !RT_OS_WINDOWS */ + *phNative = (RTHCUINTPTR)pThis->hNative; + return VINF_SUCCESS; +#endif /* !RT_OS_WINDOWS */ +} + +#ifdef RT_OS_WINDOWS + +/** + * Fallback poller thread. + * + * @returns VINF_SUCCESS. + * @param hSelf The thread handle. + * @param pvUser Socket instance data. + */ +static DECLCALLBACK(int) rtSocketPollFallbackThreadProc(RTTHREAD hSelf, void *pvUser) +{ + RTSOCKETINT *pThis = (RTSOCKETINT *)pvUser; + RT_NOREF(hSelf); +# define __WSAFDIsSet g_pfn__WSAFDIsSet + + /* + * The execution loop. + */ + while (!ASMAtomicReadBool(&pThis->fPollFallbackShutdown)) + { + /* + * Do the selecting (with a 15 second timeout because that seems like a good idea). + */ + struct fd_set SetRead; + struct fd_set SetWrite; + struct fd_set SetXcpt; + + FD_ZERO(&SetRead); + FD_ZERO(&SetWrite); + FD_ZERO(&SetXcpt); + + FD_SET(pThis->hPollFallbackNotifyR, &SetRead); + FD_SET(pThis->hPollFallbackNotifyR, &SetXcpt); + + bool fActive = ASMAtomicReadBool(&pThis->fPollFallbackActive); + uint32_t fEvents; + if (!fActive) + fEvents = 0; + else + { + fEvents = ASMAtomicReadU32(&pThis->fSubscribedEvts); + if (fEvents & RTPOLL_EVT_READ) + FD_SET(pThis->hNative, &SetRead); + if (fEvents & RTPOLL_EVT_WRITE) + FD_SET(pThis->hNative, &SetWrite); + if (fEvents & RTPOLL_EVT_ERROR) + FD_SET(pThis->hNative, &SetXcpt); + } + + struct timeval Timeout; + Timeout.tv_sec = 15; + Timeout.tv_usec = 0; + int rc = g_pfnselect(INT_MAX /*ignored*/, &SetRead, &SetWrite, &SetXcpt, &Timeout); + + /* Stop immediately if told to shut down. */ + if (ASMAtomicReadBool(&pThis->fPollFallbackShutdown)) + break; + + /* + * Process the result. + */ + if (rc > 0) + { + /* First the socket we're listening on. */ + if ( fEvents + && ( FD_ISSET(pThis->hNative, &SetRead) + || FD_ISSET(pThis->hNative, &SetWrite) + || FD_ISSET(pThis->hNative, &SetXcpt)) ) + { + ASMAtomicWriteBool(&pThis->fPollFallbackActive, false); + SetEvent(pThis->hEvent); + } + + /* Then maintain the notification pipe. (We only read one byte here + because we're overly paranoid wrt socket switching to blocking mode.) */ + if (FD_ISSET(pThis->hPollFallbackNotifyR, &SetRead)) + { + char chIgnored; + g_pfnrecv(pThis->hPollFallbackNotifyR, &chIgnored, sizeof(chIgnored), MSG_NOSIGNAL); + } + } + else + AssertMsg(rc == 0, ("%Rrc\n", rtSocketError())); + } + +# undef __WSAFDIsSet + return VINF_SUCCESS; +} + + +/** + * Pokes the fallback thread, making sure it gets out of whatever it's stuck in. + * + * @param pThis The socket handle. + */ +static void rtSocketPokePollFallbackThread(RTSOCKETINT *pThis) +{ + Assert(pThis->fPollFallback); + if (pThis->hPollFallbackThread != NIL_RTTHREAD) + { + int cbWritten = g_pfnsend(pThis->hPollFallbackNotifyW, "!", 1, MSG_NOSIGNAL); + AssertMsg(cbWritten == 1, ("cbWritten=%d err=%Rrc\n", rtSocketError())); + RT_NOREF_PV(cbWritten); + } +} + + +/** + * Called by rtSocketPollStart to make the thread start selecting on the socket. + * + * @returns 0 on success, RTPOLL_EVT_ERROR on failure. + * @param pThis The socket handle. + */ +static uint32_t rtSocketPollFallbackStart(RTSOCKETINT *pThis) +{ + /* + * Reset the event and tell the thread to start selecting on the socket. + */ + ResetEvent(pThis->hEvent); + ASMAtomicWriteBool(&pThis->fPollFallbackActive, true); + + /* + * Wake up the thread the thread. + */ + if (pThis->hPollFallbackThread != NIL_RTTHREAD) + rtSocketPokePollFallbackThread(pThis); + else + { + /* + * Not running, need to set it up and start it. + */ + AssertLogRelReturn(pThis->hEvent != NULL && pThis->hEvent != INVALID_HANDLE_VALUE, RTPOLL_EVT_ERROR); + + /* Create the notification socket pair. */ + int rc; + if (pThis->hPollFallbackNotifyR == NIL_RTSOCKETNATIVE) + { + rc = rtSocketCreateNativeTcpPair(&pThis->hPollFallbackNotifyW, &pThis->hPollFallbackNotifyR); + AssertLogRelRCReturn(rc, RTPOLL_EVT_ERROR); + + /* Make the read end non-blocking (not fatal). */ + u_long fNonBlocking = 1; + rc = g_pfnioctlsocket(pThis->hPollFallbackNotifyR, FIONBIO, &fNonBlocking); + AssertLogRelMsg(rc == 0, ("rc=%#x %Rrc\n", rc, rtSocketError())); + } + + /* Finally, start the thread. ASSUME we don't need too much stack. */ + rc = RTThreadCreate(&pThis->hPollFallbackThread, rtSocketPollFallbackThreadProc, pThis, + _128K, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "sockpoll"); + AssertLogRelRCReturn(rc, RTPOLL_EVT_ERROR); + } + return 0; +} + + +/** + * Undos the harm done by WSAEventSelect. + * + * @returns IPRT status code. + * @param pThis The socket handle. + */ +static int rtSocketPollClearEventAndRestoreBlocking(RTSOCKETINT *pThis) +{ + int rc = VINF_SUCCESS; + if (pThis->fSubscribedEvts) + { + if (!pThis->fPollFallback) + { + Assert(g_pfnWSAEventSelect && g_pfnioctlsocket); + if (g_pfnWSAEventSelect && g_pfnioctlsocket) + { + if (g_pfnWSAEventSelect(pThis->hNative, WSA_INVALID_EVENT, 0) == 0) + { + pThis->fSubscribedEvts = 0; + + /* + * Switch back to blocking mode if that was the state before the + * operation. + */ + if (pThis->fBlocking) + { + u_long fNonBlocking = 0; + int rc2 = g_pfnioctlsocket(pThis->hNative, FIONBIO, &fNonBlocking); + if (rc2 != 0) + { + rc = rtSocketError(); + AssertMsgFailed(("%Rrc; rc2=%d\n", rc, rc2)); + } + } + } + else + { + rc = rtSocketError(); + AssertMsgFailed(("%Rrc\n", rc)); + } + } + else + { + Assert(pThis->fPollFallback); + rc = VINF_SUCCESS; + } + } + /* + * Just clear the event mask as we never started waiting if we get here. + */ + else + ASMAtomicWriteU32(&pThis->fSubscribedEvts, 0); + } + return rc; +} + + +/** + * Updates the mask of events we're subscribing to. + * + * @returns IPRT status code. + * @param pThis The socket handle. + * @param fEvents The events we want to subscribe to. + */ +static int rtSocketPollUpdateEvents(RTSOCKETINT *pThis, uint32_t fEvents) +{ + if (!pThis->fPollFallback) + { + LONG fNetworkEvents = 0; + if (fEvents & RTPOLL_EVT_READ) + fNetworkEvents |= FD_READ; + if (fEvents & RTPOLL_EVT_WRITE) + fNetworkEvents |= FD_WRITE; + if (fEvents & RTPOLL_EVT_ERROR) + fNetworkEvents |= FD_CLOSE; + LogFlowFunc(("fNetworkEvents=%#x\n", fNetworkEvents)); + + if (g_pfnWSAEventSelect(pThis->hNative, pThis->hEvent, fNetworkEvents) == 0) + { + pThis->fSubscribedEvts = fEvents; + return VINF_SUCCESS; + } + + int rc = rtSocketError(); + AssertMsgFailed(("fNetworkEvents=%#x rc=%Rrc\n", fNetworkEvents, rtSocketError())); + return rc; + } + + /* + * Update the events we're waiting for. Caller will poke/start the thread. later + */ + ASMAtomicWriteU32(&pThis->fSubscribedEvts, fEvents); + return VINF_SUCCESS; +} + +#endif /* RT_OS_WINDOWS */ + + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + +/** + * Checks for pending events. + * + * @returns Event mask or 0. + * @param pThis The socket handle. + * @param fEvents The desired events. + */ +static uint32_t rtSocketPollCheck(RTSOCKETINT *pThis, uint32_t fEvents) +{ + uint32_t fRetEvents = 0; + + LogFlowFunc(("pThis=%#p fEvents=%#x\n", pThis, fEvents)); + +# ifdef RT_OS_WINDOWS + /* Make sure WSAEnumNetworkEvents returns what we want. */ + int rc = VINF_SUCCESS; + if ((pThis->fSubscribedEvts & fEvents) != fEvents) + rc = rtSocketPollUpdateEvents(pThis, pThis->fSubscribedEvts | fEvents); + + if (!pThis->fPollFallback) + { + /* Atomically get pending events and reset the event semaphore. */ + Assert(g_pfnWSAEnumNetworkEvents); + WSANETWORKEVENTS NetEvts; + RT_ZERO(NetEvts); + if (g_pfnWSAEnumNetworkEvents(pThis->hNative, pThis->hEvent, &NetEvts) == 0) + { + if ( (NetEvts.lNetworkEvents & FD_READ) + && NetEvts.iErrorCode[FD_READ_BIT] == 0) + fRetEvents |= RTPOLL_EVT_READ; + + if ( (NetEvts.lNetworkEvents & FD_WRITE) + && NetEvts.iErrorCode[FD_WRITE_BIT] == 0) + fRetEvents |= RTPOLL_EVT_WRITE; + + if (NetEvts.lNetworkEvents & FD_CLOSE) + fRetEvents |= RTPOLL_EVT_ERROR; + else + for (uint32_t i = 0; i < FD_MAX_EVENTS; i++) + if ( (NetEvts.lNetworkEvents & (1L << i)) + && NetEvts.iErrorCode[i] != 0) + fRetEvents |= RTPOLL_EVT_ERROR; + + pThis->fEventsSaved = fRetEvents |= pThis->fEventsSaved; + fRetEvents &= fEvents | RTPOLL_EVT_ERROR; + } + else + rc = rtSocketError(); + } + + /* Fall back on select if we hit an error above or is using fallback polling. */ + if (pThis->fPollFallback || RT_FAILURE(rc)) + { + rc = rtSocketSelectOneEx(pThis, fEvents & RTPOLL_EVT_ERROR ? fEvents | RTPOLL_EVT_READ : fEvents, &fRetEvents, 0); + if (RT_SUCCESS(rc)) + { + /* rtSocketSelectOneEx may return RTPOLL_EVT_READ on disconnect. Use + getpeername to fix this. */ + if ((fRetEvents & (RTPOLL_EVT_READ | RTPOLL_EVT_ERROR)) == RTPOLL_EVT_READ) + { +# if 0 /* doens't work */ + rtSocketErrorReset(); + char chIgn; + rc = g_pfnrecv(pThis->hNative, &chIgn, 0, MSG_NOSIGNAL); + rc = rtSocketError(); + if (RT_FAILURE(rc)) + fRetEvents |= RTPOLL_EVT_ERROR; + + rc = g_pfnsend(pThis->hNative, &chIgn, 0, MSG_NOSIGNAL); + rc = rtSocketError(); + if (RT_FAILURE(rc)) + fRetEvents |= RTPOLL_EVT_ERROR; + + RTSOCKADDRUNION u; + int cbAddr = sizeof(u); + if (g_pfngetpeername(pThis->hNative, &u.Addr, &cbAddr) == SOCKET_ERROR) + fRetEvents |= RTPOLL_EVT_ERROR; +# endif + /* If no bytes are available, assume error condition. */ + u_long cbAvail = 0; + rc = g_pfnioctlsocket(pThis->hNative, FIONREAD, &cbAvail); + if (rc == 0 && cbAvail == 0) + fRetEvents |= RTPOLL_EVT_ERROR; + } + fRetEvents &= fEvents | RTPOLL_EVT_ERROR; + } + else if (rc == VERR_TIMEOUT) + fRetEvents = 0; + else + fRetEvents |= RTPOLL_EVT_ERROR; + } + +# else /* RT_OS_OS2 */ + int aFds[4] = { pThis->hNative, pThis->hNative, pThis->hNative, -1 }; + int rc = os2_select(aFds, 1, 1, 1, 0); + if (rc > 0) + { + if (aFds[0] == pThis->hNative) + fRetEvents |= RTPOLL_EVT_READ; + if (aFds[1] == pThis->hNative) + fRetEvents |= RTPOLL_EVT_WRITE; + if (aFds[2] == pThis->hNative) + fRetEvents |= RTPOLL_EVT_ERROR; + fRetEvents &= fEvents; + } +# endif /* RT_OS_OS2 */ + + LogFlowFunc(("fRetEvents=%#x\n", fRetEvents)); + return fRetEvents; +} + + +/** + * Internal RTPoll helper that polls the socket handle and, if @a fNoWait is + * clear, starts whatever actions we've got running during the poll call. + * + * @returns 0 if no pending events, actions initiated if @a fNoWait is clear. + * Event mask (in @a fEvents) and no actions if the handle is ready + * already. + * UINT32_MAX (asserted) if the socket handle is busy in I/O or a + * different poll set. + * + * @param hSocket The socket handle. + * @param hPollSet The poll set handle (for access checks). + * @param fEvents The events we're polling for. + * @param fFinalEntry Set if this is the final entry for this handle + * in this poll set. This can be used for dealing + * with duplicate entries. + * @param fNoWait Set if it's a zero-wait poll call. Clear if + * we'll wait for an event to occur. + * + * @remarks There is a potential race wrt duplicate handles when @a fNoWait is + * @c true, we don't currently care about that oddity... + */ +DECLHIDDEN(uint32_t) rtSocketPollStart(RTSOCKET hSocket, RTPOLLSET hPollSet, uint32_t fEvents, bool fFinalEntry, bool fNoWait) +{ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, UINT32_MAX); + /** @todo This isn't quite sane. Replace by critsect and open up concurrent + * reads and writes! */ + if (rtSocketTryLock(pThis)) + pThis->hPollSet = hPollSet; + else + { + AssertReturn(pThis->hPollSet == hPollSet, UINT32_MAX); + ASMAtomicIncU32(&pThis->cUsers); + } + + /* (rtSocketPollCheck will reset the event object). */ +# ifdef RT_OS_WINDOWS + uint32_t fRetEvents = pThis->fEventsSaved; + pThis->fEventsSaved = 0; /* Reset */ + fRetEvents |= rtSocketPollCheck(pThis, fEvents); + + if ( !fRetEvents + && !fNoWait) + { + pThis->fPollEvts |= fEvents; + if (fFinalEntry) + { + if (pThis->fSubscribedEvts != pThis->fPollEvts) + { + /** @todo seems like there might be a call to many here and that fPollEvts is + * totally unnecessary... (bird) */ + int rc = rtSocketPollUpdateEvents(pThis, pThis->fPollEvts); + if (RT_FAILURE(rc)) + { + pThis->fPollEvts = 0; + fRetEvents = UINT32_MAX; + } + } + + /* Make sure we don't block when there are events pending relevant to an earlier poll set entry. */ + if (pThis->fEventsSaved && !pThis->fPollFallback && g_pfnWSASetEvent && fRetEvents == 0) + g_pfnWSASetEvent(pThis->hEvent); + } + } +# else + uint32_t fRetEvents = rtSocketPollCheck(pThis, fEvents); +# endif + + if (fRetEvents || fNoWait) + { + if (pThis->cUsers == 1) + { +# ifdef RT_OS_WINDOWS + pThis->fEventsSaved &= RTPOLL_EVT_ERROR; + pThis->fHarvestedEvents = false; + rtSocketPollClearEventAndRestoreBlocking(pThis); +# endif + pThis->hPollSet = NIL_RTPOLLSET; + } +# ifdef RT_OS_WINDOWS + else + pThis->fHarvestedEvents = true; +# endif + ASMAtomicDecU32(&pThis->cUsers); + } +# ifdef RT_OS_WINDOWS + /* + * Kick the poller thread on if this is the final entry and we're in + * winsock 1.x fallback mode. + */ + else if (pThis->fPollFallback && fFinalEntry) + fRetEvents = rtSocketPollFallbackStart(pThis); +# endif + + return fRetEvents; +} + + +/** + * Called after a WaitForMultipleObjects returned in order to check for pending + * events and stop whatever actions that rtSocketPollStart() initiated. + * + * @returns Event mask or 0. + * + * @param hSocket The socket handle. + * @param fEvents The events we're polling for. + * @param fFinalEntry Set if this is the final entry for this handle + * in this poll set. This can be used for dealing + * with duplicate entries. Only keep in mind that + * this method is called in reverse order, so the + * first call will have this set (when the entire + * set was processed). + * @param fHarvestEvents Set if we should check for pending events. + */ +DECLHIDDEN(uint32_t) rtSocketPollDone(RTSOCKET hSocket, uint32_t fEvents, bool fFinalEntry, bool fHarvestEvents) +{ + RTSOCKETINT *pThis = hSocket; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTSOCKET_MAGIC, 0); + Assert(pThis->cUsers > 0); + Assert(pThis->hPollSet != NIL_RTPOLLSET); + RT_NOREF_PV(fFinalEntry); + +# ifdef RT_OS_WINDOWS + /* + * Deactivate the poll thread if we're in winsock 1.x fallback poll mode. + */ + if ( pThis->fPollFallback + && pThis->hPollFallbackThread != NIL_RTTHREAD) + { + ASMAtomicWriteU32(&pThis->fSubscribedEvts, 0); + if (ASMAtomicXchgBool(&pThis->fPollFallbackActive, false)) + rtSocketPokePollFallbackThread(pThis); + } +# endif + + /* + * Harvest events and clear the event mask for the next round of polling. + */ + uint32_t fRetEvents; +# ifdef RT_OS_WINDOWS + if (!pThis->fPollFallback) + { + if (!pThis->fHarvestedEvents) + { + fRetEvents = rtSocketPollCheck(pThis, fEvents); + pThis->fHarvestedEvents = true; + } + else + fRetEvents = pThis->fEventsSaved; + if (fHarvestEvents) + fRetEvents &= fEvents; + else + fRetEvents = 0; + pThis->fPollEvts = 0; + } + else +# endif + { + if (fHarvestEvents) + fRetEvents = rtSocketPollCheck(pThis, fEvents); + else + fRetEvents = 0; + } + + /* + * Make the socket blocking again and unlock the handle. + */ + if (pThis->cUsers == 1) + { +# ifdef RT_OS_WINDOWS + pThis->fEventsSaved &= RTPOLL_EVT_ERROR; + pThis->fHarvestedEvents = false; + rtSocketPollClearEventAndRestoreBlocking(pThis); +# endif + pThis->hPollSet = NIL_RTPOLLSET; + } + ASMAtomicDecU32(&pThis->cUsers); + return fRetEvents; +} + +#endif /* RT_OS_WINDOWS || RT_OS_OS2 */ + diff --git a/src/VBox/Runtime/r3/solaris/Makefile.kup b/src/VBox/Runtime/r3/solaris/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/Makefile.kup diff --git a/src/VBox/Runtime/r3/solaris/RTFileQuerySectorSize-solaris.cpp b/src/VBox/Runtime/r3/solaris/RTFileQuerySectorSize-solaris.cpp new file mode 100644 index 00000000..4620b71c --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/RTFileQuerySectorSize-solaris.cpp @@ -0,0 +1,90 @@ +/* $Id: RTFileQuerySectorSize-solaris.cpp $ */ +/** @file + * IPRT - RTFileQuerySectorSize, Solaris. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#include <errno.h> +#include <stropts.h> +#include <sys/types.h> +#include <sys/dkio.h> +#include <sys/vtoc.h> +#include <sys/stat.h> + + + +RTDECL(int) RTFileQuerySectorSize(RTFILE hFile, uint32_t *pcbSector) +{ + AssertPtrReturn(pcbSector, VERR_INVALID_PARAMETER); + + int rc; + int const fd = (int)RTFileToNative(hFile); + struct stat DevStat = { 0 }; + if (!fstat(fd, &DevStat)) + { + if (S_ISBLK(DevStat.st_mode) || S_ISCHR(DevStat.st_mode)) + { + struct dk_minfo MediaInfo; + if (!ioctl(fd, DKIOCGMEDIAINFO, &MediaInfo)) + { + AssertReturn(MediaInfo.dki_lbsize > 0, VERR_INVALID_FUNCTION); + *pcbSector = MediaInfo.dki_lbsize; + return VINF_SUCCESS; + } + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("ioctl failed: errno=%d / %Rrc\n", errno, rc)); + } + else + { + AssertMsgFailed(("not a block or character device.\n")); + rc = VERR_INVALID_FUNCTION; + } + } + else + { + rc = RTErrConvertFromErrno(errno); + AssertMsgFailed(("fstat failed: errno=%d / %Rrc\n", errno, rc)); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/solaris/RTSystemFirmware-solaris.cpp b/src/VBox/Runtime/r3/solaris/RTSystemFirmware-solaris.cpp new file mode 100644 index 00000000..d986e9f2 --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/RTSystemFirmware-solaris.cpp @@ -0,0 +1,78 @@ +/* $Id: RTSystemFirmware-solaris.cpp $ */ +/** @file + * IPRT - System firmware information for Solaris. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/system.h> +#include <iprt/errcore.h> + +#include <libdevinfo.h> + + +RTDECL(int) RTSystemQueryFirmwareType(PRTSYSFWTYPE penmFirmwareType) +{ + di_node_t di_node; + di_node = di_init("/", DINFOSUBTREE|DINFOPROP); + if (di_node == DI_NODE_NIL) + { + *penmFirmwareType = RTSYSFWTYPE_INVALID; + return VERR_NOT_SUPPORTED; + } + + int64_t *efiprop; + int rc = di_prop_lookup_int64(DDI_DEV_T_ANY, di_node, "efi-systab", &efiprop); + if (rc == -1) + *penmFirmwareType = RTSYSFWTYPE_BIOS; + else + *penmFirmwareType = RTSYSFWTYPE_UEFI; + + di_fini(di_node); + + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTSystemQueryFirmwareType); + + +RTDECL(int) RTSystemQueryFirmwareBoolean(RTSYSFWBOOL enmBoolean, bool *pfValue) +{ + RT_NOREF(enmBoolean, pfValue); + return VERR_NOT_SUPPORTED; +} +RT_EXPORT_SYMBOL(RTSystemQueryFirmwareBoolean); + diff --git a/src/VBox/Runtime/r3/solaris/RTSystemQueryDmiString-solaris.cpp b/src/VBox/Runtime/r3/solaris/RTSystemQueryDmiString-solaris.cpp new file mode 100644 index 00000000..8e62a7a0 --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/RTSystemQueryDmiString-solaris.cpp @@ -0,0 +1,117 @@ +/* $Id: RTSystemQueryDmiString-solaris.cpp $ */ +/** @file + * IPRT - RTSystemQueryDmiString, solaris ring-3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> + +#include <smbios.h> +#include <errno.h> + + +RTDECL(int) RTSystemQueryDmiString(RTSYSDMISTR enmString, char *pszBuf, size_t cbBuf) +{ + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER); + *pszBuf = '\0'; + AssertReturn(enmString > RTSYSDMISTR_INVALID && enmString < RTSYSDMISTR_END, VERR_INVALID_PARAMETER); + + int rc = VERR_NOT_SUPPORTED; + int err = 0; + smbios_hdl_t *pSMB = smbios_open(NULL /* default fd */, SMB_VERSION, 0 /* flags */, &err); + if (pSMB) + { + smbios_system_t hSMBSys; + id_t hSMBId = smbios_info_system(pSMB, &hSMBSys); + if (hSMBId != SMB_ERR) + { + /* Don't need the common bits for the product UUID. */ + if (enmString == RTSYSDMISTR_PRODUCT_UUID) + { + static char const s_szHex[17] = "0123456789ABCDEF"; + char szData[64]; + char *pszData = szData; + unsigned cchUuid = RT_MIN(hSMBSys.smbs_uuidlen, sizeof(szData) - 1); + for (unsigned i = 0; i < cchUuid; i++) + { + *pszData++ = s_szHex[hSMBSys.smbs_uuid[i] >> 4]; + *pszData++ = s_szHex[hSMBSys.smbs_uuid[i] & 0xf]; + if (i == 3 || i == 5 || i == 7 || i == 9) + *pszData++ = '-'; + } + *pszData = '\0'; + rc = RTStrCopy(pszBuf, cbBuf, szData); + smbios_close(pSMB); + return rc; + } + + smbios_info_t hSMBInfo; + id_t hSMBInfoId = smbios_info_common(pSMB, hSMBId, &hSMBInfo); + if (hSMBInfoId != SMB_ERR) + { + switch (enmString) + { + case RTSYSDMISTR_PRODUCT_NAME: rc = RTStrCopy(pszBuf, cbBuf, hSMBInfo.smbi_product); break; + case RTSYSDMISTR_PRODUCT_VERSION: rc = RTStrCopy(pszBuf, cbBuf, hSMBInfo.smbi_version); break; + case RTSYSDMISTR_PRODUCT_SERIAL: rc = RTStrCopy(pszBuf, cbBuf, hSMBInfo.smbi_serial); break; + case RTSYSDMISTR_MANUFACTURER: rc = RTStrCopy(pszBuf, cbBuf, hSMBInfo.smbi_manufacturer); break; + + default: /* make gcc happy */ + rc = VERR_NOT_SUPPORTED; + } + smbios_close(pSMB); + return rc; + } + } + + /* smbios_* error path. */ + err = smbios_errno(pSMB); + smbios_close(pSMB); + } + + /* Do some error conversion. */ + if (err == EPERM || err == EACCES) + rc = VERR_ACCESS_DENIED; + return rc; +} + diff --git a/src/VBox/Runtime/r3/solaris/RTSystemShutdown-solaris.cpp b/src/VBox/Runtime/r3/solaris/RTSystemShutdown-solaris.cpp new file mode 100644 index 00000000..163b608f --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/RTSystemShutdown-solaris.cpp @@ -0,0 +1,112 @@ +/* $Id: RTSystemShutdown-solaris.cpp $ */ +/** @file + * IPRT - RTSystemShutdown, linux implementation. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/process.h> +#include <iprt/string.h> + + +RTDECL(int) RTSystemShutdown(RTMSINTERVAL cMsDelay, uint32_t fFlags, const char *pszLogMsg) +{ + AssertPtrReturn(pszLogMsg, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTSYSTEM_SHUTDOWN_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Assemble the argument vector. + */ + int iArg = 0; + const char *apszArgs[8]; + + RT_BZERO(apszArgs, sizeof(apszArgs)); + + apszArgs[iArg++] = "/usr/sbin/shutdown"; + apszArgs[iArg++] = "-y"; /* Pre-answer confirmation question. */ + apszArgs[iArg++] = "-i"; /* Change to the following state. */ + switch (fFlags & RTSYSTEM_SHUTDOWN_ACTION_MASK) + { + case RTSYSTEM_SHUTDOWN_HALT: + apszArgs[iArg++] = "0"; + break; + case RTSYSTEM_SHUTDOWN_REBOOT: + apszArgs[iArg++] = "6"; + break; + case RTSYSTEM_SHUTDOWN_POWER_OFF: + case RTSYSTEM_SHUTDOWN_POWER_OFF_HALT: + apszArgs[iArg++] = "5"; + break; + } + + apszArgs[iArg++] = "-g"; /* Grace period. */ + + char szWhen[80]; + if (cMsDelay < 500) + strcpy(szWhen, "0"); + else + RTStrPrintf(szWhen, sizeof(szWhen), "%u", (unsigned)((cMsDelay + 499) / 1000)); + apszArgs[iArg++] = szWhen; + + apszArgs[iArg++] = pszLogMsg; + + + /* + * Start the shutdown process and wait for it to complete. + */ + RTPROCESS hProc; + int rc = RTProcCreate(apszArgs[0], apszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &hProc); + if (RT_FAILURE(rc)) + return rc; + + RTPROCSTATUS ProcStatus; + rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus); + if (RT_SUCCESS(rc)) + { + if ( ProcStatus.enmReason != RTPROCEXITREASON_NORMAL + || ProcStatus.iStatus != 0) + rc = VERR_SYS_SHUTDOWN_FAILED; + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/solaris/coredumper-solaris.cpp b/src/VBox/Runtime/r3/solaris/coredumper-solaris.cpp new file mode 100644 index 00000000..d1a24b2c --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/coredumper-solaris.cpp @@ -0,0 +1,2375 @@ +/* $Id: coredumper-solaris.cpp $ */ +/** @file + * IPRT - Custom Core Dumper, Solaris. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/coredumper.h> + +#include <iprt/asm.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include "coredumper-solaris.h" + +#ifdef RT_OS_SOLARIS +# include <syslog.h> +# include <signal.h> +# include <stdlib.h> +# include <unistd.h> +# include <errno.h> +# include <zone.h> +# include <sys/proc.h> +# include <sys/sysmacros.h> +# include <sys/systeminfo.h> +# include <sys/mman.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <fcntl.h> +# include <ucontext.h> +#endif /* RT_OS_SOLARIS */ + +#include <iprt/formats/elf.h> +#include <iprt/formats/elf64.h> + + +/********************************************************************************************************************************* +* Globals * +*********************************************************************************************************************************/ +static RTNATIVETHREAD volatile g_CoreDumpThread = NIL_RTNATIVETHREAD; +static bool volatile g_fCoreDumpSignalSetup = false; +static uint32_t volatile g_fCoreDumpFlags = 0; +static char g_szCoreDumpDir[PATH_MAX] = { 0 }; +static char g_szCoreDumpFile[PATH_MAX] = { 0 }; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define CORELOG_NAME "CoreDumper: " +#define CORELOG(a) Log(a) +#define CORELOGRELSYS(a) \ + do { \ + rtCoreDumperSysLogWrapper a; \ + } while (0) + + +/** + * ELFNOTEHDR: ELF NOTE header. + */ +typedef struct ELFNOTEHDR +{ + Elf64_Nhdr Hdr; /* Header of NOTE section */ + char achName[8]; /* Name of NOTE section */ +} ELFNOTEHDR; +typedef ELFNOTEHDR *PELFNOTEHDR; + +/** + * Wrapper function to write IPRT format style string to the syslog. + * + * @param pszFormat Format string + */ +static void rtCoreDumperSysLogWrapper(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + char szBuf[1024]; + RTStrPrintfV(szBuf, sizeof(szBuf), pszFormat, va); + va_end(va); + syslog(LOG_ERR, "%s", szBuf); +} + + +/** + * Determines endianness of the system. Just for completeness. + * + * @return Will return false if system is little endian, true otherwise. + */ +static bool IsBigEndian() +{ + const int i = 1; + char *p = (char *)&i; + if (p[0] == 1) + return false; + return true; +} + + +/** + * Reads from a file making sure an interruption doesn't cause a failure. + * + * @param fd Handle to the file to read. + * @param pv Where to store the read data. + * @param cbToRead Size of data to read. + * + * @return IPRT status code. + */ +static int ReadFileNoIntr(int fd, void *pv, size_t cbToRead) +{ + for (;;) + { + ssize_t cbRead = read(fd, pv, cbToRead); + if (cbRead < 0) + { + if (errno == EINTR) + continue; + return RTErrConvertFromErrno(errno); + } + if ((size_t)cbRead == cbToRead) + return VINF_SUCCESS; + if ((size_t)cbRead > cbToRead) + return VERR_INTERNAL_ERROR_3; + if (cbRead == 0) + return VERR_EOF; + pv = (uint8_t *)pv + cbRead; + cbToRead -= cbRead; + } +} + + +/** + * Writes to a file making sure an interruption doesn't cause a failure. + * + * @param fd Handle to the file to write to. + * @param pv Pointer to what to write. + * @param cbToWrite Size of data to write. + * + * @return IPRT status code. + */ +static int WriteFileNoIntr(int fd, const void *pv, size_t cbToWrite) +{ + for (;;) + { + ssize_t cbWritten = write(fd, pv, cbToWrite); + if (cbWritten < 0) + { + if (errno == EINTR) + continue; + return RTErrConvertFromErrno(errno); + } + if ((size_t)cbWritten == cbToWrite) + return VINF_SUCCESS; + if ((size_t)cbWritten > cbToWrite) + return VERR_INTERNAL_ERROR_2; + pv = (uint8_t const *)pv + cbWritten; + cbToWrite -= cbWritten; + } +} + + +/** + * Read from a given offset in the process' address space. + * + * @param pSolProc Pointer to the solaris process. + * @param off The offset to read from. + * @param pvBuf Where to read the data into. + * @param cbToRead Number of bytes to read. + * + * @return VINF_SUCCESS, if all the given bytes was read in, otherwise VERR_READ_ERROR. + */ +static ssize_t ProcReadAddrSpace(PRTSOLCOREPROCESS pSolProc, RTFOFF off, void *pvBuf, size_t cbToRead) +{ + for (;;) + { + ssize_t cbRead = pread(pSolProc->fdAs, pvBuf, cbToRead, off); + if (cbRead < 0) + { + if (errno == EINTR) + continue; + return RTErrConvertFromErrno(errno); + } + if ((size_t)cbRead == cbToRead) + return VINF_SUCCESS; + if ((size_t)cbRead > cbToRead) + return VERR_INTERNAL_ERROR_4; + if (cbRead == 0) + return VERR_EOF; + + pvBuf = (uint8_t *)pvBuf + cbRead; + cbToRead -= cbRead; + off += cbRead; + } +} + + +/** + * Determines if the current process' architecture is suitable for dumping core. + * + * @param pSolProc Pointer to the solaris process. + * + * @return true if the architecture matches the current one. + */ +static inline bool IsProcessArchNative(PRTSOLCOREPROCESS pSolProc) +{ + psinfo_t *pProcInfo = (psinfo_t *)pSolProc->pvProcInfo; + return pProcInfo->pr_dmodel == PR_MODEL_NATIVE; +} + + +/** + * Helper function to get the size_t compatible file size from a file + * descriptor. + * + * @return The file size (in bytes). + * @param fd The file descriptor. + */ +static size_t GetFileSizeByFd(int fd) +{ + struct stat st; + if (fstat(fd, &st) == 0) + { + if (st.st_size <= 0) + return 0; + size_t cbFile = (size_t)st.st_size; + return (off_t)cbFile == st.st_size ? cbFile : ~(size_t)0; + } + + CORELOGRELSYS((CORELOG_NAME "GetFileSizeByFd: fstat failed rc=%Rrc\n", RTErrConvertFromErrno(errno))); + return 0; +} + + +/** + * Helper function to get the size_t compatible size of a file given its path. + * + * @return The file size (in bytes). + * @param pszPath Pointer to the full path of the file. + */ +static size_t GetFileSizeByName(const char *pszPath) +{ + int fd = open(pszPath, O_RDONLY); + if (fd < 0) + { + CORELOGRELSYS((CORELOG_NAME "GetFileSizeByName: failed to open %s rc=%Rrc\n", pszPath, RTErrConvertFromErrno(errno))); + return 0; + } + + size_t cb = GetFileSizeByFd(fd); + close(fd); + return cb; +} + + +/** + * Pre-compute and pre-allocate sufficient memory for dumping core. + * This is meant to be called once, as a single-large anonymously + * mapped memory area which will be used during the core dumping routines. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int AllocMemoryArea(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore->pvCore == NULL, VERR_ALREADY_EXISTS); + + static struct + { + const char *pszFilePath; /* Proc based path */ + size_t cbHeader; /* Size of header */ + size_t cbEntry; /* Size of each entry in file */ + size_t cbAccounting; /* Size of each accounting entry per entry */ + } const s_aPreAllocTable[] = + { + { "/proc/%d/psinfo", 0, 0, 0 }, + { "/proc/%d/map", 0, sizeof(prmap_t), sizeof(RTSOLCOREMAPINFO) }, + { "/proc/%d/auxv", 0, 0, 0 }, + { "/proc/%d/lpsinfo", sizeof(prheader_t), sizeof(lwpsinfo_t), sizeof(RTSOLCORETHREADINFO) }, + { "/proc/%d/lstatus", 0, 0, 0 }, + { "/proc/%d/ldt", 0, 0, 0 }, + { "/proc/%d/cred", sizeof(prcred_t), sizeof(gid_t), 0 }, + { "/proc/%d/priv", sizeof(prpriv_t), sizeof(priv_chunk_t), 0 }, + }; + + size_t cb = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_aPreAllocTable); i++) + { + char szPath[PATH_MAX]; + RTStrPrintf(szPath, sizeof(szPath), s_aPreAllocTable[i].pszFilePath, (int)pSolCore->SolProc.Process); + size_t cbFile = GetFileSizeByName(szPath); + cb += cbFile; + if ( cbFile > 0 + && s_aPreAllocTable[i].cbEntry > 0) + { + cb += ((cbFile - s_aPreAllocTable[i].cbHeader) / s_aPreAllocTable[i].cbEntry) + * (s_aPreAllocTable[i].cbAccounting > 0 ? s_aPreAllocTable[i].cbAccounting : 1); + cb += s_aPreAllocTable[i].cbHeader; + } + } + + /* + * Make room for our own mapping accountant entry which will also be included in the core. + */ + cb += sizeof(RTSOLCOREMAPINFO); + + /* + * Allocate the required space, plus some extra room. + */ + cb += _128K; + void *pv = mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1 /* fd */, 0 /* offset */); + if (pv != MAP_FAILED) + { + CORELOG((CORELOG_NAME "AllocMemoryArea: memory area of %u bytes allocated.\n", cb)); + pSolCore->pvCore = pv; + pSolCore->pvFree = pv; + pSolCore->cbCore = cb; + return VINF_SUCCESS; + } + CORELOGRELSYS((CORELOG_NAME "AllocMemoryArea: failed cb=%u\n", cb)); + return VERR_NO_MEMORY; +} + + +/** + * Free memory area used by the core object. + * + * @param pSolCore Pointer to the core object. + */ +static void FreeMemoryArea(PRTSOLCORE pSolCore) +{ + AssertReturnVoid(pSolCore); + AssertReturnVoid(pSolCore->pvCore); + AssertReturnVoid(pSolCore->cbCore > 0); + + munmap(pSolCore->pvCore, pSolCore->cbCore); + CORELOG((CORELOG_NAME "FreeMemoryArea: memory area of %u bytes freed.\n", pSolCore->cbCore)); + + pSolCore->pvCore = NULL; + pSolCore->pvFree= NULL; + pSolCore->cbCore = 0; +} + + +/** + * Get a chunk from the area of allocated memory. + * + * @param pSolCore Pointer to the core object. + * @param cb Size of requested chunk. + * + * @return Pointer to allocated memory, or NULL on failure. + */ +static void *GetMemoryChunk(PRTSOLCORE pSolCore, size_t cb) +{ + AssertReturn(pSolCore, NULL); + AssertReturn(pSolCore->pvCore, NULL); + AssertReturn(pSolCore->pvFree, NULL); + + size_t cbAllocated = (char *)pSolCore->pvFree - (char *)pSolCore->pvCore; + if (cbAllocated < pSolCore->cbCore) + { + char *pb = (char *)pSolCore->pvFree; + pSolCore->pvFree = pb + cb; + return pb; + } + + return NULL; +} + + +/** + * Reads the proc file's content into a newly allocated buffer. + * + * @param pSolCore Pointer to the core object. + * @param pszProcFileName Only the name of the file to read from + * (/proc/\<pid\> will be prepended) + * @param ppv Where to store the allocated buffer. + * @param pcb Where to store size of the buffer. + * + * @return IPRT status code. If the proc file is 0 bytes, VINF_SUCCESS is + * returned with pointed to values of @c ppv, @c pcb set to NULL and 0 + * respectively. + */ +static int ProcReadFileInto(PRTSOLCORE pSolCore, const char *pszProcFileName, void **ppv, size_t *pcb) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + char szPath[PATH_MAX]; + RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/%s", (int)pSolCore->SolProc.Process, pszProcFileName); + int rc = VINF_SUCCESS; + int fd = open(szPath, O_RDONLY); + if (fd >= 0) + { + *pcb = GetFileSizeByFd(fd); + if (*pcb > 0) + { + *ppv = GetMemoryChunk(pSolCore, *pcb); + if (*ppv) + rc = ReadFileNoIntr(fd, *ppv, *pcb); + else + rc = VERR_NO_MEMORY; + } + else + { + *pcb = 0; + *ppv = NULL; + rc = VINF_SUCCESS; + } + close(fd); + } + else + { + rc = RTErrConvertFromErrno(fd); + CORELOGRELSYS((CORELOG_NAME "ProcReadFileInto: failed to open %s. rc=%Rrc\n", szPath, rc)); + } + return rc; +} + + +/** + * Read process information (format psinfo_t) from /proc. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int ProcReadInfo(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + return ProcReadFileInto(pSolCore, "psinfo", &pSolProc->pvProcInfo, &pSolProc->cbProcInfo); +} + + +/** + * Read process status (format pstatus_t) from /proc. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int ProcReadStatus(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + + char szPath[PATH_MAX]; + int rc = VINF_SUCCESS; + + RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/status", (int)pSolProc->Process); + int fd = open(szPath, O_RDONLY); + if (fd >= 0) + { + size_t cbProcStatus = sizeof(pstatus_t); + AssertCompile(sizeof(pstatus_t) == sizeof(pSolProc->ProcStatus)); + rc = ReadFileNoIntr(fd, &pSolProc->ProcStatus, cbProcStatus); + close(fd); + } + else + { + rc = RTErrConvertFromErrno(fd); + CORELOGRELSYS((CORELOG_NAME "ProcReadStatus: failed to open %s. rc=%Rrc\n", szPath, rc)); + } + return rc; +} + + +/** + * Read process credential information (format prcred_t + array of guid_t) + * + * @return IPRT status code. + * @param pSolCore Pointer to the core object. + * + * @remarks Should not be called before successful call to @see AllocMemoryArea() + */ +static int ProcReadCred(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + return ProcReadFileInto(pSolCore, "cred", &pSolProc->pvCred, &pSolProc->cbCred); +} + + +/** + * Read process privilege information (format prpriv_t + array of priv_chunk_t) + * + * @return IPRT status code. + * @param pSolCore Pointer to the core object. + * + * @remarks Should not be called before successful call to @see AllocMemoryArea() + */ +static int ProcReadPriv(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + int rc = ProcReadFileInto(pSolCore, "priv", (void **)&pSolProc->pPriv, &pSolProc->cbPriv); + if (RT_FAILURE(rc)) + return rc; + pSolProc->pcPrivImpl = getprivimplinfo(); + if (!pSolProc->pcPrivImpl) + { + CORELOGRELSYS((CORELOG_NAME "ProcReadPriv: getprivimplinfo returned NULL.\n")); + return VERR_INVALID_STATE; + } + return rc; +} + + +/** + * Read process LDT information (format array of struct ssd) from /proc. + * + * @return IPRT status code. + * @param pSolCore Pointer to the core object. + * + * @remarks Should not be called before successful call to @see AllocMemoryArea() + */ +static int ProcReadLdt(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + return ProcReadFileInto(pSolCore, "ldt", &pSolProc->pvLdt, &pSolProc->cbLdt); +} + + +/** + * Read process auxiliary vectors (format auxv_t) for the process. + * + * @return IPRT status code. + * @param pSolCore Pointer to the core object. + * + * @remarks Should not be called before successful call to @see AllocMemoryArea() + */ +static int ProcReadAuxVecs(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + char szPath[PATH_MAX]; + int rc = VINF_SUCCESS; + RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/auxv", (int)pSolProc->Process); + int fd = open(szPath, O_RDONLY); + if (fd < 0) + { + rc = RTErrConvertFromErrno(fd); + CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: failed to open %s rc=%Rrc\n", szPath, rc)); + return rc; + } + + size_t cbAuxFile = GetFileSizeByFd(fd); + if (cbAuxFile >= sizeof(auxv_t)) + { + pSolProc->pAuxVecs = (auxv_t*)GetMemoryChunk(pSolCore, cbAuxFile + sizeof(auxv_t)); + if (pSolProc->pAuxVecs) + { + rc = ReadFileNoIntr(fd, pSolProc->pAuxVecs, cbAuxFile); + if (RT_SUCCESS(rc)) + { + /* Terminate list of vectors */ + pSolProc->cAuxVecs = cbAuxFile / sizeof(auxv_t); + CORELOG((CORELOG_NAME "ProcReadAuxVecs: cbAuxFile=%u auxv_t size %d cAuxVecs=%u\n", cbAuxFile, sizeof(auxv_t), + pSolProc->cAuxVecs)); + if (pSolProc->cAuxVecs > 0) + { + pSolProc->pAuxVecs[pSolProc->cAuxVecs].a_type = AT_NULL; + pSolProc->pAuxVecs[pSolProc->cAuxVecs].a_un.a_val = 0L; + close(fd); + return VINF_SUCCESS; + } + + CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: Invalid vector count %u\n", pSolProc->cAuxVecs)); + rc = VERR_READ_ERROR; + } + else + CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: ReadFileNoIntr failed. rc=%Rrc cbAuxFile=%u\n", rc, cbAuxFile)); + + pSolProc->pAuxVecs = NULL; + pSolProc->cAuxVecs = 0; + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: no memory for %u bytes\n", cbAuxFile + sizeof(auxv_t))); + rc = VERR_NO_MEMORY; + } + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: aux file too small %u, expecting %u or more\n", cbAuxFile, sizeof(auxv_t))); + rc = VERR_READ_ERROR; + } + + close(fd); + return rc; +} + + +/* + * Find an element in the process' auxiliary vector. + */ +static long GetAuxVal(PRTSOLCOREPROCESS pSolProc, int Type) +{ + AssertReturn(pSolProc, -1); + if (pSolProc->pAuxVecs) + { + auxv_t *pAuxVec = pSolProc->pAuxVecs; + for (; pAuxVec->a_type != AT_NULL; pAuxVec++) + { + if (pAuxVec->a_type == Type) + return pAuxVec->a_un.a_val; + } + } + return -1; +} + + +/** + * Read the process mappings (format prmap_t array). + * + * @return IPRT status code. + * @param pSolCore Pointer to the core object. + * + * @remarks Should not be called before successful call to @see AllocMemoryArea() + */ +static int ProcReadMappings(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + char szPath[PATH_MAX]; + int rc = VINF_SUCCESS; + RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/map", (int)pSolProc->Process); + int fdMap = open(szPath, O_RDONLY); + if (fdMap < 0) + { + rc = RTErrConvertFromErrno(errno); + CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: failed to open %s. rc=%Rrc\n", szPath, rc)); + return rc; + } + + RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/as", (int)pSolProc->Process); + pSolProc->fdAs = open(szPath, O_RDONLY); + if (pSolProc->fdAs >= 0) + { + /* + * Allocate and read all the prmap_t objects from proc. + */ + size_t cbMapFile = GetFileSizeByFd(fdMap); + if (cbMapFile >= sizeof(prmap_t)) + { + prmap_t *pMap = (prmap_t*)GetMemoryChunk(pSolCore, cbMapFile); + if (pMap) + { + rc = ReadFileNoIntr(fdMap, pMap, cbMapFile); + if (RT_SUCCESS(rc)) + { + pSolProc->cMappings = cbMapFile / sizeof(prmap_t); + if (pSolProc->cMappings > 0) + { + /* + * Allocate for each prmap_t object, a corresponding RTSOLCOREMAPINFO object. + */ + pSolProc->pMapInfoHead = (PRTSOLCOREMAPINFO)GetMemoryChunk(pSolCore, + pSolProc->cMappings * sizeof(RTSOLCOREMAPINFO)); + if (pSolProc->pMapInfoHead) + { + /* + * Associate the prmap_t with the mapping info object. + */ + /*Assert(pSolProc->pMapInfoHead == NULL); - does not make sense */ + PRTSOLCOREMAPINFO pCur = pSolProc->pMapInfoHead; + PRTSOLCOREMAPINFO pPrev = NULL; + for (uint64_t i = 0; i < pSolProc->cMappings; i++, pMap++, pCur++) + { + memcpy(&pCur->pMap, pMap, sizeof(pCur->pMap)); + if (pPrev) + pPrev->pNext = pCur; + + pCur->fError = 0; + + /* + * Make sure we can read the mapping, otherwise mark them to be skipped. + */ + char achBuf[PAGE_SIZE]; + uint64_t k = 0; + while (k < pCur->pMap.pr_size) + { + size_t cb = RT_MIN(sizeof(achBuf), pCur->pMap.pr_size - k); + int rc2 = ProcReadAddrSpace(pSolProc, pCur->pMap.pr_vaddr + k, &achBuf, cb); + if (RT_FAILURE(rc2)) + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: skipping mapping. vaddr=%#x rc=%Rrc\n", + pCur->pMap.pr_vaddr, rc2)); + + /* + * Instead of storing the actual mapping data which we failed to read, the core + * will contain an errno in place. So we adjust the prmap_t's size field too + * so the program header offsets match. + */ + pCur->pMap.pr_size = RT_ALIGN_Z(sizeof(int), 8); + pCur->fError = errno; + if (pCur->fError == 0) /* huh!? somehow errno got reset? fake one! EFAULT is nice. */ + pCur->fError = EFAULT; + break; + } + k += cb; + } + + pPrev = pCur; + } + if (pPrev) + pPrev->pNext = NULL; + + close(fdMap); + close(pSolProc->fdAs); + pSolProc->fdAs = -1; + CORELOG((CORELOG_NAME "ProcReadMappings: successfully read in %u mappings\n", pSolProc->cMappings)); + return VINF_SUCCESS; + } + + CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: GetMemoryChunk failed %u\n", + pSolProc->cMappings * sizeof(RTSOLCOREMAPINFO))); + rc = VERR_NO_MEMORY; + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: Invalid mapping count %u\n", pSolProc->cMappings)); + rc = VERR_READ_ERROR; + } + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: FileReadNoIntr failed. rc=%Rrc cbMapFile=%u\n", rc, + cbMapFile)); + } + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: GetMemoryChunk failed. cbMapFile=%u\n", cbMapFile)); + rc = VERR_NO_MEMORY; + } + } + + close(pSolProc->fdAs); + pSolProc->fdAs = -1; + } + else + CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: failed to open %s. rc=%Rrc\n", szPath, rc)); + + close(fdMap); + return rc; +} + + +/** + * Reads the thread information for all threads in the process. + * + * @return IPRT status code. + * @param pSolCore Pointer to the core object. + * + * @remarks Should not be called before successful call to @see AllocMemoryArea() + */ +static int ProcReadThreads(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + AssertReturn(pSolProc->pCurThreadCtx, VERR_NO_DATA); + + /* + * Read the information for threads. + * Format: prheader_t + array of lwpsinfo_t's. + */ + size_t cbInfoHdrAndData; + void *pvInfoHdr = NULL; + int rc = ProcReadFileInto(pSolCore, "lpsinfo", &pvInfoHdr, &cbInfoHdrAndData); + if (RT_SUCCESS(rc)) + { + /* + * Read the status of threads. + * Format: prheader_t + array of lwpstatus_t's. + */ + void *pvStatusHdr = NULL; + size_t cbStatusHdrAndData; + rc = ProcReadFileInto(pSolCore, "lstatus", &pvStatusHdr, &cbStatusHdrAndData); + if (RT_SUCCESS(rc)) + { + prheader_t *pInfoHdr = (prheader_t *)pvInfoHdr; + prheader_t *pStatusHdr = (prheader_t *)pvStatusHdr; + lwpstatus_t *pStatus = (lwpstatus_t *)((uintptr_t)pStatusHdr + sizeof(prheader_t)); + lwpsinfo_t *pInfo = (lwpsinfo_t *)((uintptr_t)pInfoHdr + sizeof(prheader_t)); + uint64_t cStatus = pStatusHdr->pr_nent; + uint64_t cInfo = pInfoHdr->pr_nent; + + CORELOG((CORELOG_NAME "ProcReadThreads: read info(%u) status(%u), threads:cInfo=%u cStatus=%u\n", cbInfoHdrAndData, + cbStatusHdrAndData, cInfo, cStatus)); + + /* + * Minor sanity size check (remember sizeof lwpstatus_t & lwpsinfo_t is <= size in file per entry). + */ + if ( (cbStatusHdrAndData - sizeof(prheader_t)) % pStatusHdr->pr_entsize == 0 + && (cbInfoHdrAndData - sizeof(prheader_t)) % pInfoHdr->pr_entsize == 0) + { + /* + * Make sure we have a matching lstatus entry for an lpsinfo entry unless + * it is a zombie thread, in which case we will not have a matching lstatus entry. + */ + for (; cInfo != 0; cInfo--) + { + if (pInfo->pr_sname != 'Z') /* zombie */ + { + if ( cStatus == 0 + || pStatus->pr_lwpid != pInfo->pr_lwpid) + { + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: cStatus = %u pStatuslwpid=%d infolwpid=%d\n", cStatus, + pStatus->pr_lwpid, pInfo->pr_lwpid)); + rc = VERR_INVALID_STATE; + break; + } + pStatus = (lwpstatus_t *)((uintptr_t)pStatus + pStatusHdr->pr_entsize); + cStatus--; + } + pInfo = (lwpsinfo_t *)((uintptr_t)pInfo + pInfoHdr->pr_entsize); + } + + if (RT_SUCCESS(rc)) + { + /* + * There can still be more lwpsinfo_t's than lwpstatus_t's, build the + * lists accordingly. + */ + pStatus = (lwpstatus_t *)((uintptr_t)pStatusHdr + sizeof(prheader_t)); + pInfo = (lwpsinfo_t *)((uintptr_t)pInfoHdr + sizeof(prheader_t)); + cInfo = pInfoHdr->pr_nent; + cStatus = pInfoHdr->pr_nent; + + size_t cbThreadInfo = RT_MAX(cStatus, cInfo) * sizeof(RTSOLCORETHREADINFO); + pSolProc->pThreadInfoHead = (PRTSOLCORETHREADINFO)GetMemoryChunk(pSolCore, cbThreadInfo); + if (pSolProc->pThreadInfoHead) + { + PRTSOLCORETHREADINFO pCur = pSolProc->pThreadInfoHead; + PRTSOLCORETHREADINFO pPrev = NULL; + for (uint64_t i = 0; i < cInfo; i++, pCur++) + { + pCur->Info = *pInfo; + if ( pInfo->pr_sname != 'Z' + && pInfo->pr_lwpid == pStatus->pr_lwpid) + { + /* + * Adjust the context of the dumping thread to reflect the context + * when the core dump got initiated before whatever signal caused it. + */ + if ( pStatus /* noid droid */ + && pStatus->pr_lwpid == (id_t)pSolProc->hCurThread) + { + AssertCompile(sizeof(pStatus->pr_reg) == sizeof(pSolProc->pCurThreadCtx->uc_mcontext.gregs)); + AssertCompile(sizeof(pStatus->pr_fpreg) == sizeof(pSolProc->pCurThreadCtx->uc_mcontext.fpregs)); + memcpy(&pStatus->pr_reg, &pSolProc->pCurThreadCtx->uc_mcontext.gregs, sizeof(pStatus->pr_reg)); + memcpy(&pStatus->pr_fpreg, &pSolProc->pCurThreadCtx->uc_mcontext.fpregs, sizeof(pStatus->pr_fpreg)); + + AssertCompile(sizeof(pStatus->pr_lwphold) == sizeof(pSolProc->pCurThreadCtx->uc_sigmask)); + memcpy(&pStatus->pr_lwphold, &pSolProc->pCurThreadCtx->uc_sigmask, sizeof(pStatus->pr_lwphold)); + pStatus->pr_ustack = (uintptr_t)&pSolProc->pCurThreadCtx->uc_stack; + + CORELOG((CORELOG_NAME "ProcReadThreads: patched dumper thread with pre-dump time context.\n")); + } + + pCur->pStatus = pStatus; + pStatus = (lwpstatus_t *)((uintptr_t)pStatus + pStatusHdr->pr_entsize); + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: missing status for lwp %d\n", pInfo->pr_lwpid)); + pCur->pStatus = NULL; + } + + if (pPrev) + pPrev->pNext = pCur; + pPrev = pCur; + pInfo = (lwpsinfo_t *)((uintptr_t)pInfo + pInfoHdr->pr_entsize); + } + if (pPrev) + pPrev->pNext = NULL; + + CORELOG((CORELOG_NAME "ProcReadThreads: successfully read %u threads.\n", cInfo)); + pSolProc->cThreads = cInfo; + return VINF_SUCCESS; + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: GetMemoryChunk failed for %u bytes\n", cbThreadInfo)); + rc = VERR_NO_MEMORY; + } + } + else + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: Invalid state information for threads. rc=%Rrc\n", rc)); + } + else + { + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: huh!? cbStatusHdrAndData=%u prheader_t=%u entsize=%u\n", + cbStatusHdrAndData, sizeof(prheader_t), pStatusHdr->pr_entsize)); + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: huh!? cbInfoHdrAndData=%u entsize=%u\n", cbInfoHdrAndData, + pStatusHdr->pr_entsize)); + rc = VERR_INVALID_STATE; + } + } + else + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: ReadFileNoIntr failed for \"lpsinfo\" rc=%Rrc\n", rc)); + } + else + CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: ReadFileNoIntr failed for \"lstatus\" rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Reads miscellaneous information that is collected as part of a core file. + * This may include platform name, zone name and other OS-specific information. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int ProcReadMiscInfo(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + +#ifdef RT_OS_SOLARIS + /* + * Read the platform name, uname string and zone name. + */ + int rc = sysinfo(SI_PLATFORM, pSolProc->szPlatform, sizeof(pSolProc->szPlatform)); + if (rc == -1) + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: sysinfo failed. rc=%d errno=%d\n", rc, errno)); + return VERR_GENERAL_FAILURE; + } + pSolProc->szPlatform[sizeof(pSolProc->szPlatform) - 1] = '\0'; + + rc = uname(&pSolProc->UtsName); + if (rc == -1) + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: uname failed. rc=%d errno=%d\n", rc, errno)); + return VERR_GENERAL_FAILURE; + } + + /* + * See comment in GetOldProcessInfo() for why we need to verify the offset here. + * It's not perfect, but it should be fine unless they really mess up the structure + * layout in the future. See @bugref{8479}. + */ + size_t const offZoneId = RT_UOFFSETOF(psinfo_t, pr_zoneid); + if (pSolProc->cbProcInfo < offZoneId) + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: psinfo size mismatch. cbProcInfo=%u expected >= %u\n", + pSolProc->cbProcInfo, offZoneId)); + return VERR_GENERAL_FAILURE; + } + + psinfo_t *pProcInfo = (psinfo_t *)pSolProc->pvProcInfo; + rc = getzonenamebyid(pProcInfo->pr_zoneid, pSolProc->szZoneName, sizeof(pSolProc->szZoneName)); + if (rc < 0) + { + CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: getzonenamebyid failed. rc=%d errno=%d zoneid=%d\n", rc, errno, + pProcInfo->pr_zoneid)); + return VERR_GENERAL_FAILURE; + } + pSolProc->szZoneName[sizeof(pSolProc->szZoneName) - 1] = '\0'; + rc = VINF_SUCCESS; + +#else +# error Port Me! +#endif + return rc; +} + + +/** + * On Solaris use the old-style procfs interfaces but the core file still should have this + * info. for backward and GDB compatibility, hence the need for this ugly function. + * + * @returns IPRT status code. + * + * @param pSolCore Pointer to the core object. + * @param pInfo Pointer to the old prpsinfo_t structure to update. + */ +static int GetOldProcessInfo(PRTSOLCORE pSolCore, prpsinfo_t *pInfo) +{ + AssertReturn(pSolCore, VERR_INVALID_PARAMETER); + AssertReturn(pInfo, VERR_INVALID_PARAMETER); + + /* + * The psinfo_t and the size of /proc/<pid>/psinfo varies both within the same Solaris system + * and across Solaris major versions. However, manual dumping of the structure and offsets shows + * that they changed the size of lwpsinfo_t and the size of the lwpsinfo_t::pr_filler. + * + * The proc psinfo file will be what gets dumped ultimately in the core file but we still need + * to read the fields to translate to the older process info structure here. + * + * See @bugref{8479}. + */ + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + + size_t offLwp = RT_UOFFSETOF(psinfo_t, pr_lwp); + /* last member we care about in lwpsinfo_t is pr_bindpset which is also present on ancient Solaris version we use for + building the additions. Should be safe enough as we don't need/access members upto or beyond that point anyway. */ + size_t offLastOnProc = RT_UOFFSETOF(lwpsinfo_t, pr_bindpset); + if (pSolProc->cbProcInfo >= offLwp + offLastOnProc) + { + psinfo_t *pSrc = (psinfo_t *)pSolProc->pvProcInfo; + memset(pInfo, 0, sizeof(prpsinfo_t)); + pInfo->pr_state = pSrc->pr_lwp.pr_state; + pInfo->pr_zomb = (pInfo->pr_state == SZOMB); + RTStrCopy(pInfo->pr_clname, sizeof(pInfo->pr_clname), pSrc->pr_lwp.pr_clname); + RTStrCopy(pInfo->pr_fname, sizeof(pInfo->pr_fname), pSrc->pr_fname); + memcpy(&pInfo->pr_psargs, &pSrc->pr_psargs, sizeof(pInfo->pr_psargs)); + pInfo->pr_nice = pSrc->pr_lwp.pr_nice; + pInfo->pr_flag = pSrc->pr_lwp.pr_flag; + pInfo->pr_uid = pSrc->pr_uid; + pInfo->pr_gid = pSrc->pr_gid; + pInfo->pr_pid = pSrc->pr_pid; + pInfo->pr_ppid = pSrc->pr_ppid; + pInfo->pr_pgrp = pSrc->pr_pgid; + pInfo->pr_sid = pSrc->pr_sid; + pInfo->pr_addr = (caddr_t)pSrc->pr_addr; + pInfo->pr_size = pSrc->pr_size; + pInfo->pr_rssize = pSrc->pr_rssize; + pInfo->pr_wchan = (caddr_t)pSrc->pr_lwp.pr_wchan; + pInfo->pr_start = pSrc->pr_start; + pInfo->pr_time = pSrc->pr_time; + pInfo->pr_pri = pSrc->pr_lwp.pr_pri; + pInfo->pr_oldpri = pSrc->pr_lwp.pr_oldpri; + pInfo->pr_cpu = pSrc->pr_lwp.pr_cpu; + pInfo->pr_ottydev = cmpdev(pSrc->pr_ttydev); + pInfo->pr_lttydev = pSrc->pr_ttydev; + pInfo->pr_syscall = pSrc->pr_lwp.pr_syscall; + pInfo->pr_ctime = pSrc->pr_ctime; + pInfo->pr_bysize = pSrc->pr_size * PAGESIZE; + pInfo->pr_byrssize = pSrc->pr_rssize * PAGESIZE; + pInfo->pr_argc = pSrc->pr_argc; + pInfo->pr_argv = (char **)pSrc->pr_argv; + pInfo->pr_envp = (char **)pSrc->pr_envp; + pInfo->pr_wstat = pSrc->pr_wstat; + pInfo->pr_pctcpu = pSrc->pr_pctcpu; + pInfo->pr_pctmem = pSrc->pr_pctmem; + pInfo->pr_euid = pSrc->pr_euid; + pInfo->pr_egid = pSrc->pr_egid; + pInfo->pr_aslwpid = 0; + pInfo->pr_dmodel = pSrc->pr_dmodel; + + return VINF_SUCCESS; + } + + CORELOGRELSYS((CORELOG_NAME "GetOldProcessInfo: Size/offset mismatch. offLwp=%u offLastOnProc=%u cbProcInfo=%u\n", + offLwp, offLastOnProc, pSolProc->cbProcInfo)); + return VERR_MISMATCH; +} + + +/** + * On Solaris use the old-style procfs interfaces but the core file still should have this + * info. for backward and GDB compatibility, hence the need for this ugly function. + * + * @param pSolCore Pointer to the core object. + * @param pInfo Pointer to the thread info. + * @param pStatus Pointer to the thread status. + * @param pDst Pointer to the old-style status structure to update. + * + */ +static void GetOldProcessStatus(PRTSOLCORE pSolCore, lwpsinfo_t *pInfo, lwpstatus_t *pStatus, prstatus_t *pDst) +{ + AssertReturnVoid(pSolCore); + AssertReturnVoid(pInfo); + AssertReturnVoid(pStatus); + AssertReturnVoid(pDst); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + memset(pDst, 0, sizeof(prstatus_t)); + if (pStatus->pr_flags & PR_STOPPED) + pDst->pr_flags = 0x0001; + if (pStatus->pr_flags & PR_ISTOP) + pDst->pr_flags = 0x0002; + if (pStatus->pr_flags & PR_DSTOP) + pDst->pr_flags = 0x0004; + if (pStatus->pr_flags & PR_ASLEEP) + pDst->pr_flags = 0x0008; + if (pStatus->pr_flags & PR_FORK) + pDst->pr_flags = 0x0010; + if (pStatus->pr_flags & PR_RLC) + pDst->pr_flags = 0x0020; + /* PR_PTRACE is never set */ + if (pStatus->pr_flags & PR_PCINVAL) + pDst->pr_flags = 0x0080; + if (pStatus->pr_flags & PR_ISSYS) + pDst->pr_flags = 0x0100; + if (pStatus->pr_flags & PR_STEP) + pDst->pr_flags = 0x0200; + if (pStatus->pr_flags & PR_KLC) + pDst->pr_flags = 0x0400; + if (pStatus->pr_flags & PR_ASYNC) + pDst->pr_flags = 0x0800; + if (pStatus->pr_flags & PR_PTRACE) + pDst->pr_flags = 0x1000; + if (pStatus->pr_flags & PR_MSACCT) + pDst->pr_flags = 0x2000; + if (pStatus->pr_flags & PR_BPTADJ) + pDst->pr_flags = 0x4000; + if (pStatus->pr_flags & PR_ASLWP) + pDst->pr_flags = 0x8000; + + pDst->pr_who = pStatus->pr_lwpid; + pDst->pr_why = pStatus->pr_why; + pDst->pr_what = pStatus->pr_what; + pDst->pr_info = pStatus->pr_info; + pDst->pr_cursig = pStatus->pr_cursig; + pDst->pr_sighold = pStatus->pr_lwphold; + pDst->pr_altstack = pStatus->pr_altstack; + pDst->pr_action = pStatus->pr_action; + pDst->pr_syscall = pStatus->pr_syscall; + pDst->pr_nsysarg = pStatus->pr_nsysarg; + pDst->pr_lwppend = pStatus->pr_lwppend; + pDst->pr_oldcontext = (ucontext_t *)pStatus->pr_oldcontext; + memcpy(pDst->pr_reg, pStatus->pr_reg, sizeof(pDst->pr_reg)); + memcpy(pDst->pr_sysarg, pStatus->pr_sysarg, sizeof(pDst->pr_sysarg)); + RTStrCopy(pDst->pr_clname, sizeof(pDst->pr_clname), pStatus->pr_clname); + + pDst->pr_nlwp = pSolProc->ProcStatus.pr_nlwp; + pDst->pr_sigpend = pSolProc->ProcStatus.pr_sigpend; + pDst->pr_pid = pSolProc->ProcStatus.pr_pid; + pDst->pr_ppid = pSolProc->ProcStatus.pr_ppid; + pDst->pr_pgrp = pSolProc->ProcStatus.pr_pgid; + pDst->pr_sid = pSolProc->ProcStatus.pr_sid; + pDst->pr_utime = pSolProc->ProcStatus.pr_utime; + pDst->pr_stime = pSolProc->ProcStatus.pr_stime; + pDst->pr_cutime = pSolProc->ProcStatus.pr_cutime; + pDst->pr_cstime = pSolProc->ProcStatus.pr_cstime; + pDst->pr_brkbase = (caddr_t)pSolProc->ProcStatus.pr_brkbase; + pDst->pr_brksize = pSolProc->ProcStatus.pr_brksize; + pDst->pr_stkbase = (caddr_t)pSolProc->ProcStatus.pr_stkbase; + pDst->pr_stksize = pSolProc->ProcStatus.pr_stksize; + + pDst->pr_processor = (short)pInfo->pr_onpro; + pDst->pr_bind = (short)pInfo->pr_bindpro; + pDst->pr_instr = pStatus->pr_instr; +} + + +/** + * Callback for rtCoreDumperForEachThread to suspend a thread. + * + * @param pSolCore Pointer to the core object. + * @param pvThreadInfo Opaque pointer to thread information. + * + * @return IPRT status code. + */ +static int suspendThread(PRTSOLCORE pSolCore, void *pvThreadInfo) +{ + AssertPtrReturn(pvThreadInfo, VERR_INVALID_POINTER); + NOREF(pSolCore); + + lwpsinfo_t *pThreadInfo = (lwpsinfo_t *)pvThreadInfo; + CORELOG((CORELOG_NAME ":suspendThread %d\n", (lwpid_t)pThreadInfo->pr_lwpid)); + if ((lwpid_t)pThreadInfo->pr_lwpid != pSolCore->SolProc.hCurThread) + _lwp_suspend(pThreadInfo->pr_lwpid); + return VINF_SUCCESS; +} + + +/** + * Callback for rtCoreDumperForEachThread to resume a thread. + * + * @param pSolCore Pointer to the core object. + * @param pvThreadInfo Opaque pointer to thread information. + * + * @return IPRT status code. + */ +static int resumeThread(PRTSOLCORE pSolCore, void *pvThreadInfo) +{ + AssertPtrReturn(pvThreadInfo, VERR_INVALID_POINTER); + NOREF(pSolCore); + + lwpsinfo_t *pThreadInfo = (lwpsinfo_t *)pvThreadInfo; + CORELOG((CORELOG_NAME ":resumeThread %d\n", (lwpid_t)pThreadInfo->pr_lwpid)); + if ((lwpid_t)pThreadInfo->pr_lwpid != (lwpid_t)pSolCore->SolProc.hCurThread) + _lwp_continue(pThreadInfo->pr_lwpid); + return VINF_SUCCESS; +} + + +/** + * Calls a thread worker function for all threads in the process as described by /proc + * + * @param pSolCore Pointer to the core object. + * @param pcThreads Number of threads read. + * @param pfnWorker Callback function for each thread. + * + * @return IPRT status code. + */ +static int rtCoreDumperForEachThread(PRTSOLCORE pSolCore, uint64_t *pcThreads, PFNRTSOLCORETHREADWORKER pfnWorker) +{ + AssertPtrReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + + /* + * Read the information for threads. + * Format: prheader_t + array of lwpsinfo_t's. + */ + char szLpsInfoPath[PATH_MAX]; + RTStrPrintf(szLpsInfoPath, sizeof(szLpsInfoPath), "/proc/%d/lpsinfo", (int)pSolProc->Process); + + int rc = VINF_SUCCESS; + int fd = open(szLpsInfoPath, O_RDONLY); + if (fd >= 0) + { + size_t cbInfoHdrAndData = GetFileSizeByFd(fd); + void *pvInfoHdr = mmap(NULL, cbInfoHdrAndData, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, + -1 /* fd */, 0 /* offset */); + if (pvInfoHdr != MAP_FAILED) + { + rc = ReadFileNoIntr(fd, pvInfoHdr, cbInfoHdrAndData); + if (RT_SUCCESS(rc)) + { + prheader_t *pHeader = (prheader_t *)pvInfoHdr; + lwpsinfo_t *pThreadInfo = (lwpsinfo_t *)((uintptr_t)pvInfoHdr + sizeof(prheader_t)); + for (long i = 0; i < pHeader->pr_nent; i++) + { + pfnWorker(pSolCore, pThreadInfo); + pThreadInfo = (lwpsinfo_t *)((uintptr_t)pThreadInfo + pHeader->pr_entsize); + } + if (pcThreads) + *pcThreads = pHeader->pr_nent; + } + + munmap(pvInfoHdr, cbInfoHdrAndData); + } + else + rc = VERR_NO_MEMORY; + close(fd); + } + else + rc = RTErrConvertFromErrno(rc); + + return rc; +} + + +/** + * Resume all threads of this process. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code.. + */ +static int rtCoreDumperResumeThreads(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + uint64_t cThreads; + return rtCoreDumperForEachThread(pSolCore, &cThreads, resumeThread); +} + + +/** + * Stop all running threads of this process except the current one. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int rtCoreDumperSuspendThreads(PRTSOLCORE pSolCore) +{ + AssertPtrReturn(pSolCore, VERR_INVALID_POINTER); + + /* + * This function tries to ensures while we suspend threads, no newly spawned threads + * or a combination of spawning and terminating threads can cause any threads to be left running. + * The assumption here is that threads can only increase not decrease across iterations. + */ + uint16_t cTries = 0; + uint64_t aThreads[4]; + RT_ZERO(aThreads); + int rc = VERR_GENERAL_FAILURE; + void *pv = NULL; + size_t cb = 0; + for (cTries = 0; cTries < RT_ELEMENTS(aThreads); cTries++) + { + rc = rtCoreDumperForEachThread(pSolCore, &aThreads[cTries], suspendThread); + if (RT_FAILURE(rc)) + break; + } + if ( RT_SUCCESS(rc) + && aThreads[cTries - 1] != aThreads[cTries - 2]) + { + CORELOGRELSYS((CORELOG_NAME "rtCoreDumperSuspendThreads: possible thread bomb!?\n")); + rc = VERR_TIMEOUT; + } + return rc; +} + + +/** + * Returns size of an ELF NOTE header given the size of data the NOTE section will contain. + * + * @param cb Size of the data. + * + * @return Size of data actually used for NOTE header and section. + */ +static inline size_t ElfNoteHeaderSize(size_t cb) +{ + return sizeof(ELFNOTEHDR) + RT_ALIGN_Z(cb, 4); +} + + +/** + * Write an ELF NOTE header into the core file. + * + * @param pSolCore Pointer to the core object. + * @param Type Type of this NOTE section. + * @param pcv Opaque pointer to the data, if NULL only computes size. + * @param cb Size of the data. + * + * @return IPRT status code. + */ +static int ElfWriteNoteHeader(PRTSOLCORE pSolCore, uint_t Type, const void *pcv, size_t cb) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + AssertReturn(pcv, VERR_INVALID_POINTER); + AssertReturn(cb > 0, VERR_NO_DATA); + AssertReturn(pSolCore->pfnWriter, VERR_WRITE_ERROR); + AssertReturn(pSolCore->fdCoreFile >= 0, VERR_INVALID_HANDLE); + + int rc = VERR_GENERAL_FAILURE; +#ifdef RT_OS_SOLARIS + ELFNOTEHDR ElfNoteHdr; + RT_ZERO(ElfNoteHdr); + ElfNoteHdr.achName[0] = 'C'; + ElfNoteHdr.achName[1] = 'O'; + ElfNoteHdr.achName[2] = 'R'; + ElfNoteHdr.achName[3] = 'E'; + + /* + * This is a known violation of the 64-bit ELF spec., see xTracker @bugref{5211} + * for the historic reasons as to the padding and 'namesz' anomalies. + */ + static const char s_achPad[3] = { 0, 0, 0 }; + size_t cbAlign = RT_ALIGN_Z(cb, 4); + ElfNoteHdr.Hdr.n_namesz = 5; + ElfNoteHdr.Hdr.n_type = Type; + ElfNoteHdr.Hdr.n_descsz = cbAlign; + + /* + * Write note header and description. + */ + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ElfNoteHdr, sizeof(ElfNoteHdr)); + if (RT_SUCCESS(rc)) + { + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, pcv, cb); + if (RT_SUCCESS(rc)) + { + if (cbAlign > cb) + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, s_achPad, cbAlign - cb); + } + } + + if (RT_FAILURE(rc)) + CORELOGRELSYS((CORELOG_NAME "ElfWriteNote: pfnWriter failed. Type=%d rc=%Rrc\n", Type, rc)); +#else +#error Port Me! +#endif + return rc; +} + + +/** + * Computes the size of NOTE section for the given core type. + * Solaris has two types of program header information (new and old). + * + * @param pSolCore Pointer to the core object. + * @param enmType Type of core file information required. + * + * @return Size of NOTE section. + */ +static size_t ElfNoteSectionSize(PRTSOLCORE pSolCore, RTSOLCORETYPE enmType) +{ + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + size_t cb = 0; + switch (enmType) + { + case enmOldEra: + { + cb += ElfNoteHeaderSize(sizeof(prpsinfo_t)); + cb += ElfNoteHeaderSize(pSolProc->cAuxVecs * sizeof(auxv_t)); + cb += ElfNoteHeaderSize(strlen(pSolProc->szPlatform)); + + PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; + while (pThreadInfo) + { + if (pThreadInfo->pStatus) + { + cb += ElfNoteHeaderSize(sizeof(prstatus_t)); + cb += ElfNoteHeaderSize(sizeof(prfpregset_t)); + } + pThreadInfo = pThreadInfo->pNext; + } + + break; + } + + case enmNewEra: + { + cb += ElfNoteHeaderSize(sizeof(psinfo_t)); + cb += ElfNoteHeaderSize(sizeof(pstatus_t)); + cb += ElfNoteHeaderSize(pSolProc->cAuxVecs * sizeof(auxv_t)); + cb += ElfNoteHeaderSize(strlen(pSolProc->szPlatform) + 1); + cb += ElfNoteHeaderSize(sizeof(struct utsname)); + cb += ElfNoteHeaderSize(sizeof(core_content_t)); + cb += ElfNoteHeaderSize(pSolProc->cbCred); + + if (pSolProc->pPriv) + cb += ElfNoteHeaderSize(PRIV_PRPRIV_SIZE(pSolProc->pPriv)); /* Ought to be same as cbPriv!? */ + + if (pSolProc->pcPrivImpl) + cb += ElfNoteHeaderSize(PRIV_IMPL_INFO_SIZE(pSolProc->pcPrivImpl)); + + cb += ElfNoteHeaderSize(strlen(pSolProc->szZoneName) + 1); + if (pSolProc->cbLdt > 0) + cb += ElfNoteHeaderSize(pSolProc->cbLdt); + + PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; + while (pThreadInfo) + { + cb += ElfNoteHeaderSize(sizeof(lwpsinfo_t)); + if (pThreadInfo->pStatus) + cb += ElfNoteHeaderSize(sizeof(lwpstatus_t)); + + pThreadInfo = pThreadInfo->pNext; + } + + break; + } + + default: + { + CORELOGRELSYS((CORELOG_NAME "ElfNoteSectionSize: Unknown segment era %d\n", enmType)); + break; + } + } + + return cb; +} + + +/** + * Write the note section for the given era into the core file. + * Solaris has two types of program header information (new and old). + * + * @param pSolCore Pointer to the core object. + * @param enmType Type of core file information required. + * + * @return IPRT status code. + */ +static int ElfWriteNoteSection(PRTSOLCORE pSolCore, RTSOLCORETYPE enmType) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + int rc = VERR_GENERAL_FAILURE; + +#ifdef RT_OS_SOLARIS + typedef int (*PFNELFWRITENOTEHDR)(PRTSOLCORE pSolCore, uint_t, const void *pcv, size_t cb); + typedef struct + { + const char *pszType; + uint_t Type; + const void *pcv; + size_t cb; + } ELFWRITENOTE; + + switch (enmType) + { + case enmOldEra: + { + ELFWRITENOTE aElfNotes[] = + { + { "NT_PRPSINFO", NT_PRPSINFO, &pSolProc->ProcInfoOld, sizeof(prpsinfo_t) }, + { "NT_AUXV", NT_AUXV, pSolProc->pAuxVecs, pSolProc->cAuxVecs * sizeof(auxv_t) }, + { "NT_PLATFORM", NT_PLATFORM, pSolProc->szPlatform, strlen(pSolProc->szPlatform) + 1 } + }; + + for (unsigned i = 0; i < RT_ELEMENTS(aElfNotes); i++) + { + rc = ElfWriteNoteHeader(pSolCore, aElfNotes[i].Type, aElfNotes[i].pcv, aElfNotes[i].cb); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader failed for %s. rc=%Rrc\n", + aElfNotes[i].pszType, rc)); + break; + } + } + + /* + * Write old-style thread info., they contain nothing about zombies, + * so we just skip if there is no status information for them. + */ + PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; + for (; pThreadInfo; pThreadInfo = pThreadInfo->pNext) + { + if (!pThreadInfo->pStatus) + continue; + + prstatus_t OldProcessStatus; + GetOldProcessStatus(pSolCore, &pThreadInfo->Info, pThreadInfo->pStatus, &OldProcessStatus); + rc = ElfWriteNoteHeader(pSolCore, NT_PRSTATUS, &OldProcessStatus, sizeof(prstatus_t)); + if (RT_SUCCESS(rc)) + { + rc = ElfWriteNoteHeader(pSolCore, NT_PRFPREG, &pThreadInfo->pStatus->pr_fpreg, sizeof(prfpregset_t)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteSegment: ElfWriteNote failed for NT_PRFPREF. rc=%Rrc\n", rc)); + break; + } + } + else + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteSegment: ElfWriteNote failed for NT_PRSTATUS. rc=%Rrc\n", rc)); + break; + } + } + break; + } + + case enmNewEra: + { + ELFWRITENOTE aElfNotes[] = + { + { "NT_PSINFO", NT_PSINFO, pSolProc->pvProcInfo, pSolProc->cbProcInfo }, + { "NT_PSTATUS", NT_PSTATUS, &pSolProc->ProcStatus, sizeof(pstatus_t) }, + { "NT_AUXV", NT_AUXV, pSolProc->pAuxVecs, pSolProc->cAuxVecs * sizeof(auxv_t) }, + { "NT_PLATFORM", NT_PLATFORM, pSolProc->szPlatform, strlen(pSolProc->szPlatform) + 1 }, + { "NT_UTSNAME", NT_UTSNAME, &pSolProc->UtsName, sizeof(struct utsname) }, + { "NT_CONTENT", NT_CONTENT, &pSolProc->CoreContent, sizeof(core_content_t) }, + { "NT_PRCRED", NT_PRCRED, pSolProc->pvCred, pSolProc->cbCred }, + { "NT_PRPRIV", NT_PRPRIV, pSolProc->pPriv, PRIV_PRPRIV_SIZE(pSolProc->pPriv) }, + { "NT_PRPRIVINFO", NT_PRPRIVINFO, pSolProc->pcPrivImpl, PRIV_IMPL_INFO_SIZE(pSolProc->pcPrivImpl) }, + { "NT_ZONENAME", NT_ZONENAME, pSolProc->szZoneName, strlen(pSolProc->szZoneName) + 1 } + }; + + for (unsigned i = 0; i < RT_ELEMENTS(aElfNotes); i++) + { + rc = ElfWriteNoteHeader(pSolCore, aElfNotes[i].Type, aElfNotes[i].pcv, aElfNotes[i].cb); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader failed for %s. rc=%Rrc\n", + aElfNotes[i].pszType, rc)); + break; + } + } + + /* + * Write new-style thread info., missing lwpstatus_t indicates it's a zombie thread + * we only dump the lwpsinfo_t in that case. + */ + PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; + for (; pThreadInfo; pThreadInfo = pThreadInfo->pNext) + { + rc = ElfWriteNoteHeader(pSolCore, NT_LWPSINFO, &pThreadInfo->Info, sizeof(lwpsinfo_t)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader for NT_LWPSINFO failed. rc=%Rrc\n", rc)); + break; + } + + if (pThreadInfo->pStatus) + { + rc = ElfWriteNoteHeader(pSolCore, NT_LWPSTATUS, pThreadInfo->pStatus, sizeof(lwpstatus_t)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader for NT_LWPSTATUS failed. rc=%Rrc\n", + rc)); + break; + } + } + } + break; + } + + default: + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: Invalid type %d\n", enmType)); + rc = VERR_GENERAL_FAILURE; + break; + } + } +#else +# error Port Me! +#endif + return rc; +} + + +/** + * Write mappings into the core file. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int ElfWriteMappings(PRTSOLCORE pSolCore) +{ + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + + int rc = VERR_GENERAL_FAILURE; + PRTSOLCOREMAPINFO pMapInfo = pSolProc->pMapInfoHead; + while (pMapInfo) + { + if (!pMapInfo->fError) + { + uint64_t k = 0; + char achBuf[PAGE_SIZE]; + while (k < pMapInfo->pMap.pr_size) + { + size_t cb = RT_MIN(sizeof(achBuf), pMapInfo->pMap.pr_size - k); + int rc2 = ProcReadAddrSpace(pSolProc, pMapInfo->pMap.pr_vaddr + k, &achBuf, cb); + if (RT_FAILURE(rc2)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: Failed to read mapping, can't recover. Bye. rc=%Rrc\n", rc)); + return VERR_INVALID_STATE; + } + + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, achBuf, sizeof(achBuf)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: pfnWriter failed. rc=%Rrc\n", rc)); + return rc; + } + k += cb; + } + } + else + { + char achBuf[RT_ALIGN_Z(sizeof(int), 8)]; + RT_ZERO(achBuf); + memcpy(achBuf, &pMapInfo->fError, sizeof(pMapInfo->fError)); + if (sizeof(achBuf) != pMapInfo->pMap.pr_size) + CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: Huh!? something is wrong!\n")); + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &achBuf, sizeof(achBuf)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: pfnWriter(2) failed. rc=%Rrc\n", rc)); + return rc; + } + } + + pMapInfo = pMapInfo->pNext; + } + + return VINF_SUCCESS; +} + + +/** + * Write program headers for all mappings into the core file. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int ElfWriteMappingHeaders(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + Elf_Phdr ProgHdr; + RT_ZERO(ProgHdr); + ProgHdr.p_type = PT_LOAD; + + int rc = VERR_GENERAL_FAILURE; + PRTSOLCOREMAPINFO pMapInfo = pSolProc->pMapInfoHead; + while (pMapInfo) + { + ProgHdr.p_vaddr = pMapInfo->pMap.pr_vaddr; /* Virtual address of this mapping in the process address space */ + ProgHdr.p_offset = pSolCore->offWrite; /* Where this mapping is located in the core file */ + ProgHdr.p_memsz = pMapInfo->pMap.pr_size; /* Size of the memory image of the mapping */ + ProgHdr.p_filesz = pMapInfo->pMap.pr_size; /* Size of the file image of the mapping */ + + ProgHdr.p_flags = 0; /* Reset fields in a loop when needed! */ + if (pMapInfo->pMap.pr_mflags & MA_READ) + ProgHdr.p_flags |= PF_R; + if (pMapInfo->pMap.pr_mflags & MA_WRITE) + ProgHdr.p_flags |= PF_W; + if (pMapInfo->pMap.pr_mflags & MA_EXEC) + ProgHdr.p_flags |= PF_X; + + if (pMapInfo->fError) + ProgHdr.p_flags |= PF_SUNW_FAILURE; + + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ProgHdr, sizeof(ProgHdr)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "ElfWriteMappingHeaders: pfnWriter failed. rc=%Rrc\n", rc)); + return rc; + } + + pSolCore->offWrite += ProgHdr.p_filesz; + pMapInfo = pMapInfo->pNext; + } + return rc; +} + +/** + * Inner worker for rtCoreDumperWriteCore, which purpose is to + * squash cleanup gotos. + */ +static int rtCoreDumperWriteCoreDoIt(PRTSOLCORE pSolCore, PFNRTCOREWRITER pfnWriter, + PRTSOLCOREPROCESS pSolProc) +{ + pSolCore->offWrite = 0; + uint32_t cProgHdrs = pSolProc->cMappings + 2; /* two PT_NOTE program headers (old, new style) */ + + /* + * Write the ELF header. + */ + Elf_Ehdr ElfHdr; + RT_ZERO(ElfHdr); + ElfHdr.e_ident[EI_MAG0] = ELFMAG0; + ElfHdr.e_ident[EI_MAG1] = ELFMAG1; + ElfHdr.e_ident[EI_MAG2] = ELFMAG2; + ElfHdr.e_ident[EI_MAG3] = ELFMAG3; + ElfHdr.e_ident[EI_DATA] = IsBigEndian() ? ELFDATA2MSB : ELFDATA2LSB; + ElfHdr.e_type = ET_CORE; + ElfHdr.e_version = EV_CURRENT; +#ifdef RT_ARCH_AMD64 + ElfHdr.e_machine = EM_AMD64; + ElfHdr.e_ident[EI_CLASS] = ELFCLASS64; +#else + ElfHdr.e_machine = EM_386; + ElfHdr.e_ident[EI_CLASS] = ELFCLASS32; +#endif + if (cProgHdrs >= PN_XNUM) + ElfHdr.e_phnum = PN_XNUM; + else + ElfHdr.e_phnum = cProgHdrs; + ElfHdr.e_ehsize = sizeof(ElfHdr); + ElfHdr.e_phoff = sizeof(ElfHdr); + ElfHdr.e_phentsize = sizeof(Elf_Phdr); + ElfHdr.e_shentsize = sizeof(Elf_Shdr); + int rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ElfHdr, sizeof(ElfHdr)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "WriteCore: pfnWriter failed writing ELF header. rc=%Rrc\n", rc)); + return rc; + } + + /* + * Setup program header. + */ + Elf_Phdr ProgHdr; + RT_ZERO(ProgHdr); + ProgHdr.p_type = PT_NOTE; + ProgHdr.p_flags = PF_R; + + /* + * Write old-style NOTE program header. + */ + pSolCore->offWrite += sizeof(ElfHdr) + cProgHdrs * sizeof(ProgHdr); + ProgHdr.p_offset = pSolCore->offWrite; + ProgHdr.p_filesz = ElfNoteSectionSize(pSolCore, enmOldEra); + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ProgHdr, sizeof(ProgHdr)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "WriteCore: pfnWriter failed writing old-style ELF program Header. rc=%Rrc\n", rc)); + return rc; + } + + /* + * Write new-style NOTE program header. + */ + pSolCore->offWrite += ProgHdr.p_filesz; + ProgHdr.p_offset = pSolCore->offWrite; + ProgHdr.p_filesz = ElfNoteSectionSize(pSolCore, enmNewEra); + rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ProgHdr, sizeof(ProgHdr)); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "WriteCore: pfnWriter failed writing new-style ELF program header. rc=%Rrc\n", rc)); + return rc; + } + + /* + * Write program headers per mapping. + */ + pSolCore->offWrite += ProgHdr.p_filesz; + rc = ElfWriteMappingHeaders(pSolCore); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "Write: ElfWriteMappings failed. rc=%Rrc\n", rc)); + return rc; + } + + /* + * Write old-style note section. + */ + rc = ElfWriteNoteSection(pSolCore, enmOldEra); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "WriteCore: ElfWriteNoteSection old-style failed. rc=%Rrc\n", rc)); + return rc; + } + + /* + * Write new-style section. + */ + rc = ElfWriteNoteSection(pSolCore, enmNewEra); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "WriteCore: ElfWriteNoteSection new-style failed. rc=%Rrc\n", rc)); + return rc; + } + + /* + * Write all mappings. + */ + rc = ElfWriteMappings(pSolCore); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "WriteCore: ElfWriteMappings failed. rc=%Rrc\n", rc)); + return rc; + } + + return rc; +} + + +/** + * Write a prepared core file using a user-passed in writer function, requires all threads + * to be in suspended state (i.e. called after CreateCore). + * + * @return IPRT status code. + * @param pSolCore Pointer to the core object. + * @param pfnWriter Pointer to the writer function to override default writer (NULL uses default). + * + * @remarks Resumes all suspended threads, unless it's an invalid core. This + * function must be called only -after- rtCoreDumperCreateCore(). + */ +static int rtCoreDumperWriteCore(PRTSOLCORE pSolCore, PFNRTCOREWRITER pfnWriter) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + + if (!pSolCore->fIsValid) + return VERR_INVALID_STATE; + + if (pfnWriter) + pSolCore->pfnWriter = pfnWriter; + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + char szPath[PATH_MAX]; + int rc; + + /* + * Open the process address space file. + */ + RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/as", (int)pSolProc->Process); + int fd = open(szPath, O_RDONLY); + if (fd >= 0) + { + pSolProc->fdAs = fd; + + /* + * Create the core file. + */ + fd = open(pSolCore->szCorePath, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR); + if (fd >= 0) + { + pSolCore->fdCoreFile = fd; + + /* + * Do the actual writing. + */ + rc = rtCoreDumperWriteCoreDoIt(pSolCore, pfnWriter, pSolProc); + + close(pSolCore->fdCoreFile); + pSolCore->fdCoreFile = -1; + } + else + { + rc = RTErrConvertFromErrno(fd); + CORELOGRELSYS((CORELOG_NAME "WriteCore: failed to open %s. rc=%Rrc\n", pSolCore->szCorePath, rc)); + } + close(pSolProc->fdAs); + pSolProc->fdAs = -1; + } + else + { + rc = RTErrConvertFromErrno(fd); + CORELOGRELSYS((CORELOG_NAME "WriteCore: Failed to open address space, %s. rc=%Rrc\n", szPath, rc)); + } + + rtCoreDumperResumeThreads(pSolCore); + return rc; +} + + +/** + * Takes a process snapshot into a passed-in core object. It has the side-effect of halting + * all threads which can lead to things like spurious wakeups of threads (if and when threads + * are ultimately resumed en-masse) already suspended while calling this function. + * + * @return IPRT status code. + * @param pSolCore Pointer to a core object. + * @param pContext Pointer to the caller context thread. + * @param pszCoreFilePath Path to the core file. If NULL is passed, the global + * path specified in RTCoreDumperSetup() would be used. + * + * @remarks Halts all threads. + */ +static int rtCoreDumperCreateCore(PRTSOLCORE pSolCore, ucontext_t *pContext, const char *pszCoreFilePath) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + AssertReturn(pContext, VERR_INVALID_POINTER); + + /* + * Initialize core structures. + */ + memset(pSolCore, 0, sizeof(RTSOLCORE)); + pSolCore->pfnReader = &ReadFileNoIntr; + pSolCore->pfnWriter = &WriteFileNoIntr; + pSolCore->fIsValid = false; + pSolCore->fdCoreFile = -1; + + PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; + pSolProc->Process = RTProcSelf(); + pSolProc->hCurThread = _lwp_self(); /* thr_self() */ + pSolProc->fdAs = -1; + pSolProc->pCurThreadCtx = pContext; + pSolProc->CoreContent = CC_CONTENT_DEFAULT; + + RTProcGetExecutablePath(pSolProc->szExecPath, sizeof(pSolProc->szExecPath)); /* this gets full path not just name */ + pSolProc->pszExecName = RTPathFilename(pSolProc->szExecPath); + + /* + * If a path has been specified, use it. Otherwise use the global path. + */ + if (!pszCoreFilePath) + { + /* + * If no output directory is specified, use current directory. + */ + if (g_szCoreDumpDir[0] == '\0') + g_szCoreDumpDir[0] = '.'; + + if (g_szCoreDumpFile[0] == '\0') + { + /* We cannot call RTPathAbs*() as they call getcwd() which calls malloc. */ + RTStrPrintf(pSolCore->szCorePath, sizeof(pSolCore->szCorePath), "%s/core.vb.%s.%d", + g_szCoreDumpDir, pSolProc->pszExecName, (int)pSolProc->Process); + } + else + RTStrPrintf(pSolCore->szCorePath, sizeof(pSolCore->szCorePath), "%s/core.vb.%s", g_szCoreDumpDir, g_szCoreDumpFile); + } + else + RTStrCopy(pSolCore->szCorePath, sizeof(pSolCore->szCorePath), pszCoreFilePath); + + CORELOG((CORELOG_NAME "CreateCore: Taking Core %s from Thread %d\n", pSolCore->szCorePath, (int)pSolProc->hCurThread)); + + /* + * Quiesce the process. + */ + int rc = rtCoreDumperSuspendThreads(pSolCore); + if (RT_SUCCESS(rc)) + { + rc = AllocMemoryArea(pSolCore); + if (RT_SUCCESS(rc)) + { + rc = ProcReadInfo(pSolCore); + if (RT_SUCCESS(rc)) + { + rc = GetOldProcessInfo(pSolCore, &pSolProc->ProcInfoOld); + if (RT_SUCCESS(rc)) + { + if (IsProcessArchNative(pSolProc)) + { + /* + * Read process status, information such as number of active LWPs will be + * invalid since we just quiesced the process. + */ + rc = ProcReadStatus(pSolCore); + if (RT_SUCCESS(rc)) + { + struct COREACCUMULATOR + { + const char *pszName; + PFNRTSOLCOREACCUMULATOR pfnAcc; + bool fOptional; + } aAccumulators[] = + { + { "ProcReadLdt", &ProcReadLdt, false }, + { "ProcReadCred", &ProcReadCred, false }, + { "ProcReadPriv", &ProcReadPriv, false }, + { "ProcReadAuxVecs", &ProcReadAuxVecs, false }, + { "ProcReadMappings", &ProcReadMappings, false }, + { "ProcReadThreads", &ProcReadThreads, false }, + { "ProcReadMiscInfo", &ProcReadMiscInfo, false } + }; + + for (unsigned i = 0; i < RT_ELEMENTS(aAccumulators); i++) + { + rc = aAccumulators[i].pfnAcc(pSolCore); + if (RT_FAILURE(rc)) + { + CORELOGRELSYS((CORELOG_NAME "CreateCore: %s failed. rc=%Rrc\n", aAccumulators[i].pszName, rc)); + if (!aAccumulators[i].fOptional) + break; + } + } + + if (RT_SUCCESS(rc)) + { + pSolCore->fIsValid = true; + return VINF_SUCCESS; + } + + FreeMemoryArea(pSolCore); + } + else + CORELOGRELSYS((CORELOG_NAME "CreateCore: ProcReadStatus failed. rc=%Rrc\n", rc)); + } + else + { + CORELOGRELSYS((CORELOG_NAME "CreateCore: IsProcessArchNative failed.\n")); + rc = VERR_BAD_EXE_FORMAT; + } + } + else + CORELOGRELSYS((CORELOG_NAME "CreateCore: GetOldProcessInfo failed. rc=%Rrc\n", rc)); + } + else + CORELOGRELSYS((CORELOG_NAME "CreateCore: ProcReadInfo failed. rc=%Rrc\n", rc)); + } + else + CORELOGRELSYS((CORELOG_NAME "CreateCore: AllocMemoryArea failed. rc=%Rrc\n", rc)); + + /* + * Resume threads on failure. + */ + rtCoreDumperResumeThreads(pSolCore); + } + else + CORELOG((CORELOG_NAME "CreateCore: SuspendAllThreads failed. Thread bomb!?! rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * Destroy an existing core object. + * + * @param pSolCore Pointer to the core object. + * + * @return IPRT status code. + */ +static int rtCoreDumperDestroyCore(PRTSOLCORE pSolCore) +{ + AssertReturn(pSolCore, VERR_INVALID_POINTER); + if (!pSolCore->fIsValid) + return VERR_INVALID_STATE; + + FreeMemoryArea(pSolCore); + pSolCore->fIsValid = false; + return VINF_SUCCESS; +} + + +/** + * Takes a core dump. + * + * @param pContext The context of the caller. + * @param pszOutputFile Path of the core file. If NULL is passed, the + * global path passed in RTCoreDumperSetup will + * be used. + * @returns IPRT status code. + */ +static int rtCoreDumperTakeDump(ucontext_t *pContext, const char *pszOutputFile) +{ + if (!pContext) + { + CORELOGRELSYS((CORELOG_NAME "TakeDump: Missing context.\n")); + return VERR_INVALID_POINTER; + } + + /* + * Take a snapshot, then dump core to disk, all threads except this one are halted + * from before taking the snapshot until writing the core is completely finished. + * Any errors would resume all threads if they were halted. + */ + RTSOLCORE SolCore; + RT_ZERO(SolCore); + int rc = rtCoreDumperCreateCore(&SolCore, pContext, pszOutputFile); + if (RT_SUCCESS(rc)) + { + rc = rtCoreDumperWriteCore(&SolCore, &WriteFileNoIntr); + if (RT_SUCCESS(rc)) + CORELOGRELSYS((CORELOG_NAME "Core dumped in %s\n", SolCore.szCorePath)); + else + CORELOGRELSYS((CORELOG_NAME "TakeDump: WriteCore failed. szCorePath=%s rc=%Rrc\n", SolCore.szCorePath, rc)); + + rtCoreDumperDestroyCore(&SolCore); + } + else + CORELOGRELSYS((CORELOG_NAME "TakeDump: CreateCore failed. rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * The signal handler that will be invoked to take core dumps. + * + * @param Sig The signal that invoked us. + * @param pSigInfo The signal information. + * @param pvArg Opaque pointer to the caller context structure, + * this cannot be NULL. + */ +static void rtCoreDumperSignalHandler(int Sig, siginfo_t *pSigInfo, void *pvArg) +{ + CORELOG((CORELOG_NAME "SignalHandler Sig=%d pvArg=%p\n", Sig, pvArg)); + + RTNATIVETHREAD hCurNativeThread = RTThreadNativeSelf(); + int rc = VERR_GENERAL_FAILURE; + bool fCallSystemDump = false; + bool fRc; + ASMAtomicCmpXchgHandle(&g_CoreDumpThread, hCurNativeThread, NIL_RTNATIVETHREAD, fRc); + if (fRc) + { + rc = rtCoreDumperTakeDump((ucontext_t *)pvArg, NULL /* Use Global Core filepath */); + ASMAtomicWriteHandle(&g_CoreDumpThread, NIL_RTNATIVETHREAD); + + if (RT_FAILURE(rc)) + CORELOGRELSYS((CORELOG_NAME "TakeDump failed! rc=%Rrc\n", rc)); + } + else if (Sig == SIGSEGV || Sig == SIGBUS || Sig == SIGTRAP) + { + /* + * Core dumping is already in progress and we've somehow ended up being + * signalled again. + */ + rc = VERR_INTERNAL_ERROR; + + /* + * If our dumper has crashed. No point in waiting, trigger the system one. + * Wait only when the dumping thread is not the one generating this signal. + */ + RTNATIVETHREAD hNativeDumperThread; + ASMAtomicReadHandle(&g_CoreDumpThread, &hNativeDumperThread); + if (hNativeDumperThread == RTThreadNativeSelf()) + { + CORELOGRELSYS((CORELOG_NAME "SignalHandler: Core dumper (thread %u) crashed Sig=%d. Triggering system dump\n", + RTThreadSelf(), Sig)); + fCallSystemDump = true; + } + else + { + /* + * Some other thread in the process is triggering a crash, wait a while + * to let our core dumper finish, on timeout trigger system dump. + */ + CORELOGRELSYS((CORELOG_NAME "SignalHandler: Core dump already in progress! Waiting a while for completion Sig=%d.\n", + Sig)); + int64_t iTimeout = 16000; /* timeout (ms) */ + for (;;) + { + ASMAtomicReadHandle(&g_CoreDumpThread, &hNativeDumperThread); + if (hNativeDumperThread == NIL_RTNATIVETHREAD) + break; + RTThreadSleep(200); + iTimeout -= 200; + if (iTimeout <= 0) + break; + } + if (iTimeout <= 0) + { + fCallSystemDump = true; + CORELOGRELSYS((CORELOG_NAME "SignalHandler: Core dumper seems to be stuck. Signalling new signal %d\n", Sig)); + } + } + } + + if (Sig == SIGSEGV || Sig == SIGBUS || Sig == SIGTRAP) + { + /* + * Reset signal handlers, we're not a live core we will be blown away + * one way or another. + */ + signal(SIGSEGV, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGTRAP, SIG_DFL); + + /* + * Hard terminate the process if this is not a live dump without invoking + * the system core dumping behaviour. + */ + if (RT_SUCCESS(rc)) + raise(SIGKILL); + + /* + * Something went wrong, fall back to the system core dumper. + */ + if (fCallSystemDump) + abort(); + } +} + + +RTDECL(int) RTCoreDumperTakeDump(const char *pszOutputFile, bool fLiveCore) +{ + ucontext_t Context; + int rc = getcontext(&Context); + if (!rc) + { + /* + * Block SIGSEGV and co. while we write the core. + */ + sigset_t SigSet, OldSigSet; + sigemptyset(&SigSet); + sigaddset(&SigSet, SIGSEGV); + sigaddset(&SigSet, SIGBUS); + sigaddset(&SigSet, SIGTRAP); + sigaddset(&SigSet, SIGUSR2); + pthread_sigmask(SIG_BLOCK, &SigSet, &OldSigSet); + rc = rtCoreDumperTakeDump(&Context, pszOutputFile); + if (RT_FAILURE(rc)) + CORELOGRELSYS(("RTCoreDumperTakeDump: rtCoreDumperTakeDump failed rc=%Rrc\n", rc)); + + if (!fLiveCore) + { + signal(SIGSEGV, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGTRAP, SIG_DFL); + if (RT_SUCCESS(rc)) + raise(SIGKILL); + else + abort(); + } + pthread_sigmask(SIG_SETMASK, &OldSigSet, NULL); + } + else + { + CORELOGRELSYS(("RTCoreDumperTakeDump: getcontext failed rc=%d.\n", rc)); + rc = VERR_INVALID_CONTEXT; + } + + return rc; +} + + +RTDECL(int) RTCoreDumperSetup(const char *pszOutputDir, uint32_t fFlags) +{ + /* + * Validate flags. + */ + AssertReturn(fFlags, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~( RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP + | RTCOREDUMPER_FLAGS_LIVE_CORE)), + VERR_INVALID_PARAMETER); + + + /* + * Setup/change the core dump directory if specified. + */ + RT_ZERO(g_szCoreDumpDir); + if (pszOutputDir) + { + if (!RTDirExists(pszOutputDir)) + return VERR_NOT_A_DIRECTORY; + RTStrCopy(g_szCoreDumpDir, sizeof(g_szCoreDumpDir), pszOutputDir); + } + + /* + * Install core dump signal handler only if the flags changed or if it's the first time. + */ + if ( ASMAtomicReadBool(&g_fCoreDumpSignalSetup) == false + || ASMAtomicReadU32(&g_fCoreDumpFlags) != fFlags) + { + struct sigaction sigAct; + RT_ZERO(sigAct); + sigAct.sa_sigaction = &rtCoreDumperSignalHandler; + + if ( (fFlags & RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP) + && !(g_fCoreDumpFlags & RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP)) + { + sigemptyset(&sigAct.sa_mask); + sigAct.sa_flags = SA_RESTART | SA_SIGINFO | SA_NODEFER; + sigaction(SIGSEGV, &sigAct, NULL); + sigaction(SIGBUS, &sigAct, NULL); + sigaction(SIGTRAP, &sigAct, NULL); + } + + if ( fFlags & RTCOREDUMPER_FLAGS_LIVE_CORE + && !(g_fCoreDumpFlags & RTCOREDUMPER_FLAGS_LIVE_CORE)) + { + sigfillset(&sigAct.sa_mask); /* Block all signals while in it's signal handler */ + sigAct.sa_flags = SA_RESTART | SA_SIGINFO; + sigaction(SIGUSR2, &sigAct, NULL); + } + + ASMAtomicWriteU32(&g_fCoreDumpFlags, fFlags); + ASMAtomicWriteBool(&g_fCoreDumpSignalSetup, true); + } + + return VINF_SUCCESS; +} + + +RTDECL(int) RTCoreDumperDisable(void) +{ + /* + * Remove core dump signal handler & reset variables. + */ + if (ASMAtomicReadBool(&g_fCoreDumpSignalSetup) == true) + { + signal(SIGSEGV, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGTRAP, SIG_DFL); + signal(SIGUSR2, SIG_DFL); + ASMAtomicWriteBool(&g_fCoreDumpSignalSetup, false); + } + + RT_ZERO(g_szCoreDumpDir); + RT_ZERO(g_szCoreDumpFile); + ASMAtomicWriteU32(&g_fCoreDumpFlags, 0); + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/solaris/coredumper-solaris.h b/src/VBox/Runtime/r3/solaris/coredumper-solaris.h new file mode 100644 index 00000000..4583eb4d --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/coredumper-solaris.h @@ -0,0 +1,177 @@ +/* $Id: coredumper-solaris.h $ */ +/** @file + * IPRT - Custom Core Dumper, Solaris. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_solaris_coredumper_solaris_h +#define IPRT_INCLUDED_SRC_r3_solaris_coredumper_solaris_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> + +#ifdef RT_OS_SOLARIS +# if defined(RT_ARCH_X86) && _FILE_OFFSET_BITS==64 +/* + * Solaris' procfs cannot be used with large file environment in 32-bit. + */ +# undef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 32 +# include <procfs.h> +# include <sys/procfs.h> +# include <sys/old_procfs.h> +# undef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +#else +# include <procfs.h> +# include <sys/procfs.h> +# include <sys/old_procfs.h> +#endif +# include <limits.h> +# include <thread.h> +# include <sys/auxv.h> +# include <sys/lwp.h> +# include <sys/zone.h> +# include <sys/utsname.h> + +#ifdef RT_ARCH_AMD64 +# define _ELF64 +# undef _ELF32_COMPAT +#endif +# include <sys/corectl.h> +#endif + + +#ifdef RT_OS_SOLARIS +/** + * Memory mapping descriptor employed by the solaris core dumper. + */ +typedef struct RTSOLCOREMAPINFO +{ + prmap_t pMap; /**< Proc description of this mapping */ + int fError; /**< Any error reading this mapping (errno) */ + struct RTSOLCOREMAPINFO *pNext; /**< Pointer to the next mapping */ +} RTSOLCOREMAPINFO; +/** Pointer to a solaris memory mapping descriptor. */ +typedef RTSOLCOREMAPINFO *PRTSOLCOREMAPINFO; + +/** + * Whether this is an old or new style solaris core. + */ +typedef enum RTSOLCORETYPE +{ + enmOldEra = 0x01d, /**< old */ + enmNewEra = 0x5c1f1 /**< sci-fi */ +} RTSOLCORETYPE; + +/** + * Per-Thread information employed by the solaris core dumper. + */ +typedef struct RTSOLCORETHREADINFO +{ + lwpsinfo_t Info; /**< Proc description of this thread */ + lwpstatus_t *pStatus; /**< Proc description of this thread's status (can be NULL, zombie lwp) */ + struct RTSOLCORETHREADINFO *pNext; /**< Pointer to the next thread */ +} RTSOLCORETHREADINFO; +typedef RTSOLCORETHREADINFO *PRTSOLCORETHREADINFO; +#endif + + +/** + * Current (also the core target) process information. + */ +typedef struct RTSOLCOREPROCESS +{ + RTPROCESS Process; /**< The pid of the process */ + char szExecPath[PATH_MAX]; /**< Path of the executable */ + char *pszExecName; /**< Name of the executable file */ +#ifdef RT_OS_SOLARIS + void *pvProcInfo; /**< Process info. */ + size_t cbProcInfo; /**< Size of the process info. */ + prpsinfo_t ProcInfoOld; /**< Process info. Older version (for GDB compat.) */ + pstatus_t ProcStatus; /**< Process status info. */ + thread_t hCurThread; /**< The current thread */ + ucontext_t *pCurThreadCtx; /**< Context info. of current thread before starting to dump */ + int fdAs; /**< proc/pid/as file handle */ + auxv_t *pAuxVecs; /**< Aux vector of process */ + int cAuxVecs; /**< Number of aux vector entries */ + PRTSOLCOREMAPINFO pMapInfoHead; /**< Pointer to the head of list of mappings */ + uint32_t cMappings; /**< Number of mappings (count of pMapInfoHead list) */ + PRTSOLCORETHREADINFO pThreadInfoHead; /**< Pointer to the head of list of threads */ + uint64_t cThreads; /**< Number of threads (count of pThreadInfoHead list) */ + char szPlatform[SYS_NMLN]; /**< Platform name */ + char szZoneName[ZONENAME_MAX]; /**< Zone name */ + struct utsname UtsName; /**< UTS name */ + void *pvCred; /**< Process credential info. */ + size_t cbCred; /**< Size of process credential info. */ + void *pvLdt; /**< Process LDT info. */ + size_t cbLdt; /**< Size of the LDT info. */ + prpriv_t *pPriv; /**< Process privilege info. */ + size_t cbPriv; /**< Size of process privilege info. */ + const priv_impl_info_t *pcPrivImpl; /**< Process privilege implementation info. (opaque handle) */ + core_content_t CoreContent; /**< What information goes in the core */ +#else +# error Port Me! +#endif + +} RTSOLCOREPROCESS; +typedef RTSOLCOREPROCESS *PRTSOLCOREPROCESS; + +typedef int (*PFNRTCOREREADER)(int fdFile, void *pv, size_t cb); +typedef int (*PFNRTCOREWRITER)(int fdhFile, const void *pcv, size_t cb); + +/** + * The solaris core file object. + */ +typedef struct RTSOLCORE +{ + char szCorePath[PATH_MAX]; /**< Path of the core file */ + RTSOLCOREPROCESS SolProc; /**< Current process information */ + void *pvCore; /**< Pointer to memory area during dumping */ + size_t cbCore; /**< Size of memory area during dumping */ + void *pvFree; /**< Pointer to base of free range in preallocated memory area */ + bool fIsValid; /**< Whether core information has been fully collected */ + PFNRTCOREREADER pfnReader; /**< Reader function */ + PFNRTCOREWRITER pfnWriter; /**< Writer function */ + int fdCoreFile; /**< Core file (used only while writing the core) */ + RTFOFF offWrite; /**< Segment/section offset (used only while writing the core) */ +} RTSOLCORE; +typedef RTSOLCORE *PRTSOLCORE; + +typedef int (*PFNRTSOLCOREACCUMULATOR)(PRTSOLCORE pSolCore); +typedef int (*PFNRTSOLCORETHREADWORKER)(PRTSOLCORE pSolCore, void *pvThreadInfo); + +#endif /* !IPRT_INCLUDED_SRC_r3_solaris_coredumper_solaris_h */ + diff --git a/src/VBox/Runtime/r3/solaris/fileaio-solaris.cpp b/src/VBox/Runtime/r3/solaris/fileaio-solaris.cpp new file mode 100644 index 00000000..f129d4fc --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/fileaio-solaris.cpp @@ -0,0 +1,574 @@ +/* $Id: fileaio-solaris.cpp $ */ +/** @file + * IPRT - File async I/O, native implementation for the Solaris host platform. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include <iprt/asm.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/fileaio.h" + +#include <port.h> +#include <aio.h> +#include <errno.h> +#include <unistd.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Async I/O completion context state. + */ +typedef struct RTFILEAIOCTXINTERNAL +{ + /** Handle to the port. */ + int iPort; + /** Current number of requests active on this context. */ + volatile int32_t cRequests; + /** Flags given during creation. */ + uint32_t fFlags; + /** Magic value (RTFILEAIOCTX_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOCTXINTERNAL; +/** Pointer to an internal context structure. */ +typedef RTFILEAIOCTXINTERNAL *PRTFILEAIOCTXINTERNAL; + +/** + * Async I/O request state. + */ +typedef struct RTFILEAIOREQINTERNAL +{ + /** The aio control block. Must be the FIRST + * element. */ + struct aiocb AioCB; + /** Current state the request is in. */ + RTFILEAIOREQSTATE enmState; + /** Flag whether this is a flush request. */ + bool fFlush; + /** Port notifier object to associate a request to a port. */ + port_notify_t PortNotifier; + /** Opaque user data. */ + void *pvUser; + /** Completion context we are assigned to. */ + PRTFILEAIOCTXINTERNAL pCtxInt; + /** Magic value (RTFILEAIOREQ_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOREQINTERNAL; +/** Pointer to an internal request structure. */ +typedef RTFILEAIOREQINTERNAL *PRTFILEAIOREQINTERNAL; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max number of events to get in one call. */ +#define AIO_MAXIMUM_REQUESTS_PER_CONTEXT 64 +/** Id for the wakeup event. */ +#define AIO_CONTEXT_WAKEUP_EVENT 1 + +RTR3DECL(int) RTFileAioGetLimits(PRTFILEAIOLIMITS pAioLimits) +{ + int rcBSD = 0; + AssertPtrReturn(pAioLimits, VERR_INVALID_POINTER); + + /* No limits known. */ + pAioLimits->cReqsOutstandingMax = RTFILEAIO_UNLIMITED_REQS; + pAioLimits->cbBufferAlignment = 0; + + return VINF_SUCCESS; +} + +RTR3DECL(int) RTFileAioReqCreate(PRTFILEAIOREQ phReq) +{ + AssertPtrReturn(phReq, VERR_INVALID_POINTER); + + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOREQINTERNAL)); + if (RT_UNLIKELY(!pReqInt)) + return VERR_NO_MEMORY; + + /* Ininitialize static parts. */ + pReqInt->AioCB.aio_sigevent.sigev_notify = SIGEV_PORT; + pReqInt->AioCB.aio_sigevent.sigev_value.sival_ptr = &pReqInt->PortNotifier; + pReqInt->PortNotifier.portnfy_user = pReqInt; + pReqInt->pCtxInt = NULL; + pReqInt->u32Magic = RTFILEAIOREQ_MAGIC; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + *phReq = (RTFILEAIOREQ)pReqInt; + + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioReqDestroy(RTFILEAIOREQ hReq) +{ + /* + * Validate the handle and ignore nil. + */ + if (hReq == NIL_RTFILEAIOREQ) + return VINF_SUCCESS; + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + + /* + * Trash the magic and free it. + */ + ASMAtomicUoWriteU32(&pReqInt->u32Magic, ~RTFILEAIOREQ_MAGIC); + RTMemFree(pReqInt); + return VINF_SUCCESS; +} + +/** + * Worker setting up the request. + */ +DECLINLINE(int) rtFileAioReqPrepareTransfer(RTFILEAIOREQ hReq, RTFILE hFile, + unsigned uTransferDirection, + RTFOFF off, void *pvBuf, size_t cbTransfer, + void *pvUser) +{ + /* + * Validate the input. + */ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + Assert(hFile != NIL_RTFILE); + AssertPtr(pvBuf); + Assert(off >= 0); + Assert(cbTransfer > 0); + + pReqInt->AioCB.aio_lio_opcode = uTransferDirection; + pReqInt->AioCB.aio_fildes = RTFileToNative(hFile); + pReqInt->AioCB.aio_offset = off; + pReqInt->AioCB.aio_nbytes = cbTransfer; + pReqInt->AioCB.aio_buf = pvBuf; + pReqInt->fFlush = false; + pReqInt->pvUser = pvUser; + pReqInt->pCtxInt = NULL; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioReqPrepareRead(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void *pvBuf, size_t cbRead, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LIO_READ, + off, pvBuf, cbRead, pvUser); +} + +RTDECL(int) RTFileAioReqPrepareWrite(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void const *pvBuf, size_t cbWrite, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, LIO_WRITE, + off, (void *)pvBuf, cbWrite, pvUser); +} + +RTDECL(int) RTFileAioReqPrepareFlush(RTFILEAIOREQ hReq, RTFILE hFile, void *pvUser) +{ + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)hReq; + + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + Assert(hFile != NIL_RTFILE); + + pReqInt->fFlush = true; + pReqInt->AioCB.aio_fildes = RTFileToNative(hFile); + pReqInt->AioCB.aio_offset = 0; + pReqInt->AioCB.aio_nbytes = 0; + pReqInt->AioCB.aio_buf = NULL; + pReqInt->pvUser = pvUser; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + + return VINF_SUCCESS; +} + +RTDECL(void *) RTFileAioReqGetUser(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN_RC(pReqInt, NULL); + + return pReqInt->pvUser; +} + +RTDECL(int) RTFileAioReqCancel(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_NOT_SUBMITTED); + + int rcSolaris = aio_cancel(pReqInt->AioCB.aio_fildes, &pReqInt->AioCB); + + if (rcSolaris == AIO_CANCELED) + { + /* + * Decrement request count because the request will never arrive at the + * completion port. + */ + AssertMsg(RT_VALID_PTR(pReqInt->pCtxInt), ("Invalid state. Request was canceled but wasn't submitted\n")); + + ASMAtomicDecS32(&pReqInt->pCtxInt->cRequests); + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + return VINF_SUCCESS; + } + else if (rcSolaris == AIO_ALLDONE) + return VERR_FILE_AIO_COMPLETED; + else if (rcSolaris == AIO_NOTCANCELED) + return VERR_FILE_AIO_IN_PROGRESS; + else + return RTErrConvertFromErrno(errno); +} + +RTDECL(int) RTFileAioReqGetRC(RTFILEAIOREQ hReq, size_t *pcbTransfered) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, PREPARED, VERR_FILE_AIO_NOT_SUBMITTED); + AssertPtrNull(pcbTransfered); + + int rcSol = aio_error(&pReqInt->AioCB); + Assert(rcSol != EINPROGRESS); /* Handled by our own state handling. */ + + if (rcSol == 0) + { + if (pcbTransfered) + *pcbTransfered = aio_return(&pReqInt->AioCB); + return VINF_SUCCESS; + } + + /* An error occurred. */ + return RTErrConvertFromErrno(rcSol); +} + +RTDECL(int) RTFileAioCtxCreate(PRTFILEAIOCTX phAioCtx, uint32_t cAioReqsMax, + uint32_t fFlags) +{ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt; + AssertPtrReturn(phAioCtx, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTFILEAIOCTX_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + pCtxInt = (PRTFILEAIOCTXINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOCTXINTERNAL)); + if (RT_UNLIKELY(!pCtxInt)) + return VERR_NO_MEMORY; + + /* Init the event handle. */ + pCtxInt->iPort = port_create(); + if (RT_LIKELY(pCtxInt->iPort > 0)) + { + pCtxInt->fFlags = fFlags; + pCtxInt->u32Magic = RTFILEAIOCTX_MAGIC; + *phAioCtx = (RTFILEAIOCTX)pCtxInt; + } + else + { + RTMemFree(pCtxInt); + rc = RTErrConvertFromErrno(errno); + } + + return rc; +} + +RTDECL(int) RTFileAioCtxDestroy(RTFILEAIOCTX hAioCtx) +{ + /* Validate the handle and ignore nil. */ + if (hAioCtx == NIL_RTFILEAIOCTX) + return VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + /* Cannot destroy a busy context. */ + if (RT_UNLIKELY(pCtxInt->cRequests)) + return VERR_FILE_AIO_BUSY; + + close(pCtxInt->iPort); + ASMAtomicUoWriteU32(&pCtxInt->u32Magic, RTFILEAIOCTX_MAGIC_DEAD); + RTMemFree(pCtxInt); + + return VINF_SUCCESS; +} + +RTDECL(uint32_t) RTFileAioCtxGetMaxReqCount(RTFILEAIOCTX hAioCtx) +{ + return RTFILEAIO_UNLIMITED_REQS; +} + +RTDECL(int) RTFileAioCtxAssociateWithFile(RTFILEAIOCTX hAioCtx, RTFILE hFile) +{ + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioCtxSubmit(RTFILEAIOCTX hAioCtx, PRTFILEAIOREQ pahReqs, size_t cReqs) +{ + /* + * Parameter validation. + */ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertReturn(cReqs > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + size_t i = cReqs; + + do + { + int rcSol = 0; + size_t cReqsSubmit = 0; + PRTFILEAIOREQINTERNAL pReqInt; + + while(i-- > 0) + { + pReqInt = pahReqs[i]; + if (RTFILEAIOREQ_IS_NOT_VALID(pReqInt)) + { + /* Undo everything and stop submitting. */ + for (size_t iUndo = 0; iUndo < i; iUndo++) + { + pReqInt = pahReqs[iUndo]; + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + pReqInt->pCtxInt = NULL; + } + rc = VERR_INVALID_HANDLE; + break; + } + + pReqInt->PortNotifier.portnfy_port = pCtxInt->iPort; + pReqInt->pCtxInt = pCtxInt; + RTFILEAIOREQ_SET_STATE(pReqInt, SUBMITTED); + + if (pReqInt->fFlush) + break; + + cReqsSubmit++; + } + + if (cReqsSubmit) + { + rcSol = lio_listio(LIO_NOWAIT, (struct aiocb **)pahReqs, cReqsSubmit, NULL); + if (RT_UNLIKELY(rcSol < 0)) + { + if (rcSol == EAGAIN) + rc = VERR_FILE_AIO_INSUFFICIENT_RESSOURCES; + else + rc = RTErrConvertFromErrno(errno); + + /* Check which requests got actually submitted and which not. */ + for (i = 0; i < cReqs; i++) + { + pReqInt = pahReqs[i]; + rcSol = aio_error(&pReqInt->AioCB); + if (rcSol == EINVAL) + { + /* Was not submitted. */ + RTFILEAIOREQ_SET_STATE(pReqInt, PREPARED); + pReqInt->pCtxInt = NULL; + } + else if (rcSol != EINPROGRESS) + { + /* The request encountered an error. */ + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + } + } + break; + } + + ASMAtomicAddS32(&pCtxInt->cRequests, cReqsSubmit); + cReqs -= cReqsSubmit; + pahReqs += cReqsSubmit; + } + + if (cReqs) + { + pReqInt = pahReqs[0]; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + + /* + * If there are still requests left we have a flush request. + * lio_listio does not work with this requests so + * we have to use aio_fsync directly. + */ + rcSol = aio_fsync(O_SYNC, &pReqInt->AioCB); + if (RT_UNLIKELY(rcSol < 0)) + { + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + rc = RTErrConvertFromErrno(errno); + break; + } + + ASMAtomicIncS32(&pCtxInt->cRequests); + cReqs--; + pahReqs++; + } + } while (cReqs); + + return rc; +} + +RTDECL(int) RTFileAioCtxWait(RTFILEAIOCTX hAioCtx, size_t cMinReqs, RTMSINTERVAL cMillies, + PRTFILEAIOREQ pahReqs, size_t cReqs, uint32_t *pcReqs) +{ + int rc = VINF_SUCCESS; + int cRequestsCompleted = 0; + + /* + * Validate the parameters, making sure to always set pcReqs. + */ + AssertPtrReturn(pcReqs, VERR_INVALID_POINTER); + *pcReqs = 0; /* always set */ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + AssertReturn(cReqs != 0, VERR_INVALID_PARAMETER); + AssertReturn(cReqs >= cMinReqs, VERR_OUT_OF_RANGE); + + if ( RT_UNLIKELY(ASMAtomicReadS32(&pCtxInt->cRequests) == 0) + && !(pCtxInt->fFlags & RTFILEAIOCTX_FLAGS_WAIT_WITHOUT_PENDING_REQUESTS)) + return VERR_FILE_AIO_NO_REQUEST; + + /* + * Convert the timeout if specified. + */ + struct timespec *pTimeout = NULL; + struct timespec Timeout = {0,0}; + uint64_t StartNanoTS = 0; + if (cMillies != RT_INDEFINITE_WAIT) + { + Timeout.tv_sec = cMillies / 1000; + Timeout.tv_nsec = cMillies % 1000 * 1000000; + pTimeout = &Timeout; + StartNanoTS = RTTimeNanoTS(); + } + + /* Wait for at least one. */ + if (!cMinReqs) + cMinReqs = 1; + + while ( cMinReqs + && RT_SUCCESS_NP(rc)) + { + port_event_t aPortEvents[AIO_MAXIMUM_REQUESTS_PER_CONTEXT]; + uint_t cRequests = cMinReqs; + int cRequestsToWait = RT_MIN(cReqs, AIO_MAXIMUM_REQUESTS_PER_CONTEXT); + int rcSol; + uint64_t StartTime; + + rcSol = port_getn(pCtxInt->iPort, &aPortEvents[0], cRequestsToWait, &cRequests, pTimeout); + + if (RT_UNLIKELY(rcSol < 0)) + rc = RTErrConvertFromErrno(errno); + + /* Process received events. */ + for (uint_t i = 0; i < cRequests; i++) + { + if (aPortEvents[i].portev_source == PORT_SOURCE_ALERT) + { + Assert(aPortEvents[i].portev_events == AIO_CONTEXT_WAKEUP_EVENT); + rc = VERR_INTERRUPTED; /* We've got interrupted. */ + /* Reset the port. */ + port_alert(pCtxInt->iPort, PORT_ALERT_SET, 0, NULL); + } + else + { + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)aPortEvents[i].portev_user; + AssertPtr(pReqInt); + Assert(pReqInt->u32Magic == RTFILEAIOREQ_MAGIC); + + /* A request has finished. */ + pahReqs[cRequestsCompleted++] = pReqInt; + + /* Mark the request as finished. */ + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + } + } + + /* + * Done Yet? If not advance and try again. + */ + if (cRequests >= cMinReqs) + break; + cMinReqs -= cRequests; + cReqs -= cRequests; + + if (cMillies != RT_INDEFINITE_WAIT) + { + uint64_t NanoTS = RTTimeNanoTS(); + uint64_t cMilliesElapsed = (NanoTS - StartNanoTS) / 1000000; + + /* The syscall supposedly updates it, but we're paranoid. :-) */ + if (cMilliesElapsed < cMillies) + { + Timeout.tv_sec = (cMillies - (RTMSINTERVAL)cMilliesElapsed) / 1000; + Timeout.tv_nsec = (cMillies - (RTMSINTERVAL)cMilliesElapsed) % 1000 * 1000000; + } + else + { + Timeout.tv_sec = 0; + Timeout.tv_nsec = 0; + } + } + } + + /* + * Update the context state and set the return value. + */ + *pcReqs = cRequestsCompleted; + ASMAtomicSubS32(&pCtxInt->cRequests, cRequestsCompleted); + + return rc; +} + +RTDECL(int) RTFileAioCtxWakeup(RTFILEAIOCTX hAioCtx) +{ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + rc = port_alert(pCtxInt->iPort, PORT_ALERT_UPDATE, AIO_CONTEXT_WAKEUP_EVENT, NULL); + if (RT_UNLIKELY((rc < 0) && (errno != EBUSY))) + return RTErrConvertFromErrno(errno); + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/solaris/krnlmod-solaris.cpp b/src/VBox/Runtime/r3/solaris/krnlmod-solaris.cpp new file mode 100644 index 00000000..bb02ac55 --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/krnlmod-solaris.cpp @@ -0,0 +1,358 @@ +/* $Id: krnlmod-solaris.cpp $ */ +/** @file + * IPRT - Kernel module, Linux. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <iprt/krnlmod.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/types.h> + +#include <iprt/stream.h> + +#include <sys/modctl.h> +#include <errno.h> + +/** + * Internal kernel information record state. + */ +typedef struct RTKRNLMODINFOINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Load address of the kernel module. */ + RTR0UINTPTR uLoadAddr; + /** Size of the kernel module. */ + size_t cbKrnlMod; + /** Size of the name in characters including the zero terminator. */ + size_t cchName; + /** Module name - variable in size. */ + char achName[1]; +} RTKRNLMODINFOINT; +/** Pointer to the internal kernel module information record. */ +typedef RTKRNLMODINFOINT *PRTKRNLMODINFOINT; +/** Pointer to a const internal kernel module information record. */ +typedef const RTKRNLMODINFOINT *PCRTKRNLMODINFOINT; + + + +/** + * Destroy the given kernel module information record. + * + * @param pThis The record to destroy. + */ +static void rtKrnlModInfoDestroy(PRTKRNLMODINFOINT pThis) +{ + RTMemFree(pThis); +} + + +/** + * Creates a new kernel module information record for the given module. + * + * @returns IPRT status code. + * @param pModInfo The Solaris kernel module information. + * @param phKrnlModInfo Where to store the handle to the kernel module information record + * on success. + */ +static int rtKrnlModSolInfoCreate(struct modinfo *pModInfo, PRTKRNLMODINFO phKrnlModInfo) +{ + int rc = VINF_SUCCESS; + size_t cchName = strlen(&pModInfo->mi_name[0]) + 1; + PRTKRNLMODINFOINT pThis = (PRTKRNLMODINFOINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTKRNLMODINFOINT, achName[cchName])); + if (RT_LIKELY(pThis)) + { + memcpy(&pThis->achName[0], &pModInfo->mi_name[0], cchName); + pThis->cchName = cchName; + pThis->cRefs = 1; + pThis->cbKrnlMod = pModInfo->mi_size; + pThis->uLoadAddr = (RTR0UINTPTR)pModInfo->mi_base; + + *phKrnlModInfo = pThis; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTKrnlModQueryLoaded(const char *pszName, bool *pfLoaded) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pfLoaded, VERR_INVALID_POINTER); + + RTKRNLMODINFO hKrnlModInfo = NIL_RTKRNLMODINFO; + int rc = RTKrnlModLoadedQueryInfo(pszName, &hKrnlModInfo); + if (RT_SUCCESS(rc)) + { + *pfLoaded = true; + RTKrnlModInfoRelease(hKrnlModInfo); + } + else if (rc == VERR_NOT_FOUND) + { + *pfLoaded = false; + rc = VINF_SUCCESS; + } + + return rc; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfo(const char *pszName, PRTKRNLMODINFO phKrnlModInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(phKrnlModInfo, VERR_INVALID_POINTER); + + int rc = VERR_NOT_FOUND; + int iId = -1; + struct modinfo ModInfo; + + ModInfo.mi_info = MI_INFO_ALL | MI_INFO_CNT; + ModInfo.mi_id = iId; + ModInfo.mi_nextid = iId; + do + { + int rcSol = modctl(MODINFO, iId, &ModInfo); + if (rcSol < 0) + { + rc = RTErrConvertFromErrno(errno); + break; + } + + if (ModInfo.mi_id != -1) + { + ModInfo.mi_name[MODMAXNAMELEN - 1] = '\0'; /* Paranoia. */ + if (!RTStrCmp(pszName, &ModInfo.mi_name[0])) + { + rc = rtKrnlModSolInfoCreate(&ModInfo, phKrnlModInfo); + break; + } + } + + iId = ModInfo.mi_id; + } while (iId != -1); + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModLoadedGetCount(void) +{ + uint32_t cKmodsLoaded = 0; + int iId = -1; + struct modinfo ModInfo; + + ModInfo.mi_info = MI_INFO_ALL | MI_INFO_CNT; + ModInfo.mi_id = iId; + ModInfo.mi_nextid = iId; + do + { + int rcSol = modctl(MODINFO, iId, &ModInfo); + if (rcSol < 0) + break; + + cKmodsLoaded++; + + iId = ModInfo.mi_id; + } while (iId != -1); + + return cKmodsLoaded; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfoAll(PRTKRNLMODINFO pahKrnlModInfo, uint32_t cEntriesMax, + uint32_t *pcEntries) +{ + if (cEntriesMax > 0) + AssertPtrReturn(pahKrnlModInfo, VERR_INVALID_POINTER); + + uint32_t cKmodsLoaded = RTKrnlModLoadedGetCount(); + if (cEntriesMax < cKmodsLoaded) + { + if (*pcEntries) + *pcEntries = cKmodsLoaded; + return VERR_BUFFER_OVERFLOW; + } + + int rc = VINF_SUCCESS; + int iId = -1; + unsigned idxKrnlModInfo = 0; + struct modinfo ModInfo; + + ModInfo.mi_info = MI_INFO_ALL | MI_INFO_CNT; + ModInfo.mi_id = iId; + ModInfo.mi_nextid = iId; + do + { + int rcSol = modctl(MODINFO, iId, &ModInfo); + if (rcSol < 0) + { + rc = RTErrConvertFromErrno(errno); + if (rc == VERR_INVALID_PARAMETER && idxKrnlModInfo > 0) + rc = VINF_SUCCESS; + break; + } + + ModInfo.mi_name[MODMAXNAMELEN - 1] = '\0'; /* Paranoia. */ + rc = rtKrnlModSolInfoCreate(&ModInfo, &pahKrnlModInfo[idxKrnlModInfo]); + if (RT_SUCCESS(rc)) + idxKrnlModInfo++; + + iId = ModInfo.mi_id; + } while (iId != -1); + + if (RT_FAILURE(rc)) + { + /* Rollback */ + while (idxKrnlModInfo-- > 0) + RTKrnlModInfoRelease(pahKrnlModInfo[idxKrnlModInfo]); + } + else if (pcEntries) + *pcEntries = idxKrnlModInfo; + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModInfoRetain(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoRelease(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + if (!pThis) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtKrnlModInfoDestroy(pThis); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoGetRefCnt(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return 0; +} + + +RTDECL(const char *) RTKrnlModInfoGetName(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + return &pThis->achName[0]; +} + + +RTDECL(const char *) RTKrnlModInfoGetFilePath(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + return NULL; +} + + +RTDECL(size_t) RTKrnlModInfoGetSize(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->cbKrnlMod; +} + + +RTDECL(RTR0UINTPTR) RTKrnlModInfoGetLoadAddr(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->uLoadAddr; +} + + +RTDECL(int) RTKrnlModInfoQueryRefModInfo(RTKRNLMODINFO hKrnlModInfo, uint32_t idx, + PRTKRNLMODINFO phKrnlModInfoRef) +{ + RT_NOREF3(hKrnlModInfo, idx, phKrnlModInfoRef); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(int) RTKrnlModLoadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModLoadByPath(const char *pszPath) +{ + AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModUnloadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} diff --git a/src/VBox/Runtime/r3/solaris/mp-solaris.cpp b/src/VBox/Runtime/r3/solaris/mp-solaris.cpp new file mode 100644 index 00000000..b412efae --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/mp-solaris.cpp @@ -0,0 +1,479 @@ +/* $Id: mp-solaris.cpp $ */ +/** @file + * IPRT - Multiprocessor, Solaris. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <kstat.h> +#include <sys/processor.h> + +#include <iprt/mp.h> +#include <iprt/cpuset.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/log.h> +#include <iprt/once.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Initialization serializing (rtMpSolarisOnce). */ +static RTONCE g_MpSolarisOnce = RTONCE_INITIALIZER; +/** Critical section serializing access to kstat. */ +static RTCRITSECT g_MpSolarisCritSect; +/** The kstat handle. */ +static kstat_ctl_t *g_pKsCtl; +/** Array pointing to the cpu_info instances. */ +static kstat_t **g_papCpuInfo; +/** The number of entries in g_papCpuInfo */ +static RTCPUID g_capCpuInfo; +/** Array of core ids. */ +static uint64_t *g_pu64CoreIds; +/** Number of entries in g_pu64CoreIds. */ +static size_t g_cu64CoreIds; +/** Number of cores in the system. */ +static size_t g_cCores; + + +/** + * Helper for getting the core ID for a given CPU/strand/hyperthread. + * + * @returns The core ID. + * @param idCpu The CPU ID instance. + */ +static inline uint64_t rtMpSolarisGetCoreId(RTCPUID idCpu) +{ + kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(g_papCpuInfo[idCpu], (char *)"core_id"); + Assert(pStat->data_type == KSTAT_DATA_LONG); + Assert(pStat->value.l >= 0); + AssertCompile(sizeof(uint64_t) >= sizeof(long)); /* Paranoia. */ + return (uint64_t)pStat->value.l; +} + + +/** + * Populates 'g_pu64CoreIds' array with unique core identifiers in the system. + * + * @returns VBox status code. + */ +static int rtMpSolarisGetCoreIds(void) +{ + for (RTCPUID idCpu = 0; idCpu < g_capCpuInfo; idCpu++) + { + /* + * It is possible that the number of cores don't match the maximum number + * of cores possible on the system. Hence check if we have a valid cpu_info + * object. We don't want to break out if it's NULL, the array may be sparse + * in theory, see @bugref{8469}. + */ + if (g_papCpuInfo[idCpu]) + { + if (kstat_read(g_pKsCtl, g_papCpuInfo[idCpu], 0) != -1) + { + /* Strands/Hyperthreads share the same core ID. */ + uint64_t u64CoreId = rtMpSolarisGetCoreId(idCpu); + bool fAddedCore = false; + for (RTCPUID i = 0; i < g_cCores; i++) + { + if (g_pu64CoreIds[i] == u64CoreId) + { + fAddedCore = true; + break; + } + } + + if (!fAddedCore) + { + g_pu64CoreIds[g_cCores] = u64CoreId; + ++g_cCores; + } + } + else + return VERR_INTERNAL_ERROR_2; + } + } + + return VINF_SUCCESS; +} + + +/** + * Run once function that initializes the kstats we need here. + * + * @returns IPRT status code. + * @param pvUser Unused. + */ +static DECLCALLBACK(int) rtMpSolarisOnce(void *pvUser) +{ + int rc = VINF_SUCCESS; + NOREF(pvUser); + + /* + * Open kstat and find the cpu_info entries for each of the CPUs. + */ + g_pKsCtl = kstat_open(); + if (g_pKsCtl) + { + g_capCpuInfo = RTMpGetCount(); + if (RT_LIKELY(g_capCpuInfo > 0)) + { + g_papCpuInfo = (kstat_t **)RTMemAllocZ(g_capCpuInfo * sizeof(kstat_t *)); + if (g_papCpuInfo) + { + g_cu64CoreIds = g_capCpuInfo; + g_pu64CoreIds = (uint64_t *)RTMemAllocZ(g_cu64CoreIds * sizeof(uint64_t)); + if (g_pu64CoreIds) + { + rc = RTCritSectInit(&g_MpSolarisCritSect); + if (RT_SUCCESS(rc)) + { + RTCPUID i = 0; + for (kstat_t *pKsp = g_pKsCtl->kc_chain; pKsp != NULL; pKsp = pKsp->ks_next) + { + if (!RTStrCmp(pKsp->ks_module, "cpu_info")) + { + AssertBreak(i < g_capCpuInfo); + g_papCpuInfo[i++] = pKsp; + /** @todo ks_instance == cpu_id (/usr/src/uts/common/os/cpu.c)? Check this and fix it ASAP. */ + } + } + + rc = rtMpSolarisGetCoreIds(); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + else + Log(("rtMpSolarisGetCoreIds failed. rc=%Rrc\n", rc)); + } + + RTMemFree(g_pu64CoreIds); + g_pu64CoreIds = NULL; + } + else + rc = VERR_NO_MEMORY; + + /* bail out, we failed. */ + RTMemFree(g_papCpuInfo); + g_papCpuInfo = NULL; + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_CPU_IPE_1; + kstat_close(g_pKsCtl); + g_pKsCtl = NULL; + } + else + { + rc = RTErrConvertFromErrno(errno); + if (RT_SUCCESS(rc)) + rc = VERR_INTERNAL_ERROR; + Log(("kstat_open() -> %d (%Rrc)\n", errno, rc)); + } + + return rc; +} + + +/** + * RTOnceEx() cleanup function. + * + * @param pvUser Unused. + * @param fLazyCleanUpOk Whether lazy cleanup is okay or not. + */ +static DECLCALLBACK(void) rtMpSolarisCleanUp(void *pvUser, bool fLazyCleanUpOk) +{ + if (g_pKsCtl) + kstat_close(g_pKsCtl); + RTMemFree(g_pu64CoreIds); + RTMemFree(g_papCpuInfo); +} + + +/** + * Worker for RTMpGetCurFrequency and RTMpGetMaxFrequency. + * + * @returns The desired frequency on success, 0 on failure. + * + * @param idCpu The CPU ID. + * @param pszStatName The cpu_info stat name. + */ +static uint64_t rtMpSolarisGetFrequency(RTCPUID idCpu, const char *pszStatName) +{ + uint64_t u64 = 0; + int rc = RTOnceEx(&g_MpSolarisOnce, rtMpSolarisOnce, rtMpSolarisCleanUp, NULL /* pvUser */); + if (RT_SUCCESS(rc)) + { + if ( idCpu < g_capCpuInfo + && g_papCpuInfo[idCpu]) + { + rc = RTCritSectEnter(&g_MpSolarisCritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (kstat_read(g_pKsCtl, g_papCpuInfo[idCpu], 0) != -1) + { + /* Solaris really need to fix their APIs. Explicitly cast for now. */ + kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(g_papCpuInfo[idCpu], (char*)pszStatName); + if (pStat) + { + Assert(pStat->data_type == KSTAT_DATA_UINT64 || pStat->data_type == KSTAT_DATA_LONG); + switch (pStat->data_type) + { + case KSTAT_DATA_UINT64: u64 = pStat->value.ui64; break; /* current_clock_Hz */ + case KSTAT_DATA_INT32: u64 = pStat->value.i32; break; /* clock_MHz */ + + /* just in case... */ + case KSTAT_DATA_UINT32: u64 = pStat->value.ui32; break; + case KSTAT_DATA_INT64: u64 = pStat->value.i64; break; + default: + AssertMsgFailed(("%d\n", pStat->data_type)); + break; + } + } + else + Log(("kstat_data_lookup(%s) -> %d\n", pszStatName, errno)); + } + else + Log(("kstat_read() -> %d\n", errno)); + RTCritSectLeave(&g_MpSolarisCritSect); + } + } + else + Log(("invalid idCpu: %d (g_capCpuInfo=%d)\n", (int)idCpu, (int)g_capCpuInfo)); + } + + return u64; +} + + +RTDECL(uint32_t) RTMpGetCurFrequency(RTCPUID idCpu) +{ + return rtMpSolarisGetFrequency(idCpu, "current_clock_Hz") / 1000000; +} + + +RTDECL(uint32_t) RTMpGetMaxFrequency(RTCPUID idCpu) +{ + return rtMpSolarisGetFrequency(idCpu, "clock_MHz"); +} + + +#if defined(RT_ARCH_SPARC) || defined(RT_ARCH_SPARC64) +RTDECL(RTCPUID) RTMpCpuId(void) +{ + /** @todo implement RTMpCpuId on solaris/r3! */ + return NIL_RTCPUID; +} +#endif + + +RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu) +{ + return idCpu < RTCPUSET_MAX_CPUS ? (int)idCpu : -1; +} + + +RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu) +{ + return (unsigned)iCpu < RTCPUSET_MAX_CPUS ? iCpu : NIL_RTCPUID; +} + + +RTDECL(RTCPUID) RTMpGetMaxCpuId(void) +{ + return RTMpGetCount() - 1; +} + + +RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) +{ + return idCpu != NIL_RTCPUID + && idCpu < (RTCPUID)RTMpGetCount(); +} + + +RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu) +{ + int iStatus = p_online(idCpu, P_STATUS); + return iStatus == P_ONLINE + || iStatus == P_NOINTR; +} + + +RTDECL(bool) RTMpIsCpuPresent(RTCPUID idCpu) +{ + int iStatus = p_online(idCpu, P_STATUS); + return iStatus != -1; +} + + +RTDECL(RTCPUID) RTMpGetCount(void) +{ + /* + * Solaris has sysconf. + */ + int cCpus = sysconf(_SC_NPROCESSORS_MAX); + if (cCpus < 0) + cCpus = sysconf(_SC_NPROCESSORS_CONF); + return cCpus; +} + + +RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet) +{ + RTCpuSetEmpty(pSet); + int idCpu = RTMpGetCount(); + while (idCpu-- > 0) + RTCpuSetAdd(pSet, idCpu); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetOnlineCount(void) +{ + /* + * Solaris has sysconf. + */ + return sysconf(_SC_NPROCESSORS_ONLN); +} + + +RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet) +{ + RTCpuSetEmpty(pSet); + RTCPUID cCpus = RTMpGetCount(); + for (RTCPUID idCpu = 0; idCpu < cCpus; idCpu++) + if (RTMpIsCpuOnline(idCpu)) + RTCpuSetAdd(pSet, idCpu); + return pSet; +} + + +RTDECL(PRTCPUSET) RTMpGetPresentSet(PRTCPUSET pSet) +{ +#ifdef RT_STRICT + RTCPUID cCpusPresent = 0; +#endif + RTCpuSetEmpty(pSet); + RTCPUID cCpus = RTMpGetCount(); + for (RTCPUID idCpu = 0; idCpu < cCpus; idCpu++) + if (RTMpIsCpuPresent(idCpu)) + { + RTCpuSetAdd(pSet, idCpu); +#ifdef RT_STRICT + cCpusPresent++; +#endif + } + Assert(cCpusPresent == RTMpGetPresentCount()); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetPresentCount(void) +{ + /* + * Solaris has sysconf. + */ + return sysconf(_SC_NPROCESSORS_CONF); +} + + +RTDECL(RTCPUID) RTMpGetPresentCoreCount(void) +{ + return RTMpGetCoreCount(); +} + + +RTDECL(RTCPUID) RTMpGetCoreCount(void) +{ + int rc = RTOnceEx(&g_MpSolarisOnce, rtMpSolarisOnce, rtMpSolarisCleanUp, NULL /* pvUser */); + if (RT_SUCCESS(rc)) + return g_cCores; + + return 0; +} + + +RTDECL(RTCPUID) RTMpGetOnlineCoreCount(void) +{ + RTCPUID uOnlineCores = 0; + int rc = RTOnceEx(&g_MpSolarisOnce, rtMpSolarisOnce, rtMpSolarisCleanUp, NULL /* pvUser */); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectEnter(&g_MpSolarisCritSect); + AssertRC(rc); + + /* + * For each core in the system, count how many are currently online. + */ + for (RTCPUID j = 0; j < g_cCores; j++) + { + uint64_t u64CoreId = g_pu64CoreIds[j]; + for (RTCPUID idCpu = 0; idCpu < g_capCpuInfo; idCpu++) + { + rc = kstat_read(g_pKsCtl, g_papCpuInfo[idCpu], 0); + AssertReturn(rc != -1, 0 /* rc */); + uint64_t u64ThreadCoreId = rtMpSolarisGetCoreId(idCpu); + if (u64ThreadCoreId == u64CoreId) + { + kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(g_papCpuInfo[idCpu], (char *)"state"); + Assert(pStat->data_type == KSTAT_DATA_CHAR); + if( !RTStrNCmp(pStat->value.c, PS_ONLINE, sizeof(PS_ONLINE) - 1) + || !RTStrNCmp(pStat->value.c, PS_NOINTR, sizeof(PS_NOINTR) - 1)) + { + uOnlineCores++; + break; /* Move to the next core. We have at least 1 hyperthread online in the current core. */ + } + } + } + } + + RTCritSectLeave(&g_MpSolarisCritSect); + } + + return uOnlineCores; +} + diff --git a/src/VBox/Runtime/r3/solaris/rtProcInitExePath-solaris.cpp b/src/VBox/Runtime/r3/solaris/rtProcInitExePath-solaris.cpp new file mode 100644 index 00000000..0cf236b0 --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/rtProcInitExePath-solaris.cpp @@ -0,0 +1,81 @@ +/* $Id: rtProcInitExePath-solaris.cpp $ */ +/** @file + * IPRT - rtProcInitName, Solaris. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <unistd.h> +#include <errno.h> + +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include "internal/process.h" +#include "internal/path.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ + /* + * Read the /proc/<pid>/path/a.out link, convert to native and return it. + */ + char szProcFile[80]; + RTStrPrintf(szProcFile, sizeof(szProcFile), "/proc/%ld/path/a.out", (long)getpid()); + int cchLink = readlink(szProcFile, pszPath, cchPath - 1); + if (cchLink > 0 && (size_t)cchLink <= cchPath - 1) + { + pszPath[cchLink] = '\0'; + + char const *pszTmp; + int rc = rtPathFromNative(&pszTmp, pszPath, NULL); + AssertMsgRCReturn(rc, ("rc=%Rrc pszLink=\"%s\"\nhex: %.*Rhxs\n", rc, pszPath, cchLink, pszPath), rc); + if (pszTmp != pszPath) + { + rc = RTStrCopy(pszPath, cchPath, pszTmp); + rtPathFreeIprt(pszTmp, pszPath); + } + return rc; + } + + int err = errno; + int rc = RTErrConvertFromErrno(err); + AssertMsgFailed(("rc=%Rrc err=%d cchLink=%d\n", rc, err, cchLink)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/solaris/systemmem-solaris.cpp b/src/VBox/Runtime/r3/solaris/systemmem-solaris.cpp new file mode 100644 index 00000000..b083c4f0 --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/systemmem-solaris.cpp @@ -0,0 +1,182 @@ +/* $Id: systemmem-solaris.cpp $ */ +/** @file + * IPRT - RTSystemQueryTotalRam, Solaris ring-3. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/once.h> +#include <iprt/param.h> + +#include <errno.h> +#include <kstat.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Initialize globals once. */ +static RTONCE g_rtSysMemSolInitOnce = RTONCE_INITIALIZER; +/** Critical section serializing access to g_pKStatCtl and the other handles. */ +static RTCRITSECT g_rtSysMemSolCritSect; +/** The kstate control handle. */ +static kstat_ctl_t *g_pKStatCtl = NULL; +/** The unix.system_pages handle. */ +static kstat_t *g_pUnixSysPages = NULL; +/** The zfs.archstats handle. */ +static kstat_t *g_pZfsArcStats = NULL; + + +/** @callback_method_impl{FNRTONCE} */ +static DECLCALLBACK(int) rtSysMemSolInit(void *pvUser) +{ + int rc = RTCritSectInit(&g_rtSysMemSolCritSect); + if (RT_SUCCESS(rc)) + { + g_pKStatCtl = kstat_open(); + if (g_pKStatCtl) + { + g_pUnixSysPages = kstat_lookup(g_pKStatCtl, (char *)"unix", 0, (char *)"system_pages"); + if (g_pUnixSysPages) + { + g_pZfsArcStats = kstat_lookup(g_pKStatCtl, (char *)"zfs", 0, (char *)"arcstats"); /* allow NULL */ + return VINF_SUCCESS; + } + + rc = RTErrConvertFromErrno(errno); + kstat_close(g_pKStatCtl); + g_pKStatCtl = NULL; + } + else + rc = RTErrConvertFromErrno(errno); + } + return rc; +} + + +/** @callback_method_impl{FNRTONCECLEANUP} */ +static DECLCALLBACK(void) rtSysMemSolCleanUp(void *pvUser, bool fLazyCleanUpOk) +{ + RTCritSectDelete(&g_rtSysMemSolCritSect); + + kstat_close(g_pKStatCtl); + g_pKStatCtl = NULL; + g_pUnixSysPages = NULL; + g_pZfsArcStats = NULL; + + NOREF(pvUser); NOREF(fLazyCleanUpOk); +} + + + +RTDECL(int) RTSystemQueryTotalRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + int rc = RTOnceEx(&g_rtSysMemSolInitOnce, rtSysMemSolInit, rtSysMemSolCleanUp, NULL); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectEnter(&g_rtSysMemSolCritSect); + if (RT_SUCCESS(rc)) + { + if (kstat_read(g_pKStatCtl, g_pUnixSysPages, NULL) != -1) + { + kstat_named_t *pData = (kstat_named_t *)kstat_data_lookup(g_pUnixSysPages, (char *)"physmem"); + if (pData) + *pcb = (uint64_t)pData->value.ul * PAGE_SIZE; + else + rc = RTErrConvertFromErrno(errno); + } + else + rc = RTErrConvertFromErrno(errno); + RTCritSectLeave(&g_rtSysMemSolCritSect); + } + } + return rc; +} + + +RTDECL(int) RTSystemQueryAvailableRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + int rc = RTOnceEx(&g_rtSysMemSolInitOnce, rtSysMemSolInit, rtSysMemSolCleanUp, NULL); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectEnter(&g_rtSysMemSolCritSect); + if (RT_SUCCESS(rc)) + { + if (kstat_read(g_pKStatCtl, g_pUnixSysPages, NULL) != -1) + { + kstat_named_t *pData = (kstat_named_t *)kstat_data_lookup(g_pUnixSysPages, (char *)"freemem"); + if (pData) + { + *pcb = (uint64_t)pData->value.ul * PAGE_SIZE; + + /* + * Adjust for ZFS greedyness if possible. + * (c_min is the target minimum size of the cache, it is not + * an absolute minimum.) + */ + if ( g_pZfsArcStats + && kstat_read(g_pKStatCtl, g_pZfsArcStats, NULL) != -1) + { + kstat_named_t *pCurSize = (kstat_named_t *)kstat_data_lookup(g_pZfsArcStats, (char *)"size"); + kstat_named_t *pMinSize = (kstat_named_t *)kstat_data_lookup(g_pZfsArcStats, (char *)"c_min"); + if ( pCurSize + && pMinSize + && pCurSize->value.ul > pMinSize->value.ul) + { + *pcb += pCurSize->value.ul - pMinSize->value.ul; + } + } + } + else + rc = RTErrConvertFromErrno(errno); + } + + RTCritSectLeave(&g_rtSysMemSolCritSect); + } + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/solaris/thread-affinity-solaris.cpp b/src/VBox/Runtime/r3/solaris/thread-affinity-solaris.cpp new file mode 100644 index 00000000..0e037aad --- /dev/null +++ b/src/VBox/Runtime/r3/solaris/thread-affinity-solaris.cpp @@ -0,0 +1,104 @@ +/* $Id: thread-affinity-solaris.cpp $ */ +/** @file + * IPRT - Thread Affinity, Solaris ring-3 implementation. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/thread.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/cpuset.h> +#include <iprt/err.h> +#include <iprt/mp.h> + +#include <sys/types.h> +#include <sys/processor.h> +#include <sys/procset.h> +#include <unistd.h> +#include <errno.h> + + +/* Note! The current implementation can only bind to a single CPU. */ + + +RTR3DECL(int) RTThreadSetAffinity(PCRTCPUSET pCpuSet) +{ + int rc; + if (pCpuSet == NULL) + rc = processor_bind(P_LWPID, P_MYID, PBIND_NONE, NULL); + else + { + RTCPUSET PresentSet; + int cCpusInSet = RTCpuSetCount(pCpuSet); + if (cCpusInSet == 1) + { + unsigned iCpu = 0; + while ( iCpu < RTCPUSET_MAX_CPUS + && !RTCpuSetIsMemberByIndex(pCpuSet, iCpu)) + iCpu++; + rc = processor_bind(P_LWPID, P_MYID, iCpu, NULL); + } + else if ( cCpusInSet == RTCPUSET_MAX_CPUS + || RTCpuSetIsEqual(pCpuSet, RTMpGetPresentSet(&PresentSet))) + rc = processor_bind(P_LWPID, P_MYID, PBIND_NONE, NULL); + else + return VERR_NOT_SUPPORTED; + } + if (!rc) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +RTR3DECL(int) RTThreadGetAffinity(PRTCPUSET pCpuSet) +{ + processorid_t iOldCpu; + int rc = processor_bind(P_LWPID, P_MYID, PBIND_QUERY, &iOldCpu); + if (rc) + return RTErrConvertFromErrno(errno); + if (iOldCpu == PBIND_NONE) + RTMpGetPresentSet(pCpuSet); + else + { + RTCpuSetEmpty(pCpuSet); + if (RTCpuSetAdd(pCpuSet, iOldCpu) != 0) + return VERR_INTERNAL_ERROR_5; + } + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/stream.cpp b/src/VBox/Runtime/r3/stream.cpp new file mode 100644 index 00000000..ead09e53 --- /dev/null +++ b/src/VBox/Runtime/r3/stream.cpp @@ -0,0 +1,2698 @@ +/* $Id: stream.cpp $ */ +/** @file + * IPRT - I/O Stream. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def RTSTREAM_STANDALONE + * Standalone streams w/o depending on stdio.h, using our RTFile API for + * file/whatever access. */ +#if (defined(IPRT_NO_CRT) && defined(RT_OS_WINDOWS)) || defined(DOXYGEN_RUNNING) +# define RTSTREAM_STANDALONE +#endif + +#if defined(RT_OS_LINUX) /* PORTME: check for the _unlocked functions in stdio.h */ +# ifndef RTSTREAM_STANDALONE +# define HAVE_FWRITE_UNLOCKED +# endif +#endif + +/** @def RTSTREAM_WITH_TEXT_MODE + * Indicates whether we need to support the 'text' mode files and convert + * CRLF to LF while reading and writing. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING) +# define RTSTREAM_WITH_TEXT_MODE +#endif + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/stream.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#ifndef HAVE_FWRITE_UNLOCKED +# include <iprt/critsect.h> +#endif +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +# include <iprt/file.h> +#ifdef RTSTREAM_STANDALONE +# include <iprt/list.h> +#endif +#include <iprt/mem.h> +#ifdef RTSTREAM_STANDALONE +# include <iprt/once.h> +#endif +#include <iprt/param.h> +#include <iprt/string.h> + +#include "internal/alignmentchecks.h" +#include "internal/magics.h" +#if defined(IPRT_NO_CRT) || defined(IN_RT_STATIC) +# include "internal/initterm.h" +#endif + +#ifdef RTSTREAM_STANDALONE +# ifdef _MSC_VER +# define IPRT_COMPILER_VCC_WITH_C_INIT_TERM_SECTIONS +# include "internal/compiler-vcc.h" +# endif +#else +# include <stdio.h> +# include <errno.h> +# if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +# include <io.h> +# include <fcntl.h> +# endif +#endif +#ifdef RT_OS_WINDOWS +# include <iprt/utf16.h> +# include <iprt/win/windows.h> +#elif !defined(RTSTREAM_STANDALONE) +# include <termios.h> +# include <unistd.h> +# include <sys/ioctl.h> +#endif + +#if defined(RT_OS_OS2) && !defined(RTSTREAM_STANDALONE) +# define _O_TEXT O_TEXT +# define _O_BINARY O_BINARY +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef RTSTREAM_STANDALONE +/** The buffer direction. */ +typedef enum RTSTREAMBUFDIR +{ + RTSTREAMBUFDIR_NONE = 0, + RTSTREAMBUFDIR_READ, + RTSTREAMBUFDIR_WRITE +} RTSTREAMBUFDIR; + +/** The buffer style. */ +typedef enum RTSTREAMBUFSTYLE +{ + RTSTREAMBUFSTYLE_UNBUFFERED = 0, + RTSTREAMBUFSTYLE_LINE, + RTSTREAMBUFSTYLE_FULL +} RTSTREAMBUFSTYLE; + +#endif + +/** + * File stream. + */ +typedef struct RTSTREAM +{ + /** Magic value used to validate the stream. (RTSTREAM_MAGIC) */ + uint32_t u32Magic; + /** File stream error. */ + int32_t volatile i32Error; +#ifndef RTSTREAM_STANDALONE + /** Pointer to the LIBC file stream. */ + FILE *pFile; +#else + /** Indicates which standard handle this is supposed to be. + * Set to RTHANDLESTD_INVALID if not one of the tree standard streams. */ + RTHANDLESTD enmStdHandle; + /** The IPRT handle backing this stream. + * This is initialized lazily using enmStdHandle for the three standard + * streams. */ + RTFILE hFile; + /** Buffer. */ + char *pchBuf; + /** Buffer allocation size. */ + size_t cbBufAlloc; + /** Offset of the first valid byte in the buffer. */ + size_t offBufFirst; + /** Offset of the end of valid bytes in the buffer (exclusive). */ + size_t offBufEnd; + /** The stream buffer direction. */ + RTSTREAMBUFDIR enmBufDir; + /** The buffering style (unbuffered, line, full). + * @todo replace by RTSTRMBUFMODE. */ + RTSTREAMBUFSTYLE enmBufStyle; +# ifdef RTSTREAM_WITH_TEXT_MODE + /** Bitmap running parallel to each char pchBuf, indicating where a '\\r' + * character have been removed during buffer filling. This is used to implement + * RTStrmTell in non-binary mode. */ + uint32_t *pbmBuf; + /** Indicates that we've got a CR ('\\r') beyond the end of official buffer + * and need to check if there is a LF following it. This member is ignored + * in binary mode. */ + bool fPendingCr; +# endif +#endif + /** Stream is using the current process code set. */ + bool fCurrentCodeSet; + /** Whether the stream was opened in binary mode. */ + bool fBinary; + /** Whether to recheck the stream mode before writing. */ + bool fRecheckMode; +#if !defined(HAVE_FWRITE_UNLOCKED) || defined(RTSTREAM_STANDALONE) + /** Critical section for serializing access to the stream. */ + PRTCRITSECT pCritSect; +#endif +#ifdef RTSTREAM_STANDALONE + /** Entry in g_StreamList (for automatic flushing and closing at + * exit/unload). */ + RTLISTNODE ListEntry; +#endif +} RTSTREAM; + + +/** + * State for wrapped output (RTStrmWrappedPrintf, RTStrmWrappedPrintfV). + */ +typedef struct RTSTRMWRAPPEDSTATE +{ + PRTSTREAM pStream; /**< The output stream. */ + uint32_t cchWidth; /**< The line width. */ + uint32_t cchLine; /**< The current line length (valid chars in szLine). */ + uint32_t cLines; /**< Number of lines written. */ + uint32_t cchIndent; /**< The indent (determined from the first line). */ + int rcStatus; /**< The output status. */ + uint8_t cchHangingIndent; /**< Hanging indent (from fFlags). */ + char szLine[0x1000+1]; /**< We must buffer output so we can do proper word splitting. */ +} RTSTRMWRAPPEDSTATE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The standard input stream. */ +static RTSTREAM g_StdIn = +{ + /* .u32Magic = */ RTSTREAM_MAGIC, + /* .i32Error = */ 0, +#ifndef RTSTREAM_STANDALONE + /* .pFile = */ stdin, +#else + /* .enmStdHandle = */ RTHANDLESTD_INPUT, + /* .hFile = */ NIL_RTFILE, + /* .pchBuf = */ NULL, + /* .cbBufAlloc = */ 0, + /* .offBufFirst = */ 0, + /* .offBufEnd = */ 0, + /* .enmBufDir = */ RTSTREAMBUFDIR_NONE, + /* .enmBufStyle = */ RTSTREAMBUFSTYLE_UNBUFFERED, +# ifdef RTSTREAM_WITH_TEXT_MODE + /* .pbmBuf = */ NULL, + /* .fPendingCr = */ false, +# endif +#endif + /* .fCurrentCodeSet = */ true, + /* .fBinary = */ false, + /* .fRecheckMode = */ true, +#ifndef HAVE_FWRITE_UNLOCKED + /* .pCritSect = */ NULL, +#endif +#ifdef RTSTREAM_STANDALONE + /* .ListEntry = */ { NULL, NULL }, +#endif +}; + +/** The standard error stream. */ +static RTSTREAM g_StdErr = +{ + /* .u32Magic = */ RTSTREAM_MAGIC, + /* .i32Error = */ 0, +#ifndef RTSTREAM_STANDALONE + /* .pFile = */ stderr, +#else + /* .enmStdHandle = */ RTHANDLESTD_ERROR, + /* .hFile = */ NIL_RTFILE, + /* .pchBuf = */ NULL, + /* .cbBufAlloc = */ 0, + /* .offBufFirst = */ 0, + /* .offBufEnd = */ 0, + /* .enmBufDir = */ RTSTREAMBUFDIR_NONE, + /* .enmBufStyle = */ RTSTREAMBUFSTYLE_UNBUFFERED, +# ifdef RTSTREAM_WITH_TEXT_MODE + /* .pbmBuf = */ NULL, + /* .fPendingCr = */ false, +# endif +#endif + /* .fCurrentCodeSet = */ true, + /* .fBinary = */ false, + /* .fRecheckMode = */ true, +#ifndef HAVE_FWRITE_UNLOCKED + /* .pCritSect = */ NULL, +#endif +#ifdef RTSTREAM_STANDALONE + /* .ListEntry = */ { NULL, NULL }, +#endif +}; + +/** The standard output stream. */ +static RTSTREAM g_StdOut = +{ + /* .u32Magic = */ RTSTREAM_MAGIC, + /* .i32Error = */ 0, +#ifndef RTSTREAM_STANDALONE + /* .pFile = */ stdout, +#else + /* .enmStdHandle = */ RTHANDLESTD_OUTPUT, + /* .hFile = */ NIL_RTFILE, + /* .pchBuf = */ NULL, + /* .cbBufAlloc = */ 0, + /* .offBufFirst = */ 0, + /* .offBufEnd = */ 0, + /* .enmBufDir = */ RTSTREAMBUFDIR_NONE, + /* .enmBufStyle = */ RTSTREAMBUFSTYLE_LINE, +# ifdef RTSTREAM_WITH_TEXT_MODE + /* .pbmBuf = */ NULL, + /* .fPendingCr = */ false, +# endif +#endif + /* .fCurrentCodeSet = */ true, + /* .fBinary = */ false, + /* .fRecheckMode = */ true, +#ifndef HAVE_FWRITE_UNLOCKED + /* .pCritSect = */ NULL, +#endif +#ifdef RTSTREAM_STANDALONE + /* .ListEntry = */ { NULL, NULL }, +#endif +}; + +/** Pointer to the standard input stream. */ +RTDATADECL(PRTSTREAM) g_pStdIn = &g_StdIn; + +/** Pointer to the standard output stream. */ +RTDATADECL(PRTSTREAM) g_pStdErr = &g_StdErr; + +/** Pointer to the standard output stream. */ +RTDATADECL(PRTSTREAM) g_pStdOut = &g_StdOut; + +#ifdef RTSTREAM_STANDALONE +/** Run-once initializer for the stream list (g_StreamList + g_StreamListCritSect). */ +static RTONCE g_StreamListOnce = RTONCE_INITIALIZER; +/** List of user created streams (excludes the standard streams). */ +static RTLISTANCHOR g_StreamList; +/** Critical section protecting the stream list. */ +static RTCRITSECT g_StreamListCritSect; + + +/** @callback_method_impl{FNRTONCE} */ +static DECLCALLBACK(int32_t) rtStrmListInitOnce(void *pvUser) +{ + RT_NOREF(pvUser); + RTListInit(&g_StreamList); + return RTCritSectInit(&g_StreamListCritSect); +} + +#endif + + +#ifndef HAVE_FWRITE_UNLOCKED +/** + * Allocates and acquires the lock for the stream. + * + * @returns IPRT status code. + * @param pStream The stream (valid). + */ +static int rtStrmAllocLock(PRTSTREAM pStream) +{ + Assert(pStream->pCritSect == NULL); + + PRTCRITSECT pCritSect = (PRTCRITSECT)RTMemAlloc(sizeof(*pCritSect)); + if (!pCritSect) + return VERR_NO_MEMORY; + + /* The native stream lock are normally not recursive. */ + uint32_t fFlags = RTCRITSECT_FLAGS_NO_NESTING; +# if defined(IPRT_NO_CRT) || defined(IN_RT_STATIC) + /* IPRT is often used deliberatly without initialization in no-CRT + binaries (for instance VBoxAddInstallNt3x.exe), so in order to avoid + asserting in the lock validator we add the bootstrap hack that disable + lock validation for the section. + Update: Applying this to all builds involving static linking, as it's + now going to be used for tests running at compile-time too. */ + if (!rtInitIsInitialized()) + fFlags |= RTCRITSECT_FLAGS_BOOTSTRAP_HACK; +# endif + int rc = RTCritSectInitEx(pCritSect, fFlags, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "RTSemSpinMutex"); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectEnter(pCritSect); + if (RT_SUCCESS(rc)) + { + if (RT_LIKELY(ASMAtomicCmpXchgPtr(&pStream->pCritSect, pCritSect, NULL))) + return VINF_SUCCESS; + + RTCritSectLeave(pCritSect); + } + RTCritSectDelete(pCritSect); + } + RTMemFree(pCritSect); + + /* Handle the lost race case... */ + pCritSect = ASMAtomicReadPtrT(&pStream->pCritSect, PRTCRITSECT); + if (pCritSect) + return RTCritSectEnter(pCritSect); + + return rc; +} +#endif /* !HAVE_FWRITE_UNLOCKED */ + + +/** + * Locks the stream. May have to allocate the lock as well. + * + * @param pStream The stream (valid). + */ +DECLINLINE(void) rtStrmLock(PRTSTREAM pStream) +{ +#ifdef HAVE_FWRITE_UNLOCKED + flockfile(pStream->pFile); +#else + if (RT_LIKELY(pStream->pCritSect)) + RTCritSectEnter(pStream->pCritSect); + else + rtStrmAllocLock(pStream); +#endif +} + + +/** + * Unlocks the stream. + * + * @param pStream The stream (valid). + */ +DECLINLINE(void) rtStrmUnlock(PRTSTREAM pStream) +{ +#ifdef HAVE_FWRITE_UNLOCKED + funlockfile(pStream->pFile); +#else + if (RT_LIKELY(pStream->pCritSect)) + RTCritSectLeave(pStream->pCritSect); +#endif +} + + +/** + * Opens a file stream. + * + * @returns iprt status code. + * @param pszFilename Path to the file to open, hFile must be NIL_RTFILE. + * NULL if a hFile is to be used instead. + * @param hFile File handle to use when called from + * RTStrmOpenFileHandle. pszFilename must be NULL. + * @param pszMode See RTStrmOpen. + * @param ppStream Where to store the opened stream. + */ +static int rtStrmOpenComon(const char *pszFilename, RTFILE hFile, const char *pszMode, PRTSTREAM *ppStream) +{ + /* + * Validate input and look for things we care for in the pszMode string. + */ + AssertReturn(pszMode && *pszMode, VERR_INVALID_FLAGS); + + /* + * Process the mode string. + */ + char chMode = '\0'; /* a|r|w */ + bool fPlus = false; /* + */ + bool fBinary = false; /* b | !t */ + bool fExclusive = false; /* x */ + bool fNoInherit = false; /* e (linux, freebsd) | N (win) | E (our for reverse) */ + const char *psz = pszMode; + char ch; + while ((ch = *psz++) != '\0') + { + switch (ch) + { + case 'a': + case 'r': + case 'w': + chMode = ch; + break; + case '+': + fPlus = true; + break; + case 'b': + fBinary = true; + break; + case 't': + fBinary = false; + break; + case 'x': + fExclusive = true; + break; + case 'e': + case 'N': + fNoInherit = true; + break; + case 'E': + fNoInherit = false; + break; + default: + AssertMsgFailedReturn(("Invalid ch='%c' in pszMode='%s', '<a|r|w>[+][b|t][x][e|N|E]'\n", ch, pszMode), + VERR_INVALID_FLAGS); + } + } + + /* + * Translate into to RTFILE_O_* flags: + */ + uint64_t fOpen; + switch (chMode) + { + case 'a': fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_APPEND; break; + case 'w': fOpen = !fExclusive + ? RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE + : RTFILE_O_CREATE | RTFILE_O_WRITE; break; + case 'r': fOpen = RTFILE_O_OPEN | RTFILE_O_READ; break; + default: AssertMsgFailedReturn(("No main mode (a|r|w) specified in '%s'!\n", pszMode), VERR_INVALID_FLAGS); + } + AssertMsgReturn(!fExclusive || chMode == 'w', ("the 'x' flag is only allowed with 'w'! (%s)\n", pszMode), + VERR_INVALID_FLAGS); + if (fExclusive) + fOpen |= RTFILE_O_READ | RTFILE_O_WRITE; + if (fPlus) + fOpen |= RTFILE_O_READ | RTFILE_O_WRITE; + if (!fNoInherit) + fOpen |= RTFILE_O_INHERIT; + fOpen |= RTFILE_O_DENY_NONE; + fOpen |= 0666 << RTFILE_O_CREATE_MODE_SHIFT; + +#ifndef RTSTREAM_STANDALONE + /* + * Normalize mode for fdopen. + */ + char szNormalizedMode[8]; + szNormalizedMode[0] = chMode; + size_t off = 1; + if (fPlus) + szNormalizedMode[off++] = '+'; + if (fBinary) + szNormalizedMode[off++] = 'b'; + szNormalizedMode[off] = '\0'; +#endif + +#ifdef RTSTREAM_STANDALONE + /* + * Make the the stream list is initialized before we allocate anything. + */ + int rc2 = RTOnce(&g_StreamListOnce, rtStrmListInitOnce, NULL); + AssertRCReturn(rc2, rc2); +#endif + + /* + * Allocate the stream handle and try open it. + */ + int rc = VERR_NO_MEMORY; + PRTSTREAM pStream = (PRTSTREAM)RTMemAllocZ(sizeof(*pStream)); + if (pStream) + { + pStream->u32Magic = RTSTREAM_MAGIC; +#ifdef RTSTREAM_STANDALONE + pStream->enmStdHandle = RTHANDLESTD_INVALID; + pStream->hFile = NIL_RTFILE; + pStream->pchBuf = NULL; + pStream->cbBufAlloc = 0; + pStream->offBufFirst = 0; + pStream->offBufEnd = 0; + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + pStream->enmBufStyle = RTSTREAMBUFSTYLE_FULL; +# ifdef RTSTREAM_WITH_TEXT_MODE + pStream->pbmBuf = NULL; + pStream->fPendingCr = false, +# endif +#endif + pStream->i32Error = VINF_SUCCESS; + pStream->fCurrentCodeSet = false; + pStream->fBinary = fBinary; + pStream->fRecheckMode = false; +#ifndef HAVE_FWRITE_UNLOCKED + pStream->pCritSect = NULL; +#endif + RTFILEACTION enmActionTaken = RTFILEACTION_INVALID; + if (pszFilename) + rc = RTFileOpenEx(pszFilename, fOpen, &hFile, &enmActionTaken); + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { +#ifndef RTSTREAM_STANDALONE +# ifndef _MSC_VER + int fd = (int)RTFileToNative(hFile); +# else + int fd = _open_osfhandle(RTFileToNative(hFile), + (fPlus ? _O_RDWR : chMode == 'r' ? _O_RDONLY : _O_WRONLY) + | (chMode == 'a' ? _O_APPEND : 0) + | (fBinary ? _O_BINARY : _O_TEXT) + | (fNoInherit ? _O_NOINHERIT : 0)); +# endif + if (fd >= 0) + { + pStream->pFile = fdopen(fd, szNormalizedMode); + if (pStream->pFile) +#endif + { +#ifdef RTSTREAM_STANDALONE + pStream->hFile = hFile; + + /* We keep a list of these for cleanup purposes. */ + RTCritSectEnter(&g_StreamListCritSect); + RTListAppend(&g_StreamList, &pStream->ListEntry); + RTCritSectLeave(&g_StreamListCritSect); +#endif + *ppStream = pStream; + return VINF_SUCCESS; + } + + /* + * This better not happen too often as in 'w' mode we might've + * truncated a file, and in 'w' and 'a' modes there is a chance + * that we'll race other access to the file when deleting it. + */ +#ifndef RTSTREAM_STANDALONE + rc = RTErrConvertFromErrno(errno); +# ifdef _MSC_VER + close(fd); + hFile = NIL_RTFILE; + /** @todo we're in trouble here when called from RTStrmOpenFileHandle! */ +# endif + } + else + { +# ifdef _MSC_VER + rc = RTErrConvertFromErrno(errno); +# else + AssertFailedStmt(rc = VERR_INVALID_HANDLE); +# endif + } + if (pszFilename) + { + RTFileClose(hFile); + if (enmActionTaken == RTFILEACTION_CREATED) + RTFileDelete(pszFilename); + } +#endif + } + RTMemFree(pStream); + } + return rc; +} + + +RTR3DECL(int) RTStrmOpen(const char *pszFilename, const char *pszMode, PRTSTREAM *ppStream) +{ + *ppStream = NULL; + AssertReturn(pszFilename, VERR_INVALID_PARAMETER); + return rtStrmOpenComon(pszFilename, NIL_RTFILE, pszMode, ppStream); +} + + +RTR3DECL(int) RTStrmOpenFV(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, va_list args) +{ + int rc; + char szFilename[RTPATH_MAX]; + size_t cch = RTStrPrintfV(szFilename, sizeof(szFilename), pszFilenameFmt, args); + if (cch < sizeof(szFilename)) + rc = RTStrmOpen(szFilename, pszMode, ppStream); + else + { + AssertMsgFailed(("The filename is too long cch=%d\n", cch)); + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} + + +RTR3DECL(int) RTStrmOpenF(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, ...) +{ + va_list args; + va_start(args, pszFilenameFmt); + int rc = RTStrmOpenFV(pszMode, ppStream, pszFilenameFmt, args); + va_end(args); + return rc; +} + + +RTR3DECL(int) RTStrmOpenFileHandle(RTFILE hFile, const char *pszMode, uint32_t fFlags, PRTSTREAM *ppStream) +{ + *ppStream = NULL; + AssertReturn(RTFileIsValid(hFile), VERR_INVALID_HANDLE); + AssertReturn(fFlags == 0, VERR_INVALID_FLAGS); + return rtStrmOpenComon(NULL, hFile, pszMode, ppStream); +} + + +RTR3DECL(int) RTStrmClose(PRTSTREAM pStream) +{ + /* + * Validate input. + */ + if (!pStream) + return VINF_SUCCESS; + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + + /* We don't implement closing any of the standard handles at present. */ + AssertReturn(pStream != &g_StdIn, VERR_NOT_SUPPORTED); + AssertReturn(pStream != &g_StdOut, VERR_NOT_SUPPORTED); + AssertReturn(pStream != &g_StdErr, VERR_NOT_SUPPORTED); + + /* + * Invalidate the stream and destroy the critical section first. + */ +#ifdef RTSTREAM_STANDALONE + RTCritSectEnter(&g_StreamListCritSect); + RTListNodeRemove(&pStream->ListEntry); + RTCritSectLeave(&g_StreamListCritSect); +#endif + pStream->u32Magic = 0xdeaddead; +#ifndef HAVE_FWRITE_UNLOCKED + if (pStream->pCritSect) + { + RTCritSectEnter(pStream->pCritSect); + RTCritSectLeave(pStream->pCritSect); + RTCritSectDelete(pStream->pCritSect); + RTMemFree(pStream->pCritSect); + pStream->pCritSect = NULL; + } +#endif + + /* + * Flush and close the underlying file. + */ +#ifdef RTSTREAM_STANDALONE + int const rc1 = RTStrmFlush(pStream); + AssertRC(rc1); + int const rc2 = RTFileClose(pStream->hFile); + AssertRC(rc2); + int const rc = RT_SUCCESS(rc1) ? rc2 : rc1; +#else + int const rc = !fclose(pStream->pFile) ? VINF_SUCCESS : RTErrConvertFromErrno(errno); +#endif + + /* + * Destroy the stream. + */ +#ifdef RTSTREAM_STANDALONE + pStream->hFile = NIL_RTFILE; + RTMemFree(pStream->pchBuf); + pStream->pchBuf = NULL; + pStream->cbBufAlloc = 0; + pStream->offBufFirst = 0; + pStream->offBufEnd = 0; +# ifdef RTSTREAM_WITH_TEXT_MODE + RTMemFree(pStream->pbmBuf); + pStream->pbmBuf = NULL; +# endif +#else + pStream->pFile = NULL; +#endif + RTMemFree(pStream); + return rc; +} + + +RTR3DECL(int) RTStrmError(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + return pStream->i32Error; +} + + +RTR3DECL(int) RTStrmClearError(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + +#ifndef RTSTREAM_STANDALONE + clearerr(pStream->pFile); +#endif + ASMAtomicWriteS32(&pStream->i32Error, VINF_SUCCESS); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTStrmSetMode(PRTSTREAM pStream, int fBinary, int fCurrentCodeSet) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn((unsigned)(fBinary + 1) <= 2, VERR_INVALID_PARAMETER); + AssertReturn((unsigned)(fCurrentCodeSet + 1) <= 2, VERR_INVALID_PARAMETER); + + rtStrmLock(pStream); + + if (fBinary != -1) + { + pStream->fBinary = RT_BOOL(fBinary); + pStream->fRecheckMode = true; + } + + if (fCurrentCodeSet != -1) + pStream->fCurrentCodeSet = RT_BOOL(fCurrentCodeSet); + + rtStrmUnlock(pStream); + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTStrmSetBufferingMode(PRTSTREAM pStream, RTSTRMBUFMODE enmMode) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(enmMode > RTSTRMBUFMODE_INVALID && enmMode < RTSTRMBUFMODE_END, VERR_INVALID_PARAMETER); + +#ifndef RTSTREAM_STANDALONE + int iCrtMode = enmMode == RTSTRMBUFMODE_FULL ? _IOFBF : enmMode == RTSTRMBUFMODE_LINE ? _IOLBF : _IONBF; + int rc = setvbuf(pStream->pFile, NULL, iCrtMode, 0); + if (rc >= 0) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); + +#else + rtStrmLock(pStream); + pStream->enmBufStyle = enmMode == RTSTRMBUFMODE_FULL ? RTSTREAMBUFSTYLE_FULL + : enmMode == RTSTRMBUFMODE_LINE ? RTSTREAMBUFSTYLE_LINE : RTSTREAMBUFSTYLE_UNBUFFERED; + rtStrmUnlock(pStream); + return VINF_SUCCESS; +#endif +} + + +#ifdef RTSTREAM_STANDALONE + +/** + * Deals with NIL_RTFILE in rtStrmGetFile. + */ +DECL_NO_INLINE(static, RTFILE) rtStrmGetFileNil(PRTSTREAM pStream) +{ +# ifdef RT_OS_WINDOWS + DWORD dwStdHandle; + switch (pStream->enmStdHandle) + { + case RTHANDLESTD_INPUT: dwStdHandle = STD_INPUT_HANDLE; break; + case RTHANDLESTD_OUTPUT: dwStdHandle = STD_OUTPUT_HANDLE; break; + case RTHANDLESTD_ERROR: dwStdHandle = STD_ERROR_HANDLE; break; + default: return NIL_RTFILE; + } + HANDLE hHandle = GetStdHandle(dwStdHandle); + if (hHandle != INVALID_HANDLE_VALUE && hHandle != NULL) + { + int rc = RTFileFromNative(&pStream->hFile, (uintptr_t)hHandle); + if (RT_SUCCESS(rc)) + { + /* Switch to full buffering if not a console handle. */ + DWORD dwMode; + if (!GetConsoleMode(hHandle, &dwMode)) + pStream->enmBufStyle = RTSTREAMBUFSTYLE_FULL; + + return pStream->hFile; + } + } + +# else + uintptr_t uNative; + switch (pStream->enmStdHandle) + { + case RTHANDLESTD_INPUT: uNative = RTFILE_NATIVE_STDIN; break; + case RTHANDLESTD_OUTPUT: uNative = RTFILE_NATIVE_STDOUT; break; + case RTHANDLESTD_ERROR: uNative = RTFILE_NATIVE_STDERR; break; + default: return NIL_RTFILE; + } + int rc = RTFileFromNative(&pStream->hFile, uNative); + if (RT_SUCCESS(rc)) + { + /* Switch to full buffering if not a console handle. */ + if (!isatty((int)uNative)) + pStream->enmBufStyle = RTSTREAMBUFDIR_FULL; + + return pStream->hFile; + } + +# endif + return NIL_RTFILE; +} + + +/** + * For lazily resolving handles for the standard streams. + */ +DECLINLINE(RTFILE) rtStrmGetFile(PRTSTREAM pStream) +{ + RTFILE hFile = pStream->hFile; + if (hFile != NIL_RTFILE) + return hFile; + return rtStrmGetFileNil(pStream); +} + + +RTR3DECL(int) RTStrmQueryFileHandle(PRTSTREAM pStream, PRTFILE phFile) +{ + AssertPtrReturn(phFile, VERR_INVALID_POINTER); + *phFile = NIL_RTFILE; + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + + rtStrmLock(pStream); + RTFILE hFile = rtStrmGetFile(pStream); + rtStrmUnlock(pStream); + if (hFile != NIL_RTFILE) + { + *phFile = hFile; + return VINF_SUCCESS; + } + return VERR_NOT_AVAILABLE; +} + +#endif /* RTSTREAM_STANDALONE */ + + +/** + * Wrapper around isatty, assumes caller takes care of stream locking/whatever + * is needed. + */ +DECLINLINE(bool) rtStrmIsTerminal(PRTSTREAM pStream) +{ +#ifdef RTSTREAM_STANDALONE + RTFILE hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + HANDLE hNative = (HANDLE)RTFileToNative(hFile); + DWORD dwType = GetFileType(hNative); + if (dwType == FILE_TYPE_CHAR) + { + DWORD dwMode; + if (GetConsoleMode(hNative, &dwMode)) + return true; + } + } + return false; + +#else + if (pStream->pFile) + { + int fh = fileno(pStream->pFile); + if (isatty(fh) != 0) + { +# ifdef RT_OS_WINDOWS + DWORD dwMode; + HANDLE hCon = (HANDLE)_get_osfhandle(fh); + if (GetConsoleMode(hCon, &dwMode)) + return true; +# else + return true; +# endif + } + } + return false; +#endif +} + + +static int rtStrmInputGetEchoCharsNative(uintptr_t hNative, bool *pfEchoChars) +{ +#ifdef RT_OS_WINDOWS + DWORD dwMode; + if (GetConsoleMode((HANDLE)hNative, &dwMode)) + *pfEchoChars = RT_BOOL(dwMode & ENABLE_ECHO_INPUT); + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_INVALID_HANDLE) + return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE; + return RTErrConvertFromWin32(dwErr); + } +#else + struct termios Termios; + int rcPosix = tcgetattr((int)hNative, &Termios); + if (!rcPosix) + *pfEchoChars = RT_BOOL(Termios.c_lflag & ECHO); + else + return errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno); +#endif + return VINF_SUCCESS; +} + + + +RTR3DECL(int) RTStrmInputGetEchoChars(PRTSTREAM pStream, bool *pfEchoChars) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfEchoChars, VERR_INVALID_POINTER); + +#ifdef RTSTREAM_STANDALONE + return rtStrmInputGetEchoCharsNative(RTFileToNative(pStream->hFile), pfEchoChars); +#else + int rc; + int fh = fileno(pStream->pFile); + if (isatty(fh)) + { +# ifdef RT_OS_WINDOWS + rc = rtStrmInputGetEchoCharsNative(_get_osfhandle(fh), pfEchoChars); +# else + rc = rtStrmInputGetEchoCharsNative(fh, pfEchoChars); +# endif + } + else + rc = VERR_INVALID_FUNCTION; + return rc; +#endif +} + + +static int rtStrmInputSetEchoCharsNative(uintptr_t hNative, bool fEchoChars) +{ + int rc; +#ifdef RT_OS_WINDOWS + DWORD dwMode; + if (GetConsoleMode((HANDLE)hNative, &dwMode)) + { + if (fEchoChars) + dwMode |= ENABLE_ECHO_INPUT; + else + dwMode &= ~ENABLE_ECHO_INPUT; + if (SetConsoleMode((HANDLE)hNative, dwMode)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_INVALID_HANDLE) + return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE; + return RTErrConvertFromWin32(dwErr); + } +#else + struct termios Termios; + int rcPosix = tcgetattr((int)hNative, &Termios); + if (!rcPosix) + { + if (fEchoChars) + Termios.c_lflag |= ECHO; + else + Termios.c_lflag &= ~ECHO; + + rcPosix = tcsetattr((int)hNative, TCSAFLUSH, &Termios); + if (rcPosix == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + } + else + rc = errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno); +#endif + return rc; +} + + +RTR3DECL(int) RTStrmInputSetEchoChars(PRTSTREAM pStream, bool fEchoChars) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + return rtStrmInputSetEchoCharsNative(RTFileToNative(pStream->hFile), fEchoChars); +#else + int rc; + int fh = fileno(pStream->pFile); + if (isatty(fh)) + { +# ifdef RT_OS_WINDOWS + rc = rtStrmInputSetEchoCharsNative(_get_osfhandle(fh), fEchoChars); +# else + rc = rtStrmInputSetEchoCharsNative(fh, fEchoChars); +# endif + } + else + rc = VERR_INVALID_FUNCTION; + return rc; +#endif +} + + +RTR3DECL(bool) RTStrmIsTerminal(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, false); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, false); + + return rtStrmIsTerminal(pStream); +} + + +RTR3DECL(int) RTStrmQueryTerminalWidth(PRTSTREAM pStream, uint32_t *pcchWidth) +{ + AssertPtrReturn(pcchWidth, VERR_INVALID_HANDLE); + *pcchWidth = 80; + + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + + if (rtStrmIsTerminal(pStream)) + { +#ifdef RT_OS_WINDOWS +# ifdef RTSTREAM_STANDALONE + HANDLE hCon = (HANDLE)RTFileToNative(pStream->hFile); +# else + HANDLE hCon = (HANDLE)_get_osfhandle(fileno(pStream->pFile)); +# endif + CONSOLE_SCREEN_BUFFER_INFO Info; + RT_ZERO(Info); + if (GetConsoleScreenBufferInfo(hCon, &Info)) + { + *pcchWidth = Info.dwSize.X ? Info.dwSize.X : 80; + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(GetLastError()); + +#elif defined(RT_OS_OS2) && !defined(TIOCGWINSZ) /* only OS/2 should currently miss this */ + return VINF_SUCCESS; /* just pretend for now. */ + +#else + struct winsize Info; + RT_ZERO(Info); + int rc = ioctl(fileno(pStream->pFile), TIOCGWINSZ, &Info); + if (rc >= 0) + { + *pcchWidth = Info.ws_col ? Info.ws_col : 80; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +#endif + } + return VERR_INVALID_FUNCTION; +} + + +#ifdef RTSTREAM_STANDALONE + +DECLINLINE(void) rtStrmBufInvalidate(PRTSTREAM pStream) +{ + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + pStream->offBufEnd = 0; + pStream->offBufFirst = 0; +} + + +static int rtStrmBufFlushWrite(PRTSTREAM pStream, size_t cbToFlush) +{ + Assert(cbToFlush <= pStream->offBufEnd - pStream->offBufFirst); + + RTFILE const hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + /** @todo do nonblocking & incomplete writes? */ + size_t offBufFirst = pStream->offBufFirst; + int rc = RTFileWrite(hFile, &pStream->pchBuf[offBufFirst], cbToFlush, NULL); + if (RT_SUCCESS(rc)) + { + offBufFirst += cbToFlush; + if (offBufFirst >= pStream->offBufEnd) + pStream->offBufEnd = 0; + else + { + /* Shift up the remaining content so the next write can take full + advantage of the buffer size. */ + size_t cbLeft = pStream->offBufEnd - offBufFirst; + memmove(pStream->pchBuf, &pStream->pchBuf[offBufFirst], cbLeft); + pStream->offBufEnd = cbLeft; + } + pStream->offBufFirst = 0; + return VINF_SUCCESS; + } + return rc; + } + return VERR_INVALID_HANDLE; +} + + +static int rtStrmBufFlushWriteMaybe(PRTSTREAM pStream, bool fInvalidate) +{ + if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE) + { + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer > 0) + { + int rc = rtStrmBufFlushWrite(pStream, cbInBuffer); + if (fInvalidate) + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + return rc; + } + } + if (fInvalidate) + rtStrmBufInvalidate(pStream); + return VINF_SUCCESS; +} + + +/** + * Worker for rtStrmBufCheckErrorAndSwitchToReadMode and + * rtStrmBufCheckErrorAndSwitchToWriteMode that allocates a buffer. + * + * Only updates cbBufAlloc and pchBuf, callers deals with error fallout. + */ +static int rtStrmBufAlloc(PRTSTREAM pStream) +{ + size_t cbBuf = pStream->enmBufStyle == RTSTREAMBUFSTYLE_FULL ? _64K : _16K; + do + { + pStream->pchBuf = (char *)RTMemAllocZ(cbBuf); + if (RT_LIKELY(pStream->pchBuf)) + { +# ifdef RTSTREAM_WITH_TEXT_MODE + Assert(RT_ALIGN_Z(cbBuf, 64 / 8) == cbBuf); + pStream->pbmBuf = (uint32_t *)RTMemAllocZ(cbBuf / 8); + if (RT_LIKELY(pStream->pbmBuf)) +# endif + { + pStream->cbBufAlloc = cbBuf; + return VINF_SUCCESS; + } +# ifdef RTSTREAM_WITH_TEXT_MODE + RTMemFree(pStream->pchBuf); + pStream->pchBuf = NULL; +# endif + } + cbBuf /= 2; + } while (cbBuf >= 256); + return VERR_NO_MEMORY; +} + + +/** + * Checks the stream error status, flushed any pending writes, ensures there is + * a buffer allocated and switches the stream to the read direction. + * + * @returns IPRT status code (same as i32Error). + * @param pStream The stream. + */ +static int rtStrmBufCheckErrorAndSwitchToReadMode(PRTSTREAM pStream) +{ + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + /* + * We're very likely already in read mode and can return without doing + * anything here. + */ + if (pStream->enmBufDir == RTSTREAMBUFDIR_READ) + return VINF_SUCCESS; + + /* + * Flush any pending writes before switching the buffer to read: + */ + rc = rtStrmBufFlushWriteMaybe(pStream, false /*fInvalidate*/); + if (RT_SUCCESS(rc)) + { + pStream->enmBufDir = RTSTREAMBUFDIR_READ; + pStream->offBufEnd = 0; + pStream->offBufFirst = 0; + pStream->fPendingCr = false; + + /* + * Read direction implies a buffer, so make sure we've got one and + * change to NONE direction if allocating one fails. + */ + if (pStream->pchBuf) + { + Assert(pStream->cbBufAlloc >= 256); + return VINF_SUCCESS; + } + + rc = rtStrmBufAlloc(pStream); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + } + ASMAtomicWriteS32(&pStream->i32Error, rc); + } + return rc; +} + + +/** + * Checks the stream error status, ensures there is a buffer allocated and + * switches the stream to the write direction. + * + * @returns IPRT status code (same as i32Error). + * @param pStream The stream. + */ +static int rtStrmBufCheckErrorAndSwitchToWriteMode(PRTSTREAM pStream) +{ + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + /* + * We're very likely already in write mode and can return without doing + * anything here. + */ + if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE) + return VINF_SUCCESS; + + /* + * A read buffer does not need any flushing, so we just have to make + * sure there is a buffer present before switching to the write direction. + */ + pStream->enmBufDir = RTSTREAMBUFDIR_WRITE; + pStream->offBufEnd = 0; + pStream->offBufFirst = 0; + if (pStream->pchBuf) + { + Assert(pStream->cbBufAlloc >= 256); + return VINF_SUCCESS; + } + + rc = rtStrmBufAlloc(pStream); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + ASMAtomicWriteS32(&pStream->i32Error, rc); + } + return rc; +} + + +/** + * Reads more bytes into the buffer. + * + * @returns IPRT status code (same as i32Error). + * @param pStream The stream. + */ +static int rtStrmBufFill(PRTSTREAM pStream) +{ + /* + * Check preconditions + */ + Assert(pStream->i32Error == VINF_SUCCESS); + Assert(pStream->enmBufDir == RTSTREAMBUFDIR_READ); + AssertPtr(pStream->pchBuf); + Assert(pStream->cbBufAlloc >= 256); + Assert(RT_ALIGN_Z(pStream->cbBufAlloc, 64) == pStream->cbBufAlloc); + Assert(pStream->offBufFirst <= pStream->cbBufAlloc); + Assert(pStream->offBufEnd <= pStream->cbBufAlloc); + Assert(pStream->offBufFirst <= pStream->offBufEnd); +# ifdef RTSTREAM_WITH_TEXT_MODE + AssertPtr(pStream->pbmBuf); +# endif + /* + * If there is data in the buffer, move it up to the start. + */ + size_t cbInBuffer; + if (!pStream->offBufFirst) + cbInBuffer = pStream->offBufEnd; + else + { + cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer) + { + memmove(pStream->pchBuf, &pStream->pchBuf[pStream->offBufFirst], cbInBuffer); +# ifdef RTSTREAM_WITH_TEXT_MODE + if (!pStream->fBinary) /** @todo this isn't very efficient, must be a better way of shifting a bitmap. */ + for (size_t off = 0; off < pStream->offBufFirst; off++) + if (ASMBitTest(pStream->pbmBuf, (int32_t)off)) + ASMBitSet(pStream->pbmBuf, (int32_t)off); + else + ASMBitClear(pStream->pbmBuf, (int32_t)off); +# endif + } + pStream->offBufFirst = 0; + pStream->offBufEnd = cbInBuffer; + } + + /* + * Add pending CR to the buffer. + */ + size_t const offCrLfConvStart = cbInBuffer; + Assert(cbInBuffer + 2 <= pStream->cbBufAlloc); + if (!pStream->fPendingCr || pStream->fBinary) + { /* likely */ } + else + { + pStream->pchBuf[cbInBuffer] = '\r'; + pStream->fPendingCr = false; + pStream->offBufEnd = ++cbInBuffer; + } + + /* + * Read data till the buffer is full. + */ + int rc = VERR_INVALID_HANDLE; + RTFILE const hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + size_t cbRead = 0; + rc = RTFileRead(hFile, &pStream->pchBuf[cbInBuffer], pStream->cbBufAlloc - cbInBuffer, &cbRead); + if (RT_SUCCESS(rc)) + { + cbInBuffer += cbRead; + pStream->offBufEnd = cbInBuffer; + + if (cbInBuffer != 0) + { +# ifdef RTSTREAM_WITH_TEXT_MODE + if (pStream->fBinary) +# endif + return VINF_SUCCESS; + } + else + { + /** @todo this shouldn't be sticky, should it? */ + ASMAtomicWriteS32(&pStream->i32Error, VERR_EOF); + return VERR_EOF; + } + +# ifdef RTSTREAM_WITH_TEXT_MODE + /* + * Do CRLF -> LF conversion in the buffer. + */ + ASMBitClearRange(pStream->pbmBuf, offCrLfConvStart, RT_ALIGN_Z(cbInBuffer, 64)); + char *pchCur = &pStream->pchBuf[offCrLfConvStart]; + size_t cbLeft = cbInBuffer - offCrLfConvStart; + while (cbLeft > 0) + { + Assert(&pchCur[cbLeft] == &pStream->pchBuf[pStream->offBufEnd]); + char *pchCr = (char *)memchr(pchCur, '\r', cbLeft); + if (pchCr) + { + size_t offCur = (size_t)(pchCr - pchCur); + if (offCur + 1 < cbLeft) + { + if (pchCr[1] == '\n') + { + /* Found one '\r\n' sequence. Look for more before shifting the buffer content. */ + cbLeft -= offCur; + pchCur = pchCr; + + do + { + ASMBitSet(pStream->pbmBuf, (int32_t)(pchCur - pStream->pchBuf)); + *pchCur++ = '\n'; /* dst */ + cbLeft -= 2; + pchCr += 2; /* src */ + } while (cbLeft >= 2 && pchCr[0] == '\r' && pchCr[1] == '\n'); + + memmove(&pchCur, pchCr, cbLeft); + } + else + { + cbLeft -= offCur + 1; + pchCur = pchCr + 1; + } + } + else + { + Assert(pchCr == &pStream->pchBuf[pStream->offBufEnd - 1]); + pStream->fPendingCr = true; + pStream->offBufEnd = --cbInBuffer; + break; + } + } + else + break; + } + + return VINF_SUCCESS; +# endif + } + } + + /* + * If there is data in the buffer, don't raise the error till it has all + * been consumed, ASSUMING that another fill call will follow and that the + * error condition will reoccur then. + * + * Note! We may currently end up not converting a CRLF pair, if it's + * split over a temporary EOF condition, since we forces the caller + * to read the CR before requesting more data. However, it's not a + * very likely scenario, so we'll just leave it like that for now. + */ + if (cbInBuffer) + return VINF_SUCCESS; + ASMAtomicWriteS32(&pStream->i32Error, rc); + return rc; +} + + +/** + * Copies @a cbSrc bytes from @a pvSrc and into the buffer, flushing as needed + * to make space available. + * + * + * @returns IPRT status code (errors not assigned to i32Error). + * @param pStream The stream. + * @param pvSrc The source buffer. + * @param cbSrc Number of bytes to copy from @a pvSrc. + * @param pcbTotal A total counter to update with what was copied. + */ +static int rtStrmBufCopyTo(PRTSTREAM pStream, const void *pvSrc, size_t cbSrc, size_t *pcbTotal) +{ + Assert(cbSrc > 0); + for (;;) + { + size_t cbToCopy = RT_MIN(pStream->cbBufAlloc - pStream->offBufEnd, cbSrc); + if (cbToCopy) + { + memcpy(&pStream->pchBuf[pStream->offBufEnd], pvSrc, cbToCopy); + pStream->offBufEnd += cbToCopy; + pvSrc = (const char *)pvSrc + cbToCopy; + *pcbTotal += cbToCopy; + cbSrc -= cbToCopy; + if (!cbSrc) + break; + } + + int rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** + * Worker for rtStrmFlushAndCloseAll and rtStrmFlushAndClose. + */ +static RTFILE rtStrmFlushAndCleanup(PRTSTREAM pStream) +{ + if (pStream->pchBuf) + { + if ( pStream->enmBufDir == RTSTREAMBUFDIR_WRITE + && pStream->offBufFirst < pStream->offBufEnd + && RT_SUCCESS(pStream->i32Error) ) + rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst); + RTMemFree(pStream->pchBuf); + pStream->pchBuf = NULL; + pStream->offBufFirst = 0; + pStream->offBufEnd = 0; +# ifdef RTSTREAM_WITH_TEXT_MODE + RTMemFree(pStream->pbmBuf); + pStream->pbmBuf = NULL; +# endif + } + + PRTCRITSECT pCritSect = pStream->pCritSect; + if (pCritSect) + { + pStream->pCritSect = NULL; + RTCritSectDelete(pCritSect); + RTMemFree(pCritSect); + } + + RTFILE hFile = pStream->hFile; + pStream->hFile = NIL_RTFILE; + return hFile; +} + + +/** + * Worker for rtStrmFlushAndCloseAll. + */ +static void rtStrmFlushAndClose(PRTSTREAM pStream) +{ + pStream->u32Magic = ~RTSTREAM_MAGIC; + RTFILE hFile = rtStrmFlushAndCleanup(pStream); + if (hFile != NIL_RTFILE) + RTFileClose(hFile); + RTMemFree(pStream); +} + + +/** + * Flushes and cleans up the standard streams, should flush and close all others + * too but doesn't yet... + */ +DECLCALLBACK(void) rtStrmFlushAndCloseAll(void) +{ + /* + * Flush the standard handles. + */ + rtStrmFlushAndCleanup(&g_StdOut); + rtStrmFlushAndCleanup(&g_StdErr); + rtStrmFlushAndCleanup(&g_StdIn); + + /* + * Make a list of the rest and flush+close those too. + */ + if (RTOnceWasInitialized(&g_StreamListOnce)) + { + RTCritSectDelete(&g_StreamListCritSect); + + PRTSTREAM pStream; + while ((pStream = RTListRemoveFirst(&g_StreamList, RTSTREAM, ListEntry)) != NULL) + rtStrmFlushAndClose(pStream); + + RTOnceReset(&g_StreamListOnce); + } +} + +# ifdef IPRT_COMPILER_TERM_CALLBACK +IPRT_COMPILER_TERM_CALLBACK(rtStrmFlushAndCloseAll); +# endif + +#endif /* RTSTREAM_STANDALONE */ + + +RTR3DECL(int) RTStrmRewind(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int const rc1 = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/); + int const rc2 = RTFileSeek(rtStrmGetFile(pStream), 0, RTFILE_SEEK_BEGIN, NULL); + int rc = RT_SUCCESS(rc1) ? rc2 : rc1; + ASMAtomicWriteS32(&pStream->i32Error, rc); + rtStrmUnlock(pStream); +#else + clearerr(pStream->pFile); + errno = 0; + int rc; + if (!fseek(pStream->pFile, 0, SEEK_SET)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + ASMAtomicWriteS32(&pStream->i32Error, rc); +#endif + return rc; +} + + +RTR3DECL(int) RTStrmSeek(PRTSTREAM pStream, RTFOFF off, uint32_t uMethod) +{ + AssertReturn(uMethod <= RTFILE_SEEK_END, VERR_INVALID_PARAMETER); +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/); + if (RT_SUCCESS(rc)) + rc = RTFileSeek(rtStrmGetFile(pStream), off, uMethod, NULL); + if (RT_FAILURE(rc)) + ASMAtomicWriteS32(&pStream->i32Error, rc); + rtStrmUnlock(pStream); +#else + int const iCrtMethod = uMethod == RTFILE_SEEK_BEGIN ? SEEK_SET : uMethod == RTFILE_SEEK_CURRENT ? SEEK_CUR : SEEK_END; + errno = 0; + int rc; +# ifdef _MSC_VER + if (!_fseeki64(pStream->pFile, off, iCrtMethod)) +# else + if (!fseeko(pStream->pFile, off, iCrtMethod)) +# endif + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + ASMAtomicWriteS32(&pStream->i32Error, rc); +#endif + return rc; +} + + +RTR3DECL(RTFOFF) RTStrmTell(PRTSTREAM pStream) +{ +#ifdef RTSTREAM_STANDALONE + uint64_t off = 0; + rtStrmLock(pStream); + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + RTFILE const hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + rc = RTFileSeek(hFile, 0, RTFILE_SEEK_CURRENT, &off); + if (RT_SUCCESS(rc)) + { + switch (pStream->enmBufDir) + { + case RTSTREAMBUFDIR_READ: + /* Subtract unconsumed chars and removed '\r' characters. */ + off -= pStream->offBufEnd - pStream->offBufFirst; + if (!pStream->fBinary) + for (size_t offBuf = pStream->offBufFirst; offBuf < pStream->offBufEnd; offBuf++) + off -= ASMBitTest(pStream->pbmBuf, (int32_t)offBuf); + break; + case RTSTREAMBUFDIR_WRITE: + /* Add unwrittend chars in the buffer. */ + off += pStream->offBufEnd - pStream->offBufFirst; + break; + default: + AssertFailed(); + case RTSTREAMBUFDIR_NONE: + break; + } + } + } + else + rc = VERR_INVALID_HANDLE; + } + if (RT_FAILURE(rc)) + { + ASMAtomicWriteS32(&pStream->i32Error, rc); + off = rc; + } + rtStrmUnlock(pStream); +#else +# ifdef _MSC_VER + RTFOFF off = _ftelli64(pStream->pFile); +# else + RTFOFF off = ftello(pStream->pFile); +# endif + if (off < 0) + { + int rc = RTErrConvertFromErrno(errno); + ASMAtomicWriteS32(&pStream->i32Error, rc); + off = rc; + } +#endif + return off; +} + + +/** + * Recheck the stream mode. + * + * @param pStream The stream (locked). + */ +static void rtStreamRecheckMode(PRTSTREAM pStream) +{ +#if (defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)) && !defined(RTSTREAM_STANDALONE) + int fh = fileno(pStream->pFile); + if (fh >= 0) + { + int fExpected = pStream->fBinary ? _O_BINARY : _O_TEXT; + int fActual = _setmode(fh, fExpected); + if (fActual != -1 && fExpected != (fActual & (_O_BINARY | _O_TEXT))) + { + fActual = _setmode(fh, fActual & (_O_BINARY | _O_TEXT)); + pStream->fBinary = !(fActual & _O_TEXT); + } + } +#else + NOREF(pStream); +#endif + pStream->fRecheckMode = false; +} + + +RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream); +#else + int rc = pStream->i32Error; +#endif + if (RT_SUCCESS(rc)) + { + if (pStream->fRecheckMode) + rtStreamRecheckMode(pStream); + +#ifdef RTSTREAM_STANDALONE + + /* + * Copy data thru the read buffer for now as that'll handle both binary + * and text modes seamlessly. We could optimize larger reads here when + * in binary mode, that can wait till the basics work, I think. + */ + size_t cbTotal = 0; + if (cbToRead > 0) + for (;;) + { + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer > 0) + { + size_t cbToCopy = RT_MIN(cbInBuffer, cbToRead); + memcpy(pvBuf, &pStream->pchBuf[pStream->offBufFirst], cbToCopy); + cbTotal += cbToRead; + cbToRead -= cbToCopy; + pvBuf = (char *)pvBuf + cbToCopy; + if (!cbToRead) + break; + } + rc = rtStrmBufFill(pStream); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + { + if (rc == VERR_EOF && pcbRead && cbTotal > 0) + rc = VINF_EOF; + break; + } + } + if (pcbRead) + *pcbRead = cbTotal; + +#else /* !RTSTREAM_STANDALONE */ + if (pcbRead) + { + /* + * Can do with a partial read. + */ + *pcbRead = fread(pvBuf, 1, cbToRead, pStream->pFile); + if ( *pcbRead == cbToRead + || !ferror(pStream->pFile)) + rc = VINF_SUCCESS; + else if (feof(pStream->pFile)) + rc = *pcbRead ? VINF_EOF : VERR_EOF; + else if (ferror(pStream->pFile)) + rc = VERR_READ_ERROR; + else + { + AssertMsgFailed(("This shouldn't happen\n")); + rc = VERR_INTERNAL_ERROR; + } + } + else + { + /* + * Must read it all! + */ + if (fread(pvBuf, cbToRead, 1, pStream->pFile) == 1) + rc = VINF_SUCCESS; + /* possible error/eof. */ + else if (feof(pStream->pFile)) + rc = VERR_EOF; + else if (ferror(pStream->pFile)) + rc = VERR_READ_ERROR; + else + { + AssertMsgFailed(("This shouldn't happen\n")); + rc = VERR_INTERNAL_ERROR; + } + } +#endif /* !RTSTREAM_STANDALONE */ + if (RT_FAILURE(rc)) + ASMAtomicWriteS32(&pStream->i32Error, rc); + } +#ifdef RTSTREAM_STANDALONE + rtStrmUnlock(pStream); +#endif + return rc; +} + + +/** + * Check if the input text is valid UTF-8. + * + * @returns true/false. + * @param pvBuf Pointer to the buffer. + * @param cbBuf Size of the buffer. + */ +static bool rtStrmIsUtf8Text(const void *pvBuf, size_t cbBuf) +{ + NOREF(pvBuf); + NOREF(cbBuf); + /** @todo not sure this is a good idea... Better redefine RTStrmWrite. */ + return false; +} + + +#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE) + +/** + * Check if the stream is for a Window console. + * + * @returns true / false. + * @param pStream The stream. + * @param phCon Where to return the console handle. + */ +static bool rtStrmIsConsoleUnlocked(PRTSTREAM pStream, HANDLE *phCon) +{ + int fh = fileno(pStream->pFile); + if (isatty(fh)) + { + DWORD dwMode; + HANDLE hCon = (HANDLE)_get_osfhandle(fh); + if (GetConsoleMode(hCon, &dwMode)) + { + *phCon = hCon; + return true; + } + } + return false; +} + + +static int rtStrmWriteWinConsoleLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, HANDLE hCon) +{ + int rc; +# ifdef HAVE_FWRITE_UNLOCKED + if (!fflush_unlocked(pStream->pFile)) +# else + if (!fflush(pStream->pFile)) +# endif + { + /** @todo Consider buffering later. For now, we'd rather correct output than + * fast output. */ + DWORD cwcWritten = 0; + PRTUTF16 pwszSrc = NULL; + size_t cwcSrc = 0; + rc = RTStrToUtf16Ex((const char *)pvBuf, cbToWrite, &pwszSrc, 0, &cwcSrc); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (!WriteConsoleW(hCon, pwszSrc, (DWORD)cwcSrc, &cwcWritten, NULL)) + { + /* try write char-by-char to avoid heap problem. */ + cwcWritten = 0; + while (cwcWritten != cwcSrc) + { + DWORD cwcThis; + if (!WriteConsoleW(hCon, &pwszSrc[cwcWritten], 1, &cwcThis, NULL)) + { + if (!pcbWritten || cwcWritten == 0) + rc = RTErrConvertFromErrno(GetLastError()); + break; + } + if (cwcThis != 1) /* Unable to write current char (amount)? */ + break; + cwcWritten++; + } + } + if (RT_SUCCESS(rc)) + { + if (cwcWritten == cwcSrc) + { + if (pcbWritten) + *pcbWritten = cbToWrite; + } + else if (pcbWritten) + { + PCRTUTF16 pwszCur = pwszSrc; + const char *pszCur = (const char *)pvBuf; + while ((uintptr_t)(pwszCur - pwszSrc) < cwcWritten) + { + RTUNICP CpIgnored; + RTUtf16GetCpEx(&pwszCur, &CpIgnored); + RTStrGetCpEx(&pszCur, &CpIgnored); + } + *pcbWritten = pszCur - (const char *)pvBuf; + } + else + rc = VERR_WRITE_ERROR; + } + RTUtf16Free(pwszSrc); + } + } + else + rc = RTErrConvertFromErrno(errno); + return rc; +} + +#endif /* RT_OS_WINDOWS */ + +static int rtStrmWriteWorkerLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fMustWriteAll) +{ +#ifdef RTSTREAM_STANDALONE + /* + * Check preconditions. + */ + Assert(pStream->enmBufDir == RTSTREAMBUFDIR_WRITE); + Assert(pStream->cbBufAlloc >= 256); + Assert(pStream->offBufFirst <= pStream->cbBufAlloc); + Assert(pStream->offBufEnd <= pStream->cbBufAlloc); + Assert(pStream->offBufFirst <= pStream->offBufEnd); + + /* + * We write everything via the buffer, letting the buffer flushing take + * care of console output hacks and similar. + */ + RT_NOREF(fMustWriteAll); + int rc = VINF_SUCCESS; + size_t cbTotal = 0; + if (cbToWrite > 0) + { +# ifdef RTSTREAM_WITH_TEXT_MODE + const char *pchLf; + if ( !pStream->fBinary + && (pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite)) != NULL) + for (;;) + { + /* Deal with everything up to the newline. */ + size_t const cbToLf = (size_t)(pchLf - (const char *)pvBuf); + if (cbToLf > 0) + { + rc = rtStrmBufCopyTo(pStream, pvBuf, cbToLf, &cbTotal); + if (RT_FAILURE(rc)) + break; + } + + /* Copy the CRLF sequence into the buffer in one go to avoid complications. */ + if (pStream->cbBufAlloc - pStream->offBufEnd < 2) + { + rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst); + if (RT_FAILURE(rc)) + break; + Assert(pStream->cbBufAlloc - pStream->offBufEnd >= 2); + } + pStream->pchBuf[pStream->offBufEnd++] = '\r'; + pStream->pchBuf[pStream->offBufEnd++] = '\n'; + + /* Advance past the newline. */ + pvBuf = (const char *)pvBuf + 1 + cbToLf; + cbTotal += 1 + cbToLf; + cbToWrite -= 1 + cbToLf; + if (!cbToWrite) + break; + + /* More newlines? */ + pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite); + if (!pchLf) + { + rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal); + break; + } + } + else +# endif + rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal); + + /* + * If line buffered or unbuffered, we probably have to do some flushing now. + */ + if (RT_SUCCESS(rc) && pStream->enmBufStyle != RTSTREAMBUFSTYLE_FULL) + { + Assert(pStream->enmBufStyle == RTSTREAMBUFSTYLE_LINE || pStream->enmBufStyle == RTSTREAMBUFSTYLE_UNBUFFERED); + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer > 0) + { + if ( pStream->enmBufStyle != RTSTREAMBUFSTYLE_LINE + || pStream->pchBuf[pStream->offBufEnd - 1] == '\n') + rc = rtStrmBufFlushWrite(pStream, cbInBuffer); + else + { + const char *pchToFlush = &pStream->pchBuf[pStream->offBufFirst]; + const char *pchLastLf = (const char *)memrchr(pchToFlush, '\n', cbInBuffer); + if (pchLastLf) + rc = rtStrmBufFlushWrite(pStream, (size_t)(&pchLastLf[1] - pchToFlush)); + } + } + } + } + if (pcbWritten) + *pcbWritten = cbTotal; + return rc; + + +#else + if (!fMustWriteAll) + { + IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */ +# ifdef HAVE_FWRITE_UNLOCKED + *pcbWritten = fwrite_unlocked(pvBuf, 1, cbToWrite, pStream->pFile); +# else + *pcbWritten = fwrite(pvBuf, 1, cbToWrite, pStream->pFile); +# endif + IPRT_ALIGNMENT_CHECKS_ENABLE(); + if ( *pcbWritten == cbToWrite +# ifdef HAVE_FWRITE_UNLOCKED + || !ferror_unlocked(pStream->pFile)) +# else + || !ferror(pStream->pFile)) +# endif + return VINF_SUCCESS; + } + else + { + /* Must write it all! */ + IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */ +# ifdef HAVE_FWRITE_UNLOCKED + size_t cbWritten = fwrite_unlocked(pvBuf, cbToWrite, 1, pStream->pFile); +# else + size_t cbWritten = fwrite(pvBuf, cbToWrite, 1, pStream->pFile); +# endif + if (pcbWritten) + *pcbWritten = cbWritten; + IPRT_ALIGNMENT_CHECKS_ENABLE(); + if (cbWritten == 1) + return VINF_SUCCESS; +# ifdef HAVE_FWRITE_UNLOCKED + if (!ferror_unlocked(pStream->pFile)) +# else + if (!ferror(pStream->pFile)) +# endif + return VINF_SUCCESS; /* WEIRD! But anyway... */ + } + return VERR_WRITE_ERROR; +#endif +} + + +/** + * Internal write API, stream lock already held. + * + * @returns IPRT status code. + * @param pStream The stream. + * @param pvBuf What to write. + * @param cbToWrite How much to write. + * @param pcbWritten Where to optionally return the number of bytes + * written. + * @param fSureIsText Set if we're sure this is UTF-8 text already. + */ +static int rtStrmWriteLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText) +{ +#ifdef RTSTREAM_STANDALONE + int rc = rtStrmBufCheckErrorAndSwitchToWriteMode(pStream); +#else + int rc = pStream->i32Error; +#endif + if (RT_FAILURE(rc)) + return rc; + if (pStream->fRecheckMode) + rtStreamRecheckMode(pStream); + +#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE) + /* + * Use the unicode console API when possible in order to avoid stuff + * getting lost in unnecessary code page translations. + */ + HANDLE hCon; + if (rtStrmIsConsoleUnlocked(pStream, &hCon)) + rc = rtStrmWriteWinConsoleLocked(pStream, pvBuf, cbToWrite, pcbWritten, hCon); +#else + if (0) { } +#endif /* RT_OS_WINDOWS && !RTSTREAM_STANDALONE */ + + /* + * If we're sure it's text output, convert it from UTF-8 to the current + * code page before printing it. + * + * Note! Partial writes are not supported in this scenario because we + * cannot easily report back a written length matching the input. + */ + /** @todo Skip this if the current code set is UTF-8. */ + else if ( pStream->fCurrentCodeSet + && !pStream->fBinary + && ( fSureIsText + || rtStrmIsUtf8Text(pvBuf, cbToWrite)) + ) + { + char *pszSrcFree = NULL; + const char *pszSrc = (const char *)pvBuf; + if (pszSrc[cbToWrite - 1]) + { + pszSrc = pszSrcFree = RTStrDupN(pszSrc, cbToWrite); + if (pszSrc == NULL) + rc = VERR_NO_STR_MEMORY; + } + if (RT_SUCCESS(rc)) + { + char *pszSrcCurCP; + rc = RTStrUtf8ToCurrentCP(&pszSrcCurCP, pszSrc); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + size_t cchSrcCurCP = strlen(pszSrcCurCP); + size_t cbWritten = 0; + rc = rtStrmWriteWorkerLocked(pStream, pszSrcCurCP, cchSrcCurCP, &cbWritten, true /*fMustWriteAll*/); + if (pcbWritten) + *pcbWritten = cbWritten == cchSrcCurCP ? cbToWrite : 0; + RTStrFree(pszSrcCurCP); + } + RTStrFree(pszSrcFree); + } + } + /* + * Otherwise, just write it as-is. + */ + else + rc = rtStrmWriteWorkerLocked(pStream, pvBuf, cbToWrite, pcbWritten, pcbWritten == NULL); + + /* + * Update error status on failure and return. + * + * We ignore failures from RTStrUtf8ToCurrentCP and RTStrToUtf16Ex regarding + * invalid UTF-8 encoding, as that's an input issue and shouldn't affect the + * stream state. + */ + if (RT_FAILURE(rc) && rc != VERR_INVALID_UTF8_ENCODING) + ASMAtomicWriteS32(&pStream->i32Error, rc); + return rc; +} + + +/** + * Internal write API. + * + * @returns IPRT status code. + * @param pStream The stream. + * @param pvBuf What to write. + * @param cbToWrite How much to write. + * @param pcbWritten Where to optionally return the number of bytes + * written. + * @param fSureIsText Set if we're sure this is UTF-8 text already. + */ +DECLINLINE(int) rtStrmWrite(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText) +{ + rtStrmLock(pStream); + int rc = rtStrmWriteLocked(pStream, pvBuf, cbToWrite, pcbWritten, fSureIsText); + rtStrmUnlock(pStream); + return rc; +} + + +RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER); + return rtStrmWrite(pStream, pvBuf, cbToWrite, pcbWritten, false); +} + + +RTR3DECL(int) RTStrmGetCh(PRTSTREAM pStream) +{ + unsigned char ch; + int rc = RTStrmReadEx(pStream, &ch, 1, NULL); + if (RT_SUCCESS(rc)) + return ch; + return -1; +} + + +RTR3DECL(int) RTStrmPutCh(PRTSTREAM pStream, int ch) +{ + return rtStrmWrite(pStream, &ch, 1, NULL, true /*fSureIsText*/); +} + + +RTR3DECL(int) RTStrmPutStr(PRTSTREAM pStream, const char *pszString) +{ + size_t cch = strlen(pszString); + return rtStrmWrite(pStream, pszString, cch, NULL, true /*fSureIsText*/); +} + + +RTR3DECL(int) RTStrmGetLine(PRTSTREAM pStream, char *pszString, size_t cbString) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pszString, VERR_INVALID_POINTER); + AssertReturn(cbString >= 2, VERR_INVALID_PARAMETER); + + rtStrmLock(pStream); + +#ifdef RTSTREAM_STANDALONE + int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream); +#else + int rc = pStream->i32Error; +#endif + if (RT_SUCCESS(rc)) + { + cbString--; /* Reserve space for the terminator. */ + +#ifdef RTSTREAM_STANDALONE + char * const pszStringStart = pszString; +#endif + for (;;) + { +#ifdef RTSTREAM_STANDALONE + /* Make sure there is at least one character in the buffer: */ + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer == 0) + { + rc = rtStrmBufFill(pStream); + if (RT_SUCCESS(rc)) + cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + else + break; + } + + /* Scan the buffer content terminating on a '\n', '\r\n' and '\0' sequence. */ + const char *pchSrc = &pStream->pchBuf[pStream->offBufFirst]; + const char *pchNewline = (const char *)memchr(pchSrc, '\n', cbInBuffer); + const char *pchTerm = (const char *)memchr(pchSrc, '\0', cbInBuffer); + size_t cbCopy; + size_t cbAdvance; + bool fStop = pchNewline || pchTerm; + if (!fStop) + cbAdvance = cbCopy = cbInBuffer; + else if (!pchTerm || (pchNewline && pchTerm && (uintptr_t)pchNewline < (uintptr_t)pchTerm)) + { + cbCopy = (size_t)(pchNewline - pchSrc); + cbAdvance = cbCopy + 1; + if (cbCopy && pchNewline[-1] == '\r') + cbCopy--; + else if (cbCopy == 0 && (uintptr_t)pszString > (uintptr_t)pszStringStart && pszString[-1] == '\r') + pszString--, cbString++; /* drop trailing '\r' that it turns out was followed by '\n' */ + } + else + { + cbCopy = (size_t)(pchTerm - pchSrc); + cbAdvance = cbCopy + 1; + } + + /* Adjust for available space in the destination buffer, copy over the string + characters and advance the buffer position (even on overflow). */ + if (cbCopy <= cbString) + pStream->offBufFirst += cbAdvance; + else + { + rc = VERR_BUFFER_OVERFLOW; + fStop = true; + cbCopy = cbString; + pStream->offBufFirst += cbString; + } + + memcpy(pszString, pchSrc, cbCopy); + pszString += cbCopy; + cbString -= cbCopy; + + if (fStop) + break; + +#else /* !RTSTREAM_STANDALONE */ +# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */ + int ch = fgetc_unlocked(pStream->pFile); +# else + int ch = fgetc(pStream->pFile); +# endif + + /* Deal with \r\n sequences here. We'll return lone CR, but + treat CRLF as LF. */ + if (ch == '\r') + { +# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */ + ch = fgetc_unlocked(pStream->pFile); +# else + ch = fgetc(pStream->pFile); +# endif + if (ch == '\n') + break; + + *pszString++ = '\r'; + if (--cbString <= 0) + { + /* yeah, this is an error, we dropped a character. */ + rc = VERR_BUFFER_OVERFLOW; + break; + } + } + + /* Deal with end of file. */ + if (ch == EOF) + { +# ifdef HAVE_FWRITE_UNLOCKED + if (feof_unlocked(pStream->pFile)) +# else + if (feof(pStream->pFile)) +# endif + { + rc = VERR_EOF; + break; + } +# ifdef HAVE_FWRITE_UNLOCKED + if (ferror_unlocked(pStream->pFile)) +# else + if (ferror(pStream->pFile)) +# endif + rc = VERR_READ_ERROR; + else + { + AssertMsgFailed(("This shouldn't happen\n")); + rc = VERR_INTERNAL_ERROR; + } + break; + } + + /* Deal with null terminator and (lone) new line. */ + if (ch == '\0' || ch == '\n') + break; + + /* No special character, append it to the return string. */ + *pszString++ = ch; + if (--cbString <= 0) + { + rc = VINF_BUFFER_OVERFLOW; + break; + } +#endif /* !RTSTREAM_STANDALONE */ + } + + *pszString = '\0'; + if (RT_FAILURE(rc)) + ASMAtomicWriteS32(&pStream->i32Error, rc); + } + + rtStrmUnlock(pStream); + return rc; +} + + +RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/); + rtStrmUnlock(pStream); + return rc; + +#else + if (!fflush(pStream->pFile)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +#endif +} + + +/** + * Output callback. + * + * @returns number of bytes written. + * @param pvArg User argument. + * @param pachChars Pointer to an array of utf-8 characters. + * @param cchChars Number of bytes in the character array pointed to by pachChars. + */ +static DECLCALLBACK(size_t) rtstrmOutput(void *pvArg, const char *pachChars, size_t cchChars) +{ + if (cchChars) + rtStrmWriteLocked((PRTSTREAM)pvArg, pachChars, cchChars, NULL, true /*fSureIsText*/); + /* else: ignore termination call. */ + return cchChars; +} + + +RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args) +{ + AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER); + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + rtStrmLock(pStream); +// pStream->fShouldFlush = true; + rc = (int)RTStrFormatV(rtstrmOutput, pStream, NULL, NULL, pszFormat, args); + rtStrmUnlock(pStream); + Assert(rc >= 0); + } + else + rc = -1; + return rc; +} + + +RTR3DECL(int) RTStrmPrintf(PRTSTREAM pStream, const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + int rc = RTStrmPrintfV(pStream, pszFormat, args); + va_end(args); + return rc; +} + + +RTDECL(void) RTStrmDumpPrintfV(void *pvUser, const char *pszFormat, va_list va) +{ + RTStrmPrintfV(pvUser ? (PRTSTREAM)pvUser : g_pStdOut, pszFormat, va); +} + + +RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list args) +{ + return RTStrmPrintfV(g_pStdOut, pszFormat, args); +} + + +RTR3DECL(int) RTPrintf(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + int rc = RTStrmPrintfV(g_pStdOut, pszFormat, args); + va_end(args); + return rc; +} + + +/** + * Outputs @a cchIndent spaces. + */ +static void rtStrmWrapppedIndent(RTSTRMWRAPPEDSTATE *pState, uint32_t cchIndent) +{ + static const char s_szSpaces[] = " "; + while (cchIndent) + { + uint32_t cchToWrite = RT_MIN(cchIndent, sizeof(s_szSpaces) - 1); + int rc = RTStrmWrite(pState->pStream, s_szSpaces, cchToWrite); + if (RT_SUCCESS(rc)) + cchIndent -= cchToWrite; + else + { + pState->rcStatus = rc; + break; + } + } +} + + +/** + * Flushes the current line. + * + * @param pState The wrapped output state. + * @param fPartial Set if partial flush due to buffer overflow, clear when + * flushing due to '\n'. + */ +static void rtStrmWrappedFlushLine(RTSTRMWRAPPEDSTATE *pState, bool fPartial) +{ + /* + * Check indentation in case we need to split the line later. + */ + uint32_t cchIndent = pState->cchIndent; + if (cchIndent == UINT32_MAX) + { + pState->cchIndent = 0; + cchIndent = pState->cchHangingIndent; + while (RT_C_IS_BLANK(pState->szLine[cchIndent])) + cchIndent++; + } + + /* + * Do the flushing. + */ + uint32_t cchLine = pState->cchLine; + Assert(cchLine < sizeof(pState->szLine)); + while (cchLine >= pState->cchWidth || !fPartial) + { + /* + * Hopefully we don't need to do any wrapping ... + */ + uint32_t offSplit; + if (pState->cchIndent + cchLine <= pState->cchWidth) + { + if (!fPartial) + { + rtStrmWrapppedIndent(pState, pState->cchIndent); + pState->szLine[cchLine] = '\n'; + int rc = RTStrmWrite(pState->pStream, pState->szLine, cchLine + 1); + if (RT_FAILURE(rc)) + pState->rcStatus = rc; + pState->cLines += 1; + pState->cchLine = 0; + pState->cchIndent = UINT32_MAX; + return; + } + + /* + * ... no such luck. + */ + offSplit = cchLine; + } + else + offSplit = pState->cchWidth - pState->cchIndent; + + /* Find the start of the current word: */ + while (offSplit > 0 && !RT_C_IS_BLANK(pState->szLine[offSplit - 1])) + offSplit--; + + /* Skip spaces. */ + while (offSplit > 0 && RT_C_IS_BLANK(pState->szLine[offSplit - 1])) + offSplit--; + uint32_t offNextLine = offSplit; + + /* If the first word + indent is wider than the screen width, so just output it in full. */ + if (offSplit == 0) /** @todo Split words, look for hyphen... This code is currently a bit crude. */ + { + while (offSplit < cchLine && !RT_C_IS_BLANK(pState->szLine[offSplit])) + offSplit++; + offNextLine = offSplit; + } + + while (offNextLine < cchLine && RT_C_IS_BLANK(pState->szLine[offNextLine])) + offNextLine++; + + /* + * Output and advance. + */ + rtStrmWrapppedIndent(pState, pState->cchIndent); + int rc = RTStrmWrite(pState->pStream, pState->szLine, offSplit); + if (RT_SUCCESS(rc)) + rc = RTStrmPutCh(pState->pStream, '\n'); + if (RT_FAILURE(rc)) + pState->rcStatus = rc; + + cchLine -= offNextLine; + pState->cchLine = cchLine; + pState->cLines += 1; + pState->cchIndent = cchIndent; + memmove(&pState->szLine[0], &pState->szLine[offNextLine], cchLine); + } + + /* The indentation level is reset for each '\n' we process, so only save cchIndent if partial. */ + pState->cchIndent = fPartial ? cchIndent : UINT32_MAX; +} + + +/** + * @callback_method_impl{FNRTSTROUTPUT} + */ +static DECLCALLBACK(size_t) rtStrmWrappedOutput(void *pvArg, const char *pachChars, size_t cbChars) +{ + RTSTRMWRAPPEDSTATE *pState = (RTSTRMWRAPPEDSTATE *)pvArg; + size_t const cchRet = cbChars; + while (cbChars > 0) + { + if (*pachChars == '\n') + { + rtStrmWrappedFlushLine(pState, false /*fPartial*/); + pachChars++; + cbChars--; + } + else + { + const char *pszEol = (const char *)memchr(pachChars, '\n', cbChars); + size_t cchToCopy = pszEol ? (size_t)(pszEol - pachChars) : cbChars; + uint32_t cchLine = pState->cchLine; + Assert(cchLine < sizeof(pState->szLine)); + bool const fFlush = cchLine + cchToCopy >= sizeof(pState->szLine); + if (fFlush) + cchToCopy = cchToCopy - sizeof(pState->szLine) - 1; + + pState->cchLine = cchLine + (uint32_t)cchToCopy; + memcpy(&pState->szLine[cchLine], pachChars, cchToCopy); + + pachChars += cchToCopy; + cbChars -= cchToCopy; + + if (fFlush) + rtStrmWrappedFlushLine(pState, true /*fPartial*/); + } + } + return cchRet; +} + + +RTDECL(int32_t) RTStrmWrappedPrintfV(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, va_list va) +{ + /* + * Figure the output width and set up the rest of the output state. + */ + RTSTRMWRAPPEDSTATE State; + State.pStream = pStream; + State.cchLine = fFlags & RTSTRMWRAPPED_F_LINE_OFFSET_MASK; + State.cLines = 0; + State.rcStatus = VINF_SUCCESS; + State.cchIndent = UINT32_MAX; + State.cchHangingIndent = 0; + if (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT) + { + State.cchHangingIndent = (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT_MASK) >> RTSTRMWRAPPED_F_HANGING_INDENT_SHIFT; + if (!State.cchHangingIndent) + State.cchHangingIndent = 4; + } + + int rc = RTStrmQueryTerminalWidth(pStream, &State.cchWidth); + if (RT_SUCCESS(rc)) + State.cchWidth = RT_MIN(State.cchWidth, RTSTRMWRAPPED_F_LINE_OFFSET_MASK + 1); + else + { + State.cchWidth = (uint32_t)fFlags & RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK; + if (!State.cchWidth) + State.cchWidth = 80; + } + if (State.cchWidth < 32) + State.cchWidth = 32; + //State.cchWidth -= 1; /* necessary here? */ + + /* + * Do the formatting. + */ + RTStrFormatV(rtStrmWrappedOutput, &State, NULL, NULL, pszFormat, va); + + /* + * Returning is simple if the buffer is empty. Otherwise we'll have to + * perform a partial flush and write out whatever is left ourselves. + */ + if (RT_SUCCESS(State.rcStatus)) + { + if (State.cchLine == 0) + return State.cLines << 16; + + rtStrmWrappedFlushLine(&State, true /*fPartial*/); + if (RT_SUCCESS(State.rcStatus) && State.cchLine > 0) + { + rtStrmWrapppedIndent(&State, State.cchIndent); + State.rcStatus = RTStrmWrite(State.pStream, State.szLine, State.cchLine); + } + if (RT_SUCCESS(State.rcStatus)) + return RT_MIN(State.cchIndent + State.cchLine, RTSTRMWRAPPED_F_LINE_OFFSET_MASK) | (State.cLines << 16); + } + return State.rcStatus; +} + + +RTDECL(int32_t) RTStrmWrappedPrintf(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int32_t rcRet = RTStrmWrappedPrintfV(pStream, fFlags, pszFormat, va); + va_end(va); + return rcRet; +} + diff --git a/src/VBox/Runtime/r3/tcp.cpp b/src/VBox/Runtime/r3/tcp.cpp new file mode 100644 index 00000000..8e9988a6 --- /dev/null +++ b/src/VBox/Runtime/r3/tcp.cpp @@ -0,0 +1,1082 @@ +/* $Id: tcp.cpp $ */ +/** @file + * IPRT - TCP/IP. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/winsock2.h> +#else +# include <sys/types.h> +# include <sys/socket.h> +# include <errno.h> +# include <netinet/in.h> +# include <netinet/tcp.h> +# include <arpa/inet.h> +# include <netdb.h> +# ifdef FIX_FOR_3_2 +# include <fcntl.h> +# endif +#endif +#include <limits.h> + +#include "internal/iprt.h" +#include <iprt/tcp.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mempool.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/socket.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "internal/magics.h" +#include "internal/socket.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* non-standard linux stuff (it seems). */ +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif +#ifndef SHUT_RDWR +# ifdef SD_BOTH +# define SHUT_RDWR SD_BOTH +# else +# define SHUT_RDWR 2 +# endif +#endif +#ifndef SHUT_WR +# ifdef SD_SEND +# define SHUT_WR SD_SEND +# else +# define SHUT_WR 1 +# endif +#endif + +/* fixup backlevel OSes. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) +# define socklen_t int +#endif + +/** How many pending connection. */ +#define RTTCP_SERVER_BACKLOG 10 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * TCP Server state. + */ +typedef enum RTTCPSERVERSTATE +{ + /** Invalid. */ + RTTCPSERVERSTATE_INVALID = 0, + /** Created. */ + RTTCPSERVERSTATE_CREATED, + /** Listener thread is starting up. */ + RTTCPSERVERSTATE_STARTING, + /** Accepting client connections. */ + RTTCPSERVERSTATE_ACCEPTING, + /** Serving a client. */ + RTTCPSERVERSTATE_SERVING, + /** Listener terminating. */ + RTTCPSERVERSTATE_STOPPING, + /** Listener terminated. */ + RTTCPSERVERSTATE_STOPPED, + /** Listener cleans up. */ + RTTCPSERVERSTATE_DESTROYING +} RTTCPSERVERSTATE; + +/* + * Internal representation of the TCP Server handle. + */ +typedef struct RTTCPSERVER +{ + /** The magic value (RTTCPSERVER_MAGIC). */ + uint32_t volatile u32Magic; + /** The server state. */ + RTTCPSERVERSTATE volatile enmState; + /** The server thread. */ + RTTHREAD Thread; + /** The server socket. */ + RTSOCKET volatile hServerSocket; + /** The socket to the client currently being serviced. + * This is NIL_RTSOCKET when no client is serviced. */ + RTSOCKET volatile hClientSocket; + /** The connection function. */ + PFNRTTCPSERVE pfnServe; + /** Argument to pfnServer. */ + void *pvUser; +} RTTCPSERVER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) rtTcpServerThread(RTTHREAD ThreadSelf, void *pvServer); +static int rtTcpServerListen(PRTTCPSERVER pServer); +static int rtTcpServerListenCleanup(PRTTCPSERVER pServer); +static int rtTcpClose(RTSOCKET Sock, const char *pszMsg, bool fTryGracefulShutdown); + + +/** + * Atomicly updates a socket variable. + * @returns The old handle value. + * @param phSock The socket handle variable to update. + * @param hNew The new socket handle value. + */ +DECLINLINE(RTSOCKET) rtTcpAtomicXchgSock(RTSOCKET volatile *phSock, const RTSOCKET hNew) +{ + RTSOCKET hRet; + ASMAtomicXchgHandle(phSock, hNew, &hRet); + return hRet; +} + + +/** + * Tries to change the TCP server state. + */ +DECLINLINE(bool) rtTcpServerTrySetState(PRTTCPSERVER pServer, RTTCPSERVERSTATE enmStateNew, RTTCPSERVERSTATE enmStateOld) +{ + bool fRc; + ASMAtomicCmpXchgSize(&pServer->enmState, enmStateNew, enmStateOld, fRc); + return fRc; +} + +/** + * Changes the TCP server state. + */ +DECLINLINE(void) rtTcpServerSetState(PRTTCPSERVER pServer, RTTCPSERVERSTATE enmStateNew, RTTCPSERVERSTATE enmStateOld) +{ + bool fRc; + ASMAtomicCmpXchgSize(&pServer->enmState, enmStateNew, enmStateOld, fRc); + Assert(fRc); NOREF(fRc); +} + + +/** + * Closes the a socket (client or server). + * + * @returns IPRT status code. + */ +static int rtTcpServerDestroySocket(RTSOCKET volatile *pSock, const char *pszMsg, bool fTryGracefulShutdown) +{ + RTSOCKET hSocket = rtTcpAtomicXchgSock(pSock, NIL_RTSOCKET); + if (hSocket != NIL_RTSOCKET) + { + if (!fTryGracefulShutdown) + RTSocketShutdown(hSocket, true /*fRead*/, true /*fWrite*/); + return rtTcpClose(hSocket, pszMsg, fTryGracefulShutdown); + } + return VINF_TCP_SERVER_NO_CLIENT; +} + + +RTR3DECL(int) RTTcpServerCreate(const char *pszAddress, unsigned uPort, RTTHREADTYPE enmType, const char *pszThrdName, + PFNRTTCPSERVE pfnServe, void *pvUser, PPRTTCPSERVER ppServer) +{ + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pfnServe, VERR_INVALID_POINTER); + AssertPtrReturn(pszThrdName, VERR_INVALID_POINTER); + AssertPtrReturn(ppServer, VERR_INVALID_POINTER); + + /* + * Create the server. + */ + PRTTCPSERVER pServer; + int rc = RTTcpServerCreateEx(pszAddress, uPort, &pServer); + if (RT_SUCCESS(rc)) + { + /* + * Create the listener thread. + */ + RTMemPoolRetain(pServer); + pServer->enmState = RTTCPSERVERSTATE_STARTING; + pServer->pvUser = pvUser; + pServer->pfnServe = pfnServe; + rc = RTThreadCreate(&pServer->Thread, rtTcpServerThread, pServer, 0, enmType, /*RTTHREADFLAGS_WAITABLE*/0, pszThrdName); + if (RT_SUCCESS(rc)) + { + /* done */ + if (ppServer) + *ppServer = pServer; + else + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return rc; + } + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + + /* + * Destroy the server. + */ + rtTcpServerSetState(pServer, RTTCPSERVERSTATE_CREATED, RTTCPSERVERSTATE_STARTING); + RTTcpServerDestroy(pServer); + } + + return rc; +} + + +/** + * Server thread, loops accepting connections until it's terminated. + * + * @returns iprt status code. (ignored). + * @param ThreadSelf Thread handle. + * @param pvServer Server handle. + */ +static DECLCALLBACK(int) rtTcpServerThread(RTTHREAD ThreadSelf, void *pvServer) +{ + PRTTCPSERVER pServer = (PRTTCPSERVER)pvServer; + int rc; + if (rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_ACCEPTING, RTTCPSERVERSTATE_STARTING)) + rc = rtTcpServerListen(pServer); + else + rc = rtTcpServerListenCleanup(pServer); + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + NOREF(ThreadSelf); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTcpServerCreateEx(const char *pszAddress, uint32_t uPort, PPRTTCPSERVER ppServer) +{ + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(ppServer, VERR_INVALID_PARAMETER); + + /* + * Resolve the address. + */ + RTNETADDR LocalAddr; + int rc = RTSocketParseInetAddress(pszAddress, uPort, &LocalAddr); + if (RT_FAILURE(rc)) + return rc; + + /* + * Setting up socket. + */ + RTSOCKET WaitSock; + rc = rtSocketCreate(&WaitSock, AF_INET, SOCK_STREAM, IPPROTO_TCP, false /*fInheritable*/); + if (RT_SUCCESS(rc)) + { + /* + * Set socket options. + */ + int fFlag = 1; + if (!rtSocketSetOpt(WaitSock, SOL_SOCKET, SO_REUSEADDR, &fFlag, sizeof(fFlag))) + { + /* + * Bind a name to a socket and set it listening for connections. + */ + rc = rtSocketBind(WaitSock, &LocalAddr); + if (RT_SUCCESS(rc)) + rc = rtSocketListen(WaitSock, RTTCP_SERVER_BACKLOG); + if (RT_SUCCESS(rc)) + { + /* + * Create the server handle. + */ + PRTTCPSERVER pServer = (PRTTCPSERVER)RTMemPoolAlloc(RTMEMPOOL_DEFAULT, sizeof(*pServer)); + if (pServer) + { + pServer->u32Magic = RTTCPSERVER_MAGIC; + pServer->enmState = RTTCPSERVERSTATE_CREATED; + pServer->Thread = NIL_RTTHREAD; + pServer->hServerSocket = WaitSock; + pServer->hClientSocket = NIL_RTSOCKET; + pServer->pfnServe = NULL; + pServer->pvUser = NULL; + *ppServer = pServer; + return VINF_SUCCESS; + } + + /* bail out */ + rc = VERR_NO_MEMORY; + } + } + else + AssertMsgFailed(("rtSocketSetOpt: %Rrc\n", rc)); + rtTcpClose(WaitSock, "RTServerCreateEx", false /*fTryGracefulShutdown*/); + } + + return rc; +} + + +RTR3DECL(int) RTTcpServerListen(PRTTCPSERVER pServer, PFNRTTCPSERVE pfnServe, void *pvUser) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pfnServe, VERR_INVALID_POINTER); + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTTCPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); + + int rc = VERR_INVALID_STATE; + if (rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_ACCEPTING, RTTCPSERVERSTATE_CREATED)) + { + Assert(!pServer->pfnServe); + Assert(!pServer->pvUser); + Assert(pServer->Thread == NIL_RTTHREAD); + Assert(pServer->hClientSocket == NIL_RTSOCKET); + + pServer->pfnServe = pfnServe; + pServer->pvUser = pvUser; + pServer->Thread = RTThreadSelf(); + Assert(pServer->Thread != NIL_RTTHREAD); + rc = rtTcpServerListen(pServer); + } + else + { + AssertMsgFailed(("enmState=%d\n", pServer->enmState)); + rc = VERR_INVALID_STATE; + } + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return rc; +} + + +/** + * Internal worker common for RTTcpServerListen and the thread created by + * RTTcpServerCreate(). + * + * The caller makes sure it has its own memory reference and releases it upon + * return. + */ +static int rtTcpServerListen(PRTTCPSERVER pServer) +{ + /* + * Accept connection loop. + */ + for (;;) + { + /* + * Change state, getting an extra reference to the socket so we can + * allow others to close it while we're stuck in rtSocketAccept. + */ + RTTCPSERVERSTATE enmState = pServer->enmState; + RTSOCKET hServerSocket; + ASMAtomicXchgHandle(&pServer->hServerSocket, NIL_RTSOCKET, &hServerSocket); + if (hServerSocket != NIL_RTSOCKET) + { + RTSocketRetain(hServerSocket); + ASMAtomicWriteHandle(&pServer->hServerSocket, hServerSocket); + } + if ( enmState != RTTCPSERVERSTATE_ACCEPTING + && enmState != RTTCPSERVERSTATE_SERVING) + { + RTSocketRelease(hServerSocket); + return rtTcpServerListenCleanup(pServer); + } + if (!rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_ACCEPTING, enmState)) + { + RTSocketRelease(hServerSocket); + continue; + } + + /* + * Accept connection. + */ + struct sockaddr_in RemoteAddr; + size_t cbRemoteAddr = sizeof(RemoteAddr); + RTSOCKET hClientSocket; + RT_ZERO(RemoteAddr); + int rc = rtSocketAccept(hServerSocket, &hClientSocket, (struct sockaddr *)&RemoteAddr, &cbRemoteAddr); + RTSocketRelease(hServerSocket); + if (RT_FAILURE(rc)) + { + /* These are typical for what can happen during destruction. */ + if ( rc == VERR_INVALID_HANDLE + || rc == VERR_INVALID_PARAMETER + || rc == VERR_NET_NOT_SOCKET) + return rtTcpServerListenCleanup(pServer); + continue; + } + RTSocketSetInheritance(hClientSocket, false /*fInheritable*/); + + /* + * Run a pfnServe callback. + */ + if (!rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_SERVING, RTTCPSERVERSTATE_ACCEPTING)) + { + rtTcpClose(hClientSocket, "rtTcpServerListen", true /*fTryGracefulShutdown*/); + return rtTcpServerListenCleanup(pServer); + } + RTSocketRetain(hClientSocket); + rtTcpAtomicXchgSock(&pServer->hClientSocket, hClientSocket); + rc = pServer->pfnServe(hClientSocket, pServer->pvUser); + rtTcpServerDestroySocket(&pServer->hClientSocket, "Listener: client (secondary)", true /*fTryGracefulShutdown*/); + RTSocketRelease(hClientSocket); + + /* + * Stop the server? + */ + if (rc == VERR_TCP_SERVER_STOP) + { + if (rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_STOPPING, RTTCPSERVERSTATE_SERVING)) + { + /* + * Reset the server socket and change the state to stopped. After that state change + * we cannot safely access the handle so we'll have to return here. + */ + hServerSocket = rtTcpAtomicXchgSock(&pServer->hServerSocket, NIL_RTSOCKET); + rtTcpServerSetState(pServer, RTTCPSERVERSTATE_STOPPED, RTTCPSERVERSTATE_STOPPING); + rtTcpClose(hServerSocket, "Listener: server stopped", false /*fTryGracefulShutdown*/); + } + else + rtTcpServerListenCleanup(pServer); /* ignore rc */ + return rc; + } + } +} + + +/** + * Clean up after listener. + */ +static int rtTcpServerListenCleanup(PRTTCPSERVER pServer) +{ + /* + * Close the server socket, the client one shouldn't be set. + */ + rtTcpServerDestroySocket(&pServer->hServerSocket, "ListenCleanup", false /*fTryGracefulShutdown*/); + Assert(pServer->hClientSocket == NIL_RTSOCKET); + + /* + * Figure the return code and make sure the state is OK. + */ + RTTCPSERVERSTATE enmState = pServer->enmState; + switch (enmState) + { + case RTTCPSERVERSTATE_STOPPING: + case RTTCPSERVERSTATE_STOPPED: + return VERR_TCP_SERVER_SHUTDOWN; + + case RTTCPSERVERSTATE_ACCEPTING: + rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_STOPPED, enmState); + return VERR_TCP_SERVER_DESTROYED; + + case RTTCPSERVERSTATE_DESTROYING: + return VERR_TCP_SERVER_DESTROYED; + + case RTTCPSERVERSTATE_STARTING: + case RTTCPSERVERSTATE_SERVING: + default: + AssertMsgFailedReturn(("pServer=%p enmState=%d\n", pServer, enmState), VERR_INTERNAL_ERROR_4); + } +} + + +RTR3DECL(int) RTTcpServerListen2(PRTTCPSERVER pServer, PRTSOCKET phClientSocket) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(phClientSocket, VERR_INVALID_HANDLE); + *phClientSocket = NIL_RTSOCKET; + AssertReturn(pServer->u32Magic == RTTCPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); + + int rc = VERR_INVALID_STATE; + for (;;) + { + /* + * Change state, getting an extra reference to the socket so we can + * allow others to close it while we're stuck in rtSocketAccept. + */ + RTTCPSERVERSTATE enmState = pServer->enmState; + RTSOCKET hServerSocket; + ASMAtomicXchgHandle(&pServer->hServerSocket, NIL_RTSOCKET, &hServerSocket); + if (hServerSocket != NIL_RTSOCKET) + { + RTSocketRetain(hServerSocket); + ASMAtomicWriteHandle(&pServer->hServerSocket, hServerSocket); + } + if ( enmState != RTTCPSERVERSTATE_SERVING + && enmState != RTTCPSERVERSTATE_CREATED) + { + RTSocketRelease(hServerSocket); + return rtTcpServerListenCleanup(pServer); + } + if (!rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_ACCEPTING, enmState)) + { + RTSocketRelease(hServerSocket); + continue; + } + Assert(!pServer->pfnServe); + Assert(!pServer->pvUser); + Assert(pServer->Thread == NIL_RTTHREAD); + Assert(pServer->hClientSocket == NIL_RTSOCKET); + + /* + * Accept connection. + */ + struct sockaddr_in RemoteAddr; + size_t cbRemoteAddr = sizeof(RemoteAddr); + RTSOCKET hClientSocket; + RT_ZERO(RemoteAddr); + rc = rtSocketAccept(hServerSocket, &hClientSocket, (struct sockaddr *)&RemoteAddr, &cbRemoteAddr); + RTSocketRelease(hServerSocket); + if (RT_FAILURE(rc)) + { + if (!rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_CREATED, RTTCPSERVERSTATE_ACCEPTING)) + rc = rtTcpServerListenCleanup(pServer); + if (RT_FAILURE(rc)) + break; + continue; + } + RTSocketSetInheritance(hClientSocket, false /*fInheritable*/); + + /* + * Chance to the 'serving' state and return the socket. + */ + if (rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_SERVING, RTTCPSERVERSTATE_ACCEPTING)) + { + *phClientSocket = hClientSocket; + rc = VINF_SUCCESS; + } + else + { + rtTcpClose(hClientSocket, "RTTcpServerListen2", true /*fTryGracefulShutdown*/); + rc = rtTcpServerListenCleanup(pServer); + } + break; + } + + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return rc; +} + + +RTR3DECL(int) RTTcpServerDisconnectClient(PRTTCPSERVER pServer) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTTCPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); + + int rc = rtTcpServerDestroySocket(&pServer->hClientSocket, "DisconnectClient: client", true /*fTryGracefulShutdown*/); + + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return rc; +} + + +RTR3DECL(int) RTTcpServerDisconnectClient2(RTSOCKET hClientSocket) +{ + return rtTcpClose(hClientSocket, "RTTcpServerDisconnectClient2", true /*fTryGracefulShutdown*/); +} + + +RTR3DECL(int) RTTcpServerShutdown(PRTTCPSERVER pServer) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTTCPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); + + /* + * Try change the state to stopping, then replace and destroy the server socket. + */ + for (;;) + { + RTTCPSERVERSTATE enmState = pServer->enmState; + if ( enmState != RTTCPSERVERSTATE_ACCEPTING + && enmState != RTTCPSERVERSTATE_SERVING) + { + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + switch (enmState) + { + case RTTCPSERVERSTATE_CREATED: + case RTTCPSERVERSTATE_STARTING: + default: + AssertMsgFailed(("%d\n", enmState)); + return VERR_INVALID_STATE; + + case RTTCPSERVERSTATE_STOPPING: + case RTTCPSERVERSTATE_STOPPED: + return VINF_SUCCESS; + + case RTTCPSERVERSTATE_DESTROYING: + return VERR_TCP_SERVER_DESTROYED; + } + } + if (rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_STOPPING, enmState)) + { + rtTcpServerDestroySocket(&pServer->hServerSocket, "RTTcpServerShutdown", false /*fTryGracefulShutdown*/); + rtTcpServerSetState(pServer, RTTCPSERVERSTATE_STOPPED, RTTCPSERVERSTATE_STOPPING); + + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return VINF_SUCCESS; + } + } +} + + +RTR3DECL(int) RTTcpServerDestroy(PRTTCPSERVER pServer) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTTCPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); /* paranoia */ + + /* + * Move the state along so the listener can figure out what's going on. + */ + for (;;) + { + bool fDestroyable; + RTTCPSERVERSTATE enmState = pServer->enmState; + switch (enmState) + { + case RTTCPSERVERSTATE_STARTING: + case RTTCPSERVERSTATE_ACCEPTING: + case RTTCPSERVERSTATE_SERVING: + case RTTCPSERVERSTATE_CREATED: + case RTTCPSERVERSTATE_STOPPED: + fDestroyable = rtTcpServerTrySetState(pServer, RTTCPSERVERSTATE_DESTROYING, enmState); + break; + + /* destroyable states */ + case RTTCPSERVERSTATE_STOPPING: + fDestroyable = true; + break; + + /* + * Everything else means user or internal misbehavior. + */ + default: + AssertMsgFailed(("pServer=%p enmState=%d\n", pServer, enmState)); + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return VERR_INTERNAL_ERROR; + } + if (fDestroyable) + break; + } + + /* + * Destroy it. + */ + ASMAtomicWriteU32(&pServer->u32Magic, ~RTTCPSERVER_MAGIC); + rtTcpServerDestroySocket(&pServer->hServerSocket, "Destroyer: server", false /*fTryGracefulShutdown*/); + rtTcpServerDestroySocket(&pServer->hClientSocket, "Destroyer: client", true /*fTryGracefulShutdown*/); + + /* + * Release it. + */ + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTcpClientConnect(const char *pszAddress, uint32_t uPort, PRTSOCKET pSock) +{ + return RTTcpClientConnectEx(pszAddress, uPort, pSock, RT_SOCKETCONNECT_DEFAULT_WAIT, NULL); +} + + +RTR3DECL(int) RTTcpClientConnectEx(const char *pszAddress, uint32_t uPort, PRTSOCKET pSock, + RTMSINTERVAL cMillies, PRTTCPCLIENTCONNECTCANCEL volatile *ppCancelCookie) +{ + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszAddress, VERR_INVALID_POINTER); + AssertPtrNullReturn(ppCancelCookie, VERR_INVALID_POINTER); + + /* + * Resolve the address. + */ + RTNETADDR Addr; + int rc = RTSocketParseInetAddress(pszAddress, uPort, &Addr); + if (RT_FAILURE(rc)) + return rc; + + /* + * Create the socket and connect. + */ + RTSOCKET Sock; + rc = rtSocketCreate(&Sock, PF_INET, SOCK_STREAM, 0, false /*fInheritable*/); + if (RT_SUCCESS(rc)) + { + if (!ppCancelCookie) + rc = rtSocketConnect(Sock, &Addr, cMillies); + else + { + RTSocketRetain(Sock); + if (ASMAtomicCmpXchgPtr(ppCancelCookie, (PRTTCPCLIENTCONNECTCANCEL)Sock, NULL)) + { + rc = rtSocketConnect(Sock, &Addr, cMillies); + if (ASMAtomicCmpXchgPtr(ppCancelCookie, NULL, (PRTTCPCLIENTCONNECTCANCEL)Sock)) + RTSocketRelease(Sock); + else + rc = VERR_CANCELLED; + } + else + { + RTSocketRelease(Sock); + rc = VERR_CANCELLED; + } + } + if (RT_SUCCESS(rc)) + { + *pSock = Sock; + return VINF_SUCCESS; + } + + rtTcpClose(Sock, "RTTcpClientConnect", false /*fTryGracefulShutdown*/); + } + if (ppCancelCookie) + *ppCancelCookie = NULL; + return rc; +} + + +RTR3DECL(int) RTTcpClientCancelConnect(PRTTCPCLIENTCONNECTCANCEL volatile *ppCancelCookie) +{ + AssertPtrReturn(ppCancelCookie, VERR_INVALID_POINTER); + + RTSOCKET const hSockCancelled = (RTSOCKET)(uintptr_t)0xdead9999; + + AssertCompile(NIL_RTSOCKET == NULL); + RTSOCKET hSock = (RTSOCKET)ASMAtomicXchgPtr((void * volatile *)ppCancelCookie, hSockCancelled); + if (hSock != NIL_RTSOCKET && hSock != hSockCancelled) + { + int rc = rtTcpClose(hSock, "RTTcpClientCancelConnect", false /*fTryGracefulShutdown*/); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTcpClientClose(RTSOCKET Sock) +{ + return rtTcpClose(Sock, "RTTcpClientClose", true /*fTryGracefulShutdown*/); +} + + +RTR3DECL(int) RTTcpClientCloseEx(RTSOCKET Sock, bool fGracefulShutdown) +{ + return rtTcpClose(Sock, "RTTcpClientCloseEx", fGracefulShutdown); +} + + +#ifdef FIX_FOR_3_2 +/** + * Changes the blocking mode of the socket. + * + * @returns 0 on success, -1 on failure. + * @param hSocket The socket to work on. + * @param fBlocking The desired mode of operation. + */ +static int rtTcpSetBlockingMode(RTHCUINTPTR hSocket, bool fBlocking) +{ + int rc = VINF_SUCCESS; +#ifdef RT_OS_WINDOWS + u_long uBlocking = fBlocking ? 0 : 1; + if (ioctlsocket(hSocket, FIONBIO, &uBlocking)) + return -1; + +#else + int fFlags = fcntl(hSocket, F_GETFL, 0); + if (fFlags == -1) + return -1; + + if (fBlocking) + fFlags &= ~O_NONBLOCK; + else + fFlags |= O_NONBLOCK; + if (fcntl(hSocket, F_SETFL, fFlags) == -1) + return -1; +#endif + + return 0; +} +#endif + + +/** + * Internal close function which does all the proper bitching. + */ +static int rtTcpClose(RTSOCKET Sock, const char *pszMsg, bool fTryGracefulShutdown) +{ + NOREF(pszMsg); /** @todo drop this parameter? */ + + /* ignore nil handles. */ + if (Sock == NIL_RTSOCKET) + return VINF_SUCCESS; + + /* + * Try to gracefully shut it down. + */ + int rc; + if (fTryGracefulShutdown) + { + rc = RTSocketShutdown(Sock, false /*fRead*/, true /*fWrite*/); +#ifdef FIX_FOR_3_2 + RTHCUINTPTR hNative = RTSocketToNative(Sock); + if (RT_SUCCESS(rc) && rtTcpSetBlockingMode(hNative, false /*fBlocking*/) == 0) +#else + if (RT_SUCCESS(rc)) +#endif + { + + size_t cbReceived = 0; + uint64_t u64Start = RTTimeMilliTS(); + while ( cbReceived < _1G + && RTTimeMilliTS() - u64Start < 30000) + { +#ifdef FIX_FOR_3_2 + fd_set FdSetR; + FD_ZERO(&FdSetR); + FD_SET(hNative, &FdSetR); + + fd_set FdSetE; + FD_ZERO(&FdSetE); + FD_SET(hNative, &FdSetE); + + struct timeval TvTimeout; + TvTimeout.tv_sec = 1; + TvTimeout.tv_usec = 0; + rc = select(hNative + 1, &FdSetR, NULL, &FdSetE, &TvTimeout); + if (rc == 0) + continue; + if (rc < 0) + break; + if (FD_ISSET(hNative, &FdSetE)) + break; +#else + uint32_t fEvents; + rc = RTSocketSelectOneEx(Sock, RTSOCKET_EVT_READ | RTSOCKET_EVT_ERROR, &fEvents, 1000); + if (rc == VERR_TIMEOUT) + continue; + if (RT_FAILURE(rc)) + break; + if (fEvents & RTSOCKET_EVT_ERROR) + break; +#endif + + char abBitBucket[16*_1K]; +#ifdef FIX_FOR_3_2 + ssize_t cbRead = recv(hNative, &abBitBucket[0], sizeof(abBitBucket), MSG_NOSIGNAL); + if (cbRead == 0) + break; /* orderly shutdown in progress */ + if (cbRead < 0 && errno != EAGAIN) + break; /* some kind of error, never mind which... */ +#else + size_t cbRead; + rc = RTSocketReadNB(Sock, &abBitBucket[0], sizeof(abBitBucket), &cbRead); + if (RT_FAILURE(rc)) + break; /* some kind of error, never mind which... */ + if (rc != VINF_TRY_AGAIN && !cbRead) + break; /* orderly shutdown in progress */ +#endif + + cbReceived += cbRead; + } + } + } + + /* + * Close the socket handle (drops our reference to it). + */ + return RTSocketClose(Sock); +} + + +RTR3DECL(int) RTTcpCreatePair(PRTSOCKET phServer, PRTSOCKET phClient, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(phServer, VERR_INVALID_PARAMETER); + AssertPtrReturn(phClient, VERR_INVALID_PARAMETER); + AssertReturn(!fFlags, VERR_INVALID_PARAMETER); + + /* + * Do the job. + */ + return rtSocketCreateTcpPair(phServer, phClient); +} + + +RTR3DECL(int) RTTcpRead(RTSOCKET Sock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead) +{ + return RTSocketRead(Sock, pvBuffer, cbBuffer, pcbRead); +} + + +RTR3DECL(int) RTTcpWrite(RTSOCKET Sock, const void *pvBuffer, size_t cbBuffer) +{ + return RTSocketWrite(Sock, pvBuffer, cbBuffer); +} + + +RTR3DECL(int) RTTcpFlush(RTSOCKET Sock) +{ + int fFlag = 1; + int rc = rtSocketSetOpt(Sock, IPPROTO_TCP, TCP_NODELAY, &fFlag, sizeof(fFlag)); + if (RT_SUCCESS(rc)) + { + fFlag = 0; + rc = rtSocketSetOpt(Sock, IPPROTO_TCP, TCP_NODELAY, &fFlag, sizeof(fFlag)); + } + return rc; +} + + +RTR3DECL(int) RTTcpSetSendCoalescing(RTSOCKET Sock, bool fEnable) +{ + int fFlag = fEnable ? 0 : 1; + return rtSocketSetOpt(Sock, IPPROTO_TCP, TCP_NODELAY, &fFlag, sizeof(fFlag)); +} + + +RTR3DECL(int) RTTcpSetBufferSize(RTSOCKET hSocket, uint32_t cbSize) +{ + int cbIntSize = (int)cbSize; + AssertReturn(cbIntSize >= 0, VERR_OUT_OF_RANGE); + int rc = rtSocketSetOpt(hSocket, SOL_SOCKET, SO_SNDBUF, &cbIntSize, sizeof(cbIntSize)); + if (RT_SUCCESS(rc)) + rc = rtSocketSetOpt(hSocket, SOL_SOCKET, SO_RCVBUF, &cbIntSize, sizeof(cbIntSize)); + return rc; +} + + +RTR3DECL(int) RTTcpSelectOne(RTSOCKET Sock, RTMSINTERVAL cMillies) +{ + return RTSocketSelectOne(Sock, cMillies); +} + + +RTR3DECL(int) RTTcpSelectOneEx(RTSOCKET Sock, uint32_t fEvents, uint32_t *pfEvents, + RTMSINTERVAL cMillies) +{ + return RTSocketSelectOneEx(Sock, fEvents, pfEvents, cMillies); +} + + +RTR3DECL(int) RTTcpGetLocalAddress(RTSOCKET Sock, PRTNETADDR pAddr) +{ + return RTSocketGetLocalAddress(Sock, pAddr); +} + + +RTR3DECL(int) RTTcpGetPeerAddress(RTSOCKET Sock, PRTNETADDR pAddr) +{ + return RTSocketGetPeerAddress(Sock, pAddr); +} + + +RTR3DECL(int) RTTcpSgWrite(RTSOCKET Sock, PCRTSGBUF pSgBuf) +{ + return RTSocketSgWrite(Sock, pSgBuf); +} + + +RTR3DECL(int) RTTcpSgWriteL(RTSOCKET hSocket, size_t cSegs, ...) +{ + va_list va; + va_start(va, cSegs); + int rc = RTSocketSgWriteLV(hSocket, cSegs, va); + va_end(va); + return rc; +} + + +RTR3DECL(int) RTTcpSgWriteLV(RTSOCKET hSocket, size_t cSegs, va_list va) +{ + return RTSocketSgWriteLV(hSocket, cSegs, va); +} + + +RTR3DECL(int) RTTcpReadNB(RTSOCKET Sock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead) +{ + return RTSocketReadNB(Sock, pvBuffer, cbBuffer, pcbRead); +} + + +RTR3DECL(int) RTTcpWriteNB(RTSOCKET Sock, const void *pvBuffer, size_t cbBuffer, size_t *pcbWritten) +{ + return RTSocketWriteNB(Sock, pvBuffer, cbBuffer, pcbWritten); +} + + +RTR3DECL(int) RTTcpSgWriteNB(RTSOCKET Sock, PCRTSGBUF pSgBuf, size_t *pcbWritten) +{ + return RTSocketSgWriteNB(Sock, pSgBuf, pcbWritten); +} + + +RTR3DECL(int) RTTcpSgWriteLNB(RTSOCKET hSocket, size_t cSegs, size_t *pcbWritten, ...) +{ + va_list va; + va_start(va, pcbWritten); + int rc = RTSocketSgWriteLVNB(hSocket, cSegs, pcbWritten, va); + va_end(va); + return rc; +} + + +RTR3DECL(int) RTTcpSgWriteLVNB(RTSOCKET hSocket, size_t cSegs, size_t *pcbWritten, va_list va) +{ + return RTSocketSgWriteLVNB(hSocket, cSegs, pcbWritten, va); +} + diff --git a/src/VBox/Runtime/r3/test.cpp b/src/VBox/Runtime/r3/test.cpp new file mode 100644 index 00000000..a0b90870 --- /dev/null +++ b/src/VBox/Runtime/r3/test.cpp @@ -0,0 +1,1662 @@ +/* $Id: test.cpp $ */ +/** @file + * IPRT - Testcase Framework. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> + +#include <iprt/asm.h> +#include <iprt/critsect.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/param.h> +#include <iprt/pipe.h> +#include <iprt/string.h> +#include <iprt/stream.h> + +#include "internal/magics.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Guarded memory allocation record. + */ +typedef struct RTTESTGUARDEDMEM +{ + /** Pointer to the next record. */ + struct RTTESTGUARDEDMEM *pNext; + /** The address we return to the user. */ + void *pvUser; + /** The base address of the allocation. */ + void *pvAlloc; + /** The size of the allocation. */ + size_t cbAlloc; + /** Guards. */ + struct + { + /** The guard address. */ + void *pv; + /** The guard size. */ + size_t cb; + } aGuards[2]; +} RTTESTGUARDEDMEM; +/** Pointer to an guarded memory allocation. */ +typedef RTTESTGUARDEDMEM *PRTTESTGUARDEDMEM; + +/** + * Test instance structure. + */ +typedef struct RTTESTINT +{ + /** Magic. */ + uint32_t u32Magic; + /** The number of errors. */ + volatile uint32_t cErrors; + /** The test name. */ + const char *pszTest; + /** The length of the test name. */ + size_t cchTest; + /** The size of a guard. Multiple of PAGE_SIZE. */ + uint32_t cbGuard; + /** The verbosity level. */ + RTTESTLVL enmMaxLevel; + /** The creation flags. */ + uint32_t fFlags; + + + /** Critical section serializing output. */ + RTCRITSECT OutputLock; + /** The output stream. */ + PRTSTREAM pOutStrm; + /** Whether we're currently at a newline. */ + bool fNewLine; + + + /** Critical section serializing access to the members following it. */ + RTCRITSECT Lock; + + /** The list of guarded memory allocations. */ + PRTTESTGUARDEDMEM pGuardedMem; + + /** The current sub-test. */ + const char *pszSubTest; + /** The length of the sub-test name. */ + size_t cchSubTest; + /** Whether the current subtest should figure as 'SKIPPED'. */ + bool fSubTestSkipped; + /** Whether we've reported the sub-test result or not. */ + bool fSubTestReported; + /** The start error count of the current subtest. */ + uint32_t cSubTestAtErrors; + + /** The number of sub tests. */ + uint32_t cSubTests; + /** The number of sub tests that failed. */ + uint32_t cSubTestsFailed; + + /** Error context message. */ + char *pszErrCtx; + + /** Set if XML output is enabled. */ + bool fXmlEnabled; + /** Set if we omit the top level test in the XML report. */ + bool fXmlOmitTopTest; + /** Set if we've reported the top test (for RTTEST_C_XML_DELAY_TOP_TEST). */ + bool fXmlTopTestDone; + enum { + kXmlPos_ValueStart, + kXmlPos_Value, + kXmlPos_ElementEnd + } eXmlState; + /** Test pipe for the XML output stream going to the server. */ + RTPIPE hXmlPipe; + /** File where the XML output stream might be directed. */ + RTFILE hXmlFile; + /** The number of XML elements on the stack. */ + size_t cXmlElements; + /** XML element stack. */ + const char *apszXmlElements[10]; + + /** Number of times assertions has been disabled and quieted. */ + uint32_t volatile cAssertionsDisabledAndQuieted; + /** Saved RTAssertSetQuiet return code. */ + bool fAssertSavedQuiet; + /** Saved RTAssertSetMayPanic return code. */ + bool fAssertSavedMayPanic; +} RTTESTINT; +/** Pointer to a test instance. */ +typedef RTTESTINT *PRTTESTINT; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Validate a test instance. */ +#define RTTEST_VALID_RETURN(pTest) \ + do { \ + AssertPtrReturn(pTest, VERR_INVALID_HANDLE); \ + AssertReturn(pTest->u32Magic == RTTESTINT_MAGIC, VERR_INVALID_HANDLE); \ + } while (0) + +/** Gets and validates a test instance. + * If the handle is nil, we will try retrieve it from the test TLS entry. + */ +#define RTTEST_GET_VALID_RETURN(pTest) \ + do { \ + if (pTest == NIL_RTTEST) \ + pTest = (PRTTESTINT)RTTlsGet(g_iTestTls); \ + AssertPtrReturn(pTest, VERR_INVALID_HANDLE); \ + AssertReturn(pTest->u32Magic == RTTESTINT_MAGIC, VERR_INVALID_MAGIC); \ + } while (0) + + +/** Gets and validates a test instance. + * If the handle is nil, we will try retrieve it from the test TLS entry. + */ +#define RTTEST_GET_VALID_RETURN_RC(pTest, rc) \ + do { \ + if (pTest == NIL_RTTEST) \ + pTest = (PRTTESTINT)RTTlsGet(g_iTestTls); \ + AssertPtrReturn(pTest, (rc)); \ + AssertReturn(pTest->u32Magic == RTTESTINT_MAGIC, (rc)); \ + } while (0) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void rtTestGuardedFreeOne(PRTTESTGUARDEDMEM pMem); +static int rtTestPrintf(PRTTESTINT pTest, const char *pszFormat, ...); +static void rtTestXmlStart(PRTTESTINT pTest, const char *pszTest); +static void rtTestXmlElemV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va); +static void rtTestXmlElem(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...); +static void rtTestXmlElemStartV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va); +static void rtTestXmlElemStart(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...); +static void rtTestXmlElemEnd(PRTTESTINT pTest, const char *pszTag); +static void rtTestXmlEnd(PRTTESTINT pTest); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** For serializing TLS init. */ +static RTONCE g_TestInitOnce = RTONCE_INITIALIZER; +/** Our TLS entry. */ +static RTTLS g_iTestTls = NIL_RTTLS; + + + +/** + * Init TLS index once. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int32_t) rtTestInitOnce(void *pvUser) +{ + NOREF(pvUser); + return RTTlsAllocEx(&g_iTestTls, NULL); +} + + +RTR3DECL(int) RTTestCreateEx(const char *pszTest, uint32_t fFlags, RTTESTLVL enmMaxLevel, + RTHCINTPTR iNativeTestPipe, const char *pszXmlFile, PRTTEST phTest) +{ + AssertReturn(!(fFlags & ~RTTEST_C_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrNull(phTest); + AssertPtrNull(pszXmlFile); + /* RTTESTLVL_INVALID is valid! */ + AssertReturn(enmMaxLevel >= RTTESTLVL_INVALID && enmMaxLevel < RTTESTLVL_END, VERR_INVALID_PARAMETER); + + /* + * Global init. + */ + int rc = RTOnce(&g_TestInitOnce, rtTestInitOnce, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Create the instance. + */ + PRTTESTINT pTest = (PRTTESTINT)RTMemAllocZ(sizeof(*pTest)); + if (!pTest) + return VERR_NO_MEMORY; + pTest->u32Magic = RTTESTINT_MAGIC; + pTest->pszTest = RTStrDup(pszTest); + pTest->cchTest = strlen(pszTest); + pTest->cbGuard = PAGE_SIZE * 7; + pTest->enmMaxLevel = enmMaxLevel == RTTESTLVL_INVALID ? RTTESTLVL_INFO : enmMaxLevel; + pTest->fFlags = fFlags; + + pTest->pOutStrm = g_pStdOut; + pTest->fNewLine = true; + + pTest->pGuardedMem = NULL; + + pTest->pszSubTest = NULL; + pTest->cchSubTest = 0; + pTest->fSubTestSkipped = false; + pTest->fSubTestReported = true; + pTest->cSubTestAtErrors = 0; + pTest->cSubTests = 0; + pTest->cSubTestsFailed = 0; + + pTest->fXmlEnabled = false; + pTest->fXmlTopTestDone = false; + pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd; + pTest->hXmlPipe = NIL_RTPIPE; + pTest->hXmlFile = NIL_RTFILE; + pTest->cXmlElements = 0; + pTest->cAssertionsDisabledAndQuieted = 0; + pTest->fAssertSavedMayPanic = true; + pTest->fAssertSavedQuiet = false; + + rc = RTCritSectInit(&pTest->Lock); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pTest->OutputLock); + if (RT_SUCCESS(rc)) + { + /* + * Associate it with our TLS entry unless there is already + * an instance there. + */ + if ( !(fFlags & RTTEST_C_NO_TLS) + && !RTTlsGet(g_iTestTls)) + rc = RTTlsSet(g_iTestTls, pTest); + if (RT_SUCCESS(rc)) + { + /* + * Output level override? + */ + char szEnvVal[RTPATH_MAX]; + if ((fFlags & RTTEST_C_USE_ENV) && enmMaxLevel == RTTESTLVL_INVALID) + { + rc = RTEnvGetEx(RTENV_DEFAULT, "IPRT_TEST_MAX_LEVEL", szEnvVal, sizeof(szEnvVal), NULL); + if (RT_SUCCESS(rc)) + { + char *pszMaxLevel = RTStrStrip(szEnvVal); + if (!strcmp(pszMaxLevel, "all")) + pTest->enmMaxLevel = RTTESTLVL_DEBUG; + if (!strcmp(pszMaxLevel, "quiet")) + pTest->enmMaxLevel = RTTESTLVL_FAILURE; + else if (!strcmp(pszMaxLevel, "debug")) + pTest->enmMaxLevel = RTTESTLVL_DEBUG; + else if (!strcmp(pszMaxLevel, "info")) + pTest->enmMaxLevel = RTTESTLVL_INFO; + else if (!strcmp(pszMaxLevel, "sub_test")) + pTest->enmMaxLevel = RTTESTLVL_SUB_TEST; + else if (!strcmp(pszMaxLevel, "failure")) + pTest->enmMaxLevel = RTTESTLVL_FAILURE; + } + else if (rc != VERR_ENV_VAR_NOT_FOUND) + RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTEnvGetEx(IPRT_TEST_MAX_LEVEL) -> %Rrc\n", pszTest, rc); + } + + /* + * Any test driver we are connected or should connect to? + */ + if (!(fFlags & RTTEST_C_NO_XML_REPORTING_PIPE)) + { + if ( (fFlags & RTTEST_C_USE_ENV) + && iNativeTestPipe == -1) + { + rc = RTEnvGetEx(RTENV_DEFAULT, "IPRT_TEST_PIPE", szEnvVal, sizeof(szEnvVal), NULL); + if (RT_SUCCESS(rc)) + { +#if ARCH_BITS == 64 + rc = RTStrToInt64Full(szEnvVal, 0, &iNativeTestPipe); +#else + rc = RTStrToInt32Full(szEnvVal, 0, &iNativeTestPipe); +#endif + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTStrToInt32Full(\"%s\") -> %Rrc\n", + pszTest, szEnvVal, rc); + iNativeTestPipe = -1; + } + } + else if (rc != VERR_ENV_VAR_NOT_FOUND) + RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTEnvGetEx(IPRT_TEST_PIPE) -> %Rrc\n", pszTest, rc); + } + if (iNativeTestPipe != -1) + { + rc = RTPipeFromNative(&pTest->hXmlPipe, iNativeTestPipe, RTPIPE_N_WRITE); + if (RT_SUCCESS(rc)) + pTest->fXmlEnabled = true; + else + { + RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTPipeFromNative(,%p,WRITE) -> %Rrc\n", + pszTest, iNativeTestPipe, rc); + pTest->hXmlPipe = NIL_RTPIPE; + } + } + } + + /* + * Any test file we should write the test report to? + */ + if (!(fFlags & RTTEST_C_NO_XML_REPORTING_FILE)) + { + if ((fFlags & RTTEST_C_USE_ENV) && pszXmlFile == NULL) + { + rc = RTEnvGetEx(RTENV_DEFAULT, "IPRT_TEST_FILE", szEnvVal, sizeof(szEnvVal), NULL); + if (RT_SUCCESS(rc)) + pszXmlFile = szEnvVal; + else if (rc != VERR_ENV_VAR_NOT_FOUND) + RTStrmPrintf(g_pStdErr, "%s: test file error: RTEnvGetEx(IPRT_TEST_MAX_LEVEL) -> %Rrc\n", pszTest, rc); + } + if (pszXmlFile && *pszXmlFile) + { + rc = RTFileOpen(&pTest->hXmlFile, pszXmlFile, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE); + if (RT_SUCCESS(rc)) + pTest->fXmlEnabled = true; + else + { + RTStrmPrintf(g_pStdErr, "%s: test file error: RTFileOpen(,\"%s\",) -> %Rrc\n", + pszTest, pszXmlFile, rc); + pTest->hXmlFile = NIL_RTFILE; + } + } + } + + /* + * What do we report in the XML stream/file.? + */ + pTest->fXmlOmitTopTest = (fFlags & RTTEST_C_XML_OMIT_TOP_TEST) + || ( (fFlags & RTTEST_C_USE_ENV) + && RTEnvExistEx(RTENV_DEFAULT, "IPRT_TEST_OMIT_TOP_TEST")); + + /* + * Tell the test driver that we're up to. + */ + rtTestXmlStart(pTest, pszTest); + + *phTest = pTest; + return VINF_SUCCESS; + } + + /* bail out. */ + RTCritSectDelete(&pTest->OutputLock); + } + RTCritSectDelete(&pTest->Lock); + } + pTest->u32Magic = 0; + RTStrFree((char *)pTest->pszTest); + RTMemFree(pTest); + return rc; +} + + +RTR3DECL(int) RTTestCreate(const char *pszTest, PRTTEST phTest) +{ + return RTTestCreateEx(pszTest, RTTEST_C_USE_ENV, RTTESTLVL_INVALID, -1 /*iNativeTestPipe*/, NULL /*pszXmlFile*/, phTest); +} + + +RTR3DECL(int) RTTestCreateChild(const char *pszTest, PRTTEST phTest) +{ + return RTTestCreateEx(pszTest, RTTEST_C_USE_ENV | RTTEST_C_NO_XML_REPORTING, + RTTESTLVL_INVALID, -1 /*iNativeTestPipe*/, NULL /*pszXmlFile*/, phTest); +} + + +RTR3DECL(RTEXITCODE) RTTestInitAndCreate(const char *pszTest, PRTTEST phTest) +{ + int rc = RTR3InitExeNoArguments(0); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "%s: fatal error: RTR3InitExeNoArguments failed with rc=%Rrc\n", pszTest, rc); + return RTEXITCODE_INIT; + } + + rc = RTTestCreate(pszTest, phTest); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "%s: fatal error: RTTestCreate failed with rc=%Rrc\n", pszTest, rc); + return RTEXITCODE_INIT; + } + return RTEXITCODE_SUCCESS; +} + + +RTR3DECL(RTEXITCODE) RTTestInitExAndCreate(int cArgs, char ***ppapszArgs, uint32_t fRtInit, const char *pszTest, PRTTEST phTest) +{ + int rc; + if (cArgs <= 0 && ppapszArgs == NULL) + rc = RTR3InitExeNoArguments(fRtInit); + else + rc = RTR3InitExe(cArgs, ppapszArgs, fRtInit); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "%s: fatal error: RTR3InitExe(,,%#x) failed with rc=%Rrc\n", pszTest, fRtInit, rc); + return RTEXITCODE_INIT; + } + + rc = RTTestCreate(pszTest, phTest); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "%s: fatal error: RTTestCreate failed with rc=%Rrc\n", pszTest, rc); + return RTEXITCODE_INIT; + } + return RTEXITCODE_SUCCESS; +} + + +RTR3DECL(int) RTTestDestroy(RTTEST hTest) +{ + /* + * Validate + */ + if (hTest == NIL_RTTEST) + return VINF_SUCCESS; + RTTESTINT *pTest = hTest; + RTTEST_VALID_RETURN(pTest); + + /* + * Make sure we end with a new line and have finished up the XML. + */ + if (!pTest->fNewLine) + rtTestPrintf(pTest, "\n"); + rtTestXmlEnd(pTest); + + /* + * Clean up. + */ + if ((RTTESTINT *)RTTlsGet(g_iTestTls) == pTest) + RTTlsSet(g_iTestTls, NULL); + + ASMAtomicWriteU32(&pTest->u32Magic, ~RTTESTINT_MAGIC); + RTCritSectDelete(&pTest->Lock); + RTCritSectDelete(&pTest->OutputLock); + + /* free guarded memory. */ + PRTTESTGUARDEDMEM pMem = pTest->pGuardedMem; + pTest->pGuardedMem = NULL; + while (pMem) + { + PRTTESTGUARDEDMEM pFree = pMem; + pMem = pMem->pNext; + rtTestGuardedFreeOne(pFree); + } + + RTStrFree((char *)pTest->pszSubTest); + pTest->pszSubTest = NULL; + RTStrFree((char *)pTest->pszTest); + pTest->pszTest = NULL; + RTStrFree(pTest->pszErrCtx); + pTest->pszErrCtx = NULL; + RTMemFree(pTest); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTestSetDefault(RTTEST hNewDefaultTest, PRTTEST phOldTest) +{ + if (phOldTest) + *phOldTest = (RTTEST)RTTlsGet(g_iTestTls); + return RTTlsSet(g_iTestTls, hNewDefaultTest); +} + + +RTR3DECL(int) RTTestChangeName(RTTEST hTest, const char *pszName) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(*pszName, VERR_INVALID_PARAMETER); + + size_t cchName = strlen(pszName); + AssertReturn(cchName < 128, VERR_INVALID_PARAMETER); + char *pszDupName = RTStrDup(pszName); + if (!pszDupName) + return VERR_NO_STR_MEMORY; + + RTCritSectEnter(&pTest->Lock); + RTCritSectEnter(&pTest->OutputLock); + + char *pszOldName = (char *)pTest->pszTest; + pTest->pszTest = pszDupName; + pTest->cchTest = cchName; + + RTCritSectLeave(&pTest->OutputLock); + RTCritSectLeave(&pTest->Lock); + + RTStrFree(pszOldName); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTestGuardedAlloc(RTTEST hTest, size_t cb, uint32_t cbAlign, bool fHead, void **ppvUser) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + if (cbAlign == 0) + cbAlign = 1; + AssertReturn(cbAlign <= PAGE_SIZE, VERR_INVALID_PARAMETER); + AssertReturn(cbAlign == (UINT32_C(1) << (ASMBitFirstSetU32(cbAlign) - 1)), VERR_INVALID_PARAMETER); + + /* + * Allocate the record and block and initialize them. + */ + int rc = VERR_NO_MEMORY; + PRTTESTGUARDEDMEM pMem = (PRTTESTGUARDEDMEM)RTMemAlloc(sizeof(*pMem)); + if (RT_LIKELY(pMem)) + { + size_t const cbAligned = RT_ALIGN_Z(cb, PAGE_SIZE); + pMem->aGuards[0].cb = pMem->aGuards[1].cb = pTest->cbGuard; + pMem->cbAlloc = pMem->aGuards[0].cb + pMem->aGuards[1].cb + cbAligned; + pMem->pvAlloc = RTMemPageAlloc(pMem->cbAlloc); + if (pMem->pvAlloc) + { + pMem->aGuards[0].pv = pMem->pvAlloc; + pMem->pvUser = (uint8_t *)pMem->pvAlloc + pMem->aGuards[0].cb; + pMem->aGuards[1].pv = (uint8_t *)pMem->pvUser + cbAligned; + if (!fHead) + { + size_t off = cb & PAGE_OFFSET_MASK; + if (off) + { + off = PAGE_SIZE - RT_ALIGN_Z(off, cbAlign); + pMem->pvUser = (uint8_t *)pMem->pvUser + off; + } + } + + /* + * Set up the guards and link the record. + */ + ASMMemFill32(pMem->aGuards[0].pv, pMem->aGuards[0].cb, 0xdeadbeef); + ASMMemFill32(pMem->aGuards[1].pv, pMem->aGuards[1].cb, 0xdeadbeef); + rc = RTMemProtect(pMem->aGuards[0].pv, pMem->aGuards[0].cb, RTMEM_PROT_NONE); + if (RT_SUCCESS(rc)) + { + rc = RTMemProtect(pMem->aGuards[1].pv, pMem->aGuards[1].cb, RTMEM_PROT_NONE); + if (RT_SUCCESS(rc)) + { + *ppvUser = pMem->pvUser; + + RTCritSectEnter(&pTest->Lock); + pMem->pNext = pTest->pGuardedMem; + pTest->pGuardedMem = pMem; + RTCritSectLeave(&pTest->Lock); + + return VINF_SUCCESS; + } + + RTMemProtect(pMem->aGuards[0].pv, pMem->aGuards[0].cb, RTMEM_PROT_WRITE | RTMEM_PROT_READ); + } + + RTMemPageFree(pMem->pvAlloc, pMem->cbAlloc); + } + RTMemFree(pMem); + } + return rc; +} + + +RTR3DECL(void *) RTTestGuardedAllocTail(RTTEST hTest, size_t cb) +{ + void *pvUser; + int rc = RTTestGuardedAlloc(hTest, cb, 1 /* cbAlign */, false /* fHead */, &pvUser); + if (RT_SUCCESS(rc)) + return pvUser; + return NULL; +} + + +RTR3DECL(void *) RTTestGuardedAllocHead(RTTEST hTest, size_t cb) +{ + void *pvUser; + int rc = RTTestGuardedAlloc(hTest, cb, 1 /* cbAlign */, true /* fHead */, &pvUser); + if (RT_SUCCESS(rc)) + return pvUser; + return NULL; +} + + +/** + * Frees one block of guarded memory. + * + * The caller is responsible for unlinking it. + * + * @param pMem The memory record. + */ +static void rtTestGuardedFreeOne(PRTTESTGUARDEDMEM pMem) +{ + int rc; + rc = RTMemProtect(pMem->aGuards[0].pv, pMem->aGuards[0].cb, RTMEM_PROT_WRITE | RTMEM_PROT_READ); AssertRC(rc); + rc = RTMemProtect(pMem->aGuards[1].pv, pMem->aGuards[1].cb, RTMEM_PROT_WRITE | RTMEM_PROT_READ); AssertRC(rc); + RTMemPageFree(pMem->pvAlloc, pMem->cbAlloc); + RTMemFree(pMem); +} + + +RTR3DECL(int) RTTestGuardedFree(RTTEST hTest, void *pv) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + if (!pv) + return VINF_SUCCESS; + + /* + * Find it. + */ + int rc = VERR_INVALID_POINTER; + PRTTESTGUARDEDMEM pPrev = NULL; + + RTCritSectEnter(&pTest->Lock); + for (PRTTESTGUARDEDMEM pMem = pTest->pGuardedMem; pMem; pMem = pMem->pNext) + { + if (pMem->pvUser == pv) + { + if (pPrev) + pPrev->pNext = pMem->pNext; + else + pTest->pGuardedMem = pMem->pNext; + rtTestGuardedFreeOne(pMem); + rc = VINF_SUCCESS; + break; + } + pPrev = pMem; + } + RTCritSectLeave(&pTest->Lock); + + return rc; +} + + +/** + * Outputs the formatted XML. + * + * @param pTest The test instance. + * @param pszFormat The format string. + * @param va The format arguments. + */ +static void rtTestXmlOutputV(PRTTESTINT pTest, const char *pszFormat, va_list va) +{ + if (pTest->fXmlEnabled) + { + char *pszStr; + ssize_t cchStr = RTStrAPrintfV(&pszStr, pszFormat, va); + if (pszStr) + { + if (pTest->hXmlPipe != NIL_RTPIPE) + RTPipeWriteBlocking(pTest->hXmlPipe, pszStr, cchStr, NULL); + if (pTest->hXmlFile != NIL_RTFILE) + RTFileWrite(pTest->hXmlFile, pszStr, cchStr, NULL); + RTStrFree(pszStr); + } + } +} + + +/** + * Outputs the formatted XML. + * + * @param pTest The test instance. + * @param pszFormat The format string. + * @param ... The format arguments. + */ +static void rtTestXmlOutput(PRTTESTINT pTest, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + rtTestXmlOutputV(pTest, pszFormat, va); + va_end(va); +} + + +/** + * Starts the XML stream. + * + * @param pTest The test instance. + * @param pszTest The test name. + */ +static void rtTestXmlStart(PRTTESTINT pTest, const char *pszTest) +{ + pTest->cXmlElements = 0; + if (pTest->fXmlEnabled) + { + rtTestXmlOutput(pTest, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"); + pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd; + pTest->fXmlTopTestDone = !(pTest->fFlags & RTTEST_C_XML_DELAY_TOP_TEST) || pTest->fXmlOmitTopTest; + if (pTest->fXmlTopTestDone && !pTest->fXmlOmitTopTest) + rtTestXmlElemStart(pTest, "Test", "name=%RMas", pszTest); + } +} + + +/** + * Emit an XML element that doesn't have any value and instead ends immediately. + * + * The caller must own the instance lock. + * + * @param pTest The test instance. + * @param pszTag The element tag. + * @param pszAttrFmt The element attributes as a format string. Use + * NULL if none. + * @param va Format string arguments. + */ +static void rtTestXmlElemV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va) +{ + if (pTest->fXmlEnabled) + { + RTTIMESPEC TimeSpec; + RTTIME Time; + char szTS[80]; + RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeSpec)), szTS, sizeof(szTS)); + + if (pTest->eXmlState != RTTESTINT::kXmlPos_ElementEnd) + rtTestXmlOutput(pTest, "\n"); + + if (!pszAttrFmt || !*pszAttrFmt) + rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas/>\n", + pTest->cXmlElements * 2, "", pszTag, szTS); + else + { + va_list va2; + va_copy(va2, va); + rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas %N/>\n", + pTest->cXmlElements * 2, "", pszTag, szTS, pszAttrFmt, &va2); + va_end(va2); + } + pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd; + } +} + + +/** + * Wrapper around rtTestXmlElemV. + */ +static void rtTestXmlElem(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...) +{ + va_list va; + va_start(va, pszAttrFmt); + rtTestXmlElemV(pTest, pszTag, pszAttrFmt, va); + va_end(va); +} + + +/** + * Starts a new XML element. + * + * The caller must own the instance lock. + * + * @param pTest The test instance. + * @param pszTag The element tag. + * @param pszAttrFmt The element attributes as a format string. Use + * NULL if none. + * @param va Format string arguments. + */ +static void rtTestXmlElemStartV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va) +{ + /* Push it onto the stack. */ + size_t i = pTest->cXmlElements; + AssertReturnVoid(i < RT_ELEMENTS(pTest->apszXmlElements)); + pTest->apszXmlElements[i] = pszTag; + pTest->cXmlElements = i + 1; + + if (pTest->fXmlEnabled) + { + RTTIMESPEC TimeSpec; + RTTIME Time; + char szTS[80]; + RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeSpec)), szTS, sizeof(szTS)); + + if (pTest->eXmlState != RTTESTINT::kXmlPos_ElementEnd) + rtTestXmlOutput(pTest, "\n"); + + if (!pszAttrFmt || !*pszAttrFmt) + rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas>", + i * 2, "", pszTag, szTS); + else + { + va_list va2; + va_copy(va2, va); + rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas %N>", + i * 2, "", pszTag, szTS, pszAttrFmt, &va2); + va_end(va2); + } + pTest->eXmlState = RTTESTINT::kXmlPos_ValueStart; + } +} + + +/** + * Wrapper around rtTestXmlElemStartV. + */ +static void rtTestXmlElemStart(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...) +{ + va_list va; + va_start(va, pszAttrFmt); + rtTestXmlElemStartV(pTest, pszTag, pszAttrFmt, va); + va_end(va); +} + + +/** + * Ends the current element. + * + * The caller must own the instance lock. + * + * @param pTest The test instance. + * @param pszTag The tag we're ending (chiefly for sanity + * checking). + */ +static void rtTestXmlElemEnd(PRTTESTINT pTest, const char *pszTag) +{ + /* pop the element */ + size_t i = pTest->cXmlElements; + AssertReturnVoid(i > 0); + i--; + AssertReturnVoid(!strcmp(pszTag, pTest->apszXmlElements[i])); + pTest->cXmlElements = i; + + /* Do the closing. */ + if (pTest->fXmlEnabled) + { + if (pTest->eXmlState == RTTESTINT::kXmlPos_ValueStart) + rtTestXmlOutput(pTest, "\n%*s</%s>\n", i * 2, "", pszTag); + else if (pTest->eXmlState == RTTESTINT::kXmlPos_ElementEnd) + rtTestXmlOutput(pTest, "%*s</%s>\n", i * 2, "", pszTag); + else + rtTestXmlOutput(pTest, "</%s>\n", pszTag); + pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd; + } +} + + +/** + * Ends the XML stream, closing all open elements. + * + * The caller must own the instance lock. + * + * @param pTest The test instance. + */ +static void rtTestXmlEnd(PRTTESTINT pTest) +{ + if (pTest->fXmlEnabled) + { + /* + * Close all the elements and add the final TestEnd one to get a + * final timestamp and some certainty that the XML is valid. + */ + size_t i = pTest->cXmlElements; + AssertReturnVoid(i > 0 || pTest->fXmlOmitTopTest || !pTest->fXmlTopTestDone); + while (i-- > 1) + { + const char *pszTag = pTest->apszXmlElements[pTest->cXmlElements]; + if (pTest->eXmlState == RTTESTINT::kXmlPos_ValueStart) + rtTestXmlOutput(pTest, "\n%*s</%s>\n", i * 2, "", pszTag); + else if (pTest->eXmlState == RTTESTINT::kXmlPos_ElementEnd) + rtTestXmlOutput(pTest, "%*s</%s>\n", i * 2, "", pszTag); + else + rtTestXmlOutput(pTest, "</%s>\n", pszTag); + pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd; + } + + if (!pTest->fXmlOmitTopTest && pTest->fXmlTopTestDone) + { + rtTestXmlElem(pTest, "End", "SubTests=\"%u\" SubTestsFailed=\"%u\" errors=\"%u\"", + pTest->cSubTests, pTest->cSubTestsFailed, pTest->cErrors); + rtTestXmlOutput(pTest, "</Test>\n"); + } + + /* + * Close the XML outputs. + */ + if (pTest->hXmlPipe != NIL_RTPIPE) + { + RTPipeClose(pTest->hXmlPipe); + pTest->hXmlPipe = NIL_RTPIPE; + } + if (pTest->hXmlFile != NIL_RTFILE) + { + RTFileClose(pTest->hXmlFile); + pTest->hXmlFile = NIL_RTFILE; + } + pTest->fXmlEnabled = false; + pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd; + } + pTest->cXmlElements = 0; +} + +/** + * Output callback. + * + * @returns number of bytes written. + * @param pvArg User argument. + * @param pachChars Pointer to an array of utf-8 characters. + * @param cbChars Number of bytes in the character array pointed to by pachChars. + */ +static DECLCALLBACK(size_t) rtTestPrintfOutput(void *pvArg, const char *pachChars, size_t cbChars) +{ + size_t cch = 0; + PRTTESTINT pTest = (PRTTESTINT)pvArg; + if (cbChars) + { + do + { + /* insert prefix if at a newline. */ + if (pTest->fNewLine) + { + RTStrmWrite(pTest->pOutStrm, pTest->pszTest, pTest->cchTest); + RTStrmWrite(pTest->pOutStrm, ": ", 2); + cch += 2 + pTest->cchTest; + } + + /* look for newline and write the stuff. */ + const char *pchEnd = (const char *)memchr(pachChars, '\n', cbChars); + if (!pchEnd) + { + pTest->fNewLine = false; + RTStrmWrite(pTest->pOutStrm, pachChars, cbChars); + cch += cbChars; + break; + } + + pTest->fNewLine = true; + size_t const cchPart = pchEnd - pachChars + 1; + RTStrmWrite(pTest->pOutStrm, pachChars, cchPart); + cch += cchPart; + pachChars += cchPart; + cbChars -= cchPart; + } while (cbChars); + } + else + RTStrmFlush(pTest->pOutStrm); + return cch; +} + + +/** + * Internal output worker. + * + * Caller takes the lock. + * + * @returns Number of chars printed. + * @param pTest The test instance. + * @param pszFormat The message. + * @param va The arguments. + */ +static int rtTestPrintfV(PRTTESTINT pTest, const char *pszFormat, va_list va) +{ + return (int)RTStrFormatV(rtTestPrintfOutput, pTest, NULL, NULL, pszFormat, va); +} + + +/** + * Internal output worker. + * + * Caller takes the lock. + * + * @returns Number of chars printed. + * @param pTest The test instance. + * @param pszFormat The message. + * @param ... The arguments. + */ +static int rtTestPrintf(PRTTESTINT pTest, const char *pszFormat, ...) +{ + va_list va; + + va_start(va, pszFormat); + int cch = rtTestPrintfV(pTest, pszFormat, va); + va_end(va); + + return cch; +} + + +RTR3DECL(int) RTTestPrintfNlV(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, va_list va) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, -1); + + RTCritSectEnter(&pTest->OutputLock); + + int cch = 0; + if (enmLevel <= pTest->enmMaxLevel) + { + if (!pTest->fNewLine) + cch += rtTestPrintf(pTest, "\n"); + cch += rtTestPrintfV(pTest, pszFormat, va); + } + + RTCritSectLeave(&pTest->OutputLock); + + return cch; +} + + +RTR3DECL(int) RTTestPrintfNl(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, ...) +{ + va_list va; + + va_start(va, pszFormat); + int cch = RTTestPrintfNlV(hTest, enmLevel, pszFormat, va); + va_end(va); + + return cch; +} + + +RTR3DECL(int) RTTestPrintfV(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, va_list va) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, -1); + + RTCritSectEnter(&pTest->OutputLock); + int cch = 0; + if (enmLevel <= pTest->enmMaxLevel) + cch += rtTestPrintfV(pTest, pszFormat, va); + RTCritSectLeave(&pTest->OutputLock); + + return cch; +} + + +RTR3DECL(int) RTTestPrintf(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, ...) +{ + va_list va; + + va_start(va, pszFormat); + int cch = RTTestPrintfV(hTest, enmLevel, pszFormat, va); + va_end(va); + + return cch; +} + + +RTR3DECL(int) RTTestBanner(RTTEST hTest) +{ + return RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "TESTING...\n"); +} + + +/** + * Prints the result of a sub-test if necessary. + * + * @returns Number of chars printed. + * @param pTest The test instance. + * @remarks Caller own the test Lock. + */ +static int rtTestSubTestReport(PRTTESTINT pTest) +{ + int cch = 0; + if ( !pTest->fSubTestReported + && pTest->pszSubTest) + { + pTest->fSubTestReported = true; + uint32_t cErrors = ASMAtomicUoReadU32(&pTest->cErrors) - pTest->cSubTestAtErrors; + if (!cErrors) + { + if (!pTest->fSubTestSkipped) + { + rtTestXmlElem(pTest, "Passed", NULL); + rtTestXmlElemEnd(pTest, "Test"); + cch += RTTestPrintfNl(pTest, RTTESTLVL_SUB_TEST, "%-60s: PASSED\n", pTest->pszSubTest); + } + else + { + rtTestXmlElem(pTest, "Skipped", NULL); + rtTestXmlElemEnd(pTest, "Test"); + cch += RTTestPrintfNl(pTest, RTTESTLVL_SUB_TEST, "%-60s: SKIPPED\n", pTest->pszSubTest); + } + } + else + { + pTest->cSubTestsFailed++; + rtTestXmlElem(pTest, "Failed", "errors=\"%u\"", cErrors); + rtTestXmlElemEnd(pTest, "Test"); + cch += RTTestPrintfNl(pTest, RTTESTLVL_SUB_TEST, "%-60s: FAILED (%u errors)\n", + pTest->pszSubTest, cErrors); + } + } + return cch; +} + + +/** + * RTTestSub and RTTestSubDone worker that cleans up the current (if any) + * sub test. + * + * @returns Number of chars printed. + * @param pTest The test instance. + * @remarks Caller own the test Lock. + */ +static int rtTestSubCleanup(PRTTESTINT pTest) +{ + int cch = 0; + if (pTest->pszSubTest) + { + cch += rtTestSubTestReport(pTest); + + RTStrFree((char *)pTest->pszSubTest); + pTest->pszSubTest = NULL; + pTest->fSubTestReported = true; + } + RTStrFree(pTest->pszErrCtx); + pTest->pszErrCtx = NULL; + return cch; +} + + +RTR3DECL(RTEXITCODE) RTTestSummaryAndDestroy(RTTEST hTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, RTEXITCODE_FAILURE); + + RTCritSectEnter(&pTest->Lock); + rtTestSubTestReport(pTest); + RTCritSectLeave(&pTest->Lock); + + RTEXITCODE enmExitCode; + if (!pTest->cErrors) + { + RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "SUCCESS\n"); + enmExitCode = RTEXITCODE_SUCCESS; + } + else + { + RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "FAILURE - %u errors\n", pTest->cErrors); + enmExitCode = RTEXITCODE_FAILURE; + } + + RTTestDestroy(pTest); + return enmExitCode; +} + + +RTR3DECL(RTEXITCODE) RTTestSkipAndDestroyV(RTTEST hTest, const char *pszReasonFmt, va_list va) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, RTEXITCODE_SKIPPED); + + RTCritSectEnter(&pTest->Lock); + rtTestSubTestReport(pTest); + RTCritSectLeave(&pTest->Lock); + + RTEXITCODE enmExitCode; + if (!pTest->cErrors) + { + if (pszReasonFmt) + RTTestPrintfNlV(hTest, RTTESTLVL_FAILURE, pszReasonFmt, va); + RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "SKIPPED\n"); + enmExitCode = RTEXITCODE_SKIPPED; + } + else + { + RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "FAILURE - %u errors\n", pTest->cErrors); + enmExitCode = RTEXITCODE_FAILURE; + } + + RTTestDestroy(pTest); + return enmExitCode; +} + + +RTR3DECL(RTEXITCODE) RTTestSkipAndDestroy(RTTEST hTest, const char *pszReasonFmt, ...) +{ + va_list va; + va_start(va, pszReasonFmt); + RTEXITCODE enmExitCode = RTTestSkipAndDestroyV(hTest, pszReasonFmt, va); + va_end(va); + return enmExitCode; +} + + +RTR3DECL(int) RTTestSub(RTTEST hTest, const char *pszSubTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, -1); + + RTCritSectEnter(&pTest->Lock); + + /* Cleanup, reporting if necessary previous sub test. */ + rtTestSubCleanup(pTest); + + /* Start new sub test. */ + pTest->cSubTests++; + pTest->cSubTestAtErrors = ASMAtomicUoReadU32(&pTest->cErrors); + pTest->pszSubTest = RTStrDup(pszSubTest); + pTest->cchSubTest = strlen(pszSubTest); + AssertMsg(pTest->cchSubTest < 64 /* See g_kcchMaxTestResultName in testmanager/config.py. */, + ("cchSubTest=%u: '%s'\n", pTest->cchSubTest, pTest->pszSubTest)); + pTest->fSubTestSkipped = false; + pTest->fSubTestReported = false; + + int cch = 0; + if (pTest->enmMaxLevel >= RTTESTLVL_DEBUG) + cch = RTTestPrintfNl(hTest, RTTESTLVL_DEBUG, "debug: Starting sub-test '%s'\n", pszSubTest); + + if (!pTest->fXmlTopTestDone) + { + pTest->fXmlTopTestDone = true; + rtTestXmlElemStart(pTest, "Test", "name=%RMas", pTest->pszTest); + } + + rtTestXmlElemStart(pTest, "Test", "name=%RMas", pszSubTest); + + RTCritSectLeave(&pTest->Lock); + + return cch; +} + + +RTR3DECL(int) RTTestSubF(RTTEST hTest, const char *pszSubTestFmt, ...) +{ + va_list va; + va_start(va, pszSubTestFmt); + int cch = RTTestSubV(hTest, pszSubTestFmt, va); + va_end(va); + return cch; +} + + +RTR3DECL(int) RTTestSubV(RTTEST hTest, const char *pszSubTestFmt, va_list va) +{ + char *pszSubTest; + RTStrAPrintfV(&pszSubTest, pszSubTestFmt, va); + if (pszSubTest) + { + int cch = RTTestSub(hTest, pszSubTest); + RTStrFree(pszSubTest); + return cch; + } + return 0; +} + + +RTR3DECL(int) RTTestSubDone(RTTEST hTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE); + + RTCritSectEnter(&pTest->Lock); + int cch = rtTestSubCleanup(pTest); + RTCritSectLeave(&pTest->Lock); + + return cch; +} + + +RTR3DECL(int) RTTestPassedV(RTTEST hTest, const char *pszFormat, va_list va) +{ + PRTTESTINT pTest = hTest; + AssertPtr(pszFormat); + RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE); + + int cch = 0; + if (pTest->enmMaxLevel >= RTTESTLVL_INFO) + { + va_list va2; + va_copy(va2, va); + + RTCritSectEnter(&pTest->OutputLock); + cch += rtTestPrintf(pTest, "%N\n", pszFormat, &va2); + RTCritSectLeave(&pTest->OutputLock); + + va_end(va2); + } + + return cch; +} + + +RTR3DECL(int) RTTestPassed(RTTEST hTest, const char *pszFormat, ...) +{ + va_list va; + + va_start(va, pszFormat); + int cch = RTTestPassedV(hTest, pszFormat, va); + va_end(va); + + return cch; +} + + +RTR3DECL(int) RTTestSkippedV(RTTEST hTest, const char *pszFormat, va_list va) +{ + PRTTESTINT pTest = hTest; + AssertPtrNull(pszFormat); + RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE); + + pTest->fSubTestSkipped = true; + + int cch = 0; + if (pszFormat && *pszFormat && pTest->enmMaxLevel >= RTTESTLVL_INFO) + { + va_list va2; + va_copy(va2, va); + + RTCritSectEnter(&pTest->OutputLock); + cch += rtTestPrintf(pTest, "%N\n", pszFormat, &va2); + RTCritSectLeave(&pTest->OutputLock); + + va_end(va2); + } + + return cch; +} + + +RTR3DECL(int) RTTestSkipped(RTTEST hTest, const char *pszFormat, ...) +{ + va_list va; + + va_start(va, pszFormat); + int cch = RTTestSkippedV(hTest, pszFormat, va); + va_end(va); + + return cch; +} + + + +/** + * Gets the unit name. + * + * @returns Unit name. + * @param enmUnit The unit. + */ +static const char *rtTestUnitName(RTTESTUNIT enmUnit) +{ + switch (enmUnit) + { + case RTTESTUNIT_PCT: return "%"; + case RTTESTUNIT_BYTES: return "bytes"; + case RTTESTUNIT_BYTES_PER_SEC: return "bytes/s"; + case RTTESTUNIT_KILOBYTES: return "KB"; + case RTTESTUNIT_KILOBYTES_PER_SEC: return "KB/s"; + case RTTESTUNIT_MEGABYTES: return "MB"; + case RTTESTUNIT_MEGABYTES_PER_SEC: return "MB/s"; + case RTTESTUNIT_PACKETS: return "packets"; + case RTTESTUNIT_PACKETS_PER_SEC: return "packets/s"; + case RTTESTUNIT_FRAMES: return "frames"; + case RTTESTUNIT_FRAMES_PER_SEC: return "frames/s"; + case RTTESTUNIT_OCCURRENCES: return "occurrences"; + case RTTESTUNIT_OCCURRENCES_PER_SEC: return "occurrences/s"; + case RTTESTUNIT_ROUND_TRIP: return "roundtrips"; + case RTTESTUNIT_CALLS: return "calls"; + case RTTESTUNIT_CALLS_PER_SEC: return "calls/s"; + case RTTESTUNIT_SECS: return "s"; + case RTTESTUNIT_MS: return "ms"; + case RTTESTUNIT_NS: return "ns"; + case RTTESTUNIT_NS_PER_CALL: return "ns/call"; + case RTTESTUNIT_NS_PER_FRAME: return "ns/frame"; + case RTTESTUNIT_NS_PER_OCCURRENCE: return "ns/occurrence"; + case RTTESTUNIT_NS_PER_PACKET: return "ns/packet"; + case RTTESTUNIT_NS_PER_ROUND_TRIP: return "ns/roundtrip"; + case RTTESTUNIT_INSTRS: return "ins"; + case RTTESTUNIT_INSTRS_PER_SEC: return "ins/sec"; + case RTTESTUNIT_NONE: return ""; + case RTTESTUNIT_PP1K: return "pp1k"; + case RTTESTUNIT_PP10K: return "pp10k"; + case RTTESTUNIT_PPM: return "ppm"; + case RTTESTUNIT_PPB: return "ppb"; + case RTTESTUNIT_TICKS: return "ticks"; + case RTTESTUNIT_TICKS_PER_CALL: return "ticks/call"; + case RTTESTUNIT_TICKS_PER_OCCURENCE: return "ticks/occ"; + case RTTESTUNIT_PAGES: return "pages"; + case RTTESTUNIT_PAGES_PER_SEC: return "pages/s"; + case RTTESTUNIT_TICKS_PER_PAGE: return "ticks/page"; + case RTTESTUNIT_NS_PER_PAGE: return "ns/page"; + case RTTESTUNIT_PS: return "ps"; + case RTTESTUNIT_PS_PER_CALL: return "ps/call"; + case RTTESTUNIT_PS_PER_FRAME: return "ps/frame"; + case RTTESTUNIT_PS_PER_OCCURRENCE: return "ps/occurrence"; + case RTTESTUNIT_PS_PER_PACKET: return "ps/packet"; + case RTTESTUNIT_PS_PER_ROUND_TRIP: return "ps/roundtrip"; + case RTTESTUNIT_PS_PER_PAGE: return "ps/page"; + + /* No default so gcc helps us keep this up to date. */ + case RTTESTUNIT_INVALID: + case RTTESTUNIT_END: + break; + } + AssertMsgFailed(("%d\n", enmUnit)); + return "unknown"; +} + + +RTR3DECL(int) RTTestValue(RTTEST hTest, const char *pszName, uint64_t u64Value, RTTESTUNIT enmUnit) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + + Assert(strlen(pszName) < 56 /* See g_kcchMaxTestValueName in testmanager/config.py. */); + + const char *pszUnit = rtTestUnitName(enmUnit); + + RTCritSectEnter(&pTest->Lock); + rtTestXmlElem(pTest, "Value", "name=%RMas unit=%RMas value=\"%llu\"", pszName, pszUnit, u64Value); + RTCritSectLeave(&pTest->Lock); + + RTCritSectEnter(&pTest->OutputLock); + rtTestPrintf(pTest, " %-58s: %'16llu %s\n", pszName, u64Value, pszUnit); + RTCritSectLeave(&pTest->OutputLock); + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTestValueF(RTTEST hTest, uint64_t u64Value, RTTESTUNIT enmUnit, const char *pszNameFmt, ...) +{ + va_list va; + va_start(va, pszNameFmt); + int rc = RTTestValueV(hTest, u64Value, enmUnit, pszNameFmt, va); + va_end(va); + return rc; +} + + +RTR3DECL(int) RTTestValueV(RTTEST hTest, uint64_t u64Value, RTTESTUNIT enmUnit, const char *pszNameFmt, va_list va) +{ + char *pszName; + RTStrAPrintfV(&pszName, pszNameFmt, va); + if (!pszName) + return VERR_NO_MEMORY; + int rc = RTTestValue(hTest, pszName, u64Value, enmUnit); + RTStrFree(pszName); + return rc; +} + + +RTR3DECL(int) RTTestErrorInc(RTTEST hTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + + ASMAtomicIncU32(&pTest->cErrors); + + return VINF_SUCCESS; +} + + +RTR3DECL(uint32_t) RTTestErrorCount(RTTEST hTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, UINT32_MAX); + + return ASMAtomicReadU32(&pTest->cErrors); +} + + +RTR3DECL(uint32_t) RTTestSubErrorCount(RTTEST hTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, UINT32_MAX); + + return ASMAtomicReadU32(&pTest->cErrors) - pTest->cSubTestAtErrors; +} + + +RTR3DECL(int) RTTestFailedV(RTTEST hTest, const char *pszFormat, va_list va) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE); + + RTTestErrorInc(pTest); + + int cch = 0; + if (pTest->enmMaxLevel >= RTTESTLVL_FAILURE) + { + va_list va2; + va_copy(va2, va); + + const char *pszEnd = strchr(pszFormat, '\0'); + bool fHasNewLine = pszFormat != pszEnd + && pszEnd[-1] == '\n'; + + RTCritSectEnter(&pTest->OutputLock); + cch += rtTestPrintf(pTest, fHasNewLine ? "%N" : "%N\n", pszFormat, &va2); + if (pTest->pszErrCtx) + { + cch += rtTestPrintf(pTest, "context: %s\n", pTest->pszErrCtx); + RTStrFree(pTest->pszErrCtx); + pTest->pszErrCtx = NULL; + } + RTCritSectLeave(&pTest->OutputLock); + + va_end(va2); + } + + return cch; +} + + +RTR3DECL(int) RTTestFailed(RTTEST hTest, const char *pszFormat, ...) +{ + va_list va; + + va_start(va, pszFormat); + int cch = RTTestFailedV(hTest, pszFormat, va); + va_end(va); + + return cch; +} + + +RTR3DECL(int) RTTestFailureDetailsV(RTTEST hTest, const char *pszFormat, va_list va) +{ + return RTTestPrintfV(hTest, RTTESTLVL_FAILURE, pszFormat, va); +} + + +RTR3DECL(int) RTTestFailureDetails(RTTEST hTest, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int cch = RTTestFailureDetailsV(hTest, pszFormat, va); + va_end(va); + return cch; +} + + +RTR3DECL(int) RTTestErrContextV(RTTEST hTest, const char *pszFormat, va_list va) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + + RTStrFree(pTest->pszErrCtx); + pTest->pszErrCtx = NULL; + + if (pszFormat && *pszFormat) + { + pTest->pszErrCtx = RTStrAPrintf2V(pszFormat, va); + AssertReturn(pTest->pszErrCtx, VERR_NO_STR_MEMORY); + RTStrStripR(pTest->pszErrCtx); + } + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTestErrContext(RTTEST hTest, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTTestErrContextV(hTest, pszFormat, va); + va_end(va); + return rc; +} + + +RTR3DECL(int) RTTestDisableAssertions(RTTEST hTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + + uint32_t cTimes = ASMAtomicIncU32(&pTest->cAssertionsDisabledAndQuieted); + if (cTimes >= 2 && cTimes <= 8) + return VINF_SUCCESS; + if (cTimes > 8) + { + RTAssertSetMayPanic(pTest->fAssertSavedMayPanic); + RTAssertSetQuiet(pTest->fAssertSavedQuiet); + Assert(cTimes <= 8); + } + pTest->fAssertSavedMayPanic = RTAssertSetMayPanic(false); + pTest->fAssertSavedQuiet = RTAssertSetQuiet(true); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTTestRestoreAssertions(RTTEST hTest) +{ + PRTTESTINT pTest = hTest; + RTTEST_GET_VALID_RETURN(pTest); + + uint32_t cTimes = ASMAtomicDecU32(&pTest->cAssertionsDisabledAndQuieted); + if (cTimes == 0) + { + RTAssertSetMayPanic(pTest->fAssertSavedMayPanic); + RTAssertSetQuiet(pTest->fAssertSavedQuiet); + } + else + AssertStmt(cTimes < UINT32_MAX / 2, ASMAtomicIncU32(&pTest->cAssertionsDisabledAndQuieted)); + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/testi.cpp b/src/VBox/Runtime/r3/testi.cpp new file mode 100644 index 00000000..28daa7bc --- /dev/null +++ b/src/VBox/Runtime/r3/testi.cpp @@ -0,0 +1,214 @@ +/* $Id: testi.cpp $ */ +/** @file + * IPRT - Testcase Framework, the implicit test handle API variation. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> +#include <iprt/stdarg.h> + + +RTR3DECL(int) RTTestIPrintfV(RTTESTLVL enmLevel, const char *pszFormat, va_list va) +{ + return RTTestPrintfV(NIL_RTTEST, enmLevel, pszFormat, va); +} + + +RTR3DECL(int) RTTestIPrintf(RTTESTLVL enmLevel, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int cch = RTTestPrintfV(NIL_RTTEST, enmLevel, pszFormat, va); + va_end(va); + return cch; +} + + +RTR3DECL(int) RTTestISub(const char *pszSubTest) +{ + return RTTestSub(NIL_RTTEST, pszSubTest); +} + + +RTR3DECL(int) RTTestISubF(const char *pszSubTestFmt, ...) +{ + va_list va; + va_start(va, pszSubTestFmt); + int cch = RTTestSubV(NIL_RTTEST, pszSubTestFmt, va); + va_end(va); + return cch; +} + + +RTR3DECL(int) RTTestISubV(const char *pszSubTestFmt, va_list va) +{ + return RTTestSubV(NIL_RTTEST, pszSubTestFmt, va); +} + + +RTR3DECL(int) RTTestISubDone(void) +{ + return RTTestSubDone(NIL_RTTEST); +} + + +RTR3DECL(int) RTTestIPassedV(const char *pszFormat, va_list va) +{ + return RTTestPassedV(NIL_RTTEST, pszFormat, va); +} + + +RTR3DECL(int) RTTestIPassed(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int cch = RTTestPassedV(NIL_RTTEST, pszFormat, va); + va_end(va); + return cch; +} + + +RTR3DECL(int) RTTestIValue(const char *pszName, uint64_t u64Value, RTTESTUNIT enmUnit) +{ + return RTTestValue(NIL_RTTEST, pszName, u64Value, enmUnit); +} + + +RTR3DECL(int) RTTestIValueF(uint64_t u64Value, RTTESTUNIT enmUnit, const char *pszNameFmt, ...) +{ + va_list va; + va_start(va, pszNameFmt); + int rc = RTTestValueV(NIL_RTTEST, u64Value, enmUnit, pszNameFmt, va); + va_end(va); + return rc; +} + + +RTR3DECL(int) RTTestIValueV(uint64_t u64Value, RTTESTUNIT enmUnit, const char *pszNameFmt, va_list va) +{ + return RTTestValueV(NIL_RTTEST, u64Value, enmUnit, pszNameFmt, va); +} + + +RTR3DECL(int) RTTestIErrorInc(void) +{ + return RTTestErrorInc(NIL_RTTEST); +} + + +RTR3DECL(uint32_t) RTTestIErrorCount(void) +{ + return RTTestErrorCount(NIL_RTTEST); +} + + +RTR3DECL(int) RTTestIFailedV(const char *pszFormat, va_list va) +{ + return RTTestFailedV(NIL_RTTEST, pszFormat, va); +} + + +RTR3DECL(int) RTTestIFailed(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int cch = RTTestFailedV(NIL_RTTEST, pszFormat, va); + va_end(va); + return cch; +} + + +RTR3DECL(int) RTTestIFailedRcV(int rcRet, const char *pszFormat, va_list va) +{ + RTTestFailedV(NIL_RTTEST, pszFormat, va); + return rcRet; +} + + +RTR3DECL(int) RTTestIFailedRc(int rcRet, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + RTTestFailedV(NIL_RTTEST, pszFormat, va); + va_end(va); + return rcRet; +} + + +RTR3DECL(int) RTTestIFailureDetailsV(const char *pszFormat, va_list va) +{ + return RTTestFailureDetails(NIL_RTTEST, pszFormat, va); +} + + +RTR3DECL(int) RTTestIFailureDetails(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int cch = RTTestFailureDetailsV(NIL_RTTEST, pszFormat, va); + va_end(va); + return cch; +} + + +RTR3DECL(int) RTTestIErrContextV(const char *pszFormat, va_list va) +{ + return RTTestErrContextV(NIL_RTTEST, pszFormat, va); +} + + +RTR3DECL(int) RTTestIErrContext(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTTestErrContextV(NIL_RTTEST, pszFormat, va); + va_end(va); + return rc; +} + + +RTR3DECL(int) RTTestIDisableAssertions(void) +{ + return RTTestDisableAssertions(NIL_RTTEST); +} + + +RTR3DECL(int) RTTestIRestoreAssertions(void) +{ + return RTTestRestoreAssertions(NIL_RTTEST); +} + diff --git a/src/VBox/Runtime/r3/udp.cpp b/src/VBox/Runtime/r3/udp.cpp new file mode 100644 index 00000000..2ed12047 --- /dev/null +++ b/src/VBox/Runtime/r3/udp.cpp @@ -0,0 +1,726 @@ +/* $Id: udp.cpp $ */ +/** @file + * IPRT - UDP/IP. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/winsock2.h> +#else +# include <sys/types.h> +# include <sys/socket.h> +# include <errno.h> +# include <netinet/in.h> +# include <netinet/udp.h> +# include <arpa/inet.h> +# include <netdb.h> +#endif +#include <limits.h> + +#include "internal/iprt.h" +#include <iprt/udp.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mempool.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/socket.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "internal/magics.h" +#include "internal/socket.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* fixup backlevel OSes. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) +# define socklen_t int +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * UDP Server state. + */ +typedef enum RTUDPSERVERSTATE +{ + /** Invalid. */ + RTUDPSERVERSTATE_INVALID = 0, + /** Created. */ + RTUDPSERVERSTATE_CREATED, + /** Thread for incoming datagrams is starting up. */ + RTUDPSERVERSTATE_STARTING, + /** Waiting for incoming datagrams. */ + RTUDPSERVERSTATE_WAITING, + /** Handling an incoming datagram. */ + RTUDPSERVERSTATE_RECEIVING, + /** Thread terminating. */ + RTUDPSERVERSTATE_STOPPING, + /** Thread terminated. */ + RTUDPSERVERSTATE_STOPPED, + /** Final cleanup before being unusable. */ + RTUDPSERVERSTATE_DESTROYING +} RTUDPSERVERSTATE; + +/* + * Internal representation of the UDP Server handle. + */ +typedef struct RTUDPSERVER +{ + /** The magic value (RTUDPSERVER_MAGIC). */ + uint32_t volatile u32Magic; + /** The server state. */ + RTUDPSERVERSTATE volatile enmState; + /** The server thread. */ + RTTHREAD Thread; + /** The server socket. */ + RTSOCKET volatile hSocket; + /** The datagram receiver function. */ + PFNRTUDPSERVE pfnServe; + /** Argument to pfnServer. */ + void *pvUser; +} RTUDPSERVER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) rtUdpServerThread(RTTHREAD ThreadSelf, void *pvServer); +static int rtUdpServerListen(PRTUDPSERVER pServer); +static int rtUdpServerListenCleanup(PRTUDPSERVER pServer); +static int rtUdpServerDestroySocket(RTSOCKET volatile *pSock, const char *pszMsg); +static int rtUdpClose(RTSOCKET Sock, const char *pszMsg); + + +/** + * Atomicly updates a socket variable. + * @returns The old handle value. + * @param phSock The socket handle variable to update. + * @param hNew The new socket handle value. + */ +DECLINLINE(RTSOCKET) rtUdpAtomicXchgSock(RTSOCKET volatile *phSock, const RTSOCKET hNew) +{ + RTSOCKET hRet; + ASMAtomicXchgHandle(phSock, hNew, &hRet); + return hRet; +} + + +/** + * Tries to change the UDP server state. + */ +DECLINLINE(bool) rtUdpServerTrySetState(PRTUDPSERVER pServer, RTUDPSERVERSTATE enmStateNew, RTUDPSERVERSTATE enmStateOld) +{ + bool fRc; + ASMAtomicCmpXchgSize(&pServer->enmState, enmStateNew, enmStateOld, fRc); + return fRc; +} + +/** + * Changes the UDP server state. + */ +DECLINLINE(void) rtUdpServerSetState(PRTUDPSERVER pServer, RTUDPSERVERSTATE enmStateNew, RTUDPSERVERSTATE enmStateOld) +{ + bool fRc; + ASMAtomicCmpXchgSize(&pServer->enmState, enmStateNew, enmStateOld, fRc); + Assert(fRc); NOREF(fRc); +} + + +/** + * Closes a socket. + * + * @returns IPRT status code. + */ +static int rtUdpServerDestroySocket(RTSOCKET volatile *pSock, const char *pszMsg) +{ + RTSOCKET hSocket = rtUdpAtomicXchgSock(pSock, NIL_RTSOCKET); + if (hSocket != NIL_RTSOCKET) + { + return rtUdpClose(hSocket, pszMsg); + } + return VINF_UDP_SERVER_NO_CLIENT; +} + + +RTR3DECL(int) RTUdpServerCreate(const char *pszAddress, unsigned uPort, RTTHREADTYPE enmType, const char *pszThrdName, + PFNRTUDPSERVE pfnServe, void *pvUser, PPRTUDPSERVER ppServer) +{ + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pfnServe, VERR_INVALID_POINTER); + AssertPtrReturn(pszThrdName, VERR_INVALID_POINTER); + AssertPtrReturn(ppServer, VERR_INVALID_POINTER); + + /* + * Create the server. + */ + PRTUDPSERVER pServer; + int rc = RTUdpServerCreateEx(pszAddress, uPort, &pServer); + if (RT_SUCCESS(rc)) + { + /* + * Create the listener thread. + */ + RTMemPoolRetain(pServer); + pServer->enmState = RTUDPSERVERSTATE_STARTING; + pServer->pvUser = pvUser; + pServer->pfnServe = pfnServe; + rc = RTThreadCreate(&pServer->Thread, rtUdpServerThread, pServer, 0, enmType, /*RTTHREADFLAGS_WAITABLE*/0, pszThrdName); + if (RT_SUCCESS(rc)) + { + /* done */ + if (ppServer) + *ppServer = pServer; + else + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return rc; + } + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + + /* + * Destroy the server. + */ + rtUdpServerSetState(pServer, RTUDPSERVERSTATE_CREATED, RTUDPSERVERSTATE_STARTING); + RTUdpServerDestroy(pServer); + } + + return rc; +} + + +/** + * Server thread, loops waiting for datagrams until it's terminated. + * + * @returns iprt status code. (ignored). + * @param ThreadSelf Thread handle. + * @param pvServer Server handle. + */ +static DECLCALLBACK(int) rtUdpServerThread(RTTHREAD ThreadSelf, void *pvServer) +{ + PRTUDPSERVER pServer = (PRTUDPSERVER)pvServer; + int rc; + if (rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_WAITING, RTUDPSERVERSTATE_STARTING)) + rc = rtUdpServerListen(pServer); + else + rc = rtUdpServerListenCleanup(pServer); + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + NOREF(ThreadSelf); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTUdpServerCreateEx(const char *pszAddress, uint32_t uPort, PPRTUDPSERVER ppServer) +{ + + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(ppServer, VERR_INVALID_PARAMETER); + + /* + * Resolve the address. + */ + RTNETADDR LocalAddr; + int rc = RTSocketParseInetAddress(pszAddress, uPort, &LocalAddr); + if (RT_FAILURE(rc)) + return rc; + + /* + * Setting up socket. + */ + RTSOCKET Sock; + rc = rtSocketCreate(&Sock, AF_INET, SOCK_DGRAM, IPPROTO_UDP, false /*fInheritable*/); + if (RT_SUCCESS(rc)) + { + /* + * Set socket options. + */ + int fFlag = 1; + if (!rtSocketSetOpt(Sock, SOL_SOCKET, SO_REUSEADDR, &fFlag, sizeof(fFlag))) + { + /* + * Bind a name to the socket. + */ + rc = rtSocketBind(Sock, &LocalAddr); + if (RT_SUCCESS(rc)) + { + /* + * Create the server handle. + */ + PRTUDPSERVER pServer = (PRTUDPSERVER)RTMemPoolAlloc(RTMEMPOOL_DEFAULT, sizeof(*pServer)); + if (pServer) + { + pServer->u32Magic = RTUDPSERVER_MAGIC; + pServer->enmState = RTUDPSERVERSTATE_CREATED; + pServer->Thread = NIL_RTTHREAD; + pServer->hSocket = Sock; + pServer->pfnServe = NULL; + pServer->pvUser = NULL; + *ppServer = pServer; + return VINF_SUCCESS; + } + + /* bail out */ + rc = VERR_NO_MEMORY; + } + } + else + AssertMsgFailed(("rtSocketSetOpt: %Rrc\n", rc)); + rtUdpClose(Sock, "RTServerCreateEx"); + } + + return rc; +} + + +RTR3DECL(int) RTUdpServerListen(PRTUDPSERVER pServer, PFNRTUDPSERVE pfnServe, void *pvUser) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pfnServe, VERR_INVALID_POINTER); + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTUDPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); + + int rc = VERR_INVALID_STATE; + if (rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_WAITING, RTUDPSERVERSTATE_CREATED)) + { + Assert(!pServer->pfnServe); + Assert(!pServer->pvUser); + Assert(pServer->Thread == NIL_RTTHREAD); + + pServer->pfnServe = pfnServe; + pServer->pvUser = pvUser; + pServer->Thread = RTThreadSelf(); + Assert(pServer->Thread != NIL_RTTHREAD); + rc = rtUdpServerListen(pServer); + } + else + { + AssertMsgFailed(("enmState=%d\n", pServer->enmState)); + rc = VERR_INVALID_STATE; + } + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return rc; +} + + +/** + * Internal worker common for RTUdpServerListen and the thread created by + * RTUdpServerCreate(). + * + * The caller makes sure it has its own memory reference and releases it upon + * return. + */ +static int rtUdpServerListen(PRTUDPSERVER pServer) +{ + /* + * Wait for incoming datagrams loop. + */ + for (;;) + { + /* + * Change state, getting an extra reference to the socket so we can + * allow others to close it while we're stuck in rtSocketAccept. + */ + RTUDPSERVERSTATE enmState = pServer->enmState; + RTSOCKET hSocket; + ASMAtomicReadHandle(&pServer->hSocket, &hSocket); + if (hSocket != NIL_RTSOCKET) + RTSocketRetain(hSocket); + if ( enmState != RTUDPSERVERSTATE_WAITING + && enmState != RTUDPSERVERSTATE_RECEIVING) + { + RTSocketRelease(hSocket); + return rtUdpServerListenCleanup(pServer); + } + if (!rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_WAITING, enmState)) + { + RTSocketRelease(hSocket); + continue; + } + + /* + * Wait for incoming datagrams or errors. + */ + uint32_t fEvents; + int rc = RTSocketSelectOneEx(hSocket, RTSOCKET_EVT_READ | RTSOCKET_EVT_ERROR, &fEvents, 1000); + RTSocketRelease(hSocket); + if (rc == VERR_TIMEOUT) + continue; + if (RT_FAILURE(rc)) + { + /* These are typical for what can happen during destruction. */ + if ( rc == VERR_INVALID_HANDLE + || rc == VERR_INVALID_PARAMETER + || rc == VERR_NET_NOT_SOCKET) + return rtUdpServerListenCleanup(pServer); + continue; + } + if (fEvents & RTSOCKET_EVT_ERROR) + return rtUdpServerListenCleanup(pServer); + + /* + * Run a pfnServe callback. + */ + if (!rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_RECEIVING, RTUDPSERVERSTATE_WAITING)) + return rtUdpServerListenCleanup(pServer); + rc = pServer->pfnServe(hSocket, pServer->pvUser); + + /* + * Stop the server? + */ + if (rc == VERR_UDP_SERVER_STOP) + { + if (rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_STOPPING, RTUDPSERVERSTATE_RECEIVING)) + { + /* + * Reset the server socket and change the state to stopped. After that state change + * we cannot safely access the handle so we'll have to return here. + */ + hSocket = rtUdpAtomicXchgSock(&pServer->hSocket, NIL_RTSOCKET); + rtUdpServerSetState(pServer, RTUDPSERVERSTATE_STOPPED, RTUDPSERVERSTATE_STOPPING); + rtUdpClose(hSocket, "Listener: server stopped"); + } + else + rtUdpServerListenCleanup(pServer); /* ignore rc */ + return rc; + } + } +} + + +/** + * Clean up after listener. + */ +static int rtUdpServerListenCleanup(PRTUDPSERVER pServer) +{ + /* + * Close the server socket. + */ + rtUdpServerDestroySocket(&pServer->hSocket, "ListenCleanup"); + + /* + * Figure the return code and make sure the state is OK. + */ + RTUDPSERVERSTATE enmState = pServer->enmState; + switch (enmState) + { + case RTUDPSERVERSTATE_STOPPING: + case RTUDPSERVERSTATE_STOPPED: + return VERR_UDP_SERVER_SHUTDOWN; + + case RTUDPSERVERSTATE_WAITING: + rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_STOPPED, enmState); + return VERR_UDP_SERVER_DESTROYED; + + case RTUDPSERVERSTATE_DESTROYING: + return VERR_UDP_SERVER_DESTROYED; + + case RTUDPSERVERSTATE_STARTING: + case RTUDPSERVERSTATE_RECEIVING: + default: + AssertMsgFailedReturn(("pServer=%p enmState=%d\n", pServer, enmState), VERR_INTERNAL_ERROR_4); + } +} + + +RTR3DECL(int) RTUdpServerShutdown(PRTUDPSERVER pServer) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTUDPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); + + /* + * Try change the state to stopping, then replace and destroy the server socket. + */ + for (;;) + { + RTUDPSERVERSTATE enmState = pServer->enmState; + if ( enmState != RTUDPSERVERSTATE_WAITING + && enmState != RTUDPSERVERSTATE_RECEIVING) + { + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + switch (enmState) + { + case RTUDPSERVERSTATE_CREATED: + case RTUDPSERVERSTATE_STARTING: + default: + AssertMsgFailed(("%d\n", enmState)); + return VERR_INVALID_STATE; + + case RTUDPSERVERSTATE_STOPPING: + case RTUDPSERVERSTATE_STOPPED: + return VINF_SUCCESS; + + case RTUDPSERVERSTATE_DESTROYING: + return VERR_UDP_SERVER_DESTROYED; + } + } + if (rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_STOPPING, enmState)) + { + rtUdpServerDestroySocket(&pServer->hSocket, "RTUdpServerShutdown"); + rtUdpServerSetState(pServer, RTUDPSERVERSTATE_STOPPED, RTUDPSERVERSTATE_STOPPING); + + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return VINF_SUCCESS; + } + } +} + + +RTR3DECL(int) RTUdpServerDestroy(PRTUDPSERVER pServer) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTUDPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); /* paranoia */ + + /* + * Move the state along so the listener can figure out what's going on. + */ + for (;;) + { + bool fDestroyable; + RTUDPSERVERSTATE enmState = pServer->enmState; + switch (enmState) + { + case RTUDPSERVERSTATE_STARTING: + case RTUDPSERVERSTATE_WAITING: + case RTUDPSERVERSTATE_RECEIVING: + case RTUDPSERVERSTATE_CREATED: + case RTUDPSERVERSTATE_STOPPED: + fDestroyable = rtUdpServerTrySetState(pServer, RTUDPSERVERSTATE_DESTROYING, enmState); + break; + + /* destroyable states */ + case RTUDPSERVERSTATE_STOPPING: + fDestroyable = true; + break; + + /* + * Everything else means user or internal misbehavior. + */ + default: + AssertMsgFailed(("pServer=%p enmState=%d\n", pServer, enmState)); + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return VERR_INTERNAL_ERROR; + } + if (fDestroyable) + break; + } + + /* + * Destroy it. + */ + ASMAtomicWriteU32(&pServer->u32Magic, ~RTUDPSERVER_MAGIC); + rtUdpServerDestroySocket(&pServer->hSocket, "Destroyer: server"); + + /* + * Release it. + */ + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return VINF_SUCCESS; +} + + +/** + * Internal close function which does all the proper bitching. + */ +static int rtUdpClose(RTSOCKET Sock, const char *pszMsg) +{ + NOREF(pszMsg); /** @todo drop this parameter? */ + + /* ignore nil handles. */ + if (Sock == NIL_RTSOCKET) + return VINF_SUCCESS; + + /* + * Close the socket handle (drops our reference to it). + */ + return RTSocketClose(Sock); +} + + +RTR3DECL(int) RTUdpRead(RTSOCKET Sock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead, PRTNETADDR pSrcAddr) +{ + if (!RT_VALID_PTR(pcbRead)) + return VERR_INVALID_POINTER; + return RTSocketReadFrom(Sock, pvBuffer, cbBuffer, pcbRead, pSrcAddr); +} + + +RTR3DECL(int) RTUdpWrite(PRTUDPSERVER pServer, const void *pvBuffer, size_t cbBuffer, PCRTNETADDR pDstAddr) +{ + /* + * Validate input and retain the instance. + */ + AssertPtrReturn(pServer, VERR_INVALID_HANDLE); + AssertReturn(pServer->u32Magic == RTUDPSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTMemPoolRetain(pServer) != UINT32_MAX, VERR_INVALID_HANDLE); + + RTSOCKET hSocket; + ASMAtomicReadHandle(&pServer->hSocket, &hSocket); + if (hSocket == NIL_RTSOCKET) + { + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + return VERR_INVALID_HANDLE; + } + RTSocketRetain(hSocket); + + int rc = VINF_SUCCESS; + RTUDPSERVERSTATE enmState = pServer->enmState; + if ( enmState != RTUDPSERVERSTATE_CREATED + && enmState != RTUDPSERVERSTATE_STARTING + && enmState != RTUDPSERVERSTATE_WAITING + && enmState != RTUDPSERVERSTATE_RECEIVING + && enmState != RTUDPSERVERSTATE_STOPPING) + rc = VERR_INVALID_STATE; + + if (RT_SUCCESS(rc)) + rc = RTSocketWriteTo(hSocket, pvBuffer, cbBuffer, pDstAddr); + + RTSocketRelease(hSocket); + RTMemPoolRelease(RTMEMPOOL_DEFAULT, pServer); + + return rc; +} + + +RTR3DECL(int) RTUdpCreateClientSocket(const char *pszAddress, uint32_t uPort, PRTNETADDR pLocalAddr, PRTSOCKET pSock) +{ + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszAddress, VERR_INVALID_POINTER); + AssertPtrReturn(pSock, VERR_INVALID_POINTER); + + /* + * Resolve the address. + */ + RTNETADDR Addr; + int rc = RTSocketParseInetAddress(pszAddress, uPort, &Addr); + if (RT_FAILURE(rc)) + return rc; + + /* + * Create the socket and connect. + */ + RTSOCKET Sock; + rc = rtSocketCreate(&Sock, AF_INET, SOCK_DGRAM, 0, false /*fInheritable*/); + if (RT_SUCCESS(rc)) + { + if (pLocalAddr) + rc = rtSocketBind(Sock, pLocalAddr); + if (RT_SUCCESS(rc)) + { + rc = rtSocketConnect(Sock, &Addr, RT_SOCKETCONNECT_DEFAULT_WAIT); + if (RT_SUCCESS(rc)) + { + *pSock = Sock; + return VINF_SUCCESS; + } + } + RTSocketClose(Sock); + } + return rc; +} + + +RTR3DECL(int) RTUdpCreateServerSocket(const char *pszAddress, uint32_t uPort, PRTSOCKET pSock) +{ + /* + * Validate input. + */ + AssertReturn(uPort > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszAddress, VERR_INVALID_POINTER); + AssertPtrReturn(pSock, VERR_INVALID_POINTER); + + /* + * Resolve the address. + */ + RTNETADDR LocalAddr; + int rc = RTSocketParseInetAddress(pszAddress, uPort, &LocalAddr); + if (RT_FAILURE(rc)) + return rc; + + /* + * Setting up socket. + */ + RTSOCKET Sock; + rc = rtSocketCreate(&Sock, AF_INET, SOCK_DGRAM, IPPROTO_UDP, false /*fInheritable*/); + if (RT_SUCCESS(rc)) + { + /* + * Set socket options. + */ + int fFlag = 1; + if (!rtSocketSetOpt(Sock, SOL_SOCKET, SO_REUSEADDR, &fFlag, sizeof(fFlag))) + { + /* + * Bind a name to the socket. + */ + rc = rtSocketBind(Sock, &LocalAddr); + if (RT_SUCCESS(rc)) + { + *pSock = Sock; + return VINF_SUCCESS; + } + } + RTSocketClose(Sock); + } + return rc; +} diff --git a/src/VBox/Runtime/r3/win/Makefile.kup b/src/VBox/Runtime/r3/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/win/Makefile.kup diff --git a/src/VBox/Runtime/r3/win/RTCrStoreCreateSnapshotById-win.cpp b/src/VBox/Runtime/r3/win/RTCrStoreCreateSnapshotById-win.cpp new file mode 100644 index 00000000..1eead2b1 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTCrStoreCreateSnapshotById-win.cpp @@ -0,0 +1,194 @@ +/* $Id: RTCrStoreCreateSnapshotById-win.cpp $ */ +/** @file + * IPRT - RTCrStoreCreateSnapshotById, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/crypto/store.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/once.h> +#include <iprt/ldr.h> + +#include <iprt/win/windows.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef HCERTSTORE (WINAPI *PFNCERTOPENSTORE)(PCSTR pszStoreProvider, DWORD dwEncodingType, HCRYPTPROV_LEGACY hCryptProv, + DWORD dwFlags, const void *pvParam); +typedef BOOL (WINAPI *PFNCERTCLOSESTORE)(HCERTSTORE hCertStore, DWORD dwFlags); +typedef PCCERT_CONTEXT (WINAPI *PFNCERTENUMCERTIFICATESINSTORE)(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext); + + + +static int rtCrStoreAddCertsFromNative(RTCRSTORE hStore, DWORD fStore, PCRTUTF16 pwszStoreName, + PFNCERTOPENSTORE pfnOpenStore, PFNCERTCLOSESTORE pfnCloseStore, + PFNCERTENUMCERTIFICATESINSTORE pfnEnumCerts, int rc, PRTERRINFO pErrInfo) +{ + DWORD fOpenStore = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG; + HCERTSTORE hNativeStore = pfnOpenStore(CERT_STORE_PROV_SYSTEM_W, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL /* hCryptProv = default */, fStore | fOpenStore, pwszStoreName); + if (hNativeStore) + { + PCCERT_CONTEXT pCurCtx = NULL; + while ((pCurCtx = pfnEnumCerts(hNativeStore, pCurCtx)) != NULL) + { + if (pCurCtx->dwCertEncodingType & X509_ASN_ENCODING) + { + RTERRINFOSTATIC StaticErrInfo; + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, + RTErrInfoInitStatic(&StaticErrInfo), + &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "CurCtx"); + RTCRX509CERTIFICATE MyCert; + int rc2 = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &MyCert, "Cert"); + if (RT_SUCCESS(rc2)) + { + rc2 = RTCrStoreCertAddEncoded(hStore, RTCRCERTCTX_F_ENC_X509_DER | RTCRCERTCTX_F_ADD_IF_NOT_FOUND, + pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, + RTErrInfoInitStatic(&StaticErrInfo)); + RTCrX509Certificate_Delete(&MyCert); + } + if (RT_FAILURE(rc2)) + { + if (RTErrInfoIsSet(&StaticErrInfo.Core)) + RTErrInfoAddF(pErrInfo, -rc2, " %s", StaticErrInfo.Core.pszMsg); + else + RTErrInfoAddF(pErrInfo, -rc2, " %Rrc adding cert", rc2); + rc = -rc2; + } + } + } + pfnCloseStore(hNativeStore, CERT_CLOSE_STORE_CHECK_FLAG); + } + else + { + DWORD uLastErr = GetLastError(); + if (uLastErr != ERROR_FILE_NOT_FOUND) + rc = RTErrInfoAddF(pErrInfo, -RTErrConvertFromWin32(uLastErr), + " CertOpenStore(%#x,'%ls') failed: %u", fStore, pwszStoreName); + } + return rc; +} + + + +RTDECL(int) RTCrStoreCreateSnapshotById(PRTCRSTORE phStore, RTCRSTOREID enmStoreId, PRTERRINFO pErrInfo) +{ + AssertReturn(enmStoreId > RTCRSTOREID_INVALID && enmStoreId < RTCRSTOREID_END, VERR_INVALID_PARAMETER); + + /* + * Create an empty in-memory store. + */ + RTCRSTORE hStore; + int rc = RTCrStoreCreateInMem(&hStore, 128); + if (RT_SUCCESS(rc)) + { + *phStore = hStore; + + /* + * Resolve the APIs we need to do this job. + */ + RTLDRMOD hLdrMod; + int rc2 = RTLdrLoadSystem("crypt32.dll", false /*NoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc2)) + { + PFNCERTOPENSTORE pfnOpenStore = NULL; + rc2 = RTLdrGetSymbol(hLdrMod, "CertOpenStore", (void **)&pfnOpenStore); + + PFNCERTCLOSESTORE pfnCloseStore = NULL; + if (RT_SUCCESS(rc2)) + rc2 = RTLdrGetSymbol(hLdrMod, "CertCloseStore", (void **)&pfnCloseStore); + + PFNCERTENUMCERTIFICATESINSTORE pfnEnumCerts = NULL; + if (RT_SUCCESS(rc2)) + rc2 = RTLdrGetSymbol(hLdrMod, "CertEnumCertificatesInStore", (void **)&pfnEnumCerts); + if (RT_SUCCESS(rc2)) + { + /* + * Do the work. + */ + DWORD fStore = CERT_SYSTEM_STORE_CURRENT_USER; + switch (enmStoreId) + { + case RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES: + case RTCRSTOREID_SYSTEM_INTERMEDIATE_CAS: + fStore = CERT_SYSTEM_STORE_LOCAL_MACHINE; + RT_FALL_THRU(); + case RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES: + case RTCRSTOREID_USER_INTERMEDIATE_CAS: + { + /** @todo CA and MY in s_apwszRootStores are _very_ questionable!!! However, + * curl may need them to work correct and it doesn't seem to have any + * intermediate ca file. :/ */ + static PCRTUTF16 const s_apwszRootStores[] = { L"AuthRoot", L"CA", L"MY", L"Root" }; + static PCRTUTF16 const s_apwszIntermediateStores[] = { L"CA", L"MY" }; + PCRTUTF16 const *papwszStores = s_apwszRootStores; + uint32_t cStores = RT_ELEMENTS(s_apwszRootStores); + if (enmStoreId == RTCRSTOREID_USER_INTERMEDIATE_CAS || enmStoreId == RTCRSTOREID_SYSTEM_INTERMEDIATE_CAS) + { + papwszStores = s_apwszIntermediateStores; + cStores = RT_ELEMENTS(s_apwszIntermediateStores); + } + + for (uint32_t i = 0; i < cStores; i++) + rc = rtCrStoreAddCertsFromNative(hStore, fStore, papwszStores[i], pfnOpenStore, pfnCloseStore, + pfnEnumCerts, rc, pErrInfo); + break; + } + + default: + AssertFailed(); /* implement me */ + } + } + else + rc = RTErrInfoSetF(pErrInfo, -rc2, "Error resolving crypt32.dll APIs"); + RTLdrClose(hLdrMod); + } + else + rc = RTErrInfoSetF(pErrInfo, -rc2, "Error loading crypt32.dll"); + } + else + RTErrInfoSet(pErrInfo, rc, "RTCrStoreCreateInMem failed"); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCreateSnapshotById); + diff --git a/src/VBox/Runtime/r3/win/RTFileQuerySectorSize-win.cpp b/src/VBox/Runtime/r3/win/RTFileQuerySectorSize-win.cpp new file mode 100644 index 00000000..97a38271 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTFileQuerySectorSize-win.cpp @@ -0,0 +1,71 @@ +/* $Id: RTFileQuerySectorSize-win.cpp $ */ +/** @file + * IPRT - RTFileQuerySectorSize, Windows. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/file.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> + +#include <iprt/win/windows.h> + + + +RTDECL(int) RTFileQuerySectorSize(RTFILE hFile, uint32_t *pcbSector) +{ + AssertPtrReturn(pcbSector, VERR_INVALID_PARAMETER); + + DISK_GEOMETRY DriveGeo; + RT_ZERO(DriveGeo); + DWORD cbDriveGeo = 0; + if (DeviceIoControl((HANDLE)RTFileToNative(hFile), + IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, + &DriveGeo, sizeof(DriveGeo), &cbDriveGeo, NULL)) + { + AssertReturn(DriveGeo.BytesPerSector > 0, VERR_INVALID_PARAMETER); + *pcbSector = DriveGeo.BytesPerSector; + return VINF_SUCCESS; + } + int rc = RTErrConvertFromWin32(GetLastError()); + AssertMsg(rc == VERR_IO_NOT_READY, ("%d / %Rrc\n", GetLastError(), rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/RTHandleGetStandard-win.cpp b/src/VBox/Runtime/r3/win/RTHandleGetStandard-win.cpp new file mode 100644 index 00000000..c0b7f780 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTHandleGetStandard-win.cpp @@ -0,0 +1,143 @@ +/* $Id: RTHandleGetStandard-win.cpp $ */ +/** @file + * IPRT - RTHandleGetStandard, Windows. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/handle.h> + +#include <iprt/file.h> +#include <iprt/pipe.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/log.h> + +#include <iprt/win/windows.h> + +#include "internal/socket.h" /* (Needs Windows.h.) */ +#include "internal-r3-win.h" /* (Needs Windows.h.) */ + + +RTDECL(int) RTHandleGetStandard(RTHANDLESTD enmStdHandle, bool fLeaveOpen, PRTHANDLE ph) +{ + /* + * Validate and convert input. + */ + AssertPtrReturn(ph, VERR_INVALID_POINTER); + DWORD dwStdHandle; + switch (enmStdHandle) + { + case RTHANDLESTD_INPUT: dwStdHandle = STD_INPUT_HANDLE; break; + case RTHANDLESTD_OUTPUT: dwStdHandle = STD_OUTPUT_HANDLE; break; + case RTHANDLESTD_ERROR: dwStdHandle = STD_ERROR_HANDLE; break; + default: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + + /* + * Is the requested descriptor valid and which IPRT handle type does it + * best map on to? + */ + HANDLE hNative = GetStdHandle(dwStdHandle); + if (hNative == INVALID_HANDLE_VALUE) + return RTErrConvertFromWin32(GetLastError()); + + DWORD dwInfo = 0; + if (g_pfnGetHandleInformation && !g_pfnGetHandleInformation(hNative, &dwInfo)) + return RTErrConvertFromWin32(GetLastError()); + bool const fInherit = RT_BOOL(dwInfo & HANDLE_FLAG_INHERIT); + + SetLastError(NO_ERROR); + RTHANDLE h; + DWORD dwType = GetFileType(hNative); + switch (dwType & ~FILE_TYPE_REMOTE) + { + case FILE_TYPE_UNKNOWN: + if (GetLastError() != NO_ERROR) + return RTErrConvertFromWin32(GetLastError()); + RT_FALL_THROUGH(); + default: + case FILE_TYPE_CHAR: + case FILE_TYPE_DISK: + h.enmType = RTHANDLETYPE_FILE; + break; + + case FILE_TYPE_PIPE: + { + DWORD cMaxInstances; + DWORD fInfo; + if (!GetNamedPipeInfo(hNative, &fInfo, NULL, NULL, &cMaxInstances)) + h.enmType = RTHANDLETYPE_SOCKET; + else + h.enmType = RTHANDLETYPE_PIPE; + break; + } + } + + /* + * Create the IPRT handle. + */ + int rc; + switch (h.enmType) + { + case RTHANDLETYPE_FILE: + /** @todo fLeaveOpen */ + rc = RTFileFromNative(&h.u.hFile, (RTHCUINTPTR)hNative); + break; + + case RTHANDLETYPE_PIPE: + rc = RTPipeFromNative(&h.u.hPipe, (RTHCUINTPTR)hNative, + (enmStdHandle == RTHANDLESTD_INPUT ? RTPIPE_N_READ : RTPIPE_N_WRITE) + | (fInherit ? RTPIPE_N_INHERIT : 0) + | (fLeaveOpen ? RTPIPE_N_LEAVE_OPEN : 0)); + break; + + case RTHANDLETYPE_SOCKET: + rc = rtSocketCreateForNative(&h.u.hSocket, (RTHCUINTPTR)hNative, fLeaveOpen); + break; + + default: /* shut up gcc */ + return VERR_INTERNAL_ERROR; + } + + if (RT_SUCCESS(rc)) + *ph = h; + + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/RTLocaleQueryNormalizedBaseLocaleName-win.cpp b/src/VBox/Runtime/r3/win/RTLocaleQueryNormalizedBaseLocaleName-win.cpp new file mode 100644 index 00000000..10efd342 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTLocaleQueryNormalizedBaseLocaleName-win.cpp @@ -0,0 +1,116 @@ +/* $Id: RTLocaleQueryNormalizedBaseLocaleName-win.cpp $ */ +/** @file + * IPRT - RTLocaleQueryNormalizedBaseLocaleName, ring-3, Windows. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/locale.h> +#include "internal/iprt.h" + +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + + +RTDECL(int) RTLocaleQueryNormalizedBaseLocaleName(char *pszName, size_t cbName) +{ + /* + * Note! This part is duplicate of r3/generic/RTLocaleQueryNormalizedBaseLocaleName-r3-generic.cpp! + */ + char szLocale[_1K]; + int rc = RTLocaleQueryLocaleName(szLocale, sizeof(szLocale)); + if (RT_SUCCESS(rc)) + { + /* + * May return some complicated "LC_XXX=yyy;LC.." sequence if + * partially set (like IPRT does). Try get xx_YY sequence first + * because 'C' or 'POSIX' may be LC_xxx variants that haven't been + * set yet. + * + * ASSUMES complicated locale mangling is done in a certain way... + */ + const char *pszLocale = strchr(szLocale, '='); + if (!pszLocale) + pszLocale = szLocale; + else + pszLocale++; + bool fSeenC = false; + bool fSeenPOSIX = false; + do + { + const char *pszEnd = strchr(pszLocale, ';'); + + if ( RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszLocale) + && ( pszLocale[5] == '\0' + || RT_C_IS_PUNCT(pszLocale[5])) ) + return RTStrCopyEx(pszName, cbName, pszLocale, 5); + + if ( pszLocale[0] == 'C' + && ( pszLocale[1] == '\0' + || RT_C_IS_PUNCT(pszLocale[1])) ) + fSeenC = true; + else if ( strncmp(pszLocale, "POSIX", 5) == 0 + && ( pszLocale[5] == '\0' + || RT_C_IS_PUNCT(pszLocale[5])) ) + fSeenPOSIX = true; + + /* advance */ + pszLocale = pszEnd ? strchr(pszEnd + 1, '=') : NULL; + } while (pszLocale++); + + if (fSeenC || fSeenPOSIX) + return RTStrCopy(pszName, cbName, "C"); /* C and POSIX should be identical IIRC, so keep it simple. */ + + rc = VERR_NOT_AVAILABLE; + } + + /* + * Fallback. + */ + if ( GetLocaleInfoA(GetUserDefaultLCID(), LOCALE_SISO639LANGNAME, szLocale, sizeof(szLocale)) == 3 + && GetLocaleInfoA(GetUserDefaultLCID(), LOCALE_SISO3166CTRYNAME, &szLocale[3], sizeof(szLocale) - 4) == 3) + { + szLocale[2] = '_'; + Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szLocale)); + return RTStrCopy(pszName, cbName, szLocale); + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/RTLocaleQueryUserCountryCode-win.cpp b/src/VBox/Runtime/r3/win/RTLocaleQueryUserCountryCode-win.cpp new file mode 100644 index 00000000..a95b1c22 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTLocaleQueryUserCountryCode-win.cpp @@ -0,0 +1,129 @@ +/* $Id: RTLocaleQueryUserCountryCode-win.cpp $ */ +/** @file + * IPRT - RTLocaleQueryUserCountryCode, ring-3, Windows. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/locale.h> +#include "internal/iprt.h" + +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef GEOID (WINAPI *PFNGETUSERGEOID)(GEOCLASS); +typedef INT (WINAPI *PFNGETGEOINFOW)(GEOID,GEOTYPE,LPWSTR,INT,LANGID); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Pointer to GetUserGeoID. */ +static PFNGETUSERGEOID g_pfnGetUserGeoID = NULL; +/** Pointer to GetGeoInfoW. */ +static PFNGETGEOINFOW g_pfnGetGeoInfoW = NULL; +/** Set if we've tried to resolve the APIs. */ +static bool volatile g_fResolvedApis = false; + + +RTDECL(int) RTLocaleQueryUserCountryCode(char pszCountryCode[3]) +{ + /* + * Get API pointers. + */ + PFNGETUSERGEOID pfnGetUserGeoID; + PFNGETGEOINFOW pfnGetGeoInfoW; + if (g_fResolvedApis) + { + pfnGetUserGeoID = g_pfnGetUserGeoID; + pfnGetGeoInfoW = g_pfnGetGeoInfoW; + } + else + { + pfnGetUserGeoID = (PFNGETUSERGEOID)GetProcAddress(g_hModKernel32, "GetUserGeoID"); + pfnGetGeoInfoW = (PFNGETGEOINFOW)GetProcAddress(g_hModKernel32, "GetGeoInfoW"); + g_pfnGetUserGeoID = pfnGetUserGeoID; + g_pfnGetGeoInfoW = pfnGetGeoInfoW; + g_fResolvedApis = true; + } + + int rc; + if ( pfnGetGeoInfoW + && pfnGetUserGeoID) + { + /* + * Call the API and retrieve the two letter ISO country code. + */ + GEOID idGeo = pfnGetUserGeoID(GEOCLASS_NATION); + if (idGeo != GEOID_NOT_AVAILABLE) + { + RTUTF16 wszName[16]; + RT_ZERO(wszName); + DWORD cwcReturned = pfnGetGeoInfoW(idGeo, GEO_ISO2, wszName, RT_ELEMENTS(wszName), LOCALE_NEUTRAL); + if ( cwcReturned >= 2 + && cwcReturned <= 3 + && wszName[2] == '\0' + && wszName[1] != '\0' + && RT_C_IS_ALPHA(wszName[1]) + && wszName[0] != '\0' + && RT_C_IS_ALPHA(wszName[0]) ) + { + pszCountryCode[0] = RT_C_TO_UPPER(wszName[0]); + pszCountryCode[1] = RT_C_TO_UPPER(wszName[1]); + pszCountryCode[2] = '\0'; + return VINF_SUCCESS; + } + AssertMsgFailed(("cwcReturned=%d err=%u wszName='%.16ls'\n", cwcReturned, GetLastError(), wszName)); + } + rc = VERR_NOT_AVAILABLE; + } + else + rc = VERR_NOT_SUPPORTED; + pszCountryCode[0] = 'Z'; + pszCountryCode[1] = 'Z'; + pszCountryCode[2] = '\0'; + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/RTLogWriteDebugger-win.cpp b/src/VBox/Runtime/r3/win/RTLogWriteDebugger-win.cpp new file mode 100644 index 00000000..27d3bc05 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTLogWriteDebugger-win.cpp @@ -0,0 +1,54 @@ +/* $Id: RTLogWriteDebugger-win.cpp $ */ +/** @file + * IPRT - Log To Debugger, Win32. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/log.h> +#include <iprt/assert.h> + + +RTDECL(void) RTLogWriteDebugger(const char *pch, size_t cb) +{ + if (pch[cb] != '\0') + AssertBreakpoint(); + OutputDebugStringA(pch); + return; +} + diff --git a/src/VBox/Runtime/r3/win/RTSystemFirmware-win.cpp b/src/VBox/Runtime/r3/win/RTSystemFirmware-win.cpp new file mode 100644 index 00000000..d00dacb1 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTSystemFirmware-win.cpp @@ -0,0 +1,223 @@ +/* $Id: RTSystemFirmware-win.cpp $ */ +/** @file + * IPRT - System firmware information, Win32. + */ + +/* + * Copyright (C) 2019-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/system.h> + +#include <iprt/nt/nt-and-windows.h> +#include <WinSDKVer.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/ldr.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#if _WIN32_MAXVER < 0x0602 /* Windows 7 or older, supply missing GetFirmwareType bits. */ +typedef enum _FIRMWARE_TYPE +{ + FirmwareTypeUnknown, + FirmwareTypeBios, + FirmwareTypeUefi, + FirmwareTypeMax +} FIRMWARE_TYPE; +typedef FIRMWARE_TYPE *PFIRMWARE_TYPE; +WINBASEAPI BOOL WINAPI GetFirmwareType(PFIRMWARE_TYPE); +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Defines the UEFI Globals UUID. */ +#define VBOX_UEFI_UUID_GLOBALS L"{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}" +/** Defines an UEFI dummy UUID, see MSDN docs of the API. */ +#define VBOX_UEFI_UUID_DUMMY L"{00000000-0000-0000-0000-000000000000}" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static volatile bool g_fResolvedApis = false; +static decltype(GetFirmwareType) *g_pfnGetFirmwareType; +static decltype(GetFirmwareEnvironmentVariableW) *g_pfnGetFirmwareEnvironmentVariableW; + + +static void rtSystemFirmwareResolveApis(void) +{ + FARPROC pfnTmp1 = GetProcAddress(g_hModKernel32, "GetFirmwareType"); + FARPROC pfnTmp2 = GetProcAddress(g_hModKernel32, "GetFirmwareEnvironmentVariableW"); + ASMCompilerBarrier(); /* paranoia^2 */ + + g_pfnGetFirmwareType = (decltype(GetFirmwareType) *)pfnTmp1; + g_pfnGetFirmwareEnvironmentVariableW = (decltype(GetFirmwareEnvironmentVariableW) *)pfnTmp2; + ASMAtomicWriteBool(&g_fResolvedApis, true); +} + + +static int rtSystemFirmwareGetPrivileges(LPCTSTR pcszPrivilege) +{ + HANDLE hToken; + BOOL fRc = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken); + if (!fRc) + return RTErrConvertFromWin32(GetLastError()); + + int rc = VINF_SUCCESS; + + TOKEN_PRIVILEGES tokenPriv; + fRc = LookupPrivilegeValue(NULL, pcszPrivilege, &tokenPriv.Privileges[0].Luid); + if (fRc) + { + tokenPriv.PrivilegeCount = 1; + tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + fRc = AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, 0, (PTOKEN_PRIVILEGES)NULL, 0); + if (!fRc) + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + CloseHandle(hToken); + + return rc; +} + + +RTDECL(int) RTSystemQueryFirmwareType(PRTSYSFWTYPE penmFirmwareType) +{ + AssertPtrReturn(penmFirmwareType, VERR_INVALID_POINTER); + + if (!g_fResolvedApis) + rtSystemFirmwareResolveApis(); + + *penmFirmwareType = RTSYSFWTYPE_INVALID; + int rc = VERR_NOT_SUPPORTED; + + /* GetFirmwareType is Windows 8 and later. */ + if (g_pfnGetFirmwareType) + { + FIRMWARE_TYPE enmWinFwType; + if (g_pfnGetFirmwareType(&enmWinFwType)) + { + switch (enmWinFwType) + { + case FirmwareTypeBios: + *penmFirmwareType = RTSYSFWTYPE_BIOS; + break; + case FirmwareTypeUefi: + *penmFirmwareType = RTSYSFWTYPE_UEFI; + break; + default: + *penmFirmwareType = RTSYSFWTYPE_UNKNOWN; + AssertMsgFailed(("%d\n", enmWinFwType)); + break; + } + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + /* GetFirmwareEnvironmentVariableW is XP and later. */ + else if (g_pfnGetFirmwareEnvironmentVariableW) + { + rtSystemFirmwareGetPrivileges(SE_SYSTEM_ENVIRONMENT_NAME); + + /* On a non-UEFI system (or such a system in legacy boot mode), we will get + back ERROR_INVALID_FUNCTION when querying any firmware variable. While on a + UEFI system we'll typically get ERROR_ACCESS_DENIED or similar as the dummy + is a non-exising dummy namespace. See the API docs. */ + SetLastError(0); + uint8_t abWhatever[64]; + DWORD cbRet = g_pfnGetFirmwareEnvironmentVariableW(L"", VBOX_UEFI_UUID_DUMMY, abWhatever, sizeof(abWhatever)); + DWORD dwErr = GetLastError(); + *penmFirmwareType = cbRet != 0 || dwErr != ERROR_INVALID_FUNCTION ? RTSYSFWTYPE_UEFI : RTSYSFWTYPE_BIOS; + rc = VINF_SUCCESS; + } + return rc; +} + + +RTDECL(int) RTSystemQueryFirmwareBoolean(RTSYSFWBOOL enmBoolean, bool *pfValue) +{ + *pfValue = false; + + /* + * Translate the enmBoolean to a name: + */ + const wchar_t *pwszName = NULL; + switch (enmBoolean) + { + case RTSYSFWBOOL_SECURE_BOOT: + pwszName = L"SecureBoot"; + break; + + default: + AssertReturn(enmBoolean > RTSYSFWBOOL_INVALID && enmBoolean < RTSYSFWBOOL_END, VERR_INVALID_PARAMETER); + return VERR_SYS_UNSUPPORTED_FIRMWARE_PROPERTY; + } + + /* + * Do the query. + * Note! This will typically fail with access denied unless we're in an elevated process. + */ + if (!g_pfnGetFirmwareEnvironmentVariableW) + return VERR_NOT_SUPPORTED; + rtSystemFirmwareGetPrivileges(SE_SYSTEM_ENVIRONMENT_NAME); + + uint8_t bValue = 0; + DWORD cbRet = g_pfnGetFirmwareEnvironmentVariableW(pwszName, VBOX_UEFI_UUID_GLOBALS, &bValue, sizeof(bValue)); + *pfValue = cbRet != 0 && bValue != 0; + if (cbRet != 0) + return VINF_SUCCESS; + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_INVALID_FUNCTION + || dwErr == ERROR_ENVVAR_NOT_FOUND) + return VINF_SUCCESS; + return RTErrConvertFromWin32(dwErr); +} + diff --git a/src/VBox/Runtime/r3/win/RTSystemQueryDmiString-win.cpp b/src/VBox/Runtime/r3/win/RTSystemQueryDmiString-win.cpp new file mode 100644 index 00000000..bd9c97e5 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTSystemQueryDmiString-win.cpp @@ -0,0 +1,269 @@ +/* $Id: RTSystemQueryDmiString-win.cpp $ */ +/** @file + * IPRT - RTSystemQueryDmiString, windows ring-3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define _WIN32_DCOM +#include <iprt/win/windows.h> +#include <WbemCli.h> + +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + + +/** + * Initialize COM. + * + * @returns COM status code. + */ +static HRESULT rtSystemDmiWinInitialize(void) +{ + HRESULT hrc = CoInitializeEx(0, COINIT_MULTITHREADED); + if (SUCCEEDED(hrc)) + { + hrc = CoInitializeSecurity(NULL, + -1, /* COM authentication. */ + NULL, /* Which authentication services. */ + NULL, /* Reserved. */ + RPC_C_AUTHN_LEVEL_DEFAULT, /* Default authentication. */ + RPC_C_IMP_LEVEL_IMPERSONATE, /* Default impersonation. */ + NULL, /* Authentication info. */ + EOAC_NONE, /* Additional capabilities. */ + NULL); /* Reserved. */ + if (hrc == RPC_E_TOO_LATE) + hrc = S_OK; + else if (FAILED(hrc)) + CoUninitialize(); + } + return hrc; +} + + +/** + * Undo what rtSystemDmiWinInitialize did. + */ +static void rtSystemDmiWinTerminate(void) +{ + CoUninitialize(); +} + + +/** + * Convert a UTF-8 string to a BSTR. + * + * @returns BSTR pointer. + * @param psz The UTF-8 string. + */ +static BSTR rtSystemWinBstrFromUtf8(const char *psz) +{ + PRTUTF16 pwsz = NULL; + int rc = RTStrToUtf16(psz, &pwsz); + if (RT_FAILURE(rc)) + return NULL; + BSTR pBStr = SysAllocString((const OLECHAR *)pwsz); + RTUtf16Free(pwsz); + return pBStr; +} + + +/** + * Connect to the DMI server. + * + * @returns COM status code. + * @param pLocator The locator. + * @param pszServer The server name. + * @param ppServices Where to return the services interface. + */ +static HRESULT rtSystemDmiWinConnectToServer(IWbemLocator *pLocator, const char *pszServer, IWbemServices **ppServices) +{ + AssertPtr(pLocator); + AssertPtrNull(pszServer); + AssertPtr(ppServices); + + BSTR pBStrServer = rtSystemWinBstrFromUtf8(pszServer); + if (!pBStrServer) + return E_OUTOFMEMORY; + + HRESULT hrc = pLocator->ConnectServer(pBStrServer, + NULL, + NULL, + 0, + NULL, + 0, + 0, + ppServices); + if (SUCCEEDED(hrc)) + { + hrc = CoSetProxyBlanket(*ppServices, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + NULL, + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE); + if (FAILED(hrc)) + (*ppServices)->Release(); + } + SysFreeString(pBStrServer); + return hrc; +} + + +RTDECL(int) RTSystemQueryDmiString(RTSYSDMISTR enmString, char *pszBuf, size_t cbBuf) +{ + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER); + *pszBuf = '\0'; + AssertReturn(enmString > RTSYSDMISTR_INVALID && enmString < RTSYSDMISTR_END, VERR_INVALID_PARAMETER); + + /* + * Figure the property name before we start. + */ + const char *pszPropName; + switch (enmString) + { + case RTSYSDMISTR_PRODUCT_NAME: pszPropName = "Name"; break; + case RTSYSDMISTR_PRODUCT_VERSION: pszPropName = "Version"; break; + case RTSYSDMISTR_PRODUCT_UUID: pszPropName = "UUID"; break; + case RTSYSDMISTR_PRODUCT_SERIAL: pszPropName = "IdentifyingNumber"; break; + case RTSYSDMISTR_MANUFACTURER: pszPropName = "Vendor"; break; + + default: + return VERR_NOT_SUPPORTED; + } + + /* + * Before we do anything with COM, we have to initialize it. + */ + bool fUninit = true; + HRESULT hrc = rtSystemDmiWinInitialize(); + if (hrc == RPC_E_CHANGED_MODE) + fUninit = false; /* don't fail if already initialized */ + else if (FAILED(hrc)) + return VERR_NOT_SUPPORTED; + + int rc = VERR_NOT_SUPPORTED; + BSTR pBstrPropName = rtSystemWinBstrFromUtf8(pszPropName); + if (pBstrPropName) + { + /* + * Instantiate the IWbemLocator, whatever that is and connect to the + * DMI serve. + */ + IWbemLocator *pLoc; + hrc = CoCreateInstance(CLSID_WbemLocator, + 0, + CLSCTX_INPROC_SERVER, + IID_IWbemLocator, + (LPVOID *)&pLoc); + if (SUCCEEDED(hrc)) + { + IWbemServices *pServices; + hrc = rtSystemDmiWinConnectToServer(pLoc, "ROOT\\CIMV2", &pServices); + if (SUCCEEDED(hrc)) + { + /* + * Enumerate whatever it is we're looking at and try get + * the desired property. + */ + BSTR pBstrFilter = rtSystemWinBstrFromUtf8("Win32_ComputerSystemProduct"); + if (pBstrFilter) + { + IEnumWbemClassObject *pEnum; + hrc = pServices->CreateInstanceEnum(pBstrFilter, 0, NULL, &pEnum); + if (SUCCEEDED(hrc)) + { + do + { + IWbemClassObject *pObj; + ULONG cObjRet; + hrc = pEnum->Next(WBEM_INFINITE, 1, &pObj, &cObjRet); + if ( SUCCEEDED(hrc) + && cObjRet >= 1) + { + VARIANT Var; + VariantInit(&Var); + hrc = pObj->Get(pBstrPropName, 0, &Var, 0, 0); + if ( SUCCEEDED(hrc) + && V_VT(&Var) == VT_BSTR) + { + /* + * Convert the BSTR to UTF-8 and copy it + * into the return buffer. + */ + char *pszValue; + rc = RTUtf16ToUtf8(Var.bstrVal, &pszValue); + if (RT_SUCCESS(rc)) + { + rc = RTStrCopy(pszBuf, cbBuf, pszValue); + RTStrFree(pszValue); + hrc = WBEM_S_FALSE; + } + } + VariantClear(&Var); + pObj->Release(); + } + } while (hrc != WBEM_S_FALSE); + + pEnum->Release(); + } + SysFreeString(pBstrFilter); + } + else + hrc = E_OUTOFMEMORY; + pServices->Release(); + } + pLoc->Release(); + } + SysFreeString(pBstrPropName); + } + else + hrc = E_OUTOFMEMORY; + if (fUninit) + rtSystemDmiWinTerminate(); + if (FAILED(hrc) && rc == VERR_NOT_SUPPORTED) + rc = VERR_NOT_SUPPORTED; + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/RTSystemQueryOSInfo-win.cpp b/src/VBox/Runtime/r3/win/RTSystemQueryOSInfo-win.cpp new file mode 100644 index 00000000..56688e42 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTSystemQueryOSInfo-win.cpp @@ -0,0 +1,358 @@ +/* $Id: RTSystemQueryOSInfo-win.cpp $ */ +/** @file + * IPRT - RTSystemQueryOSInfo, generic stub. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/win/windows.h> +#include <WinUser.h> + +#include "internal-r3-win.h" +#include <iprt/system.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * These are the PRODUCT_* defines found in the Vista Platform SDK and returned + * by GetProductInfo(). + * + * We define them ourselves because we don't necessarily have any Vista PSDK around. + */ +typedef enum RTWINPRODTYPE +{ + kRTWinProdType_UNDEFINED = 0x00000000, ///< An unknown product + kRTWinProdType_BUSINESS = 0x00000006, ///< Business Edition + kRTWinProdType_BUSINESS_N = 0x00000010, ///< Business Edition + kRTWinProdType_CLUSTER_SERVER = 0x00000012, ///< Cluster Server Edition + kRTWinProdType_DATACENTER_SERVER = 0x00000008, ///< Server Datacenter Edition (full installation) + kRTWinProdType_DATACENTER_SERVER_CORE = 0x0000000C, ///< Server Datacenter Edition (core installation) + kRTWinProdType_ENTERPRISE = 0x00000004, ///< Enterprise Edition + kRTWinProdType_ENTERPRISE_N = 0x0000001B, ///< Enterprise Edition + kRTWinProdType_ENTERPRISE_SERVER = 0x0000000A, ///< Server Enterprise Edition (full installation) + kRTWinProdType_ENTERPRISE_SERVER_CORE = 0x0000000E, ///< Server Enterprise Edition (core installation) + kRTWinProdType_ENTERPRISE_SERVER_IA64 = 0x0000000F, ///< Server Enterprise Edition for Itanium-based Systems + kRTWinProdType_HOME_BASIC = 0x00000002, ///< Home Basic Edition + kRTWinProdType_HOME_BASIC_N = 0x00000005, ///< Home Basic Edition + kRTWinProdType_HOME_PREMIUM = 0x00000003, ///< Home Premium Edition + kRTWinProdType_HOME_PREMIUM_N = 0x0000001A, ///< Home Premium Edition + kRTWinProdType_HOME_SERVER = 0x00000013, ///< Home Server Edition + kRTWinProdType_SERVER_FOR_SMALLBUSINESS = 0x00000018, ///< Server for Small Business Edition + kRTWinProdType_SMALLBUSINESS_SERVER = 0x00000009, ///< Small Business Server + kRTWinProdType_SMALLBUSINESS_SERVER_PREMIUM = 0x00000019, ///< Small Business Server Premium Edition + kRTWinProdType_STANDARD_SERVER = 0x00000007, ///< Server Standard Edition (full installation) + kRTWinProdType_STANDARD_SERVER_CORE = 0x0000000D, ///< Server Standard Edition (core installation) + kRTWinProdType_STARTER = 0x0000000B, ///< Starter Edition + kRTWinProdType_STORAGE_ENTERPRISE_SERVER = 0x00000017, ///< Storage Server Enterprise Edition + kRTWinProdType_STORAGE_EXPRESS_SERVER = 0x00000014, ///< Storage Server Express Edition + kRTWinProdType_STORAGE_STANDARD_SERVER = 0x00000015, ///< Storage Server Standard Edition + kRTWinProdType_STORAGE_WORKGROUP_SERVER = 0x00000016, ///< Storage Server Workgroup Edition + kRTWinProdType_ULTIMATE = 0x00000001, ///< Ultimate Edition + kRTWinProdType_ULTIMATE_N = 0x0000001C, ///< Ultimate Edition + kRTWinProdType_WEB_SERVER = 0x00000011, ///< Web Server Edition (full) + kRTWinProdType_WEB_SERVER_CORE = 0x0000001D ///< Web Server Edition (core) +} RTWINPRODTYPE; + + +/** + * Wrapper around the GetProductInfo API. + * + * @returns The vista type. + */ +static RTWINPRODTYPE rtSystemWinGetProductInfo(DWORD dwOSMajorVersion, DWORD dwOSMinorVersion, DWORD dwSpMajorVersion, DWORD dwSpMinorVersion) +{ + BOOL (WINAPI *pfnGetProductInfo)(DWORD, DWORD, DWORD, DWORD, PDWORD); + pfnGetProductInfo = (BOOL (WINAPI *)(DWORD, DWORD, DWORD, DWORD, PDWORD))GetProcAddress(GetModuleHandle("kernel32.dll"), + "GetProductInfo"); + if (pfnGetProductInfo) + { + DWORD dwProductType = kRTWinProdType_UNDEFINED; + if (pfnGetProductInfo(dwOSMajorVersion, dwOSMinorVersion, dwSpMajorVersion, dwSpMinorVersion, &dwProductType)) + return (RTWINPRODTYPE)dwProductType; + } + return kRTWinProdType_UNDEFINED; +} + + + +/** + * Appends the product type if available (Vista & 2003 only for some reason). + * + * @param pszTmp The buffer. + * @param cbTmp The buffer size. + */ +static void rtSystemWinAppendProductType(char *pszTmp, size_t cbTmp) +{ + RTWINPRODTYPE enmVistaType = rtSystemWinGetProductInfo(6, 0, 0, 0); + switch (enmVistaType) + { + case kRTWinProdType_BUSINESS: RTStrCat(pszTmp, cbTmp, " Business Edition"); break; + case kRTWinProdType_BUSINESS_N: RTStrCat(pszTmp, cbTmp, " Business Edition"); break; + case kRTWinProdType_CLUSTER_SERVER: RTStrCat(pszTmp, cbTmp, " Cluster Server Edition"); break; + case kRTWinProdType_DATACENTER_SERVER: RTStrCat(pszTmp, cbTmp, " Server Datacenter Edition (full installation)"); break; + case kRTWinProdType_DATACENTER_SERVER_CORE: RTStrCat(pszTmp, cbTmp, " Server Datacenter Edition (core installation)"); break; + case kRTWinProdType_ENTERPRISE: RTStrCat(pszTmp, cbTmp, " Enterprise Edition"); break; + case kRTWinProdType_ENTERPRISE_N: RTStrCat(pszTmp, cbTmp, " Enterprise Edition"); break; + case kRTWinProdType_ENTERPRISE_SERVER: RTStrCat(pszTmp, cbTmp, " Server Enterprise Edition (full installation)"); break; + case kRTWinProdType_ENTERPRISE_SERVER_CORE: RTStrCat(pszTmp, cbTmp, " Server Enterprise Edition (core installation)"); break; + case kRTWinProdType_ENTERPRISE_SERVER_IA64: RTStrCat(pszTmp, cbTmp, " Server Enterprise Edition for Itanium-based Systems"); break; + case kRTWinProdType_HOME_BASIC: RTStrCat(pszTmp, cbTmp, " Home Basic Edition"); break; + case kRTWinProdType_HOME_BASIC_N: RTStrCat(pszTmp, cbTmp, " Home Basic Edition"); break; + case kRTWinProdType_HOME_PREMIUM: RTStrCat(pszTmp, cbTmp, " Home Premium Edition"); break; + case kRTWinProdType_HOME_PREMIUM_N: RTStrCat(pszTmp, cbTmp, " Home Premium Edition"); break; + case kRTWinProdType_HOME_SERVER: RTStrCat(pszTmp, cbTmp, " Home Server Edition"); break; + case kRTWinProdType_SERVER_FOR_SMALLBUSINESS: RTStrCat(pszTmp, cbTmp, " Server for Small Business Edition"); break; + case kRTWinProdType_SMALLBUSINESS_SERVER: RTStrCat(pszTmp, cbTmp, " Small Business Server"); break; + case kRTWinProdType_SMALLBUSINESS_SERVER_PREMIUM: RTStrCat(pszTmp, cbTmp, " Small Business Server Premium Edition"); break; + case kRTWinProdType_STANDARD_SERVER: RTStrCat(pszTmp, cbTmp, " Server Standard Edition (full installation)"); break; + case kRTWinProdType_STANDARD_SERVER_CORE: RTStrCat(pszTmp, cbTmp, " Server Standard Edition (core installation)"); break; + case kRTWinProdType_STARTER: RTStrCat(pszTmp, cbTmp, " Starter Edition"); break; + case kRTWinProdType_STORAGE_ENTERPRISE_SERVER: RTStrCat(pszTmp, cbTmp, " Storage Server Enterprise Edition"); break; + case kRTWinProdType_STORAGE_EXPRESS_SERVER: RTStrCat(pszTmp, cbTmp, " Storage Server Express Edition"); break; + case kRTWinProdType_STORAGE_STANDARD_SERVER: RTStrCat(pszTmp, cbTmp, " Storage Server Standard Edition"); break; + case kRTWinProdType_STORAGE_WORKGROUP_SERVER: RTStrCat(pszTmp, cbTmp, " Storage Server Workgroup Edition"); break; + case kRTWinProdType_ULTIMATE: RTStrCat(pszTmp, cbTmp, " Ultimate Edition"); break; + case kRTWinProdType_ULTIMATE_N: RTStrCat(pszTmp, cbTmp, " Ultimate Edition"); break; + case kRTWinProdType_WEB_SERVER: RTStrCat(pszTmp, cbTmp, " Web Server Edition (full installation)"); break; + case kRTWinProdType_WEB_SERVER_CORE: RTStrCat(pszTmp, cbTmp, " Web Server Edition (core installation)"); break; + case kRTWinProdType_UNDEFINED: break; + } +} + + +/** + * Services the RTSYSOSINFO_PRODUCT, RTSYSOSINFO_RELEASE + * and RTSYSOSINFO_SERVICE_PACK requests. + * + * @returns See RTSystemQueryOSInfo. + * @param enmInfo See RTSystemQueryOSInfo. + * @param pszInfo See RTSystemQueryOSInfo. + * @param cchInfo See RTSystemQueryOSInfo. + */ +static int rtSystemWinQueryOSVersion(RTSYSOSINFO enmInfo, char *pszInfo, size_t cchInfo) +{ + /* + * Make sure it's terminated correctly in case of error. + */ + *pszInfo = '\0'; + + /* + * Check that we got the windows version at init time. + */ + AssertReturn(g_WinOsInfoEx.dwOSVersionInfoSize, VERR_WRONG_ORDER); + + /* + * Service the request. + */ + char szTmp[512]; + szTmp[0] = '\0'; + switch (enmInfo) + { + /* + * The product name. + */ + case RTSYSOSINFO_PRODUCT: + { + switch (g_enmWinVer) + { + case kRTWinOSType_95: strcpy(szTmp, "Windows 95"); break; + case kRTWinOSType_95SP1: strcpy(szTmp, "Windows 95 (Service Pack 1)"); break; + case kRTWinOSType_95OSR2: strcpy(szTmp, "Windows 95 (OSR 2)"); break; + case kRTWinOSType_98: strcpy(szTmp, "Windows 98"); break; + case kRTWinOSType_98SP1: strcpy(szTmp, "Windows 98 (Service Pack 1)"); break; + case kRTWinOSType_98SE: strcpy(szTmp, "Windows 98 (Second Edition)"); break; + case kRTWinOSType_ME: strcpy(szTmp, "Windows Me"); break; + case kRTWinOSType_NT310: strcpy(szTmp, "Windows NT 3.10"); break; + case kRTWinOSType_NT350: strcpy(szTmp, "Windows NT 3.50"); break; + case kRTWinOSType_NT351: strcpy(szTmp, "Windows NT 3.51"); break; + case kRTWinOSType_NT4: strcpy(szTmp, "Windows NT 4.0"); break; + case kRTWinOSType_2K: strcpy(szTmp, "Windows 2000"); break; + case kRTWinOSType_XP: + strcpy(szTmp, "Windows XP"); + if (g_WinOsInfoEx.wSuiteMask & VER_SUITE_PERSONAL) + RTStrCat(szTmp, sizeof(szTmp), " Home"); + if ( g_WinOsInfoEx.wProductType == VER_NT_WORKSTATION + && !(g_WinOsInfoEx.wSuiteMask & VER_SUITE_PERSONAL)) + RTStrCat(szTmp, sizeof(szTmp), " Professional"); +#if 0 /** @todo fixme */ + if (GetSystemMetrics(SM_MEDIACENTER)) + RTStrCat(szTmp, sizeof(szTmp), " Media Center"); +#endif + break; + + case kRTWinOSType_2003: strcpy(szTmp, "Windows 2003"); break; + case kRTWinOSType_VISTA: + { + strcpy(szTmp, "Windows Vista"); + rtSystemWinAppendProductType(szTmp, sizeof(szTmp)); + break; + } + case kRTWinOSType_2008: strcpy(szTmp, "Windows 2008"); break; + case kRTWinOSType_7: strcpy(szTmp, "Windows 7"); break; + case kRTWinOSType_2008R2: strcpy(szTmp, "Windows 2008 R2"); break; + case kRTWinOSType_8: strcpy(szTmp, "Windows 8"); break; + case kRTWinOSType_2012: strcpy(szTmp, "Windows 2012"); break; + case kRTWinOSType_81: strcpy(szTmp, "Windows 8.1"); break; + case kRTWinOSType_2012R2: strcpy(szTmp, "Windows 2012 R2"); break; + case kRTWinOSType_10: strcpy(szTmp, "Windows 10"); break; + case kRTWinOSType_2016: strcpy(szTmp, "Windows 2016"); break; + case kRTWinOSType_2019: strcpy(szTmp, "Windows 2019"); break; + case kRTWinOSType_2022: strcpy(szTmp, "Windows 2022"); break; + case kRTWinOSType_11: strcpy(szTmp, "Windows 11"); break; + + case kRTWinOSType_NT_UNKNOWN: + RTStrPrintf(szTmp, sizeof(szTmp), "Unknown NT v%u.%u", + g_WinOsInfoEx.dwMajorVersion, g_WinOsInfoEx.dwMinorVersion); + break; + + default: + AssertFailed(); + case kRTWinOSType_UNKNOWN: + RTStrPrintf(szTmp, sizeof(szTmp), "Unknown %d v%u.%u", + g_WinOsInfoEx.dwPlatformId, g_WinOsInfoEx.dwMajorVersion, g_WinOsInfoEx.dwMinorVersion); + break; + } + break; + } + + /* + * The release. + */ + case RTSYSOSINFO_RELEASE: + { + RTStrPrintf(szTmp, sizeof(szTmp), "%u.%u.%u", + g_WinOsInfoEx.dwMajorVersion, g_WinOsInfoEx.dwMinorVersion, g_WinOsInfoEx.dwBuildNumber); + break; + } + + + /* + * Get the service pack. + */ + case RTSYSOSINFO_SERVICE_PACK: + { + if (g_WinOsInfoEx.wServicePackMajor) + { + if (g_WinOsInfoEx.wServicePackMinor) + RTStrPrintf(szTmp, sizeof(szTmp), "%u.%u", + (unsigned)g_WinOsInfoEx.wServicePackMajor, (unsigned)g_WinOsInfoEx.wServicePackMinor); + else + RTStrPrintf(szTmp, sizeof(szTmp), "%u", + (unsigned)g_WinOsInfoEx.wServicePackMajor); + } + else if (g_WinOsInfoEx.szCSDVersion[0]) + { + /* just copy the entire string. */ + char *pszTmp = szTmp; + int rc = RTUtf16ToUtf8Ex(g_WinOsInfoEx.szCSDVersion, RT_ELEMENTS(g_WinOsInfoEx.szCSDVersion), + &pszTmp, sizeof(szTmp), NULL); + if (RT_SUCCESS(rc)) + RTStrStripR(szTmp); + else + szTmp[0] = '\0'; + AssertCompile(sizeof(szTmp) > sizeof(g_WinOsInfoEx.szCSDVersion)); + } + else + { + switch (g_enmWinVer) + { + case kRTWinOSType_95SP1: strcpy(szTmp, "1"); break; + case kRTWinOSType_98SP1: strcpy(szTmp, "1"); break; + default: + break; + } + } + break; + } + + default: + AssertFatalFailed(); + } + + /* + * Copy the result to the return buffer. + */ + size_t cchTmp = strlen(szTmp); + Assert(cchTmp < sizeof(szTmp)); + if (cchTmp < cchInfo) + { + memcpy(pszInfo, szTmp, cchTmp + 1); + return VINF_SUCCESS; + } + memcpy(pszInfo, szTmp, cchInfo - 1); + pszInfo[cchInfo - 1] = '\0'; + return VERR_BUFFER_OVERFLOW; +} + + +RTDECL(int) RTSystemQueryOSInfo(RTSYSOSINFO enmInfo, char *pszInfo, size_t cchInfo) +{ + /* + * Quick validation. + */ + AssertReturn(enmInfo > RTSYSOSINFO_INVALID && enmInfo < RTSYSOSINFO_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszInfo, VERR_INVALID_POINTER); + if (!cchInfo) + return VERR_BUFFER_OVERFLOW; + + + /* + * Handle the request. + */ + switch (enmInfo) + { + case RTSYSOSINFO_PRODUCT: + case RTSYSOSINFO_RELEASE: + case RTSYSOSINFO_SERVICE_PACK: + return rtSystemWinQueryOSVersion(enmInfo, pszInfo, cchInfo); + + case RTSYSOSINFO_VERSION: + default: + *pszInfo = '\0'; + } + + return VERR_NOT_SUPPORTED; +} + diff --git a/src/VBox/Runtime/r3/win/RTSystemQueryTotalRam-win.cpp b/src/VBox/Runtime/r3/win/RTSystemQueryTotalRam-win.cpp new file mode 100644 index 00000000..49465166 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTSystemQueryTotalRam-win.cpp @@ -0,0 +1,131 @@ +/* $Id: RTSystemQueryTotalRam-win.cpp $ */ +/** @file + * IPRT - RTSystemQueryTotalRam, windows ring-3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> + +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static bool volatile g_fInitialized = false; +typedef BOOL (WINAPI *PFNGLOBALMEMORYSTATUSEX)(LPMEMORYSTATUSEX); +static PFNGLOBALMEMORYSTATUSEX g_pfnGlobalMemoryStatusEx = NULL; + + +/** + * The GlobalMemoryStatusEx API is not available on older Windows version. + * + * @returns Pointer to GlobalMemoryStatusEx or NULL if not available. + */ +DECLINLINE(PFNGLOBALMEMORYSTATUSEX) rtSystemWinGetExApi(void) +{ + PFNGLOBALMEMORYSTATUSEX pfnEx; + if (g_fInitialized) + pfnEx = g_pfnGlobalMemoryStatusEx; + else + { + pfnEx = (PFNGLOBALMEMORYSTATUSEX)GetProcAddress(g_hModKernel32, "GlobalMemoryStatusEx"); + g_pfnGlobalMemoryStatusEx = pfnEx; + g_fInitialized = true; + } + return pfnEx; +} + + +RTDECL(int) RTSystemQueryTotalRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PFNGLOBALMEMORYSTATUSEX pfnGlobalMemoryStatusEx = rtSystemWinGetExApi(); + if (pfnGlobalMemoryStatusEx) + { + MEMORYSTATUSEX MemStatus; + MemStatus.dwLength = sizeof(MemStatus); + if (pfnGlobalMemoryStatusEx(&MemStatus)) + *pcb = MemStatus.ullTotalPhys; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + MEMORYSTATUS MemStatus; + RT_ZERO(MemStatus); + MemStatus.dwLength = sizeof(MemStatus); + GlobalMemoryStatus(&MemStatus); + *pcb = MemStatus.dwTotalPhys; + } + return rc; +} + + +RTDECL(int) RTSystemQueryAvailableRam(uint64_t *pcb) +{ + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PFNGLOBALMEMORYSTATUSEX pfnGlobalMemoryStatusEx = rtSystemWinGetExApi(); + if (pfnGlobalMemoryStatusEx) + { + MEMORYSTATUSEX MemStatus; + MemStatus.dwLength = sizeof(MemStatus); + if (pfnGlobalMemoryStatusEx(&MemStatus)) + *pcb = MemStatus.ullAvailPhys; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + MEMORYSTATUS MemStatus; + RT_ZERO(MemStatus); + MemStatus.dwLength = sizeof(MemStatus); + GlobalMemoryStatus(&MemStatus); + *pcb = MemStatus.dwAvailPhys; + } + return rc; +} diff --git a/src/VBox/Runtime/r3/win/RTSystemShutdown-win.cpp b/src/VBox/Runtime/r3/win/RTSystemShutdown-win.cpp new file mode 100644 index 00000000..9a19ef3c --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTSystemShutdown-win.cpp @@ -0,0 +1,183 @@ +/* $Id: RTSystemShutdown-win.cpp $ */ +/** @file + * IPRT - RTSystemShutdown, Windows. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/system.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include <iprt/win/windows.h> + + +RTDECL(int) RTSystemShutdown(RTMSINTERVAL cMsDelay, uint32_t fFlags, const char *pszLogMsg) +{ + AssertPtrReturn(pszLogMsg, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTSYSTEM_SHUTDOWN_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Before we start, try grant the necessary privileges. + */ + DWORD dwErr; + HANDLE hToken = NULL; + if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE /*OpenAsSelf*/, &hToken)) + dwErr = NO_ERROR; + else + { + dwErr = GetLastError(); + if (dwErr == ERROR_NO_TOKEN) + { + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + dwErr = NO_ERROR; + else + dwErr = GetLastError(); + } + } + if (dwErr == NO_ERROR) + { + union + { + TOKEN_PRIVILEGES TokenPriv; + char ab[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)]; + } u; + u.TokenPriv.PrivilegeCount = 1; + u.TokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (LookupPrivilegeValue(NULL /*localhost*/, SE_SHUTDOWN_NAME, &u.TokenPriv.Privileges[0].Luid)) + { + if (!AdjustTokenPrivileges(hToken, + FALSE /*DisableAllPrivileges*/, + &u.TokenPriv, + RT_UOFFSETOF(TOKEN_PRIVILEGES, Privileges[1]), + NULL, + NULL) ) + dwErr = GetLastError(); + } + else + dwErr = GetLastError(); + CloseHandle(hToken); + } + + /* + * Do some parameter conversion. + */ + PRTUTF16 pwszLogMsg; + int rc = RTStrToUtf16(pszLogMsg, &pwszLogMsg); + if (RT_FAILURE(rc)) + return rc; + DWORD cSecsTimeout = (cMsDelay + 499) / 1000; + + /* + * If we're told to power off the system, we should try use InitiateShutdownW (6.0+) + * or ExitWindowsEx (3.50) rather than InitiateSystemShutdownW, because these other + * APIs allows us to explicitly specify that we want to power off. + * + * Note! For NT version 4, 3.51, and 3.50 the system may instaed reboot since the + * x86 HALs typically didn't know how to perform a power off. + */ + bool fDone = false; + if ( (fFlags & RTSYSTEM_SHUTDOWN_ACTION_MASK) == RTSYSTEM_SHUTDOWN_POWER_OFF + || (fFlags & RTSYSTEM_SHUTDOWN_ACTION_MASK) == RTSYSTEM_SHUTDOWN_POWER_OFF_HALT) + { + /* This API has the grace period thing. */ + decltype(InitiateShutdownW) *pfnInitiateShutdownW; + pfnInitiateShutdownW = (decltype(InitiateShutdownW) *)GetProcAddress(GetModuleHandleW(L"ADVAPI32.DLL"), "InitiateShutdownW"); + if (pfnInitiateShutdownW) + { + DWORD fShutdownFlags = SHUTDOWN_POWEROFF; + if (fFlags & RTSYSTEM_SHUTDOWN_FORCE) + fShutdownFlags |= SHUTDOWN_FORCE_OTHERS | SHUTDOWN_FORCE_SELF; + DWORD fReason = SHTDN_REASON_MAJOR_OTHER | (fFlags & RTSYSTEM_SHUTDOWN_PLANNED ? SHTDN_REASON_FLAG_PLANNED : 0); + dwErr = pfnInitiateShutdownW(NULL /*pwszMachineName*/, pwszLogMsg, cSecsTimeout, fShutdownFlags, fReason); + if (dwErr == ERROR_INVALID_PARAMETER) + { + fReason &= ~SHTDN_REASON_FLAG_PLANNED; /* just in case... */ + dwErr = pfnInitiateShutdownW(NULL /*pwszMachineName*/, pwszLogMsg, cSecsTimeout, fShutdownFlags, fReason); + } + if (dwErr == ERROR_SUCCESS) + { + rc = VINF_SUCCESS; + fDone = true; + } + } + + if (!fDone) + { + /* No grace period here, too bad. */ + decltype(ExitWindowsEx) *pfnExitWindowsEx; + pfnExitWindowsEx = (decltype(ExitWindowsEx) *)GetProcAddress(GetModuleHandleW(L"USER32.DLL"), "ExitWindowsEx"); + if (pfnExitWindowsEx) + { + DWORD fExitWindows = EWX_POWEROFF | EWX_SHUTDOWN; + if (fFlags & RTSYSTEM_SHUTDOWN_FORCE) + fExitWindows |= EWX_FORCE | EWX_FORCEIFHUNG; + + if (pfnExitWindowsEx(fExitWindows, SHTDN_REASON_MAJOR_OTHER)) + fDone = true; + else if (pfnExitWindowsEx(fExitWindows & ~EWX_FORCEIFHUNG, SHTDN_REASON_MAJOR_OTHER)) + fDone = true; + } + } + } + + /* + * Fall back on the oldest API. + */ + if (!fDone) + { + BOOL fRebootAfterShutdown = (fFlags & RTSYSTEM_SHUTDOWN_ACTION_MASK) == RTSYSTEM_SHUTDOWN_REBOOT + ? TRUE : FALSE; + BOOL fForceAppsClosed = fFlags & RTSYSTEM_SHUTDOWN_FORCE ? TRUE : FALSE; + if (InitiateSystemShutdownW(NULL /*pwszMachineName = NULL = localhost*/, + pwszLogMsg, + cSecsTimeout, + fForceAppsClosed, + fRebootAfterShutdown)) + rc = (fFlags & RTSYSTEM_SHUTDOWN_ACTION_MASK) == RTSYSTEM_SHUTDOWN_HALT ? VINF_SYS_MAY_POWER_OFF : VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(dwErr); + } + + RTUtf16Free(pwszLogMsg); + return rc; +} +RT_EXPORT_SYMBOL(RTSystemShutdown); + diff --git a/src/VBox/Runtime/r3/win/RTTimeZoneGetCurrent-win.cpp b/src/VBox/Runtime/r3/win/RTTimeZoneGetCurrent-win.cpp new file mode 100644 index 00000000..310d8507 --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTTimeZoneGetCurrent-win.cpp @@ -0,0 +1,117 @@ +/* $Id: RTTimeZoneGetCurrent-win.cpp $ */ +/** @file + * IPRT - RTTimeZoneGetCurrent, generic. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/time.h> +#include "internal/iprt.h" + +#include <iprt/errcore.h> +#include <iprt/env.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/win/windows.h> +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef DWORD (WINAPI *PFNGETDYNAMICTIMEZONEINFORMATION)(PDYNAMIC_TIME_ZONE_INFORMATION); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Pointer to the GetDynamicTimeZoneInformation API if present. */ +static PFNGETDYNAMICTIMEZONEINFORMATION g_pfnGetDynamicTimeZoneInformation = NULL; +/** Flipped after we've tried to resolve g_pfnGetDynamicTimeZoneInformation. */ +static bool volatile g_fResolvedApi = false; + + +RTDECL(int) RTTimeZoneGetCurrent(char *pszName, size_t cbName) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(cbName > 0, VERR_BUFFER_OVERFLOW); + + /* + * Resolve API. + */ + PFNGETDYNAMICTIMEZONEINFORMATION pfnApi; + if (g_fResolvedApi) + pfnApi = g_pfnGetDynamicTimeZoneInformation; + else + { + pfnApi = (PFNGETDYNAMICTIMEZONEINFORMATION)GetProcAddress(g_hModKernel32, "GetDynamicTimeZoneInformation"); + g_pfnGetDynamicTimeZoneInformation = pfnApi; + g_fResolvedApi = true; + } + + /* + * Call the API and convert the name we get. + */ + union + { + TIME_ZONE_INFORMATION Tzi; + DYNAMIC_TIME_ZONE_INFORMATION DynTzi; + } uBuf; + RT_ZERO(uBuf); + DWORD dwRc; + PCRTUTF16 pwszSrcName; + size_t cwcSrcName; + if (pfnApi) + { + dwRc = pfnApi(&uBuf.DynTzi); + pwszSrcName = uBuf.DynTzi.TimeZoneKeyName; + cwcSrcName = RT_ELEMENTS(uBuf.DynTzi.TimeZoneKeyName); + } + else + { + /* Not sure how helpful this fallback really is... */ + dwRc = GetTimeZoneInformation(&uBuf.Tzi); + pwszSrcName = uBuf.Tzi.StandardName; + cwcSrcName = RT_ELEMENTS(uBuf.Tzi.StandardName); + } + if (dwRc != TIME_ZONE_ID_INVALID) + { + Assert(*pwszSrcName != '\0'); + return RTUtf16ToUtf8Ex(pwszSrcName, cwcSrcName, &pszName, cbName, &cbName); + } + return RTErrConvertFromWin32(GetLastError()); +} + diff --git a/src/VBox/Runtime/r3/win/RTUuidCreate-win.cpp b/src/VBox/Runtime/r3/win/RTUuidCreate-win.cpp new file mode 100644 index 00000000..99a5692f --- /dev/null +++ b/src/VBox/Runtime/r3/win/RTUuidCreate-win.cpp @@ -0,0 +1,82 @@ +/* $Id: RTUuidCreate-win.cpp $ */ +/** @file + * IPRT - UUID, Windows RTUuidCreate implementation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_UUID +#include <iprt/win/windows.h> + +#include <iprt/uuid.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/rand.h> + +#include "internal-r3-win.h" + + +RTDECL(int) RTUuidCreate(PRTUUID pUuid) +{ + /* + * Input validation. + */ + AssertPtrReturn(pUuid, VERR_INVALID_POINTER); + + /* + * When using the UuidCreate API shortly after boot on NT 3.1 it typcially + * hangs for a long long time while polling for some service to start. + * What then usually happens next is a failure because it couldn't figure + * out the MAC address of the NIC. So, on NT 3.1 we always use the fallback. + */ + if (g_enmWinVer != kRTWinOSType_NT310) + { + RPC_STATUS rc = UuidCreate((UUID *)pUuid); + if ( rc == RPC_S_OK + || rc == RPC_S_UUID_LOCAL_ONLY) + return VINF_SUCCESS; + AssertMsg(rc == RPC_S_UUID_NO_ADDRESS, ("UuidCreate -> %u (%#x)\n", rc, rc)); + } + + /* + * Use generic implementation as fallback (copy of RTUuidCreate-generic.cpp). + */ + RTRandBytes(pUuid, sizeof(*pUuid)); + pUuid->Gen.u8ClockSeqHiAndReserved = (pUuid->Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80; + pUuid->Gen.u16TimeHiAndVersion = (pUuid->Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000; + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/win/VBoxRT-msvcp100-win32.def b/src/VBox/Runtime/r3/win/VBoxRT-msvcp100-win32.def new file mode 100644 index 00000000..5395dc50 --- /dev/null +++ b/src/VBox/Runtime/r3/win/VBoxRT-msvcp100-win32.def @@ -0,0 +1,1678 @@ + + ??0?$_Yarn@D@std@@QAE@ABV01@@Z + ??0?$_Yarn@D@std@@QAE@PBD@Z + ??0?$_Yarn@D@std@@QAE@XZ + ??0?$basic_ios@DU?$char_traits@D@std@@@std@@IAE@XZ + ??0?$basic_ios@DU?$char_traits@D@std@@@std@@QAE@PAV?$basic_streambuf@DU?$char_traits@D@std@@@1@@Z + ??0?$basic_ios@GU?$char_traits@G@std@@@std@@IAE@XZ + ??0?$basic_ios@GU?$char_traits@G@std@@@std@@QAE@PAV?$basic_streambuf@GU?$char_traits@G@std@@@1@@Z + ??0?$basic_ios@_WU?$char_traits@_W@std@@@std@@IAE@XZ + ??0?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAE@PAV?$basic_streambuf@_WU?$char_traits@_W@std@@@1@@Z + ??0?$basic_iostream@DU?$char_traits@D@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_iostream@DU?$char_traits@D@std@@@std@@QAE@PAV?$basic_streambuf@DU?$char_traits@D@std@@@1@@Z + ??0?$basic_iostream@GU?$char_traits@G@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_iostream@GU?$char_traits@G@std@@@std@@QAE@PAV?$basic_streambuf@GU?$char_traits@G@std@@@1@@Z + ??0?$basic_iostream@_WU?$char_traits@_W@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_iostream@_WU?$char_traits@_W@std@@@std@@QAE@PAV?$basic_streambuf@_WU?$char_traits@_W@std@@@1@@Z + ??0?$basic_istream@DU?$char_traits@D@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_istream@DU?$char_traits@D@std@@@std@@QAE@PAV?$basic_streambuf@DU?$char_traits@D@std@@@1@_N1@Z + ??0?$basic_istream@DU?$char_traits@D@std@@@std@@QAE@PAV?$basic_streambuf@DU?$char_traits@D@std@@@1@_N@Z + ??0?$basic_istream@DU?$char_traits@D@std@@@std@@QAE@W4_Uninitialized@1@@Z + ??0?$basic_istream@GU?$char_traits@G@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_istream@GU?$char_traits@G@std@@@std@@QAE@PAV?$basic_streambuf@GU?$char_traits@G@std@@@1@_N1@Z + ??0?$basic_istream@GU?$char_traits@G@std@@@std@@QAE@PAV?$basic_streambuf@GU?$char_traits@G@std@@@1@_N@Z + ??0?$basic_istream@GU?$char_traits@G@std@@@std@@QAE@W4_Uninitialized@1@@Z + ??0?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE@PAV?$basic_streambuf@_WU?$char_traits@_W@std@@@1@_N1@Z + ??0?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE@PAV?$basic_streambuf@_WU?$char_traits@_W@std@@@1@_N@Z + ??0?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE@W4_Uninitialized@1@@Z + ??0?$basic_ostream@DU?$char_traits@D@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_ostream@DU?$char_traits@D@std@@@std@@QAE@PAV?$basic_streambuf@DU?$char_traits@D@std@@@1@_N@Z + ??0?$basic_ostream@DU?$char_traits@D@std@@@std@@QAE@W4_Uninitialized@1@_N@Z + ??0?$basic_ostream@GU?$char_traits@G@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_ostream@GU?$char_traits@G@std@@@std@@QAE@PAV?$basic_streambuf@GU?$char_traits@G@std@@@1@_N@Z + ??0?$basic_ostream@GU?$char_traits@G@std@@@std@@QAE@W4_Uninitialized@1@_N@Z + ??0?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAE@$$QAV01@@Z + ??0?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAE@PAV?$basic_streambuf@_WU?$char_traits@_W@std@@@1@_N@Z + ??0?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAE@W4_Uninitialized@1@_N@Z + ??0?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAE@ABV01@@Z + ??0?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAE@W4_Uninitialized@1@@Z + ??0?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAE@XZ + ??0?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAE@ABV01@@Z + ??0?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAE@W4_Uninitialized@1@@Z + ??0?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAE@XZ + ??0?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAE@ABV01@@Z + ??0?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAE@W4_Uninitialized@1@@Z + ??0?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAE@XZ + ??0?$codecvt@DDH@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$codecvt@DDH@std@@QAE@I@Z + ??0?$codecvt@GDH@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$codecvt@GDH@std@@QAE@I@Z + ??0?$codecvt@_WDH@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$codecvt@_WDH@std@@QAE@I@Z + ??0?$ctype@D@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$ctype@D@std@@QAE@PBF_NI@Z + ??0?$ctype@G@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$ctype@G@std@@QAE@I@Z + ??0?$ctype@_W@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$ctype@_W@std@@QAE@I@Z + ??0?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@I@Z + ??0?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@I@Z + ??0?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@I@Z + ??0?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@I@Z + ??0?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@I@Z + ??0?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@I@Z + ??0?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@IAE@PBDI@Z + ??0?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@I@Z + ??0?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@IAE@PBDI@Z + ??0?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@I@Z + ??0?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@IAE@PBDI@Z + ??0?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@I@Z + ??0?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAE@I@Z + ??0?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@IAE@PBDI@Z + ??0?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAE@I@Z + ??0?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@IAE@PBDI@Z + ??0?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@ABV_Locinfo@1@I@Z + ??0?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAE@I@Z + ??0Init@ios_base@std@@QAE@XZ + ??0_Concurrent_queue_base_v4@details@Concurrency@@IAE@I@Z + ??0_Concurrent_queue_iterator_base_v4@details@Concurrency@@IAE@ABV_Concurrent_queue_base_v4@12@@Z + ??0_Container_base12@std@@QAE@ABU01@@Z + ??0_Container_base12@std@@QAE@XZ + ??0_Init_locks@std@@QAE@XZ + ??0_Locimp@locale@std@@AAE@ABV012@@Z + ??0_Locimp@locale@std@@AAE@_N@Z + ??0_Locinfo@std@@QAE@HPBD@Z + ??0_Locinfo@std@@QAE@PBD@Z + ??0_Lockit@std@@QAE@H@Z + ??0_Lockit@std@@QAE@XZ + ??0_Mutex@std@@QAE@W4_Uninitialized@1@@Z + ??0_Mutex@std@@QAE@XZ + ??0_Runtime_object@details@Concurrency@@QAE@H@Z + ??0_Runtime_object@details@Concurrency@@QAE@XZ + ??0_Timevec@std@@QAE@ABV01@@Z + ??0_Timevec@std@@QAE@PAX@Z + ??0_UShinit@std@@QAE@XZ + ??0_Winit@std@@QAE@XZ + ??0agent@Concurrency@@QAE@AAVScheduleGroup@1@@Z + ??0agent@Concurrency@@QAE@AAVScheduler@1@@Z + ??0agent@Concurrency@@QAE@XZ + ??0codecvt_base@std@@QAE@I@Z + ??0ctype_base@std@@QAE@I@Z + ??0facet@locale@std@@IAE@I@Z + ??0id@locale@std@@QAE@I@Z + ??0ios_base@std@@IAE@XZ + ??0ios_base@std@@QAE@ABV01@@Z + ??0time_base@std@@QAE@I@Z + ??1?$_Yarn@D@std@@QAE@XZ + ??1?$basic_ios@DU?$char_traits@D@std@@@std@@UAE@XZ + ??1?$basic_ios@GU?$char_traits@G@std@@@std@@UAE@XZ + ??1?$basic_ios@_WU?$char_traits@_W@std@@@std@@UAE@XZ + ??1?$basic_iostream@DU?$char_traits@D@std@@@std@@UAE@XZ + ??1?$basic_iostream@GU?$char_traits@G@std@@@std@@UAE@XZ + ??1?$basic_iostream@_WU?$char_traits@_W@std@@@std@@UAE@XZ + ??1?$basic_istream@DU?$char_traits@D@std@@@std@@UAE@XZ + ??1?$basic_istream@GU?$char_traits@G@std@@@std@@UAE@XZ + ??1?$basic_istream@_WU?$char_traits@_W@std@@@std@@UAE@XZ + ??1?$basic_ostream@DU?$char_traits@D@std@@@std@@UAE@XZ + ??1?$basic_ostream@GU?$char_traits@G@std@@@std@@UAE@XZ + ??1?$basic_ostream@_WU?$char_traits@_W@std@@@std@@UAE@XZ + ??1?$basic_streambuf@DU?$char_traits@D@std@@@std@@UAE@XZ + ??1?$basic_streambuf@GU?$char_traits@G@std@@@std@@UAE@XZ + ??1?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@UAE@XZ + ??1?$codecvt@DDH@std@@MAE@XZ + ??1?$codecvt@GDH@std@@MAE@XZ + ??1?$codecvt@_WDH@std@@MAE@XZ + ??1?$ctype@D@std@@MAE@XZ + ??1?$ctype@G@std@@MAE@XZ + ??1?$ctype@_W@std@@MAE@XZ + ??1?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MAE@XZ + ??1?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MAE@XZ + ??1?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MAE@XZ + ??1?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MAE@XZ + ??1?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MAE@XZ + ??1?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MAE@XZ + ??1?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MAE@XZ + ??1?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MAE@XZ + ??1?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MAE@XZ + ??1?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MAE@XZ + ??1?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MAE@XZ + ??1?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MAE@XZ + ??1Init@ios_base@std@@QAE@XZ + ??1_Concurrent_queue_base_v4@details@Concurrency@@MAE@XZ + ??1_Concurrent_queue_iterator_base_v4@details@Concurrency@@IAE@XZ + ??1_Concurrent_vector_base_v4@details@Concurrency@@IAE@XZ + ??1_Container_base12@std@@QAE@XZ + ??1_Init_locks@std@@QAE@XZ + ??1_Locimp@locale@std@@MAE@XZ + ??1_Locinfo@std@@QAE@XZ + ??1_Lockit@std@@QAE@XZ + ??1_Mutex@std@@QAE@XZ + ??1_Timevec@std@@QAE@XZ + ??1_UShinit@std@@QAE@XZ + ??1_Winit@std@@QAE@XZ + ??1agent@Concurrency@@UAE@XZ + ??1codecvt_base@std@@UAE@XZ + ??1ctype_base@std@@UAE@XZ + ??1facet@locale@std@@UAE@XZ + ??1ios_base@std@@UAE@XZ + ??1time_base@std@@UAE@XZ + ??4?$_Iosb@H@std@@QAEAAV01@ABV01@@Z + ??4?$_Yarn@D@std@@QAEAAV01@ABV01@@Z + ??4?$_Yarn@D@std@@QAEAAV01@PBD@Z + ??4?$basic_iostream@DU?$char_traits@D@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_iostream@GU?$char_traits@G@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_iostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@$$QAV01@@Z + ??4?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEAAV01@ABV01@@Z + ??4?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEAAV01@ABV01@@Z + ??4?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEAAV01@ABV01@@Z + ??4?$numeric_limits@C@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@D@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@E@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@F@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@G@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@H@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@I@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@J@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@K@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@M@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@N@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@O@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@_J@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@_K@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@_N@std@@QAEAAV01@ABV01@@Z + ??4?$numeric_limits@_W@std@@QAEAAV01@ABV01@@Z + ??4Init@ios_base@std@@QAEAAV012@ABV012@@Z + ??4_Container_base0@std@@QAEAAU01@ABU01@@Z + ??4_Container_base12@std@@QAEAAU01@ABU01@@Z + ??4_Init_locks@std@@QAEAAV01@ABV01@@Z + ??4_Num_base@std@@QAEAAU01@ABU01@@Z + ??4_Num_float_base@std@@QAEAAU01@ABU01@@Z + ??4_Num_int_base@std@@QAEAAU01@ABU01@@Z + ??4_Timevec@std@@QAEAAV01@ABV01@@Z + ??4_UShinit@std@@QAEAAV01@ABV01@@Z + ??4_Winit@std@@QAEAAV01@ABV01@@Z + ??4ios_base@std@@QAEAAV01@ABV01@@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAF@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAG@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAH@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAI@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAJ@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAK@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAM@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAN@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAO@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AAPAX@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AA_J@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AA_K@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@AA_N@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV?$basic_ios@DU?$char_traits@D@std@@@1@AAV21@@Z@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAVios_base@1@AAV21@@Z@Z + ??5?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV01@PAV?$basic_streambuf@DU?$char_traits@D@std@@@1@@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAF@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAG@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAH@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAI@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAJ@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAK@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAM@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAN@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAO@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AAPAX@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AA_J@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AA_K@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@AA_N@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@P6AAAV?$basic_ios@GU?$char_traits@G@std@@@1@AAV21@@Z@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@P6AAAVios_base@1@AAV21@@Z@Z + ??5?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV01@PAV?$basic_streambuf@GU?$char_traits@G@std@@@1@@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAF@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAG@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAH@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAI@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAJ@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAK@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAM@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAN@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAO@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AAPAX@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AA_J@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AA_K@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@AA_N@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAV?$basic_ios@_WU?$char_traits@_W@std@@@1@AAV21@@Z@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAVios_base@1@AAV21@@Z@Z + ??5?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@PAV?$basic_streambuf@_WU?$char_traits@_W@std@@@1@@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@F@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@G@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@I@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@J@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@K@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@M@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@N@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@O@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV?$basic_ios@DU?$char_traits@D@std@@@1@AAV21@@Z@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAVios_base@1@AAV21@@Z@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@PAV?$basic_streambuf@DU?$char_traits@D@std@@@1@@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@PBX@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@_J@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@_K@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@_N@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@F@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@G@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@H@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@I@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@J@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@K@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@M@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@N@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@O@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@P6AAAV?$basic_ios@GU?$char_traits@G@std@@@1@AAV21@@Z@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@P6AAAVios_base@1@AAV21@@Z@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@PAV?$basic_streambuf@GU?$char_traits@G@std@@@1@@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@PBX@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@_J@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@_K@Z + ??6?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV01@_N@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@F@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@G@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@H@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@I@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@J@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@K@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@M@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@N@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@O@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAV?$basic_ios@_WU?$char_traits@_W@std@@@1@AAV21@@Z@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@P6AAAVios_base@1@AAV21@@Z@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@PAV?$basic_streambuf@_WU?$char_traits@_W@std@@@1@@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@PBX@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@_J@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@_K@Z + ??6?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV01@_N@Z + ??7ios_base@std@@QBE_NXZ + ??Bid@locale@std@@QAEIXZ + ??Bios_base@std@@QBEPAXXZ + ??_7?$basic_ios@DU?$char_traits@D@std@@@std@@6B@ + ??_7?$basic_ios@GU?$char_traits@G@std@@@std@@6B@ + ??_7?$basic_ios@_WU?$char_traits@_W@std@@@std@@6B@ + ??_7?$basic_iostream@DU?$char_traits@D@std@@@std@@6B@ + ??_7?$basic_iostream@GU?$char_traits@G@std@@@std@@6B@ + ??_7?$basic_iostream@_WU?$char_traits@_W@std@@@std@@6B@ + ??_7?$basic_istream@DU?$char_traits@D@std@@@std@@6B@ + ??_7?$basic_istream@GU?$char_traits@G@std@@@std@@6B@ + ??_7?$basic_istream@_WU?$char_traits@_W@std@@@std@@6B@ + ??_7?$basic_ostream@DU?$char_traits@D@std@@@std@@6B@ + ??_7?$basic_ostream@GU?$char_traits@G@std@@@std@@6B@ + ??_7?$basic_ostream@_WU?$char_traits@_W@std@@@std@@6B@ + ??_7?$basic_streambuf@DU?$char_traits@D@std@@@std@@6B@ + ??_7?$basic_streambuf@GU?$char_traits@G@std@@@std@@6B@ + ??_7?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@6B@ + ??_7?$codecvt@DDH@std@@6B@ + ??_7?$codecvt@GDH@std@@6B@ + ??_7?$codecvt@_WDH@std@@6B@ + ??_7?$ctype@D@std@@6B@ + ??_7?$ctype@G@std@@6B@ + ??_7?$ctype@_W@std@@6B@ + ??_7?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@6B@ + ??_7?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@6B@ + ??_7?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@6B@ + ??_7?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@6B@ + ??_7?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@6B@ + ??_7?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@6B@ + ??_7?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@6B@ + ??_7?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@6B@ + ??_7?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@6B@ + ??_7?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@6B@ + ??_7?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@6B@ + ??_7?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@6B@ + ??_7_Locimp@locale@std@@6B@ + ??_7codecvt_base@std@@6B@ + ??_7ctype_base@std@@6B@ + ??_7ios_base@std@@6B@ + ??_7time_base@std@@6B@ + ??_8?$basic_iostream@DU?$char_traits@D@std@@@std@@7B?$basic_istream@DU?$char_traits@D@std@@@1@@ + ??_8?$basic_iostream@DU?$char_traits@D@std@@@std@@7B?$basic_ostream@DU?$char_traits@D@std@@@1@@ + ??_8?$basic_iostream@GU?$char_traits@G@std@@@std@@7B?$basic_istream@GU?$char_traits@G@std@@@1@@ + ??_8?$basic_iostream@GU?$char_traits@G@std@@@std@@7B?$basic_ostream@GU?$char_traits@G@std@@@1@@ + ??_8?$basic_iostream@_WU?$char_traits@_W@std@@@std@@7B?$basic_istream@_WU?$char_traits@_W@std@@@1@@ + ??_8?$basic_iostream@_WU?$char_traits@_W@std@@@std@@7B?$basic_ostream@_WU?$char_traits@_W@std@@@1@@ + ??_8?$basic_istream@DU?$char_traits@D@std@@@std@@7B@ + ??_8?$basic_istream@GU?$char_traits@G@std@@@std@@7B@ + ??_8?$basic_istream@_WU?$char_traits@_W@std@@@std@@7B@ + ??_8?$basic_ostream@DU?$char_traits@D@std@@@std@@7B@ + ??_8?$basic_ostream@GU?$char_traits@G@std@@@std@@7B@ + ??_8?$basic_ostream@_WU?$char_traits@_W@std@@@std@@7B@ + ??_D?$basic_iostream@DU?$char_traits@D@std@@@std@@QAEXXZ + ??_D?$basic_iostream@GU?$char_traits@G@std@@@std@@QAEXXZ + ??_D?$basic_iostream@_WU?$char_traits@_W@std@@@std@@QAEXXZ + ??_D?$basic_istream@DU?$char_traits@D@std@@@std@@QAEXXZ + ??_D?$basic_istream@GU?$char_traits@G@std@@@std@@QAEXXZ + ??_D?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEXXZ + ??_D?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEXXZ + ??_D?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEXXZ + ??_D?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEXXZ + ??_F?$codecvt@DDH@std@@QAEXXZ + ??_F?$codecvt@GDH@std@@QAEXXZ + ??_F?$codecvt@_WDH@std@@QAEXXZ + ??_F?$ctype@D@std@@QAEXXZ + ??_F?$ctype@G@std@@QAEXXZ + ??_F?$ctype@_W@std@@QAEXXZ + ??_F?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAEXXZ + ??_F?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAEXXZ + ??_F?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAEXXZ + ??_F?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAEXXZ + ??_F?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAEXXZ + ??_F?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAEXXZ + ??_F?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAEXXZ + ??_F?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAEXXZ + ??_F?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAEXXZ + ??_F?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QAEXXZ + ??_F?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QAEXXZ + ??_F?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QAEXXZ + ??_F_Locimp@locale@std@@QAEXXZ + ??_F_Locinfo@std@@QAEXXZ + ??_F_Timevec@std@@QAEXXZ + ??_Fcodecvt_base@std@@QAEXXZ + ??_Fctype_base@std@@QAEXXZ + ??_Ffacet@locale@std@@QAEXXZ + ??_Fid@locale@std@@QAEXXZ + ??_Ftime_base@std@@QAEXXZ + ?NFS_Allocate@details@Concurrency@@YAPAXIIPAX@Z + ?NFS_Free@details@Concurrency@@YAXPAX@Z + ?NFS_GetLineSize@details@Concurrency@@YAIXZ + ?_Addcats@_Locinfo@std@@QAEAAV12@HPBD@Z + ?_Addfac@_Locimp@locale@std@@AAEXPAVfacet@23@I@Z + ?_Addstd@ios_base@std@@SAXPAV12@@Z + ?_Advance@_Concurrent_queue_iterator_base_v4@details@Concurrency@@IAEXXZ + ?_Assign@_Concurrent_queue_iterator_base_v4@details@Concurrency@@IAEXABV123@@Z + ?_Atexit@@YAXP6AXXZ@Z + ?_BADOFF@std@@3_JB + ?_C_str@?$_Yarn@D@std@@QBEPBDXZ + ?_Callfns@ios_base@std@@AAEXW4event@12@@Z + ?_Clocptr@_Locimp@locale@std@@0PAV123@A + ?_Decref@facet@locale@std@@QAEPAV123@XZ + ?_Donarrow@?$ctype@G@std@@IBEDGD@Z + ?_Donarrow@?$ctype@_W@std@@IBED_WD@Z + ?_Dowiden@?$ctype@G@std@@IBEGD@Z + ?_Dowiden@?$ctype@_W@std@@IBE_WD@Z + ?_Empty@?$_Yarn@D@std@@QBE_NXZ + ?_Ffmt@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABAPADPADDH@Z + ?_Ffmt@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABAPADPADDH@Z + ?_Ffmt@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABAPADPADDH@Z + ?_Findarr@ios_base@std@@AAEAAU_Iosarray@12@H@Z + ?_Fiopen@std@@YAPAU_iobuf@@PBDHH@Z + ?_Fiopen@std@@YAPAU_iobuf@@PBGHH@Z + ?_Fiopen@std@@YAPAU_iobuf@@PB_WHH@Z + ?_Fput@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DPBDIIII@Z + ?_Fput@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GPBDIIII@Z + ?_Fput@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WPBDIIII@Z + ?_GetCombinableSize@details@Concurrency@@YAIXZ + ?_GetCurrentThreadId@details@Concurrency@@YAKXZ + ?_Getcat@?$codecvt@DDH@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$codecvt@GDH@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$codecvt@_WDH@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$ctype@D@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$ctype@G@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$ctype@_W@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@SAIPAPBVfacet@locale@2@PBV42@@Z + ?_Getcat@facet@locale@std@@SAIPAPBV123@PBV23@@Z + ?_Getcoll@_Locinfo@std@@QBE?AU_Collvec@@XZ + ?_Getctype@_Locinfo@std@@QBE?AU_Ctypevec@@XZ + ?_Getcvt@_Locinfo@std@@QBE?AU_Cvtvec@@XZ + ?_Getdateorder@_Locinfo@std@@QBEHXZ + ?_Getdays@_Locinfo@std@@QBEPBDXZ + ?_Getfalse@_Locinfo@std@@QBEPBDXZ + ?_Getffld@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@1AAVios_base@2@PAH@Z + ?_Getffld@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@1AAVios_base@2@PAH@Z + ?_Getffld@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@1AAVios_base@2@PAH@Z + ?_Getffldx@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@1AAVios_base@2@PAH@Z + ?_Getffldx@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@1AAVios_base@2@PAH@Z + ?_Getffldx@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@1AAVios_base@2@PAH@Z + ?_Getfmt@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@IBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@PBD@Z + ?_Getfmt@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@IBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@PBD@Z + ?_Getfmt@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@IBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@PBD@Z + ?_Getgloballocale@locale@std@@CAPAV_Locimp@12@XZ + ?_Getifld@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@1HABVlocale@2@@Z + ?_Getifld@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@1HABVlocale@2@@Z + ?_Getifld@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABAHPADAAV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@1HABVlocale@2@@Z + ?_Getint@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABAHAAV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@0HHAAH@Z + ?_Getint@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABAHAAV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@0HHAAH@Z + ?_Getint@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABAHAAV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@0HHAAH@Z + ?_Getlconv@_Locinfo@std@@QBEPBUlconv@@XZ + ?_Getmonths@_Locinfo@std@@QBEPBDXZ + ?_Getname@_Locinfo@std@@QBEPBDXZ + ?_Getpfirst@_Container_base12@std@@QBEPAPAU_Iterator_base12@2@XZ + ?_Getptr@_Timevec@std@@QBEPAXXZ + ?_Gettnames@_Locinfo@std@@QBE?AV_Timevec@2@XZ + ?_Gettrue@_Locinfo@std@@QBEPBDXZ + ?_Gnavail@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBE_JXZ + ?_Gnavail@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBE_JXZ + ?_Gnavail@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBE_JXZ + ?_Gndec@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEPADXZ + ?_Gndec@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEPAGXZ + ?_Gndec@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEPA_WXZ + ?_Gninc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEPADXZ + ?_Gninc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEPAGXZ + ?_Gninc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEPA_WXZ + ?_Gnpreinc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEPADXZ + ?_Gnpreinc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEPAGXZ + ?_Gnpreinc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEPA_WXZ + ?_Hexdig@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABAHDDDD@Z + ?_Hexdig@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABAHGGGG@Z + ?_Hexdig@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABAH_W000@Z + ?_Id_cnt@id@locale@std@@0HA + ?_Ifmt@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABAPADPADPBDH@Z + ?_Ifmt@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABAPADPADPBDH@Z + ?_Ifmt@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABAPADPADPBDH@Z + ?_Incref@facet@locale@std@@QAEXXZ + ?_Index@ios_base@std@@0HA + ?_Init@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXPAPAD0PAH001@Z + ?_Init@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXXZ + ?_Init@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXPAPAG0PAH001@Z + ?_Init@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXXZ + ?_Init@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXPAPA_W0PAH001@Z + ?_Init@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXXZ + ?_Init@?$codecvt@DDH@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$codecvt@GDH@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$codecvt@_WDH@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$ctype@D@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$ctype@G@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$ctype@_W@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@IAEXABV_Locinfo@2@@Z + ?_Init@ios_base@std@@IAEXXZ + ?_Init@locale@std@@CAPAV_Locimp@12@XZ + ?_Init_cnt@Init@ios_base@std@@0HA + ?_Init_cnt@_UShinit@std@@0HA + ?_Init_cnt@_Winit@std@@0HA + ?_Init_cnt_func@Init@ios_base@std@@CAAAHXZ + ?_Init_ctor@Init@ios_base@std@@CAXPAV123@@Z + ?_Init_dtor@Init@ios_base@std@@CAXPAV123@@Z + ?_Init_locks_ctor@_Init_locks@std@@CAXPAV12@@Z + ?_Init_locks_dtor@_Init_locks@std@@CAXPAV12@@Z + ?_Internal_assign@_Concurrent_vector_base_v4@details@Concurrency@@IAEXABV123@IP6AXPAXI@ZP6AX1PBXI@Z4@Z + ?_Internal_capacity@_Concurrent_vector_base_v4@details@Concurrency@@IBEIXZ + ?_Internal_clear@_Concurrent_vector_base_v4@details@Concurrency@@IAEIP6AXPAXI@Z@Z + ?_Internal_compact@_Concurrent_vector_base_v4@details@Concurrency@@IAEPAXIPAXP6AX0I@ZP6AX0PBXI@Z@Z + ?_Internal_copy@_Concurrent_vector_base_v4@details@Concurrency@@IAEXABV123@IP6AXPAXPBXI@Z@Z + ?_Internal_empty@_Concurrent_queue_base_v4@details@Concurrency@@IBE_NXZ + ?_Internal_finish_clear@_Concurrent_queue_base_v4@details@Concurrency@@IAEXXZ + ?_Internal_grow_by@_Concurrent_vector_base_v4@details@Concurrency@@IAEIIIP6AXPAXPBXI@Z1@Z + ?_Internal_grow_to_at_least_with_result@_Concurrent_vector_base_v4@details@Concurrency@@IAEIIIP6AXPAXPBXI@Z1@Z + ?_Internal_pop_if_present@_Concurrent_queue_base_v4@details@Concurrency@@IAE_NPAX@Z + ?_Internal_push@_Concurrent_queue_base_v4@details@Concurrency@@IAEXPBX@Z + ?_Internal_push_back@_Concurrent_vector_base_v4@details@Concurrency@@IAEPAXIAAI@Z + ?_Internal_reserve@_Concurrent_vector_base_v4@details@Concurrency@@IAEXIII@Z + ?_Internal_resize@_Concurrent_vector_base_v4@details@Concurrency@@IAEXIIIP6AXPAXI@ZP6AX0PBXI@Z2@Z + ?_Internal_size@_Concurrent_queue_base_v4@details@Concurrency@@IBEIXZ + ?_Internal_swap@_Concurrent_vector_base_v4@details@Concurrency@@IAEXAAV123@@Z + ?_Internal_throw_exception@_Concurrent_queue_base_v4@details@Concurrency@@IBEXXZ + ?_Internal_throw_exception@_Concurrent_vector_base_v4@details@Concurrency@@IBEXI@Z + ?_Ios_base_dtor@ios_base@std@@CAXPAV12@@Z + ?_Ipfx@?$basic_istream@DU?$char_traits@D@std@@@std@@QAE_N_N@Z + ?_Ipfx@?$basic_istream@GU?$char_traits@G@std@@@std@@QAE_N_N@Z + ?_Ipfx@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE_N_N@Z + ?_Iput@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DPADI@Z + ?_Iput@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GPADI@Z + ?_Iput@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WPADI@Z + ?_Locimp_Addfac@_Locimp@locale@std@@CAXPAV123@PAVfacet@23@I@Z + ?_Locimp_ctor@_Locimp@locale@std@@CAXPAV123@ABV123@@Z + ?_Locimp_dtor@_Locimp@locale@std@@CAXPAV123@@Z + ?_Locinfo_Addcats@_Locinfo@std@@SAAAV12@PAV12@HPBD@Z + ?_Locinfo_ctor@_Locinfo@std@@SAXPAV12@HPBD@Z + ?_Locinfo_ctor@_Locinfo@std@@SAXPAV12@PBD@Z + ?_Locinfo_dtor@_Locinfo@std@@SAXPAV12@@Z + ?_Lock@?$basic_streambuf@DU?$char_traits@D@std@@@std@@UAEXXZ + ?_Lock@?$basic_streambuf@GU?$char_traits@G@std@@@std@@UAEXXZ + ?_Lock@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@UAEXXZ + ?_Lock@_Mutex@std@@QAEXXZ + ?_Lockit_ctor@_Lockit@std@@CAXPAV12@@Z + ?_Lockit_ctor@_Lockit@std@@CAXPAV12@H@Z + ?_Lockit_ctor@_Lockit@std@@SAXH@Z + ?_Lockit_dtor@_Lockit@std@@CAXPAV12@@Z + ?_Lockit_dtor@_Lockit@std@@SAXH@Z + ?_MP_Add@tr1@std@@YAXQA_K_K@Z + ?_MP_Get@tr1@std@@YA_KQA_K@Z + ?_MP_Mul@tr1@std@@YAXQA_K_K1@Z + ?_MP_Rem@tr1@std@@YAXQA_K_K@Z + ?_Makeloc@_Locimp@locale@std@@CAPAV123@ABV_Locinfo@3@HPAV123@PBV23@@Z + ?_Makeushloc@_Locimp@locale@std@@CAXABV_Locinfo@3@HPAV123@PBV23@@Z + ?_Makewloc@_Locimp@locale@std@@CAXABV_Locinfo@3@HPAV123@PBV23@@Z + ?_Makexloc@_Locimp@locale@std@@CAXABV_Locinfo@3@HPAV123@PBV23@@Z + ?_Mtx_delete@threads@stdext@@YAXPAX@Z + ?_Mtx_lock@threads@stdext@@YAXPAX@Z + ?_Mtx_new@threads@stdext@@YAXAAPAX@Z + ?_Mtx_unlock@threads@stdext@@YAXPAX@Z + ?_Mutex_Lock@_Mutex@std@@CAXPAV12@@Z + ?_Mutex_Unlock@_Mutex@std@@CAXPAV12@@Z + ?_Mutex_ctor@_Mutex@std@@CAXPAV12@@Z + ?_Mutex_dtor@_Mutex@std@@CAXPAV12@@Z + ?_Nomemory@std@@YAXXZ + ?_Orphan_all@_Container_base0@std@@QAEXXZ + ?_Orphan_all@_Container_base12@std@@QAEXXZ + ?_Osfx@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEXXZ + ?_Osfx@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEXXZ + ?_Osfx@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEXXZ + ?_Pnavail@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBE_JXZ + ?_Pnavail@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBE_JXZ + ?_Pnavail@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBE_JXZ + ?_Pninc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEPADXZ + ?_Pninc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEPAGXZ + ?_Pninc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEPA_WXZ + ?_Ptr_cerr@std@@3PAV?$basic_ostream@DU?$char_traits@D@std@@@1@A + ?_Ptr_cin@std@@3PAV?$basic_istream@DU?$char_traits@D@std@@@1@A + ?_Ptr_clog@std@@3PAV?$basic_ostream@DU?$char_traits@D@std@@@1@A + ?_Ptr_cout@std@@3PAV?$basic_ostream@DU?$char_traits@D@std@@@1@A + ?_Ptr_wcerr@std@@3PAV?$basic_ostream@GU?$char_traits@G@std@@@1@A + ?_Ptr_wcerr@std@@3PAV?$basic_ostream@_WU?$char_traits@_W@std@@@1@A + ?_Ptr_wcin@std@@3PAV?$basic_istream@GU?$char_traits@G@std@@@1@A + ?_Ptr_wcin@std@@3PAV?$basic_istream@_WU?$char_traits@_W@std@@@1@A + ?_Ptr_wclog@std@@3PAV?$basic_ostream@GU?$char_traits@G@std@@@1@A + ?_Ptr_wclog@std@@3PAV?$basic_ostream@_WU?$char_traits@_W@std@@@1@A + ?_Ptr_wcout@std@@3PAV?$basic_ostream@GU?$char_traits@G@std@@@1@A + ?_Ptr_wcout@std@@3PAV?$basic_ostream@_WU?$char_traits@_W@std@@@1@A + ?_Put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@PBDI@Z + ?_Put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@PBGI@Z + ?_Put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@PB_WI@Z + ?_Putc@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@PBDI@Z + ?_Putc@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@PBDI@Z + ?_Putc@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@PBDI@Z + ?_Putgrouped@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@PBDID@Z + ?_Putgrouped@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@PBDIG@Z + ?_Putgrouped@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@PBDI_W@Z + ?_Raise_handler@std@@3P6AXABVexception@stdext@@@ZA + ?_Random_device@tr1@std@@YAIXZ + ?_Rep@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@DI@Z + ?_Rep@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@GI@Z + ?_Rep@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@ABA?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@_WI@Z + ?_Rng_abort@tr1@std@@YAXPBD@Z + ?_Segment_index_of@_Concurrent_vector_base_v4@details@Concurrency@@KAII@Z + ?_Setgloballocale@locale@std@@CAXPAX@Z + ?_Swap_all@_Container_base0@std@@QAEXAAU12@@Z + ?_Swap_all@_Container_base12@std@@QAEXAAU12@@Z + ?_Sync@ios_base@std@@0_NA + ?_Tidy@?$_Yarn@D@std@@AAEXXZ + ?_Tidy@?$ctype@D@std@@IAEXXZ + ?_Tidy@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@AAEXXZ + ?_Tidy@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@AAEXXZ + ?_Tidy@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@AAEXXZ + ?_Tidy@ios_base@std@@AAEXXZ + ?_Unlock@?$basic_streambuf@DU?$char_traits@D@std@@@std@@UAEXXZ + ?_Unlock@?$basic_streambuf@GU?$char_traits@G@std@@@std@@UAEXXZ + ?_Unlock@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@UAEXXZ + ?_Unlock@_Mutex@std@@QAEXXZ + ?_XLgamma@tr1@std@@YAMM@Z + ?_XLgamma@tr1@std@@YANN@Z + ?_XLgamma@tr1@std@@YAOO@Z + ?_Xbad@tr1@std@@YAXW4error_type@regex_constants@12@@Z + ?_Xfunc@tr1@std@@YAXXZ + ?_Xinvalid_argument@std@@YAXPBD@Z + ?_Xlength_error@std@@YAXPBD@Z + ?_Xmem@tr1@std@@YAXXZ + ?_Xout_of_range@std@@YAXPBD@Z + ?_Xoverflow_error@std@@YAXPBD@Z + ?_Xruntime_error@std@@YAXPBD@Z + ?always_noconv@codecvt_base@std@@QBE_NXZ + ?bad@ios_base@std@@QBE_NXZ + ?c_str@?$_Yarn@D@std@@QBEPBDXZ + ?cancel@agent@Concurrency@@QAE_NXZ + ?cerr@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A + ?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A + ?classic@locale@std@@SAABV12@XZ + ?classic_table@?$ctype@D@std@@SAPBFXZ + ?clear@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEXH_N@Z + ?clear@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEXI@Z + ?clear@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEXH_N@Z + ?clear@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEXI@Z + ?clear@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEXH_N@Z + ?clear@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEXI@Z + ?clear@ios_base@std@@QAEXH@Z + ?clear@ios_base@std@@QAEXH_N@Z + ?clear@ios_base@std@@QAEXI@Z + ?clog@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A + ?copyfmt@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEAAV12@ABV12@@Z + ?copyfmt@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEAAV12@ABV12@@Z + ?copyfmt@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEAAV12@ABV12@@Z + ?copyfmt@ios_base@std@@QAEAAV12@ABV12@@Z + ?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A + ?date_order@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AW4dateorder@time_base@2@XZ + ?date_order@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AW4dateorder@time_base@2@XZ + ?date_order@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AW4dateorder@time_base@2@XZ + ?denorm_min@?$numeric_limits@C@std@@SACXZ + ?denorm_min@?$numeric_limits@D@std@@SADXZ + ?denorm_min@?$numeric_limits@E@std@@SAEXZ + ?denorm_min@?$numeric_limits@F@std@@SAFXZ + ?denorm_min@?$numeric_limits@G@std@@SAGXZ + ?denorm_min@?$numeric_limits@H@std@@SAHXZ + ?denorm_min@?$numeric_limits@I@std@@SAIXZ + ?denorm_min@?$numeric_limits@J@std@@SAJXZ + ?denorm_min@?$numeric_limits@K@std@@SAKXZ + ?denorm_min@?$numeric_limits@M@std@@SAMXZ + ?denorm_min@?$numeric_limits@N@std@@SANXZ + ?denorm_min@?$numeric_limits@O@std@@SAOXZ + ?denorm_min@?$numeric_limits@_J@std@@SA_JXZ + ?denorm_min@?$numeric_limits@_K@std@@SA_KXZ + ?denorm_min@?$numeric_limits@_N@std@@SA_NXZ + ?denorm_min@?$numeric_limits@_W@std@@SA_WXZ + ?digits10@?$numeric_limits@C@std@@2HB + ?digits10@?$numeric_limits@D@std@@2HB + ?digits10@?$numeric_limits@E@std@@2HB + ?digits10@?$numeric_limits@F@std@@2HB + ?digits10@?$numeric_limits@G@std@@2HB + ?digits10@?$numeric_limits@H@std@@2HB + ?digits10@?$numeric_limits@I@std@@2HB + ?digits10@?$numeric_limits@J@std@@2HB + ?digits10@?$numeric_limits@K@std@@2HB + ?digits10@?$numeric_limits@M@std@@2HB + ?digits10@?$numeric_limits@N@std@@2HB + ?digits10@?$numeric_limits@O@std@@2HB + ?digits10@?$numeric_limits@_J@std@@2HB + ?digits10@?$numeric_limits@_K@std@@2HB + ?digits10@?$numeric_limits@_N@std@@2HB + ?digits10@?$numeric_limits@_W@std@@2HB + ?digits10@_Num_base@std@@2HB + ?digits@?$numeric_limits@C@std@@2HB + ?digits@?$numeric_limits@D@std@@2HB + ?digits@?$numeric_limits@E@std@@2HB + ?digits@?$numeric_limits@F@std@@2HB + ?digits@?$numeric_limits@G@std@@2HB + ?digits@?$numeric_limits@H@std@@2HB + ?digits@?$numeric_limits@I@std@@2HB + ?digits@?$numeric_limits@J@std@@2HB + ?digits@?$numeric_limits@K@std@@2HB + ?digits@?$numeric_limits@M@std@@2HB + ?digits@?$numeric_limits@N@std@@2HB + ?digits@?$numeric_limits@O@std@@2HB + ?digits@?$numeric_limits@_J@std@@2HB + ?digits@?$numeric_limits@_K@std@@2HB + ?digits@?$numeric_limits@_N@std@@2HB + ?digits@?$numeric_limits@_W@std@@2HB + ?digits@_Num_base@std@@2HB + ?do_always_noconv@?$codecvt@GDH@std@@MBE_NXZ + ?do_always_noconv@?$codecvt@_WDH@std@@MBE_NXZ + ?do_always_noconv@codecvt_base@std@@MBE_NXZ + ?do_date_order@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AW4dateorder@time_base@2@XZ + ?do_date_order@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AW4dateorder@time_base@2@XZ + ?do_date_order@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AW4dateorder@time_base@2@XZ + ?do_encoding@codecvt_base@std@@MBEHXZ + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAG@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAI@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAJ@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAK@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAM@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAN@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAO@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAPAX@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAA_J@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAA_K@Z + ?do_get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAA_N@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAG@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAI@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAJ@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAK@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAM@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAN@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAO@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAPAX@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAA_J@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAA_K@Z + ?do_get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAA_N@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAG@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAI@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAJ@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAK@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAM@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAN@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAO@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAPAX@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAA_J@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAA_K@Z + ?do_get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAA_N@Z + ?do_get@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@DD@Z + ?do_get@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@DD@Z + ?do_get@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@DD@Z + ?do_get_date@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_date@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_date@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_monthname@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_monthname@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_monthname@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_time@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_time@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_time@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_weekday@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_weekday@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_weekday@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_year@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_year@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_get_year@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?do_in@?$codecvt@DDH@std@@MBEHAAHPBD1AAPBDPAD3AAPAD@Z + ?do_in@?$codecvt@GDH@std@@MBEHAAHPBD1AAPBDPAG3AAPAG@Z + ?do_in@?$codecvt@_WDH@std@@MBEHAAHPBD1AAPBDPA_W3AAPA_W@Z + ?do_is@?$ctype@G@std@@MBEPBGPBG0PAF@Z + ?do_is@?$ctype@G@std@@MBE_NFG@Z + ?do_is@?$ctype@_W@std@@MBEPB_WPB_W0PAF@Z + ?do_is@?$ctype@_W@std@@MBE_NF_W@Z + ?do_length@?$codecvt@DDH@std@@MBEHABHPBD1I@Z + ?do_length@?$codecvt@GDH@std@@MBEHABHPBD1I@Z + ?do_length@?$codecvt@_WDH@std@@MBEHABHPBD1I@Z + ?do_max_length@?$codecvt@GDH@std@@MBEHXZ + ?do_max_length@?$codecvt@_WDH@std@@MBEHXZ + ?do_max_length@codecvt_base@std@@MBEHXZ + ?do_narrow@?$ctype@D@std@@MBEDDD@Z + ?do_narrow@?$ctype@D@std@@MBEPBDPBD0DPAD@Z + ?do_narrow@?$ctype@G@std@@MBEDGD@Z + ?do_narrow@?$ctype@G@std@@MBEPBGPBG0DPAD@Z + ?do_narrow@?$ctype@_W@std@@MBED_WD@Z + ?do_narrow@?$ctype@_W@std@@MBEPB_WPB_W0DPAD@Z + ?do_out@?$codecvt@DDH@std@@MBEHAAHPBD1AAPBDPAD3AAPAD@Z + ?do_out@?$codecvt@GDH@std@@MBEHAAHPBG1AAPBGPAD3AAPAD@Z + ?do_out@?$codecvt@_WDH@std@@MBEHAAHPB_W1AAPB_WPAD3AAPAD@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DJ@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DK@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DN@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DO@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DPBX@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@D_J@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@D_K@Z + ?do_put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@D_N@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GJ@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GK@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GN@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GO@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GPBX@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@G_J@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@G_K@Z + ?do_put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@G_N@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WJ@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WK@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WN@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WO@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WPBX@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_W_J@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_W_K@Z + ?do_put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_W_N@Z + ?do_put@?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DPBUtm@@DD@Z + ?do_put@?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GPBUtm@@DD@Z + ?do_put@?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@MBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WPBUtm@@DD@Z + ?do_scan_is@?$ctype@G@std@@MBEPBGFPBG0@Z + ?do_scan_is@?$ctype@_W@std@@MBEPB_WFPB_W0@Z + ?do_scan_not@?$ctype@G@std@@MBEPBGFPBG0@Z + ?do_scan_not@?$ctype@_W@std@@MBEPB_WFPB_W0@Z + ?do_tolower@?$ctype@D@std@@MBEDD@Z + ?do_tolower@?$ctype@D@std@@MBEPBDPADPBD@Z + ?do_tolower@?$ctype@G@std@@MBEGG@Z + ?do_tolower@?$ctype@G@std@@MBEPBGPAGPBG@Z + ?do_tolower@?$ctype@_W@std@@MBEPB_WPA_WPB_W@Z + ?do_tolower@?$ctype@_W@std@@MBE_W_W@Z + ?do_toupper@?$ctype@D@std@@MBEDD@Z + ?do_toupper@?$ctype@D@std@@MBEPBDPADPBD@Z + ?do_toupper@?$ctype@G@std@@MBEGG@Z + ?do_toupper@?$ctype@G@std@@MBEPBGPAGPBG@Z + ?do_toupper@?$ctype@_W@std@@MBEPB_WPA_WPB_W@Z + ?do_toupper@?$ctype@_W@std@@MBE_W_W@Z + ?do_unshift@?$codecvt@DDH@std@@MBEHAAHPAD1AAPAD@Z + ?do_unshift@?$codecvt@GDH@std@@MBEHAAHPAD1AAPAD@Z + ?do_unshift@?$codecvt@_WDH@std@@MBEHAAHPAD1AAPAD@Z + ?do_widen@?$ctype@D@std@@MBEDD@Z + ?do_widen@?$ctype@D@std@@MBEPBDPBD0PAD@Z + ?do_widen@?$ctype@G@std@@MBEGD@Z + ?do_widen@?$ctype@G@std@@MBEPBDPBD0PAG@Z + ?do_widen@?$ctype@_W@std@@MBEPBDPBD0PA_W@Z + ?do_widen@?$ctype@_W@std@@MBE_WD@Z + ?done@agent@Concurrency@@IAE_NXZ + ?eback@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBEPADXZ + ?eback@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBEPAGXZ + ?eback@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBEPA_WXZ + ?egptr@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBEPADXZ + ?egptr@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBEPAGXZ + ?egptr@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBEPA_WXZ + ?empty@?$_Yarn@D@std@@QBE_NXZ + ?empty@locale@std@@SA?AV12@XZ + ?encoding@codecvt_base@std@@QBEHXZ + ?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z + ?endl@std@@YAAAV?$basic_ostream@GU?$char_traits@G@std@@@1@AAV21@@Z + ?endl@std@@YAAAV?$basic_ostream@_WU?$char_traits@_W@std@@@1@AAV21@@Z + ?ends@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z + ?ends@std@@YAAAV?$basic_ostream@GU?$char_traits@G@std@@@1@AAV21@@Z + ?ends@std@@YAAAV?$basic_ostream@_WU?$char_traits@_W@std@@@1@AAV21@@Z + ?eof@ios_base@std@@QBE_NXZ + ?epptr@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBEPADXZ + ?epptr@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBEPAGXZ + ?epptr@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBEPA_WXZ + ?epsilon@?$numeric_limits@C@std@@SACXZ + ?epsilon@?$numeric_limits@D@std@@SADXZ + ?epsilon@?$numeric_limits@E@std@@SAEXZ + ?epsilon@?$numeric_limits@F@std@@SAFXZ + ?epsilon@?$numeric_limits@G@std@@SAGXZ + ?epsilon@?$numeric_limits@H@std@@SAHXZ + ?epsilon@?$numeric_limits@I@std@@SAIXZ + ?epsilon@?$numeric_limits@J@std@@SAJXZ + ?epsilon@?$numeric_limits@K@std@@SAKXZ + ?epsilon@?$numeric_limits@M@std@@SAMXZ + ?epsilon@?$numeric_limits@N@std@@SANXZ + ?epsilon@?$numeric_limits@O@std@@SAOXZ + ?epsilon@?$numeric_limits@_J@std@@SA_JXZ + ?epsilon@?$numeric_limits@_K@std@@SA_KXZ + ?epsilon@?$numeric_limits@_N@std@@SA_NXZ + ?epsilon@?$numeric_limits@_W@std@@SA_WXZ + ?exceptions@ios_base@std@@QAEXH@Z + ?exceptions@ios_base@std@@QAEXI@Z + ?exceptions@ios_base@std@@QBEHXZ + ?fail@ios_base@std@@QBE_NXZ + ?fill@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEDD@Z + ?fill@?$basic_ios@DU?$char_traits@D@std@@@std@@QBEDXZ + ?fill@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEGG@Z + ?fill@?$basic_ios@GU?$char_traits@G@std@@@std@@QBEGXZ + ?fill@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAE_W_W@Z + ?fill@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QBE_WXZ + ?flags@ios_base@std@@QAEHH@Z + ?flags@ios_base@std@@QBEHXZ + ?flush@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV12@XZ + ?flush@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV12@XZ + ?flush@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@XZ + ?flush@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z + ?flush@std@@YAAAV?$basic_ostream@GU?$char_traits@G@std@@@1@AAV21@@Z + ?flush@std@@YAAAV?$basic_ostream@_WU?$char_traits@_W@std@@@1@AAV21@@Z + ?gbump@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXH@Z + ?gbump@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXH@Z + ?gbump@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXH@Z + ?gcount@?$basic_istream@DU?$char_traits@D@std@@@std@@QBE_JXZ + ?gcount@?$basic_istream@GU?$char_traits@G@std@@@std@@QBE_JXZ + ?gcount@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QBE_JXZ + ?generic_category@std@@YAABVerror_category@1@XZ + ?get@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@AAD@Z + ?get@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@AAV?$basic_streambuf@DU?$char_traits@D@std@@@2@@Z + ?get@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@AAV?$basic_streambuf@DU?$char_traits@D@std@@@2@D@Z + ?get@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@PAD_J@Z + ?get@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@PAD_JD@Z + ?get@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEHXZ + ?get@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@AAG@Z + ?get@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@AAV?$basic_streambuf@GU?$char_traits@G@std@@@2@@Z + ?get@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@AAV?$basic_streambuf@GU?$char_traits@G@std@@@2@G@Z + ?get@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@PAG_J@Z + ?get@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@PAG_JG@Z + ?get@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEGXZ + ?get@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@AAV?$basic_streambuf@_WU?$char_traits@_W@std@@@2@@Z + ?get@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@AAV?$basic_streambuf@_WU?$char_traits@_W@std@@@2@_W@Z + ?get@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@AA_W@Z + ?get@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@PA_W_J@Z + ?get@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@PA_W_J_W@Z + ?get@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEGXZ + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAG@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAI@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAJ@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAK@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAM@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAN@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAO@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAAPAX@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAA_J@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAA_K@Z + ?get@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHAA_N@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAG@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAI@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAJ@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAK@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAM@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAN@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAO@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAAPAX@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAA_J@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAA_K@Z + ?get@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHAA_N@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAG@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAI@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAJ@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAK@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAM@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAN@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAO@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAAPAX@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAA_J@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAA_K@Z + ?get@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHAA_N@Z + ?get@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@DD@Z + ?get@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@PBD4@Z + ?get@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@DD@Z + ?get@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@PBG4@Z + ?get@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@DD@Z + ?get@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@PB_W4@Z + ?get_date@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_date@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_date@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_monthname@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_monthname@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_monthname@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_time@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_time@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_time@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_weekday@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_weekday@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_weekday@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_year@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@DU?$char_traits@D@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_year@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@GU?$char_traits@G@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?get_year@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@0AAVios_base@2@AAHPAUtm@@@Z + ?getline@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@PAD_J@Z + ?getline@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@PAD_JD@Z + ?getline@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@PAG_J@Z + ?getline@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@PAG_JG@Z + ?getline@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@PA_W_J@Z + ?getline@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@PA_W_J_W@Z + ?getloc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QBE?AVlocale@2@XZ + ?getloc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QBE?AVlocale@2@XZ + ?getloc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QBE?AVlocale@2@XZ + ?getloc@ios_base@std@@QBE?AVlocale@2@XZ + ?global@locale@std@@SA?AV12@ABV12@@Z + ?good@ios_base@std@@QBE_NXZ + ?gptr@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBEPADXZ + ?gptr@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBEPAGXZ + ?gptr@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBEPA_WXZ + ?has_denorm@_Num_base@std@@2W4float_denorm_style@2@B + ?has_denorm@_Num_float_base@std@@2W4float_denorm_style@2@B + ?has_denorm_loss@_Num_base@std@@2_NB + ?has_denorm_loss@_Num_float_base@std@@2_NB + ?has_infinity@_Num_base@std@@2_NB + ?has_infinity@_Num_float_base@std@@2_NB + ?has_quiet_NaN@_Num_base@std@@2_NB + ?has_quiet_NaN@_Num_float_base@std@@2_NB + ?has_signaling_NaN@_Num_base@std@@2_NB + ?has_signaling_NaN@_Num_float_base@std@@2_NB + ?id@?$codecvt@DDH@std@@2V0locale@2@A + ?id@?$codecvt@GDH@std@@2V0locale@2@A + ?id@?$codecvt@_WDH@std@@2V0locale@2@A + ?id@?$collate@D@std@@2V0locale@2@A + ?id@?$collate@G@std@@2V0locale@2@A + ?id@?$collate@_W@std@@2V0locale@2@A + ?id@?$ctype@D@std@@2V0locale@2@A + ?id@?$ctype@G@std@@2V0locale@2@A + ?id@?$ctype@_W@std@@2V0locale@2@A + ?id@?$messages@D@std@@2V0locale@2@A + ?id@?$messages@G@std@@2V0locale@2@A + ?id@?$messages@_W@std@@2V0locale@2@A + ?id@?$money_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@2V0locale@2@A + ?id@?$money_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@2V0locale@2@A + ?id@?$money_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@2V0locale@2@A + ?id@?$money_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@2V0locale@2@A + ?id@?$money_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@2V0locale@2@A + ?id@?$money_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@2V0locale@2@A + ?id@?$moneypunct@D$00@std@@2V0locale@2@A + ?id@?$moneypunct@D$0A@@std@@2V0locale@2@A + ?id@?$moneypunct@G$00@std@@2V0locale@2@A + ?id@?$moneypunct@G$0A@@std@@2V0locale@2@A + ?id@?$moneypunct@_W$00@std@@2V0locale@2@A + ?id@?$moneypunct@_W$0A@@std@@2V0locale@2@A + ?id@?$num_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@2V0locale@2@A + ?id@?$num_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@2V0locale@2@A + ?id@?$num_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@2V0locale@2@A + ?id@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@2V0locale@2@A + ?id@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@2V0locale@2@A + ?id@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@2V0locale@2@A + ?id@?$numpunct@D@std@@2V0locale@2@A + ?id@?$numpunct@G@std@@2V0locale@2@A + ?id@?$numpunct@_W@std@@2V0locale@2@A + ?id@?$time_get@DV?$istreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@2V0locale@2@A + ?id@?$time_get@GV?$istreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@2V0locale@2@A + ?id@?$time_get@_WV?$istreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@2V0locale@2@A + ?id@?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@2V0locale@2@A + ?id@?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@2V0locale@2@A + ?id@?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@2V0locale@2@A + ?ignore@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@_JH@Z + ?ignore@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@_JG@Z + ?ignore@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@_JG@Z + ?imbue@?$basic_ios@DU?$char_traits@D@std@@@std@@QAE?AVlocale@2@ABV32@@Z + ?imbue@?$basic_ios@GU?$char_traits@G@std@@@std@@QAE?AVlocale@2@ABV32@@Z + ?imbue@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAE?AVlocale@2@ABV32@@Z + ?imbue@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAEXABVlocale@2@@Z + ?imbue@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAEXABVlocale@2@@Z + ?imbue@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAEXABVlocale@2@@Z + ?imbue@ios_base@std@@QAE?AVlocale@2@ABV32@@Z + ?in@?$codecvt@DDH@std@@QBEHAAHPBD1AAPBDPAD3AAPAD@Z + ?in@?$codecvt@GDH@std@@QBEHAAHPBD1AAPBDPAG3AAPAG@Z + ?in@?$codecvt@_WDH@std@@QBEHAAHPBD1AAPBDPA_W3AAPA_W@Z + ?in_avail@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE_JXZ + ?in_avail@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE_JXZ + ?in_avail@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE_JXZ + ?infinity@?$numeric_limits@C@std@@SACXZ + ?infinity@?$numeric_limits@D@std@@SADXZ + ?infinity@?$numeric_limits@E@std@@SAEXZ + ?infinity@?$numeric_limits@F@std@@SAFXZ + ?infinity@?$numeric_limits@G@std@@SAGXZ + ?infinity@?$numeric_limits@H@std@@SAHXZ + ?infinity@?$numeric_limits@I@std@@SAIXZ + ?infinity@?$numeric_limits@J@std@@SAJXZ + ?infinity@?$numeric_limits@K@std@@SAKXZ + ?infinity@?$numeric_limits@M@std@@SAMXZ + ?infinity@?$numeric_limits@N@std@@SANXZ + ?infinity@?$numeric_limits@O@std@@SAOXZ + ?infinity@?$numeric_limits@_J@std@@SA_JXZ + ?infinity@?$numeric_limits@_K@std@@SA_KXZ + ?infinity@?$numeric_limits@_N@std@@SA_NXZ + ?infinity@?$numeric_limits@_W@std@@SA_WXZ + ?init@?$basic_ios@DU?$char_traits@D@std@@@std@@IAEXPAV?$basic_streambuf@DU?$char_traits@D@std@@@2@_N@Z + ?init@?$basic_ios@GU?$char_traits@G@std@@@std@@IAEXPAV?$basic_streambuf@GU?$char_traits@G@std@@@2@_N@Z + ?init@?$basic_ios@_WU?$char_traits@_W@std@@@std@@IAEXPAV?$basic_streambuf@_WU?$char_traits@_W@std@@@2@_N@Z + ?intl@?$moneypunct@D$00@std@@2_NB + ?intl@?$moneypunct@D$0A@@std@@2_NB + ?intl@?$moneypunct@G$00@std@@2_NB + ?intl@?$moneypunct@G$0A@@std@@2_NB + ?intl@?$moneypunct@_W$00@std@@2_NB + ?intl@?$moneypunct@_W$0A@@std@@2_NB + ?iostream_category@std@@YAABVerror_category@1@XZ + ?ipfx@?$basic_istream@DU?$char_traits@D@std@@@std@@QAE_N_N@Z + ?ipfx@?$basic_istream@GU?$char_traits@G@std@@@std@@QAE_N_N@Z + ?ipfx@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE_N_N@Z + ?is@?$ctype@D@std@@QBEPBDPBD0PAF@Z + ?is@?$ctype@D@std@@QBE_NFD@Z + ?is@?$ctype@G@std@@QBEPBGPBG0PAF@Z + ?is@?$ctype@G@std@@QBE_NFG@Z + ?is@?$ctype@_W@std@@QBEPB_WPB_W0PAF@Z + ?is@?$ctype@_W@std@@QBE_NF_W@Z + ?is_bounded@_Num_base@std@@2_NB + ?is_bounded@_Num_float_base@std@@2_NB + ?is_bounded@_Num_int_base@std@@2_NB + ?is_current_task_group_canceling@Concurrency@@YA_NXZ + ?is_exact@_Num_base@std@@2_NB + ?is_exact@_Num_float_base@std@@2_NB + ?is_exact@_Num_int_base@std@@2_NB + ?is_iec559@_Num_base@std@@2_NB + ?is_iec559@_Num_float_base@std@@2_NB + ?is_integer@_Num_base@std@@2_NB + ?is_integer@_Num_float_base@std@@2_NB + ?is_integer@_Num_int_base@std@@2_NB + ?is_modulo@?$numeric_limits@_N@std@@2_NB + ?is_modulo@_Num_base@std@@2_NB + ?is_modulo@_Num_float_base@std@@2_NB + ?is_modulo@_Num_int_base@std@@2_NB + ?is_signed@?$numeric_limits@C@std@@2_NB + ?is_signed@?$numeric_limits@D@std@@2_NB + ?is_signed@?$numeric_limits@E@std@@2_NB + ?is_signed@?$numeric_limits@F@std@@2_NB + ?is_signed@?$numeric_limits@G@std@@2_NB + ?is_signed@?$numeric_limits@H@std@@2_NB + ?is_signed@?$numeric_limits@I@std@@2_NB + ?is_signed@?$numeric_limits@J@std@@2_NB + ?is_signed@?$numeric_limits@K@std@@2_NB + ?is_signed@?$numeric_limits@_J@std@@2_NB + ?is_signed@?$numeric_limits@_K@std@@2_NB + ?is_signed@?$numeric_limits@_N@std@@2_NB + ?is_signed@?$numeric_limits@_W@std@@2_NB + ?is_signed@_Num_base@std@@2_NB + ?is_signed@_Num_float_base@std@@2_NB + ?is_specialized@_Num_base@std@@2_NB + ?is_specialized@_Num_float_base@std@@2_NB + ?is_specialized@_Num_int_base@std@@2_NB + ?isfx@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEXXZ + ?isfx@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEXXZ + ?isfx@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEXXZ + ?iword@ios_base@std@@QAEAAJH@Z + ?length@?$codecvt@DDH@std@@QBEHABHPBD1I@Z + ?length@?$codecvt@GDH@std@@QBEHABHPBD1I@Z + ?length@?$codecvt@_WDH@std@@QBEHABHPBD1I@Z + ?lowest@?$numeric_limits@C@std@@SACXZ + ?lowest@?$numeric_limits@D@std@@SADXZ + ?lowest@?$numeric_limits@E@std@@SAEXZ + ?lowest@?$numeric_limits@F@std@@SAFXZ + ?lowest@?$numeric_limits@G@std@@SAGXZ + ?lowest@?$numeric_limits@H@std@@SAHXZ + ?lowest@?$numeric_limits@I@std@@SAIXZ + ?lowest@?$numeric_limits@J@std@@SAJXZ + ?lowest@?$numeric_limits@K@std@@SAKXZ + ?lowest@?$numeric_limits@M@std@@SAMXZ + ?lowest@?$numeric_limits@N@std@@SANXZ + ?lowest@?$numeric_limits@O@std@@SAOXZ + ?lowest@?$numeric_limits@_J@std@@SA_JXZ + ?lowest@?$numeric_limits@_K@std@@SA_KXZ + ?lowest@?$numeric_limits@_N@std@@SA_NXZ + ?lowest@?$numeric_limits@_W@std@@SA_WXZ + ?max@?$numeric_limits@C@std@@SACXZ + ?max@?$numeric_limits@D@std@@SADXZ + ?max@?$numeric_limits@E@std@@SAEXZ + ?max@?$numeric_limits@F@std@@SAFXZ + ?max@?$numeric_limits@G@std@@SAGXZ + ?max@?$numeric_limits@H@std@@SAHXZ + ?max@?$numeric_limits@I@std@@SAIXZ + ?max@?$numeric_limits@J@std@@SAJXZ + ?max@?$numeric_limits@K@std@@SAKXZ + ?max@?$numeric_limits@M@std@@SAMXZ + ?max@?$numeric_limits@N@std@@SANXZ + ?max@?$numeric_limits@O@std@@SAOXZ + ?max@?$numeric_limits@_J@std@@SA_JXZ + ?max@?$numeric_limits@_K@std@@SA_KXZ + ?max@?$numeric_limits@_N@std@@SA_NXZ + ?max@?$numeric_limits@_W@std@@SA_WXZ + ?max_digits10@?$numeric_limits@C@std@@2HB + ?max_digits10@?$numeric_limits@D@std@@2HB + ?max_digits10@?$numeric_limits@E@std@@2HB + ?max_digits10@?$numeric_limits@F@std@@2HB + ?max_digits10@?$numeric_limits@G@std@@2HB + ?max_digits10@?$numeric_limits@H@std@@2HB + ?max_digits10@?$numeric_limits@I@std@@2HB + ?max_digits10@?$numeric_limits@J@std@@2HB + ?max_digits10@?$numeric_limits@K@std@@2HB + ?max_digits10@?$numeric_limits@M@std@@2HB + ?max_digits10@?$numeric_limits@N@std@@2HB + ?max_digits10@?$numeric_limits@O@std@@2HB + ?max_digits10@?$numeric_limits@_J@std@@2HB + ?max_digits10@?$numeric_limits@_K@std@@2HB + ?max_digits10@?$numeric_limits@_N@std@@2HB + ?max_digits10@?$numeric_limits@_W@std@@2HB + ?max_digits10@_Num_base@std@@2HB + ?max_exponent10@?$numeric_limits@M@std@@2HB + ?max_exponent10@?$numeric_limits@N@std@@2HB + ?max_exponent10@?$numeric_limits@O@std@@2HB + ?max_exponent10@_Num_base@std@@2HB + ?max_exponent@?$numeric_limits@M@std@@2HB + ?max_exponent@?$numeric_limits@N@std@@2HB + ?max_exponent@?$numeric_limits@O@std@@2HB + ?max_exponent@_Num_base@std@@2HB + ?max_length@codecvt_base@std@@QBEHXZ + ?min@?$numeric_limits@C@std@@SACXZ + ?min@?$numeric_limits@D@std@@SADXZ + ?min@?$numeric_limits@E@std@@SAEXZ + ?min@?$numeric_limits@F@std@@SAFXZ + ?min@?$numeric_limits@G@std@@SAGXZ + ?min@?$numeric_limits@H@std@@SAHXZ + ?min@?$numeric_limits@I@std@@SAIXZ + ?min@?$numeric_limits@J@std@@SAJXZ + ?min@?$numeric_limits@K@std@@SAKXZ + ?min@?$numeric_limits@M@std@@SAMXZ + ?min@?$numeric_limits@N@std@@SANXZ + ?min@?$numeric_limits@O@std@@SAOXZ + ?min@?$numeric_limits@_J@std@@SA_JXZ + ?min@?$numeric_limits@_K@std@@SA_KXZ + ?min@?$numeric_limits@_N@std@@SA_NXZ + ?min@?$numeric_limits@_W@std@@SA_WXZ + ?min_exponent10@?$numeric_limits@M@std@@2HB + ?min_exponent10@?$numeric_limits@N@std@@2HB + ?min_exponent10@?$numeric_limits@O@std@@2HB + ?min_exponent10@_Num_base@std@@2HB + ?min_exponent@?$numeric_limits@M@std@@2HB + ?min_exponent@?$numeric_limits@N@std@@2HB + ?min_exponent@?$numeric_limits@O@std@@2HB + ?min_exponent@_Num_base@std@@2HB + ?move@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEX$$QAV12@@Z + ?move@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEX$$QAV12@@Z + ?move@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEX$$QAV12@@Z + ?narrow@?$basic_ios@DU?$char_traits@D@std@@@std@@QBEDDD@Z + ?narrow@?$basic_ios@GU?$char_traits@G@std@@@std@@QBEDGD@Z + ?narrow@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QBED_WD@Z + ?narrow@?$ctype@D@std@@QBEDDD@Z + ?narrow@?$ctype@D@std@@QBEPBDPBD0DPAD@Z + ?narrow@?$ctype@G@std@@QBEDGD@Z + ?narrow@?$ctype@G@std@@QBEPBGPBG0DPAD@Z + ?narrow@?$ctype@_W@std@@QBED_WD@Z + ?narrow@?$ctype@_W@std@@QBEPB_WPB_W0DPAD@Z + ?opfx@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAE_NXZ + ?opfx@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAE_NXZ + ?opfx@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAE_NXZ + ?osfx@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEXXZ + ?osfx@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEXXZ + ?osfx@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEXXZ + ?out@?$codecvt@DDH@std@@QBEHAAHPBD1AAPBDPAD3AAPAD@Z + ?out@?$codecvt@GDH@std@@QBEHAAHPBG1AAPBGPAD3AAPAD@Z + ?out@?$codecvt@_WDH@std@@QBEHAAHPB_W1AAPB_WPAD3AAPAD@Z + ?overflow@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAEHH@Z + ?overflow@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAEGG@Z + ?overflow@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAEGG@Z + ?pbackfail@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAEHH@Z + ?pbackfail@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAEGG@Z + ?pbackfail@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAEGG@Z + ?pbase@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBEPADXZ + ?pbase@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBEPAGXZ + ?pbase@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBEPA_WXZ + ?pbump@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXH@Z + ?pbump@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXH@Z + ?pbump@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXH@Z + ?peek@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEHXZ + ?peek@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEGXZ + ?peek@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEGXZ + ?pptr@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IBEPADXZ + ?pptr@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IBEPAGXZ + ?pptr@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IBEPA_WXZ + ?precision@ios_base@std@@QAE_J_J@Z + ?precision@ios_base@std@@QBE_JXZ + ?pubimbue@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE?AVlocale@2@ABV32@@Z + ?pubimbue@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE?AVlocale@2@ABV32@@Z + ?pubimbue@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE?AVlocale@2@ABV32@@Z + ?pubseekoff@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE?AV?$fpos@H@2@_JHH@Z + ?pubseekoff@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE?AV?$fpos@H@2@_JII@Z + ?pubseekoff@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE?AV?$fpos@H@2@_JHH@Z + ?pubseekoff@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE?AV?$fpos@H@2@_JII@Z + ?pubseekoff@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE?AV?$fpos@H@2@_JHH@Z + ?pubseekoff@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE?AV?$fpos@H@2@_JII@Z + ?pubseekpos@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE?AV?$fpos@H@2@V32@H@Z + ?pubseekpos@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE?AV?$fpos@H@2@V32@I@Z + ?pubseekpos@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE?AV?$fpos@H@2@V32@H@Z + ?pubseekpos@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE?AV?$fpos@H@2@V32@I@Z + ?pubseekpos@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE?AV?$fpos@H@2@V32@H@Z + ?pubseekpos@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE?AV?$fpos@H@2@V32@I@Z + ?pubsetbuf@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEPAV12@PAD_J@Z + ?pubsetbuf@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEPAV12@PAG_J@Z + ?pubsetbuf@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEPAV12@PA_W_J@Z + ?pubsync@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHXZ + ?pubsync@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEHXZ + ?pubsync@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEHXZ + ?put@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV12@D@Z + ?put@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV12@G@Z + ?put@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@_W@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DJ@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DK@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DN@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DO@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DPBX@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@D_J@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@D_K@Z + ?put@?$num_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@D_N@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GJ@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GK@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GN@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GO@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GPBX@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@G_J@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@G_K@Z + ?put@?$num_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@G_N@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WJ@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WK@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WN@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WO@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WPBX@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_W_J@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_W_K@Z + ?put@?$num_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_W_N@Z + ?put@?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DPBUtm@@DD@Z + ?put@?$time_put@DV?$ostreambuf_iterator@DU?$char_traits@D@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@DU?$char_traits@D@std@@@2@V32@AAVios_base@2@DPBUtm@@PBD3@Z + ?put@?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GPBUtm@@DD@Z + ?put@?$time_put@GV?$ostreambuf_iterator@GU?$char_traits@G@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@GU?$char_traits@G@std@@@2@V32@AAVios_base@2@GPBUtm@@PBG3@Z + ?put@?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WPBUtm@@DD@Z + ?put@?$time_put@_WV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@std@@@std@@QBE?AV?$ostreambuf_iterator@_WU?$char_traits@_W@std@@@2@V32@AAVios_base@2@_WPBUtm@@PB_W4@Z + ?putback@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@D@Z + ?putback@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@G@Z + ?putback@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@_W@Z + ?pword@ios_base@std@@QAEAAPAXH@Z + ?quiet_NaN@?$numeric_limits@C@std@@SACXZ + ?quiet_NaN@?$numeric_limits@D@std@@SADXZ + ?quiet_NaN@?$numeric_limits@E@std@@SAEXZ + ?quiet_NaN@?$numeric_limits@F@std@@SAFXZ + ?quiet_NaN@?$numeric_limits@G@std@@SAGXZ + ?quiet_NaN@?$numeric_limits@H@std@@SAHXZ + ?quiet_NaN@?$numeric_limits@I@std@@SAIXZ + ?quiet_NaN@?$numeric_limits@J@std@@SAJXZ + ?quiet_NaN@?$numeric_limits@K@std@@SAKXZ + ?quiet_NaN@?$numeric_limits@M@std@@SAMXZ + ?quiet_NaN@?$numeric_limits@N@std@@SANXZ + ?quiet_NaN@?$numeric_limits@O@std@@SAOXZ + ?quiet_NaN@?$numeric_limits@_J@std@@SA_JXZ + ?quiet_NaN@?$numeric_limits@_K@std@@SA_KXZ + ?quiet_NaN@?$numeric_limits@_N@std@@SA_NXZ + ?quiet_NaN@?$numeric_limits@_W@std@@SA_WXZ + ?radix@_Num_base@std@@2HB + ?radix@_Num_float_base@std@@2HB + ?radix@_Num_int_base@std@@2HB + ?rdbuf@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEPAV?$basic_streambuf@DU?$char_traits@D@std@@@2@PAV32@@Z + ?rdbuf@?$basic_ios@DU?$char_traits@D@std@@@std@@QBEPAV?$basic_streambuf@DU?$char_traits@D@std@@@2@XZ + ?rdbuf@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEPAV?$basic_streambuf@GU?$char_traits@G@std@@@2@PAV32@@Z + ?rdbuf@?$basic_ios@GU?$char_traits@G@std@@@std@@QBEPAV?$basic_streambuf@GU?$char_traits@G@std@@@2@XZ + ?rdbuf@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEPAV?$basic_streambuf@_WU?$char_traits@_W@std@@@2@PAV32@@Z + ?rdbuf@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QBEPAV?$basic_streambuf@_WU?$char_traits@_W@std@@@2@XZ + ?rdstate@ios_base@std@@QBEHXZ + ?read@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@PAD_J@Z + ?read@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@PAG_J@Z + ?read@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@PA_W_J@Z + ?readsome@?$basic_istream@DU?$char_traits@D@std@@@std@@QAE_JPAD_J@Z + ?readsome@?$basic_istream@GU?$char_traits@G@std@@@std@@QAE_JPAG_J@Z + ?readsome@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE_JPA_W_J@Z + ?register_callback@ios_base@std@@QAEXP6AXW4event@12@AAV12@H@ZH@Z + ?resetiosflags@std@@YA?AU?$_Smanip@H@1@H@Z + ?round_error@?$numeric_limits@C@std@@SACXZ + ?round_error@?$numeric_limits@D@std@@SADXZ + ?round_error@?$numeric_limits@E@std@@SAEXZ + ?round_error@?$numeric_limits@F@std@@SAFXZ + ?round_error@?$numeric_limits@G@std@@SAGXZ + ?round_error@?$numeric_limits@H@std@@SAHXZ + ?round_error@?$numeric_limits@I@std@@SAIXZ + ?round_error@?$numeric_limits@J@std@@SAJXZ + ?round_error@?$numeric_limits@K@std@@SAKXZ + ?round_error@?$numeric_limits@M@std@@SAMXZ + ?round_error@?$numeric_limits@N@std@@SANXZ + ?round_error@?$numeric_limits@O@std@@SAOXZ + ?round_error@?$numeric_limits@_J@std@@SA_JXZ + ?round_error@?$numeric_limits@_K@std@@SA_KXZ + ?round_error@?$numeric_limits@_N@std@@SA_NXZ + ?round_error@?$numeric_limits@_W@std@@SA_WXZ + ?round_style@_Num_base@std@@2W4float_round_style@2@B + ?round_style@_Num_float_base@std@@2W4float_round_style@2@B + ?sbumpc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHXZ + ?sbumpc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEGXZ + ?sbumpc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEGXZ + ?scan_is@?$ctype@D@std@@QBEPBDFPBD0@Z + ?scan_is@?$ctype@G@std@@QBEPBGFPBG0@Z + ?scan_is@?$ctype@_W@std@@QBEPB_WFPB_W0@Z + ?scan_not@?$ctype@D@std@@QBEPBDFPBD0@Z + ?scan_not@?$ctype@G@std@@QBEPBGFPBG0@Z + ?scan_not@?$ctype@_W@std@@QBEPB_WFPB_W0@Z + ?seekg@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@V?$fpos@H@2@@Z + ?seekg@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@_JH@Z + ?seekg@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@V?$fpos@H@2@@Z + ?seekg@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@_JH@Z + ?seekg@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@V?$fpos@H@2@@Z + ?seekg@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@_JH@Z + ?seekoff@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAE?AV?$fpos@H@2@_JHH@Z + ?seekoff@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAE?AV?$fpos@H@2@_JHH@Z + ?seekoff@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAE?AV?$fpos@H@2@_JHH@Z + ?seekp@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV12@V?$fpos@H@2@@Z + ?seekp@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV12@_JH@Z + ?seekp@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV12@V?$fpos@H@2@@Z + ?seekp@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV12@_JH@Z + ?seekp@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@V?$fpos@H@2@@Z + ?seekp@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@_JH@Z + ?seekpos@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAE?AV?$fpos@H@2@V32@H@Z + ?seekpos@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAE?AV?$fpos@H@2@V32@H@Z + ?seekpos@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAE?AV?$fpos@H@2@V32@H@Z + ?set_new_handler@std@@YAP6AXXZH@Z + ?set_new_handler@std@@YAP6AXXZP6AXXZ@Z + ?set_rdbuf@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEXPAV?$basic_streambuf@DU?$char_traits@D@std@@@2@@Z + ?set_rdbuf@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEXPAV?$basic_streambuf@GU?$char_traits@G@std@@@2@@Z + ?set_rdbuf@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEXPAV?$basic_streambuf@_WU?$char_traits@_W@std@@@2@@Z + ?setbase@std@@YA?AU?$_Smanip@H@1@H@Z + ?setbuf@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAEPAV12@PAD_J@Z + ?setbuf@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAEPAV12@PAG_J@Z + ?setbuf@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAEPAV12@PA_W_J@Z + ?setf@ios_base@std@@QAEHH@Z + ?setf@ios_base@std@@QAEHHH@Z + ?setg@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXPAD00@Z + ?setg@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXPAG00@Z + ?setg@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXPA_W00@Z + ?setiosflags@std@@YA?AU?$_Smanip@H@1@H@Z + ?setp@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXPAD00@Z + ?setp@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXPAD0@Z + ?setp@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXPAG00@Z + ?setp@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXPAG0@Z + ?setp@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXPA_W00@Z + ?setp@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXPA_W0@Z + ?setprecision@std@@YA?AU?$_Smanip@_J@1@_J@Z + ?setstate@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEXH_N@Z + ?setstate@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEXI@Z + ?setstate@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEXH_N@Z + ?setstate@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEXI@Z + ?setstate@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEXH_N@Z + ?setstate@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEXI@Z + ?setstate@ios_base@std@@QAEXH@Z + ?setstate@ios_base@std@@QAEXH_N@Z + ?setstate@ios_base@std@@QAEXI@Z + ?setw@std@@YA?AU?$_Smanip@_J@1@_J@Z + ?sgetc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHXZ + ?sgetc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEGXZ + ?sgetc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEGXZ + ?sgetn@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE_JPAD_J@Z + ?sgetn@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE_JPAG_J@Z + ?sgetn@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE_JPA_W_J@Z + ?showmanyc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAE_JXZ + ?showmanyc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAE_JXZ + ?showmanyc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAE_JXZ + ?signaling_NaN@?$numeric_limits@C@std@@SACXZ + ?signaling_NaN@?$numeric_limits@D@std@@SADXZ + ?signaling_NaN@?$numeric_limits@E@std@@SAEXZ + ?signaling_NaN@?$numeric_limits@F@std@@SAFXZ + ?signaling_NaN@?$numeric_limits@G@std@@SAGXZ + ?signaling_NaN@?$numeric_limits@H@std@@SAHXZ + ?signaling_NaN@?$numeric_limits@I@std@@SAIXZ + ?signaling_NaN@?$numeric_limits@J@std@@SAJXZ + ?signaling_NaN@?$numeric_limits@K@std@@SAKXZ + ?signaling_NaN@?$numeric_limits@M@std@@SAMXZ + ?signaling_NaN@?$numeric_limits@N@std@@SANXZ + ?signaling_NaN@?$numeric_limits@O@std@@SAOXZ + ?signaling_NaN@?$numeric_limits@_J@std@@SA_JXZ + ?signaling_NaN@?$numeric_limits@_K@std@@SA_KXZ + ?signaling_NaN@?$numeric_limits@_N@std@@SA_NXZ + ?signaling_NaN@?$numeric_limits@_W@std@@SA_WXZ + ?snextc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHXZ + ?snextc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEGXZ + ?snextc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEGXZ + ?sputbackc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHD@Z + ?sputbackc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEGG@Z + ?sputbackc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEG_W@Z + ?sputc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHD@Z + ?sputc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEGG@Z + ?sputc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEG_W@Z + ?sputn@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAE_JPBD_J@Z + ?sputn@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAE_JPBG_J@Z + ?sputn@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAE_JPB_W_J@Z + ?start@agent@Concurrency@@QAE_NXZ + ?status@agent@Concurrency@@QAE?AW4agent_status@2@XZ + ?status_port@agent@Concurrency@@QAEPAV?$ISource@W4agent_status@Concurrency@@@2@XZ + ?stossc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEXXZ + ?stossc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEXXZ + ?stossc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEXXZ + ?sungetc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHXZ + ?sungetc@?$basic_streambuf@GU?$char_traits@G@std@@@std@@QAEGXZ + ?sungetc@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@QAEGXZ + ?swap@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_iostream@DU?$char_traits@D@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_iostream@GU?$char_traits@G@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_iostream@_WU?$char_traits@_W@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEXAAV12@@Z + ?swap@?$basic_streambuf@DU?$char_traits@D@std@@@std@@IAEXAAV12@@Z + ?swap@?$basic_streambuf@GU?$char_traits@G@std@@@std@@IAEXAAV12@@Z + ?swap@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@IAEXAAV12@@Z + ?swap@ios_base@std@@QAEXAAV12@@Z + ?sync@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEHXZ + ?sync@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEHXZ + ?sync@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEHXZ + ?sync@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAEHXZ + ?sync@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAEHXZ + ?sync@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAEHXZ + ?sync_with_stdio@ios_base@std@@SA_N_N@Z + ?system_category@std@@YAABVerror_category@1@XZ + ?table@?$ctype@D@std@@QBEPBFXZ + ?table_size@?$ctype@D@std@@2IB + ?tellg@?$basic_istream@DU?$char_traits@D@std@@@std@@QAE?AV?$fpos@H@2@XZ + ?tellg@?$basic_istream@GU?$char_traits@G@std@@@std@@QAE?AV?$fpos@H@2@XZ + ?tellg@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAE?AV?$fpos@H@2@XZ + ?tellp@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAE?AV?$fpos@H@2@XZ + ?tellp@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAE?AV?$fpos@H@2@XZ + ?tellp@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAE?AV?$fpos@H@2@XZ + ?tie@?$basic_ios@DU?$char_traits@D@std@@@std@@QAEPAV?$basic_ostream@DU?$char_traits@D@std@@@2@PAV32@@Z + ?tie@?$basic_ios@DU?$char_traits@D@std@@@std@@QBEPAV?$basic_ostream@DU?$char_traits@D@std@@@2@XZ + ?tie@?$basic_ios@GU?$char_traits@G@std@@@std@@QAEPAV?$basic_ostream@GU?$char_traits@G@std@@@2@PAV32@@Z + ?tie@?$basic_ios@GU?$char_traits@G@std@@@std@@QBEPAV?$basic_ostream@GU?$char_traits@G@std@@@2@XZ + ?tie@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QAEPAV?$basic_ostream@_WU?$char_traits@_W@std@@@2@PAV32@@Z + ?tie@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QBEPAV?$basic_ostream@_WU?$char_traits@_W@std@@@2@XZ + ?tinyness_before@_Num_base@std@@2_NB + ?tinyness_before@_Num_float_base@std@@2_NB + ?tolower@?$ctype@D@std@@QBEDD@Z + ?tolower@?$ctype@D@std@@QBEPBDPADPBD@Z + ?tolower@?$ctype@G@std@@QBEGG@Z + ?tolower@?$ctype@G@std@@QBEPBGPAGPBG@Z + ?tolower@?$ctype@_W@std@@QBEPB_WPA_WPB_W@Z + ?tolower@?$ctype@_W@std@@QBE_W_W@Z + ?toupper@?$ctype@D@std@@QBEDD@Z + ?toupper@?$ctype@D@std@@QBEPBDPADPBD@Z + ?toupper@?$ctype@G@std@@QBEGG@Z + ?toupper@?$ctype@G@std@@QBEPBGPAGPBG@Z + ?toupper@?$ctype@_W@std@@QBEPB_WPA_WPB_W@Z + ?toupper@?$ctype@_W@std@@QBE_W_W@Z + ?traps@_Num_base@std@@2_NB + ?traps@_Num_float_base@std@@2_NB + ?uflow@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAEHXZ + ?uflow@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAEGXZ + ?uflow@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAEGXZ + ?uncaught_exception@std@@YA_NXZ + ?underflow@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAEHXZ + ?underflow@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAEGXZ + ?underflow@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAEGXZ + ?unget@?$basic_istream@DU?$char_traits@D@std@@@std@@QAEAAV12@XZ + ?unget@?$basic_istream@GU?$char_traits@G@std@@@std@@QAEAAV12@XZ + ?unget@?$basic_istream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@XZ + ?unsetf@ios_base@std@@QAEXH@Z + ?unshift@?$codecvt@DDH@std@@QBEHAAHPAD1AAPAD@Z + ?unshift@?$codecvt@GDH@std@@QBEHAAHPAD1AAPAD@Z + ?unshift@?$codecvt@_WDH@std@@QBEHAAHPAD1AAPAD@Z + ?wait@agent@Concurrency@@SA?AW4agent_status@2@PAV12@I@Z + ?wait_for_all@agent@Concurrency@@SAXIPAPAV12@PAW4agent_status@2@I@Z + ?wait_for_one@agent@Concurrency@@SAXIPAPAV12@AAW4agent_status@2@AAII@Z + ?wcerr@std@@3V?$basic_ostream@GU?$char_traits@G@std@@@1@A + ?wcerr@std@@3V?$basic_ostream@_WU?$char_traits@_W@std@@@1@A + ?wcin@std@@3V?$basic_istream@GU?$char_traits@G@std@@@1@A + ?wcin@std@@3V?$basic_istream@_WU?$char_traits@_W@std@@@1@A + ?wclog@std@@3V?$basic_ostream@GU?$char_traits@G@std@@@1@A + ?wclog@std@@3V?$basic_ostream@_WU?$char_traits@_W@std@@@1@A + ?wcout@std@@3V?$basic_ostream@GU?$char_traits@G@std@@@1@A + ?wcout@std@@3V?$basic_ostream@_WU?$char_traits@_W@std@@@1@A + ?widen@?$basic_ios@DU?$char_traits@D@std@@@std@@QBEDD@Z + ?widen@?$basic_ios@GU?$char_traits@G@std@@@std@@QBEGD@Z + ?widen@?$basic_ios@_WU?$char_traits@_W@std@@@std@@QBE_WD@Z + ?widen@?$ctype@D@std@@QBEDD@Z + ?widen@?$ctype@D@std@@QBEPBDPBD0PAD@Z + ?widen@?$ctype@G@std@@QBEGD@Z + ?widen@?$ctype@G@std@@QBEPBDPBD0PAG@Z + ?widen@?$ctype@_W@std@@QBEPBDPBD0PA_W@Z + ?widen@?$ctype@_W@std@@QBE_WD@Z + ?width@ios_base@std@@QAE_J_J@Z + ?width@ios_base@std@@QBE_JXZ + ?write@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV12@PBD_J@Z + ?write@?$basic_ostream@GU?$char_traits@G@std@@@std@@QAEAAV12@PBG_J@Z + ?write@?$basic_ostream@_WU?$char_traits@_W@std@@@std@@QAEAAV12@PB_W_J@Z + ?ws@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@1@AAV21@@Z + ?ws@std@@YAAAV?$basic_istream@GU?$char_traits@G@std@@@1@AAV21@@Z + ?ws@std@@YAAAV?$basic_istream@_WU?$char_traits@_W@std@@@1@AAV21@@Z + ?xalloc@ios_base@std@@SAHXZ + ?xsgetn@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAE_JPAD_J@Z + ?xsgetn@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAE_JPAG_J@Z + ?xsgetn@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAE_JPA_W_J@Z + ?xsputn@?$basic_streambuf@DU?$char_traits@D@std@@@std@@MAE_JPBD_J@Z + ?xsputn@?$basic_streambuf@GU?$char_traits@G@std@@@std@@MAE_JPBG_J@Z + ?xsputn@?$basic_streambuf@_WU?$char_traits@_W@std@@@std@@MAE_JPB_W_J@Z + _Cosh + _Denorm + _Dnorm + _Dscale + _Dtest + _Eps + _Exp + _FCosh + _FDenorm + _FDnorm + _FDscale + _FDtest + _FEps + _FExp + _FInf + _FNan + _FRteps + _FSinh + _FSnan + _FXbig + _GetLocaleForCP + _Getcoll + _Getctype + _Getcvt + _Getdateorder + _Getwctype + _Getwctypes + _Hugeval + _Inf + _LCosh + _LDenorm + _LDscale + _LDtest + _LEps + _LExp + _LInf + _LNan + _LPoly + _LRteps + _LSinh + _LSnan + _LXbig + _LZero + _Mbrtowc + _Mtxdst + _Mtxinit + _Mtxlock + _Mtxunlock + _Nan + _Once + _Poly + _Rteps + _Sinh + _Snan + _Stod + _Stodx + _Stof + _Stofx + _Stold + _Stoldx + _Stoll + _Stollx + _Stolx + _Stoul + _Stoull + _Stoullx + _Stoulx + _Strcoll + _Strxfrm + _Tolower + _Toupper + _Towlower + _Towupper + _Wcrtomb + _Wcscoll + _Wcsxfrm + _Xbig + __Wcrtomb_lk + towctrans + wctrans + wctype + diff --git a/src/VBox/Runtime/r3/win/VBoxRT-msvcr100-win32.def b/src/VBox/Runtime/r3/win/VBoxRT-msvcr100-win32.def new file mode 100644 index 00000000..6317e658 --- /dev/null +++ b/src/VBox/Runtime/r3/win/VBoxRT-msvcr100-win32.def @@ -0,0 +1,1640 @@ + + ; + _except_handler4 + ;_heap_init + ;__crtExitProcess + ;_NMSG_WRITE + ;_FF_MSGBANNER + ;_cinit + ;_setenvp + ;__crtGetEnvironmentStringsA + ;_ioinit + ;_mtinit + + _initterm=my_initterm + __dllonexit=my_dllonexit + __getmainargs=my_getmainargs + __setusermatherr=my_setusermatherr + + _onexit DATA ; the one is crazy. atonexit.obj in msvcrt.lib which calls the __imp___onexit directly. + + ; MSVCR100.DLL exports + ??0?$_SpinWait@$0A@@details@Concurrency@@QAE@P6AXXZ@Z + ??0SchedulerPolicy@Concurrency@@QAA@IZZ + ??0SchedulerPolicy@Concurrency@@QAE@ABV01@@Z + ??0SchedulerPolicy@Concurrency@@QAE@XZ + ??0_NonReentrantBlockingLock@details@Concurrency@@QAE@XZ + ??0_NonReentrantPPLLock@details@Concurrency@@QAE@XZ + ??0_ReaderWriterLock@details@Concurrency@@QAE@XZ + ??0_ReentrantBlockingLock@details@Concurrency@@QAE@XZ + ??0_ReentrantLock@details@Concurrency@@QAE@XZ + ??0_ReentrantPPLLock@details@Concurrency@@QAE@XZ + ??0_Scoped_lock@_NonReentrantPPLLock@details@Concurrency@@QAE@AAV123@@Z + ??0_Scoped_lock@_ReentrantPPLLock@details@Concurrency@@QAE@AAV123@@Z + ??0_SpinLock@details@Concurrency@@QAE@ACJ@Z + ??0_TaskCollection@details@Concurrency@@QAE@XZ + ??0_Timer@details@Concurrency@@IAE@I_N@Z + ??0__non_rtti_object@std@@QAE@ABV01@@Z + ??0__non_rtti_object@std@@QAE@PBD@Z + ;fixme-not-in-libcmt.lib; ??0bad_cast@std@@AAE@PBQBD@Z + ??0bad_cast@std@@QAE@ABV01@@Z + ??0bad_cast@std@@QAE@PBD@Z + ??0bad_target@Concurrency@@QAE@PBD@Z + ??0bad_target@Concurrency@@QAE@XZ + ??0bad_typeid@std@@QAE@ABV01@@Z + ??0bad_typeid@std@@QAE@PBD@Z + ??0context_self_unblock@Concurrency@@QAE@PBD@Z + ??0context_self_unblock@Concurrency@@QAE@XZ + ??0context_unblock_unbalanced@Concurrency@@QAE@PBD@Z + ??0context_unblock_unbalanced@Concurrency@@QAE@XZ + ??0critical_section@Concurrency@@QAE@XZ + ??0default_scheduler_exists@Concurrency@@QAE@PBD@Z + ??0default_scheduler_exists@Concurrency@@QAE@XZ + ??0event@Concurrency@@QAE@XZ + ??0exception@std@@QAE@ABQBD@Z + ??0exception@std@@QAE@ABQBDH@Z + ??0exception@std@@QAE@ABV01@@Z + ??0exception@std@@QAE@XZ + ??0improper_lock@Concurrency@@QAE@PBD@Z + ??0improper_lock@Concurrency@@QAE@XZ + ??0improper_scheduler_attach@Concurrency@@QAE@PBD@Z + ??0improper_scheduler_attach@Concurrency@@QAE@XZ + ??0improper_scheduler_detach@Concurrency@@QAE@PBD@Z + ??0improper_scheduler_detach@Concurrency@@QAE@XZ + ??0improper_scheduler_reference@Concurrency@@QAE@PBD@Z + ??0improper_scheduler_reference@Concurrency@@QAE@XZ + ??0invalid_link_target@Concurrency@@QAE@PBD@Z + ??0invalid_link_target@Concurrency@@QAE@XZ + ??0invalid_multiple_scheduling@Concurrency@@QAE@PBD@Z + ??0invalid_multiple_scheduling@Concurrency@@QAE@XZ + ??0invalid_operation@Concurrency@@QAE@PBD@Z + ??0invalid_operation@Concurrency@@QAE@XZ + ??0invalid_oversubscribe_operation@Concurrency@@QAE@PBD@Z + ??0invalid_oversubscribe_operation@Concurrency@@QAE@XZ + ??0invalid_scheduler_policy_key@Concurrency@@QAE@PBD@Z + ??0invalid_scheduler_policy_key@Concurrency@@QAE@XZ + ??0invalid_scheduler_policy_thread_specification@Concurrency@@QAE@PBD@Z + ??0invalid_scheduler_policy_thread_specification@Concurrency@@QAE@XZ + ??0invalid_scheduler_policy_value@Concurrency@@QAE@PBD@Z + ??0invalid_scheduler_policy_value@Concurrency@@QAE@XZ + ??0message_not_found@Concurrency@@QAE@PBD@Z + ??0message_not_found@Concurrency@@QAE@XZ + ??0missing_wait@Concurrency@@QAE@PBD@Z + ??0missing_wait@Concurrency@@QAE@XZ + ??0nested_scheduler_missing_detach@Concurrency@@QAE@PBD@Z + ??0nested_scheduler_missing_detach@Concurrency@@QAE@XZ + ??0operation_timed_out@Concurrency@@QAE@PBD@Z + ??0operation_timed_out@Concurrency@@QAE@XZ + ??0reader_writer_lock@Concurrency@@QAE@XZ + ??0scheduler_not_attached@Concurrency@@QAE@PBD@Z + ??0scheduler_not_attached@Concurrency@@QAE@XZ + ??0scheduler_resource_allocation_error@Concurrency@@QAE@J@Z + ??0scheduler_resource_allocation_error@Concurrency@@QAE@PBDJ@Z + ??0scoped_lock@critical_section@Concurrency@@QAE@AAV12@@Z + ??0scoped_lock@reader_writer_lock@Concurrency@@QAE@AAV12@@Z + ??0scoped_lock_read@reader_writer_lock@Concurrency@@QAE@AAV12@@Z + ??0task_canceled@details@Concurrency@@QAE@PBD@Z + ??0task_canceled@details@Concurrency@@QAE@XZ + ??0unsupported_os@Concurrency@@QAE@PBD@Z + ??0unsupported_os@Concurrency@@QAE@XZ + ??1SchedulerPolicy@Concurrency@@QAE@XZ + ??1_NonReentrantBlockingLock@details@Concurrency@@QAE@XZ + ??1_ReentrantBlockingLock@details@Concurrency@@QAE@XZ + ??1_Scoped_lock@_NonReentrantPPLLock@details@Concurrency@@QAE@XZ + ??1_Scoped_lock@_ReentrantPPLLock@details@Concurrency@@QAE@XZ + ??1_SpinLock@details@Concurrency@@QAE@XZ + ??1_TaskCollection@details@Concurrency@@QAE@XZ + ??1_Timer@details@Concurrency@@IAE@XZ + ??1__non_rtti_object@std@@UAE@XZ + ??1bad_cast@std@@UAE@XZ + ??1bad_typeid@std@@UAE@XZ + ??1critical_section@Concurrency@@QAE@XZ + ??1event@Concurrency@@QAE@XZ + ??1exception@std@@UAE@XZ + ??1reader_writer_lock@Concurrency@@QAE@XZ + ??1scoped_lock@critical_section@Concurrency@@QAE@XZ + ??1scoped_lock@reader_writer_lock@Concurrency@@QAE@XZ + ??1scoped_lock_read@reader_writer_lock@Concurrency@@QAE@XZ + ??1type_info@@UAE@XZ + ??2@YAPAXI@Z + ??2@YAPAXIHPBDH@Z + ??3@YAXPAX@Z + ;fixme-not-in-libcmt.lib; ??4?$_SpinWait@$00@details@Concurrency@@QAEAAV012@ABV012@@Z + ;fixme-not-in-libcmt.lib; ??4?$_SpinWait@$0A@@details@Concurrency@@QAEAAV012@ABV012@@Z + ??4SchedulerPolicy@Concurrency@@QAEAAV01@ABV01@@Z + ??4__non_rtti_object@std@@QAEAAV01@ABV01@@Z + ??4bad_cast@std@@QAEAAV01@ABV01@@Z + ??4bad_typeid@std@@QAEAAV01@ABV01@@Z + ??4exception@std@@QAEAAV01@ABV01@@Z + ??8type_info@@QBE_NABV0@@Z + ??9type_info@@QBE_NABV0@@Z + ??_7__non_rtti_object@std@@6B@ + ??_7bad_cast@std@@6B@ + ??_7bad_typeid@std@@6B@ + ;fixme-not-in-libcmt.lib; ??_7exception@@6B@ + ??_7exception@std@@6B@ + ;fixme-not-in-libcmt.lib; ??_F?$_SpinWait@$00@details@Concurrency@@QAEXXZ + ;fixme-not-in-libcmt.lib; ??_F?$_SpinWait@$0A@@details@Concurrency@@QAEXXZ + ??_Fbad_cast@std@@QAEXXZ + ??_Fbad_typeid@std@@QAEXXZ + ??_U@YAPAXI@Z + ??_U@YAPAXIHPBDH@Z + ??_V@YAXPAX@Z + ?Alloc@Concurrency@@YAPAXI@Z + ?Block@Context@Concurrency@@SAXXZ + ?Create@CurrentScheduler@Concurrency@@SAXABVSchedulerPolicy@2@@Z + ?Create@Scheduler@Concurrency@@SAPAV12@ABVSchedulerPolicy@2@@Z + ?CreateResourceManager@Concurrency@@YAPAUIResourceManager@1@XZ + ?CreateScheduleGroup@CurrentScheduler@Concurrency@@SAPAVScheduleGroup@2@XZ + ?CurrentContext@Context@Concurrency@@SAPAV12@XZ + ?Detach@CurrentScheduler@Concurrency@@SAXXZ + ?DisableTracing@Concurrency@@YAJXZ + ?EnableTracing@Concurrency@@YAJXZ + ?Free@Concurrency@@YAXPAX@Z + ?Get@CurrentScheduler@Concurrency@@SAPAVScheduler@2@XZ + ?GetExecutionContextId@Concurrency@@YAIXZ + ?GetNumberOfVirtualProcessors@CurrentScheduler@Concurrency@@SAIXZ + ?GetOSVersion@Concurrency@@YA?AW4OSVersion@IResourceManager@1@XZ + ?GetPolicy@CurrentScheduler@Concurrency@@SA?AVSchedulerPolicy@2@XZ + ?GetPolicyValue@SchedulerPolicy@Concurrency@@QBEIW4PolicyElementKey@2@@Z + ?GetProcessorCount@Concurrency@@YAIXZ + ?GetProcessorNodeCount@Concurrency@@YAIXZ + ?GetSchedulerId@Concurrency@@YAIXZ + ?GetSharedTimerQueue@details@Concurrency@@YAPAXXZ + ?Id@Context@Concurrency@@SAIXZ + ?Id@CurrentScheduler@Concurrency@@SAIXZ + ?IsCurrentTaskCollectionCanceling@Context@Concurrency@@SA_NXZ + ?Log2@details@Concurrency@@YAKI@Z + ?Oversubscribe@Context@Concurrency@@SAX_N@Z + ?RegisterShutdownEvent@CurrentScheduler@Concurrency@@SAXPAX@Z + ?ResetDefaultSchedulerPolicy@Scheduler@Concurrency@@SAXXZ + ?ScheduleGroupId@Context@Concurrency@@SAIXZ + ?ScheduleTask@CurrentScheduler@Concurrency@@SAXP6AXPAX@Z0@Z + ?SetConcurrencyLimits@SchedulerPolicy@Concurrency@@QAEXII@Z + ?SetDefaultSchedulerPolicy@Scheduler@Concurrency@@SAXABVSchedulerPolicy@2@@Z + ?SetPolicyValue@SchedulerPolicy@Concurrency@@QAEIW4PolicyElementKey@2@I@Z + ?VirtualProcessorId@Context@Concurrency@@SAIXZ + ?Yield@Context@Concurrency@@SAXXZ + ?_Abort@_StructuredTaskCollection@details@Concurrency@@AAEXXZ + ?_Acquire@_NonReentrantBlockingLock@details@Concurrency@@QAEXXZ + ?_Acquire@_NonReentrantPPLLock@details@Concurrency@@QAEXPAX@Z + ?_Acquire@_ReentrantBlockingLock@details@Concurrency@@QAEXXZ + ?_Acquire@_ReentrantLock@details@Concurrency@@QAEXXZ + ?_Acquire@_ReentrantPPLLock@details@Concurrency@@QAEXPAX@Z + ?_AcquireRead@_ReaderWriterLock@details@Concurrency@@QAEXXZ + ?_AcquireWrite@_ReaderWriterLock@details@Concurrency@@QAEXXZ + ?_Cancel@_StructuredTaskCollection@details@Concurrency@@QAEXXZ + ?_Cancel@_TaskCollection@details@Concurrency@@QAEXXZ + ?_CheckTaskCollection@_UnrealizedChore@details@Concurrency@@IAEXXZ + ?_ConcRT_Assert@details@Concurrency@@YAXPBD0H@Z + ?_ConcRT_CoreAssert@details@Concurrency@@YAXPBD0H@Z + ?_ConcRT_DumpMessage@details@Concurrency@@YAXPB_WZZ + ?_ConcRT_Trace@details@Concurrency@@YAXHPB_WZZ + ?_Copy_str@exception@std@@AAEXPBD@Z + ?_DoYield@?$_SpinWait@$00@details@Concurrency@@IAEXXZ + ?_DoYield@?$_SpinWait@$0A@@details@Concurrency@@IAEXXZ + ?_IsCanceling@_StructuredTaskCollection@details@Concurrency@@QAE_NXZ + ?_IsCanceling@_TaskCollection@details@Concurrency@@QAE_NXZ + ?_Name_base@type_info@@CAPBDPBV1@PAU__type_info_node@@@Z + ?_Name_base_internal@type_info@@CAPBDPBV1@PAU__type_info_node@@@Z + ?_NumberOfSpins@?$_SpinWait@$00@details@Concurrency@@IAEKXZ + ?_NumberOfSpins@?$_SpinWait@$0A@@details@Concurrency@@IAEKXZ + ?_Release@_NonReentrantBlockingLock@details@Concurrency@@QAEXXZ + ?_Release@_NonReentrantPPLLock@details@Concurrency@@QAEXXZ + ?_Release@_ReentrantBlockingLock@details@Concurrency@@QAEXXZ + ?_Release@_ReentrantLock@details@Concurrency@@QAEXXZ + ?_Release@_ReentrantPPLLock@details@Concurrency@@QAEXXZ + ?_ReleaseRead@_ReaderWriterLock@details@Concurrency@@QAEXXZ + ?_ReleaseWrite@_ReaderWriterLock@details@Concurrency@@QAEXXZ + ?_Reset@?$_SpinWait@$00@details@Concurrency@@IAEXXZ + ?_Reset@?$_SpinWait@$0A@@details@Concurrency@@IAEXXZ + ?_RunAndWait@_StructuredTaskCollection@details@Concurrency@@QAG?AW4_TaskCollectionStatus@23@PAV_UnrealizedChore@23@@Z + ?_RunAndWait@_TaskCollection@details@Concurrency@@QAG?AW4_TaskCollectionStatus@23@PAV_UnrealizedChore@23@@Z + ?_Schedule@_StructuredTaskCollection@details@Concurrency@@QAEXPAV_UnrealizedChore@23@@Z + ?_Schedule@_TaskCollection@details@Concurrency@@QAEXPAV_UnrealizedChore@23@@Z + ?_SetSpinCount@?$_SpinWait@$00@details@Concurrency@@QAEXI@Z + ?_SetSpinCount@?$_SpinWait@$0A@@details@Concurrency@@QAEXI@Z + ?_ShouldSpinAgain@?$_SpinWait@$00@details@Concurrency@@IAE_NXZ + ?_ShouldSpinAgain@?$_SpinWait@$0A@@details@Concurrency@@IAE_NXZ + ?_SpinOnce@?$_SpinWait@$00@details@Concurrency@@QAE_NXZ + ?_SpinOnce@?$_SpinWait@$0A@@details@Concurrency@@QAE_NXZ + ?_SpinYield@Context@Concurrency@@SAXXZ + ?_Start@_Timer@details@Concurrency@@IAEXXZ + ?_Stop@_Timer@details@Concurrency@@IAEXXZ + ?_Tidy@exception@std@@AAEXXZ + ?_Trace_ppl_function@Concurrency@@YAXABU_GUID@@EW4ConcRT_EventType@1@@Z + ?_TryAcquire@_NonReentrantBlockingLock@details@Concurrency@@QAE_NXZ + ?_TryAcquire@_ReentrantBlockingLock@details@Concurrency@@QAE_NXZ + ?_TryAcquire@_ReentrantLock@details@Concurrency@@QAE_NXZ + ?_TryAcquireWrite@_ReaderWriterLock@details@Concurrency@@QAE_NXZ + ?_Type_info_dtor@type_info@@CAXPAV1@@Z + ?_Type_info_dtor_internal@type_info@@CAXPAV1@@Z + ?_UnderlyingYield@details@Concurrency@@YAXXZ + ?_ValidateExecute@@YAHP6GHXZ@Z + ?_ValidateRead@@YAHPBXI@Z + ?_ValidateWrite@@YAHPAXI@Z + ?_Value@_SpinCount@details@Concurrency@@SAIXZ + ?__ExceptionPtrAssign@@YAXPAXPBX@Z + ?__ExceptionPtrCompare@@YA_NPBX0@Z + ?__ExceptionPtrCopy@@YAXPAXPBX@Z + ?__ExceptionPtrCopyException@@YAXPAXPBX1@Z + ?__ExceptionPtrCreate@@YAXPAX@Z + ?__ExceptionPtrCurrentException@@YAXPAX@Z + ?__ExceptionPtrDestroy@@YAXPAX@Z + ?__ExceptionPtrRethrow@@YAXPBX@Z + __uncaught_exception + ?_inconsistency@@YAXXZ + ?_invalid_parameter@@YAXPBG00II@Z + ?_is_exception_typeof@@YAHABVtype_info@@PAU_EXCEPTION_POINTERS@@@Z + ?_name_internal_method@type_info@@QBEPBDPAU__type_info_node@@@Z + ?_open@@YAHPBDHH@Z + ?_query_new_handler@@YAP6AHI@ZXZ + ?_query_new_mode@@YAHXZ + ?_set_new_handler@@YAP6AHI@ZH@Z + ?_set_new_handler@@YAP6AHI@ZP6AHI@Z@Z + ?_set_new_mode@@YAHH@Z + ?_set_se_translator@@YAP6AXIPAU_EXCEPTION_POINTERS@@@ZH@Z + ?_set_se_translator@@YAP6AXIPAU_EXCEPTION_POINTERS@@@ZP6AXI0@Z@Z + ?_sopen@@YAHPBDHHH@Z + ?_type_info_dtor_internal_method@type_info@@QAEXXZ + ?_wopen@@YAHPB_WHH@Z + ?_wsopen@@YAHPB_WHHH@Z + ?before@type_info@@QBEHABV1@@Z + ?get_error_code@scheduler_resource_allocation_error@Concurrency@@QBEJXZ + ?lock@critical_section@Concurrency@@QAEXXZ + ?lock@reader_writer_lock@Concurrency@@QAEXXZ + ?lock_read@reader_writer_lock@Concurrency@@QAEXXZ + ?name@type_info@@QBEPBDPAU__type_info_node@@@Z + ?native_handle@critical_section@Concurrency@@QAEAAV12@XZ + ?raw_name@type_info@@QBEPBDXZ + ?reset@event@Concurrency@@QAEXXZ + ?set@event@Concurrency@@QAEXXZ + ?set_new_handler@@YAP6AXXZP6AXXZ@Z + ?set_terminate@@YAP6AXXZH@Z + ?set_terminate@@YAP6AXXZP6AXXZ@Z + ?set_unexpected@@YAP6AXXZH@Z + ?set_unexpected@@YAP6AXXZP6AXXZ@Z + ?swprintf@@YAHPAGIPBGZZ + ?swprintf@@YAHPA_WIPB_WZZ + ?terminate@@YAXXZ ;fixme-causes-trouble-with-version-in-libcmt.lib; + ?try_lock@critical_section@Concurrency@@QAE_NXZ + ?try_lock@reader_writer_lock@Concurrency@@QAE_NXZ + ?try_lock_read@reader_writer_lock@Concurrency@@QAE_NXZ + ?unexpected@@YAXXZ + ?unlock@critical_section@Concurrency@@QAEXXZ + ?unlock@reader_writer_lock@Concurrency@@QAEXXZ + ?vswprintf@@YAHPA_WIPB_WPAD@Z + ?wait@Concurrency@@YAXI@Z + ?wait@event@Concurrency@@QAEII@Z + ?wait_for_multiple@event@Concurrency@@SAIPAPAV12@I_NI@Z + ?what@exception@std@@UBEPBDXZ + $I10_OUTPUT + _CIacos + _CIasin + _CIatan + _CIatan2 + _CIcos + _CIcosh + _CIexp + _CIfmod + _CIlog + _CIlog10 + _CIpow + _CIsin + _CIsinh + _CIsqrt + _CItan + _CItanh + _CRT_RTC_INIT + _CRT_RTC_INITW + _CreateFrameInfo + _CxxThrowException + _EH_prolog + _FindAndUnlinkFrame + _Getdays + _Getmonths + _Gettnames + _HUGE + _IsExceptionObjectToBeDestroyed + _NLG_Dispatch2 + _NLG_Return + _NLG_Return2 + _Strftime + _XcptFilter + __AdjustPointer + __BuildCatchObject + __BuildCatchObjectHelper + __CppXcptFilter + __CxxCallUnwindDelDtor + __CxxCallUnwindDtor + __CxxCallUnwindStdDelDtor + __CxxCallUnwindVecDtor + __CxxDetectRethrow + __CxxExceptionFilter + __CxxFrameHandler + __CxxFrameHandler2 + __CxxFrameHandler3 + __CxxLongjmpUnwind + __CxxQueryExceptionSize + __CxxRegisterExceptionObject + __CxxUnregisterExceptionObject + __DestructExceptionObject + __FrameUnwindFilter + __RTCastToVoid + __RTDynamicCast + __RTtypeid + __STRINGTOLD + __STRINGTOLD_L + __TypeMatch + ___lc_codepage_func + ___lc_collate_cp_func + ___lc_handle_func + ___mb_cur_max_func + ___mb_cur_max_l_func + ___setlc_active_func + ___unguarded_readlc_active_add_func + __argc + __argv + __badioinfo + __clean_type_info_names_internal + __control87_2 + __create_locale + __crtCompareStringA + __crtCompareStringW + __crtLCMapStringA + __crtLCMapStringW + __daylight + __doserrno + __dstbias + ___fls_getvalue@4 + ___fls_setvalue@8 + __fpecode + __free_locale + __get_current_locale + __get_flsindex + ;fixme?; __get_tlsindex + __initenv + __iob_func + __isascii + __iscsym + __iscsymf + __iswcsym + __iswcsymf + __lconv + __lconv_init + __libm_sse2_acos + __libm_sse2_acosf + __libm_sse2_asin + __libm_sse2_asinf + __libm_sse2_atan + __libm_sse2_atan2 + __libm_sse2_atanf + __libm_sse2_cos + __libm_sse2_cosf + __libm_sse2_exp + __libm_sse2_expf + __libm_sse2_log + __libm_sse2_log10 + __libm_sse2_log10f + __libm_sse2_logf + __libm_sse2_pow + __libm_sse2_powf + __libm_sse2_sin + __libm_sse2_sinf + __libm_sse2_tan + __libm_sse2_tanf + __mb_cur_max + ;fixme?; __p___argc + ;fixme?; __p___argv + ;fixme?; __p___initenv + ;fixme?; __p___mb_cur_max + ;fixme?; __p___wargv + ;fixme?; __p___winitenv + ;fixme?; __p__acmdln + ;fixme?; __p__commode + ;fixme?; __p__daylight + ;fixme?; __p__dstbias + ;fixme?; __p__environ + ;fixme?; __p__fmode + ;fixme?; __p__iob + ;fixme?; __p__mbcasemap + ;fixme?; __p__mbctype + ;fixme?; __p__pctype + ;fixme?; __p__pgmptr + ;fixme?; __p__pwctype + ;fixme?; __p__timezone + ;fixme?; __p__tzname + ;fixme?; __p__wcmdln + ;fixme?; __p__wenviron + ;fixme?; __p__wpgmptr + + __pctype_func + __pioinfo + __pwctype_func + __pxcptinfoptrs + __report_gsfailure + __set_app_type + __set_flsgetvalue + __setlc_active + ;fixme?; __setusermatherr + __strncnt + __swprintf_l + __sys_errlist + __sys_nerr + __threadhandle + __threadid + __timezone + __toascii + __tzname + __unDName + __unDNameEx + __unDNameHelper + __unguarded_readlc_active + __vswprintf_l + __wargv + __wcserror + __wcserror_s + __wcsncnt + ;fixme?; __wgetmainargs + __winitenv + _abnormal_termination + _abs64 + _access + _access_s + _acmdln + _aligned_free + _aligned_malloc + _aligned_msize + _aligned_offset_malloc + _aligned_offset_realloc + _aligned_offset_recalloc + _aligned_realloc + _aligned_recalloc + _amsg_exit + _assert + _atodbl + _atodbl_l + _atof_l + _atoflt + _atoflt_l + _atoi64 + _atoi64_l + _atoi_l + _atol_l + _atoldbl + _atoldbl_l + _beep + _beginthread + _beginthreadex + _byteswap_uint64 + _byteswap_ulong + _byteswap_ushort + _c_exit + _cabs + _callnewh + _calloc_crt + _cexit + _cgets + _cgets_s + _cgetws + _cgetws_s + _chdir + _chdrive + _chgsign + _chkesp + _chmod + _chsize + _chsize_s + _clearfp + _close + _commit + ;fixme?; _commode + _configthreadlocale + _control87 + _controlfp + _controlfp_s + _copysign + _cprintf + _cprintf_l + _cprintf_p + _cprintf_p_l + _cprintf_s + _cprintf_s_l + _cputs + _cputws + _creat + _create_locale + _crt_debugger_hook + _cscanf + _cscanf_l + _cscanf_s + _cscanf_s_l + _ctime32 + _ctime32_s + _ctime64 + _ctime64_s + _cwait + _cwprintf + _cwprintf_l + _cwprintf_p + _cwprintf_p_l + _cwprintf_s + _cwprintf_s_l + _cwscanf + _cwscanf_l + _cwscanf_s + _cwscanf_s_l + _daylight + _difftime32 + _difftime64 + _dosmaperr + _dstbias + _dup + _dup2 + _dupenv_s + _ecvt + _ecvt_s + _encoded_null + _endthread + _endthreadex + _environ + _eof + _errno + _except_handler2 + _except_handler3 + ;fixme?; _except_handler4_common + _execl + _execle + _execlp + _execlpe + _execv + _execve + _execvp + _execvpe + _exit + _expand + _fclose_nolock + _fcloseall + _fcvt + _fcvt_s + _fdopen + _fflush_nolock + _fgetchar + _fgetwc_nolock + _fgetwchar + _filbuf + _filelength + _filelengthi64 + _fileno + _findclose + _findfirst32 + _findfirst32i64 + _findfirst64 + _findfirst64i32 + _findnext32 + _findnext32i64 + _findnext64 + _findnext64i32 + _finite + _flsbuf + _flushall + ;fixme?; _fmode + _fpclass + _fpieee_flt + _fpreset + _fprintf_l + _fprintf_p + _fprintf_p_l + _fprintf_s_l + _fputchar + _fputwc_nolock + _fputwchar + _fread_nolock + _fread_nolock_s + _free_locale + _freea + _freea_s + _freefls + _fscanf_l + _fscanf_s_l + _fseek_nolock + _fseeki64 + _fseeki64_nolock + _fsopen + _fstat32 + _fstat32i64 + _fstat64 + _fstat64i32 + _ftell_nolock + _ftelli64 + _ftelli64_nolock + _ftime32 + _ftime32_s + _ftime64 + _ftime64_s + _ftol + _fullpath + _futime32 + _futime64 + _fwprintf_l + _fwprintf_p + _fwprintf_p_l + _fwprintf_s_l + _fwrite_nolock + _fwscanf_l + _fwscanf_s_l + _gcvt + _gcvt_s + _get_current_locale + _get_daylight + _get_doserrno + _get_dstbias + _get_errno + _get_fmode + _get_heap_handle + _get_invalid_parameter_handler + _get_osfhandle + _get_output_format + _get_pgmptr + _get_printf_count_output + _get_purecall_handler + _get_terminate + _get_timezone + _get_tzname + _get_unexpected + _get_wpgmptr + _getc_nolock + _getch + _getch_nolock + _getche + _getche_nolock + _getcwd + _getdcwd + _getdcwd_nolock + _getdiskfree + _getdllprocaddr + _getdrive + _getdrives + _getmaxstdio + _getmbcp + _getpid + _getptd + _getsystime + _getw + _getwch + _getwch_nolock + _getwche + _getwche_nolock + _getws + _getws_s + _global_unwind2 + _gmtime32 + _gmtime32_s + _gmtime64 + _gmtime64_s + _heapadd + _heapchk + _heapmin + _heapset + _heapused + _heapwalk + _hypot + _hypotf + _i64toa + _i64toa_s + _i64tow + _i64tow_s + _initptd + ;fixme?; _initterm + _initterm_e + _inp + _inpd + _inpw + _invalid_parameter + _invalid_parameter_noinfo + _invalid_parameter_noinfo_noreturn + _invoke_watson + _iob + _isalnum_l + _isalpha_l + _isatty + _iscntrl_l + _isctype + _isctype_l + _isdigit_l + _isgraph_l + _isleadbyte_l + _islower_l + _ismbbalnum + _ismbbalnum_l + _ismbbalpha + _ismbbalpha_l + _ismbbgraph + _ismbbgraph_l + _ismbbkalnum + _ismbbkalnum_l + _ismbbkana + _ismbbkana_l + _ismbbkprint + _ismbbkprint_l + _ismbbkpunct + _ismbbkpunct_l + _ismbblead + _ismbblead_l + _ismbbprint + _ismbbprint_l + _ismbbpunct + _ismbbpunct_l + _ismbbtrail + _ismbbtrail_l + _ismbcalnum + _ismbcalnum_l + _ismbcalpha + _ismbcalpha_l + _ismbcdigit + _ismbcdigit_l + _ismbcgraph + _ismbcgraph_l + _ismbchira + _ismbchira_l + _ismbckata + _ismbckata_l + _ismbcl0 + _ismbcl0_l + _ismbcl1 + _ismbcl1_l + _ismbcl2 + _ismbcl2_l + _ismbclegal + _ismbclegal_l + _ismbclower + _ismbclower_l + _ismbcprint + _ismbcprint_l + _ismbcpunct + _ismbcpunct_l + _ismbcspace + _ismbcspace_l + _ismbcsymbol + _ismbcsymbol_l + _ismbcupper + _ismbcupper_l + _ismbslead + _ismbslead_l + _ismbstrail + _ismbstrail_l + _isnan + _isprint_l + _ispunct_l + _isspace_l + _isupper_l + _iswalnum_l + _iswalpha_l + _iswcntrl_l + _iswcsym_l + _iswcsymf_l + _iswctype_l + _iswdigit_l + _iswgraph_l + _iswlower_l + _iswprint_l + _iswpunct_l + _iswspace_l + _iswupper_l + _iswxdigit_l + _isxdigit_l + _itoa + _itoa_s + _itow + _itow_s + _j0 + _j1 + _jn + _kbhit + _lfind + _lfind_s + _loaddll + _local_unwind2 + _local_unwind4 + _localtime32 + _localtime32_s + _localtime64 + _localtime64_s + _lock + _lock_file + _locking + _logb + _longjmpex + _lrotl + _lrotr + _lsearch + _lsearch_s + _lseek + _lseeki64 + _ltoa + _ltoa_s + _ltow + _ltow_s + _makepath + _makepath_s + _malloc_crt + _mbbtombc + _mbbtombc_l + _mbbtype + _mbbtype_l + _mbcasemap + _mbccpy + _mbccpy_l + _mbccpy_s + _mbccpy_s_l + _mbcjistojms + _mbcjistojms_l + _mbcjmstojis + _mbcjmstojis_l + _mbclen + _mbclen_l + _mbctohira + _mbctohira_l + _mbctokata + _mbctokata_l + _mbctolower + _mbctolower_l + _mbctombb + _mbctombb_l + _mbctoupper + _mbctoupper_l + _mbctype + _mblen_l + _mbsbtype + _mbsbtype_l + _mbscat_s + _mbscat_s_l + _mbschr + _mbschr_l + _mbscmp + _mbscmp_l + _mbscoll + _mbscoll_l + _mbscpy_s + _mbscpy_s_l + _mbscspn + _mbscspn_l + _mbsdec + _mbsdec_l + _mbsicmp + _mbsicmp_l + _mbsicoll + _mbsicoll_l + _mbsinc + _mbsinc_l + _mbslen + _mbslen_l + _mbslwr + _mbslwr_l + _mbslwr_s + _mbslwr_s_l + _mbsnbcat + _mbsnbcat_l + _mbsnbcat_s + _mbsnbcat_s_l + _mbsnbcmp + _mbsnbcmp_l + _mbsnbcnt + _mbsnbcnt_l + _mbsnbcoll + _mbsnbcoll_l + _mbsnbcpy + _mbsnbcpy_l + _mbsnbcpy_s + _mbsnbcpy_s_l + _mbsnbicmp + _mbsnbicmp_l + _mbsnbicoll + _mbsnbicoll_l + _mbsnbset + _mbsnbset_l + _mbsnbset_s + _mbsnbset_s_l + _mbsncat + _mbsncat_l + _mbsncat_s + _mbsncat_s_l + _mbsnccnt + _mbsnccnt_l + _mbsncmp + _mbsncmp_l + _mbsncoll + _mbsncoll_l + _mbsncpy + _mbsncpy_l + _mbsncpy_s + _mbsncpy_s_l + _mbsnextc + _mbsnextc_l + _mbsnicmp + _mbsnicmp_l + _mbsnicoll + _mbsnicoll_l + _mbsninc + _mbsninc_l + _mbsnlen + _mbsnlen_l + _mbsnset + _mbsnset_l + _mbsnset_s + _mbsnset_s_l + _mbspbrk + _mbspbrk_l + _mbsrchr + _mbsrchr_l + _mbsrev + _mbsrev_l + _mbsset + _mbsset_l + _mbsset_s + _mbsset_s_l + _mbsspn + _mbsspn_l + _mbsspnp + _mbsspnp_l + _mbsstr + _mbsstr_l + _mbstok + _mbstok_l + _mbstok_s + _mbstok_s_l + _mbstowcs_l + _mbstowcs_s_l + _mbstrlen + _mbstrlen_l + _mbstrnlen + _mbstrnlen_l + _mbsupr + _mbsupr_l + _mbsupr_s + _mbsupr_s_l + _mbtowc_l + _memccpy + _memicmp + _memicmp_l + _mkdir + _mkgmtime32 + _mkgmtime64 + _mktemp + _mktemp_s + _mktime32 + _mktime64 + _msize + _nextafter + _open + _open_osfhandle + _outp + _outpd + _outpw + _pclose + _pctype + _pgmptr + _pipe + _popen + _printf_l + _printf_p + _printf_p_l + _printf_s_l + _purecall + _putch + _putch_nolock + _putenv + _putenv_s + _putw + _putwch + _putwch_nolock + _putws + _pwctype + _read + _realloc_crt + _recalloc + _recalloc_crt + _resetstkoflw + _rmdir + _rmtmp + _rotl + _rotl64 + _rotr + _rotr64 + _scalb + _scanf_l + _scanf_s_l + _scprintf + _scprintf_l + _scprintf_p + _scprintf_p_l + _scwprintf + _scwprintf_l + _scwprintf_p + _scwprintf_p_l + _searchenv + _searchenv_s + _seh_longjmp_unwind4 + _seh_longjmp_unwind + _set_SSE2_enable + _set_abort_behavior + _set_controlfp + _set_doserrno + _set_errno + _set_error_mode + _set_fmode + _set_invalid_parameter_handler + _set_malloc_crt_max_wait + _set_output_format + _set_printf_count_output + _set_purecall_handler + _seterrormode + _setjmp + _setjmp3 + _setmaxstdio + _setmbcp + _setmode + _setsystime + _sleep + _snprintf + _snprintf_c + _snprintf_c_l + _snprintf_l + _snprintf_s + _snprintf_s_l + _snscanf + _snscanf_l + _snscanf_s + _snscanf_s_l + _snwprintf + _snwprintf_l + _snwprintf_s + _snwprintf_s_l + _snwscanf + _snwscanf_l + _snwscanf_s + _snwscanf_s_l + _sopen + _sopen_s + _spawnl + _spawnle + _spawnlp + _spawnlpe + _spawnv + _spawnve + _spawnvp + _spawnvpe + _splitpath + _splitpath_s + _sprintf_l + _sprintf_p + _sprintf_p_l + _sprintf_s_l + _sscanf_l + _sscanf_s_l + _stat32 + _stat32i64 + _stat64 + _stat64i32 + _statusfp + _statusfp2 + _strcoll_l + _strdate + _strdate_s + _strdup + _strerror + _strerror_s + _strftime_l + _stricmp + _stricmp_l + _stricoll + _stricoll_l + _strlwr + _strlwr_l + _strlwr_s + _strlwr_s_l + _strncoll + _strncoll_l + _strnicmp + _strnicmp_l + _strnicoll + _strnicoll_l + _strnset + _strnset_s + _strrev + _strset + _strset_s + _strtime + _strtime_s + _strtod_l + _strtoi64 + _strtoi64_l + _strtol_l + _strtoui64 + _strtoui64_l + _strtoul_l + _strupr + _strupr_l + _strupr_s + _strupr_s_l + _strxfrm_l + _swab + _swprintf + _swprintf_c + _swprintf_c_l + _swprintf_p + _swprintf_p_l + _swprintf_s_l + _swscanf_l + _swscanf_s_l + _sys_errlist + _sys_nerr + _tell + _telli64 + _tempnam + _time32 + _time64 + _timezone + _tolower + _tolower_l + _toupper + _toupper_l + _towlower_l + _towupper_l + _tzname + _tzset + _ui64toa + _ui64toa_s + _ui64tow + _ui64tow_s + _ultoa + _ultoa_s + _ultow + _ultow_s + _umask + _umask_s + _ungetc_nolock + _ungetch + _ungetch_nolock + _ungetwc_nolock + _ungetwch + _ungetwch_nolock + _unlink + _unloaddll + _unlock + _unlock_file + _utime32 + _utime64 + _vcprintf + _vcprintf_l + _vcprintf_p + _vcprintf_p_l + _vcprintf_s + _vcprintf_s_l + _vcwprintf + _vcwprintf_l + _vcwprintf_p + _vcwprintf_p_l + _vcwprintf_s + _vcwprintf_s_l + _vfprintf_l + _vfprintf_p + _vfprintf_p_l + _vfprintf_s_l + _vfwprintf_l + _vfwprintf_p + _vfwprintf_p_l + _vfwprintf_s_l + _vprintf_l + _vprintf_p + _vprintf_p_l + _vprintf_s_l + _vscprintf + _vscprintf_l + _vscprintf_p + _vscprintf_p_l + _vscwprintf + _vscwprintf_l + _vscwprintf_p + _vscwprintf_p_l + _vsnprintf + _vsnprintf_c + _vsnprintf_c_l + _vsnprintf_l + _vsnprintf_s + _vsnprintf_s_l + _vsnwprintf + _vsnwprintf_l + _vsnwprintf_s + _vsnwprintf_s_l + _vsprintf_l + _vsprintf_p + _vsprintf_p_l + _vsprintf_s_l + _vswprintf + _vswprintf_c + _vswprintf_c_l + _vswprintf_l + _vswprintf_p + _vswprintf_p_l + _vswprintf_s_l + _vwprintf_l + _vwprintf_p + _vwprintf_p_l + _vwprintf_s_l + _waccess + _waccess_s + _wasctime + _wasctime_s + _wassert + _wchdir + _wchmod + ;fixme?; _wcmdln + _wcreat + _wcscoll_l + _wcsdup + _wcserror + _wcserror_s + _wcsftime_l + _wcsicmp + _wcsicmp_l + _wcsicoll + _wcsicoll_l + _wcslwr + _wcslwr_l + _wcslwr_s + _wcslwr_s_l + _wcsncoll + _wcsncoll_l + _wcsnicmp + _wcsnicmp_l + _wcsnicoll + _wcsnicoll_l + _wcsnset + _wcsnset_s + _wcsrev + _wcsset + _wcsset_s + _wcstod_l + _wcstoi64 + _wcstoi64_l + _wcstol_l + _wcstombs_l + _wcstombs_s_l + _wcstoui64 + _wcstoui64_l + _wcstoul_l + _wcsupr + _wcsupr_l + _wcsupr_s + _wcsupr_s_l + _wcsxfrm_l + _wctime32 + _wctime32_s + _wctime64 + _wctime64_s + _wctomb_l + _wctomb_s_l + _wctype + _wdupenv_s + _wenviron + _wexecl + _wexecle + _wexeclp + _wexeclpe + _wexecv + _wexecve + _wexecvp + _wexecvpe + _wfdopen + _wfindfirst32 + _wfindfirst32i64 + _wfindfirst64 + _wfindfirst64i32 + _wfindnext32 + _wfindnext32i64 + _wfindnext64 + _wfindnext64i32 + _wfopen + _wfopen_s + _wfreopen + _wfreopen_s + _wfsopen + _wfullpath + _wgetcwd + _wgetdcwd + _wgetdcwd_nolock + _wgetenv + _wgetenv_s + _wmakepath + _wmakepath_s + _wmkdir + _wmktemp + _wmktemp_s + _wopen + _wperror + _wpgmptr + _wpopen + _wprintf_l + _wprintf_p + _wprintf_p_l + _wprintf_s_l + _wputenv + _wputenv_s + _wremove + _wrename + _write + _wrmdir + _wscanf_l + _wscanf_s_l + _wsearchenv + _wsearchenv_s + _wsetlocale + _wsopen + _wsopen_s + _wspawnl + _wspawnle + _wspawnlp + _wspawnlpe + _wspawnv + _wspawnve + _wspawnvp + _wspawnvpe + _wsplitpath + _wsplitpath_s + _wstat32 + _wstat32i64 + _wstat64 + _wstat64i32 + _wstrdate + _wstrdate_s + _wstrtime + _wstrtime_s + _wsystem + _wtempnam + _wtmpnam + _wtmpnam_s + _wtof + _wtof_l + _wtoi + _wtoi64 + _wtoi64_l + _wtoi_l + _wtol + _wtol_l + _wunlink + _wutime32 + _wutime64 + _y0 + _y1 + _yn + abort + abs + acos + asctime + asctime_s + asin + atan + atan2 + atexit + atof + atoi + atol + bsearch + bsearch_s + btowc + calloc + ceil + clearerr + clearerr_s + clock + cos + cosh + div + exit + exp + fabs + fclose + feof + ferror + fflush + fgetc + fgetpos + fgets + fgetwc + fgetws + floor + fmod + fopen + fopen_s + fprintf + fprintf_s + fputc + fputs + fputwc + fputws + fread + fread_s + free + freopen + freopen_s + frexp + fscanf + fscanf_s + fseek + fsetpos + ftell + fwprintf + fwprintf_s + fwrite + fwscanf + fwscanf_s + getc + getchar + getenv + getenv_s + gets + gets_s + getwc + getwchar + is_wctype + isalnum + isalpha + iscntrl + isdigit + isgraph + isleadbyte + islower + isprint + ispunct + isspace + isupper + iswalnum + iswalpha + iswascii + iswcntrl + iswctype + iswdigit + iswgraph + iswlower + iswprint + iswpunct + iswspace + iswupper + iswxdigit + isxdigit + labs + ldexp + ldiv + llabs + lldiv + localeconv + log + log10 + longjmp + malloc + mblen + mbrlen + mbrtowc + mbsrtowcs + mbsrtowcs_s + mbstowcs + mbstowcs_s + mbtowc + memchr + memcmp + memcpy + memcpy_s + memmove + memmove_s + memset + modf + perror + pow + printf + printf_s + putc + putchar + puts + putwc + putwchar + qsort + qsort_s + raise + rand + rand_s + realloc + remove + rename + rewind + scanf + scanf_s + setbuf + setlocale + setvbuf + signal + sin + sinh + sprintf + sprintf_s + sqrt + srand + sscanf + sscanf_s + strcat + strcat_s + strchr + strcmp + strcoll + strcpy + strcpy_s + strcspn + strerror + strerror_s + strftime + strlen + strncat + strncat_s + strncmp + strncpy + strncpy_s + strnlen + strpbrk + strrchr + strspn + strstr + strtod + strtok + strtok_s + strtol + strtoul + strxfrm + swprintf_s + swscanf + swscanf_s + system + tan + tanh + tmpfile + tmpfile_s + tmpnam + tmpnam_s + tolower + toupper + towlower + towupper + ungetc + ungetwc + vfprintf + vfprintf_s + vfwprintf + vfwprintf_s + vprintf + vprintf_s + vsprintf + vsprintf_s + vswprintf_s + vwprintf + vwprintf_s + wcrtomb + wcrtomb_s + wcscat + wcscat_s + wcschr + wcscmp + wcscoll + wcscpy + wcscpy_s + wcscspn + wcsftime + wcslen + wcsncat + wcsncat_s + wcsncmp + wcsncpy + wcsncpy_s + wcsnlen + wcspbrk + wcsrchr + wcsrtombs + wcsrtombs_s + wcsspn + wcsstr + wcstod + wcstok + wcstok_s + wcstol + wcstombs + wcstombs_s + wcstoul + wcsxfrm + wctob + wctomb + wctomb_s + wmemcpy_s + wmemmove_s + wprintf + wprintf_s + wscanf + wscanf_s + diff --git a/src/VBox/Runtime/r3/win/VBoxRT-openssl-1.1plus.def b/src/VBox/Runtime/r3/win/VBoxRT-openssl-1.1plus.def new file mode 100644 index 00000000..8787224f --- /dev/null +++ b/src/VBox/Runtime/r3/win/VBoxRT-openssl-1.1plus.def @@ -0,0 +1,395 @@ +; $Id: VBoxRT-openssl-1.1plus.def $ +;; @file +; IPRT - Windows OpenSSL exports we use outside VBoxRT (keep them few!). +; +; This file is appended to the architecture specific .def file. +; + +; +; Copyright (C) 2009-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + ; ConsoleImpl.cpp uses this when VBOX_OPENSSL_FIPS is enabled. + FIPS_mode + + ; VBoxVRDP.dll - secure.cpp + BIO_free + BIO_new_file + BN_bin2bn + BN_bn2bin + BN_CTX_free + BN_CTX_new + BN_free + BN_mod_exp + BN_new + BN_num_bits + BN_set_word + EVP_PKEY_get0_RSA + i2d_X509 + MD5_Final + MD5_Init + MD5_Update + OBJ_obj2nid + PEM_read_bio_PrivateKey + PEM_read_bio_X509 + RAND_bytes + RC4 + RC4_set_key + RSA_free + RSA_generate_key_ex + RSA_get0_key + RSA_new + SHA1_Final + SHA1_Init + SHA1_Update + X509_free + X509_get_X509_PUBKEY + X509_PUBKEY_get0_param + + ; VBoxVRDP.dll - tcp_vrdp.cpp + BIO_new_socket + BIO_test_flags + OPENSSL_init_ssl + SSL_accept + SSL_CTX_free + SSL_CTX_load_verify_locations + SSL_CTX_new + SSL_CTX_set_verify + SSL_CTX_use_certificate_file + SSL_CTX_use_PrivateKey_file + SSL_free + SSL_get_certificate + SSL_new + SSL_pending + SSL_read + SSL_set_bio + SSL_set_read_ahead + SSL_write + TLSv1_server_method + X509_get_issuer_name + X509_NAME_oneline + + ; VDPluginCrypt.dll (if it wanted to use IPRT) - VDKeyStore.cpp: + EVP_aes_128_xts + EVP_aes_256_xts + EVP_CIPHER_CTX_free + EVP_CIPHER_CTX_new + EVP_DecryptFinal + EVP_DecryptInit + EVP_DecryptUpdate + EVP_EncryptFinal + EVP_EncryptInit + EVP_EncryptUpdate + EVP_MD_size + EVP_sha1 + EVP_sha256 + EVP_sha512 + PKCS5_PBKDF2_HMAC + ;exported above: RAND_bytes + + ; VDPluginCrypt.dll (if it wanted to use IPRT) - VDFilterCrypt.cpp: + ;exported above: EVP_aes_128_xts + ;exported above: EVP_aes_256_xts + ;exported above: EVP_CIPHER_CTX_free + ;exported above: EVP_CIPHER_CTX_new + ;exported above: EVP_DecryptFinal + ;exported above: EVP_DecryptInit + ;exported above: EVP_DecryptUpdate + ;exported above: EVP_EncryptFinal + ;exported above: EVP_EncryptInit + ;exported above: EVP_EncryptUpdate + ;exported above: RAND_bytes + + ; vboxwebsrv needs SSL support. + ASN1_STRING_data + ASN1_STRING_get0_data + ASN1_STRING_to_UTF8 + ;exported above: BIO_free + ;exported above: BIO_new_file + ;exported above: BIO_new_socket + BIO_read + BIO_write + CRYPTO_free + DH_check + DH_free + DH_generate_parameters + DH_generate_parameters_ex + DH_new + ERR_clear_error + ERR_error_string + ERR_error_string_n + ERR_get_error + ERR_peek_error + GENERAL_NAME_free + i2v_GENERAL_NAMES + OpenSSL_version_num + OPENSSL_init_crypto + ;exported above: OPENSSL_init_ssl + OPENSSL_sk_num + OPENSSL_sk_pop_free + OPENSSL_sk_value + PEM_read_bio_DHparams + RAND_load_file + RAND_pseudo_bytes + RAND_seed + RAND_status + ;exported above: RSA_free + RSA_generate_key + ;exported above: SSL_accept + SSL_clear + SSL_connect + SSL_ctrl + SSL_CTX_ctrl + ;exported above: SSL_CTX_free + SSL_CTX_get_cert_store + ;exported above: SSL_CTX_load_verify_locations + ;exported above: SSL_CTX_new + SSL_CTX_set_client_CA_list + SSL_CTX_set_default_passwd_cb + SSL_CTX_set_default_passwd_cb_userdata + SSL_CTX_set_default_verify_paths + SSL_CTX_set_options + SSL_CTX_set_session_id_context + ;exported above: SSL_CTX_set_verify + SSL_CTX_set_verify_depth + SSL_CTX_use_certificate_chain_file + ;exported above: SSL_CTX_use_PrivateKey_file + ;exported above: SSL_free + SSL_get_error + SSL_get_peer_certificate + SSL_get_verify_result + SSL_get1_session + SSL_is_init_finished + SSL_load_client_CA_file + ;exported above: SSL_new + SSL_peek + ;exported above: SSL_read + SSL_SESSION_free + ;exported above: SSL_set_bio + SSL_set_session + SSL_shutdown + SSL_want + ;exported above: SSL_write + TLS_method + ;exported above: X509_free + X509_get_ext_d2i + ;exported above: X509_get_issuer_name + X509_get_subject_name + X509_load_crl_file + X509_LOOKUP_file + X509_NAME_ENTRY_get_data + X509_NAME_get_entry + X509_NAME_get_index_by_NID + ;exported above: X509_NAME_oneline + X509_STORE_add_lookup + X509_STORE_CTX_get_current_cert + X509_STORE_CTX_get_error + X509_STORE_CTX_get_error_depth + X509_STORE_CTX_set_error + X509_STORE_set1_param + X509_STORE_set_flags + X509_verify_cert_error_string + X509_VERIFY_PARAM_free + X509_VERIFY_PARAM_new + X509_VERIFY_PARAM_set_flags + X509V3_conf_free + + ; tstRTBigNum.cpp + BN_div + BN_mul + BN_mod_exp_simple + BN_ucmp + + ; VBox-libtpms + BN_set_flags + BN_clear_free + BN_cmp + BN_dup + BN_sub + BN_add + BN_copy + RAND_add + BN_is_zero + BN_is_one + BN_value_one + BN_CTX_start + BN_CTX_get + BN_CTX_end + BN_mod_add + BN_mod_mul + BN_lshift + BN_mask_bits + BN_rshift + BN_mod_inverse + RSA_size + RSA_set0_key + RSA_get0_factors + RSA_public_encrypt + RSA_private_encrypt + RSA_private_decrypt + RSA_sign + RSA_verify + RSA_padding_add_PKCS1_type_1 + RSA_padding_add_PKCS1_type_2 + RSA_padding_check_PKCS1_type_1 + RSA_padding_check_PKCS1_type_2 + RSA_padding_add_PKCS1_OAEP + RSA_padding_check_PKCS1_OAEP + ERR_get_error_line_data + AES_set_encrypt_key + AES_set_decrypt_key + AES_decrypt + AES_encrypt + AES_cbc_encrypt + AES_ofb128_encrypt + SHA256_Init + SHA256_Update + SHA256_Final + SHA384_Init + SHA384_Update + SHA384_Final + SHA512_Init + SHA512_Update + SHA512_Final + EC_GROUP_free + EC_GROUP_set_generator + EC_GROUP_new_curve_GFp + EC_POINT_new + EC_POINT_free + EC_POINT_clear_free + EC_POINT_set_affine_coordinates + EC_POINT_get_affine_coordinates + EC_POINT_add + EC_POINTs_mul + EC_POINT_mul + Camellia_set_key + Camellia_encrypt + Camellia_decrypt + DES_ecb3_encrypt + DES_set_key_unchecked + + ; VBox-libssh + BIO_ctrl + BIO_new + BIO_new_mem_buf + BIO_s_mem + BN_bn2hex + BN_is_bit_set + d2i_DSA_SIG + d2i_ECDSA_SIG + DH_compute_key + DH_generate_key + DH_get0_key + DH_get0_pqg + DH_set0_key + DH_set0_pqg + DH_size + DSA_free + DSA_generate_key + DSA_generate_parameters_ex + DSA_get0_key + DSA_get0_pqg + DSA_new + DSA_set0_key + DSA_set0_pqg + DSA_SIG_free + DSA_SIG_get0 + DSA_SIG_new + DSA_SIG_set0 + DSA_size + EC_GROUP_cmp + EC_GROUP_get_curve_name + EC_GROUP_get_degree + EC_KEY_dup + EC_KEY_free + EC_KEY_generate_key + EC_KEY_get0_group + EC_KEY_get0_private_key + EC_KEY_get0_public_key + EC_KEY_new_by_curve_name + EC_KEY_set_asn1_flag + EC_KEY_set_private_key + EC_KEY_set_public_key + EC_POINT_cmp + EC_POINT_oct2point + EC_POINT_point2oct + ECDH_compute_key + ECDSA_SIG_free + ECDSA_SIG_get0 + ECDSA_SIG_new + ECDSA_SIG_set0 + EVP_aes_128_cbc + EVP_aes_128_ctr + EVP_aes_128_gcm + EVP_aes_192_cbc + EVP_aes_192_ctr + EVP_aes_256_cbc + EVP_aes_256_ctr + EVP_aes_256_gcm + EVP_CIPHER_CTX_ctrl + EVP_CIPHER_CTX_reset + EVP_CIPHER_CTX_set_padding + EVP_DecryptInit_ex + EVP_DigestFinal + EVP_DigestInit + EVP_DigestInit_ex + EVP_DigestSignFinal + EVP_DigestSignInit + EVP_DigestUpdate + EVP_DigestVerifyFinal + EVP_DigestVerifyInit + EVP_EncryptInit_ex + EVP_MD_CTX_free + EVP_MD_CTX_new + EVP_MD_CTX_reset + EVP_md5 + EVP_PKEY_base_id + EVP_PKEY_free + EVP_PKEY_get1_DSA + EVP_PKEY_get1_EC_KEY + EVP_PKEY_get1_RSA + EVP_PKEY_new + EVP_PKEY_set1_DSA + EVP_PKEY_set1_EC_KEY + EVP_PKEY_set1_RSA + EVP_PKEY_size + EVP_sha384 + HMAC_CTX_free + HMAC_CTX_new + HMAC_Final + HMAC_Init_ex + HMAC_Update + i2d_DSA_SIG + i2d_ECDSA_SIG + OpenSSL_version + PEM_write_bio_PrivateKey + RSA_get0_crt_params + RSA_set0_crt_params + RSA_set0_factors diff --git a/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def b/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def new file mode 100644 index 00000000..ea9407a8 --- /dev/null +++ b/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def @@ -0,0 +1,430 @@ +; $Id: VBoxRT-openssl-3.0.def $ +;; @file +; IPRT - Windows OpenSSL exports we use outside VBoxRT (keep them few!). +; +; This file is appended to the architecture specific .def file. +; + +; +; Copyright (C) 2009-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + ; VBoxVRDP.dll - secure.cpp + BIO_free + BIO_new_file + BN_bin2bn + BN_bn2bin + BN_CTX_free + BN_CTX_new + BN_free + BN_mod_exp + BN_new + BN_num_bits + BN_set_word + EVP_PKEY_get0_RSA + i2d_X509 + MD5_Final + MD5_Init + MD5_Update + OBJ_obj2nid + PEM_read_bio_PrivateKey + PEM_read_bio_X509 + RAND_bytes + RC4 + RC4_set_key + RSA_free + RSA_generate_key_ex + RSA_get0_key + RSA_new + SHA1_Final + SHA1_Init + SHA1_Update + X509_free + X509_get_X509_PUBKEY + X509_PUBKEY_get0_param + + ; VBoxVRDP.dll - tcp_vrdp.cpp + BIO_new_socket + BIO_test_flags + OPENSSL_init_ssl + SSL_accept + SSL_CTX_free + SSL_CTX_load_verify_locations + SSL_CTX_new + SSL_CTX_set_verify + SSL_CTX_use_certificate_file + SSL_CTX_use_PrivateKey_file + SSL_free + SSL_get_certificate + SSL_new + SSL_pending + SSL_read + SSL_set_bio + SSL_set_read_ahead + SSL_write + TLSv1_server_method + X509_get_issuer_name + X509_NAME_oneline + + ; VDPluginCrypt.dll (if it wanted to use IPRT) - VDKeyStore.cpp: + EVP_aes_128_xts + EVP_aes_256_xts + EVP_CIPHER_CTX_free + EVP_CIPHER_CTX_new + EVP_DecryptFinal + EVP_DecryptInit + EVP_DecryptUpdate + EVP_EncryptFinal + EVP_EncryptInit + EVP_EncryptUpdate + EVP_MD_get_size + EVP_sha1 + EVP_sha256 + EVP_sha512 + PKCS5_PBKDF2_HMAC + ;exported above: RAND_bytes + + ; VDPluginCrypt.dll (if it wanted to use IPRT) - VDFilterCrypt.cpp: + ;exported above: EVP_aes_128_xts + ;exported above: EVP_aes_256_xts + ;exported above: EVP_CIPHER_CTX_free + ;exported above: EVP_CIPHER_CTX_new + ;exported above: EVP_DecryptFinal + ;exported above: EVP_DecryptInit + ;exported above: EVP_DecryptUpdate + ;exported above: EVP_EncryptFinal + ;exported above: EVP_EncryptInit + ;exported above: EVP_EncryptUpdate + ;exported above: RAND_bytes + + ; vboxwebsrv needs SSL support. + ASN1_STRING_get0_data + ASN1_STRING_to_UTF8 + ;exported above: BIO_free + ;exported above: BIO_new_file + ;exported above: BIO_new_socket + BIO_read + BIO_write + CRYPTO_free + DH_check + DH_free + DH_generate_parameters + DH_generate_parameters_ex + DH_new + ERR_clear_error + ERR_error_string + ERR_error_string_n + ERR_get_error + ERR_peek_error + GENERAL_NAME_free + i2v_GENERAL_NAMES + OpenSSL_version_num + OPENSSL_init_crypto + ;exported above: OPENSSL_init_ssl + OPENSSL_sk_num + OPENSSL_sk_pop_free + OPENSSL_sk_value + PEM_read_bio_DHparams + RAND_load_file + RAND_seed + RAND_status + ;exported above: RSA_free + RSA_generate_key + ;exported above: SSL_accept + SSL_clear + SSL_connect + SSL_ctrl + SSL_CTX_ctrl + ;exported above: SSL_CTX_free + SSL_CTX_get_cert_store + ;exported above: SSL_CTX_load_verify_locations + ;exported above: SSL_CTX_new + SSL_CTX_set_client_CA_list + SSL_CTX_set_default_passwd_cb + SSL_CTX_set_default_passwd_cb_userdata + SSL_CTX_set_default_verify_paths + SSL_CTX_set_options + SSL_CTX_set_session_id_context + ;exported above: SSL_CTX_set_verify + SSL_CTX_set_verify_depth + SSL_CTX_use_certificate_chain_file + ;exported above: SSL_CTX_use_PrivateKey_file + ;exported above: SSL_free + SSL_get_error + SSL_get1_peer_certificate + SSL_get_verify_result + SSL_get1_session + SSL_is_init_finished + SSL_load_client_CA_file + ;exported above: SSL_new + SSL_peek + ;exported above: SSL_read + SSL_SESSION_free + ;exported above: SSL_set_bio + SSL_set_session + SSL_shutdown + SSL_want + ;exported above: SSL_write + TLS_method + ;exported above: X509_free + X509_get_ext_d2i + ;exported above: X509_get_issuer_name + X509_get_subject_name + X509_load_crl_file + X509_LOOKUP_file + X509_NAME_ENTRY_get_data + X509_NAME_get_entry + X509_NAME_get_index_by_NID + ;exported above: X509_NAME_oneline + X509_STORE_add_lookup + X509_STORE_CTX_get_current_cert + X509_STORE_CTX_get_error + X509_STORE_CTX_get_error_depth + X509_STORE_CTX_set_error + X509_STORE_set1_param + X509_STORE_set_flags + X509_verify_cert_error_string + X509_VERIFY_PARAM_free + X509_VERIFY_PARAM_new + X509_VERIFY_PARAM_set_flags + X509V3_conf_free + + ; tstRTBigNum.cpp + BN_div + BN_mul + BN_mod_exp_simple + BN_ucmp + + ; VBox-libtpms + BN_set_flags + BN_clear_free + BN_cmp + BN_dup + BN_sub + BN_add + BN_copy + RAND_add + BN_is_zero + BN_is_one + BN_value_one + BN_CTX_start + BN_CTX_get + BN_CTX_end + BN_mod_add + BN_mod_mul + BN_lshift + BN_mask_bits + BN_rshift + BN_mod_inverse + RSA_size + RSA_set0_key + RSA_get0_factors + RSA_public_encrypt + RSA_private_encrypt + RSA_private_decrypt + RSA_sign + RSA_verify + RSA_padding_add_PKCS1_type_1 + RSA_padding_add_PKCS1_type_2 + RSA_padding_check_PKCS1_type_1 + RSA_padding_check_PKCS1_type_2 + RSA_padding_add_PKCS1_OAEP + RSA_padding_check_PKCS1_OAEP + ERR_get_error_line_data + AES_set_encrypt_key + AES_set_decrypt_key + AES_decrypt + AES_encrypt + AES_cbc_encrypt + AES_ofb128_encrypt + SHA256_Init + SHA256_Update + SHA256_Final + SHA384_Init + SHA384_Update + SHA384_Final + SHA512_Init + SHA512_Update + SHA512_Final + EC_GROUP_free + EC_GROUP_set_generator + EC_GROUP_new_curve_GFp + EC_POINT_new + EC_POINT_free + EC_POINT_clear_free + EC_POINT_set_affine_coordinates + EC_POINT_get_affine_coordinates + EC_POINT_add + EC_POINTs_mul + EC_POINT_mul + Camellia_set_key + Camellia_encrypt + Camellia_decrypt + DES_ecb3_encrypt + DES_set_key_unchecked + + ; VBox-libssh + BIO_ctrl + BIO_new + BIO_new_mem_buf + BIO_s_mem + BN_bn2hex + BN_is_bit_set + d2i_DSA_SIG + d2i_ECDSA_SIG + DH_compute_key + DH_generate_key + DH_get0_key + DH_get0_pqg + DH_set0_key + DH_set0_pqg + DH_size + DSA_free + DSA_generate_key + DSA_generate_parameters_ex + DSA_get0_key + DSA_get0_pqg + DSA_new + DSA_set0_key + DSA_set0_pqg + DSA_SIG_free + DSA_SIG_get0 + DSA_SIG_new + DSA_SIG_set0 + DSA_size + EC_GROUP_cmp + EC_GROUP_get_curve_name + EC_GROUP_get_degree + EC_KEY_dup + EC_KEY_free + EC_KEY_generate_key + EC_KEY_get0_group + EC_KEY_get0_private_key + EC_KEY_get0_public_key + EC_KEY_new_by_curve_name + EC_KEY_set_asn1_flag + EC_KEY_set_private_key + EC_KEY_set_public_key + EC_POINT_cmp + EC_POINT_oct2point + EC_POINT_point2oct + ECDH_compute_key + ECDSA_SIG_free + ECDSA_SIG_get0 + ECDSA_SIG_new + ECDSA_SIG_set0 + EVP_aes_128_cbc + EVP_aes_128_ctr + EVP_aes_128_gcm + EVP_aes_192_cbc + EVP_aes_192_ctr + EVP_aes_256_cbc + EVP_aes_256_ctr + EVP_aes_256_gcm + EVP_CIPHER_CTX_ctrl + EVP_CIPHER_CTX_reset + EVP_CIPHER_CTX_set_padding + EVP_DecryptInit_ex + EVP_DigestFinal + EVP_DigestInit + EVP_DigestInit_ex + EVP_DigestSignFinal + EVP_DigestSignInit + EVP_DigestSignUpdate + EVP_DigestUpdate + EVP_DigestVerifyFinal + EVP_DigestVerifyInit + EVP_DigestVerifyUpdate + EVP_EncryptInit_ex + EVP_MD_CTX_free + EVP_MD_CTX_new + EVP_MD_CTX_reset + EVP_md5 + EVP_PKEY_get_base_id + EVP_PKEY_free + EVP_PKEY_get1_DSA + EVP_PKEY_get1_EC_KEY + EVP_PKEY_get1_RSA + EVP_PKEY_new + EVP_PKEY_set1_DSA + EVP_PKEY_set1_EC_KEY + EVP_PKEY_set1_RSA + EVP_PKEY_get_size + EVP_sha384 + HMAC_CTX_free + HMAC_CTX_new + HMAC_Final + HMAC_Init_ex + HMAC_Update + i2d_DSA_SIG + i2d_ECDSA_SIG + OpenSSL_version + PEM_write_bio_PrivateKey + RSA_get0_crt_params + RSA_set0_crt_params + RSA_set0_factors + ; since 0.10.5 also: + EC_KEY_up_ref + ENGINE_by_id + ENGINE_free + ENGINE_init + ENGINE_load_builtin_engines + EVP_default_properties_is_fips_enabled + EVP_PKEY_CTX_free + EVP_PKEY_CTX_new_from_name + EVP_PKEY_CTX_new_from_pkey + EVP_PKEY_CTX_set_params + EVP_PKEY_derive + EVP_PKEY_derive_init + EVP_PKEY_derive_set_peer + EVP_PKEY_eq + EVP_PKEY_fromdata + EVP_PKEY_fromdata_init + EVP_PKEY_generate + EVP_PKEY_get_bits + EVP_PKEY_get_bn_param + EVP_PKEY_keygen_init + EVP_PKEY_new_mac_key + EVP_PKEY_paramgen_init + EVP_PKEY_set_bn_param + EVP_PKEY_todata + EVP_PKEY_up_ref + OSSL_PARAM_BLD_free + OSSL_PARAM_BLD_new + OSSL_PARAM_BLD_push_BN + OSSL_PARAM_BLD_to_param + OSSL_PARAM_construct_end + OSSL_PARAM_construct_int + OSSL_PARAM_construct_uint + OSSL_PARAM_free + OSSL_PARAM_get_BN + OSSL_PARAM_locate_const + OSSL_PARAM_merge + diff --git a/src/VBox/Runtime/r3/win/VBoxRT-win32.def b/src/VBox/Runtime/r3/win/VBoxRT-win32.def new file mode 100644 index 00000000..261b3ce1 --- /dev/null +++ b/src/VBox/Runtime/r3/win/VBoxRT-win32.def @@ -0,0 +1,62 @@ +; $Id: VBoxRT-win32.def $ +;; @file +; IPRT - Win32 ASM exports. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +EXPORTS + ASMMultU64ByU32DivByU32 + + RTTimeNanoTSLegacySyncInvarNoDelta + RTTimeNanoTSLFenceSyncInvarNoDelta + RTTimeNanoTSLegacyAsyncUseApicId + RTTimeNanoTSLegacyAsyncUseRdtscp + RTTimeNanoTSLegacyAsyncUseIdtrLim + RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId + RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp + RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim + RTTimeNanoTSLFenceAsyncUseApicId + RTTimeNanoTSLFenceAsyncUseRdtscp + RTTimeNanoTSLFenceAsyncUseIdtrLim + RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId + RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp + RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim + + RTStrMemFind32 + + ; Export aliases for noexcept affected methods. + ?setConsumerCallback@RTCRestBinaryResponse@@QAEXP6AHPAV1@PBXII_K2@_EPAX@Z=?setConsumerCallback@RTCRestBinaryResponse@@QAEXP6AHPAV1@PBXII_K2@ZPAX@Z ; before-noexcept ; (public: void __thiscall RTCRestBinaryResponse::setConsumerCallback(int (__cdecl*)(class RTCRestBinaryResponse *,void const *,unsigned int,unsigned int,unsigned __int64,unsigned __int64) noexcept,void *)) + ?setConsumerCallback@RTCRestBinaryResponse@@QAEXP6AHPAV1@PBXII_K2@ZPAX@Z=?setConsumerCallback@RTCRestBinaryResponse@@QAEXP6AHPAV1@PBXII_K2@_EPAX@Z ; after-noexcept ; (public: void __thiscall RTCRestBinaryResponse::setConsumerCallback(int (__cdecl*)(class RTCRestBinaryResponse *,void const *,unsigned int,unsigned int,unsigned __int64,unsigned __int64),void *)) + ?setProducerCallback@RTCRestBinaryParameter@@QAEXP6AHPAV1@PAXI_KPAI@_E12@Z=?setProducerCallback@RTCRestBinaryParameter@@QAEXP6AHPAV1@PAXI_KPAI@Z12@Z ; before-noexcept ; (public: void __thiscall RTCRestBinaryParameter::setProducerCallback(int (__cdecl*)(class RTCRestBinaryParameter *,void *,unsigned int,unsigned __int64,unsigned int *) noexcept,void *,unsigned __int64)) + ?setProducerCallback@RTCRestBinaryParameter@@QAEXP6AHPAV1@PAXI_KPAI@Z12@Z=?setProducerCallback@RTCRestBinaryParameter@@QAEXP6AHPAV1@PAXI_KPAI@_E12@Z ; after-noexcept ; (public: void __thiscall RTCRestBinaryParameter::setProducerCallback(int (__cdecl*)(class RTCRestBinaryParameter *,void *,unsigned int,unsigned __int64,unsigned int *),void *,unsigned __int64)) + diff --git a/src/VBox/Runtime/r3/win/VBoxRT-win64.def b/src/VBox/Runtime/r3/win/VBoxRT-win64.def new file mode 100644 index 00000000..e3a83833 --- /dev/null +++ b/src/VBox/Runtime/r3/win/VBoxRT-win64.def @@ -0,0 +1,74 @@ +; $Id: VBoxRT-win64.def $ +;; @file +; IPRT - Win64 ASM exports. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +EXPORTS + ASMAtomicBitClear + ASMAtomicBitToggle + ASMAtomicBitTestAndToggle + ASMBitFirstClear + ASMBitFirstSet + ASMAtomicReadU64 + ASMAtomicXchgU8 + ASMAtomicXchgU16 + ASMGetFlags + ASMProbeReadByte + ASMSetFlags + ASMMultU64ByU32DivByU32 + ASMNopPause + + RTTimeNanoTSLegacySyncInvarNoDelta + RTTimeNanoTSLFenceSyncInvarNoDelta + RTTimeNanoTSLegacyAsyncUseApicId + RTTimeNanoTSLegacyAsyncUseRdtscp + RTTimeNanoTSLegacyAsyncUseIdtrLim + RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId + RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp + RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim + RTTimeNanoTSLFenceAsyncUseApicId + RTTimeNanoTSLFenceAsyncUseRdtscp + RTTimeNanoTSLFenceAsyncUseIdtrLim + RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId + RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp + RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim + + RTStrMemFind32 + + ; Export aliases for noexcept affected methods. + ?setConsumerCallback@RTCRestBinaryResponse@@QEAAXP6AHPEAV1@PEBX_KI22@_EPEAX@Z=?setConsumerCallback@RTCRestBinaryResponse@@QEAAXP6AHPEAV1@PEBX_KI22@ZPEAX@Z ; before-noexcept ; (public: void __cdecl RTCRestBinaryResponse::setConsumerCallback(int (__cdecl*)(class RTCRestBinaryResponse * __ptr64,void const * __ptr64,unsigned __int64,unsigned int,unsigned __int64,unsigned __int64) noexcept,void * __ptr64) __ptr64 + ?setConsumerCallback@RTCRestBinaryResponse@@QEAAXP6AHPEAV1@PEBX_KI22@ZPEAX@Z=?setConsumerCallback@RTCRestBinaryResponse@@QEAAXP6AHPEAV1@PEBX_KI22@_EPEAX@Z ; after-noexcept ; (public: void __cdecl RTCRestBinaryResponse::setConsumerCallback(int (__cdecl*)(class RTCRestBinaryResponse * __ptr64,void const * __ptr64,unsigned __int64,unsigned int,unsigned __int64,unsigned __int64),void * __ptr64) __ptr64) + ?setProducerCallback@RTCRestBinaryParameter@@QEAAXP6AHPEAV1@PEAX_K2PEA_K@_E12@Z=?setProducerCallback@RTCRestBinaryParameter@@QEAAXP6AHPEAV1@PEAX_K2PEA_K@Z12@Z ; before-noexcept ; (public: void __cdecl RTCRestBinaryParameter::setProducerCallback(int (__cdecl*)(class RTCRestBinaryParameter * __ptr64,void * __ptr64,unsigned __int64,unsigned __int64,unsigned __int64 * __ptr64) noexcept,void * __ptr64,unsigned __int64) __ptr64) + ?setProducerCallback@RTCRestBinaryParameter@@QEAAXP6AHPEAV1@PEAX_K2PEA_K@Z12@Z=?setProducerCallback@RTCRestBinaryParameter@@QEAAXP6AHPEAV1@PEAX_K2PEA_K@_E12@Z ; after-noexcept ; (public: void __cdecl RTCRestBinaryParameter::setProducerCallback(int (__cdecl*)(class RTCRestBinaryParameter * __ptr64,void * __ptr64,unsigned __int64,unsigned __int64,unsigned __int64 * __ptr64),void * __ptr64,unsigned __int64) __ptr64) + diff --git a/src/VBox/Runtime/r3/win/alloc-win.cpp b/src/VBox/Runtime/r3/win/alloc-win.cpp new file mode 100644 index 00000000..a9ebe7f0 --- /dev/null +++ b/src/VBox/Runtime/r3/win/alloc-win.cpp @@ -0,0 +1,210 @@ +/* $Id: alloc-win.cpp $ */ +/** @file + * IPRT - Memory Allocation, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef IPRT_NO_CRT +# define USE_VIRTUAL_ALLOC +#endif +#define LOG_GROUP RTLOGGROUP_MEM +#include <iprt/win/windows.h> + +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/errcore.h> + +#ifndef USE_VIRTUAL_ALLOC +# include <malloc.h> +#endif + + +RTDECL(void *) RTMemPageAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF_PV(pszTag); + +#ifdef USE_VIRTUAL_ALLOC + void *pv = VirtualAlloc(NULL, RT_ALIGN_Z(cb, PAGE_SIZE), MEM_COMMIT, PAGE_READWRITE); +#else + void *pv = _aligned_malloc(RT_ALIGN_Z(cb, PAGE_SIZE), PAGE_SIZE); +#endif + AssertMsg(pv, ("cb=%d lasterr=%d\n", cb, GetLastError())); + return pv; +} + + +RTDECL(void *) RTMemPageAllocExTag(size_t cb, uint32_t fFlags, const char *pszTag) RT_NO_THROW_DEF +{ + size_t const cbAligned = RT_ALIGN_Z(cb, PAGE_SIZE); + RT_NOREF_PV(pszTag); + AssertReturn(!(fFlags & ~RTMEMPAGEALLOC_F_VALID_MASK), NULL); + +#ifdef USE_VIRTUAL_ALLOC + void *pv = VirtualAlloc(NULL, cbAligned, MEM_COMMIT, PAGE_READWRITE); +#else + void *pv = _aligned_malloc(cbAligned, PAGE_SIZE); +#endif + AssertMsgReturn(pv, ("cb=%d lasterr=%d\n", cb, GetLastError()), NULL); + + if (fFlags & RTMEMPAGEALLOC_F_ADVISE_LOCKED) + { + /** @todo check why we get ERROR_WORKING_SET_QUOTA here. */ + BOOL const fOkay = VirtualLock(pv, cbAligned); + AssertMsg(fOkay || GetLastError() == ERROR_WORKING_SET_QUOTA, ("pv=%p cb=%d lasterr=%d\n", pv, cb, GetLastError())); + NOREF(fOkay); + } + + if (fFlags & RTMEMPAGEALLOC_F_ZERO) + RT_BZERO(pv, cbAligned); + + return pv; +} + + +RTDECL(void *) RTMemPageAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF_PV(pszTag); + +#ifdef USE_VIRTUAL_ALLOC + void *pv = VirtualAlloc(NULL, RT_ALIGN_Z(cb, PAGE_SIZE), MEM_COMMIT, PAGE_READWRITE); +#else + void *pv = _aligned_malloc(RT_ALIGN_Z(cb, PAGE_SIZE), PAGE_SIZE); +#endif + if (pv) + { + memset(pv, 0, RT_ALIGN_Z(cb, PAGE_SIZE)); + return pv; + } + AssertMsgFailed(("cb=%d lasterr=%d\n", cb, GetLastError())); + return NULL; +} + + +RTDECL(void) RTMemPageFree(void *pv, size_t cb) RT_NO_THROW_DEF +{ + RT_NOREF_PV(cb); + + if (pv) + { +#ifdef USE_VIRTUAL_ALLOC + if (!VirtualFree(pv, 0, MEM_RELEASE)) + AssertMsgFailed(("pv=%p lasterr=%d\n", pv, GetLastError())); +#else + _aligned_free(pv); +#endif + } +} + + +RTDECL(int) RTMemProtect(void *pv, size_t cb, unsigned fProtect) RT_NO_THROW_DEF +{ + /* + * Validate input. + */ + if (cb == 0) + { + AssertMsgFailed(("!cb\n")); + return VERR_INVALID_PARAMETER; + } + if (fProtect & ~(RTMEM_PROT_NONE | RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC)) + { + AssertMsgFailed(("fProtect=%#x\n", fProtect)); + return VERR_INVALID_PARAMETER; + } + + /* + * Convert the flags. + */ + int fProt; + Assert(!RTMEM_PROT_NONE); + switch (fProtect & (RTMEM_PROT_NONE | RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC)) + { + case RTMEM_PROT_NONE: + fProt = PAGE_NOACCESS; + break; + + case RTMEM_PROT_READ: + fProt = PAGE_READONLY; + break; + + case RTMEM_PROT_READ | RTMEM_PROT_WRITE: + fProt = PAGE_READWRITE; + break; + + case RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC: + fProt = PAGE_EXECUTE_READWRITE; + break; + + case RTMEM_PROT_READ | RTMEM_PROT_EXEC: + fProt = PAGE_EXECUTE_READWRITE; + break; + + case RTMEM_PROT_WRITE: + fProt = PAGE_READWRITE; + break; + + case RTMEM_PROT_WRITE | RTMEM_PROT_EXEC: + fProt = PAGE_EXECUTE_READWRITE; + break; + + case RTMEM_PROT_EXEC: + fProt = PAGE_EXECUTE_READWRITE; + break; + + /* If the compiler had any brains it would warn about this case. */ + default: + AssertMsgFailed(("fProtect=%#x\n", fProtect)); + return VERR_INTERNAL_ERROR; + } + + /* + * Align the request. + */ + cb += (uintptr_t)pv & PAGE_OFFSET_MASK; + pv = (void *)((uintptr_t)pv & ~(uintptr_t)PAGE_OFFSET_MASK); + + /* + * Change the page attributes. + */ + DWORD fFlags = 0; + if (VirtualProtect(pv, cb, fProt, &fFlags)) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + diff --git a/src/VBox/Runtime/r3/win/allocex-win.cpp b/src/VBox/Runtime/r3/win/allocex-win.cpp new file mode 100644 index 00000000..dca6d053 --- /dev/null +++ b/src/VBox/Runtime/r3/win/allocex-win.cpp @@ -0,0 +1,133 @@ +/* $Id: allocex-win.cpp $ */ +/** @file + * IPRT - Memory Allocation, Extended Alloc Workers, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define RTMEM_NO_WRAP_TO_EF_APIS +#include <iprt/mem.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/param.h> +#include "../allocex.h" + +#include <iprt/win/windows.h> + + +static int rtMemAllocExInRange(size_t cbAlloc, uint32_t fFlags, void **ppv, uintptr_t uAddr, uintptr_t uAddrLast) +{ + /* + * Try with every possible address hint since the possible range is very limited. + */ + DWORD fPageProt = (fFlags & RTMEMALLOCEX_FLAGS_EXEC ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); + while (uAddr <= uAddrLast) + { + MEMORY_BASIC_INFORMATION MemInfo; + SIZE_T cbRange = VirtualQuery((void *)uAddr, &MemInfo, sizeof(MemInfo)); + AssertReturn(cbRange == sizeof(MemInfo), VERR_NOT_SUPPORTED); + Assert(MemInfo.RegionSize > 0); + + if ( MemInfo.State == MEM_FREE + && MemInfo.RegionSize >= cbAlloc) + { + void *pv = VirtualAlloc((void *)uAddr, cbAlloc, MEM_RESERVE | MEM_COMMIT, fPageProt); + if ((uintptr_t)pv == uAddr) + { + *ppv = pv; + return VINF_SUCCESS; + } + AssertStmt(!pv, VirtualFree(pv, cbAlloc, MEM_RELEASE)); + } + + /* skip ahead */ + uintptr_t uAddrNext = (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize; + if (uAddrNext <= uAddr) + break; + uAddr += uAddrNext; + } + + return VERR_NO_MEMORY; +} + + +DECLHIDDEN(int) rtMemAllocEx16BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv) +{ + cbAlloc = RT_ALIGN_Z(cbAlloc, PAGE_SIZE); + AssertReturn(cbAlloc <= _64K - PAGE_SIZE, VERR_NO_MEMORY); + + /* Seems this doesn't work on W7/64... */ + return rtMemAllocExInRange(cbAlloc, fFlags, ppv, PAGE_SIZE, _64K - cbAlloc); +} + + +DECLHIDDEN(int) rtMemAllocEx32BitReach(size_t cbAlloc, uint32_t fFlags, void **ppv) +{ + cbAlloc = RT_ALIGN_Z(cbAlloc, PAGE_SIZE); + AssertReturn(cbAlloc <= _2G+_1G, VERR_NO_MEMORY); + + /* + * Just try first. + */ + DWORD fPageProt = (fFlags & RTMEMALLOCEX_FLAGS_EXEC ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); + void *pv = VirtualAlloc(NULL, cbAlloc, MEM_RESERVE | MEM_COMMIT, fPageProt); + if (!pv) + return VERR_NO_MEMORY; + if ((uintptr_t)pv + cbAlloc - 1 < _4G) + { + *ppv = pv; + return VINF_SUCCESS; + } + VirtualFree(pv, cbAlloc, MEM_RELEASE); + + /* + * No luck, do address scan based allocation. + */ + return rtMemAllocExInRange(cbAlloc, fFlags, ppv, _64K, _4G - cbAlloc); +} + + +DECLHIDDEN(void) rtMemFreeExYyBitReach(void *pv, size_t cb, uint32_t fFlags) +{ + RT_NOREF_PV(fFlags); + + BOOL fRc = VirtualFree(pv, cb, MEM_RELEASE); + Assert(fRc); RT_NOREF_PV(fRc); +} + diff --git a/src/VBox/Runtime/r3/win/dir-win.cpp b/src/VBox/Runtime/r3/win/dir-win.cpp new file mode 100644 index 00000000..f0a193ff --- /dev/null +++ b/src/VBox/Runtime/r3/win/dir-win.cpp @@ -0,0 +1,169 @@ +/* $Id: dir-win.cpp $ */ +/** @file + * IPRT - Directory, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/win/windows.h> + +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include "internal/fs.h" +#include "internal/path.h" + + + +RTDECL(int) RTDirCreate(const char *pszPath, RTFMODE fMode, uint32_t fCreate) +{ + /* + * Validate the file mode. + */ + int rc; + fMode = rtFsModeNormalize(fMode, pszPath, 0, RTFS_TYPE_DIRECTORY); + if (rtFsModeIsValidPermissions(fMode)) + { + /* + * Convert to UTF-16. + */ + PRTUTF16 pwszString; + rc = RTPathWinFromUtf8(&pwszString, pszPath, 0 /*fFlags*/); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /* + * Create the directory. + */ + if (CreateDirectoryW((LPCWSTR)pwszString, NULL)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + + /* + * Turn off indexing of directory through Windows Indexing Service + */ + /** @todo This FILE_ATTRIBUTE_NOT_CONTENT_INDEXED hack (for .VDI files, + * really) may cause failures on samba shares. That really sweet and + * need to be addressed differently. We shouldn't be doing this + * unless the caller actually asks for it, must less returning failure, + * for crying out loud! This is only important a couple of places in + * main, if important is the right way to put it... */ + if ( RT_SUCCESS(rc) + && !(fCreate & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET)) + { + if ( SetFileAttributesW((LPCWSTR)pwszString, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) + || (fCreate & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL) ) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + + RTPathWinFree(pwszString); + } + } + else + { + AssertMsgFailed(("Invalid file mode! %RTfmode\n", fMode)); + rc = VERR_INVALID_FMODE; + } + + LogFlow(("RTDirCreate(%p:{%s}, %RTfmode): returns %Rrc\n", pszPath, pszPath, fMode, rc)); + return rc; +} + + +RTDECL(int) RTDirRemove(const char *pszPath) +{ + /* + * Convert to UTF-16. + */ + PRTUTF16 pwszString; + int rc = RTPathWinFromUtf8(&pwszString, pszPath, 0 /*fFlags*/); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /* + * Remove the directory. + */ + if (RemoveDirectoryW((LPCWSTR)pwszString)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + + RTPathWinFree(pwszString); + } + + LogFlow(("RTDirRemove(%p:{%s}): returns %Rrc\n", pszPath, pszPath, rc)); + return rc; +} + + +RTDECL(int) RTDirFlush(const char *pszPath) +{ + RT_NOREF_PV(pszPath); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTDirRename(const char *pszSrc, const char *pszDst, unsigned fRename) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + AssertMsgReturn(*pszSrc, ("%p\n", pszSrc), VERR_INVALID_PARAMETER); + AssertMsgReturn(*pszDst, ("%p\n", pszDst), VERR_INVALID_PARAMETER); + AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER); + + /* + * Call the worker. + */ + int rc = rtPathWin32MoveRename(pszSrc, pszDst, + fRename & RTPATHRENAME_FLAGS_REPLACE ? MOVEFILE_REPLACE_EXISTING : 0, + RTFS_TYPE_DIRECTORY); + + LogFlow(("RTDirRename(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", pszSrc, pszSrc, pszDst, pszDst, fRename, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/direnum-win.cpp b/src/VBox/Runtime/r3/win/direnum-win.cpp new file mode 100644 index 00000000..80d36864 --- /dev/null +++ b/src/VBox/Runtime/r3/win/direnum-win.cpp @@ -0,0 +1,404 @@ +/* $Id: direnum-win.cpp $ */ +/** @file + * IPRT - Directory Enumeration, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/win/windows.h> + +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include "internal/fs.h" +#include "internal/dir.h" + + +size_t rtDirNativeGetStructSize(const char *pszPath) +{ + NOREF(pszPath); + return sizeof(RTDIRINTERNAL); +} + + +int rtDirNativeOpen(PRTDIRINTERNAL pDir, uintptr_t hRelativeDir, void *pvNativeRelative)) +{ + RT_NOREF(hRelativeDir, pvNativeRelative); + + /* + * Setup the search expression. + * + * pszPathBuf is pointing to the return 4K return buffer for the RTPathReal() + * call in rtDirOpenCommon(), so all we gota do is check that we don't overflow + * it when adding the wildcard expression. + */ +/** @todo the pszPathBuf argument was removed in order to support paths longer than RTPATH_MAX. Rewrite this code. */ + size_t cbExpr; + const char *pszExpr; + if (pDir->enmFilter == RTDIRFILTER_WINNT) + { + pszExpr = pDir->pszFilter; + cbExpr = pDir->cchFilter + 1; + } + else + { + pszExpr = "*"; + cbExpr = sizeof("*"); + } + if (pDir->cchPath + cbExpr > RTPATH_MAX) + return VERR_FILENAME_TOO_LONG; + memcpy(pszPathBuf + pDir->cchPath, pszExpr, cbExpr); + + + /* + * Attempt opening the search. + */ + PRTUTF16 pwszName; + int rc = RTPathWinFromUtf8(pwszPathBuf, &pwszName, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + pDir->hDir = FindFirstFileW((LPCWSTR)pwszName, &pDir->Data); + if (pDir->hDir != INVALID_HANDLE_VALUE) + pDir->fDataUnread = true; + else + { + DWORD dwErr = GetLastError(); + /* Theoretical case of an empty directory or more normal case of no matches. */ + if ( dwErr == ERROR_FILE_NOT_FOUND + || dwErr == ERROR_NO_MORE_FILES /* ???*/) + pDir->fDataUnread = false; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + RTPathWinFree(pwszName); + } + + return rc; +} + + +RTDECL(int) RTDirClose(PRTDIRINTERNAL pDir) +{ + /* + * Validate input. + */ + if (!pDir) + return VERR_INVALID_PARAMETER; + if (pDir->u32Magic != RTDIR_MAGIC) + { + AssertMsgFailed(("Invalid pDir=%p\n", pDir)); + return VERR_INVALID_PARAMETER; + } + + /* + * Close the handle. + */ + pDir->u32Magic++; + if (pDir->hDir != INVALID_HANDLE_VALUE) + { + BOOL fRc = FindClose(pDir->hDir); + Assert(fRc); + pDir->hDir = INVALID_HANDLE_VALUE; + } + RTStrFree(pDir->pszName); + pDir->pszName = NULL; + RTMemFree(pDir); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTDirRead(RTDIR hDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry) +{ + PPRTDIRINTERNAL pDir = hDir; + + /* + * Validate input. + */ + if (!pDir || pDir->u32Magic != RTDIR_MAGIC) + { + AssertMsgFailed(("Invalid pDir=%p\n", pDir)); + return VERR_INVALID_PARAMETER; + } + if (!pDirEntry) + { + AssertMsgFailed(("Invalid pDirEntry=%p\n", pDirEntry)); + return VERR_INVALID_PARAMETER; + } + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + cbDirEntry = *pcbDirEntry; + if (cbDirEntry < RT_UOFFSETOF(RTDIRENTRY, szName[2])) + { + AssertMsgFailed(("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRY, szName[2]))); + return VERR_INVALID_PARAMETER; + } + } + + /* + * Fetch data? + */ + if (!pDir->fDataUnread) + { + RTStrFree(pDir->pszName); + pDir->pszName = NULL; + + BOOL fRc = FindNextFileW(pDir->hDir, &pDir->Data); + if (!fRc) + { + int iErr = GetLastError(); + if (pDir->hDir == INVALID_HANDLE_VALUE || iErr == ERROR_NO_MORE_FILES) + return VERR_NO_MORE_FILES; + return RTErrConvertFromWin32(iErr); + } + } + + /* + * Convert the filename to UTF-8. + */ + if (!pDir->pszName) + { + int rc = RTUtf16ToUtf8((PCRTUTF16)pDir->Data.cFileName, &pDir->pszName); + if (RT_FAILURE(rc)) + { + pDir->pszName = NULL; + return rc; + } + pDir->cchName = strlen(pDir->pszName); + } + + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRY, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired > cbDirEntry) + return VERR_BUFFER_OVERFLOW; + + /* + * Setup the returned data. + */ + pDir->fDataUnread = false; + pDirEntry->INodeId = 0; /** @todo we can use the fileid here if we must (see GetFileInformationByHandle). */ + pDirEntry->enmType = pDir->Data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY + ? RTDIRENTRYTYPE_DIRECTORY : RTDIRENTRYTYPE_FILE; + pDirEntry->cbName = (uint16_t)cchName; + Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + PPRTDIRINTERNAL pDir = hDir; + /** @todo Symlinks: Find[First|Next]FileW will return info about + the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */ + /* + * Validate input. + */ + if (!pDir || pDir->u32Magic != RTDIR_MAGIC) + { + AssertMsgFailed(("Invalid pDir=%p\n", pDir)); + return VERR_INVALID_PARAMETER; + } + if (!pDirEntry) + { + AssertMsgFailed(("Invalid pDirEntry=%p\n", pDirEntry)); + return VERR_INVALID_PARAMETER; + } + if ( enmAdditionalAttribs < RTFSOBJATTRADD_NOTHING + || enmAdditionalAttribs > RTFSOBJATTRADD_LAST) + { + AssertMsgFailed(("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs)); + return VERR_INVALID_PARAMETER; + } + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + cbDirEntry = *pcbDirEntry; + if (cbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName[2])) + { + AssertMsgFailed(("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2]))); + return VERR_INVALID_PARAMETER; + } + } + + /* + * Fetch data? + */ + if (!pDir->fDataUnread) + { + RTStrFree(pDir->pszName); + pDir->pszName = NULL; + + BOOL fRc = FindNextFileW(pDir->hDir, &pDir->Data); + if (!fRc) + { + int iErr = GetLastError(); + if (pDir->hDir == INVALID_HANDLE_VALUE || iErr == ERROR_NO_MORE_FILES) + return VERR_NO_MORE_FILES; + return RTErrConvertFromWin32(iErr); + } + } + + /* + * Convert the filename to UTF-8. + */ + if (!pDir->pszName) + { + int rc = RTUtf16ToUtf8((PCRTUTF16)pDir->Data.cFileName, &pDir->pszName); + if (RT_FAILURE(rc)) + { + pDir->pszName = NULL; + return rc; + } + pDir->cchName = strlen(pDir->pszName); + } + + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRYEX, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired > cbDirEntry) + return VERR_BUFFER_OVERFLOW; + + /* + * Setup the returned data. + */ + pDir->fDataUnread = false; + pDirEntry->cbName = (uint16_t)cchName; + Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + if (pDir->Data.cAlternateFileName[0]) + { + /* copy and calc length */ + PCRTUTF16 pwszSrc = (PCRTUTF16)pDir->Data.cAlternateFileName; + PRTUTF16 pwszDst = pDirEntry->wszShortName; + uint32_t off = 0; + while (off < RT_ELEMENTS(pDirEntry->wszShortName) - 1U && pwszSrc[off]) + { + pwszDst[off] = pwszSrc[off]; + off++; + } + pDirEntry->cwcShortName = (uint16_t)off; + + /* zero the rest */ + do + pwszDst[off++] = '\0'; + while (off < RT_ELEMENTS(pDirEntry->wszShortName)); + } + else + { + memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName)); + pDirEntry->cwcShortName = 0; + } + + pDirEntry->Info.cbObject = ((uint64_t)pDir->Data.nFileSizeHigh << 32) + | (uint64_t)pDir->Data.nFileSizeLow; + pDirEntry->Info.cbAllocated = pDirEntry->Info.cbObject; + + Assert(sizeof(uint64_t) == sizeof(pDir->Data.ftCreationTime)); + RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, *(uint64_t *)&pDir->Data.ftCreationTime); + RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, *(uint64_t *)&pDir->Data.ftLastAccessTime); + RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, *(uint64_t *)&pDir->Data.ftLastWriteTime); + pDirEntry->Info.ChangeTime = pDirEntry->Info.ModificationTime; + + pDirEntry->Info.Attr.fMode = rtFsModeFromDos((pDir->Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszName, cchName, pDir->Data.dwReserved0, 0); + + /* + * Requested attributes (we cannot provide anything actually). + */ + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_EASIZE: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; + pDirEntry->Info.Attr.u.EASize.cb = 0; + break; + + case RTFSOBJATTRADD_UNIX: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pDirEntry->Info.Attr.u.Unix.uid = ~0U; + pDirEntry->Info.Attr.u.Unix.gid = ~0U; + pDirEntry->Info.Attr.u.Unix.cHardlinks = 1; + pDirEntry->Info.Attr.u.Unix.INodeIdDevice = 0; /** @todo Use the volume serial number (see GetFileInformationByHandle). */ + pDirEntry->Info.Attr.u.Unix.INodeId = 0; /** @todo Use the fileid (see GetFileInformationByHandle). */ + pDirEntry->Info.Attr.u.Unix.fFlags = 0; + pDirEntry->Info.Attr.u.Unix.GenerationId = 0; + pDirEntry->Info.Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_NOTHING: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pDirEntry->Info.Attr.u.UnixOwner.uid = ~0U; + pDirEntry->Info.Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pDirEntry->Info.Attr.u.UnixGroup.gid = ~0U; + pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0'; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR; + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/r3/win/dllmain-win.cpp b/src/VBox/Runtime/r3/win/dllmain-win.cpp new file mode 100644 index 00000000..b52d059b --- /dev/null +++ b/src/VBox/Runtime/r3/win/dllmain-win.cpp @@ -0,0 +1,100 @@ +/* $Id: dllmain-win.cpp $ */ +/** @file + * IPRT - Win32 DllMain (Ring-3). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> +#include <iprt/thread.h> +#include <iprt/param.h> +#include "internal/thread.h" + + + +/** + * Increases the load count on the IPRT DLL so it won't unload. + * + * This is a separate function so as to not overflow the stack of threads with + * very little of it. + * + * @param hModule The IPRT DLL module handle. + */ +DECL_NO_INLINE(static, void) EnsureNoUnload(HMODULE hModule) +{ + WCHAR wszName[RTPATH_MAX]; + SetLastError(NO_ERROR); + if ( GetModuleFileNameW(hModule, wszName, RT_ELEMENTS(wszName)) > 0 + && GetLastError() == NO_ERROR) + { + int cExtraLoads = 32; + while (cExtraLoads-- > 0) + LoadLibraryW(wszName); + } +} + + +/** + * The Dll main entry point. + */ +BOOL __stdcall DllMain(HANDLE hModule, DWORD dwReason, PVOID pvReserved) +{ + RT_NOREF_PV(pvReserved); + + switch (dwReason) + { + /* + * When attaching to a process, we'd like to make sure IPRT stays put + * and doesn't get unloaded. + */ + case DLL_PROCESS_ATTACH: + EnsureNoUnload((HMODULE)hModule); + break; + + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + default: + /* ignore */ + break; + + case DLL_THREAD_DETACH: + rtThreadWinTlsDestruction(); + rtThreadNativeDetach(); + break; + } + return TRUE; +} + diff --git a/src/VBox/Runtime/r3/win/env-win.cpp b/src/VBox/Runtime/r3/win/env-win.cpp new file mode 100644 index 00000000..89577098 --- /dev/null +++ b/src/VBox/Runtime/r3/win/env-win.cpp @@ -0,0 +1,520 @@ +/* $Id: env-win.cpp $ */ +/** @file + * IPRT - Environment, Posix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/env.h> + +#ifdef IPRT_NO_CRT +# include <iprt/asm.h> +#endif +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/utf16.h> + +#ifndef IPRT_NO_CRT +# include <stdlib.h> +# include <errno.h> +#endif +#include <iprt/win/windows.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef IPRT_NO_CRT +static uint32_t volatile g_idxGetEnvBufs = 0; +static char *g_apszGetEnvBufs[64]; /* leak */ +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +int rtEnvSetUtf8Worker(const char *pchVar, size_t cchVar, const char *pszValue); + + +#if defined(RT_ARCH_X86) && defined(RT_OS_WINDOWS) +/** + * This is a workaround for NT 3.x not setting the last error. + */ +static DWORD rtEnvNt31CheckEmpty(PCRTUTF16 pwszVar) +{ + /* Check the version first: */ + DWORD dwVersion = GetVersion(); + if (RT_BYTE1(dwVersion) != 3) + return 0; + + /* When called with an empty buffer, we should get 1 if empty value + and 0 if not found: */ + DWORD cwcNeeded = GetEnvironmentVariableW(pwszVar, NULL, 0); + return cwcNeeded == 0 ? ERROR_ENVVAR_NOT_FOUND : NO_ERROR; +} +#endif + + +RTDECL(bool) RTEnvExistsBad(const char *pszVar) +{ +#ifndef IPRT_NO_CRT + return RTEnvGetBad(pszVar) != NULL; +#else + return RTEnvExistsUtf8(pszVar); +#endif +} + + +RTDECL(bool) RTEnvExist(const char *pszVar) +{ +#ifndef IPRT_NO_CRT + return RTEnvExistsBad(pszVar); +#else + return RTEnvExistsUtf8(pszVar); +#endif +} + + +RTDECL(bool) RTEnvExistsUtf8(const char *pszVar) +{ + AssertReturn(strchr(pszVar, '=') == NULL, false); + + PRTUTF16 pwszVar; + int rc = RTStrToUtf16(pszVar, &pwszVar); + AssertRCReturn(rc, false); + +#ifndef IPRT_NO_CRT + bool fRet = _wgetenv(pwszVar) != NULL; +#else + DWORD dwRet = GetEnvironmentVariableW(pwszVar, NULL, 0); + bool fRet = dwRet != 0; +#endif + + RTUtf16Free(pwszVar); + return fRet; +} + + +RTDECL(const char *) RTEnvGetBad(const char *pszVar) +{ + AssertReturn(strchr(pszVar, '=') == NULL, NULL); +#ifndef IPRT_NO_CRT + return getenv(pszVar); +#else + /* + * Query the value into heap buffer which we give a lifetime of 64 + * RTEnvGetBad calls. + */ + char *pszValue = RTEnvDup(pszVar); + if (pszValue) + { + RTMEM_MAY_LEAK(pszValue); /* Quite possible we'll leak this, but the leak is limited to 64 values. */ + + uint32_t idx = ASMAtomicIncU32(&g_idxGetEnvBufs) % RT_ELEMENTS(g_apszGetEnvBufs); + char *pszOld = (char *)ASMAtomicXchgPtr((void * volatile *)&g_apszGetEnvBufs[idx], pszValue); + RTStrFree(pszOld); + } + return pszValue; +#endif +} + + +RTDECL(const char *) RTEnvGet(const char *pszVar) +{ + return RTEnvGetBad(pszVar); +} + +RTDECL(int) RTEnvGetUtf8(const char *pszVar, char *pszValue, size_t cbValue, size_t *pcchActual) +{ + AssertPtrReturn(pszVar, VERR_INVALID_POINTER); + AssertPtrNullReturn(pszValue, VERR_INVALID_POINTER); + AssertReturn(pszValue || !cbValue, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pcchActual, VERR_INVALID_POINTER); + AssertReturn(pcchActual || (pszValue && cbValue), VERR_INVALID_PARAMETER); + AssertReturn(strchr(pszVar, '=') == NULL, VERR_ENV_INVALID_VAR_NAME); + + if (pcchActual) + *pcchActual = 0; + + /* + * Convert the name to UTF-16. + */ + PRTUTF16 pwszVar; + int rc = RTStrToUtf16(pszVar, &pwszVar); + AssertRCReturn(rc, rc); + + /* + * Query the variable. First try with a medium sized stack buffer (too + * small for your typical PATH, but large enough for most other things). + */ + RTUTF16 wszValue[512]; + uint32_t cwcValueBuf = RT_ELEMENTS(wszValue); + PRTUTF16 pwszValue = wszValue; + PRTUTF16 pwszValueFree = NULL; + + for (unsigned iTry = 0;; iTry++) + { + /* This API is weird, it didn't always set ERROR_BUFFER_OVERFLOW nor ERROR_ENVVAR_NOT_FOUND. + Note! Assume that the CRT transparently updates the process + environment and that we don't need to use _wgetenv_s here. */ + SetLastError(NO_ERROR); + DWORD const cwcValueRet = GetEnvironmentVariableW(pwszVar, pwszValue, cwcValueBuf); + DWORD dwErr = GetLastError(); + + if (cwcValueRet < cwcValueBuf) + { +#ifdef RT_ARCH_X86 + if (cwcValueRet == 0 && dwErr == NO_ERROR) + dwErr = rtEnvNt31CheckEmpty(pwszVar); +#endif + if (cwcValueRet > 0 || dwErr == NO_ERROR) /* In case of empty values we have to see if last error was set or not. */ + { + if (cbValue) + rc = RTUtf16ToUtf8Ex(pwszValue, cwcValueRet, &pszValue, cbValue, pcchActual); + else + rc = RTUtf16CalcUtf8LenEx(pwszValue, cwcValueRet, pcchActual); + } + else + { + Assert(cwcValueRet == 0); + Assert(dwErr != NO_ERROR); + rc = RTErrConvertFromWin32(dwErr); + } + break; + } + + /* + * Insufficient buffer, so increase it. The first re-try will use the + * returned size, further re-tries will max out with a multiple of the + * stack buffer till we reaches 32KB chars (128 loops). + */ + Assert(dwErr == NO_ERROR || dwErr == ERROR_BUFFER_OVERFLOW); + RTMemTmpFree(pwszValueFree); + AssertBreakStmt(cwcValueBuf < _32K, rc = VERR_INTERNAL_ERROR_3 /* not a good one */); + + cwcValueBuf = RT_MAX(cwcValueRet + iTry, RT_ELEMENTS(wszValue) * iTry); + pwszValueFree = pwszValue = (PRTUTF16)RTMemTmpAlloc(cwcValueBuf * sizeof(RTUTF16)); + AssertBreakStmt(pwszValue, rc = VERR_NO_TMP_MEMORY); + } + + RTMemTmpFree(pwszValueFree); + RTUtf16Free(pwszVar); + return rc; +} + + +RTDECL(char *) RTEnvDup(const char *pszVar) +{ + AssertPtrReturn(pszVar, NULL); + + /* + * Convert the name to UTF-16. + */ + PRTUTF16 pwszVar; + int rc = RTStrToUtf16(pszVar, &pwszVar); + AssertRCReturn(rc, NULL); + + /* + * Query the variable. First try with a medium sized stack buffer (too + * small for your typical PATH, but large enough for most other things). + */ + char *pszRet = NULL; + RTUTF16 wszValue[512]; + uint32_t cwcValueBuf = RT_ELEMENTS(wszValue); + PRTUTF16 pwszValue = wszValue; + PRTUTF16 pwszValueFree = NULL; + + for (unsigned iTry = 0;; iTry++) + { + /* This API is weird, it didn't always set ERROR_BUFFER_OVERFLOW nor ERROR_ENVVAR_NOT_FOUND. + Note! Assume that the CRT transparently updates the process + environment and that we don't need to use _wgetenv_s here. */ + SetLastError(NO_ERROR); + DWORD const cwcValueRet = GetEnvironmentVariableW(pwszVar, pwszValue, cwcValueBuf); + DWORD dwErr = GetLastError(); + + if (cwcValueRet < cwcValueBuf) + { +#ifdef RT_ARCH_X86 + if (cwcValueRet == 0 && dwErr == NO_ERROR) + dwErr = rtEnvNt31CheckEmpty(pwszVar); +#endif + if (cwcValueRet > 0 || dwErr == NO_ERROR) /* In case of empty values we have to see if last error was set or not. */ + { + rc = RTUtf16ToUtf8Ex(pwszValue, cwcValueRet, &pszRet, 0, NULL); + if (RT_FAILURE(rc)) + pszRet = NULL; + } + else + { + Assert(cwcValueRet == 0); + Assert(dwErr != NO_ERROR); + } + break; + } + + /* + * Insufficient buffer, so increase it. The first re-try will use the + * returned size, further re-tries will max out with a multiple of the + * stack buffer till we reaches 32KB chars (128 loops). + */ + Assert(dwErr == NO_ERROR || dwErr == ERROR_BUFFER_OVERFLOW); + RTMemTmpFree(pwszValueFree); + AssertBreakStmt(cwcValueBuf < _32K, rc = VERR_INTERNAL_ERROR_3 /* not a good one */); + + cwcValueBuf = RT_MAX(cwcValueRet + iTry, RT_ELEMENTS(wszValue) * iTry); + pwszValueFree = pwszValue = (PRTUTF16)RTMemTmpAlloc(cwcValueBuf * sizeof(RTUTF16)); + AssertBreakStmt(pwszValue, rc = VERR_NO_TMP_MEMORY); + } + + RTMemTmpFree(pwszValueFree); + RTUtf16Free(pwszVar); + return pszRet; +} + + +RTDECL(int) RTEnvPutBad(const char *pszVarEqualValue) +{ +#ifndef IPRT_NO_CRT + /** @todo putenv is a source memory leaks. deal with this on a per system basis. */ + if (!putenv((char *)pszVarEqualValue)) + return 0; + return RTErrConvertFromErrno(errno); +#else + return RTEnvPutUtf8(pszVarEqualValue); +#endif +} + + +RTDECL(int) RTEnvPut(const char *pszVarEqualValue) +{ +#ifndef IPRT_NO_CRT + return RTEnvPutBad(pszVarEqualValue); +#else + return RTEnvPutUtf8(pszVarEqualValue); +#endif +} + + +RTDECL(int) RTEnvPutUtf8(const char *pszVarEqualValue) +{ + PRTUTF16 pwszVarEqualValue; + int rc = RTStrToUtf16(pszVarEqualValue, &pwszVarEqualValue); + if (RT_SUCCESS(rc)) + { +#ifndef IPRT_NO_CRT + if (!_wputenv(pwszVarEqualValue)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); +#else + PRTUTF16 pwszValue = RTUtf16Chr(pwszVarEqualValue, '='); + if (pwszValue) + { + *pwszValue++ = '\0'; + + SetLastError(*pwszValue ? ERROR_OUTOFMEMORY : ERROR_ENVVAR_NOT_FOUND); /* The API did not always set the last error. */ + if (SetEnvironmentVariableW(pwszVarEqualValue, *pwszValue ? pwszValue : NULL)) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_ENVVAR_NOT_FOUND) + { + Assert(!*pwszValue); + rc = VINF_SUCCESS; + } + else + { + Assert(*pwszValue); + rc = RTErrConvertFromWin32(GetLastError()); + } + } + } + else + rc = VERR_INVALID_PARAMETER; +#endif + RTUtf16Free(pwszVarEqualValue); + } + return rc; +} + + + +RTDECL(int) RTEnvSetBad(const char *pszVar, const char *pszValue) +{ +#ifndef IPRT_NO_CRT + AssertMsgReturn(strchr(pszVar, '=') == NULL, ("'%s'\n", pszVar), VERR_ENV_INVALID_VAR_NAME); + int rc; + if (!RTEnvExist(pszVar)) + rc = VINF_ENV_VAR_NOT_FOUND; + else + { + errno_t rcErrno = _putenv_s(pszVar, *pszValue ? pszValue : " " /* wrong, but will be treated as unset otherwise */); + if (rcErrno == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(rcErrno); + } + return rc; +#else + return RTEnvSetUtf8(pszVar, pszValue); +#endif +} + + +RTDECL(int) RTEnvSet(const char *pszVar, const char *pszValue) +{ +#ifndef IPRT_NO_CRT + return RTEnvSetBad(pszVar, pszValue); +#else + return RTEnvSetUtf8(pszVar, pszValue); +#endif +} + + +/** + * Worker common to RTEnvSetUtf8() and rtEnvSetExWorker(). + */ +int rtEnvSetUtf8Worker(const char *pchVar, size_t cchVar, const char *pszValue) +{ + PRTUTF16 pwszVar; + int rc = RTStrToUtf16Ex(pchVar, cchVar, &pwszVar, 0, NULL); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszValue; + rc = RTStrToUtf16(pszValue, &pwszValue); + if (RT_SUCCESS(rc)) + { +#ifndef IPRT_NO_CRT + errno_t rcErrno = _wputenv_s(pwszVar, + *pwszValue ? pwszValue : L" " /* wrong, but will be treated as unset otherwise */); + if (rcErrno == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(rcErrno); +#else + SetLastError(ERROR_OUTOFMEMORY); /* The API did not always set the last error. */ + if (SetEnvironmentVariableW(pwszVar, pwszValue)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); +#endif + RTUtf16Free(pwszValue); + } + RTUtf16Free(pwszVar); + } + return rc; +} + + +RTDECL(int) RTEnvSetUtf8(const char *pszVar, const char *pszValue) +{ + size_t cchVar = strlen(pszVar); + AssertReturn(memchr(pszVar, '=', cchVar) == NULL, VERR_ENV_INVALID_VAR_NAME); + return rtEnvSetUtf8Worker(pszVar, cchVar, pszValue); +} + + +RTDECL(int) RTEnvUnsetBad(const char *pszVar) +{ +#ifndef IPRT_NO_CRT + AssertReturn(strchr(pszVar, '=') == NULL, VERR_ENV_INVALID_VAR_NAME); + int rc; + if (!RTEnvExist(pszVar)) + rc = VINF_ENV_VAR_NOT_FOUND; + else + { + errno_t rcErrno = _putenv_s(pszVar, NULL); + if (rcErrno == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(rcErrno); + } + return rc; +#else + return RTEnvUnsetUtf8(pszVar); +#endif +} + + +RTDECL(int) RTEnvUnset(const char *pszVar) +{ +#ifndef IPRT_NO_CRT + return RTEnvUnsetBad(pszVar); +#else + return RTEnvUnsetUtf8(pszVar); +#endif +} + + +RTDECL(int) RTEnvUnsetUtf8(const char *pszVar) +{ + AssertReturn(strchr(pszVar, '=') == NULL, VERR_ENV_INVALID_VAR_NAME); + + PRTUTF16 pwszVar; + int rc = RTStrToUtf16(pszVar, &pwszVar); + if (RT_SUCCESS(rc)) + { +#ifndef IPRT_NO_CRT + if (_wgetenv(pwszVar)) + { + errno_t rcErrno = _wputenv_s(pwszVar, NULL); + if (rcErrno == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(rcErrno); + } + else + rc = VINF_ENV_VAR_NOT_FOUND; +#else + SetLastError(ERROR_ENVVAR_NOT_FOUND); /* The API did not always set the last error. */ + if (SetEnvironmentVariableW(pwszVar, NULL)) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + rc = dwErr == ERROR_ENVVAR_NOT_FOUND ? VINF_ENV_VAR_NOT_FOUND : RTErrConvertFromWin32(dwErr); + } +#endif + RTUtf16Free(pwszVar); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/errvars-win.cpp b/src/VBox/Runtime/r3/win/errvars-win.cpp new file mode 100644 index 00000000..6d8b4814 --- /dev/null +++ b/src/VBox/Runtime/r3/win/errvars-win.cpp @@ -0,0 +1,108 @@ +/* $Id: errvars-win.cpp $ */ +/** @file + * IPRT - Save and Restore Error Variables, Windows Ring-3. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/winsock2.h> +#ifndef IPRT_NO_CRT +# include <errno.h> +#endif + +#include <iprt/errcore.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include "internal/magics.h" +#include "internal-r3-win.h" + + + +RTDECL(PRTERRVARS) RTErrVarsSave(PRTERRVARS pVars) +{ + pVars->ai32Vars[0] = RTERRVARS_MAGIC; + pVars->ai32Vars[1] = GetLastError(); + pVars->ai32Vars[2] = g_pfnWSAGetLastError ? g_pfnWSAGetLastError() : WSANOTINITIALISED; +#ifndef IPRT_NO_CRT + pVars->ai32Vars[3] = errno; +#endif + return pVars; +} + + +RTDECL(void) RTErrVarsRestore(PCRTERRVARS pVars) +{ + AssertReturnVoid(pVars->ai32Vars[0] == RTERRVARS_MAGIC); +#ifndef IPRT_NO_CRT + errno = pVars->ai32Vars[3]; +#endif + if ( pVars->ai32Vars[2] != WSANOTINITIALISED + && g_pfnWSASetLastError) + g_pfnWSASetLastError(pVars->ai32Vars[2]); + SetLastError(pVars->ai32Vars[1]); +} + + +RTDECL(bool) RTErrVarsAreEqual(PCRTERRVARS pVars1, PCRTERRVARS pVars2) +{ + Assert(pVars1->ai32Vars[0] == RTERRVARS_MAGIC); + Assert(pVars2->ai32Vars[0] == RTERRVARS_MAGIC); + + return pVars1->ai32Vars[0] == pVars2->ai32Vars[0] + && pVars1->ai32Vars[1] == pVars2->ai32Vars[1] + && pVars1->ai32Vars[2] == pVars2->ai32Vars[2] +#ifndef IPRT_NO_CRT + && pVars1->ai32Vars[3] == pVars2->ai32Vars[3] +#endif + ; +} + + +RTDECL(bool) RTErrVarsHaveChanged(PCRTERRVARS pVars) +{ + Assert(pVars->ai32Vars[0] == RTERRVARS_MAGIC); + + return pVars->ai32Vars[0] != RTERRVARS_MAGIC + || (uint32_t)pVars->ai32Vars[1] != GetLastError() + || pVars->ai32Vars[2] != (g_pfnWSAGetLastError ? g_pfnWSAGetLastError() : WSANOTINITIALISED) +#ifndef IPRT_NO_CRT + || pVars->ai32Vars[3] != errno +#endif + ; + +} + diff --git a/src/VBox/Runtime/r3/win/fileaio-win.cpp b/src/VBox/Runtime/r3/win/fileaio-win.cpp new file mode 100644 index 00000000..a3fb934d --- /dev/null +++ b/src/VBox/Runtime/r3/win/fileaio-win.cpp @@ -0,0 +1,554 @@ +/* $Id: fileaio-win.cpp $ */ +/** @file + * IPRT - File async I/O, native implementation for the Windows host platform. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR + +#include <iprt/asm.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/fileaio.h" + +#include <iprt/win/windows.h> + +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Transfer direction. + */ +typedef enum TRANSFERDIRECTION +{ + TRANSFERDIRECTION_INVALID = 0, + /** Read. */ + TRANSFERDIRECTION_READ, + /** Write. */ + TRANSFERDIRECTION_WRITE, + /** The usual 32-bit hack. */ + TRANSFERDIRECTION_32BIT_HACK = 0x7fffffff +} TRANSFERDIRECTION; + +/** + * Async I/O completion context state. + */ +typedef struct RTFILEAIOCTXINTERNAL +{ + /** handle to I/O completion port. */ + HANDLE hIoCompletionPort; + /** Current number of requests pending. */ + volatile int32_t cRequests; + /** Flag whether the thread was woken up. */ + volatile bool fWokenUp; + /** Flag whether the thread is currently waiting. */ + volatile bool fWaiting; + /** Flags given during creation. */ + uint32_t fFlags; + /** Magic value (RTFILEAIOCTX_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOCTXINTERNAL; +/** Pointer to an internal context structure. */ +typedef RTFILEAIOCTXINTERNAL *PRTFILEAIOCTXINTERNAL; + +/** + * Async I/O request state. + */ +typedef struct RTFILEAIOREQINTERNAL +{ + /** Overlapped structure. */ + OVERLAPPED Overlapped; + /** Current state the request is in. */ + RTFILEAIOREQSTATE enmState; + /** The file handle. */ + HANDLE hFile; + /** Kind of transfer Read/Write. */ + TRANSFERDIRECTION enmTransferDirection; + /** Number of bytes to transfer. */ + size_t cbTransfer; + /** Pointer to the buffer. */ + void *pvBuf; + /** Opaque user data. */ + void *pvUser; + /** Flag whether the request completed. */ + bool fCompleted; + /** Number of bytes transferred successfully. */ + size_t cbTransfered; + /** Error code of the completed request. */ + int Rc; + /** Completion context we are assigned to. */ + PRTFILEAIOCTXINTERNAL pCtxInt; + /** Magic value (RTFILEAIOREQ_MAGIC). */ + uint32_t u32Magic; +} RTFILEAIOREQINTERNAL; +/** Pointer to an internal request structure. */ +typedef RTFILEAIOREQINTERNAL *PRTFILEAIOREQINTERNAL; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Id for the wakeup event. */ +#define AIO_CONTEXT_WAKEUP_EVENT 1 +/** Converts a pointer to an OVERLAPPED structure to a internal request. */ +#define OVERLAPPED_2_RTFILEAIOREQINTERNAL(pOverlapped) ( (PRTFILEAIOREQINTERNAL)((uintptr_t)(pOverlapped) - RT_UOFFSETOF(RTFILEAIOREQINTERNAL, Overlapped)) ) + +RTR3DECL(int) RTFileAioGetLimits(PRTFILEAIOLIMITS pAioLimits) +{ + AssertPtrReturn(pAioLimits, VERR_INVALID_POINTER); + + /* No limits known. */ + pAioLimits->cReqsOutstandingMax = RTFILEAIO_UNLIMITED_REQS; + pAioLimits->cbBufferAlignment = 0; + + return VINF_SUCCESS; +} + +RTR3DECL(int) RTFileAioReqCreate(PRTFILEAIOREQ phReq) +{ + AssertPtrReturn(phReq, VERR_INVALID_POINTER); + + PRTFILEAIOREQINTERNAL pReqInt = (PRTFILEAIOREQINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOREQINTERNAL)); + if (RT_UNLIKELY(!pReqInt)) + return VERR_NO_MEMORY; + + pReqInt->pCtxInt = NULL; + pReqInt->fCompleted = false; + pReqInt->u32Magic = RTFILEAIOREQ_MAGIC; + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + *phReq = (RTFILEAIOREQ)pReqInt; + + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioReqDestroy(RTFILEAIOREQ hReq) +{ + /* + * Validate the handle and ignore nil. + */ + if (hReq == NIL_RTFILEAIOREQ) + return VINF_SUCCESS; + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + + /* + * Trash the magic and free it. + */ + ASMAtomicUoWriteU32(&pReqInt->u32Magic, ~RTFILEAIOREQ_MAGIC); + RTMemFree(pReqInt); + return VINF_SUCCESS; +} + +/** + * Worker setting up the request. + */ +DECLINLINE(int) rtFileAioReqPrepareTransfer(RTFILEAIOREQ hReq, RTFILE hFile, + TRANSFERDIRECTION enmTransferDirection, + RTFOFF off, void *pvBuf, size_t cbTransfer, + void *pvUser) +{ + /* + * Validate the input. + */ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + Assert(hFile != NIL_RTFILE); + AssertPtr(pvBuf); + Assert(off >= 0); + Assert(cbTransfer > 0); + + pReqInt->enmTransferDirection = enmTransferDirection; + pReqInt->hFile = (HANDLE)RTFileToNative(hFile); + pReqInt->Overlapped.Offset = (DWORD)(off & 0xffffffff); + pReqInt->Overlapped.OffsetHigh = (DWORD)(off >> 32); + pReqInt->cbTransfer = cbTransfer; + pReqInt->pvBuf = pvBuf; + pReqInt->pvUser = pvUser; + pReqInt->fCompleted = false; + + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioReqPrepareRead(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void *pvBuf, size_t cbRead, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, TRANSFERDIRECTION_READ, + off, pvBuf, cbRead, pvUser); +} + +RTDECL(int) RTFileAioReqPrepareWrite(RTFILEAIOREQ hReq, RTFILE hFile, RTFOFF off, + void const *pvBuf, size_t cbWrite, void *pvUser) +{ + return rtFileAioReqPrepareTransfer(hReq, hFile, TRANSFERDIRECTION_WRITE, + off, (void *)pvBuf, cbWrite, pvUser); +} + +RTDECL(int) RTFileAioReqPrepareFlush(RTFILEAIOREQ hReq, RTFILE hFile, void *pvUser) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + AssertReturn(hFile != NIL_RTFILE, VERR_INVALID_HANDLE); + RT_NOREF_PV(pvUser); + + return VERR_NOT_SUPPORTED; +} + +RTDECL(void *) RTFileAioReqGetUser(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN_RC(pReqInt, NULL); + + return pReqInt->pvUser; +} + +RTDECL(int) RTFileAioReqCancel(RTFILEAIOREQ hReq) +{ + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_NOT_SUBMITTED); + + /** + * @todo r=aeichner It is not possible to cancel specific + * requests on Windows before Vista. + * CancelIo cancels all requests for a file issued by the + * calling thread and CancelIoEx which does what we need + * is only available from Vista and up. + * The solution is to return VERR_FILE_AIO_IN_PROGRESS + * if the request didn't completed yet (checked above). + * Shouldn't be a big issue because a request is normally + * only canceled if it exceeds a timeout which is quite huge. + */ + return VERR_FILE_AIO_COMPLETED; +} + +RTDECL(int) RTFileAioReqGetRC(RTFILEAIOREQ hReq, size_t *pcbTransfered) +{ + int rc = VINF_SUCCESS; + PRTFILEAIOREQINTERNAL pReqInt = hReq; + RTFILEAIOREQ_VALID_RETURN(pReqInt); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, SUBMITTED, VERR_FILE_AIO_IN_PROGRESS); + RTFILEAIOREQ_NOT_STATE_RETURN_RC(pReqInt, PREPARED, VERR_FILE_AIO_NOT_SUBMITTED); + + rc = pReqInt->Rc; + if (pcbTransfered && RT_SUCCESS(rc)) + *pcbTransfered = pReqInt->cbTransfered; + + return rc; +} + +RTDECL(int) RTFileAioCtxCreate(PRTFILEAIOCTX phAioCtx, uint32_t cAioReqsMax, uint32_t fFlags) +{ + AssertPtrReturn(phAioCtx, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTFILEAIOCTX_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + RT_NOREF_PV(cAioReqsMax); + + if ( g_pfnCreateIoCompletionPort + && g_pfnGetQueuedCompletionStatus + && g_pfnPostQueuedCompletionStatus) + { + PRTFILEAIOCTXINTERNAL pCtxInt = (PRTFILEAIOCTXINTERNAL)RTMemAllocZ(sizeof(RTFILEAIOCTXINTERNAL)); + if (RT_UNLIKELY(!pCtxInt)) + return VERR_NO_MEMORY; + + pCtxInt->hIoCompletionPort = g_pfnCreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (RT_UNLIKELY(!pCtxInt->hIoCompletionPort)) + { + RTMemFree(pCtxInt); + return VERR_NO_MEMORY; + } + + pCtxInt->fFlags = fFlags; + pCtxInt->u32Magic = RTFILEAIOCTX_MAGIC; + + *phAioCtx = (RTFILEAIOCTX)pCtxInt; + + return VINF_SUCCESS; + } + return VERR_NOT_SUPPORTED; +} + +RTDECL(int) RTFileAioCtxDestroy(RTFILEAIOCTX hAioCtx) +{ + /* Validate the handle and ignore nil. */ + if (hAioCtx == NIL_RTFILEAIOCTX) + return VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + /* Cannot destroy a busy context. */ + if (RT_UNLIKELY(pCtxInt->cRequests)) + return VERR_FILE_AIO_BUSY; + + CloseHandle(pCtxInt->hIoCompletionPort); + ASMAtomicUoWriteU32(&pCtxInt->u32Magic, RTFILEAIOCTX_MAGIC_DEAD); + RTMemFree(pCtxInt); + + return VINF_SUCCESS; +} + +RTDECL(int) RTFileAioCtxAssociateWithFile(RTFILEAIOCTX hAioCtx, RTFILE hFile) +{ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + if ( g_pfnCreateIoCompletionPort + && g_pfnGetQueuedCompletionStatus + && g_pfnPostQueuedCompletionStatus) + { + int rc = VINF_SUCCESS; + HANDLE hTemp = g_pfnCreateIoCompletionPort((HANDLE)RTFileToNative(hFile), pCtxInt->hIoCompletionPort, 0, 1); + if (hTemp != pCtxInt->hIoCompletionPort) + rc = RTErrConvertFromWin32(GetLastError()); + + return rc; + } + return VERR_NOT_SUPPORTED; +} + +RTDECL(uint32_t) RTFileAioCtxGetMaxReqCount(RTFILEAIOCTX hAioCtx) +{ + RT_NOREF_PV(hAioCtx); + return RTFILEAIO_UNLIMITED_REQS; +} + +RTDECL(int) RTFileAioCtxSubmit(RTFILEAIOCTX hAioCtx, PRTFILEAIOREQ pahReqs, size_t cReqs) +{ + /* + * Parameter validation. + */ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertReturn(cReqs > 0, VERR_INVALID_PARAMETER); + Assert(cReqs <= INT32_MAX); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + size_t i; + + for (i = 0; i < cReqs; i++) + { + PRTFILEAIOREQINTERNAL pReqInt = pahReqs[i]; + BOOL fSucceeded; + + Assert(pReqInt->cbTransfer == (DWORD)pReqInt->cbTransfer); + if (pReqInt->enmTransferDirection == TRANSFERDIRECTION_READ) + { + fSucceeded = ReadFile(pReqInt->hFile, pReqInt->pvBuf, + (DWORD)pReqInt->cbTransfer, NULL, + &pReqInt->Overlapped); + } + else if (pReqInt->enmTransferDirection == TRANSFERDIRECTION_WRITE) + { + fSucceeded = WriteFile(pReqInt->hFile, pReqInt->pvBuf, + (DWORD)pReqInt->cbTransfer, NULL, + &pReqInt->Overlapped); + } + else + { + fSucceeded = false; + AssertMsgFailed(("Invalid transfer direction\n")); + } + + if (RT_UNLIKELY(!fSucceeded && GetLastError() != ERROR_IO_PENDING)) + { + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + rc = RTErrConvertFromWin32(GetLastError()); + pReqInt->Rc = rc; + break; + } + RTFILEAIOREQ_SET_STATE(pReqInt, SUBMITTED); + } + + ASMAtomicAddS32(&pCtxInt->cRequests, (int32_t)i); + + return rc; +} + +RTDECL(int) RTFileAioCtxWait(RTFILEAIOCTX hAioCtx, size_t cMinReqs, RTMSINTERVAL cMillies, + PRTFILEAIOREQ pahReqs, size_t cReqs, uint32_t *pcReqs) +{ + /* + * Validate the parameters, making sure to always set pcReqs. + */ + AssertPtrReturn(pcReqs, VERR_INVALID_POINTER); + *pcReqs = 0; /* always set */ + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + AssertPtrReturn(pahReqs, VERR_INVALID_POINTER); + AssertReturn(cReqs != 0, VERR_INVALID_PARAMETER); + AssertReturn(cReqs >= cMinReqs, VERR_OUT_OF_RANGE); + + /* + * Can't wait if there are no requests around. + */ + if ( RT_UNLIKELY(ASMAtomicUoReadS32(&pCtxInt->cRequests) == 0) + && !(pCtxInt->fFlags & RTFILEAIOCTX_FLAGS_WAIT_WITHOUT_PENDING_REQUESTS)) + return VERR_FILE_AIO_NO_REQUEST; + + /* Wait for at least one. */ + if (!cMinReqs) + cMinReqs = 1; + + /* + * Loop until we're woken up, hit an error (incl timeout), or + * have collected the desired number of requests. + */ + int rc = VINF_SUCCESS; + int cRequestsCompleted = 0; + while ( !pCtxInt->fWokenUp + && cMinReqs > 0) + { + uint64_t StartNanoTS = 0; + DWORD dwTimeout = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies; + DWORD cbTransfered; + LPOVERLAPPED pOverlapped; + ULONG_PTR lCompletionKey; + BOOL fSucceeded; + + if (cMillies != RT_INDEFINITE_WAIT) + StartNanoTS = RTTimeNanoTS(); + + ASMAtomicXchgBool(&pCtxInt->fWaiting, true); + fSucceeded = g_pfnGetQueuedCompletionStatus(pCtxInt->hIoCompletionPort, + &cbTransfered, + &lCompletionKey, + &pOverlapped, + dwTimeout); + ASMAtomicXchgBool(&pCtxInt->fWaiting, false); + if ( !fSucceeded + && !pOverlapped) + { + /* The call failed to dequeue a completion packet, includes VERR_TIMEOUT */ + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + + /* Check if we got woken up. */ + if (lCompletionKey == AIO_CONTEXT_WAKEUP_EVENT) + { + Assert(fSucceeded && !pOverlapped); + break; + } + + /* A request completed. */ + PRTFILEAIOREQINTERNAL pReqInt = OVERLAPPED_2_RTFILEAIOREQINTERNAL(pOverlapped); + AssertPtr(pReqInt); + Assert(pReqInt->u32Magic == RTFILEAIOREQ_MAGIC); + + /* Mark the request as finished. */ + RTFILEAIOREQ_SET_STATE(pReqInt, COMPLETED); + + pReqInt->cbTransfered = cbTransfered; + if (fSucceeded) + pReqInt->Rc = VINF_SUCCESS; + else + { + DWORD errCode = GetLastError(); + pReqInt->Rc = RTErrConvertFromWin32(errCode); + if (pReqInt->Rc == VERR_UNRESOLVED_ERROR) + LogRel(("AIO/win: Request %#p returned rc=%Rrc (native %u\n)", pReqInt, pReqInt->Rc, errCode)); + } + + pahReqs[cRequestsCompleted++] = (RTFILEAIOREQ)pReqInt; + + /* Update counter. */ + cMinReqs--; + + if (cMillies != RT_INDEFINITE_WAIT) + { + /* Recalculate timeout. */ + uint64_t NanoTS = RTTimeNanoTS(); + uint64_t cMilliesElapsed = (NanoTS - StartNanoTS) / 1000000; + if (cMilliesElapsed < cMillies) + cMillies -= cMilliesElapsed; + else + cMillies = 0; + } + } + + /* + * Update the context state and set the return value. + */ + *pcReqs = cRequestsCompleted; + ASMAtomicSubS32(&pCtxInt->cRequests, cRequestsCompleted); + + /* + * Clear the wakeup flag and set rc. + */ + bool fWokenUp = ASMAtomicXchgBool(&pCtxInt->fWokenUp, false); + + if ( fWokenUp + && RT_SUCCESS(rc)) + rc = VERR_INTERRUPTED; + + return rc; +} + +RTDECL(int) RTFileAioCtxWakeup(RTFILEAIOCTX hAioCtx) +{ + int rc = VINF_SUCCESS; + PRTFILEAIOCTXINTERNAL pCtxInt = hAioCtx; + RTFILEAIOCTX_VALID_RETURN(pCtxInt); + + bool fWokenUp = ASMAtomicXchgBool(&pCtxInt->fWokenUp, true); + bool fWaiting = ASMAtomicReadBool(&pCtxInt->fWaiting); + + if ( !fWokenUp + && fWaiting) + { + BOOL fSucceeded = g_pfnPostQueuedCompletionStatus(pCtxInt->hIoCompletionPort, + 0, AIO_CONTEXT_WAKEUP_EVENT, + NULL); + + if (!fSucceeded) + rc = RTErrConvertFromWin32(GetLastError()); + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/fileio-win.cpp b/src/VBox/Runtime/r3/win/fileio-win.cpp new file mode 100644 index 00000000..3feba3a2 --- /dev/null +++ b/src/VBox/Runtime/r3/win/fileio-win.cpp @@ -0,0 +1,1549 @@ +/* $Id: fileio-win.cpp $ */ +/** @file + * IPRT - File I/O, native implementation for the Windows host platform. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0500 +#endif +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/file.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/utf16.h> +#include "internal/file.h" +#include "internal/fs.h" +#include "internal/path.h" +#include "internal-r3-win.h" /* For g_enmWinVer + kRTWinOSType_XXX */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +typedef BOOL WINAPI FNVERIFYCONSOLEIOHANDLE(HANDLE); +typedef FNVERIFYCONSOLEIOHANDLE *PFNVERIFYCONSOLEIOHANDLE; /* No, nobody fell on the keyboard, really! */ + + +/** + * This is wrapper around the ugly SetFilePointer api. + * + * It's equivalent to SetFilePointerEx which we so unfortunately cannot use because of + * it not being present in NT4 GA. + * + * @returns Success indicator. Extended error information obtainable using GetLastError(). + * @param hFile Filehandle. + * @param offSeek Offset to seek. + * @param poffNew Where to store the new file offset. NULL allowed. + * @param uMethod Seek method. (The windows one!) + */ +DECLINLINE(bool) MySetFilePointer(RTFILE hFile, uint64_t offSeek, uint64_t *poffNew, unsigned uMethod) +{ + bool fRc; + LARGE_INTEGER off; + + off.QuadPart = offSeek; +#if 1 + if (off.LowPart != INVALID_SET_FILE_POINTER) + { + off.LowPart = SetFilePointer((HANDLE)RTFileToNative(hFile), off.LowPart, &off.HighPart, uMethod); + fRc = off.LowPart != INVALID_SET_FILE_POINTER; + } + else + { + SetLastError(NO_ERROR); + off.LowPart = SetFilePointer((HANDLE)RTFileToNative(hFile), off.LowPart, &off.HighPart, uMethod); + fRc = GetLastError() == NO_ERROR; + } +#else + fRc = SetFilePointerEx((HANDLE)RTFileToNative(hFile), off, &off, uMethod); +#endif + if (fRc && poffNew) + *poffNew = off.QuadPart; + return fRc; +} + + +/** + * Helper for checking if a VERR_DISK_FULL isn't a VERR_FILE_TOO_BIG. + * @returns VERR_DISK_FULL or VERR_FILE_TOO_BIG. + */ +static int rtFileWinCheckIfDiskReallyFull(RTFILE hFile, uint64_t cbDesired) +{ + /* + * Windows doesn't appear to have a way to query the file size limit of a + * file system, so we have to deduce the limit from the file system driver name. + * This means it will only work for known file systems. + */ + if (cbDesired >= _2G - 1) + { + uint64_t cbMaxFile = UINT64_MAX; + RTFSTYPE enmFsType; + int rc = rtNtQueryFsType((HANDLE)RTFileToNative(hFile), &enmFsType); + if (RT_SUCCESS(rc)) + switch (enmFsType) + { + case RTFSTYPE_NTFS: + case RTFSTYPE_EXFAT: + case RTFSTYPE_UDF: + cbMaxFile = UINT64_C(0xffffffffffffffff); /* (May be limited by IFS.) */ + break; + + case RTFSTYPE_ISO9660: + cbMaxFile = 8 *_1T; + break; + + case RTFSTYPE_FAT: + cbMaxFile = _4G; + break; + + case RTFSTYPE_HPFS: + cbMaxFile = _2G; + break; + + default: + break; + } + if (cbDesired >= cbMaxFile) + return VERR_FILE_TOO_BIG; + } + return VERR_DISK_FULL; +} + + +RTR3DECL(int) RTFileFromNative(PRTFILE pFile, RTHCINTPTR uNative) +{ + HANDLE h = (HANDLE)uNative; + AssertCompile(sizeof(h) == sizeof(uNative)); + if (h == INVALID_HANDLE_VALUE) + { + AssertMsgFailed(("%p\n", uNative)); + *pFile = NIL_RTFILE; + return VERR_INVALID_HANDLE; + } + *pFile = (RTFILE)h; + return VINF_SUCCESS; +} + + +RTR3DECL(RTHCINTPTR) RTFileToNative(RTFILE hFile) +{ + AssertReturn(hFile != NIL_RTFILE, (RTHCINTPTR)INVALID_HANDLE_VALUE); + return (RTHCINTPTR)hFile; +} + + +RTR3DECL(int) RTFileOpen(PRTFILE pFile, const char *pszFilename, uint64_t fOpen) +{ + return RTFileOpenEx(pszFilename, fOpen, pFile, NULL); +} + + +RTDECL(int) RTFileOpenEx(const char *pszFilename, uint64_t fOpen, PRTFILE phFile, PRTFILEACTION penmActionTaken) +{ + /* + * Validate input. + */ + AssertReturn(phFile, VERR_INVALID_PARAMETER); + *phFile = NIL_RTFILE; + if (penmActionTaken) + *penmActionTaken = RTFILEACTION_INVALID; + AssertReturn(pszFilename, VERR_INVALID_PARAMETER); + + /* + * Merge forced open flags and validate them. + */ + int rc = rtFileRecalcAndValidateFlags(&fOpen); + if (RT_FAILURE(rc)) + return rc; + + /* + * Determine disposition, access, share mode, creation flags, and security attributes + * for the CreateFile API call. + */ + DWORD dwCreationDisposition; + switch (fOpen & RTFILE_O_ACTION_MASK) + { + case RTFILE_O_OPEN: + dwCreationDisposition = fOpen & RTFILE_O_TRUNCATE ? TRUNCATE_EXISTING : OPEN_EXISTING; + break; + case RTFILE_O_OPEN_CREATE: + dwCreationDisposition = OPEN_ALWAYS; + break; + case RTFILE_O_CREATE: + dwCreationDisposition = CREATE_NEW; + break; + case RTFILE_O_CREATE_REPLACE: + dwCreationDisposition = CREATE_ALWAYS; + break; + default: + AssertMsgFailedReturn(("Impossible fOpen=%#llx\n", fOpen), VERR_INVALID_FLAGS); + } + + DWORD dwDesiredAccess; + switch (fOpen & RTFILE_O_ACCESS_MASK) + { + case RTFILE_O_READ: + dwDesiredAccess = FILE_GENERIC_READ; /* RTFILE_O_APPEND is ignored. */ + break; + case RTFILE_O_WRITE: + dwDesiredAccess = fOpen & RTFILE_O_APPEND + ? FILE_GENERIC_WRITE & ~FILE_WRITE_DATA + : FILE_GENERIC_WRITE; + break; + case RTFILE_O_READWRITE: + dwDesiredAccess = fOpen & RTFILE_O_APPEND + ? FILE_GENERIC_READ | (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA) + : FILE_GENERIC_READ | FILE_GENERIC_WRITE; + break; + case RTFILE_O_ATTR_ONLY: + if (fOpen & RTFILE_O_ACCESS_ATTR_MASK) + { + dwDesiredAccess = 0; + break; + } + RT_FALL_THRU(); + default: + AssertMsgFailedReturn(("Impossible fOpen=%#llx\n", fOpen), VERR_INVALID_FLAGS); + } + if (dwCreationDisposition == TRUNCATE_EXISTING) + /* Required for truncating the file (see MSDN), it is *NOT* part of FILE_GENERIC_WRITE. */ + dwDesiredAccess |= GENERIC_WRITE; + + /* RTFileSetMode needs following rights as well. */ + switch (fOpen & RTFILE_O_ACCESS_ATTR_MASK) + { + case RTFILE_O_ACCESS_ATTR_READ: dwDesiredAccess |= FILE_READ_ATTRIBUTES | SYNCHRONIZE; break; + case RTFILE_O_ACCESS_ATTR_WRITE: dwDesiredAccess |= FILE_WRITE_ATTRIBUTES | SYNCHRONIZE; break; + case RTFILE_O_ACCESS_ATTR_READWRITE: dwDesiredAccess |= FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE; break; + default: + /* Attributes access is the same as the file access. */ + switch (fOpen & RTFILE_O_ACCESS_MASK) + { + case RTFILE_O_READ: dwDesiredAccess |= FILE_READ_ATTRIBUTES | SYNCHRONIZE; break; + case RTFILE_O_WRITE: dwDesiredAccess |= FILE_WRITE_ATTRIBUTES | SYNCHRONIZE; break; + case RTFILE_O_READWRITE: dwDesiredAccess |= FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE; break; + default: + AssertMsgFailedReturn(("Impossible fOpen=%#llx\n", fOpen), VERR_INVALID_FLAGS); + } + } + + DWORD dwShareMode; + switch (fOpen & RTFILE_O_DENY_MASK) + { + case RTFILE_O_DENY_NONE: dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; break; + case RTFILE_O_DENY_READ: dwShareMode = FILE_SHARE_WRITE; break; + case RTFILE_O_DENY_WRITE: dwShareMode = FILE_SHARE_READ; break; + case RTFILE_O_DENY_READWRITE: dwShareMode = 0; break; + + case RTFILE_O_DENY_NOT_DELETE: dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; break; + case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_READ: dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_WRITE; break; + case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_WRITE: dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_READ; break; + case RTFILE_O_DENY_NOT_DELETE | RTFILE_O_DENY_READWRITE:dwShareMode = FILE_SHARE_DELETE; break; + default: + AssertMsgFailedReturn(("Impossible fOpen=%#llx\n", fOpen), VERR_INVALID_FLAGS); + } + + SECURITY_ATTRIBUTES SecurityAttributes; + PSECURITY_ATTRIBUTES pSecurityAttributes = NULL; + if (fOpen & RTFILE_O_INHERIT) + { + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.lpSecurityDescriptor = NULL; + SecurityAttributes.bInheritHandle = TRUE; + pSecurityAttributes = &SecurityAttributes; + } + + DWORD dwFlagsAndAttributes; + dwFlagsAndAttributes = !(fOpen & RTFILE_O_TEMP_AUTO_DELETE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_TEMPORARY; + if (fOpen & RTFILE_O_TEMP_AUTO_DELETE) + fOpen |= FILE_FLAG_DELETE_ON_CLOSE; + if (fOpen & RTFILE_O_WRITE_THROUGH) + dwFlagsAndAttributes |= FILE_FLAG_WRITE_THROUGH; + if (fOpen & RTFILE_O_ASYNC_IO) + dwFlagsAndAttributes |= FILE_FLAG_OVERLAPPED; + if (fOpen & RTFILE_O_NO_CACHE) + { + dwFlagsAndAttributes |= FILE_FLAG_NO_BUFFERING; + dwDesiredAccess &= ~FILE_APPEND_DATA; + } + + /* + * Open/Create the file. + */ + PRTUTF16 pwszFilename; + rc = RTPathWinFromUtf8(&pwszFilename, pszFilename, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + HANDLE hFile = CreateFileW(pwszFilename, + dwDesiredAccess, + dwShareMode, + pSecurityAttributes, + dwCreationDisposition, + dwFlagsAndAttributes, + NULL); + DWORD const dwErr = GetLastError(); + if (hFile != INVALID_HANDLE_VALUE) + { + /* + * Calculate the action taken value. + */ + RTFILEACTION enmActionTaken; + switch (dwCreationDisposition) + { + case CREATE_NEW: + enmActionTaken = RTFILEACTION_CREATED; + break; + case CREATE_ALWAYS: + AssertMsg(dwErr == ERROR_ALREADY_EXISTS || dwErr == NO_ERROR, ("%u\n", dwErr)); + enmActionTaken = dwErr == ERROR_ALREADY_EXISTS ? RTFILEACTION_REPLACED : RTFILEACTION_CREATED; + break; + case OPEN_EXISTING: + enmActionTaken = RTFILEACTION_OPENED; + break; + case OPEN_ALWAYS: + AssertMsg(dwErr == ERROR_ALREADY_EXISTS || dwErr == NO_ERROR, ("%u\n", dwErr)); + enmActionTaken = dwErr == ERROR_ALREADY_EXISTS ? RTFILEACTION_OPENED : RTFILEACTION_CREATED; + break; + case TRUNCATE_EXISTING: + enmActionTaken = RTFILEACTION_TRUNCATED; + break; + default: + AssertMsgFailed(("%d %#x\n", dwCreationDisposition, dwCreationDisposition)); + enmActionTaken = RTFILEACTION_INVALID; + break; + } + + /* + * Turn off indexing of directory through Windows Indexing Service if + * we created a new file or replaced an existing one. + */ + if ( (fOpen & RTFILE_O_NOT_CONTENT_INDEXED) + && ( enmActionTaken == RTFILEACTION_CREATED + || enmActionTaken == RTFILEACTION_REPLACED) ) + { + /** @todo there must be a way to do this via the handle! */ + if (!SetFileAttributesW(pwszFilename, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) + rc = RTErrConvertFromWin32(GetLastError()); + } + /* + * If RTFILEACTION_OPENED, we may need to truncate the file. + */ + else if ( (fOpen & (RTFILE_O_TRUNCATE | RTFILE_O_ACTION_MASK)) == (RTFILE_O_TRUNCATE | RTFILE_O_OPEN_CREATE) + && enmActionTaken == RTFILEACTION_OPENED) + { + if (SetEndOfFile(hFile)) + enmActionTaken = RTFILEACTION_TRUNCATED; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + if (penmActionTaken) + *penmActionTaken = enmActionTaken; + if (RT_SUCCESS(rc)) + { + *phFile = (RTFILE)hFile; + Assert((HANDLE)*phFile == hFile); + RTPathWinFree(pwszFilename); + return VINF_SUCCESS; + } + + CloseHandle(hFile); + } + else + { + if ( penmActionTaken + && dwCreationDisposition == CREATE_NEW + && dwErr == ERROR_FILE_EXISTS) + *penmActionTaken = RTFILEACTION_ALREADY_EXISTS; + rc = RTErrConvertFromWin32(dwErr); + } + RTPathWinFree(pwszFilename); + } + return rc; +} + + +RTR3DECL(int) RTFileOpenBitBucket(PRTFILE phFile, uint64_t fAccess) +{ + AssertReturn( fAccess == RTFILE_O_READ + || fAccess == RTFILE_O_WRITE + || fAccess == RTFILE_O_READWRITE, + VERR_INVALID_PARAMETER); + return RTFileOpen(phFile, "NUL", fAccess | RTFILE_O_DENY_NONE | RTFILE_O_OPEN); +} + + +RTDECL(int) RTFileDup(RTFILE hFileSrc, uint64_t fFlags, PRTFILE phFileNew) +{ + /* + * Validate input. + */ + AssertPtrReturn(phFileNew, VERR_INVALID_POINTER); + *phFileNew = NIL_RTFILE; + AssertPtrReturn(phFileNew, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~(uint64_t)RTFILE_O_INHERIT), VERR_INVALID_FLAGS); + + /* + * Do the job. + */ + HANDLE hNew = INVALID_HANDLE_VALUE; + if (DuplicateHandle(GetCurrentProcess(), (HANDLE)RTFileToNative(hFileSrc), + GetCurrentProcess(), &hNew, 0, RT_BOOL(fFlags & RTFILE_O_INHERIT), DUPLICATE_SAME_ACCESS)) + { + *phFileNew = (RTFILE)hNew; + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(GetLastError()); +} + + +RTR3DECL(int) RTFileClose(RTFILE hFile) +{ + if (hFile == NIL_RTFILE) + return VINF_SUCCESS; + if (CloseHandle((HANDLE)RTFileToNative(hFile))) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + + +RTFILE rtFileGetStandard(RTHANDLESTD enmStdHandle) +{ + DWORD dwStdHandle; + switch (enmStdHandle) + { + case RTHANDLESTD_INPUT: dwStdHandle = STD_INPUT_HANDLE; break; + case RTHANDLESTD_OUTPUT: dwStdHandle = STD_OUTPUT_HANDLE; break; + case RTHANDLESTD_ERROR: dwStdHandle = STD_ERROR_HANDLE; break; + default: + AssertFailedReturn(NIL_RTFILE); + } + + HANDLE hNative = GetStdHandle(dwStdHandle); + if (hNative == INVALID_HANDLE_VALUE) + return NIL_RTFILE; + + RTFILE hFile = (RTFILE)(uintptr_t)hNative; + AssertReturn((HANDLE)(uintptr_t)hFile == hNative, NIL_RTFILE); + return hFile; +} + + +RTR3DECL(int) RTFileSeek(RTFILE hFile, int64_t offSeek, unsigned uMethod, uint64_t *poffActual) +{ + static ULONG aulSeekRecode[] = + { + FILE_BEGIN, + FILE_CURRENT, + FILE_END, + }; + + /* + * Validate input. + */ + if (uMethod > RTFILE_SEEK_END) + { + AssertMsgFailed(("Invalid uMethod=%d\n", uMethod)); + return VERR_INVALID_PARAMETER; + } + + /* + * Execute the seek. + */ + if (MySetFilePointer(hFile, offSeek, poffActual, aulSeekRecode[uMethod])) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + + +RTR3DECL(int) RTFileRead(RTFILE hFile, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + if (cbToRead <= 0) + { + if (pcbRead) + *pcbRead = 0; + return VINF_SUCCESS; + } + ULONG cbToReadAdj = (ULONG)cbToRead; + AssertReturn(cbToReadAdj == cbToRead, VERR_NUMBER_TOO_BIG); + + ULONG cbRead = 0; + if (ReadFile((HANDLE)RTFileToNative(hFile), pvBuf, cbToReadAdj, &cbRead, NULL)) + { + if (pcbRead) + /* Caller can handle partial reads. */ + *pcbRead = cbRead; + else + { + /* Caller expects everything to be read. */ + while (cbToReadAdj > cbRead) + { + ULONG cbReadPart = 0; + if (!ReadFile((HANDLE)RTFileToNative(hFile), (char*)pvBuf + cbRead, cbToReadAdj - cbRead, &cbReadPart, NULL)) + return RTErrConvertFromWin32(GetLastError()); + if (cbReadPart == 0) + return VERR_EOF; + cbRead += cbReadPart; + } + } + return VINF_SUCCESS; + } + + /* + * If it's a console, we might bump into out of memory conditions in the + * ReadConsole call. + */ + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_NOT_ENOUGH_MEMORY) + { + ULONG cbChunk = cbToReadAdj / 2; + if (cbChunk > 16*_1K) + cbChunk = 16*_1K; + else + cbChunk = RT_ALIGN_32(cbChunk, 256); + + cbRead = 0; + while (cbToReadAdj > cbRead) + { + ULONG cbToReadNow = RT_MIN(cbChunk, cbToReadAdj - cbRead); + ULONG cbReadPart = 0; + if (!ReadFile((HANDLE)RTFileToNative(hFile), (char *)pvBuf + cbRead, cbToReadNow, &cbReadPart, NULL)) + { + /* If we failed because the buffer is too big, shrink it and + try again. */ + dwErr = GetLastError(); + if ( dwErr == ERROR_NOT_ENOUGH_MEMORY + && cbChunk > 8) + { + cbChunk /= 2; + continue; + } + return RTErrConvertFromWin32(dwErr); + } + cbRead += cbReadPart; + + /* Return if the caller can handle partial reads, otherwise try + fill the buffer all the way up. */ + if (pcbRead) + { + *pcbRead = cbRead; + break; + } + if (cbReadPart == 0) + return VERR_EOF; + } + return VINF_SUCCESS; + } + + return RTErrConvertFromWin32(dwErr); +} + + +RTDECL(int) RTFileReadAt(RTFILE hFile, RTFOFF off, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + ULONG cbToReadAdj = (ULONG)cbToRead; + AssertReturn(cbToReadAdj == cbToRead, VERR_NUMBER_TOO_BIG); + + OVERLAPPED Overlapped; + Overlapped.Offset = (uint32_t)off; + Overlapped.OffsetHigh = (uint32_t)(off >> 32); + Overlapped.hEvent = NULL; + Overlapped.Internal = 0; + Overlapped.InternalHigh = 0; + + ULONG cbRead = 0; + if (ReadFile((HANDLE)RTFileToNative(hFile), pvBuf, cbToReadAdj, &cbRead, &Overlapped)) + { + if (pcbRead) + /* Caller can handle partial reads. */ + *pcbRead = cbRead; + else + { + /* Caller expects everything to be read. */ + while (cbToReadAdj > cbRead) + { + Overlapped.Offset = (uint32_t)(off + cbRead); + Overlapped.OffsetHigh = (uint32_t)((off + cbRead) >> 32); + Overlapped.hEvent = NULL; + Overlapped.Internal = 0; + Overlapped.InternalHigh = 0; + + ULONG cbReadPart = 0; + if (!ReadFile((HANDLE)RTFileToNative(hFile), (char *)pvBuf + cbRead, cbToReadAdj - cbRead, + &cbReadPart, &Overlapped)) + return RTErrConvertFromWin32(GetLastError()); + if (cbReadPart == 0) + return VERR_EOF; + cbRead += cbReadPart; + } + } + return VINF_SUCCESS; + } + + /* We will get an EOF error when using overlapped I/O. So, make sure we don't + return it when pcbhRead is not NULL. */ + DWORD dwErr = GetLastError(); + if (pcbRead && dwErr == ERROR_HANDLE_EOF) + { + *pcbRead = 0; + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(dwErr); +} + + +RTR3DECL(int) RTFileWrite(RTFILE hFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + if (cbToWrite <= 0) + return VINF_SUCCESS; + ULONG const cbToWriteAdj = (ULONG)cbToWrite; + AssertReturn(cbToWriteAdj == cbToWrite, VERR_NUMBER_TOO_BIG); + + ULONG cbWritten = 0; + if (WriteFile((HANDLE)RTFileToNative(hFile), pvBuf, cbToWriteAdj, &cbWritten, NULL)) + { + if (pcbWritten) + /* Caller can handle partial writes. */ + *pcbWritten = RT_MIN(cbWritten, cbToWriteAdj); /* paranoia^3 */ + else + { + /* Caller expects everything to be written. */ + while (cbWritten < cbToWriteAdj) + { + ULONG cbWrittenPart = 0; + if (!WriteFile((HANDLE)RTFileToNative(hFile), (char*)pvBuf + cbWritten, + cbToWriteAdj - cbWritten, &cbWrittenPart, NULL)) + { + int rc = RTErrConvertFromWin32(GetLastError()); + if (rc == VERR_DISK_FULL) + rc = rtFileWinCheckIfDiskReallyFull(hFile, RTFileTell(hFile) + cbToWriteAdj - cbWritten); + return rc; + } + if (cbWrittenPart == 0) + return VERR_WRITE_ERROR; + cbWritten += cbWrittenPart; + } + } + return VINF_SUCCESS; + } + + /* + * If it's a console, we might bump into out of memory conditions in the + * WriteConsole call. + */ + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_NOT_ENOUGH_MEMORY) + { + ULONG cbChunk = cbToWriteAdj / 2; + if (cbChunk > _32K) + cbChunk = _32K; + else + cbChunk = RT_ALIGN_32(cbChunk, 256); + + cbWritten = 0; + while (cbWritten < cbToWriteAdj) + { + ULONG cbToWriteNow = RT_MIN(cbChunk, cbToWriteAdj - cbWritten); + ULONG cbWrittenPart = 0; + if (!WriteFile((HANDLE)RTFileToNative(hFile), (const char *)pvBuf + cbWritten, cbToWriteNow, &cbWrittenPart, NULL)) + { + /* If we failed because the buffer is too big, shrink it and + try again. */ + dwErr = GetLastError(); + if ( dwErr == ERROR_NOT_ENOUGH_MEMORY + && cbChunk > 8) + { + cbChunk /= 2; + continue; + } + int rc = RTErrConvertFromWin32(dwErr); + if (rc == VERR_DISK_FULL) + rc = rtFileWinCheckIfDiskReallyFull(hFile, RTFileTell(hFile) + cbToWriteNow); + return rc; + } + cbWritten += cbWrittenPart; + + /* Return if the caller can handle partial writes, otherwise try + write out everything. */ + if (pcbWritten) + { + *pcbWritten = RT_MIN(cbWritten, cbToWriteAdj); /* paranoia^3 */ + break; + } + if (cbWrittenPart == 0) + return VERR_WRITE_ERROR; + } + return VINF_SUCCESS; + } + + int rc = RTErrConvertFromWin32(dwErr); + if (rc == VERR_DISK_FULL) + rc = rtFileWinCheckIfDiskReallyFull(hFile, RTFileTell(hFile) + cbToWriteAdj); + return rc; +} + + +RTDECL(int) RTFileWriteAt(RTFILE hFile, RTFOFF off, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + ULONG const cbToWriteAdj = (ULONG)cbToWrite; + AssertReturn(cbToWriteAdj == cbToWrite, VERR_NUMBER_TOO_BIG); + + OVERLAPPED Overlapped; + Overlapped.Offset = (uint32_t)off; + Overlapped.OffsetHigh = (uint32_t)(off >> 32); + Overlapped.hEvent = NULL; + Overlapped.Internal = 0; + Overlapped.InternalHigh = 0; + + ULONG cbWritten = 0; + if (WriteFile((HANDLE)RTFileToNative(hFile), pvBuf, cbToWriteAdj, &cbWritten, &Overlapped)) + { + if (pcbWritten) + /* Caller can handle partial writes. */ + *pcbWritten = RT_MIN(cbWritten, cbToWriteAdj); /* paranoia^3 */ + else + { + /* Caller expects everything to be written. */ + while (cbWritten < cbToWriteAdj) + { + Overlapped.Offset = (uint32_t)(off + cbWritten); + Overlapped.OffsetHigh = (uint32_t)((off + cbWritten) >> 32); + Overlapped.hEvent = NULL; + Overlapped.Internal = 0; + Overlapped.InternalHigh = 0; + + ULONG cbWrittenPart = 0; + if (!WriteFile((HANDLE)RTFileToNative(hFile), (char*)pvBuf + cbWritten, + cbToWriteAdj - cbWritten, &cbWrittenPart, &Overlapped)) + { + int rc = RTErrConvertFromWin32(GetLastError()); + if (rc == VERR_DISK_FULL) + rc = rtFileWinCheckIfDiskReallyFull(hFile, off + cbToWriteAdj); + return rc; + } + if (cbWrittenPart == 0) + return VERR_WRITE_ERROR; + cbWritten += cbWrittenPart; + } + } + return VINF_SUCCESS; + } + + int rc = RTErrConvertFromWin32(GetLastError()); + if (rc == VERR_DISK_FULL) + rc = rtFileWinCheckIfDiskReallyFull(hFile, off + cbToWriteAdj); + return rc; +} + + +RTR3DECL(int) RTFileFlush(RTFILE hFile) +{ + if (!FlushFileBuffers((HANDLE)RTFileToNative(hFile))) + { + int rc = GetLastError(); + Log(("FlushFileBuffers failed with %d\n", rc)); + return RTErrConvertFromWin32(rc); + } + return VINF_SUCCESS; +} + +#if 1 + +/** + * Checks the the two handles refers to the same file. + * + * @returns true if the same file, false if different ones or invalid handles. + * @param hFile1 Handle \#1. + * @param hFile2 Handle \#2. + */ +static bool rtFileIsSame(HANDLE hFile1, HANDLE hFile2) +{ + /* + * We retry in case CreationTime or the Object ID is being modified and there + * aren't any IndexNumber (file ID) on this kind of file system. + */ + for (uint32_t iTries = 0; iTries < 3; iTries++) + { + /* + * Fetch data to compare (being a little lazy here). + */ + struct + { + HANDLE hFile; + NTSTATUS rcObjId; + FILE_OBJECTID_INFORMATION ObjId; + FILE_ALL_INFORMATION All; + FILE_FS_VOLUME_INFORMATION Vol; + } auData[2]; + auData[0].hFile = hFile1; + auData[1].hFile = hFile2; + + for (uintptr_t i = 0; i < RT_ELEMENTS(auData); i++) + { + RT_ZERO(auData[i].ObjId); + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + auData[i].rcObjId = NtQueryInformationFile(auData[i].hFile, &Ios, &auData[i].ObjId, sizeof(auData[i].ObjId), + FileObjectIdInformation); + + RT_ZERO(auData[i].All); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + NTSTATUS rcNt = NtQueryInformationFile(auData[i].hFile, &Ios, &auData[i].All, sizeof(auData[i].All), + FileAllInformation); + AssertReturn(rcNt == STATUS_BUFFER_OVERFLOW /* insufficient space for name info */ || NT_SUCCESS(rcNt), false); + + union + { + FILE_FS_VOLUME_INFORMATION Info; + uint8_t abBuf[sizeof(FILE_FS_VOLUME_INFORMATION) + 4096]; + } uVol; + RT_ZERO(uVol.Info); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtQueryVolumeInformationFile(auData[i].hFile, &Ios, &uVol, sizeof(uVol), FileFsVolumeInformation); + if (NT_SUCCESS(rcNt)) + auData[i].Vol = uVol.Info; + else + RT_ZERO(auData[i].Vol); + } + + /* + * Compare it. + */ + if ( auData[0].All.StandardInformation.Directory + == auData[1].All.StandardInformation.Directory) + { /* likely */ } + else + break; + + if ( (auData[0].All.BasicInformation.FileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT)) + == (auData[1].All.BasicInformation.FileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT))) + { /* likely */ } + else + break; + + if ( auData[0].Vol.VolumeSerialNumber + == auData[1].Vol.VolumeSerialNumber) + { /* likely */ } + else + break; + + if ( auData[0].All.InternalInformation.IndexNumber.QuadPart + == auData[1].All.InternalInformation.IndexNumber.QuadPart) + { /* likely */ } + else + break; + + if ( !NT_SUCCESS(auData[0].rcObjId) + || memcmp(&auData[0].ObjId, &auData[1].ObjId, RT_UOFFSETOF(FILE_OBJECTID_INFORMATION, ExtendedInfo)) == 0) + { + if ( auData[0].All.BasicInformation.CreationTime.QuadPart + == auData[1].All.BasicInformation.CreationTime.QuadPart) + return true; + } + } + + return false; +} + + +/** + * If @a hFile is opened in append mode, try return a handle with + * FILE_WRITE_DATA permissions. + * + * @returns Duplicate handle. + * @param hFile The NT handle to check & duplicate. + * + * @todo It would be much easier to implement this by not dropping the + * FILE_WRITE_DATA access and instead have the RTFileWrite APIs + * enforce the appending. That will require keeping additional + * information along side the handle (instance structure). However, on + * windows you can grant append permissions w/o giving people access to + * overwrite existing data, so the RTFileOpenEx code would have to deal + * with those kinds of STATUS_ACCESS_DENIED too then. + */ +static HANDLE rtFileReOpenAppendOnlyWithFullWriteAccess(HANDLE hFile) +{ + OBJECT_BASIC_INFORMATION BasicInfo = {0}; + ULONG cbActual = 0; + NTSTATUS rcNt = NtQueryObject(hFile, ObjectBasicInformation, &BasicInfo, sizeof(BasicInfo), &cbActual); + if (NT_SUCCESS(rcNt)) + { + if ((BasicInfo.GrantedAccess & (FILE_APPEND_DATA | FILE_WRITE_DATA)) == FILE_APPEND_DATA) + { + /* + * We cannot use NtDuplicateObject here as it is not possible to + * upgrade the access on files, only making it more strict. So, + * query the path and re-open it (we could do by file/object/whatever + * id too, but that may not work with all file systems). + */ + for (uint32_t i = 0; i < 16; i++) + { + UNICODE_STRING NtName; + int rc = RTNtPathFromHandle(&NtName, hFile, 0); + AssertRCReturn(rc, INVALID_HANDLE_VALUE); + + HANDLE hDupFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, BasicInfo.Attributes & ~OBJ_INHERIT, NULL, NULL); + + rcNt = NtCreateFile(&hDupFile, + BasicInfo.GrantedAccess | FILE_WRITE_DATA, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT /*??*/, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + RTUtf16Free(NtName.Buffer); + if (NT_SUCCESS(rcNt)) + { + /* + * Check that we've opened the same file. + */ + if (rtFileIsSame(hFile, hDupFile)) + return hDupFile; + NtClose(hDupFile); + } + } + AssertFailed(); + } + } + return INVALID_HANDLE_VALUE; +} + +#endif + +RTR3DECL(int) RTFileSetSize(RTFILE hFile, uint64_t cbSize) +{ +#if 1 + HANDLE hNtFile = (HANDLE)RTFileToNative(hFile); + HANDLE hDupFile = INVALID_HANDLE_VALUE; + union + { + FILE_END_OF_FILE_INFORMATION Eof; + FILE_ALLOCATION_INFORMATION Alloc; + } uInfo; + + /* + * Change the EOF marker. + * + * HACK ALERT! If the file was opened in RTFILE_O_APPEND mode, we will have + * to re-open it with FILE_WRITE_DATA access to get the job done. + * This how ftruncate on a unixy system would work but not how + * it is done on Windows where appending is a separate permission + * rather than just a write modifier, making this hack totally wrong. + */ + /** @todo The right way to fix this is either to add a RTFileSetSizeEx function + * for specifically requesting the unixy behaviour, or add an additional + * flag to RTFileOpen[Ex] to request the unixy append behaviour there. + * The latter would require saving the open flags in a instance data + * structure, which is a bit of a risky move, though something we should + * do in 6.2 (or later). + * + * Note! Because handle interitance, it is not realyan option to + * always use FILE_WRITE_DATA and implement the RTFILE_O_APPEND + * bits in RTFileWrite and friends. Besides, it's not like + * RTFILE_O_APPEND is so clearly defined anyway - see + * RTFileWriteAt. + */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + uInfo.Eof.EndOfFile.QuadPart = cbSize; + NTSTATUS rcNt = NtSetInformationFile(hNtFile, &Ios, &uInfo.Eof, sizeof(uInfo.Eof), FileEndOfFileInformation); + if (rcNt == STATUS_ACCESS_DENIED) + { + hDupFile = rtFileReOpenAppendOnlyWithFullWriteAccess(hNtFile); + if (hDupFile != INVALID_HANDLE_VALUE) + { + hNtFile = hDupFile; + uInfo.Eof.EndOfFile.QuadPart = cbSize; + rcNt = NtSetInformationFile(hNtFile, &Ios, &uInfo.Eof, sizeof(uInfo.Eof), FileEndOfFileInformation); + } + } + + if (NT_SUCCESS(rcNt)) + { + /* + * Change the allocation. + */ + uInfo.Alloc.AllocationSize.QuadPart = cbSize; + rcNt = NtSetInformationFile(hNtFile, &Ios, &uInfo.Eof, sizeof(uInfo.Alloc), FileAllocationInformation); + } + + /* + * Close the temporary file handle: + */ + if (hDupFile != INVALID_HANDLE_VALUE) + NtClose(hDupFile); + + if (RT_SUCCESS(rcNt)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(rcNt); + +#else /* this version of the code will fail to truncate files when RTFILE_O_APPEND is in effect, which isn't what we want... */ + /* + * Get current file pointer. + */ + int rc; + uint64_t offCurrent; + if (MySetFilePointer(hFile, 0, &offCurrent, FILE_CURRENT)) + { + /* + * Set new file pointer. + */ + if (MySetFilePointer(hFile, cbSize, NULL, FILE_BEGIN)) + { + /* set file pointer */ + if (SetEndOfFile((HANDLE)RTFileToNative(hFile))) + { + /* + * Restore file pointer and return. + * If the old pointer was beyond the new file end, ignore failure. + */ + if ( MySetFilePointer(hFile, offCurrent, NULL, FILE_BEGIN) + || offCurrent > cbSize) + return VINF_SUCCESS; + } + + /* + * Failed, try restoring the file pointer. + */ + rc = GetLastError(); + MySetFilePointer(hFile, offCurrent, NULL, FILE_BEGIN); + + if (rc == ERROR_DISK_FULL) + return rtFileWinCheckIfDiskReallyFull(hFile, cbSize); + } + else + rc = GetLastError(); + } + else + rc = GetLastError(); + + return RTErrConvertFromWin32(rc); +#endif +} + + +RTR3DECL(int) RTFileQuerySize(RTFILE hFile, uint64_t *pcbSize) +{ + /* + * GetFileSize works for most handles. + */ + ULARGE_INTEGER Size; + Size.LowPart = GetFileSize((HANDLE)RTFileToNative(hFile), &Size.HighPart); + if (Size.LowPart != INVALID_FILE_SIZE) + { + *pcbSize = Size.QuadPart; + return VINF_SUCCESS; + } + int rc = RTErrConvertFromWin32(GetLastError()); + + /* + * Could it be a volume or a disk? + */ + DISK_GEOMETRY DriveGeo; + DWORD cbDriveGeo; + if (DeviceIoControl((HANDLE)RTFileToNative(hFile), + IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, + &DriveGeo, sizeof(DriveGeo), &cbDriveGeo, NULL)) + { + if ( DriveGeo.MediaType == FixedMedia + || DriveGeo.MediaType == RemovableMedia) + { + *pcbSize = DriveGeo.Cylinders.QuadPart + * DriveGeo.TracksPerCylinder + * DriveGeo.SectorsPerTrack + * DriveGeo.BytesPerSector; + + GET_LENGTH_INFORMATION DiskLenInfo; + DWORD Ignored; + if (DeviceIoControl((HANDLE)RTFileToNative(hFile), + IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, + &DiskLenInfo, sizeof(DiskLenInfo), &Ignored, (LPOVERLAPPED)NULL)) + { + /* IOCTL_DISK_GET_LENGTH_INFO is supported -- override cbSize. */ + *pcbSize = DiskLenInfo.Length.QuadPart; + } + return VINF_SUCCESS; + } + } + + /* + * Return the GetFileSize result if not a volume/disk. + */ + return rc; +} + + +RTR3DECL(int) RTFileQueryMaxSizeEx(RTFILE hFile, PRTFOFF pcbMax) +{ + /** @todo r=bird: + * We might have to make this code OS version specific... In the worse + * case, we'll have to try GetVolumeInformationByHandle on vista and fall + * back on NtQueryVolumeInformationFile(,,,, FileFsAttributeInformation) + * else where, and check for known file system names. (For LAN shares we'll + * have to figure out the remote file system.) */ + RT_NOREF_PV(hFile); RT_NOREF_PV(pcbMax); + return VERR_NOT_IMPLEMENTED; +} + + +RTR3DECL(bool) RTFileIsValid(RTFILE hFile) +{ + if (hFile != NIL_RTFILE) + { + DWORD dwType = GetFileType((HANDLE)RTFileToNative(hFile)); + switch (dwType) + { + case FILE_TYPE_CHAR: + case FILE_TYPE_DISK: + case FILE_TYPE_PIPE: + case FILE_TYPE_REMOTE: + return true; + + case FILE_TYPE_UNKNOWN: + if (GetLastError() == NO_ERROR) + return true; + break; + + default: + break; + } + } + return false; +} + + +#define LOW_DWORD(u64) ((DWORD)u64) +#define HIGH_DWORD(u64) (((DWORD *)&u64)[1]) + +RTR3DECL(int) RTFileLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* Check arguments. */ + if (fLock & ~RTFILE_LOCK_MASK) + { + AssertMsgFailed(("Invalid fLock=%08X\n", fLock)); + return VERR_INVALID_PARAMETER; + } + + /* Prepare flags. */ + Assert(RTFILE_LOCK_WRITE); + DWORD dwFlags = (fLock & RTFILE_LOCK_WRITE) ? LOCKFILE_EXCLUSIVE_LOCK : 0; + Assert(RTFILE_LOCK_WAIT); + if (!(fLock & RTFILE_LOCK_WAIT)) + dwFlags |= LOCKFILE_FAIL_IMMEDIATELY; + + /* Windows structure. */ + OVERLAPPED Overlapped; + memset(&Overlapped, 0, sizeof(Overlapped)); + Overlapped.Offset = LOW_DWORD(offLock); + Overlapped.OffsetHigh = HIGH_DWORD(offLock); + + /* Note: according to Microsoft, LockFileEx API call is available starting from NT 3.5 */ + if (LockFileEx((HANDLE)RTFileToNative(hFile), dwFlags, 0, LOW_DWORD(cbLock), HIGH_DWORD(cbLock), &Overlapped)) + return VINF_SUCCESS; + + return RTErrConvertFromWin32(GetLastError()); +} + + +RTR3DECL(int) RTFileChangeLock(RTFILE hFile, unsigned fLock, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + /* Check arguments. */ + if (fLock & ~RTFILE_LOCK_MASK) + { + AssertMsgFailed(("Invalid fLock=%08X\n", fLock)); + return VERR_INVALID_PARAMETER; + } + + /* Remove old lock. */ + int rc = RTFileUnlock(hFile, offLock, cbLock); + if (RT_FAILURE(rc)) + return rc; + + /* Set new lock. */ + rc = RTFileLock(hFile, fLock, offLock, cbLock); + if (RT_SUCCESS(rc)) + return rc; + + /* Try to restore old lock. */ + unsigned fLockOld = (fLock & RTFILE_LOCK_WRITE) ? fLock & ~RTFILE_LOCK_WRITE : fLock | RTFILE_LOCK_WRITE; + rc = RTFileLock(hFile, fLockOld, offLock, cbLock); + if (RT_SUCCESS(rc)) + return VERR_FILE_LOCK_VIOLATION; + else + return VERR_FILE_LOCK_LOST; +} + + +RTR3DECL(int) RTFileUnlock(RTFILE hFile, int64_t offLock, uint64_t cbLock) +{ + Assert(offLock >= 0); + + if (UnlockFile((HANDLE)RTFileToNative(hFile), + LOW_DWORD(offLock), HIGH_DWORD(offLock), + LOW_DWORD(cbLock), HIGH_DWORD(cbLock))) + return VINF_SUCCESS; + + return RTErrConvertFromWin32(GetLastError()); +} + + + +RTR3DECL(int) RTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + /* + * Validate input. + */ + if (hFile == NIL_RTFILE) + { + AssertMsgFailed(("Invalid hFile=%RTfile\n", hFile)); + return VERR_INVALID_PARAMETER; + } + if (!pObjInfo) + { + AssertMsgFailed(("Invalid pObjInfo=%p\n", pObjInfo)); + return VERR_INVALID_PARAMETER; + } + if ( enmAdditionalAttribs < RTFSOBJATTRADD_NOTHING + || enmAdditionalAttribs > RTFSOBJATTRADD_LAST) + { + AssertMsgFailed(("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs)); + return VERR_INVALID_PARAMETER; + } + + /* + * Query file info. + */ + HANDLE hHandle = (HANDLE)RTFileToNative(hFile); +#if 1 + uint64_t auBuf[168 / sizeof(uint64_t)]; /* Missing FILE_ALL_INFORMATION here. */ + int rc = rtPathNtQueryInfoFromHandle(hFile, auBuf, sizeof(auBuf), pObjInfo, enmAdditionalAttribs, NULL, 0); + if (RT_SUCCESS(rc)) + return rc; + + /* + * Console I/O handles make trouble here. On older windows versions they + * end up with ERROR_INVALID_HANDLE when handed to the above API, while on + * more recent ones they cause different errors to appear. + * + * Thus, we must ignore the latter and doubly verify invalid handle claims. + * We use the undocumented VerifyConsoleIoHandle to do this, falling back on + * GetFileType should it not be there. + */ + if ( rc == VERR_INVALID_HANDLE + || rc == VERR_ACCESS_DENIED + || rc == VERR_UNEXPECTED_FS_OBJ_TYPE) + { + static PFNVERIFYCONSOLEIOHANDLE s_pfnVerifyConsoleIoHandle = NULL; + static bool volatile s_fInitialized = false; + PFNVERIFYCONSOLEIOHANDLE pfnVerifyConsoleIoHandle; + if (s_fInitialized) + pfnVerifyConsoleIoHandle = s_pfnVerifyConsoleIoHandle; + else + { + pfnVerifyConsoleIoHandle = (PFNVERIFYCONSOLEIOHANDLE)RTLdrGetSystemSymbol("kernel32.dll", "VerifyConsoleIoHandle"); + ASMAtomicWriteBool(&s_fInitialized, true); + } + if ( pfnVerifyConsoleIoHandle + ? !pfnVerifyConsoleIoHandle(hHandle) + : GetFileType(hHandle) == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) + return VERR_INVALID_HANDLE; + } + /* + * On Windows 10 and (hopefully) 8.1 we get ERROR_INVALID_FUNCTION with console + * I/O handles and null device handles. We must ignore these just like the + * above invalid handle error. + */ + else if (rc != VERR_INVALID_FUNCTION && rc != VERR_IO_BAD_COMMAND) + return rc; + + RT_ZERO(*pObjInfo); + pObjInfo->Attr.enmAdditional = enmAdditionalAttribs; + pObjInfo->Attr.fMode = rtFsModeFromDos(RTFS_DOS_NT_DEVICE, "", 0, 0, 0); + return VINF_SUCCESS; +#else + + BY_HANDLE_FILE_INFORMATION Data; + if (!GetFileInformationByHandle(hHandle, &Data)) + { + /* + * Console I/O handles make trouble here. On older windows versions they + * end up with ERROR_INVALID_HANDLE when handed to the above API, while on + * more recent ones they cause different errors to appear. + * + * Thus, we must ignore the latter and doubly verify invalid handle claims. + * We use the undocumented VerifyConsoleIoHandle to do this, falling back on + * GetFileType should it not be there. + */ + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_INVALID_HANDLE) + { + static PFNVERIFYCONSOLEIOHANDLE s_pfnVerifyConsoleIoHandle = NULL; + static bool volatile s_fInitialized = false; + PFNVERIFYCONSOLEIOHANDLE pfnVerifyConsoleIoHandle; + if (s_fInitialized) + pfnVerifyConsoleIoHandle = s_pfnVerifyConsoleIoHandle; + else + { + pfnVerifyConsoleIoHandle = (PFNVERIFYCONSOLEIOHANDLE)RTLdrGetSystemSymbol("kernel32.dll", "VerifyConsoleIoHandle"); + ASMAtomicWriteBool(&s_fInitialized, true); + } + if ( pfnVerifyConsoleIoHandle + ? !pfnVerifyConsoleIoHandle(hHandle) + : GetFileType(hHandle) == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) + return VERR_INVALID_HANDLE; + } + /* + * On Windows 10 and (hopefully) 8.1 we get ERROR_INVALID_FUNCTION with console I/O + * handles. We must ignore these just like the above invalid handle error. + */ + else if (dwErr != ERROR_INVALID_FUNCTION) + return RTErrConvertFromWin32(dwErr); + + RT_ZERO(Data); + Data.dwFileAttributes = RTFS_DOS_NT_DEVICE; + } + + /* + * Setup the returned data. + */ + pObjInfo->cbObject = ((uint64_t)Data.nFileSizeHigh << 32) + | (uint64_t)Data.nFileSizeLow; + pObjInfo->cbAllocated = pObjInfo->cbObject; + + Assert(sizeof(uint64_t) == sizeof(Data.ftCreationTime)); + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, *(uint64_t *)&Data.ftCreationTime); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, *(uint64_t *)&Data.ftLastAccessTime); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, *(uint64_t *)&Data.ftLastWriteTime); + pObjInfo->ChangeTime = pObjInfo->ModificationTime; + + pObjInfo->Attr.fMode = rtFsModeFromDos((Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, "", 0, + RTFSMODE_SYMLINK_REPARSE_TAG /* (symlink or not, doesn't usually matter here) */); + + /* + * Requested attributes (we cannot provide anything actually). + */ + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_NOTHING: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + break; + + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pObjInfo->Attr.u.Unix.uid = ~0U; + pObjInfo->Attr.u.Unix.gid = ~0U; + pObjInfo->Attr.u.Unix.cHardlinks = Data.nNumberOfLinks ? Data.nNumberOfLinks : 1; + pObjInfo->Attr.u.Unix.INodeIdDevice = Data.dwVolumeSerialNumber; + pObjInfo->Attr.u.Unix.INodeId = RT_MAKE_U64(Data.nFileIndexLow, Data.nFileIndexHigh); + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pObjInfo->Attr.u.UnixOwner.uid = ~0U; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pObjInfo->Attr.u.UnixGroup.gid = ~0U; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR; + } + + return VINF_SUCCESS; +#endif +} + + +RTR3DECL(int) RTFileSetTimes(RTFILE hFile, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + RT_NOREF_PV(pChangeTime); /* Not exposed thru the windows API we're using. */ + + if (!pAccessTime && !pModificationTime && !pBirthTime) + return VINF_SUCCESS; /* NOP */ + + FILETIME CreationTimeFT; + PFILETIME pCreationTimeFT = NULL; + if (pBirthTime) + pCreationTimeFT = RTTimeSpecGetNtFileTime(pBirthTime, &CreationTimeFT); + + FILETIME LastAccessTimeFT; + PFILETIME pLastAccessTimeFT = NULL; + if (pAccessTime) + pLastAccessTimeFT = RTTimeSpecGetNtFileTime(pAccessTime, &LastAccessTimeFT); + + FILETIME LastWriteTimeFT; + PFILETIME pLastWriteTimeFT = NULL; + if (pModificationTime) + pLastWriteTimeFT = RTTimeSpecGetNtFileTime(pModificationTime, &LastWriteTimeFT); + + int rc = VINF_SUCCESS; + if (!SetFileTime((HANDLE)RTFileToNative(hFile), pCreationTimeFT, pLastAccessTimeFT, pLastWriteTimeFT)) + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("RTFileSetTimes(%RTfile, %p, %p, %p, %p): SetFileTime failed with lasterr %d (%Rrc)\n", + hFile, pAccessTime, pModificationTime, pChangeTime, pBirthTime, Err, rc)); + } + return rc; +} + + +#if 0 /* RTFileSetMode is implemented by RTFileSetMode-r3-nt.cpp */ +/* This comes from a source file with a different set of system headers (DDK) + * so it can't be declared in a common header, like internal/file.h. + */ +extern int rtFileNativeSetAttributes(HANDLE FileHandle, ULONG FileAttributes); + + +RTR3DECL(int) RTFileSetMode(RTFILE hFile, RTFMODE fMode) +{ + /* + * Normalize the mode and call the API. + */ + fMode = rtFsModeNormalize(fMode, NULL, 0); + if (!rtFsModeIsValid(fMode)) + return VERR_INVALID_PARAMETER; + + ULONG FileAttributes = (fMode & RTFS_DOS_MASK) >> RTFS_DOS_SHIFT; + int Err = rtFileNativeSetAttributes((HANDLE)hFile, FileAttributes); + if (Err != ERROR_SUCCESS) + { + int rc = RTErrConvertFromWin32(Err); + Log(("RTFileSetMode(%RTfile, %RTfmode): rtFileNativeSetAttributes (0x%08X) failed with err %d (%Rrc)\n", + hFile, fMode, FileAttributes, Err, rc)); + return rc; + } + return VINF_SUCCESS; +} +#endif + + +/* RTFileQueryFsSizes is implemented by ../nt/RTFileQueryFsSizes-nt.cpp */ + + +RTR3DECL(int) RTFileDelete(const char *pszFilename) +{ + PRTUTF16 pwszFilename; + int rc = RTPathWinFromUtf8(&pwszFilename, pszFilename, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + if (!DeleteFileW(pwszFilename)) + rc = RTErrConvertFromWin32(GetLastError()); + RTPathWinFree(pwszFilename); + } + + return rc; +} + + +RTDECL(int) RTFileRename(const char *pszSrc, const char *pszDst, unsigned fRename) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER); + + /* + * Hand it on to the worker. + */ + int rc = rtPathWin32MoveRename(pszSrc, pszDst, + fRename & RTPATHRENAME_FLAGS_REPLACE ? MOVEFILE_REPLACE_EXISTING : 0, + RTFS_TYPE_FILE); + + LogFlow(("RTFileMove(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", + pszSrc, pszSrc, pszDst, pszDst, fRename, rc)); + return rc; + +} + + +RTDECL(int) RTFileMove(const char *pszSrc, const char *pszDst, unsigned fMove) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + AssertMsgReturn(!(fMove & ~RTFILEMOVE_FLAGS_REPLACE), ("%#x\n", fMove), VERR_INVALID_PARAMETER); + + /* + * Hand it on to the worker. + */ + int rc = rtPathWin32MoveRename(pszSrc, pszDst, + fMove & RTFILEMOVE_FLAGS_REPLACE + ? MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING + : MOVEFILE_COPY_ALLOWED, + RTFS_TYPE_FILE); + + LogFlow(("RTFileMove(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", + pszSrc, pszSrc, pszDst, pszDst, fMove, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/fs-win.cpp b/src/VBox/Runtime/r3/win/fs-win.cpp new file mode 100644 index 00000000..4210ffed --- /dev/null +++ b/src/VBox/Runtime/r3/win/fs-win.cpp @@ -0,0 +1,440 @@ +/* $Id: fs-win.cpp $ */ +/** @file + * IPRT - File System, Win32. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <iprt/win/windows.h> + +#include <iprt/fs.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/param.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include "internal/fs.h" + +/* from ntdef.h */ +typedef LONG NTSTATUS; + +/* from ntddk.h */ +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef enum _FSINFOCLASS { + FileFsAttributeInformation = 5, +} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS; + +/* from ntifs.h */ + +typedef struct _FILE_FS_ATTRIBUTE_INFORMATION { + ULONG FileSystemAttributes; + LONG MaximumComponentNameLength; + ULONG FileSystemNameLength; + WCHAR FileSystemName[1]; +} FILE_FS_ATTRIBUTE_INFORMATION, *PFILE_FS_ATTRIBUTE_INFORMATION; + +extern "C" NTSTATUS NTAPI NtQueryVolumeInformationFile(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS); + +/** + * Checks quickly if this is an correct root specification. + * Root specs ends with a slash of some kind. + * + * @returns indicator. + * @param pszFsPath Path to check. + */ +static bool rtFsIsRoot(const char *pszFsPath) +{ + /* + * UNC has exactly two slashes.. + * + * Anything else starting with slashe(s) requires + * expansion and will have to take the long road. + */ + if (RTPATH_IS_SLASH(pszFsPath[0])) + { + if ( !RTPATH_IS_SLASH(pszFsPath[1]) + || RTPATH_IS_SLASH(pszFsPath[2])) + return false; + + /* end of machine name */ + const char *pszSlash = strpbrk(pszFsPath + 2, "\\/"); + if (!pszSlash) + return false; + + /* end of service name. */ + pszSlash = strpbrk(pszSlash + 1, "\\/"); + if (!pszSlash) + return false; + + return pszSlash[1] == '\0'; + } + + /* + * Ok the other alternative is driver letter. + */ + return pszFsPath[0] >= 'A' && pszFsPath[0] <= 'Z' + && pszFsPath[1] == ':' + && RTPATH_IS_SLASH(pszFsPath[2]) + && !pszFsPath[3]; +} + + + +/** + * Finds the root of the specified volume. + * + * @returns iprt status code. + * @param pszFsPath Path within the filesystem. Verified as one byte or more. + * @param ppwszFsRoot Where to store the returned string. Free with rtFsFreeRoot(), + */ +static int rtFsGetRoot(const char *pszFsPath, PRTUTF16 *ppwszFsRoot) +{ + /* + * Do straight forward stuff first, + */ + if (rtFsIsRoot(pszFsPath)) + return RTStrToUtf16(pszFsPath, ppwszFsRoot); + + /* + * Expand and add slash (if required). + */ + char szFullPath[RTPATH_MAX]; + int rc = RTPathAbs(pszFsPath, szFullPath, sizeof(szFullPath)); + if (RT_FAILURE(rc)) + return rc; + size_t cb = strlen(szFullPath); + if (!RTPATH_IS_SLASH(szFullPath[cb - 1])) + { + AssertReturn(cb + 1 < RTPATH_MAX, VERR_FILENAME_TOO_LONG); + szFullPath[cb] = '\\'; + szFullPath[++cb] = '\0'; + } + + /* + * Convert the path. + */ + rc = RTStrToUtf16(szFullPath, ppwszFsRoot); + if (RT_FAILURE(rc)) + return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc; + + /* + * Walk the path until our proper API is happy or there is no more path left. + */ + PRTUTF16 pwszStart = *ppwszFsRoot; + if (!GetVolumeInformationW(pwszStart, NULL, 0, NULL, NULL, 0, NULL, 0)) + { + PRTUTF16 pwszEnd = pwszStart + RTUtf16Len(pwszStart); + PRTUTF16 pwszMin = pwszStart + 2; + do + { + /* Strip off the last path component. */ + while (pwszEnd-- > pwszMin) + if (RTPATH_IS_SLASH(*pwszEnd)) + break; + AssertReturn(pwszEnd >= pwszMin, VERR_INTERNAL_ERROR); /* leaks, but that's irrelevant for an internal error. */ + pwszEnd[1] = '\0'; + } while (!GetVolumeInformationW(pwszStart, NULL, 0, NULL, NULL, 0, NULL, 0)); + } + + return VINF_SUCCESS; +} + +/** + * Frees string returned by rtFsGetRoot(). + */ +static void rtFsFreeRoot(PRTUTF16 pwszFsRoot) +{ + RTUtf16Free(pwszFsRoot); +} + + +RTR3DECL(int) RTFsQuerySizes(const char *pszFsPath, RTFOFF *pcbTotal, RTFOFF *pcbFree, + uint32_t *pcbBlock, uint32_t *pcbSector) +{ + /* + * Validate & get valid root path. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath != '\0', VERR_INVALID_PARAMETER); + PRTUTF16 pwszFsRoot; + int rc = rtFsGetRoot(pszFsPath, &pwszFsRoot); + if (RT_FAILURE(rc)) + return rc; + + /* + * Free and total. + */ + if (pcbTotal || pcbFree) + { + ULARGE_INTEGER cbTotal; + ULARGE_INTEGER cbFree; + if (GetDiskFreeSpaceExW(pwszFsRoot, &cbFree, &cbTotal, NULL)) + { + if (pcbTotal) + *pcbTotal = cbTotal.QuadPart; + if (pcbFree) + *pcbFree = cbFree.QuadPart; + } + else + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("RTFsQuerySizes(%s,): GetDiskFreeSpaceEx failed with lasterr %d (%Rrc)\n", + pszFsPath, Err, rc)); + } + } + + /* + * Block and sector size. + */ + if ( RT_SUCCESS(rc) + && (pcbBlock || pcbSector)) + { + DWORD dwDummy1, dwDummy2; + DWORD cbSector; + DWORD cSectorsPerCluster; + if (GetDiskFreeSpaceW(pwszFsRoot, &cSectorsPerCluster, &cbSector, &dwDummy1, &dwDummy2)) + { + if (pcbBlock) + *pcbBlock = cbSector * cSectorsPerCluster; + if (pcbSector) + *pcbSector = cbSector; + } + else + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("RTFsQuerySizes(%s,): GetDiskFreeSpace failed with lasterr %d (%Rrc)\n", + pszFsPath, Err, rc)); + } + } + + rtFsFreeRoot(pwszFsRoot); + return rc; +} + + +/** + * Query the serial number of a filesystem. + * + * @returns iprt status code. + * @param pszFsPath Path within the mounted filesystem. + * @param pu32Serial Where to store the serial number. + */ +RTR3DECL(int) RTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial) +{ + /* + * Validate & get valid root path. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(pu32Serial, VERR_INVALID_POINTER); + PRTUTF16 pwszFsRoot; + int rc = rtFsGetRoot(pszFsPath, &pwszFsRoot); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do work. + */ + DWORD dwMaxName; + DWORD dwFlags; + DWORD dwSerial; + if (GetVolumeInformationW(pwszFsRoot, NULL, 0, &dwSerial, &dwMaxName, &dwFlags, NULL, 0)) + *pu32Serial = dwSerial; + else + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("RTFsQuerySizes(%s,): GetDiskFreeSpaceEx failed with lasterr %d (%Rrc)\n", + pszFsPath, Err, rc)); + } + + rtFsFreeRoot(pwszFsRoot); + return rc; +} + + +/** + * Query the properties of a mounted filesystem. + * + * @returns iprt status code. + * @param pszFsPath Path within the mounted filesystem. + * @param pProperties Where to store the properties. + */ +RTR3DECL(int) RTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties) +{ + /* + * Validate & get valid root path. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(pProperties, VERR_INVALID_POINTER); + PRTUTF16 pwszFsRoot; + int rc = rtFsGetRoot(pszFsPath, &pwszFsRoot); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do work. + */ + DWORD dwMaxName; + DWORD dwFlags; + DWORD dwSerial; + if (GetVolumeInformationW(pwszFsRoot, NULL, 0, &dwSerial, &dwMaxName, &dwFlags, NULL, 0)) + { + memset(pProperties, 0, sizeof(*pProperties)); + pProperties->cbMaxComponent = dwMaxName; + pProperties->fFileCompression = !!(dwFlags & FILE_FILE_COMPRESSION); + pProperties->fCompressed = !!(dwFlags & FILE_VOLUME_IS_COMPRESSED); + pProperties->fReadOnly = !!(dwFlags & FILE_READ_ONLY_VOLUME); + pProperties->fSupportsUnicode = !!(dwFlags & FILE_UNICODE_ON_DISK); + pProperties->fCaseSensitive = false; /* win32 is case preserving only */ + /** @todo r=bird: What about FILE_CASE_SENSITIVE_SEARCH ? Is this set for NTFS + * as well perchance? If so, better mention it instead of just setting + * fCaseSensitive to false. */ + pProperties->fRemote = false; /* no idea yet */ + } + else + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("RTFsQuerySizes(%s,): GetVolumeInformation failed with lasterr %d (%Rrc)\n", + pszFsPath, Err, rc)); + } + + rtFsFreeRoot(pwszFsRoot); + return rc; +} + + +RTR3DECL(bool) RTFsIsCaseSensitive(const char *pszFsPath) +{ + return false; +} + + +/** + * Internal helper for comparing a WCHAR string with a char string. + * + * @returns @c true if equal, @c false if not. + * @param pwsz1 The first string. + * @param cch1 The length of the first string, in bytes. + * @param psz2 The second string. + * @param cch2 The length of the second string. + */ +static bool rtFsWinAreEqual(WCHAR const *pwsz1, size_t cch1, const char *psz2, size_t cch2) +{ + if (cch1 != cch2 * 2) + return false; + while (cch2-- > 0) + { + unsigned ch1 = *pwsz1++; + unsigned ch2 = (unsigned char)*psz2++; + if (ch1 != ch2) + return false; + } + return true; +} + + +RTR3DECL(int) RTFsQueryType(const char *pszFsPath, PRTFSTYPE penmType) +{ + *penmType = RTFSTYPE_UNKNOWN; + + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath, VERR_INVALID_PARAMETER); + + /* + * Convert the path and try open it. + */ + PRTUTF16 pwszFsPath; + int rc = RTPathWinFromUtf8(&pwszFsPath, pszFsPath, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + HANDLE hFile = CreateFileW(pwszFsPath, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + /* + * Use the NT api directly to get the file system name. + */ + char abBuf[8192]; + IO_STATUS_BLOCK Ios; + NTSTATUS rcNt = NtQueryVolumeInformationFile(hFile, &Ios, + abBuf, sizeof(abBuf), + FileFsAttributeInformation); + if (rcNt >= 0) + { + PFILE_FS_ATTRIBUTE_INFORMATION pFsAttrInfo = (PFILE_FS_ATTRIBUTE_INFORMATION)abBuf; +#define IS_FS(szName) \ + rtFsWinAreEqual(pFsAttrInfo->FileSystemName, pFsAttrInfo->FileSystemNameLength, szName, sizeof(szName) - 1) + if (IS_FS("NTFS")) + *penmType = RTFSTYPE_NTFS; + else if (IS_FS("FAT")) + *penmType = RTFSTYPE_FAT; + else if (IS_FS("FAT32")) + *penmType = RTFSTYPE_FAT; + else if (IS_FS("EXFAT")) + *penmType = RTFSTYPE_EXFAT; + else if (IS_FS("VBoxSharedFolderFS")) + *penmType = RTFSTYPE_VBOXSHF; +#undef IS_FS + } + else + rc = RTErrConvertFromNtStatus(rcNt); + CloseHandle(hFile); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + RTPathWinFree(pwszFsPath); + } + return rc; +} diff --git a/src/VBox/Runtime/r3/win/init-win.cpp b/src/VBox/Runtime/r3/win/init-win.cpp new file mode 100644 index 00000000..92cd2665 --- /dev/null +++ b/src/VBox/Runtime/r3/win/init-win.cpp @@ -0,0 +1,919 @@ +/* $Id: init-win.cpp $ */ +/** @file + * IPRT - Init Ring-3, Windows Specific Code. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/nt/nt-and-windows.h> +#ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR +# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x200 +# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800 +#endif + +#include "internal-r3-win.h" +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include "../init.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef VOID (WINAPI *PFNGETCURRENTTHREADSTACKLIMITS)(PULONG_PTR puLow, PULONG_PTR puHigh); +typedef LPTOP_LEVEL_EXCEPTION_FILTER (WINAPI * PFNSETUNHANDLEDEXCEPTIONFILTER)(LPTOP_LEVEL_EXCEPTION_FILTER); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Windows DLL loader protection level. */ +DECL_HIDDEN_DATA(RTR3WINLDRPROT) g_enmWinLdrProt = RTR3WINLDRPROT_NONE; +/** Our simplified windows version. */ +DECL_HIDDEN_DATA(RTWINOSTYPE) g_enmWinVer = kRTWinOSType_UNKNOWN; +/** Extended windows version information. */ +DECL_HIDDEN_DATA(OSVERSIONINFOEXW) g_WinOsInfoEx; + +/** The native kernel32.dll handle. */ +DECL_HIDDEN_DATA(HMODULE) g_hModKernel32 = NULL; +/** GetSystemWindowsDirectoryW or GetWindowsDirectoryW (NT4). */ +DECL_HIDDEN_DATA(PFNGETWINSYSDIR) g_pfnGetSystemWindowsDirectoryW = NULL; +/** The GetCurrentThreadStackLimits API. */ +static PFNGETCURRENTTHREADSTACKLIMITS g_pfnGetCurrentThreadStackLimits = NULL; +/** The previous unhandled exception filter. */ +static LPTOP_LEVEL_EXCEPTION_FILTER g_pfnUnhandledXcptFilter = NULL; +/** SystemTimeToTzSpecificLocalTime. */ +DECL_HIDDEN_DATA(decltype(SystemTimeToTzSpecificLocalTime) *) g_pfnSystemTimeToTzSpecificLocalTime = NULL; +/** CreateWaitableTimerEx . */ +DECL_HIDDEN_DATA(PFNCREATEWAITABLETIMEREX) g_pfnCreateWaitableTimerExW = NULL; +DECL_HIDDEN_DATA(decltype(GetHandleInformation) *) g_pfnGetHandleInformation = NULL; +DECL_HIDDEN_DATA(decltype(SetHandleInformation) *) g_pfnSetHandleInformation = NULL; +DECL_HIDDEN_DATA(decltype(IsDebuggerPresent) *) g_pfnIsDebuggerPresent = NULL; +DECL_HIDDEN_DATA(decltype(GetSystemTimeAsFileTime) *) g_pfnGetSystemTimeAsFileTime = NULL; +DECL_HIDDEN_DATA(decltype(GetProcessAffinityMask) *) g_pfnGetProcessAffinityMask = NULL; +DECL_HIDDEN_DATA(decltype(SetThreadAffinityMask) *) g_pfnSetThreadAffinityMask = NULL; +DECL_HIDDEN_DATA(decltype(CreateIoCompletionPort) *) g_pfnCreateIoCompletionPort = NULL; +DECL_HIDDEN_DATA(decltype(GetQueuedCompletionStatus) *) g_pfnGetQueuedCompletionStatus = NULL; +DECL_HIDDEN_DATA(decltype(PostQueuedCompletionStatus) *) g_pfnPostQueuedCompletionStatus = NULL; +DECL_HIDDEN_DATA(decltype(IsProcessorFeaturePresent) *) g_pfnIsProcessorFeaturePresent = NULL; +DECL_HIDDEN_DATA(decltype(SetUnhandledExceptionFilter) *) g_pfnSetUnhandledExceptionFilter = NULL; +DECL_HIDDEN_DATA(decltype(UnhandledExceptionFilter) *) g_pfnUnhandledExceptionFilter = NULL; + +/** The native ntdll.dll handle. */ +DECL_HIDDEN_DATA(HMODULE) g_hModNtDll = NULL; +/** NtQueryFullAttributesFile */ +DECL_HIDDEN_DATA(PFNNTQUERYFULLATTRIBUTESFILE) g_pfnNtQueryFullAttributesFile = NULL; +/** NtDuplicateToken (NT 3.51). */ +DECL_HIDDEN_DATA(PFNNTDUPLICATETOKEN) g_pfnNtDuplicateToken = NULL; +/** NtAlertThread (NT 3.51). */ +DECL_HIDDEN_DATA(decltype(NtAlertThread) *) g_pfnNtAlertThread = NULL; + +/** Either ws2_32.dll (NT4+) or wsock32.dll (NT3.x). */ +DECL_HIDDEN_DATA(HMODULE) g_hModWinSock = NULL; +/** Set if we're dealing with old winsock. */ +DECL_HIDDEN_DATA(bool) g_fOldWinSock = false; +/** WSAStartup */ +DECL_HIDDEN_DATA(PFNWSASTARTUP) g_pfnWSAStartup = NULL; +/** WSACleanup */ +DECL_HIDDEN_DATA(PFNWSACLEANUP) g_pfnWSACleanup = NULL; +/** Pointner to WSAGetLastError (for RTErrVarsSave). */ +DECL_HIDDEN_DATA(PFNWSAGETLASTERROR) g_pfnWSAGetLastError = NULL; +/** Pointner to WSASetLastError (for RTErrVarsRestore). */ +DECL_HIDDEN_DATA(PFNWSASETLASTERROR) g_pfnWSASetLastError = NULL; +/** WSACreateEvent */ +DECL_HIDDEN_DATA(PFNWSACREATEEVENT) g_pfnWSACreateEvent = NULL; +/** WSACloseEvent */ +DECL_HIDDEN_DATA(PFNWSACLOSEEVENT) g_pfnWSACloseEvent = NULL; +/** WSASetEvent */ +DECL_HIDDEN_DATA(PFNWSASETEVENT) g_pfnWSASetEvent = NULL; +/** WSAEventSelect */ +DECL_HIDDEN_DATA(PFNWSAEVENTSELECT) g_pfnWSAEventSelect = NULL; +/** WSAEnumNetworkEvents */ +DECL_HIDDEN_DATA(PFNWSAENUMNETWORKEVENTS) g_pfnWSAEnumNetworkEvents = NULL; +/** WSASocketW */ +DECL_HIDDEN_DATA(PFNWSASOCKETW) g_pfnWSASocketW = NULL; +/** WSASend */ +DECL_HIDDEN_DATA(PFNWSASEND) g_pfnWSASend = NULL; +/** socket */ +DECL_HIDDEN_DATA(PFNWINSOCKSOCKET) g_pfnsocket = NULL; +/** closesocket */ +DECL_HIDDEN_DATA(PFNWINSOCKCLOSESOCKET) g_pfnclosesocket = NULL; +/** recv */ +DECL_HIDDEN_DATA(PFNWINSOCKRECV) g_pfnrecv = NULL; +/** send */ +DECL_HIDDEN_DATA(PFNWINSOCKSEND) g_pfnsend = NULL; +/** recvfrom */ +DECL_HIDDEN_DATA(PFNWINSOCKRECVFROM) g_pfnrecvfrom = NULL; +/** sendto */ +DECL_HIDDEN_DATA(PFNWINSOCKSENDTO) g_pfnsendto = NULL; +/** bind */ +DECL_HIDDEN_DATA(PFNWINSOCKBIND) g_pfnbind = NULL; +/** listen */ +DECL_HIDDEN_DATA(PFNWINSOCKLISTEN) g_pfnlisten = NULL; +/** accept */ +DECL_HIDDEN_DATA(PFNWINSOCKACCEPT) g_pfnaccept = NULL; +/** connect */ +DECL_HIDDEN_DATA(PFNWINSOCKCONNECT) g_pfnconnect = NULL; +/** shutdown */ +DECL_HIDDEN_DATA(PFNWINSOCKSHUTDOWN) g_pfnshutdown = NULL; +/** getsockopt */ +DECL_HIDDEN_DATA(PFNWINSOCKGETSOCKOPT) g_pfngetsockopt = NULL; +/** setsockopt */ +DECL_HIDDEN_DATA(PFNWINSOCKSETSOCKOPT) g_pfnsetsockopt = NULL; +/** ioctlsocket */ +DECL_HIDDEN_DATA(PFNWINSOCKIOCTLSOCKET) g_pfnioctlsocket = NULL; +/** getpeername */ +DECL_HIDDEN_DATA(PFNWINSOCKGETPEERNAME) g_pfngetpeername = NULL; +/** getsockname */ +DECL_HIDDEN_DATA(PFNWINSOCKGETSOCKNAME) g_pfngetsockname = NULL; +/** __WSAFDIsSet */ +DECL_HIDDEN_DATA(PFNWINSOCK__WSAFDISSET) g_pfn__WSAFDIsSet = NULL; +/** select */ +DECL_HIDDEN_DATA(PFNWINSOCKSELECT) g_pfnselect = NULL; +/** gethostbyname */ +DECL_HIDDEN_DATA(PFNWINSOCKGETHOSTBYNAME) g_pfngethostbyname = NULL; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static LONG CALLBACK rtR3WinUnhandledXcptFilter(PEXCEPTION_POINTERS); + + +/** + * Translates OSVERSIONINOFEX into a Windows OS type. + * + * @returns The Windows OS type. + * @param pOSInfoEx The OS info returned by Windows. + * + * @remarks This table has been assembled from Usenet postings, personal + * observations, and reading other people's code. Please feel + * free to add to it or correct it. + * <pre> + dwPlatFormID dwMajorVersion dwMinorVersion dwBuildNumber +95 1 4 0 950 +95 SP1 1 4 0 >950 && <=1080 +95 OSR2 1 4 <10 >1080 +98 1 4 10 1998 +98 SP1 1 4 10 >1998 && <2183 +98 SE 1 4 10 >=2183 +ME 1 4 90 3000 + +NT 3.51 2 3 51 1057 +NT 4 2 4 0 1381 +2000 2 5 0 2195 +XP 2 5 1 2600 +2003 2 5 2 3790 +Vista 2 6 0 + +CE 1.0 3 1 0 +CE 2.0 3 2 0 +CE 2.1 3 2 1 +CE 3.0 3 3 0 +</pre> + */ +static RTWINOSTYPE rtR3InitWinSimplifiedVersion(OSVERSIONINFOEXW const *pOSInfoEx) +{ + RTWINOSTYPE enmVer = kRTWinOSType_UNKNOWN; + BYTE const bProductType = pOSInfoEx->wProductType; + DWORD const dwPlatformId = pOSInfoEx->dwPlatformId; + DWORD const dwMinorVersion = pOSInfoEx->dwMinorVersion; + DWORD const dwMajorVersion = pOSInfoEx->dwMajorVersion; + DWORD const dwBuildNumber = pOSInfoEx->dwBuildNumber & 0xFFFF; /* Win 9x needs this. */ + + if ( dwPlatformId == VER_PLATFORM_WIN32_WINDOWS + && dwMajorVersion == 4) + { + if ( dwMinorVersion < 10 + && dwBuildNumber == 950) + enmVer = kRTWinOSType_95; + else if ( dwMinorVersion < 10 + && dwBuildNumber > 950 + && dwBuildNumber <= 1080) + enmVer = kRTWinOSType_95SP1; + else if ( dwMinorVersion < 10 + && dwBuildNumber > 1080) + enmVer = kRTWinOSType_95OSR2; + else if ( dwMinorVersion == 10 + && dwBuildNumber == 1998) + enmVer = kRTWinOSType_98; + else if ( dwMinorVersion == 10 + && dwBuildNumber > 1998 + && dwBuildNumber < 2183) + enmVer = kRTWinOSType_98SP1; + else if ( dwMinorVersion == 10 + && dwBuildNumber >= 2183) + enmVer = kRTWinOSType_98SE; + else if (dwMinorVersion == 90) + enmVer = kRTWinOSType_ME; + } + else if (dwPlatformId == VER_PLATFORM_WIN32_NT) + { + if (dwMajorVersion == 3) + { + if ( dwMinorVersion < 50) + enmVer = kRTWinOSType_NT310; + else if (dwMinorVersion == 50) + enmVer = kRTWinOSType_NT350; + else + enmVer = kRTWinOSType_NT351; + } + else if (dwMajorVersion == 4) + enmVer = kRTWinOSType_NT4; + else if (dwMajorVersion == 5) + { + if (dwMinorVersion == 0) + enmVer = kRTWinOSType_2K; + else if (dwMinorVersion == 1) + enmVer = kRTWinOSType_XP; + else + enmVer = kRTWinOSType_2003; + } + else if (dwMajorVersion == 6) + { + if (dwMinorVersion == 0) + enmVer = bProductType != VER_NT_WORKSTATION ? kRTWinOSType_2008 : kRTWinOSType_VISTA; + else if (dwMinorVersion == 1) + enmVer = bProductType != VER_NT_WORKSTATION ? kRTWinOSType_2008R2 : kRTWinOSType_7; + else if (dwMinorVersion == 2) + enmVer = bProductType != VER_NT_WORKSTATION ? kRTWinOSType_2012 : kRTWinOSType_8; + else if (dwMinorVersion == 3) + enmVer = bProductType != VER_NT_WORKSTATION ? kRTWinOSType_2012R2 : kRTWinOSType_81; + else if (dwMinorVersion == 4) + enmVer = bProductType != VER_NT_WORKSTATION ? kRTWinOSType_2016 : kRTWinOSType_10; + else + enmVer = kRTWinOSType_NT_UNKNOWN; + } + else if (dwMajorVersion == 10) + { + if (dwMinorVersion == 0) + { + /* The version detection for server 2019, server 2022 and windows 11 + are by build number. Stupid, stupid, Microsoft. */ + if (bProductType == VER_NT_WORKSTATION) + enmVer = dwBuildNumber >= 22000 ? kRTWinOSType_11 : kRTWinOSType_10; + else + enmVer = dwBuildNumber >= 20348 ? kRTWinOSType_2022 + : dwBuildNumber >= 17763 ? kRTWinOSType_2019 : kRTWinOSType_2016; + } + else + enmVer = kRTWinOSType_NT_UNKNOWN; + } + else + enmVer = kRTWinOSType_NT_UNKNOWN; + } + + return enmVer; +} + + +/** + * Initializes the global variables related to windows version. + */ +static void rtR3InitWindowsVersion(void) +{ + Assert(g_hModNtDll != NULL); + + /* + * ASSUMES OSVERSIONINFOEX starts with the exact same layout as OSVERSIONINFO (safe). + */ + AssertCompileMembersSameSizeAndOffset(OSVERSIONINFOEX, szCSDVersion, OSVERSIONINFO, szCSDVersion); + AssertCompileMemberOffset(OSVERSIONINFOEX, wServicePackMajor, sizeof(OSVERSIONINFO)); + + /* + * Use the NT version of RtlGetVersion (since w2k) so we don't get fooled + * by the standard compatibility shims. (Sandboxes may still fool us.) + * + * Note! This API was added in windows 2000 together with the extended + * version info structure (OSVERSIONINFOEXW), so there is no need + * to retry with the smaller version (OSVERSIONINFOW). + */ + RT_ZERO(g_WinOsInfoEx); + g_WinOsInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + + LONG (__stdcall *pfnRtlGetVersion)(OSVERSIONINFOEXW *); + *(FARPROC *)&pfnRtlGetVersion = GetProcAddress(g_hModNtDll, "RtlGetVersion"); + LONG rcNt = -1; + if (pfnRtlGetVersion) + rcNt = pfnRtlGetVersion(&g_WinOsInfoEx); + if (rcNt != 0) + { + /* + * Couldn't find it or it failed, try the windows version of the API. + * The GetVersionExW API was added in NT 3.51, however only the small + * structure version existed till windows 2000. We'll try the larger + * structure version first, anyway, just in case. + */ + RT_ZERO(g_WinOsInfoEx); + g_WinOsInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + + BOOL (__stdcall *pfnGetVersionExW)(OSVERSIONINFOW *); + *(FARPROC *)&pfnGetVersionExW = GetProcAddress(g_hModKernel32, "GetVersionExW"); + + if (!pfnGetVersionExW || !pfnGetVersionExW((POSVERSIONINFOW)&g_WinOsInfoEx)) + { + /* + * If that didn't work either, just get the basic version bits. + */ + RT_ZERO(g_WinOsInfoEx); + g_WinOsInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); + if (pfnGetVersionExW && pfnGetVersionExW((POSVERSIONINFOW)&g_WinOsInfoEx)) + Assert(g_WinOsInfoEx.dwPlatformId != VER_PLATFORM_WIN32_NT || g_WinOsInfoEx.dwMajorVersion < 5); + else + { + /* + * Okay, nothing worked, so use GetVersion. + * + * This should only happen if we're on NT 3.1 or NT 3.50. + * It should never happen for 64-bit builds. + */ +#ifdef RT_ARCH_X86 + RT_ZERO(g_WinOsInfoEx); + DWORD const dwVersion = GetVersion(); + + /* Common fields: */ + g_WinOsInfoEx.dwMajorVersion = dwVersion & 0xff; + g_WinOsInfoEx.dwMinorVersion = (dwVersion >> 8) & 0xff; + if (!(dwVersion & RT_BIT_32(31))) + g_WinOsInfoEx.dwBuildNumber = dwVersion >> 16; + else + g_WinOsInfoEx.dwBuildNumber = 511; + g_WinOsInfoEx.dwPlatformId = VER_PLATFORM_WIN32_NT; + /** @todo get CSD from registry. */ +#else + AssertBreakpoint(); + RT_ZERO(g_WinOsInfoEx); +#endif + } + +#ifdef RT_ARCH_X86 + /* + * Fill in some of the extended info too. + */ + g_WinOsInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); /* Pretend. */ + g_WinOsInfoEx.wProductType = VER_NT_WORKSTATION; + NT_PRODUCT_TYPE enmProdType = NtProductWinNt; + if (RtlGetNtProductType(&enmProdType)) + g_WinOsInfoEx.wProductType = (BYTE)enmProdType; + /** @todo parse the CSD string to figure that version. */ +#endif + } + } + + if (g_WinOsInfoEx.dwOSVersionInfoSize) + g_enmWinVer = rtR3InitWinSimplifiedVersion(&g_WinOsInfoEx); +} + + +/** + * Resolves the winsock error APIs. + */ +static void rtR3InitWinSockApis(void) +{ + /* + * Try get ws2_32.dll, then try load it, then finally fall back to the old + * wsock32.dll. We use RTLdrLoadSystem to the loading as it has all the fancy + * logic for safely doing that. + */ + g_hModWinSock = GetModuleHandleW(L"ws2_32.dll"); + if (g_hModWinSock == NULL) + { + RTLDRMOD hLdrMod; + int rc = RTLdrLoadSystem("ws2_32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_FAILURE(rc)) + { + rc = RTLdrLoadSystem("wsock32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("rc=%Rrc\n", rc)); + return; + } + g_fOldWinSock = true; + } + g_hModWinSock = (HMODULE)RTLdrGetNativeHandle(hLdrMod); + RTLdrClose(hLdrMod); + } + + g_pfnWSAStartup = (decltype(g_pfnWSAStartup)) GetProcAddress(g_hModWinSock, "WSAStartup"); + g_pfnWSACleanup = (decltype(g_pfnWSACleanup)) GetProcAddress(g_hModWinSock, "WSACleanup"); + g_pfnWSAGetLastError = (decltype(g_pfnWSAGetLastError)) GetProcAddress(g_hModWinSock, "WSAGetLastError"); + g_pfnWSASetLastError = (decltype(g_pfnWSASetLastError)) GetProcAddress(g_hModWinSock, "WSASetLastError"); + g_pfnWSACreateEvent = (decltype(g_pfnWSACreateEvent)) GetProcAddress(g_hModWinSock, "WSACreateEvent"); + g_pfnWSACloseEvent = (decltype(g_pfnWSACloseEvent)) GetProcAddress(g_hModWinSock, "WSACloseEvent"); + g_pfnWSASetEvent = (decltype(g_pfnWSASetEvent)) GetProcAddress(g_hModWinSock, "WSASetEvent"); + g_pfnWSAEventSelect = (decltype(g_pfnWSAEventSelect)) GetProcAddress(g_hModWinSock, "WSAEventSelect"); + g_pfnWSAEnumNetworkEvents = (decltype(g_pfnWSAEnumNetworkEvents))GetProcAddress(g_hModWinSock,"WSAEnumNetworkEvents"); + g_pfnWSASocketW = (decltype(g_pfnWSASocketW)) GetProcAddress(g_hModWinSock, "WSASocketW"); + g_pfnWSASend = (decltype(g_pfnWSASend)) GetProcAddress(g_hModWinSock, "WSASend"); + g_pfnsocket = (decltype(g_pfnsocket)) GetProcAddress(g_hModWinSock, "socket"); + g_pfnclosesocket = (decltype(g_pfnclosesocket)) GetProcAddress(g_hModWinSock, "closesocket"); + g_pfnrecv = (decltype(g_pfnrecv)) GetProcAddress(g_hModWinSock, "recv"); + g_pfnsend = (decltype(g_pfnsend)) GetProcAddress(g_hModWinSock, "send"); + g_pfnrecvfrom = (decltype(g_pfnrecvfrom)) GetProcAddress(g_hModWinSock, "recvfrom"); + g_pfnsendto = (decltype(g_pfnsendto)) GetProcAddress(g_hModWinSock, "sendto"); + g_pfnbind = (decltype(g_pfnbind)) GetProcAddress(g_hModWinSock, "bind"); + g_pfnlisten = (decltype(g_pfnlisten)) GetProcAddress(g_hModWinSock, "listen"); + g_pfnaccept = (decltype(g_pfnaccept)) GetProcAddress(g_hModWinSock, "accept"); + g_pfnconnect = (decltype(g_pfnconnect)) GetProcAddress(g_hModWinSock, "connect"); + g_pfnshutdown = (decltype(g_pfnshutdown)) GetProcAddress(g_hModWinSock, "shutdown"); + g_pfngetsockopt = (decltype(g_pfngetsockopt)) GetProcAddress(g_hModWinSock, "getsockopt"); + g_pfnsetsockopt = (decltype(g_pfnsetsockopt)) GetProcAddress(g_hModWinSock, "setsockopt"); + g_pfnioctlsocket = (decltype(g_pfnioctlsocket)) GetProcAddress(g_hModWinSock, "ioctlsocket"); + g_pfngetpeername = (decltype(g_pfngetpeername)) GetProcAddress(g_hModWinSock, "getpeername"); + g_pfngetsockname = (decltype(g_pfngetsockname)) GetProcAddress(g_hModWinSock, "getsockname"); + g_pfn__WSAFDIsSet = (decltype(g_pfn__WSAFDIsSet)) GetProcAddress(g_hModWinSock, "__WSAFDIsSet"); + g_pfnselect = (decltype(g_pfnselect)) GetProcAddress(g_hModWinSock, "select"); + g_pfngethostbyname = (decltype(g_pfngethostbyname)) GetProcAddress(g_hModWinSock, "gethostbyname"); + + Assert(g_pfnWSAStartup); + Assert(g_pfnWSACleanup); + Assert(g_pfnWSAGetLastError); + Assert(g_pfnWSASetLastError); + Assert(g_pfnWSACreateEvent || g_fOldWinSock); + Assert(g_pfnWSACloseEvent || g_fOldWinSock); + Assert(g_pfnWSASetEvent || g_fOldWinSock); + Assert(g_pfnWSAEventSelect || g_fOldWinSock); + Assert(g_pfnWSAEnumNetworkEvents || g_fOldWinSock); + Assert(g_pfnWSASocketW || g_fOldWinSock); + Assert(g_pfnWSASend || g_fOldWinSock); + Assert(g_pfnsocket); + Assert(g_pfnclosesocket); + Assert(g_pfnrecv); + Assert(g_pfnsend); + Assert(g_pfnrecvfrom); + Assert(g_pfnsendto); + Assert(g_pfnbind); + Assert(g_pfnlisten); + Assert(g_pfnaccept); + Assert(g_pfnconnect); + Assert(g_pfnshutdown); + Assert(g_pfngetsockopt); + Assert(g_pfnsetsockopt); + Assert(g_pfnioctlsocket); + Assert(g_pfngetpeername); + Assert(g_pfngetsockname); + Assert(g_pfn__WSAFDIsSet); + Assert(g_pfnselect); + Assert(g_pfngethostbyname); +} + + +static int rtR3InitNativeObtrusiveWorker(uint32_t fFlags) +{ + /* + * Disable error popups. + */ + UINT fOldErrMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX | fOldErrMode); + + /* + * Restrict DLL searching for the process on windows versions which allow + * us to do so. + * - The first trick works on XP SP1+ and disables the searching of the + * current directory. + * - The second trick is W7 w/ KB2533623 and W8+, it restrict the DLL + * searching to the application directory (except when + * RTR3INIT_FLAGS_STANDALONE_APP is given) and the System32 directory. + */ + int rc = VINF_SUCCESS; + + typedef BOOL (WINAPI *PFNSETDLLDIRECTORY)(LPCWSTR); + PFNSETDLLDIRECTORY pfnSetDllDir = (PFNSETDLLDIRECTORY)GetProcAddress(g_hModKernel32, "SetDllDirectoryW"); + if (pfnSetDllDir) + { + if (pfnSetDllDir(L"")) + g_enmWinLdrProt = RTR3WINLDRPROT_NO_CWD; + else + rc = VERR_INTERNAL_ERROR_3; + } + + /** @bugref{6861} Observed GUI issues on Vista (32-bit and 64-bit) when using + * SetDefaultDllDirectories. + * @bugref{8194} Try use SetDefaultDllDirectories on Vista for standalone apps + * despite potential GUI issues. */ + if ( g_enmWinVer > kRTWinOSType_VISTA + || (fFlags & RTR3INIT_FLAGS_STANDALONE_APP)) + { + typedef BOOL(WINAPI *PFNSETDEFAULTDLLDIRECTORIES)(DWORD); + PFNSETDEFAULTDLLDIRECTORIES pfnSetDefDllDirs; + pfnSetDefDllDirs = (PFNSETDEFAULTDLLDIRECTORIES)GetProcAddress(g_hModKernel32, "SetDefaultDllDirectories"); + if (pfnSetDefDllDirs) + { + DWORD fDllDirs = LOAD_LIBRARY_SEARCH_SYSTEM32; + if (!(fFlags & RTR3INIT_FLAGS_STANDALONE_APP)) + fDllDirs |= LOAD_LIBRARY_SEARCH_APPLICATION_DIR; + if (pfnSetDefDllDirs(fDllDirs)) + g_enmWinLdrProt = fDllDirs & LOAD_LIBRARY_SEARCH_APPLICATION_DIR ? RTR3WINLDRPROT_SAFE : RTR3WINLDRPROT_SAFER; + else if (RT_SUCCESS(rc)) + rc = VERR_INTERNAL_ERROR_4; + } + } + + /* + * Register an unhandled exception callback if we can. + */ + g_pfnGetCurrentThreadStackLimits = (PFNGETCURRENTTHREADSTACKLIMITS)GetProcAddress(g_hModKernel32, "GetCurrentThreadStackLimits"); + g_pfnSetUnhandledExceptionFilter = (decltype(SetUnhandledExceptionFilter) *)GetProcAddress(g_hModKernel32, "SetUnhandledExceptionFilter"); + g_pfnUnhandledExceptionFilter = (decltype(UnhandledExceptionFilter) *) GetProcAddress(g_hModKernel32, "UnhandledExceptionFilter"); + if (g_pfnSetUnhandledExceptionFilter && !g_pfnUnhandledXcptFilter) + { + g_pfnUnhandledXcptFilter = g_pfnSetUnhandledExceptionFilter(rtR3WinUnhandledXcptFilter); + AssertStmt(g_pfnUnhandledXcptFilter != rtR3WinUnhandledXcptFilter, g_pfnUnhandledXcptFilter = NULL); + } + + return rc; +} + + +DECLHIDDEN(int) rtR3InitNativeFirst(uint32_t fFlags) +{ + /* + * Make sure we've got the handles of the two main Windows NT dlls. + */ + g_hModKernel32 = GetModuleHandleW(L"kernel32.dll"); + if (g_hModKernel32 == NULL) + return VERR_INTERNAL_ERROR_2; + g_hModNtDll = GetModuleHandleW(L"ntdll.dll"); + if (g_hModNtDll == NULL) + return VERR_INTERNAL_ERROR_2; + + rtR3InitWindowsVersion(); + + int rc = VINF_SUCCESS; + if (!(fFlags & RTR3INIT_FLAGS_UNOBTRUSIVE)) + rc = rtR3InitNativeObtrusiveWorker(fFlags); + + /* + * Resolve some kernel32.dll APIs we may need but aren't necessarily + * present in older windows versions. + */ + g_pfnGetSystemWindowsDirectoryW = (PFNGETWINSYSDIR)GetProcAddress(g_hModKernel32, "GetSystemWindowsDirectoryW"); + if (g_pfnGetSystemWindowsDirectoryW) + g_pfnGetSystemWindowsDirectoryW = (PFNGETWINSYSDIR)GetProcAddress(g_hModKernel32, "GetWindowsDirectoryW"); + g_pfnSystemTimeToTzSpecificLocalTime = (decltype(SystemTimeToTzSpecificLocalTime) *)GetProcAddress(g_hModKernel32, "SystemTimeToTzSpecificLocalTime"); + g_pfnCreateWaitableTimerExW = (PFNCREATEWAITABLETIMEREX) GetProcAddress(g_hModKernel32, "CreateWaitableTimerExW"); + g_pfnGetHandleInformation = (decltype(GetHandleInformation) *) GetProcAddress(g_hModKernel32, "GetHandleInformation"); + g_pfnSetHandleInformation = (decltype(SetHandleInformation) *) GetProcAddress(g_hModKernel32, "SetHandleInformation"); + g_pfnIsDebuggerPresent = (decltype(IsDebuggerPresent) *) GetProcAddress(g_hModKernel32, "IsDebuggerPresent"); + g_pfnGetSystemTimeAsFileTime = (decltype(GetSystemTimeAsFileTime) *) GetProcAddress(g_hModKernel32, "GetSystemTimeAsFileTime"); + g_pfnGetProcessAffinityMask = (decltype(GetProcessAffinityMask) *) GetProcAddress(g_hModKernel32, "GetProcessAffinityMask"); + g_pfnSetThreadAffinityMask = (decltype(SetThreadAffinityMask) *) GetProcAddress(g_hModKernel32, "SetThreadAffinityMask"); + g_pfnCreateIoCompletionPort = (decltype(CreateIoCompletionPort) *) GetProcAddress(g_hModKernel32, "CreateIoCompletionPort"); + g_pfnGetQueuedCompletionStatus = (decltype(GetQueuedCompletionStatus) *) GetProcAddress(g_hModKernel32, "GetQueuedCompletionStatus"); + g_pfnPostQueuedCompletionStatus = (decltype(PostQueuedCompletionStatus) *)GetProcAddress(g_hModKernel32, "PostQueuedCompletionStatus"); + g_pfnIsProcessorFeaturePresent = (decltype(IsProcessorFeaturePresent) *) GetProcAddress(g_hModKernel32, "IsProcessorFeaturePresent"); + + Assert(g_pfnGetHandleInformation || g_enmWinVer < kRTWinOSType_NT351); + Assert(g_pfnSetHandleInformation || g_enmWinVer < kRTWinOSType_NT351); + Assert(g_pfnIsDebuggerPresent || g_enmWinVer < kRTWinOSType_NT4); + Assert(g_pfnGetSystemTimeAsFileTime || g_enmWinVer < kRTWinOSType_NT4); + Assert(g_pfnGetProcessAffinityMask || g_enmWinVer < kRTWinOSType_NT350); + Assert(g_pfnSetThreadAffinityMask || g_enmWinVer < kRTWinOSType_NT350); + Assert(g_pfnCreateIoCompletionPort || g_enmWinVer < kRTWinOSType_NT350); + Assert(g_pfnGetQueuedCompletionStatus || g_enmWinVer < kRTWinOSType_NT350); + Assert(g_pfnPostQueuedCompletionStatus || g_enmWinVer < kRTWinOSType_NT350); + Assert(g_pfnIsProcessorFeaturePresent || g_enmWinVer < kRTWinOSType_NT4); + + /* + * Resolve some ntdll.dll APIs that weren't there in early NT versions. + */ + g_pfnNtQueryFullAttributesFile = (PFNNTQUERYFULLATTRIBUTESFILE)GetProcAddress(g_hModNtDll, "NtQueryFullAttributesFile"); + g_pfnNtDuplicateToken = (PFNNTDUPLICATETOKEN)GetProcAddress( g_hModNtDll, "NtDuplicateToken"); + g_pfnNtAlertThread = (decltype(NtAlertThread) *)GetProcAddress( g_hModNtDll, "NtAlertThread"); + + /* + * Resolve the winsock error getter and setter so assertions can save those too. + */ + rtR3InitWinSockApis(); + + return rc; +} + + +DECLHIDDEN(void) rtR3InitNativeObtrusive(uint32_t fFlags) +{ + rtR3InitNativeObtrusiveWorker(fFlags); +} + + +DECLHIDDEN(int) rtR3InitNativeFinal(uint32_t fFlags) +{ + /* Nothing to do here. */ + RT_NOREF_PV(fFlags); + return VINF_SUCCESS; +} + + +/** + * Unhandled exception filter callback. + * + * Will try log stuff. + */ +static LONG CALLBACK rtR3WinUnhandledXcptFilter(PEXCEPTION_POINTERS pPtrs) +{ + /* + * Try get the logger and log exception details. + * + * Note! We'll be using RTLogLoggerWeak for now, though we should probably add + * a less deadlock prone API here and gives up pretty fast if it + * cannot get the lock... + */ + PRTLOGGER pLogger = RTLogRelGetDefaultInstanceWeak(); + if (!pLogger) + pLogger = RTLogGetDefaultInstanceWeak(); + if (pLogger) + { + RTLogLoggerWeak(pLogger, NULL, "\n!!! rtR3WinUnhandledXcptFilter caught an exception on thread %p in %u !!!\n", + RTThreadNativeSelf(), RTProcSelf()); + + /* + * Dump the exception record. + */ + uintptr_t uXcptPC = 0; + PEXCEPTION_RECORD pXcptRec = RT_VALID_PTR(pPtrs) && RT_VALID_PTR(pPtrs->ExceptionRecord) ? pPtrs->ExceptionRecord : NULL; + if (pXcptRec) + { + RTLogLoggerWeak(pLogger, NULL, "\nExceptionCode=%#010x ExceptionFlags=%#010x ExceptionAddress=%p\n", + pXcptRec->ExceptionCode, pXcptRec->ExceptionFlags, pXcptRec->ExceptionAddress); + for (uint32_t i = 0; i < RT_MIN(pXcptRec->NumberParameters, EXCEPTION_MAXIMUM_PARAMETERS); i++) + RTLogLoggerWeak(pLogger, NULL, "ExceptionInformation[%d]=%p\n", i, pXcptRec->ExceptionInformation[i]); + uXcptPC = (uintptr_t)pXcptRec->ExceptionAddress; + + /* Nested? Display one level only. */ + PEXCEPTION_RECORD pNestedRec = pXcptRec->ExceptionRecord; + if (RT_VALID_PTR(pNestedRec)) + { + RTLogLoggerWeak(pLogger, NULL, "Nested: ExceptionCode=%#010x ExceptionFlags=%#010x ExceptionAddress=%p (nested %p)\n", + pNestedRec->ExceptionCode, pNestedRec->ExceptionFlags, pNestedRec->ExceptionAddress, + pNestedRec->ExceptionRecord); + for (uint32_t i = 0; i < RT_MIN(pNestedRec->NumberParameters, EXCEPTION_MAXIMUM_PARAMETERS); i++) + RTLogLoggerWeak(pLogger, NULL, "Nested: ExceptionInformation[%d]=%p\n", i, pNestedRec->ExceptionInformation[i]); + uXcptPC = (uintptr_t)pNestedRec->ExceptionAddress; + } + } + + /* + * Dump the context record. + */ + volatile char szMarker[] = "stackmarker"; + uintptr_t uXcptSP = (uintptr_t)&szMarker[0]; + PCONTEXT pXcptCtx = RT_VALID_PTR(pPtrs) && RT_VALID_PTR(pPtrs->ContextRecord) ? pPtrs->ContextRecord : NULL; + if (pXcptCtx) + { +#ifdef RT_ARCH_AMD64 + RTLogLoggerWeak(pLogger, NULL, "\ncs:rip=%04x:%016RX64\n", pXcptCtx->SegCs, pXcptCtx->Rip); + RTLogLoggerWeak(pLogger, NULL, "ss:rsp=%04x:%016RX64 rbp=%016RX64\n", pXcptCtx->SegSs, pXcptCtx->Rsp, pXcptCtx->Rbp); + RTLogLoggerWeak(pLogger, NULL, "rax=%016RX64 rcx=%016RX64 rdx=%016RX64 rbx=%016RX64\n", + pXcptCtx->Rax, pXcptCtx->Rcx, pXcptCtx->Rdx, pXcptCtx->Rbx); + RTLogLoggerWeak(pLogger, NULL, "rsi=%016RX64 rdi=%016RX64 rsp=%016RX64 rbp=%016RX64\n", + pXcptCtx->Rsi, pXcptCtx->Rdi, pXcptCtx->Rsp, pXcptCtx->Rbp); + RTLogLoggerWeak(pLogger, NULL, "r8 =%016RX64 r9 =%016RX64 r10=%016RX64 r11=%016RX64\n", + pXcptCtx->R8, pXcptCtx->R9, pXcptCtx->R10, pXcptCtx->R11); + RTLogLoggerWeak(pLogger, NULL, "r12=%016RX64 r13=%016RX64 r14=%016RX64 r15=%016RX64\n", + pXcptCtx->R12, pXcptCtx->R13, pXcptCtx->R14, pXcptCtx->R15); + RTLogLoggerWeak(pLogger, NULL, "ds=%04x es=%04x fs=%04x gs=%04x eflags=%08x\n", + pXcptCtx->SegDs, pXcptCtx->SegEs, pXcptCtx->SegFs, pXcptCtx->SegGs, pXcptCtx->EFlags); + RTLogLoggerWeak(pLogger, NULL, "p1home=%016RX64 p2home=%016RX64 pe3home=%016RX64\n", + pXcptCtx->P1Home, pXcptCtx->P2Home, pXcptCtx->P3Home); + RTLogLoggerWeak(pLogger, NULL, "p4home=%016RX64 p5home=%016RX64 pe6home=%016RX64\n", + pXcptCtx->P4Home, pXcptCtx->P5Home, pXcptCtx->P6Home); + RTLogLoggerWeak(pLogger, NULL, " LastBranchToRip=%016RX64 LastBranchFromRip=%016RX64\n", + pXcptCtx->LastBranchToRip, pXcptCtx->LastBranchFromRip); + RTLogLoggerWeak(pLogger, NULL, "LastExceptionToRip=%016RX64 LastExceptionFromRip=%016RX64\n", + pXcptCtx->LastExceptionToRip, pXcptCtx->LastExceptionFromRip); + uXcptSP = pXcptCtx->Rsp; + uXcptPC = pXcptCtx->Rip; + +#elif defined(RT_ARCH_X86) + RTLogLoggerWeak(pLogger, NULL, "\ncs:eip=%04x:%08RX32\n", pXcptCtx->SegCs, pXcptCtx->Eip); + RTLogLoggerWeak(pLogger, NULL, "ss:esp=%04x:%08RX32 ebp=%08RX32\n", pXcptCtx->SegSs, pXcptCtx->Esp, pXcptCtx->Ebp); + RTLogLoggerWeak(pLogger, NULL, "eax=%08RX32 ecx=%08RX32 edx=%08RX32 ebx=%08RX32\n", + pXcptCtx->Eax, pXcptCtx->Ecx, pXcptCtx->Edx, pXcptCtx->Ebx); + RTLogLoggerWeak(pLogger, NULL, "esi=%08RX32 edi=%08RX32 esp=%08RX32 ebp=%08RX32\n", + pXcptCtx->Esi, pXcptCtx->Edi, pXcptCtx->Esp, pXcptCtx->Ebp); + RTLogLoggerWeak(pLogger, NULL, "ds=%04x es=%04x fs=%04x gs=%04x eflags=%08x\n", + pXcptCtx->SegDs, pXcptCtx->SegEs, pXcptCtx->SegFs, pXcptCtx->SegGs, pXcptCtx->EFlags); + uXcptSP = pXcptCtx->Esp; + uXcptPC = pXcptCtx->Eip; +#endif + } + + /* + * Dump stack. + */ + uintptr_t uStack = (uintptr_t)(void *)&szMarker[0]; + uStack -= uStack & 15; + + size_t cbToDump = PAGE_SIZE - (uStack & PAGE_OFFSET_MASK); + if (cbToDump < 512) + cbToDump += PAGE_SIZE; + size_t cbToXcpt = uXcptSP - uStack; + while (cbToXcpt > cbToDump && cbToXcpt <= _16K) + cbToDump += PAGE_SIZE; + ULONG_PTR uLow = (uintptr_t)&szMarker[0]; + ULONG_PTR uHigh = (uintptr_t)&szMarker[0]; + if (g_pfnGetCurrentThreadStackLimits) + { + g_pfnGetCurrentThreadStackLimits(&uLow, &uHigh); + size_t cbToTop = RT_MAX(uLow, uHigh) - uStack; + if (cbToTop < _1M) + cbToDump = cbToTop; + } + + RTLogLoggerWeak(pLogger, NULL, "\nStack %p, dumping %#x bytes (low=%p, high=%p)\n", uStack, cbToDump, uLow, uHigh); + RTLogLoggerWeak(pLogger, NULL, "%.*RhxD\n", cbToDump, uStack); + + /* + * Try figure the thread name. + * + * Note! This involves the thread db lock, so it may deadlock, which + * is why it's at the end. + */ + RTLogLoggerWeak(pLogger, NULL, "Thread ID: %p\n", RTThreadNativeSelf()); + RTLogLoggerWeak(pLogger, NULL, "Thread name: %s\n", RTThreadSelfName()); + RTLogLoggerWeak(pLogger, NULL, "Thread IPRT: %p\n", RTThreadSelf()); + + /* + * Try dump the load information. + */ + PPEB pPeb = RTNtCurrentPeb(); + if (RT_VALID_PTR(pPeb)) + { + PPEB_LDR_DATA pLdrData = pPeb->Ldr; + if (RT_VALID_PTR(pLdrData)) + { + PLDR_DATA_TABLE_ENTRY pFound = NULL; + LIST_ENTRY * const pList = &pLdrData->InMemoryOrderModuleList; + LIST_ENTRY *pListEntry = pList->Flink; + uint32_t cLoops = 0; + RTLogLoggerWeak(pLogger, NULL, + "\nLoaded Modules:\n" + "%-*s[*] Timestamp Path\n", sizeof(void *) * 4 + 2 - 1, "Address range" + ); + while (pListEntry != pList && RT_VALID_PTR(pListEntry) && cLoops < 1024) + { + PLDR_DATA_TABLE_ENTRY pLdrEntry = RT_FROM_MEMBER(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + uint32_t const cbLength = (uint32_t)(uintptr_t)pLdrEntry->Reserved3[1]; + char chInd = ' '; + if (uXcptPC - (uintptr_t)pLdrEntry->DllBase < cbLength) + { + chInd = '*'; + pFound = pLdrEntry; + } + + if ( RT_VALID_PTR(pLdrEntry->FullDllName.Buffer) + && pLdrEntry->FullDllName.Length > 0 + && pLdrEntry->FullDllName.Length < _8K + && (pLdrEntry->FullDllName.Length & 1) == 0 + && pLdrEntry->FullDllName.Length <= pLdrEntry->FullDllName.MaximumLength) + RTLogLoggerWeak(pLogger, NULL, "%p..%p%c %08RX32 %.*ls\n", + pLdrEntry->DllBase, (uintptr_t)pLdrEntry->DllBase + cbLength - 1, chInd, + pLdrEntry->TimeDateStamp, pLdrEntry->FullDllName.Length / sizeof(RTUTF16), + pLdrEntry->FullDllName.Buffer); + else + RTLogLoggerWeak(pLogger, NULL, "%p..%p%c %08RX32 <bad or missing: %p LB %#x max %#x\n", + pLdrEntry->DllBase, (uintptr_t)pLdrEntry->DllBase + cbLength - 1, chInd, + pLdrEntry->TimeDateStamp, pLdrEntry->FullDllName.Buffer, pLdrEntry->FullDllName.Length, + pLdrEntry->FullDllName.MaximumLength); + + /* advance */ + pListEntry = pListEntry->Flink; + cLoops++; + } + + /* + * Use the above to pick out code addresses on the stack. + */ + if ( cLoops < 1024 + && uXcptSP - uStack < cbToDump) + { + RTLogLoggerWeak(pLogger, NULL, "\nPotential code addresses on the stack:\n"); + if (pFound) + { + if ( RT_VALID_PTR(pFound->FullDllName.Buffer) + && pFound->FullDllName.Length > 0 + && pFound->FullDllName.Length < _8K + && (pFound->FullDllName.Length & 1) == 0 + && pFound->FullDllName.Length <= pFound->FullDllName.MaximumLength) + RTLogLoggerWeak(pLogger, NULL, "%-*s: %p - %#010RX32 bytes into %.*ls\n", + sizeof(void *) * 2, "Xcpt PC", uXcptPC, (uint32_t)(uXcptPC - (uintptr_t)pFound->DllBase), + pFound->FullDllName.Length / sizeof(RTUTF16), pFound->FullDllName.Buffer); + else + RTLogLoggerWeak(pLogger, NULL, "%-*s: %p - %08RX32 into module at %p\n", + sizeof(void *) * 2, "Xcpt PC", uXcptPC, (uint32_t)(uXcptPC - (uintptr_t)pFound->DllBase), + pFound->DllBase); + } + + uintptr_t const *puStack = (uintptr_t const *)uXcptSP; + uintptr_t cLeft = (cbToDump - (uXcptSP - uStack)) / sizeof(uintptr_t); + while (cLeft-- > 0) + { + uintptr_t uPtr = *puStack; + if (RT_VALID_PTR(uPtr)) + { + /* Search the module table. */ + pFound = NULL; + cLoops = 0; + pListEntry = pList->Flink; + while (pListEntry != pList && RT_VALID_PTR(pListEntry) && cLoops < 1024) + { + PLDR_DATA_TABLE_ENTRY pLdrEntry = RT_FROM_MEMBER(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + uint32_t const cbLength = (uint32_t)(uintptr_t)pLdrEntry->Reserved3[1]; + if (uPtr - (uintptr_t)pLdrEntry->DllBase < cbLength) + { + pFound = pLdrEntry; + break; + } + + /* advance */ + pListEntry = pListEntry->Flink; + cLoops++; + } + + if (pFound) + { + if ( RT_VALID_PTR(pFound->FullDllName.Buffer) + && pFound->FullDllName.Length > 0 + && pFound->FullDllName.Length < _8K + && (pFound->FullDllName.Length & 1) == 0 + && pFound->FullDllName.Length <= pFound->FullDllName.MaximumLength) + RTLogLoggerWeak(pLogger, NULL, "%p: %p - %#010RX32 bytes into %.*ls\n", + puStack, uPtr, (uint32_t)(uPtr - (uintptr_t)pFound->DllBase), + pFound->FullDllName.Length / sizeof(RTUTF16), pFound->FullDllName.Buffer); + else + RTLogLoggerWeak(pLogger, NULL, "%p: %p - %08RX32 into module at %p\n", + puStack, uPtr, (uint32_t)(uPtr - (uintptr_t)pFound->DllBase), pFound->DllBase); + } + } + + puStack++; + } + } + } + + /* + * Dump the command line if we have one. We do this last in case it crashes. + */ + PRTL_USER_PROCESS_PARAMETERS pProcParams = pPeb->ProcessParameters; + if (RT_VALID_PTR(pProcParams)) + { + if (RT_VALID_PTR(pProcParams->CommandLine.Buffer) + && pProcParams->CommandLine.Length > 0 + && pProcParams->CommandLine.Length <= pProcParams->CommandLine.MaximumLength + && !(pProcParams->CommandLine.Length & 1) + && !(pProcParams->CommandLine.MaximumLength & 1)) + RTLogLoggerWeak(pLogger, NULL, "PEB/CommandLine: %.*ls\n", + pProcParams->CommandLine.Length / sizeof(RTUTF16), pProcParams->CommandLine.Buffer); + } + } + } + + /* + * Do the default stuff, never mind us. + */ + if (g_pfnUnhandledXcptFilter) + return g_pfnUnhandledXcptFilter(pPtrs); + return EXCEPTION_CONTINUE_SEARCH; +} + diff --git a/src/VBox/Runtime/r3/win/internal-r3-win.h b/src/VBox/Runtime/r3/win/internal-r3-win.h new file mode 100644 index 00000000..562ac0ed --- /dev/null +++ b/src/VBox/Runtime/r3/win/internal-r3-win.h @@ -0,0 +1,244 @@ +/* $Id: internal-r3-win.h $ */ +/** @file + * IPRT - some Windows OS type constants. + */ + +/* + * Copyright (C) 2013-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_win_internal_r3_win_h +#define IPRT_INCLUDED_SRC_r3_win_internal_r3_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "internal/iprt.h" +#include <iprt/types.h> + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** + * Windows OS type as determined by rtSystemWinOSType(). + * + * @note ASSUMPTIONS are made regarding ordering. Win 9x should come first, then + * NT. The Win9x and NT versions should internally be ordered in ascending + * version/code-base order. + */ +typedef enum RTWINOSTYPE +{ + kRTWinOSType_UNKNOWN = 0, + kRTWinOSType_9XFIRST = 1, + kRTWinOSType_95 = kRTWinOSType_9XFIRST, + kRTWinOSType_95SP1, + kRTWinOSType_95OSR2, + kRTWinOSType_98, + kRTWinOSType_98SP1, + kRTWinOSType_98SE, + kRTWinOSType_ME, + kRTWinOSType_9XLAST = 99, + kRTWinOSType_NTFIRST = 100, + kRTWinOSType_NT310 = kRTWinOSType_NTFIRST, + kRTWinOSType_NT350, + kRTWinOSType_NT351, + kRTWinOSType_NT4, + kRTWinOSType_2K, /* 5.0 */ + kRTWinOSType_XP, /* 5.1 */ + kRTWinOSType_XP64, /* 5.2, workstation */ + kRTWinOSType_2003, /* 5.2 */ + kRTWinOSType_VISTA, /* 6.0, workstation */ + kRTWinOSType_2008, /* 6.0, server */ + kRTWinOSType_7, /* 6.1, workstation */ + kRTWinOSType_2008R2, /* 6.1, server */ + kRTWinOSType_8, /* 6.2, workstation */ + kRTWinOSType_2012, /* 6.2, server */ + kRTWinOSType_81, /* 6.3, workstation */ + kRTWinOSType_2012R2, /* 6.3, server */ + kRTWinOSType_10, /* 10.0, workstation */ + kRTWinOSType_2016, /* 10.0 1607, server */ + kRTWinOSType_2019, /* 10.0 1809, server */ + kRTWinOSType_2022, /* 10.0 21H2, server */ + kRTWinOSType_11, /* 11.0, workstation */ + kRTWinOSType_NT_UNKNOWN = 199, + kRTWinOSType_NT_LAST = kRTWinOSType_UNKNOWN +} RTWINOSTYPE; + +/** + * Windows loader protection level. + */ +typedef enum RTR3WINLDRPROT +{ + RTR3WINLDRPROT_INVALID = 0, + RTR3WINLDRPROT_NONE, + RTR3WINLDRPROT_NO_CWD, + RTR3WINLDRPROT_SAFE, + RTR3WINLDRPROT_SAFER +} RTR3WINLDRPROT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern DECL_HIDDEN_DATA(RTR3WINLDRPROT) g_enmWinLdrProt; +extern DECL_HIDDEN_DATA(RTWINOSTYPE) g_enmWinVer; +#ifdef _WINDEF_ +extern DECL_HIDDEN_DATA(OSVERSIONINFOEXW) g_WinOsInfoEx; + +extern DECL_HIDDEN_DATA(HMODULE) g_hModKernel32; +typedef UINT (WINAPI *PFNGETWINSYSDIR)(LPWSTR,UINT); +extern DECL_HIDDEN_DATA(PFNGETWINSYSDIR) g_pfnGetSystemWindowsDirectoryW; +typedef HANDLE (WINAPI *PFNCREATEWAITABLETIMEREX)(LPSECURITY_ATTRIBUTES, LPCWSTR, DWORD, DWORD); +extern DECL_HIDDEN_DATA(PFNCREATEWAITABLETIMEREX) g_pfnCreateWaitableTimerExW; +extern DECL_HIDDEN_DATA(decltype(SystemTimeToTzSpecificLocalTime) *) g_pfnSystemTimeToTzSpecificLocalTime; +extern DECL_HIDDEN_DATA(decltype(GetHandleInformation) *) g_pfnGetHandleInformation; +extern DECL_HIDDEN_DATA(decltype(SetHandleInformation) *) g_pfnSetHandleInformation; +extern DECL_HIDDEN_DATA(decltype(IsDebuggerPresent) *) g_pfnIsDebuggerPresent; +extern DECL_HIDDEN_DATA(decltype(GetSystemTimeAsFileTime) *) g_pfnGetSystemTimeAsFileTime; +extern DECL_HIDDEN_DATA(decltype(GetProcessAffinityMask) *) g_pfnGetProcessAffinityMask; +extern DECL_HIDDEN_DATA(decltype(SetThreadAffinityMask) *) g_pfnSetThreadAffinityMask; +extern DECL_HIDDEN_DATA(decltype(CreateIoCompletionPort) *) g_pfnCreateIoCompletionPort; +extern DECL_HIDDEN_DATA(decltype(GetQueuedCompletionStatus) *) g_pfnGetQueuedCompletionStatus; +extern DECL_HIDDEN_DATA(decltype(PostQueuedCompletionStatus) *) g_pfnPostQueuedCompletionStatus; +extern DECL_HIDDEN_DATA(decltype(SetUnhandledExceptionFilter) *) g_pfnSetUnhandledExceptionFilter; +extern DECL_HIDDEN_DATA(decltype(UnhandledExceptionFilter) *) g_pfnUnhandledExceptionFilter; +extern DECL_HIDDEN_DATA(decltype(IsProcessorFeaturePresent) *) g_pfnIsProcessorFeaturePresent; + + +extern DECL_HIDDEN_DATA(HMODULE) g_hModNtDll; +typedef NTSTATUS (NTAPI *PFNNTQUERYFULLATTRIBUTESFILE)(struct _OBJECT_ATTRIBUTES *, struct _FILE_NETWORK_OPEN_INFORMATION *); +extern DECL_HIDDEN_DATA(PFNNTQUERYFULLATTRIBUTESFILE) g_pfnNtQueryFullAttributesFile; +typedef NTSTATUS (NTAPI *PFNNTDUPLICATETOKEN)(HANDLE, ACCESS_MASK, struct _OBJECT_ATTRIBUTES *, BOOLEAN, TOKEN_TYPE, PHANDLE); +extern DECL_HIDDEN_DATA(PFNNTDUPLICATETOKEN) g_pfnNtDuplicateToken; +#ifdef IPRT_INCLUDED_nt_nt_h +extern DECL_HIDDEN_DATA(decltype(NtAlertThread) *) g_pfnNtAlertThread; +#endif + +extern DECL_HIDDEN_DATA(HMODULE) g_hModWinSock; + +/** WSAStartup */ +typedef int (WINAPI *PFNWSASTARTUP)(WORD, struct WSAData *); +/** WSACleanup */ +typedef int (WINAPI *PFNWSACLEANUP)(void); +/** WSAGetLastError */ +typedef int (WINAPI *PFNWSAGETLASTERROR)(void); +/** WSASetLastError */ +typedef int (WINAPI *PFNWSASETLASTERROR)(int); +/** WSACreateEvent */ +typedef HANDLE (WINAPI *PFNWSACREATEEVENT)(void); +/** WSASetEvent */ +typedef BOOL (WINAPI *PFNWSASETEVENT)(HANDLE); +/** WSACloseEvent */ +typedef BOOL (WINAPI *PFNWSACLOSEEVENT)(HANDLE); +/** WSAEventSelect */ +typedef BOOL (WINAPI *PFNWSAEVENTSELECT)(UINT_PTR, HANDLE, LONG); +/** WSAEnumNetworkEvents */ +typedef int (WINAPI *PFNWSAENUMNETWORKEVENTS)(UINT_PTR, HANDLE, struct _WSANETWORKEVENTS *); +/** WSASocketW */ +typedef UINT_PTR (WINAPI *PFNWSASOCKETW)(int, int, int, struct _WSAPROTOCOL_INFOW *, unsigned, DWORD); +/** WSASend */ +typedef int (WINAPI *PFNWSASEND)(UINT_PTR, struct _WSABUF *, DWORD, LPDWORD, DWORD dwFlags, + struct _OVERLAPPED *, uintptr_t /*LPWSAOVERLAPPED_COMPLETION_ROUTINE*/); + +/** socket */ +typedef UINT_PTR (WINAPI *PFNWINSOCKSOCKET)(int, int, int); +/** closesocket */ +typedef int (WINAPI *PFNWINSOCKCLOSESOCKET)(UINT_PTR); +/** recv */ +typedef int (WINAPI *PFNWINSOCKRECV)(UINT_PTR, char *, int, int); +/** send */ +typedef int (WINAPI *PFNWINSOCKSEND)(UINT_PTR, const char *, int, int); +/** recvfrom */ +typedef int (WINAPI *PFNWINSOCKRECVFROM)(UINT_PTR, char *, int, int, struct sockaddr *, int *); +/** sendto */ +typedef int (WINAPI *PFNWINSOCKSENDTO)(UINT_PTR, const char *, int, int, const struct sockaddr *, int); +/** bind */ +typedef int (WINAPI *PFNWINSOCKBIND)(UINT_PTR, const struct sockaddr *, int); +/** listen */ +typedef int (WINAPI *PFNWINSOCKLISTEN)(UINT_PTR, int); +/** accept */ +typedef UINT_PTR (WINAPI *PFNWINSOCKACCEPT)(UINT_PTR, struct sockaddr *, int *); +/** connect */ +typedef int (WINAPI *PFNWINSOCKCONNECT)(UINT_PTR, const struct sockaddr *, int); +/** shutdown */ +typedef int (WINAPI *PFNWINSOCKSHUTDOWN)(UINT_PTR, int); +/** getsockopt */ +typedef int (WINAPI *PFNWINSOCKGETSOCKOPT)(UINT_PTR, int, int, char *, int *); +/** setsockopt */ +typedef int (WINAPI *PFNWINSOCKSETSOCKOPT)(UINT_PTR, int, int, const char *, int); +/** ioctlsocket */ +typedef int (WINAPI *PFNWINSOCKIOCTLSOCKET)(UINT_PTR, long, unsigned long *); +/** getpeername */ +typedef int (WINAPI *PFNWINSOCKGETPEERNAME)(UINT_PTR, struct sockaddr *, int *); +/** getsockname */ +typedef int (WINAPI *PFNWINSOCKGETSOCKNAME)(UINT_PTR, struct sockaddr *, int *); +/** __WSAFDIsSet */ +typedef int (WINAPI *PFNWINSOCK__WSAFDISSET)(UINT_PTR, struct fd_set *); +/** select */ +typedef int (WINAPI *PFNWINSOCKSELECT)(int, struct fd_set *, struct fd_set *, struct fd_set *, const struct timeval *); +/** gethostbyname */ +typedef struct hostent *(WINAPI *PFNWINSOCKGETHOSTBYNAME)(const char *); + +extern DECL_HIDDEN_DATA(PFNWSASTARTUP) g_pfnWSAStartup; +extern DECL_HIDDEN_DATA(PFNWSACLEANUP) g_pfnWSACleanup; +extern DECL_HIDDEN_DATA(PFNWSAGETLASTERROR) g_pfnWSAGetLastError; +extern DECL_HIDDEN_DATA(PFNWSASETLASTERROR) g_pfnWSASetLastError; +extern DECL_HIDDEN_DATA(PFNWSACREATEEVENT) g_pfnWSACreateEvent; +extern DECL_HIDDEN_DATA(PFNWSACLOSEEVENT) g_pfnWSACloseEvent; +extern DECL_HIDDEN_DATA(PFNWSASETEVENT) g_pfnWSASetEvent; +extern DECL_HIDDEN_DATA(PFNWSAEVENTSELECT) g_pfnWSAEventSelect; +extern DECL_HIDDEN_DATA(PFNWSAENUMNETWORKEVENTS) g_pfnWSAEnumNetworkEvents; +extern DECL_HIDDEN_DATA(PFNWSASOCKETW) g_pfnWSASocketW; +extern DECL_HIDDEN_DATA(PFNWSASEND) g_pfnWSASend; +extern DECL_HIDDEN_DATA(PFNWINSOCKSOCKET) g_pfnsocket; +extern DECL_HIDDEN_DATA(PFNWINSOCKCLOSESOCKET) g_pfnclosesocket; +extern DECL_HIDDEN_DATA(PFNWINSOCKRECV) g_pfnrecv; +extern DECL_HIDDEN_DATA(PFNWINSOCKSEND) g_pfnsend; +extern DECL_HIDDEN_DATA(PFNWINSOCKRECVFROM) g_pfnrecvfrom; +extern DECL_HIDDEN_DATA(PFNWINSOCKSENDTO) g_pfnsendto; +extern DECL_HIDDEN_DATA(PFNWINSOCKBIND) g_pfnbind; +extern DECL_HIDDEN_DATA(PFNWINSOCKLISTEN) g_pfnlisten; +extern DECL_HIDDEN_DATA(PFNWINSOCKACCEPT) g_pfnaccept; +extern DECL_HIDDEN_DATA(PFNWINSOCKCONNECT) g_pfnconnect; +extern DECL_HIDDEN_DATA(PFNWINSOCKSHUTDOWN) g_pfnshutdown; +extern DECL_HIDDEN_DATA(PFNWINSOCKGETSOCKOPT) g_pfngetsockopt; +extern DECL_HIDDEN_DATA(PFNWINSOCKSETSOCKOPT) g_pfnsetsockopt; +extern DECL_HIDDEN_DATA(PFNWINSOCKIOCTLSOCKET) g_pfnioctlsocket; +extern DECL_HIDDEN_DATA(PFNWINSOCKGETPEERNAME) g_pfngetpeername; +extern DECL_HIDDEN_DATA(PFNWINSOCKGETSOCKNAME) g_pfngetsockname; +extern DECL_HIDDEN_DATA(PFNWINSOCK__WSAFDISSET) g_pfn__WSAFDIsSet; +extern DECL_HIDDEN_DATA(PFNWINSOCKSELECT) g_pfnselect; +extern DECL_HIDDEN_DATA(PFNWINSOCKGETHOSTBYNAME) g_pfngethostbyname; +#endif + + +#endif /* !IPRT_INCLUDED_SRC_r3_win_internal_r3_win_h */ + diff --git a/src/VBox/Runtime/r3/win/krnlmod-win.cpp b/src/VBox/Runtime/r3/win/krnlmod-win.cpp new file mode 100644 index 00000000..255cc2cf --- /dev/null +++ b/src/VBox/Runtime/r3/win/krnlmod-win.cpp @@ -0,0 +1,331 @@ +/* $Id: krnlmod-win.cpp $ */ +/** @file + * IPRT - Kernel module, Windows. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <iprt/nt/nt.h> + +#include <iprt/krnlmod.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/types.h> + + +/** + * Internal kernel information record state. + */ +typedef struct RTKRNLMODINFOINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Reference count for the kernel module. */ + uint32_t cRefKrnlMod; + /** Load address of the kernel module. */ + RTR0UINTPTR uLoadAddr; + /** Size of the kernel module. */ + size_t cbKrnlMod; + /** Pointer to the driver name. */ + const char *pszName; + /** Size of the name in characters including the zero terminator. */ + size_t cchFilePath; + /** Module name - variable in size. */ + char achFilePath[1]; +} RTKRNLMODINFOINT; +/** Pointer to the internal kernel module information record. */ +typedef RTKRNLMODINFOINT *PRTKRNLMODINFOINT; +/** Pointer to a const internal kernel module information record. */ +typedef const RTKRNLMODINFOINT *PCRTKRNLMODINFOINT; + + +/** + * Destroy the given kernel module information record. + * + * @param pThis The record to destroy. + */ +static void rtKrnlModInfoDestroy(PRTKRNLMODINFOINT pThis) +{ + RTMemFree(pThis); +} + + +/** + * Queries the complete kernel modules structure and returns a pointer to it. + * + * @returns IPRT status code. + * @param ppKrnlMods Where to store the pointer to the kernel module list on success. + * Free with RTMemFree(). + */ +static int rtKrnlModWinQueryKrnlMods(PRTL_PROCESS_MODULES *ppKrnlMods) +{ + int rc = VINF_SUCCESS; + RTL_PROCESS_MODULES KrnlModsSize; + + NTSTATUS rcNt = NtQuerySystemInformation(SystemModuleInformation, &KrnlModsSize, sizeof(KrnlModsSize), NULL); + if (NT_SUCCESS(rcNt) || rcNt == STATUS_INFO_LENGTH_MISMATCH) + { + ULONG cbKrnlMods = RT_UOFFSETOF_DYN(RTL_PROCESS_MODULES, Modules[KrnlModsSize.NumberOfModules]); + PRTL_PROCESS_MODULES pKrnlMods = (PRTL_PROCESS_MODULES)RTMemAllocZ(cbKrnlMods); + if (RT_LIKELY(pKrnlMods)) + { + rcNt = NtQuerySystemInformation(SystemModuleInformation, pKrnlMods, cbKrnlMods, NULL); + if (NT_SUCCESS(rcNt)) + *ppKrnlMods = pKrnlMods; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + return rc; +} + +/** + * Creates a new kernel module information record for the given module. + * + * @returns IPRT status code. + * @param pModInfo The kernel module information. + * @param phKrnlModInfo Where to store the handle to the kernel module information record + * on success. + */ +static int rtKrnlModWinInfoCreate(PRTL_PROCESS_MODULE_INFORMATION pModInfo, PRTKRNLMODINFO phKrnlModInfo) +{ + int rc = VINF_SUCCESS; + RT_NOREF2(pModInfo, phKrnlModInfo); + size_t cchFilePath = strlen((const char *)&pModInfo->FullPathName[0]) + 1; + PRTKRNLMODINFOINT pThis = (PRTKRNLMODINFOINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTKRNLMODINFOINT, achFilePath[cchFilePath])); + if (RT_LIKELY(pThis)) + { + memcpy(&pThis->achFilePath[0], &pModInfo->FullPathName[0], cchFilePath); + pThis->cchFilePath = cchFilePath; + pThis->cRefs = 1; + pThis->cbKrnlMod = pModInfo->ImageSize; + pThis->uLoadAddr = (RTR0UINTPTR)pModInfo->ImageBase; + pThis->pszName = pModInfo->OffsetToFileName >= cchFilePath + ? NULL + : pThis->achFilePath + pModInfo->OffsetToFileName; + + *phKrnlModInfo = pThis; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTKrnlModQueryLoaded(const char *pszName, bool *pfLoaded) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pfLoaded, VERR_INVALID_POINTER); + + int rc = VERR_NOT_IMPLEMENTED; + + return rc; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfo(const char *pszName, PRTKRNLMODINFO phKrnlModInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(phKrnlModInfo, VERR_INVALID_POINTER); + + int rc = VERR_NOT_IMPLEMENTED; + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModLoadedGetCount(void) +{ + uint32_t cKrnlMods = 0; + RTL_PROCESS_MODULES ProcMods; + + NTSTATUS rcNt = NtQuerySystemInformation(SystemModuleInformation, &ProcMods, sizeof(ProcMods), NULL); + if (NT_SUCCESS(rcNt) || rcNt == STATUS_INFO_LENGTH_MISMATCH) + cKrnlMods = ProcMods.NumberOfModules; + + return cKrnlMods; +} + + +RTDECL(int) RTKrnlModLoadedQueryInfoAll(PRTKRNLMODINFO pahKrnlModInfo, uint32_t cEntriesMax, + uint32_t *pcEntries) +{ + if (cEntriesMax > 0) + AssertPtrReturn(pahKrnlModInfo, VERR_INVALID_POINTER); + + PRTL_PROCESS_MODULES pKrnlMods = NULL; + int rc = rtKrnlModWinQueryKrnlMods(&pKrnlMods); + if (RT_SUCCESS(rc)) + { + if (pKrnlMods->NumberOfModules <= cEntriesMax) + { + for (unsigned i = 0; i < pKrnlMods->NumberOfModules; i++) + { + pKrnlMods->Modules[i].FullPathName[255] = '\0'; /* Paranoia */ + rc = rtKrnlModWinInfoCreate(&pKrnlMods->Modules[i], &pahKrnlModInfo[i]); + if (RT_FAILURE(rc)) + { + while (i-- > 0) + RTKrnlModInfoRelease(pahKrnlModInfo[i]); + break; + } + } + } + else + rc = VERR_BUFFER_OVERFLOW; + + if (pcEntries) + *pcEntries = pKrnlMods->NumberOfModules; + + RTMemFree(pKrnlMods); + } + + return rc; +} + + +RTDECL(uint32_t) RTKrnlModInfoRetain(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoRelease(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + if (!pThis) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtKrnlModInfoDestroy(pThis); + return cRefs; +} + + +RTDECL(uint32_t) RTKrnlModInfoGetRefCnt(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->cRefKrnlMod; +} + + +RTDECL(const char *) RTKrnlModInfoGetName(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + return pThis->pszName; +} + + +RTDECL(const char *) RTKrnlModInfoGetFilePath(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, NULL); + + return &pThis->achFilePath[0]; +} + + +RTDECL(size_t) RTKrnlModInfoGetSize(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->cbKrnlMod; +} + + +RTDECL(RTR0UINTPTR) RTKrnlModInfoGetLoadAddr(RTKRNLMODINFO hKrnlModInfo) +{ + PRTKRNLMODINFOINT pThis = hKrnlModInfo; + AssertPtrReturn(pThis, 0); + + return pThis->uLoadAddr; +} + + +RTDECL(int) RTKrnlModInfoQueryRefModInfo(RTKRNLMODINFO hKrnlModInfo, uint32_t idx, + PRTKRNLMODINFO phKrnlModInfoRef) +{ + RT_NOREF3(hKrnlModInfo, idx, phKrnlModInfoRef); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModLoadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModLoadByPath(const char *pszPath) +{ + AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTKrnlModUnloadByName(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} diff --git a/src/VBox/Runtime/r3/win/ldrNative-win.cpp b/src/VBox/Runtime/r3/win/ldrNative-win.cpp new file mode 100644 index 00000000..2ff98a4c --- /dev/null +++ b/src/VBox/Runtime/r3/win/ldrNative-win.cpp @@ -0,0 +1,326 @@ +/* $Id: ldrNative-win.cpp $ */ +/** @file + * IPRT - Binary Image Loader, Win32 native. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/ldr.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include <iprt/once.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "internal/ldr.h" +#include "internal-r3-win.h" + +#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR +# define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR UINT32_C(0x100) +# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR UINT32_C(0x200) +# define LOAD_LIBRARY_SEARCH_SYSTEM32 UINT32_C(0x800) +#endif + + +DECLHIDDEN(int) rtldrNativeLoad(const char *pszFilename, uintptr_t *phHandle, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + Assert(sizeof(*phHandle) >= sizeof(HMODULE)); + AssertReturn(!(fFlags & RTLDRLOAD_FLAGS_GLOBAL), VERR_INVALID_FLAGS); + AssertLogRelMsgReturn(RTPathStartsWithRoot(pszFilename), /* Relative names will still be applied to the search path. */ + ("pszFilename='%s'\n", pszFilename), + VERR_INTERNAL_ERROR_2); + AssertReleaseMsg(g_hModKernel32, + ("rtldrNativeLoad(%s,,) is called before IPRT has configured the windows loader!\n", pszFilename)); + + /* + * Convert to UTF-16 and make sure it got a .DLL suffix. + */ + /** @todo Implement long path support for native DLL loading on windows. @bugref{9248} */ + int rc; + RTUTF16 *pwszNative = NULL; + if (RTPathHasSuffix(pszFilename) || (fFlags & RTLDRLOAD_FLAGS_NO_SUFFIX)) + rc = RTStrToUtf16(pszFilename, &pwszNative); + else + { + size_t cwcAlloc; + rc = RTStrCalcUtf16LenEx(pszFilename, RTSTR_MAX, &cwcAlloc); + if (RT_SUCCESS(rc)) + { + cwcAlloc += sizeof(".DLL"); + pwszNative = RTUtf16Alloc(cwcAlloc * sizeof(RTUTF16)); + if (pwszNative) + { + size_t cwcNative; + rc = RTStrToUtf16Ex(pszFilename, RTSTR_MAX, &pwszNative, cwcAlloc, &cwcNative); + if (RT_SUCCESS(rc)) + rc = RTUtf16CopyAscii(&pwszNative[cwcNative], cwcAlloc - cwcNative, ".DLL"); + } + else + rc = VERR_NO_UTF16_MEMORY; + } + } + if (RT_SUCCESS(rc)) + { + /* Convert slashes just to be on the safe side. */ + for (size_t off = 0;; off++) + { + RTUTF16 wc = pwszNative[off]; + if (wc == '/') + pwszNative[off] = '\\'; + else if (!wc) + break; + } + + /* + * Attempt load. + */ + HMODULE hmod; + static int s_iSearchDllLoadDirSupported = 0; + if ( !(fFlags & RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR) + || s_iSearchDllLoadDirSupported < 0) + hmod = LoadLibraryExW(pwszNative, NULL, 0); + else + { + hmod = LoadLibraryExW(pwszNative, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32 + | LOAD_LIBRARY_SEARCH_APPLICATION_DIR); + if (s_iSearchDllLoadDirSupported == 0) + { + if (hmod != NULL || GetLastError() != ERROR_INVALID_PARAMETER) + s_iSearchDllLoadDirSupported = 1; + else + { + s_iSearchDllLoadDirSupported = -1; + hmod = LoadLibraryExW(pwszNative, NULL, 0); + } + } + } + if (hmod) + { + *phHandle = (uintptr_t)hmod; + RTUtf16Free(pwszNative); + return VINF_SUCCESS; + } + + /* + * Try figure why it failed to load. + */ + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + rc = RTErrInfoSetF(pErrInfo, rc, "GetLastError=%u", dwErr); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "Error converting UTF-8 to UTF-16 string."); + RTUtf16Free(pwszNative); + return rc; +} + + +DECLCALLBACK(int) rtldrNativeGetSymbol(PRTLDRMODINTERNAL pMod, const char *pszSymbol, void **ppvValue) +{ + PRTLDRMODNATIVE pModNative = (PRTLDRMODNATIVE)pMod; + FARPROC pfn = GetProcAddress((HMODULE)pModNative->hNative, pszSymbol); + if (pfn) + { + *ppvValue = (void *)pfn; + return VINF_SUCCESS; + } + *ppvValue = NULL; + return RTErrConvertFromWin32(GetLastError()); +} + + +DECLCALLBACK(int) rtldrNativeClose(PRTLDRMODINTERNAL pMod) +{ + PRTLDRMODNATIVE pModNative = (PRTLDRMODNATIVE)pMod; + if ( (pModNative->fFlags & RTLDRLOAD_FLAGS_NO_UNLOAD) + || FreeLibrary((HMODULE)pModNative->hNative)) + { + pModNative->hNative = (uintptr_t)INVALID_HANDLE_VALUE; + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(GetLastError()); +} + + +DECLHIDDEN(int) rtldrNativeLoadSystem(const char *pszFilename, const char *pszExt, uint32_t fFlags, PRTLDRMOD phLdrMod) +{ + AssertReleaseMsg(g_hModKernel32, + ("rtldrNativeLoadSystem(%s,,) is called before IPRT has configured the windows loader!\n", pszFilename)); + + /* + * Resolve side-by-side resolver API. + */ + static bool volatile s_fInitialized = false; + static decltype(RtlDosApplyFileIsolationRedirection_Ustr) *s_pfnApplyRedir = NULL; + if (!s_fInitialized) + { + s_pfnApplyRedir = (decltype(s_pfnApplyRedir))GetProcAddress(g_hModNtDll, + "RtlDosApplyFileIsolationRedirection_Ustr"); + ASMCompilerBarrier(); + s_fInitialized = true; + } + + /* + * We try WinSxS via undocumented NTDLL API and flal back on the System32 + * directory. No other locations are supported. + */ + int rc = VERR_TRY_AGAIN; + char szPath[RTPATH_MAX]; + char *pszPath = szPath; + + /* Get the windows system32 directory so we can sanity check the WinSxS result. */ + WCHAR wszSysDir[MAX_PATH]; + UINT cwcSysDir = GetSystemDirectoryW(wszSysDir, MAX_PATH); + if (cwcSysDir >= MAX_PATH) + return VERR_FILENAME_TOO_LONG; + + /* Try side-by-side first (see COMCTL32.DLL). */ + if (s_pfnApplyRedir) + { + size_t cwcName = 0; + RTUTF16 wszName[MAX_PATH]; + PRTUTF16 pwszName = wszName; + int rc2 = RTStrToUtf16Ex(pszFilename, RTSTR_MAX, &pwszName, RT_ELEMENTS(wszName), &cwcName); + if (RT_SUCCESS(rc2)) + { + static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll"); + WCHAR wszPath[MAX_PATH]; + UNICODE_STRING UniStrStatic = { 0, (USHORT)sizeof(wszPath) - sizeof(WCHAR), wszPath }; + UNICODE_STRING UniStrDynamic = { 0, 0, NULL }; + PUNICODE_STRING pUniStrResult = NULL; + UNICODE_STRING UniStrName = + { (USHORT)(cwcName * sizeof(RTUTF16)), (USHORT)((cwcName + 1) * sizeof(RTUTF16)), wszName }; + + NTSTATUS rcNt = s_pfnApplyRedir(1 /*fFlags*/, + &UniStrName, + (PUNICODE_STRING)&s_DefaultSuffix, + &UniStrStatic, + &UniStrDynamic, + &pUniStrResult, + NULL /*pNewFlags*/, + NULL /*pcbFilename*/, + NULL /*pcbNeeded*/); + if (NT_SUCCESS(rcNt)) + { + /* + * Check that the resolved path has similarities to the + * system directory. + * + * ASSUMES the windows directory is a root directory and + * that both System32 and are on the same level. So, we'll + * have 2 matching components (or more if the resolver + * returns a system32 path for some reason). + */ + unsigned cMatchingComponents = 0; + size_t off = 0; + while (off < pUniStrResult->Length) + { + RTUTF16 wc1 = wszSysDir[off]; + RTUTF16 wc2 = pUniStrResult->Buffer[off]; + if (!RTPATH_IS_SLASH(wc1)) + { + if (wc1 == wc2) + off++; + else if ( wc1 < 127 + && wc2 < 127 + && RT_C_TO_LOWER(wc1) == RT_C_TO_LOWER(wc2) ) + off++; + else + break; + } + else if (RTPATH_IS_SLASH(wc2)) + { + cMatchingComponents += off > 0; + do + off++; + while ( off < pUniStrResult->Length + && RTPATH_IS_SLASH(wszSysDir[off]) + && RTPATH_IS_SLASH(pUniStrResult->Buffer[off])); + } + else + break; + } + if (cMatchingComponents >= 2) + { + pszPath = szPath; + rc2 = RTUtf16ToUtf8Ex(pUniStrResult->Buffer, pUniStrResult->Length / sizeof(RTUTF16), + &pszPath, sizeof(szPath), NULL); + if (RT_SUCCESS(rc2)) + rc = VINF_SUCCESS; + } + else + AssertMsgFailed(("%s -> '%*.ls'\n", pszFilename, pUniStrResult->Length, pUniStrResult->Buffer)); + RtlFreeUnicodeString(&UniStrDynamic); + } + } + else + AssertMsgFailed(("%Rrc\n", rc)); + } + + /* If the above didn't succeed, create a system32 path. */ + if (RT_FAILURE(rc)) + { + rc = RTUtf16ToUtf8Ex(wszSysDir, RTSTR_MAX, &pszPath, sizeof(szPath), NULL); + if (RT_SUCCESS(rc)) + { + rc = RTPathAppend(szPath, sizeof(szPath), pszFilename); + if (pszExt && RT_SUCCESS(rc)) + rc = RTStrCat(szPath, sizeof(szPath), pszExt); + } + } + + /* Do the actual loading, if we were successful constructing a name. */ + if (RT_SUCCESS(rc)) + { + if (RTFileExists(szPath)) + rc = RTLdrLoadEx(szPath, phLdrMod, fFlags, NULL); + else + rc = VERR_MODULE_NOT_FOUND; + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/localipc-win.cpp b/src/VBox/Runtime/r3/win/localipc-win.cpp new file mode 100644 index 00000000..6d56ab91 --- /dev/null +++ b/src/VBox/Runtime/r3/win/localipc-win.cpp @@ -0,0 +1,1778 @@ +/* $Id: localipc-win.cpp $ */ +/** @file + * IPRT - Local IPC, Windows Implementation Using Named Pipes. + * + * @note This code only works on W2K because of the dependency on + * ConvertStringSecurityDescriptorToSecurityDescriptor. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LOCALIPC +#include <iprt/nt/nt-and-windows.h> /* Need NtCancelIoFile and a few Rtl functions. */ +#include <sddl.h> +#include <aclapi.h> + +#include "internal/iprt.h" +#include <iprt/localipc.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/utf16.h> + +#include "internal/magics.h" +#include "internal-r3-win.h" + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Pipe prefix string. */ +#define RTLOCALIPC_WIN_PREFIX L"\\\\.\\pipe\\IPRT-" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Local IPC service instance, Windows. + */ +typedef struct RTLOCALIPCSERVERINT +{ + /** The magic (RTLOCALIPCSERVER_MAGIC). */ + uint32_t u32Magic; + /** The creation flags. */ + uint32_t fFlags; + /** Critical section protecting the structure. */ + RTCRITSECT CritSect; + /** The number of references to the instance. + * @remarks The reference counting isn't race proof. */ + uint32_t volatile cRefs; + /** Indicates that there is a pending cancel request. */ + bool volatile fCancelled; + /** The named pipe handle. */ + HANDLE hNmPipe; + /** The handle to the event object we're using for overlapped I/O. */ + HANDLE hEvent; + /** The overlapped I/O structure. */ + OVERLAPPED OverlappedIO; + /** The full pipe name (variable length). */ + RTUTF16 wszName[1]; +} RTLOCALIPCSERVERINT; +/** Pointer to a local IPC server instance (Windows). */ +typedef RTLOCALIPCSERVERINT *PRTLOCALIPCSERVERINT; + + +/** + * Local IPC session instance, Windows. + * + * This is a named pipe and we should probably merge the pipe code with this to + * save work and code duplication. + */ +typedef struct RTLOCALIPCSESSIONINT +{ + /** The magic (RTLOCALIPCSESSION_MAGIC). */ + uint32_t u32Magic; + /** Critical section protecting the structure. */ + RTCRITSECT CritSect; + /** The number of references to the instance. + * @remarks The reference counting isn't race proof. */ + uint32_t volatile cRefs; + /** Set if the zero byte read that the poll code using is pending. */ + bool fZeroByteRead; + /** Indicates that there is a pending cancel request. */ + bool volatile fCancelled; + /** Set if this is the server side, clear if the client. */ + bool fServerSide; + /** The named pipe handle. */ + HANDLE hNmPipe; + struct + { + RTTHREAD hActiveThread; + /** The handle to the event object we're using for overlapped I/O. */ + HANDLE hEvent; + /** The overlapped I/O structure. */ + OVERLAPPED OverlappedIO; + } + /** Overlapped reads. */ + Read, + /** Overlapped writes. */ + Write; +#if 0 /* Non-blocking writes are not yet supported. */ + /** Bounce buffer for writes. */ + uint8_t *pbBounceBuf; + /** Amount of used buffer space. */ + size_t cbBounceBufUsed; + /** Amount of allocated buffer space. */ + size_t cbBounceBufAlloc; +#endif + /** Buffer for the zero byte read. + * Used in RTLocalIpcSessionWaitForData(). */ + uint8_t abBuf[8]; +} RTLOCALIPCSESSIONINT; +/** Pointer to a local IPC session instance (Windows). */ +typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession); + + +/** + * DACL for block all network access and local users other than the creator/owner. + * + * ACE format: (ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid) + * + * Note! FILE_GENERIC_WRITE (SDDL_FILE_WRITE) is evil here because it includes + * the FILE_CREATE_PIPE_INSTANCE(=FILE_APPEND_DATA) flag. Thus the hardcoded + * value 0x0012019b in the client ACE. The server-side still needs + * setting FILE_CREATE_PIPE_INSTANCE although. + * It expands to: + * 0x00000001 - FILE_READ_DATA + * 0x00000008 - FILE_READ_EA + * 0x00000080 - FILE_READ_ATTRIBUTES + * 0x00020000 - READ_CONTROL + * 0x00100000 - SYNCHRONIZE + * 0x00000002 - FILE_WRITE_DATA + * 0x00000010 - FILE_WRITE_EA + * 0x00000100 - FILE_WRITE_ATTRIBUTES + * = 0x0012019b (client) + * + (only for server): + * 0x00000004 - FILE_CREATE_PIPE_INSTANCE + * = 0x0012019f + * + * @todo Triple check this! + * @todo EVERYONE -> AUTHENTICATED USERS or something more appropriate? + * @todo Have trouble allowing the owner FILE_CREATE_PIPE_INSTANCE access, so for now I'm hacking + * it just to get progress - the service runs as local system. + * The CREATOR OWNER and PERSONAL SELF works (the former is only involved in inheriting + * it seems, which is why it won't work. The latter I've no idea about. Perhaps the solution + * is to go the annoying route of OpenProcessToken, QueryTokenInformation, + * ConvertSidToStringSid and then use the result... Suggestions are very welcome + */ +#define RTLOCALIPC_WIN_SDDL_BASE \ + SDDL_DACL SDDL_DELIMINATOR \ + SDDL_ACE_BEGIN SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK SDDL_ACE_END \ + SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END +#define RTLOCALIPC_WIN_SDDL_SERVER \ + RTLOCALIPC_WIN_SDDL_BASE \ + SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019f" L";;;" SDDL_EVERYONE SDDL_ACE_END +#define RTLOCALIPC_WIN_SDDL_CLIENT \ + RTLOCALIPC_WIN_SDDL_BASE \ + SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE SDDL_ACE_END +static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) +{ + static SID_IDENTIFIER_AUTHORITY s_NtAuth = SECURITY_NT_AUTHORITY; + static SID_IDENTIFIER_AUTHORITY s_WorldAuth = SECURITY_WORLD_SID_AUTHORITY; + union + { + SID Sid; + uint8_t abPadding[SECURITY_MAX_SID_SIZE]; + } Network, LocalSystem, Everyone; + + + /* 1. SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK */ + NTSTATUS rcNt = RtlInitializeSid(&Network.Sid, &s_NtAuth, 1); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + *RtlSubAuthoritySid(&Network.Sid, 0) = SECURITY_NETWORK_RID; + + rcNt = RtlAddAccessDeniedAce(pDacl, ACL_REVISION, GENERIC_ALL, &Network.Sid); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + + /* 2. SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM */ + rcNt = RtlInitializeSid(&LocalSystem.Sid, &s_NtAuth, 1); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + *RtlSubAuthoritySid(&LocalSystem.Sid, 0) = SECURITY_LOCAL_SYSTEM_RID; + + rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, FILE_ALL_ACCESS, &Network.Sid); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + + + /* 3. server: SDDL_ACCESS_ALLOWED L";;" L"0x0012019f" L";;;" SDDL_EVERYONE + client: SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE */ + rcNt = RtlInitializeSid(&Everyone.Sid, &s_NtAuth, 1); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + *RtlSubAuthoritySid(&Everyone.Sid, 0) = SECURITY_WORLD_RID; + + DWORD const fAccess = FILE_READ_DATA /* 0x00000001 */ + | FILE_WRITE_DATA /* 0x00000002 */ + | FILE_CREATE_PIPE_INSTANCE * fServer /* 0x00000004 */ + | FILE_READ_EA /* 0x00000008 */ + | FILE_WRITE_EA /* 0x00000010 */ + | FILE_READ_ATTRIBUTES /* 0x00000080 */ + | FILE_WRITE_ATTRIBUTES /* 0x00000100 */ + | READ_CONTROL /* 0x00020000 */ + | SYNCHRONIZE; /* 0x00100000*/ + Assert(fAccess == (fServer ? 0x0012019fU : 0x0012019bU)); + + rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, fAccess, &Network.Sid); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + + return true; +} + + +/** + * Builds and allocates the security descriptor required for securing the local pipe. + * + * @return IPRT status code. + * @param ppDesc Where to store the allocated security descriptor on success. + * Must be free'd using LocalFree(). + * @param fServer Whether it's for a server or client instance. + */ +static int rtLocalIpcServerWinAllocSecurityDescriptor(PSECURITY_DESCRIPTOR *ppDesc, bool fServer) +{ + int rc; + PSECURITY_DESCRIPTOR pSecDesc = NULL; + +#if 0 + /* + * Resolve the API the first time around. + */ + static bool volatile s_fResolvedApis = false; + /** advapi32.dll API ConvertStringSecurityDescriptorToSecurityDescriptorW. */ + static decltype(ConvertStringSecurityDescriptorToSecurityDescriptorW) *s_pfnSSDLToSecDescW = NULL; + + if (!s_fResolvedApis) + { + s_pfnSSDLToSecDescW + = (decltype(s_pfnSSDLToSecDescW))RTLdrGetSystemSymbol("advapi32.dll", + "ConvertStringSecurityDescriptorToSecurityDescriptorW"); + ASMCompilerBarrier(); + s_fResolvedApis = true; + } + if (s_pfnSSDLToSecDescW) + { + /* + * We'll create a security descriptor from a SDDL that denies + * access to network clients (this is local IPC after all), it + * makes some further restrictions to prevent non-authenticated + * users from screwing around. + */ + PCRTUTF16 pwszSDDL = fServer ? RTLOCALIPC_WIN_SDDL_SERVER : RTLOCALIPC_WIN_SDDL_CLIENT; + ULONG cbSecDesc = 0; + SetLastError(0); + if (s_pfnSSDLToSecDescW(pwszSDDL, SDDL_REVISION_1, &pSecDesc, &cbSecDesc)) + { + DWORD dwErr = GetLastError(); RT_NOREF(dwErr); + AssertPtr(pSecDesc); + *ppDesc = pSecDesc; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromWin32(GetLastError()); + } + else +#endif + { + /* + * Manually construct the descriptor. + * + * This is a bit crude. The 8KB is probably 50+ times more than what we need. + */ + uint32_t const cbAlloc = SECURITY_DESCRIPTOR_MIN_LENGTH * 2 + _8K; + pSecDesc = LocalAlloc(LMEM_FIXED, cbAlloc); + if (!pSecDesc) + return VERR_NO_MEMORY; + RT_BZERO(pSecDesc, cbAlloc); + + uint32_t const cbDacl = cbAlloc - SECURITY_DESCRIPTOR_MIN_LENGTH * 2; + PACL const pDacl = (PACL)((uint8_t *)pSecDesc + SECURITY_DESCRIPTOR_MIN_LENGTH * 2); + + if ( InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION) + && InitializeAcl(pDacl, cbDacl, ACL_REVISION)) + { + if (rtLocalIpcBuildDacl(pDacl, fServer)) + { + *ppDesc = pSecDesc; + return VINF_SUCCESS; + } + rc = VERR_GENERAL_FAILURE; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + LocalFree(pSecDesc); + } + return rc; +} + + +/** + * Creates a named pipe instance. + * + * This is used by both RTLocalIpcServerCreate and RTLocalIpcServerListen. + * + * @return IPRT status code. + * @param phNmPipe Where to store the named pipe handle on success. + * This will be set to INVALID_HANDLE_VALUE on failure. + * @param pwszPipeName The named pipe name, full, UTF-16 encoded. + * @param fFirst Set on the first call (from RTLocalIpcServerCreate), + * otherwise clear. Governs the + * FILE_FLAG_FIRST_PIPE_INSTANCE flag. + */ +static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pwszPipeName, bool fFirst) +{ + *phNmPipe = INVALID_HANDLE_VALUE; + + /* + * Create a security descriptor blocking access to the pipe via network. + */ + PSECURITY_DESCRIPTOR pSecDesc; + int rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, fFirst /* Server? */); + if (RT_SUCCESS(rc)) + { +#if 0 + { /* Just for checking the security descriptor out in the debugger (!sd <addr> doesn't work): */ + DWORD dwRet = LookupSecurityDescriptorPartsW(NULL, NULL, NULL, NULL, NULL, NULL, pSecDesc); + __debugbreak(); RT_NOREF(dwRet); + + PTRUSTEE_W pOwner = NULL; + PTRUSTEE_W pGroup = NULL; + ULONG cAces = 0; + PEXPLICIT_ACCESS_W paAces = NULL; + ULONG cAuditEntries = 0; + PEXPLICIT_ACCESS_W paAuditEntries = NULL; + dwRet = LookupSecurityDescriptorPartsW(&pOwner, NULL, NULL, NULL, NULL, NULL, pSecDesc); + dwRet = LookupSecurityDescriptorPartsW(NULL, &pGroup, NULL, NULL, NULL, NULL, pSecDesc); + dwRet = LookupSecurityDescriptorPartsW(NULL, NULL, &cAces, &paAces, NULL, NULL, pSecDesc); + dwRet = LookupSecurityDescriptorPartsW(NULL, NULL, NULL, NULL, &cAuditEntries, &paAuditEntries, pSecDesc); + __debugbreak(); RT_NOREF(dwRet); + } +#endif + + /* + * Now, create the pipe. + */ + SECURITY_ATTRIBUTES SecAttrs; + SecAttrs.nLength = sizeof(SECURITY_ATTRIBUTES); + SecAttrs.lpSecurityDescriptor = pSecDesc; + SecAttrs.bInheritHandle = FALSE; + + DWORD fOpenMode = PIPE_ACCESS_DUPLEX + | PIPE_WAIT + | FILE_FLAG_OVERLAPPED; + if ( fFirst + && ( g_enmWinVer >= kRTWinOSType_XP + || ( g_enmWinVer == kRTWinOSType_2K + && g_WinOsInfoEx.wServicePackMajor >= 2) ) ) + fOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE; /* Introduced with W2K SP2 */ + + HANDLE hNmPipe = CreateNamedPipeW(pwszPipeName, /* lpName */ + fOpenMode, /* dwOpenMode */ + PIPE_TYPE_BYTE, /* dwPipeMode */ + PIPE_UNLIMITED_INSTANCES, /* nMaxInstances */ + PAGE_SIZE, /* nOutBufferSize (advisory) */ + PAGE_SIZE, /* nInBufferSize (ditto) */ + 30*1000, /* nDefaultTimeOut = 30 sec */ + &SecAttrs); /* lpSecurityAttributes */ + if (hNmPipe != INVALID_HANDLE_VALUE) + { +#if 0 /* For checking access control stuff in windbg (doesn't work): */ + PSECURITY_DESCRIPTOR pSecDesc2 = NULL; + PACL pDacl = NULL; + DWORD dwRet; + dwRet = GetSecurityInfo(hNmPipe, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &pSecDesc2); + PACL pSacl = NULL; + dwRet = GetSecurityInfo(hNmPipe, SE_FILE_OBJECT, SACL_SECURITY_INFORMATION, NULL, NULL, NULL, &pSacl, &pSecDesc2); + dwRet = GetSecurityInfo(hNmPipe, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, &pSacl, &pSecDesc2); + PSID pSidOwner = NULL; + dwRet = GetSecurityInfo(hNmPipe, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pSidOwner, NULL, NULL, NULL, &pSecDesc2); + PSID pSidGroup = NULL; + dwRet = GetSecurityInfo(hNmPipe, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, NULL, &pSidGroup, NULL, NULL, &pSecDesc2); + __debugbreak(); + RT_NOREF(dwRet); +#endif + *phNmPipe = hNmPipe; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + LocalFree(pSecDesc); + } + return rc; +} + + +/** + * Validates the user specified name. + * + * @returns IPRT status code. + * @param pszName The name to validate. + * @param pcwcFullName Where to return the UTF-16 length of the full name. + * @param fNative Whether it's a native name or a portable name. + */ +static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, bool fNative) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(*pszName, VERR_INVALID_NAME); + + if (!fNative) + { + size_t cwcName = RT_ELEMENTS(RTLOCALIPC_WIN_PREFIX) - 1; + for (;;) + { + char ch = *pszName++; + if (!ch) + break; + AssertReturn(!RT_C_IS_CNTRL(ch), VERR_INVALID_NAME); + AssertReturn((unsigned)ch < 0x80, VERR_INVALID_NAME); + AssertReturn(ch != '\\', VERR_INVALID_NAME); + AssertReturn(ch != '/', VERR_INVALID_NAME); + cwcName++; + } + *pcwcFullName = cwcName; + } + else + { + int rc = RTStrCalcUtf16LenEx(pszName, RTSTR_MAX, pcwcFullName); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + + +/** + * Constructs the full pipe name as UTF-16. + * + * @returns IPRT status code. + * @param pszName The user supplied name. ASSUMES reasonable length + * for now, so no long path prefixing needed. + * @param pwszFullName The output buffer. + * @param cwcFullName The output buffer size excluding the terminator. + * @param fNative Whether the user supplied name is a native or + * portable one. + */ +static int rtLocalIpcWinConstructName(const char *pszName, PRTUTF16 pwszFullName, size_t cwcFullName, bool fNative) +{ + if (!fNative) + { + static RTUTF16 const s_wszPrefix[] = RTLOCALIPC_WIN_PREFIX; + Assert(cwcFullName * sizeof(RTUTF16) > sizeof(s_wszPrefix)); + memcpy(pwszFullName, s_wszPrefix, sizeof(s_wszPrefix)); + cwcFullName -= RT_ELEMENTS(s_wszPrefix) - 1; + pwszFullName += RT_ELEMENTS(s_wszPrefix) - 1; + } + return RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszFullName, cwcFullName + 1, NULL); +} + + +RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszName, uint32_t fFlags) +{ + /* + * Validate parameters. + */ + AssertPtrReturn(phServer, VERR_INVALID_POINTER); + *phServer = NIL_RTLOCALIPCSERVER; + AssertReturn(!(fFlags & ~RTLOCALIPC_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + size_t cwcFullName; + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and initialize the instance data. + */ + size_t cbThis = RT_UOFFSETOF_DYN(RTLOCALIPCSERVERINT, wszName[cwcFullName + 1]); + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)RTMemAllocVar(cbThis); + AssertReturn(pThis, VERR_NO_MEMORY); + + pThis->u32Magic = RTLOCALIPCSERVER_MAGIC; + pThis->cRefs = 1; /* the one we return */ + pThis->fCancelled = false; + + rc = rtLocalIpcWinConstructName(pszName, pThis->wszName, cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pThis->hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/, + FALSE /*bInitialState*/, NULL /*lpName*/); + if (pThis->hEvent != NULL) + { + RT_ZERO(pThis->OverlappedIO); + pThis->OverlappedIO.Internal = STATUS_PENDING; + pThis->OverlappedIO.hEvent = pThis->hEvent; + + rc = rtLocalIpcServerWinCreatePipeInstance(&pThis->hNmPipe, pThis->wszName, true /* fFirst */); + if (RT_SUCCESS(rc)) + { + *phServer = pThis; + return VINF_SUCCESS; + } + + BOOL fRc = CloseHandle(pThis->hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + } + } + RTMemFree(pThis); + } + return rc; +} + + +/** + * Retains a reference to the server instance. + * + * @param pThis The server instance. + */ +DECLINLINE(void) rtLocalIpcServerRetain(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); NOREF(cRefs); +} + + +/** + * Call when the reference count reaches 0. + * + * Caller owns the critsect. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The instance to destroy. + */ +DECL_NO_INLINE(static, int) rtLocalIpcServerWinDestroy(PRTLOCALIPCSERVERINT pThis) +{ + Assert(pThis->u32Magic == ~RTLOCALIPCSERVER_MAGIC); + pThis->u32Magic = ~RTLOCALIPCSERVER_MAGIC; + + BOOL fRc = CloseHandle(pThis->hNmPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + pThis->hNmPipe = INVALID_HANDLE_VALUE; + + fRc = CloseHandle(pThis->hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + pThis->hEvent = NULL; + + RTCritSectLeave(&pThis->CritSect); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + return VINF_OBJECT_DESTROYED; +} + + +/** + * Server instance destructor. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The server instance. + */ +DECL_NO_INLINE(static, int) rtLocalIpcServerDtor(PRTLOCALIPCSERVERINT pThis) +{ + RTCritSectEnter(&pThis->CritSect); + return rtLocalIpcServerWinDestroy(pThis); +} + + +/** + * Releases a reference to the server instance. + * + * @returns VINF_SUCCESS if only release, VINF_OBJECT_DESTROYED if destroyed. + * @param pThis The server instance. + */ +DECLINLINE(int) rtLocalIpcServerRelease(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcServerDtor(pThis); + return VINF_SUCCESS; +} + + +/** + * Releases a reference to the server instance and leaves the critsect. + * + * @returns VINF_SUCCESS if only release, VINF_OBJECT_DESTROYED if destroyed. + * @param pThis The server instance. + */ +DECLINLINE(int) rtLocalIpcServerReleaseAndUnlock(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcServerWinDestroy(pThis); + return RTCritSectLeave(&pThis->CritSect); +} + + + +RTDECL(int) RTLocalIpcServerDestroy(RTLOCALIPCSERVER hServer) +{ + /* + * Validate input. + */ + if (hServer == NIL_RTLOCALIPCSERVER) + return VINF_SUCCESS; + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + + /* + * Cancel any thread currently busy using the server, + * leaving the cleanup to it. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTLOCALIPCSERVER_MAGIC, RTLOCALIPCSERVER_MAGIC), VERR_WRONG_ORDER); + + RTCritSectEnter(&pThis->CritSect); + + /* Cancel everything. */ + ASMAtomicUoWriteBool(&pThis->fCancelled, true); + if (pThis->cRefs > 1) + { + BOOL fRc = SetEvent(pThis->hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + + return rtLocalIpcServerReleaseAndUnlock(pThis); +} + + +RTDECL(int) RTLocalIpcServerGrantGroupAccess(RTLOCALIPCSERVER hServer, RTGID gid) +{ + RT_NOREF_PV(hServer); RT_NOREF(gid); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION phClientSession) +{ + /* + * Validate input. + */ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(phClientSession, VERR_INVALID_POINTER); + + /* + * Enter the critsect before inspecting the object further. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + + rtLocalIpcServerRetain(pThis); + if (!pThis->fCancelled) + { + ResetEvent(pThis->hEvent); + + RTCritSectLeave(&pThis->CritSect); + + /* + * Try connect a client. We need to use overlapped I/O here because + * of the cancellation done by RTLocalIpcServerCancel and RTLocalIpcServerDestroy. + */ + SetLastError(NO_ERROR); + BOOL fRc = ConnectNamedPipe(pThis->hNmPipe, &pThis->OverlappedIO); + DWORD dwErr = fRc ? NO_ERROR : GetLastError(); + if ( !fRc + && dwErr == ERROR_IO_PENDING) + { + WaitForSingleObject(pThis->hEvent, INFINITE); + DWORD dwIgnored; + fRc = GetOverlappedResult(pThis->hNmPipe, &pThis->OverlappedIO, &dwIgnored, FALSE /* bWait*/); + dwErr = fRc ? NO_ERROR : GetLastError(); + } + + RTCritSectEnter(&pThis->CritSect); + if ( !pThis->fCancelled /* Event signalled but not cancelled? */ + && pThis->u32Magic == RTLOCALIPCSERVER_MAGIC) + { + /* + * Still alive, some error or an actual client. + * + * If it's the latter we'll have to create a new pipe instance that + * replaces the current one for the server. The current pipe instance + * will be assigned to the client session. + */ + if ( fRc + || dwErr == ERROR_PIPE_CONNECTED) + { + HANDLE hNmPipe; + rc = rtLocalIpcServerWinCreatePipeInstance(&hNmPipe, pThis->wszName, false /* fFirst */); + if (RT_SUCCESS(rc)) + { + HANDLE hNmPipeSession = pThis->hNmPipe; /* consumed */ + pThis->hNmPipe = hNmPipe; + rc = rtLocalIpcWinCreateSession(phClientSession, hNmPipeSession); + } + else + { + /* + * We failed to create a new instance for the server, disconnect + * the client and fail. Don't try service the client here. + */ + fRc = DisconnectNamedPipe(pThis->hNmPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); + } + } + else + rc = RTErrConvertFromWin32(dwErr); + } + else + { + /* + * Cancelled. + * + * Cancel the overlapped io if it didn't complete (must be done + * in the this thread) or disconnect the client. + */ + Assert(pThis->fCancelled); + if ( fRc + || dwErr == ERROR_PIPE_CONNECTED) + fRc = DisconnectNamedPipe(pThis->hNmPipe); + else if (dwErr == ERROR_IO_PENDING) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtCancelIoFile(pThis->hNmPipe, &Ios); + fRc = NT_SUCCESS(rcNt); + } + else + fRc = TRUE; + AssertMsg(fRc, ("%d\n", GetLastError())); + rc = VERR_CANCELLED; + } + } + else + { + /*pThis->fCancelled = false; - Terrible interface idea. Add API to clear fCancelled if ever required. */ + rc = VERR_CANCELLED; + } + rtLocalIpcServerReleaseAndUnlock(pThis); + return rc; +} + + +RTDECL(int) RTLocalIpcServerCancel(RTLOCALIPCSERVER hServer) +{ + /* + * Validate input. + */ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + + /* + * Enter the critical section, then set the cancellation flag + * and signal the event (to wake up anyone in/at WaitForSingleObject). + */ + rtLocalIpcServerRetain(pThis); + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + ASMAtomicUoWriteBool(&pThis->fCancelled, true); + + BOOL fRc = SetEvent(pThis->hEvent); + if (fRc) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + AssertMsgFailed(("dwErr=%u\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + + rtLocalIpcServerReleaseAndUnlock(pThis); + } + else + rtLocalIpcServerRelease(pThis); + return rc; +} + + +/** + * Create a session instance for a new server client or a client connect. + * + * @returns IPRT status code. + * + * @param ppSession Where to store the session handle on success. + * @param hNmPipeSession The named pipe handle if server calling, + * INVALID_HANDLE_VALUE if client connect. This will + * be consumed by this session, meaning on failure to + * create the session it will be closed. + */ +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession) +{ + AssertPtr(ppSession); + + /* + * Allocate and initialize the session instance data. + */ + int rc; + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTLOCALIPCSESSION_MAGIC; + pThis->cRefs = 1; /* our ref */ + pThis->fCancelled = false; + pThis->fZeroByteRead = false; + pThis->fServerSide = hNmPipeSession != INVALID_HANDLE_VALUE; + pThis->hNmPipe = hNmPipeSession; +#if 0 /* Non-blocking writes are not yet supported. */ + pThis->pbBounceBuf = NULL; + pThis->cbBounceBufAlloc = 0; + pThis->cbBounceBufUsed = 0; +#endif + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pThis->Read.hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/, + FALSE /*bInitialState*/, NULL /*lpName*/); + if (pThis->Read.hEvent != NULL) + { + pThis->Read.OverlappedIO.Internal = STATUS_PENDING; + pThis->Read.OverlappedIO.hEvent = pThis->Read.hEvent; + pThis->Read.hActiveThread = NIL_RTTHREAD; + + pThis->Write.hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/, + FALSE /*bInitialState*/, NULL /*lpName*/); + if (pThis->Write.hEvent != NULL) + { + pThis->Write.OverlappedIO.Internal = STATUS_PENDING; + pThis->Write.OverlappedIO.hEvent = pThis->Write.hEvent; + pThis->Write.hActiveThread = NIL_RTTHREAD; + + *ppSession = pThis; + return VINF_SUCCESS; + } + + CloseHandle(pThis->Read.hEvent); + } + + /* bail out */ + rc = RTErrConvertFromWin32(GetLastError()); + RTCritSectDelete(&pThis->CritSect); + } + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + if (hNmPipeSession != INVALID_HANDLE_VALUE) + { + BOOL fRc = CloseHandle(hNmPipeSession); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + return rc; +} + + +RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *pszName, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(phSession, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTLOCALIPC_C_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + + size_t cwcFullName; + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + /* + * Create a session (shared with server client session creation). + */ + PRTLOCALIPCSESSIONINT pThis; + rc = rtLocalIpcWinCreateSession(&pThis, INVALID_HANDLE_VALUE); + if (RT_SUCCESS(rc)) + { + /* + * Try open the pipe. + */ + PSECURITY_DESCRIPTOR pSecDesc; + rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, false /*fServer*/); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszFullName = RTUtf16Alloc((cwcFullName + 1) * sizeof(RTUTF16)); + if (pwszFullName) + rc = rtLocalIpcWinConstructName(pszName, pwszFullName, cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + else + rc = VERR_NO_UTF16_MEMORY; + if (RT_SUCCESS(rc)) + { + SECURITY_ATTRIBUTES SecAttrs; + SecAttrs.nLength = sizeof(SECURITY_ATTRIBUTES); + SecAttrs.lpSecurityDescriptor = pSecDesc; + SecAttrs.bInheritHandle = FALSE; + + /* The SECURITY_XXX flags are needed in order to prevent the server from impersonating with + this thread's security context (supported at least back to NT 3.51). See @bugref{9773}. */ + HANDLE hPipe = CreateFileW(pwszFullName, + GENERIC_READ | GENERIC_WRITE, + 0 /*no sharing*/, + &SecAttrs, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, + NULL /*no template handle*/); + if (hPipe != INVALID_HANDLE_VALUE) + { + pThis->hNmPipe = hPipe; + + LocalFree(pSecDesc); + RTUtf16Free(pwszFullName); + + /* + * We're done! + */ + *phSession = pThis; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromWin32(GetLastError()); + } + + RTUtf16Free(pwszFullName); + LocalFree(pSecDesc); + } + + /* destroy the session handle. */ + CloseHandle(pThis->Read.hEvent); + CloseHandle(pThis->Write.hEvent); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + } + } + return rc; +} + + +/** + * Cancells all pending I/O operations, forcing the methods to return with + * VERR_CANCELLED (unless they've got actual data to return). + * + * Used by RTLocalIpcSessionCancel and RTLocalIpcSessionClose. + * + * @returns IPRT status code. + * @param pThis The client session instance. + */ +static int rtLocalIpcWinCancel(PRTLOCALIPCSESSIONINT pThis) +{ + ASMAtomicUoWriteBool(&pThis->fCancelled, true); + + /* + * Call NtCancelIoFile since this call cancels both read and write + * oriented operations. + */ + if ( pThis->fZeroByteRead + || pThis->Read.hActiveThread != NIL_RTTHREAD + || pThis->Write.hActiveThread != NIL_RTTHREAD) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NtCancelIoFile(pThis->hNmPipe, &Ios); + } + + /* + * Set both event semaphores. + */ + BOOL fRc = SetEvent(pThis->Read.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + fRc = SetEvent(pThis->Write.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + + return VINF_SUCCESS; +} + + +/** + * Retains a reference to the session instance. + * + * @param pThis The client session instance. + */ +DECLINLINE(void) rtLocalIpcSessionRetain(PRTLOCALIPCSESSIONINT pThis) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); NOREF(cRefs); +} + + +RTDECL(uint32_t) RTLocalIpcSessionRetain(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); + return cRefs; +} + + +/** + * Call when the reference count reaches 0. + * + * Caller owns the critsect. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The instance to destroy. + */ +DECL_NO_INLINE(static, int) rtLocalIpcSessionWinDestroy(PRTLOCALIPCSESSIONINT pThis) +{ + BOOL fRc = CloseHandle(pThis->hNmPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + pThis->hNmPipe = INVALID_HANDLE_VALUE; + + fRc = CloseHandle(pThis->Write.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); + pThis->Write.hEvent = NULL; + + fRc = CloseHandle(pThis->Read.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); + pThis->Read.hEvent = NULL; + + int rc2 = RTCritSectLeave(&pThis->CritSect); AssertRC(rc2); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + return VINF_OBJECT_DESTROYED; +} + + +/** + * Releases a reference to the session instance and unlock it. + * + * @returns VINF_SUCCESS or VINF_OBJECT_DESTROYED as appropriate. + * @param pThis The session instance. + */ +DECLINLINE(int) rtLocalIpcSessionReleaseAndUnlock(PRTLOCALIPCSESSIONINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcSessionWinDestroy(pThis); + + int rc2 = RTCritSectLeave(&pThis->CritSect); AssertRC(rc2); + Log(("rtLocalIpcSessionReleaseAndUnlock: %u refs left\n", cRefs)); + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTLocalIpcSessionRelease(RTLOCALIPCSESSION hSession) +{ + if (hSession == NIL_RTLOCALIPCSESSION) + return 0; + + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (cRefs) + Log(("RTLocalIpcSessionRelease: %u refs left\n", cRefs)); + else + { + RTCritSectEnter(&pThis->CritSect); + rtLocalIpcSessionWinDestroy(pThis); + } + return cRefs; +} + + +RTDECL(int) RTLocalIpcSessionClose(RTLOCALIPCSESSION hSession) +{ + /* + * Validate input. + */ + if (hSession == NIL_RTLOCALIPCSESSION) + return VINF_SUCCESS; + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the instance, cancel all outstanding I/O and drop our reference. + */ + RTCritSectEnter(&pThis->CritSect); + rtLocalIpcWinCancel(pThis); + return rtLocalIpcSessionReleaseAndUnlock(pThis); +} + + +/** + * Handles WaitForSingleObject return value when waiting for a zero byte read. + * + * The zero byte read is started by the RTLocalIpcSessionWaitForData method and + * left pending when the function times out. This saves us the problem of + * NtCancelIoFile messing with all active I/O operations and the trouble of + * restarting the zero byte read the next time the method is called. However + * should RTLocalIpcSessionRead be called after a failed + * RTLocalIpcSessionWaitForData call, the zero byte read will still be pending + * and it must wait for it to complete before the OVERLAPPEDIO structure can be + * reused. + * + * Thus, both functions will do WaitForSingleObject and share this routine to + * handle the outcome. + * + * @returns IPRT status code. + * @param pThis The session instance. + * @param rcWait The WaitForSingleObject return code. + */ +static int rtLocalIpcWinGetZeroReadResult(PRTLOCALIPCSESSIONINT pThis, DWORD rcWait) +{ + int rc; + DWORD cbRead = 42; + if (rcWait == WAIT_OBJECT_0) + { + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, !pThis->fCancelled /*fWait*/)) + { + Assert(cbRead == 0); + rc = VINF_SUCCESS; + pThis->fZeroByteRead = false; + } + else if (pThis->fCancelled) + rc = VERR_CANCELLED; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + /* We try get the result here too, just in case we're lucky, but no waiting. */ + DWORD dwErr = GetLastError(); + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, FALSE /*fWait*/)) + { + Assert(cbRead == 0); + rc = VINF_SUCCESS; + pThis->fZeroByteRead = false; + } + else if (rcWait == WAIT_TIMEOUT) + rc = VERR_TIMEOUT; + else if (rcWait == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(dwErr); + } + return rc; +} + + +RTDECL(int) RTLocalIpcSessionRead(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + /* pcbRead is optional. */ + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Read.hActiveThread == NIL_RTTHREAD) + { + pThis->Read.hActiveThread = RTThreadSelf(); + + size_t cbTotalRead = 0; + while (cbToRead > 0) + { + DWORD cbRead = 0; + if (!pThis->fCancelled) + { + /* + * Wait for pending zero byte read, if necessary. + * Note! It cannot easily be cancelled due to concurrent current writes. + */ + if (!pThis->fZeroByteRead) + { /* likely */ } + else + { + RTCritSectLeave(&pThis->CritSect); + DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, RT_MS_1MIN); + RTCritSectEnter(&pThis->CritSect); + + rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait); + if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT) + continue; + break; + } + + /* + * Kick of a an overlapped read. It should return immediately if + * there is bytes in the buffer. If not, we'll cancel it and see + * what we get back. + */ + rc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(rc == TRUE); + RTCritSectLeave(&pThis->CritSect); + + if (ReadFile(pThis->hNmPipe, pvBuf, + cbToRead <= ~(DWORD)0 ? (DWORD)cbToRead : ~(DWORD)0, + &cbRead, &pThis->Read.OverlappedIO)) + { + RTCritSectEnter(&pThis->CritSect); + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, INFINITE); + + RTCritSectEnter(&pThis->CritSect); + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, TRUE /*fWait*/)) + rc = VINF_SUCCESS; + else + { + if (pThis->fCancelled) + rc = VERR_CANCELLED; + else + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailedBreak(("%Rrc\n", rc)); + } + } + else + { + rc = VERR_CANCELLED; + break; + } + + /* Advance. */ + cbToRead -= cbRead; + cbTotalRead += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + } + + if (pcbRead) + { + *pcbRead = cbTotalRead; + if ( RT_FAILURE(rc) + && cbTotalRead + && rc != VERR_INVALID_POINTER) + rc = VINF_SUCCESS; + } + + pThis->Read.hActiveThread = NIL_RTTHREAD; + } + else + rc = VERR_WRONG_ORDER; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionReadNB(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + *pcbRead = 0; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Read.hActiveThread == NIL_RTTHREAD) + { + pThis->Read.hActiveThread = RTThreadSelf(); + + for (;;) + { + DWORD cbRead = 0; + if (!pThis->fCancelled) + { + /* + * Wait for pending zero byte read, if necessary. + * Note! It cannot easily be cancelled due to concurrent current writes. + */ + if (!pThis->fZeroByteRead) + { /* likely */ } + else + { + RTCritSectLeave(&pThis->CritSect); + DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, 0); + RTCritSectEnter(&pThis->CritSect); + + rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait); + if (RT_SUCCESS(rc)) + continue; + + if (rc == VERR_TIMEOUT) + rc = VINF_TRY_AGAIN; + break; + } + + /* + * Figure out how much we can read (cannot try and cancel here + * like in the anonymous pipe code). + */ + DWORD cbAvailable; + if (PeekNamedPipe(pThis->hNmPipe, NULL, 0, NULL, &cbAvailable, NULL)) + { + if (cbAvailable == 0 || cbToRead == 0) + { + *pcbRead = 0; + rc = VINF_TRY_AGAIN; + break; + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + if (cbAvailable > cbToRead) + cbAvailable = (DWORD)cbToRead; + + /* + * Kick of a an overlapped read. It should return immediately, so we + * don't really need to leave the critsect here. + */ + rc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(rc == TRUE); + if (ReadFile(pThis->hNmPipe, pvBuf, cbAvailable, &cbRead, &pThis->Read.OverlappedIO)) + { + *pcbRead = cbRead; + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, 0); + if (rcWait == WAIT_TIMEOUT) + { + RTCritSectLeave(&pThis->CritSect); + rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, INFINITE); + RTCritSectEnter(&pThis->CritSect); + } + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, TRUE /*fWait*/)) + { + *pcbRead = cbRead; + rc = VINF_SUCCESS; + } + else + { + if (pThis->fCancelled) + rc = VERR_CANCELLED; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailedBreak(("%Rrc\n", rc)); + } + } + else + rc = VERR_CANCELLED; + break; + } + + pThis->Read.hActiveThread = NIL_RTTHREAD; + } + else + rc = VERR_WRONG_ORDER; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +#if 0 /* Non-blocking writes are not yet supported. */ +/** + * Common worker for handling I/O completion. + * + * This is used by RTLocalIpcSessionClose and RTLocalIpcSessionWrite. + * + * @returns IPRT status code. + * @param pThis The pipe instance handle. + */ +static int rtLocalIpcSessionWriteCheckCompletion(PRTLOCALIPCSESSIONINT pThis) +{ + int rc; + DWORD rcWait = WaitForSingleObject(pThis->OverlappedIO.hEvent, 0); + if (rcWait == WAIT_OBJECT_0) + { + DWORD cbWritten = 0; + if (GetOverlappedResult(pThis->hNmPipe, &pThis->OverlappedIO, &cbWritten, TRUE)) + { + for (;;) + { + if (cbWritten >= pThis->cbBounceBufUsed) + { + pThis->fIOPending = false; + rc = VINF_SUCCESS; + break; + } + + /* resubmit the remainder of the buffer - can this actually happen? */ + memmove(&pThis->pbBounceBuf[0], &pThis->pbBounceBuf[cbWritten], pThis->cbBounceBufUsed - cbWritten); + rc = ResetEvent(pThis->OverlappedIO.hEvent); Assert(rc == TRUE); + if (!WriteFile(pThis->hNmPipe, pThis->pbBounceBuf, (DWORD)pThis->cbBounceBufUsed, + &cbWritten, &pThis->OverlappedIO)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_IO_PENDING) + rc = VINF_TRY_AGAIN; + else + { + pThis->fIOPending = false; + if (dwErr == ERROR_NO_DATA) + rc = VERR_BROKEN_PIPE; + else + rc = RTErrConvertFromWin32(dwErr); + } + break; + } + Assert(cbWritten > 0); + } + } + else + { + pThis->fIOPending = false; + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else if (rcWait == WAIT_TIMEOUT) + rc = VINF_TRY_AGAIN; + else + { + pThis->fIOPending = false; + if (rcWait == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + return rc; +} +#endif + + +RTDECL(int) RTLocalIpcSessionWrite(RTLOCALIPCSESSION hSession, const void *pvBuf, size_t cbToWrite) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite, VERR_INVALID_PARAMETER); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Write.hActiveThread == NIL_RTTHREAD) + { + pThis->Write.hActiveThread = RTThreadSelf(); + + /* + * Try write everything. No bounce buffering necessary. + */ + size_t cbTotalWritten = 0; + while (cbToWrite > 0) + { + DWORD cbWritten = 0; + if (!pThis->fCancelled) + { + BOOL fRc = ResetEvent(pThis->Write.OverlappedIO.hEvent); Assert(fRc == TRUE); + RTCritSectLeave(&pThis->CritSect); + + DWORD const cbToWriteInThisIteration = cbToWrite <= ~(DWORD)0 ? (DWORD)cbToWrite : ~(DWORD)0; + fRc = WriteFile(pThis->hNmPipe, pvBuf, cbToWriteInThisIteration, &cbWritten, &pThis->Write.OverlappedIO); + if (fRc) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_IO_PENDING) + { + DWORD rcWait = WaitForSingleObject(pThis->Write.OverlappedIO.hEvent, INFINITE); + if (rcWait == WAIT_OBJECT_0) + { + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Write.OverlappedIO, &cbWritten, TRUE /*fWait*/)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (rcWait == WAIT_TIMEOUT) + rc = VERR_TIMEOUT; + else if (rcWait == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (dwErr == ERROR_NO_DATA) + rc = VERR_BROKEN_PIPE; + else + rc = RTErrConvertFromWin32(dwErr); + } + + if (cbWritten > cbToWriteInThisIteration) /* paranoia^3 */ + cbWritten = cbToWriteInThisIteration; + + RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + break; + } + else + { + rc = VERR_CANCELLED; + break; + } + + /* Advance. */ + pvBuf = (char const *)pvBuf + cbWritten; + cbTotalWritten += cbWritten; + cbToWrite -= cbWritten; + } + + pThis->Write.hActiveThread = NIL_RTTHREAD; + } + else + rc = VERR_WRONG_ORDER; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionFlush(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->Write.hActiveThread == NIL_RTTHREAD) + { + /* No flushing on Windows needed since RTLocalIpcSessionWrite will block until + * all data was written (or an error occurred). */ + /** @todo r=bird: above comment is misinformed. + * Implement this as soon as we want an explicit asynchronous version of + * RTLocalIpcSessionWrite on Windows. */ + rc = VINF_SUCCESS; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTLocalIpcSessionWaitForData(RTLOCALIPCSESSION hSession, uint32_t cMillies) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + uint64_t const msStart = RTTimeMilliTS(); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Read.hActiveThread == NIL_RTTHREAD) + { + pThis->Read.hActiveThread = RTThreadSelf(); + + /* + * Wait loop. + */ + for (unsigned iLoop = 0;; iLoop++) + { + /* + * Check for cancellation before we continue. + */ + if (!pThis->fCancelled) + { /* likely */ } + else + { + rc = VERR_CANCELLED; + break; + } + + /* + * Prep something we can wait on. + */ + HANDLE hWait = INVALID_HANDLE_VALUE; + if (pThis->fZeroByteRead) + hWait = pThis->Read.OverlappedIO.hEvent; + else + { + /* Peek at the pipe buffer and see how many bytes it contains. */ + DWORD cbAvailable; + if ( PeekNamedPipe(pThis->hNmPipe, NULL, 0, NULL, &cbAvailable, NULL) + && cbAvailable) + { + rc = VINF_SUCCESS; + break; + } + + /* Start a zero byte read operation that we can wait on. */ + if (cMillies == 0) + { + rc = VERR_TIMEOUT; + break; + } + BOOL fRc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(fRc == TRUE); NOREF(fRc); + DWORD cbRead = 0; + if (ReadFile(pThis->hNmPipe, pThis->abBuf, 0 /*cbToRead*/, &cbRead, &pThis->Read.OverlappedIO)) + { + rc = VINF_SUCCESS; + if (iLoop > 10) + RTThreadYield(); + } + else if (GetLastError() == ERROR_IO_PENDING) + { + pThis->fZeroByteRead = true; + hWait = pThis->Read.OverlappedIO.hEvent; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + if (RT_FAILURE(rc)) + break; + } + + /* + * Check for timeout. + */ + DWORD cMsMaxWait = INFINITE; /* (MSC maybe used uninitialized) */ + if (cMillies == RT_INDEFINITE_WAIT) + cMsMaxWait = INFINITE; + else if ( hWait != INVALID_HANDLE_VALUE + || iLoop > 10) + { + uint64_t cMsElapsed = RTTimeMilliTS() - msStart; + if (cMsElapsed <= cMillies) + cMsMaxWait = cMillies - (uint32_t)cMsElapsed; + else if (iLoop == 0) + cMsMaxWait = cMillies ? 1 : 0; + else + { + rc = VERR_TIMEOUT; + break; + } + } + + /* + * Wait and collect the result. + */ + if (hWait != INVALID_HANDLE_VALUE) + { + RTCritSectLeave(&pThis->CritSect); + + DWORD rcWait = WaitForSingleObject(hWait, cMsMaxWait); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait); + break; + } + } + + pThis->Read.hActiveThread = NIL_RTTHREAD; + } + + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Enter the critical section, then set the cancellation flag + * and signal the event (to wake up anyone in/at WaitForSingleObject). + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + rc = rtLocalIpcWinCancel(pThis); + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess) +{ + RT_NOREF_PV(hSession); RT_NOREF_PV(pProcess); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTLocalIpcSessionQueryUserId(RTLOCALIPCSESSION hSession, PRTUID pUid) +{ + RT_NOREF_PV(hSession); RT_NOREF_PV(pUid); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGid) +{ + RT_NOREF_PV(hSession); RT_NOREF_PV(pGid); + return VERR_NOT_SUPPORTED; +} + diff --git a/src/VBox/Runtime/r3/win/mp-win.cpp b/src/VBox/Runtime/r3/win/mp-win.cpp new file mode 100644 index 00000000..b76ab254 --- /dev/null +++ b/src/VBox/Runtime/r3/win/mp-win.cpp @@ -0,0 +1,822 @@ +/* $Id: mp-win.cpp $ */ +/** @file + * IPRT - Multiprocessor, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYSTEM +#include <iprt/win/windows.h> + +#include <iprt/mp.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/cpuset.h> +#include <iprt/ldr.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/param.h> +#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) +# include <iprt/asm-amd64-x86.h> +#endif +#if defined(VBOX) && !defined(IN_GUEST) && !defined(IN_RT_STATIC) +# include <VBox/sup.h> +# define IPRT_WITH_GIP_MP_INFO +#else +# undef IPRT_WITH_GIP_MP_INFO +#endif + +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def RTMPWIN_UPDATE_GIP_GLOBALS + * Does lazy (re-)initialization using information provieded by GIP. */ +#ifdef IPRT_WITH_GIP_MP_INFO +# define RTMPWIN_UPDATE_GIP_GLOBALS() \ + do { RTMPWIN_UPDATE_GIP_GLOBALS_AND_GET_PGIP(); } while (0) +#else +# define RTMPWIN_UPDATE_GIP_GLOBALS() do { } while (0) +#endif + +/** @def RTMPWIN_UPDATE_GIP_GLOBALS_AND_GET_PGIP + * Does lazy (re-)initialization using information provieded by GIP and + * declare and initalize a pGip local variable. */ +#ifdef IPRT_WITH_GIP_MP_INFO +#define RTMPWIN_UPDATE_GIP_GLOBALS_AND_GET_PGIP() \ + PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage; \ + if (pGip) \ + { \ + if ( pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC \ + && RTOnce(&g_MpInitOnceGip, rtMpWinInitOnceGip, NULL) == VINF_SUCCESS) \ + { \ + if (g_cRtMpWinActiveCpus >= pGip->cOnlineCpus) \ + { /* likely */ } \ + else \ + rtMpWinRefreshGip(); \ + } \ + else \ + pGip = NULL; \ + } else do { } while (0) +#else +# define RTMPWIN_UPDATE_GIP_GLOBALS_AND_GET_PGIP() do { } while (0) +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Initialize once. */ +static RTONCE g_MpInitOnce = RTONCE_INITIALIZER; +#ifdef IPRT_WITH_GIP_MP_INFO +/** Initialize once using GIP. */ +static RTONCE g_MpInitOnceGip = RTONCE_INITIALIZER; +#endif + +static decltype(GetMaximumProcessorCount) *g_pfnGetMaximumProcessorCount; +//static decltype(GetActiveProcessorCount) *g_pfnGetActiveProcessorCount; +static decltype(GetCurrentProcessorNumber) *g_pfnGetCurrentProcessorNumber; +static decltype(GetCurrentProcessorNumberEx) *g_pfnGetCurrentProcessorNumberEx; +static decltype(GetLogicalProcessorInformation) *g_pfnGetLogicalProcessorInformation; +static decltype(GetLogicalProcessorInformationEx) *g_pfnGetLogicalProcessorInformationEx; + + +/** The required buffer size for getting group relations. */ +static uint32_t g_cbRtMpWinGrpRelBuf; +/** The max number of CPUs (RTMpGetCount). */ +static uint32_t g_cRtMpWinMaxCpus; +/** The max number of CPU cores (RTMpGetCoreCount). */ +static uint32_t g_cRtMpWinMaxCpuCores; +/** The max number of groups. */ +static uint32_t g_cRtMpWinMaxCpuGroups; +/** The number of active CPUs the last time we checked. */ +static uint32_t volatile g_cRtMpWinActiveCpus; +/** Static per group info. + * @remarks With 256 entries this takes up 33KB. + * @sa g_aRtMpNtCpuGroups */ +static struct +{ + /** The max CPUs in the group. */ + uint16_t cMaxCpus; + /** The number of active CPUs at the time of initialization. */ + uint16_t cActiveCpus; + /** CPU set indexes for each CPU in the group. */ + int16_t aidxCpuSetMembers[64]; +} g_aRtMpWinCpuGroups[256]; +/** Maps CPU set indexes to RTCPUID. + * @sa g_aidRtMpNtByCpuSetIdx */ +RTCPUID g_aidRtMpWinByCpuSetIdx[RTCPUSET_MAX_CPUS]; + + +/** + * @callback_method_impl{FNRTONCE, + * Resolves dynamic imports and initializes globals.} + */ +static DECLCALLBACK(int32_t) rtMpWinInitOnce(void *pvUser) +{ + RT_NOREF(pvUser); + + Assert(g_WinOsInfoEx.dwOSVersionInfoSize != 0); + Assert(g_hModKernel32 != NULL); + + /* + * Resolve dynamic APIs. + */ +#define RESOLVE_API(a_szMod, a_FnName) \ + do { \ + RT_CONCAT(g_pfn,a_FnName) = (decltype(a_FnName) *)GetProcAddress(g_hModKernel32, #a_FnName); \ + } while (0) + RESOLVE_API("kernel32.dll", GetMaximumProcessorCount); + //RESOLVE_API("kernel32.dll", GetActiveProcessorCount); - slow :/ + RESOLVE_API("kernel32.dll", GetCurrentProcessorNumber); + RESOLVE_API("kernel32.dll", GetCurrentProcessorNumberEx); + RESOLVE_API("kernel32.dll", GetLogicalProcessorInformation); + RESOLVE_API("kernel32.dll", GetLogicalProcessorInformationEx); + + /* + * Reset globals. + */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aidRtMpWinByCpuSetIdx); i++) + g_aidRtMpWinByCpuSetIdx[i] = NIL_RTCPUID; + for (unsigned idxGroup = 0; idxGroup < RT_ELEMENTS(g_aRtMpWinCpuGroups); idxGroup++) + { + g_aRtMpWinCpuGroups[idxGroup].cMaxCpus = 0; + g_aRtMpWinCpuGroups[idxGroup].cActiveCpus = 0; + for (unsigned idxMember = 0; idxMember < RT_ELEMENTS(g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers); idxMember++) + g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember] = -1; + } + + /* + * Query group information, partitioning CPU IDs and CPU set indexes. + * + * We ASSUME the GroupInfo index is the same as the group number. + * + * We CANNOT ASSUME that the kernel CPU indexes are assigned in any given + * way, though they usually are in group order by active processor. So, + * we do that to avoid trouble. We must use information provided thru GIP + * if we want the kernel CPU set indexes. Even there, the inactive CPUs + * wont have sensible indexes. Sigh. + * + * We try to assign IDs to inactive CPUs in the same manner as mp-r0drv-nt.cpp + * + * Note! We will die (AssertFatal) if there are too many processors! + */ + union + { + SYSTEM_INFO SysInfo; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX Info; + uint8_t abPaddingG[ sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) + + sizeof(PROCESSOR_GROUP_INFO) * 256]; + uint8_t abPaddingC[ sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) + + (sizeof(PROCESSOR_RELATIONSHIP) + sizeof(GROUP_AFFINITY)) + * RTCPUSET_MAX_CPUS]; + } uBuf; + if (g_pfnGetLogicalProcessorInformationEx) + { + /* Query the information. */ + DWORD cbData = sizeof(uBuf); + AssertFatalMsg(g_pfnGetLogicalProcessorInformationEx(RelationGroup, &uBuf.Info, &cbData) != FALSE, + ("last error = %u, cbData = %u (in %u)\n", GetLastError(), cbData, sizeof(uBuf))); + AssertFatalMsg(uBuf.Info.Relationship == RelationGroup, + ("Relationship = %u, expected %u!\n", uBuf.Info.Relationship, RelationGroup)); + AssertFatalMsg(uBuf.Info.Group.MaximumGroupCount <= RT_ELEMENTS(g_aRtMpWinCpuGroups), + ("MaximumGroupCount is %u, we only support up to %u!\n", + uBuf.Info.Group.MaximumGroupCount, RT_ELEMENTS(g_aRtMpWinCpuGroups))); + + AssertMsg(uBuf.Info.Group.MaximumGroupCount == uBuf.Info.Group.ActiveGroupCount, /* 2nd assumption mentioned above. */ + ("%u vs %u\n", uBuf.Info.Group.MaximumGroupCount, uBuf.Info.Group.ActiveGroupCount)); + AssertFatal(uBuf.Info.Group.MaximumGroupCount >= uBuf.Info.Group.ActiveGroupCount); + + g_cRtMpWinMaxCpuGroups = uBuf.Info.Group.MaximumGroupCount; + + /* Count max cpus (see mp-r0drv0-nt.cpp) why we don't use GetMaximumProcessorCount(ALL). */ + uint32_t idxGroup; + g_cRtMpWinMaxCpus = 0; + for (idxGroup = 0; idxGroup < uBuf.Info.Group.ActiveGroupCount; idxGroup++) + g_cRtMpWinMaxCpus += uBuf.Info.Group.GroupInfo[idxGroup].MaximumProcessorCount; + + /* Process the active groups. */ + uint32_t cActive = 0; + uint32_t cInactive = 0; + uint32_t idxCpu = 0; + uint32_t idxCpuSetNextInactive = g_cRtMpWinMaxCpus - 1; + for (idxGroup = 0; idxGroup < uBuf.Info.Group.ActiveGroupCount; idxGroup++) + { + PROCESSOR_GROUP_INFO const *pGroupInfo = &uBuf.Info.Group.GroupInfo[idxGroup]; + g_aRtMpWinCpuGroups[idxGroup].cMaxCpus = pGroupInfo->MaximumProcessorCount; + g_aRtMpWinCpuGroups[idxGroup].cActiveCpus = pGroupInfo->ActiveProcessorCount; + for (uint32_t idxMember = 0; idxMember < pGroupInfo->MaximumProcessorCount; idxMember++) + { + if (pGroupInfo->ActiveProcessorMask & RT_BIT_64(idxMember)) + { + g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember] = idxCpu; + g_aidRtMpWinByCpuSetIdx[idxCpu] = idxCpu; + idxCpu++; + cActive++; + } + else + { + if (idxCpuSetNextInactive >= idxCpu) + { + g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember] = idxCpuSetNextInactive; + g_aidRtMpWinByCpuSetIdx[idxCpuSetNextInactive] = idxCpuSetNextInactive; + idxCpuSetNextInactive--; + } + cInactive++; + } + } + } + g_cRtMpWinActiveCpus = cActive; + Assert(cActive + cInactive <= g_cRtMpWinMaxCpus); + Assert(idxCpu <= idxCpuSetNextInactive + 1); + Assert(idxCpu <= g_cRtMpWinMaxCpus); + + /* Just in case the 2nd assumption doesn't hold true and there are inactive groups. */ + for (; idxGroup < uBuf.Info.Group.MaximumGroupCount; idxGroup++) + { + DWORD cMaxMembers = g_pfnGetMaximumProcessorCount(idxGroup); + g_aRtMpWinCpuGroups[idxGroup].cMaxCpus = cMaxMembers; + g_aRtMpWinCpuGroups[idxGroup].cActiveCpus = 0; + for (uint32_t idxMember = 0; idxMember < cMaxMembers; idxMember++) + { + if (idxCpuSetNextInactive >= idxCpu) + { + g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember] = idxCpuSetNextInactive; + g_aidRtMpWinByCpuSetIdx[idxCpuSetNextInactive] = idxCpuSetNextInactive; + idxCpuSetNextInactive--; + } + cInactive++; + } + } + Assert(cActive + cInactive <= g_cRtMpWinMaxCpus); + Assert(idxCpu <= idxCpuSetNextInactive + 1); + } + else + { + /* Legacy: */ + GetSystemInfo(&uBuf.SysInfo); + g_cRtMpWinMaxCpuGroups = 1; + g_cRtMpWinMaxCpus = uBuf.SysInfo.dwNumberOfProcessors; + g_aRtMpWinCpuGroups[0].cMaxCpus = uBuf.SysInfo.dwNumberOfProcessors; + g_aRtMpWinCpuGroups[0].cActiveCpus = uBuf.SysInfo.dwNumberOfProcessors; + + for (uint32_t idxMember = 0; idxMember < uBuf.SysInfo.dwNumberOfProcessors; idxMember++) + { + g_aRtMpWinCpuGroups[0].aidxCpuSetMembers[idxMember] = idxMember; + g_aidRtMpWinByCpuSetIdx[idxMember] = idxMember; + } + } + + AssertFatalMsg(g_cRtMpWinMaxCpus <= RTCPUSET_MAX_CPUS, + ("g_cRtMpWinMaxCpus=%u (%#x); RTCPUSET_MAX_CPUS=%u\n", g_cRtMpWinMaxCpus, g_cRtMpWinMaxCpus, RTCPUSET_MAX_CPUS)); + + g_cbRtMpWinGrpRelBuf = sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) + + (g_cRtMpWinMaxCpuGroups + 2) * sizeof(PROCESSOR_GROUP_INFO); + + /* + * Get information about cores. + * + * Note! This will only give us info about active processors according to + * MSDN, we'll just have to hope that CPUs aren't hotplugged after we + * initialize here (or that the API consumers doesn't care too much). + */ + /** @todo A hot CPU plug event would be nice. */ + g_cRtMpWinMaxCpuCores = g_cRtMpWinMaxCpus; + if (g_pfnGetLogicalProcessorInformationEx) + { + /* Query the information. */ + DWORD cbData = sizeof(uBuf); + AssertFatalMsg(g_pfnGetLogicalProcessorInformationEx(RelationProcessorCore, &uBuf.Info, &cbData) != FALSE, + ("last error = %u, cbData = %u (in %u)\n", GetLastError(), cbData, sizeof(uBuf))); + g_cRtMpWinMaxCpuCores = 0; + for (uint32_t off = 0; off < cbData; ) + { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *pCur = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)&uBuf.abPaddingG[off]; + AssertFatalMsg(pCur->Relationship == RelationProcessorCore, + ("off = %#x, Relationship = %u, expected %u!\n", off, pCur->Relationship, RelationProcessorCore)); + g_cRtMpWinMaxCpuCores++; + off += pCur->Size; + } + +#if ARCH_BITS == 32 + if (g_cRtMpWinMaxCpuCores > g_cRtMpWinMaxCpus) + { + /** @todo WOW64 trouble where the emulation environment has folded the high + * processor masks (63..32) into the low (31..0), hiding some + * processors from us. Currently we don't deal with that. */ + g_cRtMpWinMaxCpuCores = g_cRtMpWinMaxCpus; + } + else + AssertStmt(g_cRtMpWinMaxCpuCores > 0, g_cRtMpWinMaxCpuCores = g_cRtMpWinMaxCpus); +#else + AssertStmt(g_cRtMpWinMaxCpuCores > 0 && g_cRtMpWinMaxCpuCores <= g_cRtMpWinMaxCpus, + g_cRtMpWinMaxCpuCores = g_cRtMpWinMaxCpus); +#endif + } + else + { + /* + * Sadly, on XP and Server 2003, even if the API is present, it does not tell us + * how many physical cores there are (any package will look like a single core). + * That is worse than not using the API at all, so just skip it unless it's Vista+. + */ + if ( g_pfnGetLogicalProcessorInformation + && g_WinOsInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT + && g_WinOsInfoEx.dwMajorVersion >= 6) + { + /* Query the info. */ + DWORD cbSysProcInfo = _4K; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION paSysInfo = NULL; + BOOL fRc = FALSE; + do + { + cbSysProcInfo = RT_ALIGN_32(cbSysProcInfo, 256); + void *pv = RTMemRealloc(paSysInfo, cbSysProcInfo); + if (!pv) + break; + paSysInfo = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)pv; + fRc = g_pfnGetLogicalProcessorInformation(paSysInfo, &cbSysProcInfo); + } while (!fRc && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + if (fRc) + { + /* Count the cores in the result. */ + g_cRtMpWinMaxCpuCores = 0; + uint32_t i = cbSysProcInfo / sizeof(paSysInfo[0]); + while (i-- > 0) + if (paSysInfo[i].Relationship == RelationProcessorCore) + g_cRtMpWinMaxCpuCores++; + + AssertStmt(g_cRtMpWinMaxCpuCores > 0 && g_cRtMpWinMaxCpuCores <= g_cRtMpWinMaxCpus, + g_cRtMpWinMaxCpuCores = g_cRtMpWinMaxCpus); + } + RTMemFree(paSysInfo); + } + } + + return VINF_SUCCESS; +} + + +#ifdef IPRT_WITH_GIP_MP_INFO +/** + * @callback_method_impl{FNRTONCE, Updates globals with information from GIP.} + */ +static DECLCALLBACK(int32_t) rtMpWinInitOnceGip(void *pvUser) +{ + RT_NOREF(pvUser); + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + + PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage; + if ( pGip + && pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC) + { + /* + * Update globals. + */ + if (g_cRtMpWinMaxCpus != pGip->cPossibleCpus) + g_cRtMpWinMaxCpus = pGip->cPossibleCpus; + if (g_cRtMpWinActiveCpus != pGip->cOnlineCpus) + g_cRtMpWinActiveCpus = pGip->cOnlineCpus; + Assert(g_cRtMpWinMaxCpuGroups == pGip->cPossibleCpuGroups); + if (g_cRtMpWinMaxCpuGroups != pGip->cPossibleCpuGroups) + { + g_cRtMpWinMaxCpuGroups = pGip->cPossibleCpuGroups; + g_cbRtMpWinGrpRelBuf = sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) + + (g_cRtMpWinMaxCpuGroups + 2) * sizeof(PROCESSOR_GROUP_INFO); + } + + /* + * Update CPU set IDs. + */ + for (unsigned i = g_cRtMpWinMaxCpus; i < RT_ELEMENTS(g_aidRtMpWinByCpuSetIdx); i++) + g_aidRtMpWinByCpuSetIdx[i] = NIL_RTCPUID; + + unsigned const cbGip = pGip->cPages * PAGE_SIZE; + for (uint32_t idxGroup = 0; idxGroup < g_cRtMpWinMaxCpuGroups; idxGroup++) + { + uint32_t idxMember; + uint32_t offCpuGroup = pGip->aoffCpuGroup[idxGroup]; + if (offCpuGroup < cbGip) + { + PSUPGIPCPUGROUP pGipCpuGrp = (PSUPGIPCPUGROUP)((uintptr_t)pGip + offCpuGroup); + uint32_t cMaxMembers = pGipCpuGrp->cMaxMembers; + AssertStmt(cMaxMembers <= RT_ELEMENTS(g_aRtMpWinCpuGroups[0].aidxCpuSetMembers), + cMaxMembers = RT_ELEMENTS(g_aRtMpWinCpuGroups[0].aidxCpuSetMembers)); + g_aRtMpWinCpuGroups[idxGroup].cMaxCpus = cMaxMembers; + g_aRtMpWinCpuGroups[idxGroup].cActiveCpus = RT_MIN(pGipCpuGrp->cMembers, cMaxMembers); + + for (idxMember = 0; idxMember < cMaxMembers; idxMember++) + { + int16_t idxSet = pGipCpuGrp->aiCpuSetIdxs[idxMember]; + g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember] = idxSet; + if ((unsigned)idxSet < RT_ELEMENTS(g_aidRtMpWinByCpuSetIdx)) +# ifdef IPRT_WITH_RTCPUID_AS_GROUP_AND_NUMBER + g_aidRtMpWinByCpuSetIdx[idxSet] = RTMPCPUID_FROM_GROUP_AND_NUMBER(idxGroup, idxMember); +# else + g_aidRtMpWinByCpuSetIdx[idxSet] = idxSet; +# endif + } + } + else + idxMember = 0; + for (; idxMember < RT_ELEMENTS(g_aRtMpWinCpuGroups[0].aidxCpuSetMembers); idxMember++) + g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember] = -1; + } + } + + return VINF_SUCCESS; +} + + +/** + * Refreshes globals from GIP after one or more CPUs were added. + * + * There are potential races here. We might race other threads and we may race + * more CPUs being added. + */ +static void rtMpWinRefreshGip(void) +{ + PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage; + if ( pGip + && pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC) + { + /* + * Since CPUs cannot be removed, we only have to update the IDs and + * indexes of CPUs that we think are inactive and the group member counts. + */ + for (;;) + { + unsigned const cbGip = pGip->cPages * PAGE_SIZE; + uint32_t const cGipActiveCpus = pGip->cOnlineCpus; + uint32_t const cMyActiveCpus = ASMAtomicReadU32(&g_cRtMpWinActiveCpus); + ASMCompilerBarrier(); + + for (uint32_t idxGroup = 0; idxGroup < g_cRtMpWinMaxCpuGroups; idxGroup++) + { + uint32_t offCpuGroup = pGip->aoffCpuGroup[idxGroup]; + if (offCpuGroup < cbGip) + { + PSUPGIPCPUGROUP pGipCpuGrp = (PSUPGIPCPUGROUP)((uintptr_t)pGip + offCpuGroup); + uint32_t cMaxMembers = pGipCpuGrp->cMaxMembers; + AssertStmt(cMaxMembers <= RT_ELEMENTS(g_aRtMpWinCpuGroups[0].aidxCpuSetMembers), + cMaxMembers = RT_ELEMENTS(g_aRtMpWinCpuGroups[0].aidxCpuSetMembers)); + for (uint32_t idxMember = g_aRtMpWinCpuGroups[idxGroup].cActiveCpus; idxMember < cMaxMembers; idxMember++) + { + int16_t idxSet = pGipCpuGrp->aiCpuSetIdxs[idxMember]; + g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember] = idxSet; + if ((unsigned)idxSet < RT_ELEMENTS(g_aidRtMpWinByCpuSetIdx)) +# ifdef IPRT_WITH_RTCPUID_AS_GROUP_AND_NUMBER + g_aidRtMpWinByCpuSetIdx[idxSet] = RTMPCPUID_FROM_GROUP_AND_NUMBER(idxGroup, idxMember); +# else + g_aidRtMpWinByCpuSetIdx[idxSet] = idxSet; +# endif + } + g_aRtMpWinCpuGroups[idxGroup].cMaxCpus = RT_MIN(pGipCpuGrp->cMembers, cMaxMembers); + g_aRtMpWinCpuGroups[idxGroup].cActiveCpus = RT_MIN(pGipCpuGrp->cMembers, cMaxMembers); + } + else + Assert(g_aRtMpWinCpuGroups[idxGroup].cActiveCpus == 0); + } + + ASMCompilerBarrier(); + if (cGipActiveCpus == pGip->cOnlineCpus) + if (ASMAtomicCmpXchgU32(&g_cRtMpWinActiveCpus, cGipActiveCpus, cMyActiveCpus)) + break; + } + } +} + +#endif /* IPRT_WITH_GIP_MP_INFO */ + + +/* + * Conversion between CPU ID and set index. + */ + +RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + +#ifdef IPRT_WITH_RTCPUID_AS_GROUP_AND_NUMBER + if (idCpu != NIL_RTCPUID) + return RTMpSetIndexFromCpuGroupMember(rtMpCpuIdGetGroup(idCpu), rtMpCpuIdGetGroupMember(idCpu)); + return -1; + +#else + /* 1:1 mapping, just do range checking. */ + return idCpu < g_cRtMpWinMaxCpus ? idCpu : -1; +#endif +} + + +RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + if ((unsigned)iCpu < RT_ELEMENTS(g_aidRtMpWinByCpuSetIdx)) + { + RTCPUID idCpu = g_aidRtMpWinByCpuSetIdx[iCpu]; + +#if defined(IPRT_WITH_RTCPUID_AS_GROUP_AND_NUMBER) && defined(RT_STRICT) + /* Check the correctness of the mapping table. */ + RTCPUID idCpuGip = NIL_RTCPUID; + if ( pGip + && (unsigned)iCpu < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx)) + { + unsigned idxSupCpu = pGip->aiCpuFromCpuSetIdx[idxGuess]; + if (idxSupCpu < pGip->cCpus) + if (pGip->aCPUs[idxSupCpu].enmState != SUPGIPCPUSTATE_INVALID) + idCpuGip = pGip->aCPUs[idxSupCpu].idCpu; + } + AssertMsg(idCpu == idCpuGip, ("table:%#x gip:%#x\n", idCpu, idCpuGip)); +#endif + + return idCpu; + } + return NIL_RTCPUID; +} + + +RTDECL(int) RTMpSetIndexFromCpuGroupMember(uint32_t idxGroup, uint32_t idxMember) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + if (idxGroup < g_cRtMpWinMaxCpuGroups) + if (idxMember < g_aRtMpWinCpuGroups[idxGroup].cMaxCpus) + return g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember]; + return -1; +} + + +RTDECL(uint32_t) RTMpGetCpuGroupCounts(uint32_t idxGroup, uint32_t *pcActive) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + if (idxGroup < g_cRtMpWinMaxCpuGroups) + { + if (pcActive) + *pcActive = g_aRtMpWinCpuGroups[idxGroup].cActiveCpus; + return g_aRtMpWinCpuGroups[idxGroup].cMaxCpus; + } + if (pcActive) + *pcActive = 0; + return 0; +} + + +RTDECL(uint32_t) RTMpGetMaxCpuGroupCount(void) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + return g_cRtMpWinMaxCpuGroups; +} + + + +/* + * Get current CPU. + */ + +RTDECL(RTCPUID) RTMpCpuId(void) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + PROCESSOR_NUMBER ProcNum; + ProcNum.Group = 0; + ProcNum.Number = 0xff; + if (g_pfnGetCurrentProcessorNumberEx) + g_pfnGetCurrentProcessorNumberEx(&ProcNum); + else if (g_pfnGetCurrentProcessorNumber) + { + DWORD iCpu = g_pfnGetCurrentProcessorNumber(); + Assert(iCpu < g_cRtMpWinMaxCpus); + ProcNum.Number = iCpu; + } + else + { +#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) + ProcNum.Number = ASMGetApicId(); +#else +# error "Not ported to this architecture." + return NIL_RTCPUID; +#endif + } + +#ifdef IPRT_WITH_RTCPUID_AS_GROUP_AND_NUMBER + return RTMPCPUID_FROM_GROUP_AND_NUMBER(ProcNum.Group, ProcNum.Number); +#else + return RTMpSetIndexFromCpuGroupMember(ProcNum.Group, ProcNum.Number); +#endif +} + + +/* + * Possible CPUs and cores. + */ + +RTDECL(RTCPUID) RTMpGetMaxCpuId(void) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + +#ifdef IPRT_WITH_RTCPUID_AS_GROUP_AND_NUMBER + return RTMPCPUID_FROM_GROUP_AND_NUMBER(g_cRtMpWinMaxCpuGroups - 1, + g_aRtMpWinCpuGroups[g_cRtMpWinMaxCpuGroups - 1].cMaxCpus - 1); +#else + return g_cRtMpWinMaxCpus - 1; +#endif +} + + +RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + /* Any CPU between 0 and g_cRtMpWinMaxCpus are possible. */ + return idCpu < g_cRtMpWinMaxCpus; +} + + +RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet) +{ + RTCPUID iCpu = RTMpGetCount(); + RTCpuSetEmpty(pSet); + while (iCpu-- > 0) + RTCpuSetAddByIndex(pSet, iCpu); + return pSet; +} + + +RTDECL(RTCPUID) RTMpGetCount(void) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + return g_cRtMpWinMaxCpus; +} + + +RTDECL(RTCPUID) RTMpGetCoreCount(void) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + RTMPWIN_UPDATE_GIP_GLOBALS(); + + return g_cRtMpWinMaxCpuCores; +} + + +/* + * Online CPUs and cores. + */ + +RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet) +{ + RTOnce(&g_MpInitOnce, rtMpWinInitOnce, NULL); + +#ifdef IPRT_WITH_GIP_MP_INFO + RTMPWIN_UPDATE_GIP_GLOBALS_AND_GET_PGIP(); + if (pGip) + { + *pSet = pGip->OnlineCpuSet; + return pSet; + } +#endif + + if (g_pfnGetLogicalProcessorInformationEx) + { + /* + * Get the group relation info. + * + * In addition to the ASSUMPTIONS that are documented in rtMpWinInitOnce, + * we ASSUME that PROCESSOR_GROUP_INFO::MaximumProcessorCount gives the + * active processor mask width. + */ + /** @todo this is not correct for WOW64 */ + DWORD cbInfo = g_cbRtMpWinGrpRelBuf; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *pInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)alloca(cbInfo); + AssertFatalMsg(g_pfnGetLogicalProcessorInformationEx(RelationGroup, pInfo, &cbInfo) != FALSE, + ("last error = %u, cbInfo = %u (in %u)\n", GetLastError(), cbInfo, g_cbRtMpWinGrpRelBuf)); + AssertFatalMsg(pInfo->Relationship == RelationGroup, + ("Relationship = %u, expected %u!\n", pInfo->Relationship, RelationGroup)); + AssertFatalMsg(pInfo->Group.MaximumGroupCount == g_cRtMpWinMaxCpuGroups, + ("MaximumGroupCount is %u, expected %u!\n", pInfo->Group.MaximumGroupCount, g_cRtMpWinMaxCpuGroups)); + + RTCpuSetEmpty(pSet); + for (uint32_t idxGroup = 0; idxGroup < pInfo->Group.MaximumGroupCount; idxGroup++) + { + Assert(pInfo->Group.GroupInfo[idxGroup].MaximumProcessorCount == g_aRtMpWinCpuGroups[idxGroup].cMaxCpus); + Assert(pInfo->Group.GroupInfo[idxGroup].ActiveProcessorCount <= g_aRtMpWinCpuGroups[idxGroup].cMaxCpus); + + KAFFINITY fActive = pInfo->Group.GroupInfo[idxGroup].ActiveProcessorMask; + if (fActive != 0) + { +#ifdef RT_STRICT + uint32_t cMembersLeft = pInfo->Group.GroupInfo[idxGroup].ActiveProcessorCount; +#endif + int const cMembers = g_aRtMpWinCpuGroups[idxGroup].cMaxCpus; + for (int idxMember = 0; idxMember < cMembers; idxMember++) + { + if (fActive & 1) + { +#ifdef RT_STRICT + cMembersLeft--; +#endif + RTCpuSetAddByIndex(pSet, g_aRtMpWinCpuGroups[idxGroup].aidxCpuSetMembers[idxMember]); + fActive >>= 1; + if (!fActive) + break; + } + else + { + fActive >>= 1; + } + } + Assert(cMembersLeft == 0); + } + else + Assert(pInfo->Group.GroupInfo[idxGroup].ActiveProcessorCount == 0); + } + + return pSet; + } + + /* + * Legacy fallback code. + */ + SYSTEM_INFO SysInfo; + GetSystemInfo(&SysInfo); + return RTCpuSetFromU64(pSet, SysInfo.dwActiveProcessorMask); +} + + +RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu) +{ + RTCPUSET Set; + return RTCpuSetIsMember(RTMpGetOnlineSet(&Set), idCpu); +} + + +RTDECL(RTCPUID) RTMpGetOnlineCount(void) +{ +#ifdef IPRT_WITH_GIP_MP_INFO + RTMPWIN_UPDATE_GIP_GLOBALS_AND_GET_PGIP(); + if (pGip) + return pGip->cOnlineCpus; +#endif + + RTCPUSET Set; + RTMpGetOnlineSet(&Set); + return RTCpuSetCount(&Set); +} + + +RTDECL(RTCPUID) RTMpGetOnlineCoreCount(void) +{ + /** @todo this isn't entirely correct, but whatever. */ + return RTMpGetCoreCount(); +} + diff --git a/src/VBox/Runtime/r3/win/nocrt-RTLogWriteStdErr-win.cpp b/src/VBox/Runtime/r3/win/nocrt-RTLogWriteStdErr-win.cpp new file mode 100644 index 00000000..5f74b4f5 --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-RTLogWriteStdErr-win.cpp @@ -0,0 +1,57 @@ +/* $Id: nocrt-RTLogWriteStdErr-win.cpp $ */ +/** @file + * IPRT - Log To StdErr, Windows no-CRT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/log.h> +#include "internal/iprt.h" + +#include <iprt/win/windows.h> + + +RTDECL(void) RTLogWriteStdErr(const char *pch, size_t cb) +{ + HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE); + if (hStdErr != NULL && hStdErr != INVALID_HANDLE_VALUE) + { + DWORD cbIgn; /* NT3.1 requires the return size parameter. */ + WriteFile(hStdErr, pch, (DWORD)cb, &cbIgn, NULL); /** @todo do we need to translate \\n to \\r\\n? */ + } +} +RT_EXPORT_SYMBOL(RTLogWriteStdErr); + diff --git a/src/VBox/Runtime/r3/win/nocrt-RTLogWriteStdOut-win.cpp b/src/VBox/Runtime/r3/win/nocrt-RTLogWriteStdOut-win.cpp new file mode 100644 index 00000000..56e29134 --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-RTLogWriteStdOut-win.cpp @@ -0,0 +1,58 @@ +/* $Id: nocrt-RTLogWriteStdOut-win.cpp $ */ +/** @file + * IPRT - Log To StdOut, Windows no-CRT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/log.h> +#include "internal/iprt.h" + +#include <iprt/win/windows.h> + + +RTDECL(void) RTLogWriteStdOut(const char *pch, size_t cb) +{ + /** @todo should flush the stdout stream first... */ + HANDLE hStdErr = GetStdHandle(STD_OUTPUT_HANDLE); + if (hStdErr != NULL && hStdErr != INVALID_HANDLE_VALUE) + { + DWORD cbIgn; /* NT3.1 requires the return size parameter. */ + WriteFile(hStdErr, pch, (DWORD)cb, &cbIgn, NULL); /** @todo do we need to translate \\n to \\r\\n? */ + } +} +RT_EXPORT_SYMBOL(RTLogWriteStdOut); + diff --git a/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm b/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm new file mode 100644 index 00000000..1943ad69 --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm @@ -0,0 +1,44 @@ +; $Id: nocrt-WinMainCRTStartup-win.asm $ +;; @file +; IPRT - Alias WinMainCRTStartup to CustomMainEntrypoint in nocrt-startup-exe-win.cpp. +; + +; +; Copyright (C) 2022-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" + +extern NAME(CustomMainEntrypoint) +BEGINPROC WinMainCRTStartup + jmp NAME(CustomMainEntrypoint) +ENDPROC WinMainCRTStartup + diff --git a/src/VBox/Runtime/r3/win/nocrt-alloc-win.cpp b/src/VBox/Runtime/r3/win/nocrt-alloc-win.cpp new file mode 100644 index 00000000..128b58df --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-alloc-win.cpp @@ -0,0 +1,115 @@ +/* $Id: nocrt-alloc-win.cpp $ */ +/** @file + * IPRT - No-CRT - Basic allocators, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/mem.h> + +#include <iprt/nt/nt-and-windows.h> + + +#undef RTMemTmpFree +RTDECL(void) RTMemTmpFree(void *pv) +{ + HeapFree(GetProcessHeap(), 0, pv); +} + + +#undef RTMemFree +RTDECL(void) RTMemFree(void *pv) +{ + HeapFree(GetProcessHeap(), 0, pv); +} + + +#undef RTMemTmpAllocTag +RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) +{ + RT_NOREF(pszTag); + return HeapAlloc(GetProcessHeap(), 0, cb); +} + + +#undef RTMemTmpAllocZTag +RTDECL(void *) RTMemTmpAllocZTag(size_t cb, const char *pszTag) +{ + RT_NOREF(pszTag); + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb); +} + + +#undef RTMemAllocTag +RTDECL(void *) RTMemAllocTag(size_t cb, const char *pszTag) +{ + RT_NOREF(pszTag); + return HeapAlloc(GetProcessHeap(), 0, cb); +} + + +#undef RTMemAllocZTag +RTDECL(void *) RTMemAllocZTag(size_t cb, const char *pszTag) +{ + RT_NOREF(pszTag); + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb); +} + + +#undef RTMemAllocVarTag +RTDECL(void *) RTMemAllocVarTag(size_t cbUnaligned, const char *pszTag) +{ + return RTMemAllocTag(cbUnaligned, pszTag); +} + + +#undef RTMemAllocZVarTag +RTDECL(void *) RTMemAllocZVarTag(size_t cbUnaligned, const char *pszTag) +{ + return RTMemAllocZTag(cbUnaligned, pszTag); +} + + +#undef RTMemReallocTag +RTDECL(void *) RTMemReallocTag(void *pvOld, size_t cbNew, const char *pszTag) +{ + RT_NOREF(pszTag); + if (pvOld) + return HeapReAlloc(GetProcessHeap(), 0, pvOld, cbNew); + return HeapAlloc(GetProcessHeap(), 0, cbNew); +} + diff --git a/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm b/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm new file mode 100644 index 00000000..881463ec --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm @@ -0,0 +1,44 @@ +; $Id: nocrt-atexit-win.asm $ +;; @file +; IPRT - Alias atexit to rtnocrt_atexit in nocrt-startup-exe-win.cpp. +; + +; +; Copyright (C) 2022-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" + +extern NAME(nocrt_atexit) +BEGINPROC atexit + jmp NAME(nocrt_atexit) +ENDPROC atexit + diff --git a/src/VBox/Runtime/r3/win/nocrt-fatal-write-win.cpp b/src/VBox/Runtime/r3/win/nocrt-fatal-write-win.cpp new file mode 100644 index 00000000..c9bfa1b4 --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-fatal-write-win.cpp @@ -0,0 +1,150 @@ +/* $Id: nocrt-fatal-write-win.cpp $ */ +/** @file + * IPRT - No-CRT - Fatal Message Writing Primitives, Windows. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/nocrt.h" + +#include <iprt/win/windows.h> +#include <iprt/string.h> + + +/** @todo invent some kind of weak linking with the debug and release + * loggers, e.g. theset some innocent function we can call to do + * logging and we'll call it if it isn't NULL. */ + +void rtNoCrtFatalWrite(const char *pchMsg, size_t cchMsg) +{ + DWORD cbIgn; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), pchMsg, (DWORD)cchMsg, &cbIgn, NULL); +} + + +void rtNoCrtFatalWriteBegin(const char *pchMsg, size_t cchMsg) +{ + rtNoCrtFatalWrite(pchMsg, cchMsg); +} + + +void rtNoCrtFatalWriteEnd(const char *pchMsg, size_t cchMsg) +{ + rtNoCrtFatalWrite(pchMsg, cchMsg); +} + + +void rtNoCrtFatalWritePtr(void const *pv) +{ + char szValue[128]; +#if ARCH_BITS == 64 + ssize_t cchValue = RTStrFormatU64(szValue, sizeof(szValue), (uintptr_t)pv, 16, 16, 16, RTSTR_F_WIDTH | RTSTR_F_64BIT); +#elif ARCH_BITS == 32 + ssize_t cchValue = RTStrFormatU32(szValue, sizeof(szValue), (uintptr_t)pv, 16, 8, 8, RTSTR_F_WIDTH | RTSTR_F_32BIT); +#else +# error ARCH_BITS +#endif + rtNoCrtFatalWrite(szValue, (size_t)cchValue); +} + + +void rtNoCrtFatalWriteX64(uint64_t uValue) +{ + char szValue[128]; + ssize_t cchValue = RTStrFormatU64(szValue, sizeof(szValue), uValue, 16, 0, 0, RTSTR_F_SPECIAL | RTSTR_F_64BIT); + rtNoCrtFatalWrite(szValue, (size_t)cchValue); +} + + +void rtNoCrtFatalWriteX32(uint32_t uValue) +{ + char szValue[128]; + ssize_t cchValue = RTStrFormatU32(szValue, sizeof(szValue), uValue, 16, 0, 0, RTSTR_F_SPECIAL | RTSTR_F_32BIT); + rtNoCrtFatalWrite(szValue, (size_t)cchValue); +} + + +void rtNoCrtFatalWriteRc(int rc) +{ + char szValue[128]; + ssize_t cchValue = RTStrFormatU32(szValue, sizeof(szValue), rc, 10, 0, 0, RTSTR_F_32BIT | RTSTR_F_VALSIGNED); + rtNoCrtFatalWrite(szValue, (size_t)cchValue); +} + +void rtNoCrtFatalWriteWinRc(uint32_t rc) +{ + char szValue[168]; + ssize_t cchValue = RTStrFormatU32(szValue, sizeof(szValue), rc, 10, 0, 0, RTSTR_F_32BIT); + szValue[cchValue++] = ' '; + szValue[cchValue++] = '('; + cchValue += RTStrFormatU32(&szValue[cchValue], sizeof(szValue) - cchValue, rc, 16, 0, 0, RTSTR_F_32BIT | RTSTR_F_SPECIAL); + szValue[cchValue++] = ')'; + rtNoCrtFatalWrite(szValue, (size_t)cchValue); +} + + +void rtNoCrtFatalWriteStr(const char *pszString) +{ + if (RT_VALID_PTR(pszString)) + { + size_t cch = 0; + while (pszString[cch] != '\0') + cch++; + rtNoCrtFatalWrite(pszString, cch); + } + else + { + rtNoCrtFatalWrite(RT_STR_TUPLE("<pszString=")); + rtNoCrtFatalWritePtr(pszString); + rtNoCrtFatalWrite(RT_STR_TUPLE(">")); + } +} + + +void rtNoCrtFatalMsg(const char *pchMsg, size_t cchMsg) +{ + rtNoCrtFatalWriteBegin(pchMsg, cchMsg); + rtNoCrtFatalWriteEnd(RT_STR_TUPLE("")); +} + + +void rtNoCrtFatalMsgWithRc(const char *pchMsg, size_t cchMsg, int rc) +{ + rtNoCrtFatalWriteBegin(pchMsg, cchMsg); + rtNoCrtFatalWriteRc(rc); + rtNoCrtFatalWriteEnd(RT_STR_TUPLE("\r\n")); +} + diff --git a/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm b/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm new file mode 100644 index 00000000..54651dd1 --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm @@ -0,0 +1,44 @@ +; $Id: nocrt-mainCRTStartup-win.asm $ +;; @file +; IPRT - Alias mainCRTStartup to CustomMainEntrypoint in nocrt-startup-exe-win.cpp. +; + +; +; Copyright (C) 2022-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" + +extern NAME(CustomMainEntrypoint) +BEGINPROC mainCRTStartup + jmp NAME(CustomMainEntrypoint) +ENDPROC mainCRTStartup + diff --git a/src/VBox/Runtime/r3/win/nocrt-startup-common-win.cpp b/src/VBox/Runtime/r3/win/nocrt-startup-common-win.cpp new file mode 100644 index 00000000..598339db --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-startup-common-win.cpp @@ -0,0 +1,211 @@ +/* $Id: nocrt-startup-common-win.cpp $ */ +/** @file + * IPRT - No-CRT - Common Windows startup code. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/nocrt.h" +#include "internal/process.h" + +#include <iprt/nt/nt-and-windows.h> +#ifndef IPRT_NOCRT_WITHOUT_FATAL_WRITE +# include <iprt/assert.h> +#endif +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "internal/compiler-vcc.h" +#include "internal/process.h" + + +#ifdef RT_ARCH_X86 +/** + * NT 3.1 does not know about the IMAGE_SECTION_HEADER::Misc.VirtualSize field + * and will therefore not handle merging initialized and uninitialized data into + * the same section. + * + * We work around this by manually zeroing the uninitialized data before any + * other code has been executed. + */ +void rtVccWinInitBssOnNt3(void *pvImageBase) +{ + /* We are called really early on, so we must figure out the NT version + on our own. It doesn't have to be all that accurate, though, as only + NT 3.10 is affected (3.50 isn't). */ + DWORD const dwRawVer = GetVersion(); + DWORD const uMajorVer = RT_BYTE1(dwRawVer); + DWORD const uMinorVer = RT_BYTE2(dwRawVer); + if (uMajorVer != 3 || uMinorVer >= 50) + return; + + /* + * Locate the NT headers. + */ + PIMAGE_NT_HEADERS pNtHdrs; + PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)pvImageBase; + if (pDosHdr->e_magic == IMAGE_DOS_SIGNATURE) + pNtHdrs = (PIMAGE_NT_HEADERS)((uintptr_t)pvImageBase + pDosHdr->e_lfanew); + else + pNtHdrs = (PIMAGE_NT_HEADERS)pvImageBase; + if (pNtHdrs->Signature == IMAGE_NT_SIGNATURE) + { + /* + * Locate the section table and walk thru it, memsetting anything that + * wasn't loaded from the file. + */ + PIMAGE_SECTION_HEADER paSHdrs = (PIMAGE_SECTION_HEADER)( (uintptr_t)&pNtHdrs->OptionalHeader + + pNtHdrs->FileHeader.SizeOfOptionalHeader); + uint32_t const cSections = pNtHdrs->FileHeader.NumberOfSections; + for (uint32_t i = 0; i < cSections; i++) + { + if (paSHdrs[i].Misc.VirtualSize > paSHdrs[i].SizeOfRawData) + { + /* ASSUMES VirtualAddress is still an RVAs */ + uint8_t *pbToZero = (uint8_t *)pvImageBase + paSHdrs[i].VirtualAddress + paSHdrs[i].SizeOfRawData; + uint32_t const cbToZero = paSHdrs[i].Misc.VirtualSize - paSHdrs[i].SizeOfRawData; + +# if 0 /* very very crude debugging */ + const char *pszHex = "0123456789abcdef"; + char szMsg[128]; + char *psz = szMsg; + *psz++ = 'd'; *psz++ = 'b'; *psz++ = 'g'; *psz++ = ':'; *psz++ = ' '; + for (uint32_t q = 0, u = i; q < 8; q++, u >>= 4) + psz[7 - q] = pszHex[u & 0xf]; + psz += 8; + *psz++ = ':'; + *psz++ = ' '; + for (uint32_t q = 0, u = (uint32_t)pbToZero; q < 8; q++, u >>= 4) + psz[7 - q] = pszHex[u & 0xf]; + psz += 8; + *psz++ = ' '; + *psz++ = 'L'; + *psz++ = 'B'; + *psz++ = ' '; + for (uint32_t q = 0, u = cbToZero; q < 8; q++, u >>= 4) + psz[7 - q] = pszHex[u & 0xf]; + psz += 8; + *psz++ = ' '; + for (uint32_t q = 0; q < 8; q++) + *psz++ = paSHdrs[i].Name[q] ? paSHdrs[i].Name[q] : ' '; + *psz++ = ' '; *psz++ = '/'; *psz++ = ' '; *psz++ = 'v'; *psz++ = 'e'; *psz++ = 'r'; *psz++ = ' '; + *psz++ = pszHex[(uMajorVer >> 4) & 0xf]; + *psz++ = pszHex[uMajorVer & 0xf]; + *psz++ = '.'; + *psz++ = pszHex[(uMinorVer >> 4) & 0xf]; + *psz++ = pszHex[uMinorVer & 0xf]; + *psz++ = '\r'; *psz++ = '\n'; + *psz = '\0'; + DWORD cbIgn; + HANDLE hOut = RTNtCurrentPeb()->ProcessParameters->StandardOutput; + if (hOut == NULL || hOut == INVALID_HANDLE_VALUE) + hOut = RTNtCurrentPeb()->ProcessParameters->StandardError; + if (hOut == NULL || hOut == INVALID_HANDLE_VALUE) + hOut = RTNtCurrentPeb()->ProcessParameters->ConsoleHandle; + if (hOut == NULL || hOut == INVALID_HANDLE_VALUE) + hOut = GetStdHandle(STD_OUTPUT_HANDLE); + WriteFile(hOut, szMsg, psz - szMsg, &cbIgn, NULL); +# endif + + if (paSHdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE) + memset(pbToZero, 0, cbToZero); + else + { + /* The section is not writable, so temporarily make it writable. */ + PVOID pvAligned = pbToZero - ((uintptr_t)pbToZero & PAGE_OFFSET_MASK); + ULONG cbAligned = RT_ALIGN_32(cbToZero + ((uintptr_t)pbToZero & PAGE_OFFSET_MASK), PAGE_SIZE); + ULONG fNewProt = paSHdrs[i].Characteristics & IMAGE_SCN_MEM_EXECUTE + ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; + ULONG fOldProt = fNewProt; + NTSTATUS rcNt = NtProtectVirtualMemory(NtCurrentProcess(), &pvAligned, &cbAligned, fNewProt, &fOldProt); + if (NT_SUCCESS(rcNt)) + { + memset(pbToZero, 0, cbToZero); + + rcNt = NtProtectVirtualMemory(NtCurrentProcess(), &pvAligned, &cbAligned, fOldProt, &fNewProt); + } + else + RT_BREAKPOINT(); + } + } + } + } + else + RT_BREAKPOINT(); +} +#endif + + +void rtVccWinInitProcExecPath(void) +{ + WCHAR wszPath[RTPATH_MAX]; + UINT cwcPath = GetModuleFileNameW(NULL, wszPath, RT_ELEMENTS(wszPath)); + if (cwcPath) + { + char *pszDst = g_szrtProcExePath; + int rc = RTUtf16ToUtf8Ex(wszPath, cwcPath, &pszDst, sizeof(g_szrtProcExePath), &g_cchrtProcExePath); + if (RT_SUCCESS(rc)) + { + g_cchrtProcExeDir = g_offrtProcName = RTPathFilename(pszDst) - g_szrtProcExePath; + while ( g_cchrtProcExeDir >= 2 + && RTPATH_IS_SLASH(g_szrtProcExePath[g_cchrtProcExeDir - 1]) + && g_szrtProcExePath[g_cchrtProcExeDir - 2] != ':') + g_cchrtProcExeDir--; + } + else + { +#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE + RTMsgError("initProcExecPath: RTUtf16ToUtf8Ex failed: %Rrc\n", rc); +#else + rtNoCrtFatalMsgWithRc(RT_STR_TUPLE("initProcExecPath: RTUtf16ToUtf8Ex failed: "), rc); +#endif + } + } + else + { +#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE + RTMsgError("initProcExecPath: GetModuleFileNameW failed: %Rhrc\n", GetLastError()); +#else + rtNoCrtFatalWriteBegin(RT_STR_TUPLE("initProcExecPath: GetModuleFileNameW failed: ")); + rtNoCrtFatalWriteWinRc(GetLastError()); + rtNoCrtFatalWrite(RT_STR_TUPLE("\r\n")); +#endif + } +} + diff --git a/src/VBox/Runtime/r3/win/nocrt-startup-dll-win.cpp b/src/VBox/Runtime/r3/win/nocrt-startup-dll-win.cpp new file mode 100644 index 00000000..77bf0951 --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-startup-dll-win.cpp @@ -0,0 +1,162 @@ +/* $Id: nocrt-startup-dll-win.cpp $ */ +/** @file + * IPRT - No-CRT - Windows EXE startup code. + * + * @note Does not run static constructors and destructors! + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include "internal/process.h" + +#include <iprt/nt/nt-and-windows.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#ifdef IPRT_NO_CRT +# include <iprt/asm.h> +# include <iprt/nocrt/stdlib.h> +#endif + +#include "internal/compiler-vcc.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static volatile int32_t g_cAttached = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID pvReserved); + + +DECL_NO_INLINE(static, BOOL) rtVccDllMainForward(HINSTANCE hInstance, DWORD dwReason, LPVOID pvReserved) +{ + return DllMain(hInstance, dwReason, pvReserved); +} + + + +DECL_NO_INLINE(static, BOOL) rtVccDllMainProcessAttach(HINSTANCE hInstance, LPVOID pvReserved) +{ + /* + * Initialize the CRT the first time thru. + */ + if (g_cAttached == 0) + { + rtVccWinInitProcExecPath(); + + int rc = rtVccInitializersRunInit(); + if (RT_FAILURE(rc)) + return FALSE; + } + g_cAttached++; + + /* + * Call the DllMain function. + */ + BOOL fRet = rtVccDllMainForward(hInstance, DLL_PROCESS_ATTACH, pvReserved); + + /* + * On failure, we call the DllMain function again, decrement the init counter + * and probably run termination callbacks. + */ + if (!fRet) + { + rtVccDllMainForward(hInstance, DLL_PROCESS_DETACH, pvReserved); + if (--g_cAttached == 0) + { + rtVccTermRunAtExit(); + rtVccInitializersRunTerm(); + } + } + return fRet; +} + + +DECL_NO_INLINE(static, BOOL) rtVccDllMainProcessDetach(HINSTANCE hInstance, LPVOID pvReserved) +{ + /* + * Make sure there isn't an imbalance before calling DllMain and shutting + * down our own internals. + */ + if (g_cAttached <= 0) + return FALSE; + + /* + * Call DllMain. + */ + BOOL fRet = rtVccDllMainForward(hInstance, DLL_PROCESS_DETACH, pvReserved); + + /* + * Work g_cAttached and probably do uninitialization. We'll do this regardless + * of what DllMain returned. + */ + if (--g_cAttached == 0) + { + rtVccTermRunAtExit(); + rtVccInitializersRunTerm(); + } + return fRet; +} + + +extern "C" BOOL WINAPI _DllMainCRTStartup(HINSTANCE hInstance, DWORD dwReason, LPVOID pvReserved) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: +#ifdef RT_ARCH_X86 + rtVccWinInitBssOnNt3((PVOID)hInstance); +#endif + rtVccInitSecurityCookie(); /* This function must be minimal because of this! */ + return rtVccDllMainProcessAttach(hInstance, pvReserved); + + case DLL_PROCESS_DETACH: + return rtVccDllMainProcessDetach(hInstance, pvReserved); + + default: + return rtVccDllMainForward(hInstance, dwReason, pvReserved); + } +} + diff --git a/src/VBox/Runtime/r3/win/nocrt-startup-exe-win.cpp b/src/VBox/Runtime/r3/win/nocrt-startup-exe-win.cpp new file mode 100644 index 00000000..543ea05c --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-startup-exe-win.cpp @@ -0,0 +1,174 @@ +/* $Id: nocrt-startup-exe-win.cpp $ */ +/** @file + * IPRT - No-CRT - Windows EXE startup code. + * + * @note Does not run static constructors and destructors! + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/nocrt.h" +#include "internal/process.h" + +#include <iprt/nt/nt-and-windows.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "internal/compiler-vcc.h" + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern int main(int argc, char **argv, char **envp); /* in program */ +#ifndef IPRT_NO_CRT +extern DECLHIDDEN(void) InitStdHandles(PRTL_USER_PROCESS_PARAMETERS pParams); /* nocrt-streams-win.cpp */ /** @todo put in header */ +#endif + + +static int rtTerminateProcess(int32_t rcExit, bool fDoAtExit) +{ +#ifdef IPRT_NO_CRT + /* + * Run atexit callback in reverse order. + */ + if (fDoAtExit) + { + rtVccTermRunAtExit(); + rtVccInitializersRunTerm(); + } +#else + RT_NOREF(fDoAtExit); +#endif + + /* + * Terminate. + */ + for (;;) + NtTerminateProcess(NtCurrentProcess(), rcExit); +} + + +DECLASM(void) CustomMainEntrypoint(void) +{ + /* Looks like might have gotten the PPEB as parameter here before NT4, + however, there the EXE entry function clearly takes no parameters. + So, we have to retrieve the PEB our selves here. */ + PPEB_COMMON const pPeb = RTNtCurrentPeb(); + + /* + * Initialize stuff. + */ +#ifdef IPRT_NO_CRT +# ifdef RT_ARCH_X86 + rtVccWinInitBssOnNt3(pPeb->ImageBaseAddress); +# endif + rtVccInitSecurityCookie(); +#else + InitStdHandles(pPeb->ProcessParameters); +#endif + rtVccWinInitProcExecPath(); + + RTEXITCODE rcExit; +#ifdef IPRT_NO_CRT + AssertCompile(sizeof(rcExit) == sizeof(int)); + rcExit = (RTEXITCODE)rtVccInitializersRunInit(); + if (rcExit == RTEXITCODE_SUCCESS) +#endif + { + /* + * Get and convert the command line to argc/argv format. + */ + rcExit = RTEXITCODE_INIT; + UNICODE_STRING const *pCmdLine = pPeb->ProcessParameters ? &pPeb->ProcessParameters->CommandLine : NULL; + if (pCmdLine) + { + char *pszCmdLine = NULL; + int rc = RTUtf16ToUtf8Ex(pCmdLine->Buffer, pCmdLine->Length / sizeof(WCHAR), &pszCmdLine, 0, NULL); + if (RT_SUCCESS(rc)) + { + char **papszArgv; + int cArgs = 0; + rc = RTGetOptArgvFromString(&papszArgv, &cArgs, pszCmdLine, + RTGETOPTARGV_CNV_MODIFY_INPUT | RTGETOPTARGV_CNV_QUOTE_MS_CRT, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Call the main function. + */ + AssertCompile(sizeof(rcExit) == sizeof(int)); + rcExit = (RTEXITCODE)main(cArgs, papszArgv, NULL /*envp*/); + } + else +#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE + RTMsgError("Error parsing command line: %Rrc\n", rc); +#else + rtNoCrtFatalMsgWithRc(RT_STR_TUPLE("Error parsing command line: "), rc); +#endif + } + else +#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE + RTMsgError("Failed to convert command line to UTF-8: %Rrc\n", rc); +#else + rtNoCrtFatalMsgWithRc(RT_STR_TUPLE("Failed to convert command line to UTF-8: "), rc); +#endif + } + else +#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE + RTMsgError("No command line\n"); +#else + rtNoCrtFatalMsg(RT_STR_TUPLE("No command line\r\n")); +#endif + rtTerminateProcess(rcExit, true /*fDoAtExit*/); + } +#ifdef IPRT_NO_CRT + else + { +# ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE + RTMsgError("A C static initializor failed (%d)\n", rcExit); +# else + rtNoCrtFatalWriteBegin(RT_STR_TUPLE("A C static initializor failed (")); + rtNoCrtFatalWriteWinRc(rcExit); + rtNoCrtFatalWriteEnd(RT_STR_TUPLE("\r\n")); +# endif + rtTerminateProcess(rcExit, false /*fDoAtExit*/); + } +#endif +} + diff --git a/src/VBox/Runtime/r3/win/nocrt-streams-win.cpp b/src/VBox/Runtime/r3/win/nocrt-streams-win.cpp new file mode 100644 index 00000000..269c9bd2 --- /dev/null +++ b/src/VBox/Runtime/r3/win/nocrt-streams-win.cpp @@ -0,0 +1,223 @@ +/* $Id: nocrt-streams-win.cpp $ */ +/** @file + * IPRT - No-CRT - minimal stream implementation + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/stream.h> + +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/ctype.h> +#include <iprt/file.h> +#include <iprt/string.h> + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct PRINTFBUF +{ + HANDLE hNative; + size_t offBuf; + char szBuf[128]; +} PRINTFBUF; + +struct RTSTREAM +{ + int iStream; + HANDLE hNative; + RTFILE hFile; +}; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MAKE_SURE_WE_HAVE_HFILE_RETURN(a_pStream) do { \ + if ((a_pStream)->hFile != NIL_RTFILE) \ + break; \ + int rc = RTFileFromNative(&(a_pStream)->hFile, (uintptr_t)(a_pStream)->hNative); \ + AssertRCReturn(rc, rc); \ + } while (0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +RTSTREAM g_aStdStreams[3] = +{ + { 0, NULL, NIL_RTFILE }, + { 1, NULL, NIL_RTFILE }, + { 2, NULL, NIL_RTFILE }, +}; + +RTSTREAM *g_pStdIn = &g_aStdStreams[0]; +RTSTREAM *g_pStdOut = &g_aStdStreams[1]; +RTSTREAM *g_pStdErr = &g_aStdStreams[2]; + + + +DECLHIDDEN(void) InitStdHandles(PRTL_USER_PROCESS_PARAMETERS pParams) +{ + if (pParams) + { + g_pStdIn->hNative = pParams->StandardInput; + g_pStdOut->hNative = pParams->StandardOutput; + g_pStdErr->hNative = pParams->StandardError; + } +} + + +static void FlushPrintfBuffer(PRINTFBUF *pBuf) +{ + if (pBuf->offBuf) + { + DWORD cbWritten = 0; + WriteFile(pBuf->hNative, pBuf->szBuf, (DWORD)pBuf->offBuf, &cbWritten, NULL); + pBuf->offBuf = 0; + pBuf->szBuf[0] = '\0'; + } +} + + +/** @callback_method_impl{FNRTSTROUTPUT} */ +static DECLCALLBACK(size_t) MyPrintfOutputter(void *pvArg, const char *pachChars, size_t cbChars) +{ + PRINTFBUF *pBuf = (PRINTFBUF *)pvArg; + if (cbChars != 0) + { + size_t offSrc = 0; + while (offSrc < cbChars) + { + size_t cbLeft = sizeof(pBuf->szBuf) - pBuf->offBuf - 1; + if (cbLeft > 0) + { + size_t cbToCopy = RT_MIN(cbChars - offSrc, cbLeft); + memcpy(&pBuf->szBuf[pBuf->offBuf], &pachChars[offSrc], cbToCopy); + pBuf->offBuf += cbToCopy; + pBuf->szBuf[pBuf->offBuf] = '\0'; + if (cbLeft > cbToCopy) + break; + offSrc += cbToCopy; + } + FlushPrintfBuffer(pBuf); + } + } + else /* Special zero byte write at the end of the formatting. */ + FlushPrintfBuffer(pBuf); + return cbChars; +} + + +RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args) +{ + PRINTFBUF Buf; + Buf.hNative = pStream->hNative; + Buf.offBuf = 0; + Buf.szBuf[0] = '\0'; + + return (int)RTStrFormatV(MyPrintfOutputter, &Buf, NULL, NULL, pszFormat, args); +} + + +RTR3DECL(int) RTStrmPrintf(PRTSTREAM pStream, const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + int rc = RTStrmPrintfV(pStream, pszFormat, args); + va_end(args); + return rc; +} + + +RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list va) +{ + PRINTFBUF Buf; + Buf.hNative = g_pStdOut->hNative; + Buf.offBuf = 0; + Buf.szBuf[0] = '\0'; + + return (int)RTStrFormatV(MyPrintfOutputter, &Buf, NULL, NULL, pszFormat, va); +} + + +RTR3DECL(int) RTPrintf(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = RTPrintfV(pszFormat, va); + va_end(va); + return rc; +} + +#ifndef IPRT_MINIMAL_STREAM + +# if 0 +RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + MAKE_SURE_WE_HAVE_HFILE_RETURN(pStream); + return RTFileRead(pStream->hFile, pvBuf, cbToRead, pcbRead); +} +# endif + + +RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + MAKE_SURE_WE_HAVE_HFILE_RETURN(pStream); + return RTFileWrite(pStream->hFile, pvBuf, cbToWrite, pcbWritten); +} + + +RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream) +{ + MAKE_SURE_WE_HAVE_HFILE_RETURN(pStream); + return RTFileFlush(pStream->hFile); +} + + +RTR3DECL(int) RTStrmSetMode(PRTSTREAM pStream, int fBinary, int fCurrentCodeSet) +{ + AssertReturn(fBinary != (int)false, VERR_NOT_IMPLEMENTED); + AssertReturn(fCurrentCodeSet <= (int)false, VERR_NOT_IMPLEMENTED); + RT_NOREF(pStream); + return VINF_SUCCESS; +} + +#endif diff --git a/src/VBox/Runtime/r3/win/ntdll-mini-implib.def b/src/VBox/Runtime/r3/win/ntdll-mini-implib.def new file mode 100644 index 00000000..c382847e --- /dev/null +++ b/src/VBox/Runtime/r3/win/ntdll-mini-implib.def @@ -0,0 +1,163 @@ +; $Id: ntdll-mini-implib.def $ +;; @file +; IPRT - Minimal NTDLL import library defintion file. +; + +; +; Copyright (C) 2010-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +LIBRARY ntdll.dll +EXPORTS + ; Exported name - The name x86 name sought by the linker. + ; - This needs to be defined as a symbol, we generate assembly. + + CsrClientCallServer ;;= _CsrClientCallServer@16 + + NtAlertThread ;;= _NtAlertThread@4 + NtAllocateVirtualMemory ;;= _NtAllocateVirtualMemory@24 + NtCancelIoFile ;;= _NtCancelIoFile@8 + NtClearEvent ;;= _NtClearEvent@4 + NtClose ;;= _NtClose@4 + NtCreateEvent ;;= _NtCreateEvent@20 + NtCreateFile ;;= _NtCreateFile@44 + NtCreateSection ;;= _NtCreateSection@28 + NtCreateSymbolicLinkObject ;;= _NtCreateSymbolicLinkObject@16 + NtDelayExecution ;;= _NtDelayExecution@8 + NtDeviceIoControlFile ;;= _NtDeviceIoControlFile@40 + NtDuplicateObject ;;= _NtDuplicateObject@28 + NtEnumerateKey ;;= _NtEnumerateKey@24 + NtFlushBuffersFile ;;= _NtFlushBuffersFile@8 + NtFlushVirtualMemory ;;= _NtFlushVirtualMemory@16 + NtFreeVirtualMemory ;;= _NtFreeVirtualMemory@16 + NtGetContextThread ;;= _NtGetContextThread@8 + NtMapViewOfSection ;;= _NtMapViewOfSection@40 + NtOpenDirectoryObject ;;= _NtOpenDirectoryObject@12 + NtOpenEvent ;;= _NtOpenEvent@12 + NtOpenKey ;;= _NtOpenKey@12 + NtOpenProcess ;;= _NtOpenProcess@16 + NtOpenProcessToken ;;= _NtOpenProcessToken@12 + NtOpenSymbolicLinkObject ;;= _NtOpenSymbolicLinkObject@12 + NtOpenThread ;;= _NtOpenThread@16 + NtOpenThreadToken ;;= _NtOpenThreadToken@16 + NtProtectVirtualMemory ;;= _NtProtectVirtualMemory@20 + NtQueryAttributesFile ;;= _NtQueryAttributesFile@8 + NtQueryDirectoryFile ;;= _NtQueryDirectoryFile@44 + NtQueryDirectoryObject ;;= _NtQueryDirectoryObject@28 + NtQueryFullAttributesFile ;;= _NtQueryFullAttributesFile@8 + NtQueryEvent ;;= _NtQueryEvent@20 + NtQueryInformationFile ;;= _NtQueryInformationFile@20 + NtQueryInformationProcess ;;= _NtQueryInformationProcess@20 + NtQueryInformationThread ;;= _NtQueryInformationThread@20 + NtQueryInformationToken ;;= _NtQueryInformationToken@20 + NtQueryKey ;;= _NtQueryKey@20 + NtQueryObject ;;= _NtQueryObject@20 + NtQuerySection ;;= _NtQuerySection@20 + NtQuerySecurityObject ;;= _NtQuerySecurityObject@20 + NtQuerySymbolicLinkObject ;;= _NtQuerySymbolicLinkObject@12 + NtQuerySystemInformation ;;= _NtQuerySystemInformation@16 + NtQueryTimerResolution ;;= _NtQueryTimerResolution@12 + NtQueryValueKey ;;= _NtQueryValueKey@24 + NtQueryVirtualMemory ;;= _NtQueryVirtualMemory@24 + NtQueryVolumeInformationFile ;;= _NtQueryVolumeInformationFile@20 + NtReadFile ;;= _NtReadFile@36 + NtReadVirtualMemory ;;= _NtReadVirtualMemory@20 + NtResetEvent ;;= _NtResetEvent@8 + NtResumeProcess ;;= _NtResumeProcess@4 + NtResumeThread ;;= _NtResumeThread@8 + NtSetContextThread ;;= _NtSetContextThread@8 + NtSetEvent ;;= _NtSetEvent@8 + NtSetInformationFile ;;= _NtSetInformationFile@20 + NtSetInformationObject ;;= _NtSetInformationObject@16 + NtSetInformationProcess ;;= _NtSetInformationProcess@16 + NtSetInformationThread ;;= _NtSetInformationThread@16 + NtSetTimerResolution ;;= _NtSetTimerResolution@12 + NtSuspendProcess ;;= _NtSuspendProcess@4 + NtSuspendThread ;;= _NtSuspendThread@8 + NtTerminateProcess ;;= _NtTerminateProcess@8 + NtTerminateThread ;;= _NtTerminateThread@8 + NtUnmapViewOfSection ;;= _NtUnmapViewOfSection@8 + NtWaitForMultipleObjects ;;= _NtWaitForMultipleObjects@20 + NtWaitForSingleObject ;;= _NtWaitForSingleObject@12 + NtWriteFile ;;= _NtWriteFile@36 + NtWriteVirtualMemory ;;= _NtWriteVirtualMemory@20 + NtYieldExecution ;;= _NtYieldExecution@0 + + LdrInitializeThunk ;;= _LdrInitializeThunk@12 + LdrRegisterDllNotification ;;= _LdrRegisterDllNotification@16 + LdrLoadDll ;;= _LdrLoadDll@16 + LdrUnloadDll ;;= _LdrUnloadDll@4 + LdrGetDllHandle ;;= _LdrGetDllHandle@16 + LdrGetDllHandleEx ;;= _LdrGetDllHandleEx@20 + LdrGetDllHandleByMapping ;;= _LdrGetDllHandleByMapping@8 + LdrGetDllHandleByName ;;= _LdrGetDllHandleByName@12 + LdrAddRefDll ;;= _LdrAddRefDll@8 + LdrGetProcedureAddress ;;= _LdrGetProcedureAddress@12 + LdrGetProcedureAddressEx ;;= _LdrGetProcedureAddressEx@16 + LdrLockLoaderLock ;;= _LdrLockLoaderLock@12 + LdrUnlockLoaderLock ;;= _LdrUnlockLoaderLock@8 + + RtlAcquirePebLock ;;= _RtlAcquirePebLock@0 + RtlAddAccessAllowedAce ;;= _RtlAddAccessAllowedAce@16 + RtlAddAccessDeniedAce ;;= _RtlAddAccessDeniedAce@16 + RtlAllocateHeap ;;= _RtlAllocateHeap@12 + RtlCompactHeap ;;= _RtlCompactHeap@8 + RtlCopySid ;;= _RtlCopySid@12 + RtlCreateAcl ;;= _RtlCreateAcl@12 + RtlCreateHeap ;;= _RtlCreateHeap@24 + RtlCreateProcessParameters ;;= _RtlCreateProcessParameters@40 + RtlCreateSecurityDescriptor ;;= _RtlCreateSecurityDescriptor@8 + RtlCreateUserProcess ;;= _RtlCreateUserProcess@40 + RtlCreateUserThread ;;= _RtlCreateUserThread@40 + RtlDestroyProcessParameters ;;= _RtlDestroyProcessParameters@4 + RtlDosApplyFileIsolationRedirection_Ustr ;;= _RtlDosApplyFileIsolationRedirection_Ustr@36 + RtlEqualSid ;;= _RtlEqualSid@8 + RtlExitUserProcess ;;= _RtlExitUserProcess@4 + RtlExitUserThread ;;= _RtlExitUserThread@4 + RtlExpandEnvironmentStrings_U ;;= _RtlExpandEnvironmentStrings_U@16 + RtlFreeHeap ;;= _RtlFreeHeap@12 + RtlFreeUnicodeString ;;= _RtlFreeUnicodeString@4 + RtlGetLastNtStatus ;;= _RtlGetLastNtStatus@0 + RtlGetLastWin32Error ;;= _RtlGetLastWin32Error@0 + RtlGetVersion ;;= _RtlGetVersion@4 + RtlGetNtProductType ;;= _RtlGetNtProductType@4 + RtlInitializeSid ;;= _RtlInitializeSid@12 + RtlNtStatusToDosError ;;= _RtlNtStatusToDosError@4 + RtlReAllocateHeap ;;= _RtlReAllocateHeap@16 + RtlReleasePebLock ;;= _RtlReleasePebLock@0 + RtlRestoreLastWin32Error ;;= _RtlRestoreLastWin32Error@4 + RtlSetDaclSecurityDescriptor ;;= _RtlSetDaclSecurityDescriptor@16 + RtlSetLastWin32Error ;;= _RtlSetLastWin32Error@4 + RtlSetLastWin32ErrorAndNtStatusFromNtStatus ;;= _RtlSetLastWin32ErrorAndNtStatusFromNtStatus@4 + RtlSizeHeap ;;= _RtlSizeHeap@12 + RtlSubAuthoritySid ;;= _RtlSubAuthoritySid@8 + RtlQueryPerformanceCounter ;;= _RtlQueryPerformanceCounter@4 + RtlGetSystemTimePrecise ;;= _RtlGetSystemTimePrecise@0 + diff --git a/src/VBox/Runtime/r3/win/path-win.cpp b/src/VBox/Runtime/r3/win/path-win.cpp new file mode 100644 index 00000000..587de0f6 --- /dev/null +++ b/src/VBox/Runtime/r3/win/path-win.cpp @@ -0,0 +1,743 @@ +/* $Id: path-win.cpp $ */ +/** @file + * IPRT - Path manipulation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PATH +#include <iprt/win/windows.h> +#include <iprt/win/shlobj.h> + +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/utf16.h> +#include "internal/path.h" +#include "internal/fs.h" + +/* Needed for lazy loading SHGetFolderPathW in RTPathUserDocuments(). */ +typedef HRESULT WINAPI FNSHGETFOLDERPATHW(HWND, int, HANDLE, DWORD, LPWSTR); +typedef FNSHGETFOLDERPATHW *PFNSHGETFOLDERPATHW; + + +RTDECL(int) RTPathReal(const char *pszPath, char *pszRealPath, size_t cchRealPath) +{ + /* + * Convert to UTF-16, call Win32 APIs, convert back. + */ + PRTUTF16 pwszPath; + int rc = RTPathWinFromUtf8(&pwszPath, pszPath, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + LPWSTR lpFile; + WCHAR wsz[RTPATH_MAX]; + rc = GetFullPathNameW((LPCWSTR)pwszPath, RT_ELEMENTS(wsz), &wsz[0], &lpFile); + if (rc > 0 && rc < RT_ELEMENTS(wsz)) + { + /* Check that it exists. (Use RTPathAbs() to just resolve the name.) */ + DWORD dwAttr = GetFileAttributesW(wsz); + if (dwAttr != INVALID_FILE_ATTRIBUTES) + rc = RTUtf16ToUtf8Ex((PRTUTF16)&wsz[0], RTSTR_MAX, &pszRealPath, cchRealPath, NULL); + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (rc <= 0) + rc = RTErrConvertFromWin32(GetLastError()); + else + rc = VERR_FILENAME_TOO_LONG; + + RTPathWinFree(pwszPath); + } + return rc; +} + +#if 0 +RTDECL(int) RTPathAbs(const char *pszPath, char *pszAbsPath, size_t cchAbsPath) +{ + /* + * Validation. + */ + AssertPtr(pszAbsPath); + AssertPtr(pszPath); + if (RT_UNLIKELY(!*pszPath)) + return VERR_INVALID_PARAMETER; + + /* + * Convert to UTF-16, call Win32 API, convert back. + */ + LPWSTR pwszPath; + int rc = RTStrToUtf16(pszPath, &pwszPath); + if (!RT_SUCCESS(rc)) + return (rc); + + LPWSTR pwszFile; /* Ignored */ + RTUTF16 wsz[RTPATH_MAX]; + rc = GetFullPathNameW(pwszPath, RT_ELEMENTS(wsz), &wsz[0], &pwszFile); + if (rc > 0 && rc < RT_ELEMENTS(wsz)) + { + size_t cch; + rc = RTUtf16ToUtf8Ex(&wsz[0], RTSTR_MAX, &pszAbsPath, cchAbsPath, &cch); + if (RT_SUCCESS(rc)) + { +# if 1 /** @todo This code is completely bonkers. */ + /* + * Remove trailing slash if the path may be pointing to a directory. + * (See posix variant.) + */ + if ( cch > 1 + && RTPATH_IS_SLASH(pszAbsPath[cch - 1]) + && !RTPATH_IS_VOLSEP(pszAbsPath[cch - 2]) + && !RTPATH_IS_SLASH(pszAbsPath[cch - 2])) + pszAbsPath[cch - 1] = '\0'; +# endif + } + } + else if (rc <= 0) + rc = RTErrConvertFromWin32(GetLastError()); + else + rc = VERR_FILENAME_TOO_LONG; + + RTUtf16Free(pwszPath); + return rc; +} +#endif + + +RTDECL(int) RTPathUserHome(char *pszPath, size_t cchPath) +{ + /* + * Validate input + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cchPath, VERR_INVALID_PARAMETER); + + RTUTF16 wszPath[RTPATH_MAX]; + bool fValidFolderPath = false; + + /* + * Try with Windows XP+ functionality first. + */ + RTLDRMOD hShell32; + int rc = RTLdrLoadSystem("Shell32.dll", true /*fNoUnload*/, &hShell32); + if (RT_SUCCESS(rc)) + { + PFNSHGETFOLDERPATHW pfnSHGetFolderPathW; + rc = RTLdrGetSymbol(hShell32, "SHGetFolderPathW", (void**)&pfnSHGetFolderPathW); + if (RT_SUCCESS(rc)) + { + HRESULT hrc = pfnSHGetFolderPathW(0, CSIDL_PROFILE, NULL, SHGFP_TYPE_CURRENT, wszPath); + fValidFolderPath = (hrc == S_OK); + } + RTLdrClose(hShell32); + } + + DWORD dwAttr; + if ( !fValidFolderPath + || (dwAttr = GetFileAttributesW(&wszPath[0])) == INVALID_FILE_ATTRIBUTES + || !(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) + { + /* + * Fall back to Windows specific environment variables. HOME is not used. + */ + if ( !GetEnvironmentVariableW(L"USERPROFILE", &wszPath[0], RTPATH_MAX) + || (dwAttr = GetFileAttributesW(&wszPath[0])) == INVALID_FILE_ATTRIBUTES + || !(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) + { + /* %HOMEDRIVE%%HOMEPATH% */ + if (!GetEnvironmentVariableW(L"HOMEDRIVE", &wszPath[0], RTPATH_MAX)) + return VERR_PATH_NOT_FOUND; + size_t const cwc = RTUtf16Len(&wszPath[0]); + if ( !GetEnvironmentVariableW(L"HOMEPATH", &wszPath[cwc], RTPATH_MAX - (DWORD)cwc) + || (dwAttr = GetFileAttributesW(&wszPath[0])) == INVALID_FILE_ATTRIBUTES + || !(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) + return VERR_PATH_NOT_FOUND; + } + } + + /* + * Convert and return. + */ + return RTUtf16ToUtf8Ex(&wszPath[0], RTSTR_MAX, &pszPath, cchPath, NULL); +} + + +RTDECL(int) RTPathUserDocuments(char *pszPath, size_t cchPath) +{ + /* + * Validate input + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cchPath, VERR_INVALID_PARAMETER); + + RTLDRMOD hShell32; + int rc = RTLdrLoadSystem("Shell32.dll", true /*fNoUnload*/, &hShell32); + if (RT_SUCCESS(rc)) + { + PFNSHGETFOLDERPATHW pfnSHGetFolderPathW; + rc = RTLdrGetSymbol(hShell32, "SHGetFolderPathW", (void**)&pfnSHGetFolderPathW); + if (RT_SUCCESS(rc)) + { + RTUTF16 wszPath[RTPATH_MAX]; + HRESULT hrc = pfnSHGetFolderPathW(0, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, wszPath); + if ( hrc == S_OK /* Found */ + || hrc == S_FALSE) /* Found, but doesn't exist */ + { + /* + * Convert and return. + */ + RTLdrClose(hShell32); + return RTUtf16ToUtf8Ex(&wszPath[0], RTSTR_MAX, &pszPath, cchPath, NULL); + } + } + RTLdrClose(hShell32); + } + return VERR_PATH_NOT_FOUND; +} + + +#if 0 /* use nt version of this */ + +RTR3DECL(int) RTPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + return RTPathQueryInfoEx(pszPath, pObjInfo, enmAdditionalAttribs, RTPATH_F_ON_LINK); +} +#endif +#if 0 + + +RTR3DECL(int) RTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + AssertMsgReturn( enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING + && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + ("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs), + VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Query file info. + */ + uint32_t uReparseTag = RTFSMODE_SYMLINK_REPARSE_TAG; + WIN32_FILE_ATTRIBUTE_DATA Data; + PRTUTF16 pwszPath; + int rc = RTStrToUtf16(pszPath, &pwszPath); + if (RT_FAILURE(rc)) + return rc; + if (!GetFileAttributesExW(pwszPath, GetFileExInfoStandard, &Data)) + { + /* Fallback to FindFileFirst in case of sharing violation. */ + if (GetLastError() == ERROR_SHARING_VIOLATION) + { + WIN32_FIND_DATAW FindData; + HANDLE hDir = FindFirstFileW(pwszPath, &FindData); + if (hDir == INVALID_HANDLE_VALUE) + { + rc = RTErrConvertFromWin32(GetLastError()); + RTUtf16Free(pwszPath); + return rc; + } + FindClose(hDir); + + Data.dwFileAttributes = FindData.dwFileAttributes; + Data.ftCreationTime = FindData.ftCreationTime; + Data.ftLastAccessTime = FindData.ftLastAccessTime; + Data.ftLastWriteTime = FindData.ftLastWriteTime; + Data.nFileSizeHigh = FindData.nFileSizeHigh; + Data.nFileSizeLow = FindData.nFileSizeLow; + uReparseTag = FindData.dwReserved0; + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + RTUtf16Free(pwszPath); + return rc; + } + } + + /* + * Getting the information for the link target is a bit annoying and + * subject to the same access violation mess as above.. :/ + */ + /** @todo we're too lazy wrt to error paths here... */ + if ( (Data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && ((fFlags & RTPATH_F_FOLLOW_LINK) || uReparseTag != RTFSMODE_SYMLINK_REPARSE_TAG)) + { + HANDLE hFinal = CreateFileW(pwszPath, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFinal != INVALID_HANDLE_VALUE) + { + BY_HANDLE_FILE_INFORMATION FileData; + if (GetFileInformationByHandle(hFinal, &FileData)) + { + Data.dwFileAttributes = FileData.dwFileAttributes; + Data.ftCreationTime = FileData.ftCreationTime; + Data.ftLastAccessTime = FileData.ftLastAccessTime; + Data.ftLastWriteTime = FileData.ftLastWriteTime; + Data.nFileSizeHigh = FileData.nFileSizeHigh; + Data.nFileSizeLow = FileData.nFileSizeLow; + uReparseTag = 0; + } + CloseHandle(hFinal); + } + else if (GetLastError() != ERROR_SHARING_VIOLATION) + { + rc = RTErrConvertFromWin32(GetLastError()); + RTUtf16Free(pwszPath); + return rc; + } + } + + RTUtf16Free(pwszPath); + + /* + * Setup the returned data. + */ + pObjInfo->cbObject = ((uint64_t)Data.nFileSizeHigh << 32) + | (uint64_t)Data.nFileSizeLow; + pObjInfo->cbAllocated = pObjInfo->cbObject; + + Assert(sizeof(uint64_t) == sizeof(Data.ftCreationTime)); + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, *(uint64_t *)&Data.ftCreationTime); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, *(uint64_t *)&Data.ftLastAccessTime); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, *(uint64_t *)&Data.ftLastWriteTime); + pObjInfo->ChangeTime = pObjInfo->ModificationTime; + + pObjInfo->Attr.fMode = rtFsModeFromDos((Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszPath, strlen(pszPath), uReparseTag); + + /* + * Requested attributes (we cannot provide anything actually). + */ + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_NOTHING: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + break; + + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pObjInfo->Attr.u.Unix.uid = ~0U; + pObjInfo->Attr.u.Unix.gid = ~0U; + pObjInfo->Attr.u.Unix.cHardlinks = 1; + pObjInfo->Attr.u.Unix.INodeIdDevice = 0; /** @todo use volume serial number */ + pObjInfo->Attr.u.Unix.INodeId = 0; /** @todo use fileid (see GetFileInformationByHandle). */ + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pObjInfo->Attr.u.UnixOwner.uid = ~0U; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pObjInfo->Attr.u.UnixGroup.gid = ~0U; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR; + } + + return VINF_SUCCESS; +} + +#endif /* using NT version*/ + + +RTR3DECL(int) RTPathSetTimes(const char *pszPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + return RTPathSetTimesEx(pszPath, pAccessTime, pModificationTime, pChangeTime, pBirthTime, RTPATH_F_ON_LINK); +} + + +RTR3DECL(int) RTPathSetTimesEx(const char *pszPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pAccessTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pModificationTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pChangeTime, VERR_INVALID_POINTER); + AssertPtrNullReturn(pBirthTime, VERR_INVALID_POINTER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Convert the path. + */ + PRTUTF16 pwszPath; + int rc = RTPathWinFromUtf8(&pwszPath, pszPath, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + HANDLE hFile; + if (fFlags & RTPATH_F_FOLLOW_LINK) + hFile = CreateFileW(pwszPath, + FILE_WRITE_ATTRIBUTES, /* dwDesiredAccess */ + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, /* dwShareMode */ + NULL, /* security attribs */ + OPEN_EXISTING, /* dwCreationDisposition */ + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, + NULL); + else + { +/** @todo Symlink: Test RTPathSetTimesEx on Windows. (The code is disabled + * because it's not tested yet.) */ +#if 0 //def FILE_FLAG_OPEN_REPARSE_POINT + hFile = CreateFileW(pwszPath, + FILE_WRITE_ATTRIBUTES, /* dwDesiredAccess */ + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, /* dwShareMode */ + NULL, /* security attribs */ + OPEN_EXISTING, /* dwCreationDisposition */ + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, + NULL); + + if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER) +#endif + hFile = CreateFileW(pwszPath, + FILE_WRITE_ATTRIBUTES, /* dwDesiredAccess */ + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, /* dwShareMode */ + NULL, /* security attribs */ + OPEN_EXISTING, /* dwCreationDisposition */ + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, + NULL); + } + if (hFile != INVALID_HANDLE_VALUE) + { + /* + * Check if it's a no-op. + */ + if (!pAccessTime && !pModificationTime && !pBirthTime) + rc = VINF_SUCCESS; /* NOP */ + else + { + /* + * Convert the input and call the API. + */ + FILETIME CreationTimeFT; + PFILETIME pCreationTimeFT = NULL; + if (pBirthTime) + pCreationTimeFT = RTTimeSpecGetNtFileTime(pBirthTime, &CreationTimeFT); + + FILETIME LastAccessTimeFT; + PFILETIME pLastAccessTimeFT = NULL; + if (pAccessTime) + pLastAccessTimeFT = RTTimeSpecGetNtFileTime(pAccessTime, &LastAccessTimeFT); + + FILETIME LastWriteTimeFT; + PFILETIME pLastWriteTimeFT = NULL; + if (pModificationTime) + pLastWriteTimeFT = RTTimeSpecGetNtFileTime(pModificationTime, &LastWriteTimeFT); + + if (SetFileTime(hFile, pCreationTimeFT, pLastAccessTimeFT, pLastWriteTimeFT)) + rc = VINF_SUCCESS; + else + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("RTPathSetTimes('%s', %p, %p, %p, %p): SetFileTime failed with lasterr %d (%Rrc)\n", + pszPath, pAccessTime, pModificationTime, pChangeTime, pBirthTime, Err, rc)); + } + } + BOOL fRc = CloseHandle(hFile); Assert(fRc); NOREF(fRc); + } + else + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("RTPathSetTimes('%s',,,,): failed with %Rrc and lasterr=%u\n", pszPath, rc, Err)); + } + + RTPathWinFree(pwszPath); + } + + LogFlow(("RTPathSetTimes(%p:{%s}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}): return %Rrc\n", + pszPath, pszPath, pAccessTime, pAccessTime, pModificationTime, pModificationTime, + pChangeTime, pChangeTime, pBirthTime, pBirthTime)); + return rc; +} + + + + +/** + * Internal worker for RTFileRename and RTFileMove. + * + * @returns iprt status code. + * @param pszSrc The source filename. + * @param pszDst The destination filename. + * @param fFlags The windows MoveFileEx flags. + * @param fFileType The filetype. We use the RTFMODE filetypes here. If it's 0, + * anything goes. If it's RTFS_TYPE_DIRECTORY we'll check that the + * source is a directory. If Its RTFS_TYPE_FILE we'll check that it's + * not a directory (we are NOT checking whether it's a file). + */ +DECLHIDDEN(int) rtPathWin32MoveRename(const char *pszSrc, const char *pszDst, uint32_t fFlags, RTFMODE fFileType) +{ + /* + * Convert the strings. + */ + PRTUTF16 pwszSrc; + int rc = RTPathWinFromUtf8(&pwszSrc, pszSrc, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszDst; + rc = RTPathWinFromUtf8(&pwszDst, pszDst, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + /* + * Check object type if requested. + * This is open to race conditions. + */ + if (fFileType) + { + DWORD dwAttr = GetFileAttributesW(pwszSrc); + if (dwAttr == INVALID_FILE_ATTRIBUTES) + rc = RTErrConvertFromWin32(GetLastError()); + else if (RTFS_IS_DIRECTORY(fFileType)) + rc = dwAttr & FILE_ATTRIBUTE_DIRECTORY ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY; + else + rc = dwAttr & FILE_ATTRIBUTE_DIRECTORY ? VERR_IS_A_DIRECTORY : VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + { + if (MoveFileExW(pwszSrc, pwszDst, fFlags)) + rc = VINF_SUCCESS; + else + { + DWORD Err = GetLastError(); + rc = RTErrConvertFromWin32(Err); + Log(("MoveFileExW('%s', '%s', %#x, %RTfmode): fails with rc=%Rrc & lasterr=%d\n", + pszSrc, pszDst, fFlags, fFileType, rc, Err)); + } + } + RTPathWinFree(pwszDst); + } + RTPathWinFree(pwszSrc); + } + return rc; +} + + +RTR3DECL(int) RTPathRename(const char *pszSrc, const char *pszDst, unsigned fRename) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pszDst, VERR_INVALID_POINTER); + AssertMsgReturn(*pszSrc, ("%p\n", pszSrc), VERR_INVALID_PARAMETER); + AssertMsgReturn(*pszDst, ("%p\n", pszDst), VERR_INVALID_PARAMETER); + AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER); + + /* + * Call the worker. + */ + int rc = rtPathWin32MoveRename(pszSrc, pszDst, fRename & RTPATHRENAME_FLAGS_REPLACE ? MOVEFILE_REPLACE_EXISTING : 0, 0); + + LogFlow(("RTPathRename(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", pszSrc, pszSrc, pszDst, pszDst, fRename, rc)); + return rc; +} + + +RTR3DECL(int) RTPathUnlink(const char *pszPath, uint32_t fUnlink) +{ + RT_NOREF_PV(pszPath); RT_NOREF_PV(fUnlink); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(bool) RTPathExists(const char *pszPath) +{ + return RTPathExistsEx(pszPath, RTPATH_F_FOLLOW_LINK); +} + + +RTDECL(bool) RTPathExistsEx(const char *pszPath, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, false); + AssertReturn(*pszPath, false); + Assert(RTPATH_F_IS_VALID(fFlags, 0)); + + /* + * Try query file info. + */ + DWORD dwAttr; + PRTUTF16 pwszPath; + int rc = RTPathWinFromUtf8(&pwszPath, pszPath, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + dwAttr = GetFileAttributesW(pwszPath); + RTPathWinFree(pwszPath); + } + else + dwAttr = INVALID_FILE_ATTRIBUTES; + if (dwAttr == INVALID_FILE_ATTRIBUTES) + return false; + +#ifdef FILE_ATTRIBUTE_REPARSE_POINT + if ( (fFlags & RTPATH_F_FOLLOW_LINK) + && (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT)) + { + AssertFailed(); + /** @todo Symlinks: RTPathExists+RTPathExistsEx is misbehaving on symbolic + * links on Windows. */ + } +#endif + + return true; +} + + +RTDECL(int) RTPathGetCurrent(char *pszPath, size_t cchPath) +{ + int rc; + + if (cchPath > 0) + { + /* + * GetCurrentDirectory may in some cases omit the drive letter, according + * to MSDN, thus the GetFullPathName call. + */ + RTUTF16 wszCurPath[RTPATH_MAX]; + if (GetCurrentDirectoryW(RTPATH_MAX, wszCurPath)) + { + RTUTF16 wszFullPath[RTPATH_MAX]; + if (GetFullPathNameW(wszCurPath, RTPATH_MAX, wszFullPath, NULL)) + { + if ( wszFullPath[1] == ':' + && RT_C_IS_LOWER(wszFullPath[0])) + wszFullPath[0] = RT_C_TO_UPPER(wszFullPath[0]); + + rc = RTUtf16ToUtf8Ex(&wszFullPath[0], RTSTR_MAX, &pszPath, cchPath, NULL); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VERR_BUFFER_OVERFLOW; + return rc; +} + + +RTDECL(int) RTPathSetCurrent(const char *pszPath) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + + /* + * This interface is almost identical to the Windows API. + */ + PRTUTF16 pwszPath; + int rc = RTPathWinFromUtf8(&pwszPath, pszPath, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + /** @todo improve the slash stripping a bit? */ + size_t cwc = RTUtf16Len(pwszPath); + if ( cwc >= 2 + && ( pwszPath[cwc - 1] == L'/' + || pwszPath[cwc - 1] == L'\\') + && pwszPath[cwc - 2] != ':') + pwszPath[cwc - 1] = L'\0'; + + if (!SetCurrentDirectoryW(pwszPath)) + rc = RTErrConvertFromWin32(GetLastError()); + + RTPathWinFree(pwszPath); + } + return rc; +} + + +RTDECL(int) RTPathGetCurrentOnDrive(char chDrive, char *pszPath, size_t cbPath) +{ + int rc; + if (cbPath > 0) + { + WCHAR wszInput[4]; + wszInput[0] = chDrive; + wszInput[1] = ':'; + wszInput[2] = '\0'; + RTUTF16 wszFullPath[RTPATH_MAX]; + if (GetFullPathNameW(wszInput, RTPATH_MAX, wszFullPath, NULL)) + rc = RTUtf16ToUtf8Ex(&wszFullPath[0], RTSTR_MAX, &pszPath, cbPath, NULL); + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VERR_BUFFER_OVERFLOW; + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/pathint-win.cpp b/src/VBox/Runtime/r3/win/pathint-win.cpp new file mode 100644 index 00000000..81799cc5 --- /dev/null +++ b/src/VBox/Runtime/r3/win/pathint-win.cpp @@ -0,0 +1,204 @@ +/* $Id: pathint-win.cpp $ */ +/** @file + * IPRT - Windows, Internal Path stuff. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <iprt/path.h> + +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include <iprt/nt/nt-and-windows.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max number of non-null characters we pass to an Win32 API. + * You would think that MAX_PATH gives this length, however CreateDirectoryW was + * found to fail on Windows 10 (1803++) if given a perfectly formed path argument + * of 248 or more characters. Same when going thru UNC. + * + * So, to be conservative, we put the max number of characters in a non-\\?\ + * path to 243, not counting the terminator. + */ +#define ACTUAL_MAX_PATH 243 + + +DECL_NO_INLINE(static, bool) rtPathWinTryConvertToAbs(PRTUTF16 *ppwszPath) +{ + RTUTF16 wszFullPath[MAX_PATH + 1]; + DWORD cwcFull = GetFullPathNameW(*ppwszPath, MAX_PATH + 1, wszFullPath, NULL); + if (cwcFull <= ACTUAL_MAX_PATH) + { + RTUtf16Free(*ppwszPath); + PRTUTF16 const pwszCopy = RTUtf16Dup(wszFullPath); + *ppwszPath = pwszCopy; + if (pwszCopy) + return true; + } + return false; +} + + +RTDECL(int) RTPathWinFromUtf8(PRTUTF16 *ppwszPath, const char *pszPath, uint32_t fFlags) +{ + Assert(fFlags == 0); + RT_NOREF(fFlags); + + /* + * Do a straight conversion first. + */ + *ppwszPath = NULL; + size_t cwcResult = 0; + int rc = RTStrToUtf16Ex(pszPath, RTSTR_MAX, ppwszPath, 0, &cwcResult); + if (RT_SUCCESS(rc)) + { + /* + * Check the resulting length. This is straight forward for absolute + * paths, but gets complicated for relative ones. + */ + if (cwcResult <= ACTUAL_MAX_PATH) + { + if (RT_C_IS_ALPHA(pszPath[0]) && pszPath[1] == ':') + { + if (RTPATH_IS_SLASH(pszPath[2])) + return VINF_SUCCESS; + + /* Drive relative path. Found no simple way of getting the current + path of a drive, so we try convert it to an absolute path and see + how that works out. It is what the API we're calling will have to + do anyway, so this should perform just as well. */ + if (rtPathWinTryConvertToAbs(ppwszPath)) + return VINF_SUCCESS; + } + else if (RTPATH_IS_SLASH(pszPath[0])) + { + if ( RTPATH_IS_SLASH(pszPath[1]) + && !RTPATH_IS_SLASH(pszPath[2]) + && pszPath[2] != '\0') + { + /* Passthru prefix '\\?\' is fine. */ + if ( pszPath[2] == '?' + && !RTPATH_IS_SLASH(pszPath[3])) + return VINF_SUCCESS; + + /* UNC requires a longer prefix ('\??\UNC\' instead of '\??\'), so + subtract 3 chars from the max limit to be on the safe side. */ + if (cwcResult <= ACTUAL_MAX_PATH - 3) + return VINF_SUCCESS; + } + else + { + /* Drive relative. Win32 will prepend a two letter drive specification. */ + if (cwcResult <= ACTUAL_MAX_PATH - 2) + return VINF_SUCCESS; + } + } + else + { + /* Relative to CWD. We can use the API to get it's current length. + Any race conditions here is entirely the caller's problem. */ + size_t cwcCwd = GetCurrentDirectoryW(0, NULL); + if (cwcCwd + cwcResult <= ACTUAL_MAX_PATH - 1) + return VINF_SUCCESS; + } + } + /* + * We're good if the caller already supplied the passthru/length prefix: '\\?\' + */ + else if ( pszPath[1] == '?' + && RTPATH_IS_SLASH(pszPath[3]) + && RTPATH_IS_SLASH(pszPath[1]) + && RTPATH_IS_SLASH(pszPath[0])) + return VINF_SUCCESS; + + /* + * Long path requiring \\?\ prefixing. + * + * We piggy back on the NT conversion here and ASSUME RTUtf16Free is the right + * way to free the result. + */ + RTUtf16Free(*ppwszPath); + *ppwszPath = NULL; + + struct _UNICODE_STRING NtName = { 0, 0, NULL }; + HANDLE hRootDir = NULL; + rc = RTNtPathFromWinUtf8(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + /* No root dir handle. */ + if (hRootDir == NULL) + { + /* Convert the NT '\??\' prefix to a win32 passthru prefix '\\?\' */ + if ( NtName.Buffer[0] == '\\' + && NtName.Buffer[1] == '?' + && NtName.Buffer[2] == '?' + && NtName.Buffer[3] == '\\') + { + NtName.Buffer[1] = '\\'; + + /* Zero termination paranoia. */ + if (NtName.Buffer[NtName.Length / sizeof(RTUTF16)] == '\0') + { + *ppwszPath = NtName.Buffer; + return VINF_SUCCESS; + } + AssertMsgFailed(("Length=%u %.*ls\n", NtName.Length, NtName.Length / sizeof(RTUTF16), NtName.Buffer)); + } + else + AssertMsgFailed(("%ls\n", NtName.Buffer)); + } + else + AssertMsgFailed(("%s\n", pszPath)); + RTNtPathFree(&NtName, &hRootDir); + } + } + return rc; +} + + +RTDECL(void) RTPathWinFree(PRTUTF16 pwszPath) +{ + RTUtf16Free(pwszPath); +} + diff --git a/src/VBox/Runtime/r3/win/pipe-win.cpp b/src/VBox/Runtime/r3/win/pipe-win.cpp new file mode 100644 index 00000000..93bf9afc --- /dev/null +++ b/src/VBox/Runtime/r3/win/pipe-win.cpp @@ -0,0 +1,1537 @@ +/* $Id: pipe-win.cpp $ */ +/** @file + * IPRT - Anonymous Pipes, Windows Implementation. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/pipe.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include "internal/pipe.h" +#include "internal/magics.h" +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The pipe buffer size we prefer. */ +#define RTPIPE_NT_SIZE _64K + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTPIPEINTERNAL +{ + /** Magic value (RTPIPE_MAGIC). */ + uint32_t u32Magic; + /** The pipe handle. */ + HANDLE hPipe; + /** Set if this is the read end, clear if it's the write end. */ + bool fRead; + /** RTPipeFromNative: Leave native handle open on RTPipeClose. */ + bool fLeaveOpen; + /** Set if there is already pending I/O. */ + bool fIOPending; + /** Set if the zero byte read that the poll code using is pending. */ + bool fZeroByteRead; + /** Set if the pipe is broken. */ + bool fBrokenPipe; + /** Set if we've promised that the handle is writable. */ + bool fPromisedWritable; + /** Set if created inheritable. */ + bool fCreatedInheritable; + /** Usage counter. */ + uint32_t cUsers; + /** The overlapped I/O structure we use. */ + OVERLAPPED Overlapped; + /** Bounce buffer for writes. */ + uint8_t *pbBounceBuf; + /** Amount of used buffer space. */ + size_t cbBounceBufUsed; + /** Amount of allocated buffer space. */ + size_t cbBounceBufAlloc; + /** The handle of the poll set currently polling on this pipe. + * We can only have one poller at the time (lazy bird). */ + RTPOLLSET hPollSet; + /** Critical section protecting the above members. + * (Taking the lazy/simple approach.) */ + RTCRITSECT CritSect; + /** Buffer for the zero byte read. */ + uint8_t abBuf[8]; +} RTPIPEINTERNAL; + + + +/** + * Wrapper for getting FILE_PIPE_LOCAL_INFORMATION via the NT API. + * + * @returns Success indicator (true/false). + * @param pThis The pipe. + * @param pInfo The info structure. + */ +static bool rtPipeQueryNtInfo(RTPIPEINTERNAL *pThis, FILE_PIPE_LOCAL_INFORMATION *pInfo) +{ + IO_STATUS_BLOCK Ios; + RT_ZERO(Ios); + RT_ZERO(*pInfo); + NTSTATUS rcNt = NtQueryInformationFile(pThis->hPipe, &Ios, pInfo, sizeof(*pInfo), FilePipeLocalInformation); + return rcNt >= 0; +} + + +RTDECL(int) RTPipeCreate(PRTPIPE phPipeRead, PRTPIPE phPipeWrite, uint32_t fFlags) +{ + AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER); + AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTPIPE_C_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Create the read end of the pipe. + */ + DWORD dwErr; + HANDLE hPipeR; + HANDLE hPipeW; + int rc; + for (;;) + { + static volatile uint32_t g_iNextPipe = 0; + char szName[128]; + RTStrPrintf(szName, sizeof(szName), "\\\\.\\pipe\\iprt-pipe-%u-%u", RTProcSelf(), ASMAtomicIncU32(&g_iNextPipe)); + + SECURITY_ATTRIBUTES SecurityAttributes; + PSECURITY_ATTRIBUTES pSecurityAttributes = NULL; + if (fFlags & RTPIPE_C_INHERIT_READ) + { + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.lpSecurityDescriptor = NULL; + SecurityAttributes.bInheritHandle = TRUE; + pSecurityAttributes = &SecurityAttributes; + } + + DWORD dwOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED; +#ifdef FILE_FLAG_FIRST_PIPE_INSTANCE + dwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE; +#endif + + DWORD dwPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT; +#ifdef PIPE_REJECT_REMOTE_CLIENTS + dwPipeMode |= PIPE_REJECT_REMOTE_CLIENTS; +#endif + + hPipeR = CreateNamedPipeA(szName, dwOpenMode, dwPipeMode, 1 /*nMaxInstances*/, RTPIPE_NT_SIZE, RTPIPE_NT_SIZE, + NMPWAIT_USE_DEFAULT_WAIT, pSecurityAttributes); +#ifdef PIPE_REJECT_REMOTE_CLIENTS + if (hPipeR == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER) + { + dwPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS; + hPipeR = CreateNamedPipeA(szName, dwOpenMode, dwPipeMode, 1 /*nMaxInstances*/, RTPIPE_NT_SIZE, RTPIPE_NT_SIZE, + NMPWAIT_USE_DEFAULT_WAIT, pSecurityAttributes); + } +#endif +#ifdef FILE_FLAG_FIRST_PIPE_INSTANCE + if (hPipeR == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER) + { + dwOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE; + hPipeR = CreateNamedPipeA(szName, dwOpenMode, dwPipeMode, 1 /*nMaxInstances*/, RTPIPE_NT_SIZE, RTPIPE_NT_SIZE, + NMPWAIT_USE_DEFAULT_WAIT, pSecurityAttributes); + } +#endif + if (hPipeR != INVALID_HANDLE_VALUE) + { + /* + * Connect to the pipe (the write end). + * We add FILE_READ_ATTRIBUTES here to make sure we can query the + * pipe state later on. + */ + pSecurityAttributes = NULL; + if (fFlags & RTPIPE_C_INHERIT_WRITE) + { + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.lpSecurityDescriptor = NULL; + SecurityAttributes.bInheritHandle = TRUE; + pSecurityAttributes = &SecurityAttributes; + } + + hPipeW = CreateFileA(szName, + GENERIC_WRITE | FILE_READ_ATTRIBUTES /*dwDesiredAccess*/, + 0 /*dwShareMode*/, + pSecurityAttributes, + OPEN_EXISTING /* dwCreationDisposition */, + FILE_FLAG_OVERLAPPED /*dwFlagsAndAttributes*/, + NULL /*hTemplateFile*/); + if (hPipeW != INVALID_HANDLE_VALUE) + break; + dwErr = GetLastError(); + CloseHandle(hPipeR); + } + else + dwErr = GetLastError(); + if ( dwErr != ERROR_PIPE_BUSY /* already exist - compatible */ + && dwErr != ERROR_ACCESS_DENIED /* already exist - incompatible */) + return RTErrConvertFromWin32(dwErr); + /* else: try again with a new name */ + } + + /* + * Create the two handles. + */ + RTPIPEINTERNAL *pThisR = (RTPIPEINTERNAL *)RTMemAllocZ(sizeof(RTPIPEINTERNAL)); + if (pThisR) + { + RTPIPEINTERNAL *pThisW = (RTPIPEINTERNAL *)RTMemAllocZ(sizeof(RTPIPEINTERNAL)); + if (pThisW) + { + rc = RTCritSectInit(&pThisR->CritSect); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pThisW->CritSect); + if (RT_SUCCESS(rc)) + { + pThisR->Overlapped.hEvent = CreateEvent(NULL, TRUE /*fManualReset*/, + TRUE /*fInitialState*/, NULL /*pName*/); + if (pThisR->Overlapped.hEvent != NULL) + { + pThisW->Overlapped.hEvent = CreateEvent(NULL, TRUE /*fManualReset*/, + TRUE /*fInitialState*/, NULL /*pName*/); + if (pThisW->Overlapped.hEvent != NULL) + { + pThisR->u32Magic = RTPIPE_MAGIC; + pThisW->u32Magic = RTPIPE_MAGIC; + pThisR->hPipe = hPipeR; + pThisW->hPipe = hPipeW; + pThisR->fRead = true; + pThisW->fRead = false; + pThisR->fLeaveOpen = false; + pThisW->fLeaveOpen = false; + //pThisR->fIOPending = false; + //pThisW->fIOPending = false; + //pThisR->fZeroByteRead = false; + //pThisW->fZeroByteRead = false; + //pThisR->fBrokenPipe = false; + //pThisW->fBrokenPipe = false; + //pThisW->fPromisedWritable = false; + //pThisR->fPromisedWritable = false; + pThisW->fCreatedInheritable = RT_BOOL(fFlags & RTPIPE_C_INHERIT_WRITE); + pThisR->fCreatedInheritable = RT_BOOL(fFlags & RTPIPE_C_INHERIT_READ); + //pThisR->cUsers = 0; + //pThisW->cUsers = 0; + //pThisR->pbBounceBuf = NULL; + //pThisW->pbBounceBuf = NULL; + //pThisR->cbBounceBufUsed = 0; + //pThisW->cbBounceBufUsed = 0; + //pThisR->cbBounceBufAlloc = 0; + //pThisW->cbBounceBufAlloc = 0; + pThisR->hPollSet = NIL_RTPOLLSET; + pThisW->hPollSet = NIL_RTPOLLSET; + + *phPipeRead = pThisR; + *phPipeWrite = pThisW; + return VINF_SUCCESS; + } + CloseHandle(pThisR->Overlapped.hEvent); + } + RTCritSectDelete(&pThisW->CritSect); + } + RTCritSectDelete(&pThisR->CritSect); + } + RTMemFree(pThisW); + } + else + rc = VERR_NO_MEMORY; + RTMemFree(pThisR); + } + else + rc = VERR_NO_MEMORY; + + CloseHandle(hPipeR); + CloseHandle(hPipeW); + return rc; +} + + +/** + * Common worker for handling I/O completion. + * + * This is used by RTPipeClose, RTPipeWrite and RTPipeWriteBlocking. + * + * @returns IPRT status code. + * @param pThis The pipe instance handle. + */ +static int rtPipeWriteCheckCompletion(RTPIPEINTERNAL *pThis) +{ + int rc; + DWORD dwRc = WaitForSingleObject(pThis->Overlapped.hEvent, 0); + if (dwRc == WAIT_OBJECT_0) + { + DWORD cbWritten = 0; + if (GetOverlappedResult(pThis->hPipe, &pThis->Overlapped, &cbWritten, TRUE)) + { + for (;;) + { + if (cbWritten >= pThis->cbBounceBufUsed) + { + pThis->fIOPending = false; + rc = VINF_SUCCESS; + break; + } + + /* resubmit the remainder of the buffer - can this actually happen? */ + pThis->cbBounceBufUsed -= cbWritten; + memmove(&pThis->pbBounceBuf[0], &pThis->pbBounceBuf[cbWritten], pThis->cbBounceBufUsed); + rc = ResetEvent(pThis->Overlapped.hEvent); Assert(rc == TRUE); + if (!WriteFile(pThis->hPipe, pThis->pbBounceBuf, (DWORD)pThis->cbBounceBufUsed, &cbWritten, &pThis->Overlapped)) + { + DWORD const dwErr = GetLastError(); + if (dwErr == ERROR_IO_PENDING) + rc = VINF_TRY_AGAIN; + else + { + pThis->fIOPending = false; + if (dwErr == ERROR_NO_DATA) + rc = VERR_BROKEN_PIPE; + else + rc = RTErrConvertFromWin32(GetLastError()); + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + } + break; + } + Assert(cbWritten > 0); + } + } + else + { + pThis->fIOPending = false; + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else if (dwRc == WAIT_TIMEOUT) + rc = VINF_TRY_AGAIN; + else + { + pThis->fIOPending = false; + if (dwRc == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + return rc; +} + + + +RTDECL(int) RTPipeCloseEx(RTPIPE hPipe, bool fLeaveOpen) +{ + RTPIPEINTERNAL *pThis = hPipe; + if (pThis == NIL_RTPIPE) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the cleanup. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTPIPE_MAGIC, RTPIPE_MAGIC), VERR_INVALID_HANDLE); + RTCritSectEnter(&pThis->CritSect); + Assert(pThis->cUsers == 0); + + if (!pThis->fRead && pThis->fIOPending) + rtPipeWriteCheckCompletion(pThis); + + if (!fLeaveOpen && !pThis->fLeaveOpen) + CloseHandle(pThis->hPipe); + pThis->hPipe = INVALID_HANDLE_VALUE; + + CloseHandle(pThis->Overlapped.hEvent); + pThis->Overlapped.hEvent = NULL; + + RTMemFree(pThis->pbBounceBuf); + pThis->pbBounceBuf = NULL; + + RTCritSectLeave(&pThis->CritSect); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTPipeClose(RTPIPE hPipe) +{ + return RTPipeCloseEx(hPipe, false /*fLeaveOpen*/); +} + + +RTDECL(int) RTPipeFromNative(PRTPIPE phPipe, RTHCINTPTR hNativePipe, uint32_t fFlags) +{ + AssertPtrReturn(phPipe, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTPIPE_N_VALID_MASK_FN), VERR_INVALID_PARAMETER); + AssertReturn(!!(fFlags & RTPIPE_N_READ) != !!(fFlags & RTPIPE_N_WRITE), VERR_INVALID_PARAMETER); + + /* + * Get and validate the pipe handle info. + */ + HANDLE hNative = (HANDLE)hNativePipe; + AssertReturn(GetFileType(hNative) == FILE_TYPE_PIPE, VERR_INVALID_HANDLE); + + DWORD cMaxInstances; + DWORD fInfo; + if (!GetNamedPipeInfo(hNative, &fInfo, NULL, NULL, &cMaxInstances)) + return RTErrConvertFromWin32(GetLastError()); + /* Doesn't seem to matter to much if the pipe is message or byte type. Cygwin + seems to hand us such pipes when capturing output (@bugref{9397}), so just + ignore skip this check: + AssertReturn(!(fInfo & PIPE_TYPE_MESSAGE), VERR_INVALID_HANDLE); */ + AssertReturn(cMaxInstances == 1, VERR_INVALID_HANDLE); + + DWORD cInstances; + DWORD fState; + if (!GetNamedPipeHandleState(hNative, &fState, &cInstances, NULL, NULL, NULL, 0)) + return RTErrConvertFromWin32(GetLastError()); + AssertReturn(!(fState & PIPE_NOWAIT), VERR_INVALID_HANDLE); + AssertReturn(!(fState & PIPE_READMODE_MESSAGE), VERR_INVALID_HANDLE); + AssertReturn(cInstances <= 1, VERR_INVALID_HANDLE); + + /* + * Looks kind of OK, create a handle so we can try rtPipeQueryNtInfo on it + * and see if we need to duplicate it to make that call work. + */ + RTPIPEINTERNAL *pThis = (RTPIPEINTERNAL *)RTMemAllocZ(sizeof(RTPIPEINTERNAL)); + if (!pThis) + return VERR_NO_MEMORY; + int rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pThis->Overlapped.hEvent = CreateEvent(NULL, TRUE /*fManualReset*/, + TRUE /*fInitialState*/, NULL /*pName*/); + if (pThis->Overlapped.hEvent != NULL) + { + pThis->u32Magic = RTPIPE_MAGIC; + pThis->hPipe = hNative; + pThis->fRead = RT_BOOL(fFlags & RTPIPE_N_READ); + pThis->fLeaveOpen = RT_BOOL(fFlags & RTPIPE_N_LEAVE_OPEN); + //pThis->fIOPending = false; + //pThis->fZeroByteRead = false; + //pThis->fBrokenPipe = false; + //pThis->fPromisedWritable = false; + pThis->fCreatedInheritable = RT_BOOL(fFlags & RTPIPE_N_INHERIT); + //pThis->cUsers = 0; + //pThis->pbBounceBuf = NULL; + //pThis->cbBounceBufUsed = 0; + //pThis->cbBounceBufAlloc = 0; + pThis->hPollSet = NIL_RTPOLLSET; + + HANDLE hNative2 = INVALID_HANDLE_VALUE; + FILE_PIPE_LOCAL_INFORMATION Info; + RT_ZERO(Info); + if ( g_pfnSetHandleInformation + && rtPipeQueryNtInfo(pThis, &Info)) + rc = VINF_SUCCESS; + else + { + if (DuplicateHandle(GetCurrentProcess() /*hSrcProcess*/, hNative /*hSrcHandle*/, + GetCurrentProcess() /*hDstProcess*/, &hNative2 /*phDstHandle*/, + pThis->fRead ? GENERIC_READ : GENERIC_WRITE | FILE_READ_ATTRIBUTES /*dwDesiredAccess*/, + !!(fFlags & RTPIPE_N_INHERIT) /*fInheritHandle*/, + 0 /*dwOptions*/)) + { + pThis->hPipe = hNative2; + if (rtPipeQueryNtInfo(pThis, &Info)) + { + pThis->fLeaveOpen = false; + rc = VINF_SUCCESS; + } + else + { + rc = VERR_ACCESS_DENIED; + CloseHandle(hNative2); + } + } + else + hNative2 = INVALID_HANDLE_VALUE; + } + if (RT_SUCCESS(rc)) + { + /* + * Verify the pipe state and correct the inheritability. + */ + AssertStmt( Info.NamedPipeState == FILE_PIPE_CONNECTED_STATE + || Info.NamedPipeState == FILE_PIPE_CLOSING_STATE + || Info.NamedPipeState == FILE_PIPE_DISCONNECTED_STATE, + VERR_INVALID_HANDLE); + AssertStmt( Info.NamedPipeConfiguration + == ( Info.NamedPipeEnd == FILE_PIPE_SERVER_END + ? (pThis->fRead ? FILE_PIPE_INBOUND : FILE_PIPE_OUTBOUND) + : (pThis->fRead ? FILE_PIPE_OUTBOUND : FILE_PIPE_INBOUND) ) + || Info.NamedPipeConfiguration == FILE_PIPE_FULL_DUPLEX, + VERR_INVALID_HANDLE); + if ( RT_SUCCESS(rc) + && hNative2 == INVALID_HANDLE_VALUE + && !g_pfnSetHandleInformation(hNative, + HANDLE_FLAG_INHERIT /*dwMask*/, + fFlags & RTPIPE_N_INHERIT ? HANDLE_FLAG_INHERIT : 0)) + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailed(("%Rrc\n", rc)); + } + if (RT_SUCCESS(rc)) + { + /* + * Ok, we're good! If we replaced the handle, make sure it's not a standard + * handle if we think we need to close it. + */ + if (hNative2 != INVALID_HANDLE_VALUE) + { + if ( !(fFlags & RTPIPE_N_LEAVE_OPEN) + && hNative != GetStdHandle(STD_INPUT_HANDLE) + && hNative != GetStdHandle(STD_OUTPUT_HANDLE) + && hNative != GetStdHandle(STD_ERROR_HANDLE) ) + CloseHandle(hNative); + } + *phPipe = pThis; + return VINF_SUCCESS; + } + } + + /* Bail out. */ + if (hNative2 != INVALID_HANDLE_VALUE) + CloseHandle(hNative2); + CloseHandle(pThis->Overlapped.hEvent); + } + RTCritSectDelete(&pThis->CritSect); + } + RTMemFree(pThis); + return rc; +} + + +RTDECL(RTHCINTPTR) RTPipeToNative(RTPIPE hPipe) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, -1); + + return (RTHCINTPTR)pThis->hPipe; +} + + +RTDECL(int) RTPipeGetCreationInheritability(RTPIPE hPipe) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, false); + + return pThis->fCreatedInheritable; +} + + +RTDECL(int) RTPipeRead(RTPIPE hPipe, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pcbRead); + AssertPtr(pvBuf); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* No concurrent readers, sorry. */ + if (pThis->cUsers == 0) + { + pThis->cUsers++; + + /* + * Kick off a an overlapped read. It should return immediately if + * there are bytes in the buffer. If not, we'll cancel it and see + * what we get back. + */ + rc = ResetEvent(pThis->Overlapped.hEvent); Assert(rc == TRUE); + DWORD cbRead = 0; + if ( cbToRead == 0 + || ReadFile(pThis->hPipe, pvBuf, + cbToRead <= ~(DWORD)0 ? (DWORD)cbToRead : ~(DWORD)0, + &cbRead, &pThis->Overlapped)) + { + *pcbRead = cbRead; + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + pThis->fIOPending = true; + RTCritSectLeave(&pThis->CritSect); + + /* We use NtCancelIoFile here because the CancelIo API + providing access to it wasn't available till NT4. This + code needs to work (or at least load) with NT 3.1 */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtCancelIoFile(pThis->hPipe, &Ios); + if (!NT_SUCCESS(rcNt)) + WaitForSingleObject(pThis->Overlapped.hEvent, INFINITE); + + if (GetOverlappedResult(pThis->hPipe, &pThis->Overlapped, &cbRead, TRUE /*fWait*/)) + { + *pcbRead = cbRead; + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_OPERATION_ABORTED) + { + *pcbRead = 0; + rc = VINF_TRY_AGAIN; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + RTCritSectEnter(&pThis->CritSect); + pThis->fIOPending = false; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTPipeReadBlocking(RTPIPE hPipe, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pvBuf); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* No concurrent readers, sorry. */ + if (pThis->cUsers == 0) + { + pThis->cUsers++; + + size_t cbTotalRead = 0; + while (cbToRead > 0) + { + /* + * Kick of a an overlapped read. It should return immediately if + * there is bytes in the buffer. If not, we'll cancel it and see + * what we get back. + */ + rc = ResetEvent(pThis->Overlapped.hEvent); Assert(rc == TRUE); + DWORD cbRead = 0; + pThis->fIOPending = true; + RTCritSectLeave(&pThis->CritSect); + + if (ReadFile(pThis->hPipe, pvBuf, + cbToRead <= ~(DWORD)0 ? (DWORD)cbToRead : ~(DWORD)0, + &cbRead, &pThis->Overlapped)) + rc = VINF_SUCCESS; + else if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(pThis->Overlapped.hEvent, INFINITE); + if (GetOverlappedResult(pThis->hPipe, &pThis->Overlapped, &cbRead, TRUE /*fWait*/)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + RTCritSectEnter(&pThis->CritSect); + pThis->fIOPending = false; + if (RT_FAILURE(rc)) + break; + + /* advance */ + cbToRead -= cbRead; + cbTotalRead += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + } + + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + + if (pcbRead) + { + *pcbRead = cbTotalRead; + if ( RT_FAILURE(rc) + && cbTotalRead + && rc != VERR_INVALID_POINTER) + rc = VINF_SUCCESS; + } + + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTPipeWrite(RTPIPE hPipe, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pcbWritten); + AssertPtr(pvBuf); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* No concurrent writers, sorry. */ + if (pThis->cUsers == 0) + { + pThis->cUsers++; + + /* If I/O is pending, check if it has completed. */ + if (pThis->fIOPending) + rc = rtPipeWriteCheckCompletion(pThis); + else + rc = VINF_SUCCESS; + if (rc == VINF_SUCCESS) + { + Assert(!pThis->fIOPending); + + /* Adjust the number of bytes to write to fit into the current + buffer quota, unless we've promised stuff in RTPipeSelectOne. + WriteQuotaAvailable better not be zero when it shouldn't!! */ + FILE_PIPE_LOCAL_INFORMATION Info; + if ( !pThis->fPromisedWritable + && cbToWrite > 0 + && rtPipeQueryNtInfo(pThis, &Info)) + { + if (Info.NamedPipeState == FILE_PIPE_CLOSING_STATE) + rc = VERR_BROKEN_PIPE; + /** @todo fixme: To get the pipe writing support to work the + * block below needs to be commented out until a + * way is found to address the problem of the incorrectly + * set field Info.WriteQuotaAvailable. + * Update: We now just write up to RTPIPE_NT_SIZE more. This is quite + * possibely what lead to the misunderstanding here wrt to + * WriteQuotaAvailable updating. */ +#if 0 + else if ( cbToWrite >= Info.WriteQuotaAvailable + && Info.OutboundQuota != 0 + && (Info.WriteQuotaAvailable || pThis->cbBounceBufAlloc) + ) + { + cbToWrite = Info.WriteQuotaAvailable; + if (!cbToWrite) + rc = VINF_TRY_AGAIN; + } +#endif + } + pThis->fPromisedWritable = false; + + /* Do the bounce buffering. */ + if ( pThis->cbBounceBufAlloc < cbToWrite + && pThis->cbBounceBufAlloc < RTPIPE_NT_SIZE) + { + if (cbToWrite > RTPIPE_NT_SIZE) + cbToWrite = RTPIPE_NT_SIZE; + void *pv = RTMemRealloc(pThis->pbBounceBuf, RT_ALIGN_Z(cbToWrite, _1K)); + if (pv) + { + pThis->pbBounceBuf = (uint8_t *)pv; + pThis->cbBounceBufAlloc = RT_ALIGN_Z(cbToWrite, _1K); + } + else + rc = VERR_NO_MEMORY; + } + else if (cbToWrite > RTPIPE_NT_SIZE) + cbToWrite = RTPIPE_NT_SIZE; + if (RT_SUCCESS(rc) && cbToWrite) + { + memcpy(pThis->pbBounceBuf, pvBuf, cbToWrite); + pThis->cbBounceBufUsed = (uint32_t)cbToWrite; + + /* Submit the write. */ + rc = ResetEvent(pThis->Overlapped.hEvent); Assert(rc == TRUE); + DWORD cbWritten = 0; + if (WriteFile(pThis->hPipe, pThis->pbBounceBuf, (DWORD)pThis->cbBounceBufUsed, + &cbWritten, &pThis->Overlapped)) + { + *pcbWritten = RT_MIN(cbWritten, cbToWrite); /* paranoia^3 */ + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + *pcbWritten = cbToWrite; + pThis->fIOPending = true; + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_NO_DATA) + rc = VERR_BROKEN_PIPE; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (RT_SUCCESS(rc)) + *pcbWritten = 0; + } + else if (RT_SUCCESS(rc)) + *pcbWritten = 0; + + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTPipeWriteBlocking(RTPIPE hPipe, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + AssertPtr(pvBuf); + AssertPtrNull(pcbWritten); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* No concurrent writers, sorry. */ + if (pThis->cUsers == 0) + { + pThis->cUsers++; + + /* + * If I/O is pending, wait for it to complete. + */ + if (pThis->fIOPending) + { + rc = rtPipeWriteCheckCompletion(pThis); + while (rc == VINF_TRY_AGAIN) + { + Assert(pThis->fIOPending); + HANDLE hEvent = pThis->Overlapped.hEvent; + RTCritSectLeave(&pThis->CritSect); + WaitForSingleObject(hEvent, INFINITE); + RTCritSectEnter(&pThis->CritSect); + } + } + if (RT_SUCCESS(rc)) + { + Assert(!pThis->fIOPending); + pThis->fPromisedWritable = false; + + /* + * Try write everything. + * No bounce buffering, cUsers protects us. + */ + size_t cbTotalWritten = 0; + while (cbToWrite > 0) + { + rc = ResetEvent(pThis->Overlapped.hEvent); Assert(rc == TRUE); + pThis->fIOPending = true; + RTCritSectLeave(&pThis->CritSect); + + DWORD cbWritten = 0; + DWORD const cbToWriteInThisIteration = cbToWrite <= ~(DWORD)0 ? (DWORD)cbToWrite : ~(DWORD)0; + if (WriteFile(pThis->hPipe, pvBuf, cbToWriteInThisIteration, &cbWritten, &pThis->Overlapped)) + rc = VINF_SUCCESS; + else if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(pThis->Overlapped.hEvent, INFINITE); + if (GetOverlappedResult(pThis->hPipe, &pThis->Overlapped, &cbWritten, TRUE /*fWait*/)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (GetLastError() == ERROR_NO_DATA) + rc = VERR_BROKEN_PIPE; + else + rc = RTErrConvertFromWin32(GetLastError()); + + RTCritSectEnter(&pThis->CritSect); + pThis->fIOPending = false; + if (RT_FAILURE(rc)) + break; + + /* advance */ + if (cbWritten > cbToWriteInThisIteration) /* paranoia^3 */ + cbWritten = cbToWriteInThisIteration; + pvBuf = (char const *)pvBuf + cbWritten; + cbTotalWritten += cbWritten; + cbToWrite -= cbWritten; + } + + if (pcbWritten) + { + *pcbWritten = cbTotalWritten; + if ( RT_FAILURE(rc) + && cbTotalWritten + && rc != VERR_INVALID_POINTER) + rc = VINF_SUCCESS; + } + } + + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + + pThis->cUsers--; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; + +#if 0 /** @todo r=bird: What's this? */ + int rc = rtPipeTryBlocking(pThis); + if (RT_SUCCESS(rc)) + { + size_t cbTotalWritten = 0; + while (cbToWrite > 0) + { + ssize_t cbWritten = write(pThis->fd, pvBuf, RT_MIN(cbToWrite, SSIZE_MAX)); + if (cbWritten < 0) + { + rc = RTErrConvertFromErrno(errno); + break; + } + + /* advance */ + pvBuf = (char const *)pvBuf + cbWritten; + cbTotalWritten += cbWritten; + cbToWrite -= cbWritten; + } + + if (pcbWritten) + { + *pcbWritten = cbTotalWritten; + if ( RT_FAILURE(rc) + && cbTotalWritten + && rc != VERR_INVALID_POINTER) + rc = VINF_SUCCESS; + } + + ASMAtomicDecU32(&pThis->u32State); + } + return rc; +#endif +} + + +RTDECL(int) RTPipeFlush(RTPIPE hPipe) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fRead, VERR_ACCESS_DENIED); + + if (!FlushFileBuffers(pThis->hPipe)) + { + int rc = RTErrConvertFromWin32(GetLastError()); + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + return rc; + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTPipeSelectOne(RTPIPE hPipe, RTMSINTERVAL cMillies) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + uint64_t const StartMsTS = RTTimeMilliTS(); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + for (unsigned iLoop = 0;; iLoop++) + { + HANDLE hWait = INVALID_HANDLE_VALUE; + if (pThis->fRead) + { + if (pThis->fIOPending) + hWait = pThis->Overlapped.hEvent; + else + { + /* Peek at the pipe buffer and see how many bytes it contains. */ + DWORD cbAvailable; + if ( PeekNamedPipe(pThis->hPipe, NULL, 0, NULL, &cbAvailable, NULL) + && cbAvailable > 0) + { + rc = VINF_SUCCESS; + break; + } + + /* Start a zero byte read operation that we can wait on. */ + if (cMillies == 0) + { + rc = VERR_TIMEOUT; + break; + } + AssertBreakStmt(pThis->cUsers == 0, rc = VERR_INTERNAL_ERROR_5); + rc = ResetEvent(pThis->Overlapped.hEvent); Assert(rc == TRUE); + DWORD cbRead = 0; + if (ReadFile(pThis->hPipe, pThis->abBuf, 0, &cbRead, &pThis->Overlapped)) + { + rc = VINF_SUCCESS; + if (iLoop > 10) + RTThreadYield(); + } + else if (GetLastError() == ERROR_IO_PENDING) + { + pThis->cUsers++; + pThis->fIOPending = true; + pThis->fZeroByteRead = true; + hWait = pThis->Overlapped.hEvent; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + { + if (pThis->fIOPending) + { + rc = rtPipeWriteCheckCompletion(pThis); + if (RT_FAILURE(rc)) + break; + } + if (pThis->fIOPending) + hWait = pThis->Overlapped.hEvent; + else + { + FILE_PIPE_LOCAL_INFORMATION Info; +#if 1 + /* We can always write one bounce buffer full of data regardless of + the pipe buffer state. We must of course take this into account, + or code like "Full write buffer" test in tstRTPipe gets confused. */ + rc = VINF_SUCCESS; + if (rtPipeQueryNtInfo(pThis, &Info)) + { + /* Check for broken pipe. */ + if (Info.NamedPipeState != FILE_PIPE_CLOSING_STATE) + pThis->fPromisedWritable = true; + else + rc = VERR_BROKEN_PIPE; + } + else + pThis->fPromisedWritable = true; + break; + +#else /* old code: */ + if (rtPipeQueryNtInfo(pThis, &Info)) + { + /* Check for broken pipe. */ + if (Info.NamedPipeState == FILE_PIPE_CLOSING_STATE) + { + rc = VERR_BROKEN_PIPE; + break; + } + + /* Check for available write buffer space. */ + if (Info.WriteQuotaAvailable > 0) + { + pThis->fPromisedWritable = false; + rc = VINF_SUCCESS; + break; + } + + /* delayed buffer alloc or timeout: phony promise + later: See if we still can associate a semaphore with + the pipe, like on OS/2. */ + if (Info.OutboundQuota == 0 || cMillies) + { + pThis->fPromisedWritable = true; + rc = VINF_SUCCESS; + break; + } + } + else + { + pThis->fPromisedWritable = true; + rc = VINF_SUCCESS; + break; + } +#endif + } + } + if (RT_FAILURE(rc)) + break; + + /* + * Check for timeout. + */ + DWORD cMsMaxWait = INFINITE; + if ( cMillies != RT_INDEFINITE_WAIT + && ( hWait != INVALID_HANDLE_VALUE + || iLoop > 10) + ) + { + uint64_t cElapsed = RTTimeMilliTS() - StartMsTS; + if (cElapsed >= cMillies) + { + rc = VERR_TIMEOUT; + break; + } + cMsMaxWait = cMillies - (uint32_t)cElapsed; + } + + /* + * Wait. + */ + if (hWait != INVALID_HANDLE_VALUE) + { + RTCritSectLeave(&pThis->CritSect); + + DWORD dwRc = WaitForSingleObject(hWait, cMsMaxWait); + if (dwRc == WAIT_OBJECT_0) + rc = VINF_SUCCESS; + else if (dwRc == WAIT_TIMEOUT) + rc = VERR_TIMEOUT; + else if (dwRc == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(GetLastError()); + if ( RT_FAILURE(rc) + && pThis->u32Magic != RTPIPE_MAGIC) + return rc; + + RTCritSectEnter(&pThis->CritSect); + if (pThis->fZeroByteRead) + { + pThis->cUsers--; + pThis->fIOPending = false; + if (rc != VINF_SUCCESS) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NtCancelIoFile(pThis->hPipe, &Ios); + } + DWORD cbRead = 0; + GetOverlappedResult(pThis->hPipe, &pThis->Overlapped, &cbRead, TRUE /*fWait*/); + } + if (RT_FAILURE(rc)) + break; + } + } + + if (rc == VERR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + + RTCritSectLeave(&pThis->CritSect); + return rc; +} + + +RTDECL(int) RTPipeQueryReadable(RTPIPE hPipe, size_t *pcbReadable) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fRead, VERR_PIPE_NOT_READ); + AssertPtrReturn(pcbReadable, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + /** @todo The file size should give the same info and be slightly faster... */ + DWORD cbAvailable = 0; + if (PeekNamedPipe(pThis->hPipe, NULL, 0, NULL, &cbAvailable, NULL)) + { +#if ARCH_BITS == 32 + /* + * Kludge! + * + * Prior to XP SP1 (?), the returned cbAvailable value was not adjusted + * by the read position in the current message/buffer, so it could + * potentially be too high. This may cause the caller to try read more + * data than what's actually available, which may cause the read to + * block when the caller thought it wouldn't. + * + * To get an accurate readable size, we have to provide an output + * buffer and see how much we actually get back in it, as the data + * peeking works correctly (as you would expect). + */ + if (cbAvailable == 0 || g_enmWinVer >= kRTWinOSType_XP64) + { /* No data available or kernel shouldn't be affected. */ } + else + { + for (unsigned i = 0; ; i++) + { + uint8_t abBufStack[_16K]; + void *pvBufFree = NULL; + void *pvBuf; + DWORD cbBuf = RT_ALIGN_32(cbAvailable + i * 256, 64); + if (cbBuf <= sizeof(abBufStack)) + { + pvBuf = abBufStack; + /* No cbBuf = sizeof(abBufStack) here! PeekNamedPipe bounce buffers the request on the heap. */ + } + else + { + pvBufFree = pvBuf = RTMemTmpAlloc(cbBuf); + if (!pvBuf) + { + rc = VERR_NO_TMP_MEMORY; + cbAvailable = 1; + break; + } + } + + DWORD cbAvailable2 = 0; + DWORD cbRead = 0; + BOOL fRc = PeekNamedPipe(pThis->hPipe, pvBuf, cbBuf, &cbRead, &cbAvailable2, NULL); + Log(("RTPipeQueryReadable: #%u: cbAvailable=%#x cbRead=%#x cbAvailable2=%#x (cbBuf=%#x)\n", + i, cbAvailable, cbRead, cbAvailable2, cbBuf)); + + RTMemTmpFree(pvBufFree); + + if (fRc) + { + if (cbAvailable2 <= cbBuf || i >= 10) + cbAvailable = cbRead; + else + { + cbAvailable = cbAvailable2; + continue; + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + cbAvailable = 1; + } + break; + } + } +#endif + *pcbReadable = cbAvailable; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + RTCritSectLeave(&pThis->CritSect); + return rc; +} + + +RTDECL(int) RTPipeQueryInfo(RTPIPE hPipe, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, 0); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, 0); + + rtPipeFakeQueryInfo(pObjInfo, enmAddAttr, pThis->fRead); + + FILE_PIPE_LOCAL_INFORMATION Info; + if (rtPipeQueryNtInfo(pThis, &Info)) + { + pObjInfo->cbAllocated = pThis->fRead ? Info.InboundQuota : Info.OutboundQuota; + pObjInfo->cbObject = pThis->fRead ? Info.ReadDataAvailable : Info.WriteQuotaAvailable; + } + + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; +} + + +int rtPipePollGetHandle(RTPIPE hPipe, uint32_t fEvents, PRTHCINTPTR phNative) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, VERR_INVALID_HANDLE); + + AssertReturn(!(fEvents & RTPOLL_EVT_READ) || pThis->fRead, VERR_INVALID_PARAMETER); + AssertReturn(!(fEvents & RTPOLL_EVT_WRITE) || !pThis->fRead, VERR_INVALID_PARAMETER); + + /* Later: Try register an event handle with the pipe like on OS/2, there is + a file control for doing this obviously intended for the OS/2 subsys. + The question is whether this still exists on Vista and W7. */ + *phNative = (RTHCINTPTR)pThis->Overlapped.hEvent; + return VINF_SUCCESS; +} + + +/** + * Checks for pending events. + * + * @returns Event mask or 0. + * @param pThis The pipe handle. + * @param fEvents The desired events. + */ +static uint32_t rtPipePollCheck(RTPIPEINTERNAL *pThis, uint32_t fEvents) +{ + uint32_t fRetEvents = 0; + if (pThis->fBrokenPipe) + fRetEvents |= RTPOLL_EVT_ERROR; + else if (pThis->fRead) + { + if (!pThis->fIOPending) + { + DWORD cbAvailable; + if (PeekNamedPipe(pThis->hPipe, NULL, 0, NULL, &cbAvailable, NULL)) + { + if ( (fEvents & RTPOLL_EVT_READ) + && cbAvailable > 0) + fRetEvents |= RTPOLL_EVT_READ; + } + else + { + if (GetLastError() == ERROR_BROKEN_PIPE) + pThis->fBrokenPipe = true; + fRetEvents |= RTPOLL_EVT_ERROR; + } + } + } + else + { + if (pThis->fIOPending) + { + rtPipeWriteCheckCompletion(pThis); + if (pThis->fBrokenPipe) + fRetEvents |= RTPOLL_EVT_ERROR; + } + if ( !pThis->fIOPending + && !fRetEvents) + { + FILE_PIPE_LOCAL_INFORMATION Info; + if (rtPipeQueryNtInfo(pThis, &Info)) + { + /* Check for broken pipe. */ + if (Info.NamedPipeState == FILE_PIPE_CLOSING_STATE) + { + fRetEvents = RTPOLL_EVT_ERROR; + pThis->fBrokenPipe = true; + } + + /* Check if there is available buffer space. */ + if ( !fRetEvents + && (fEvents & RTPOLL_EVT_WRITE) + && ( Info.WriteQuotaAvailable > 0 + || Info.OutboundQuota == 0) + ) + fRetEvents |= RTPOLL_EVT_WRITE; + } + else if (fEvents & RTPOLL_EVT_WRITE) + fRetEvents |= RTPOLL_EVT_WRITE; + } + } + + return fRetEvents; +} + + +/** + * Internal RTPoll helper that polls the pipe handle and, if @a fNoWait is + * clear, starts whatever actions we've got running during the poll call. + * + * @returns 0 if no pending events, actions initiated if @a fNoWait is clear. + * Event mask (in @a fEvents) and no actions if the handle is ready + * already. + * UINT32_MAX (asserted) if the pipe handle is busy in I/O or a + * different poll set. + * + * @param hPipe The pipe handle. + * @param hPollSet The poll set handle (for access checks). + * @param fEvents The events we're polling for. + * @param fFinalEntry Set if this is the final entry for this handle + * in this poll set. This can be used for dealing + * with duplicate entries. + * @param fNoWait Set if it's a zero-wait poll call. Clear if + * we'll wait for an event to occur. + */ +uint32_t rtPipePollStart(RTPIPE hPipe, RTPOLLSET hPollSet, uint32_t fEvents, bool fFinalEntry, bool fNoWait) +{ + /** @todo All this polling code could be optimized to make fewer system + * calls; like for instance the ResetEvent calls. */ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, UINT32_MAX); + RT_NOREF_PV(fFinalEntry); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, UINT32_MAX); + + /* Check that this is the only current use of this pipe. */ + uint32_t fRetEvents; + if ( pThis->cUsers == 0 + || pThis->hPollSet == hPollSet) + { + /* Check what the current events are. */ + fRetEvents = rtPipePollCheck(pThis, fEvents); + if ( !fRetEvents + && !fNoWait) + { + /* Make sure the event semaphore has been reset. */ + if (!pThis->fIOPending) + { + rc = ResetEvent(pThis->Overlapped.hEvent); + Assert(rc == TRUE); + } + + /* Kick off the zero byte read thing if applicable. */ + if ( !pThis->fIOPending + && pThis->fRead + && (fEvents & RTPOLL_EVT_READ) + ) + { + DWORD cbRead = 0; + if (ReadFile(pThis->hPipe, pThis->abBuf, 0, &cbRead, &pThis->Overlapped)) + fRetEvents = rtPipePollCheck(pThis, fEvents); + else if (GetLastError() == ERROR_IO_PENDING) + { + pThis->fIOPending = true; + pThis->fZeroByteRead = true; + } + else + fRetEvents = RTPOLL_EVT_ERROR; + } + + /* If we're still set for the waiting, record the poll set and + mark the pipe used. */ + if (!fRetEvents) + { + pThis->cUsers++; + pThis->hPollSet = hPollSet; + } + } + } + else + { + AssertFailed(); + fRetEvents = UINT32_MAX; + } + + RTCritSectLeave(&pThis->CritSect); + return fRetEvents; +} + + +/** + * Called after a WaitForMultipleObjects returned in order to check for pending + * events and stop whatever actions that rtPipePollStart() initiated. + * + * @returns Event mask or 0. + * + * @param hPipe The pipe handle. + * @param fEvents The events we're polling for. + * @param fFinalEntry Set if this is the final entry for this handle + * in this poll set. This can be used for dealing + * with duplicate entries. Only keep in mind that + * this method is called in reverse order, so the + * first call will have this set (when the entire + * set was processed). + * @param fHarvestEvents Set if we should check for pending events. + */ +uint32_t rtPipePollDone(RTPIPE hPipe, uint32_t fEvents, bool fFinalEntry, bool fHarvestEvents) +{ + RTPIPEINTERNAL *pThis = hPipe; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTPIPE_MAGIC, 0); + RT_NOREF_PV(fFinalEntry); + RT_NOREF_PV(fHarvestEvents); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, 0); + + Assert(pThis->cUsers > 0); + + + /* Cancel the zero byte read. */ + uint32_t fRetEvents = 0; + if (pThis->fZeroByteRead) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NtCancelIoFile(pThis->hPipe, &Ios); + + DWORD cbRead = 0; + if ( !GetOverlappedResult(pThis->hPipe, &pThis->Overlapped, &cbRead, TRUE /*fWait*/) + && GetLastError() != ERROR_OPERATION_ABORTED) + fRetEvents = RTPOLL_EVT_ERROR; + + pThis->fIOPending = false; + pThis->fZeroByteRead = false; + } + + /* harvest events. */ + fRetEvents |= rtPipePollCheck(pThis, fEvents); + + /* update counters. */ + pThis->cUsers--; + /** @todo This isn't sane, or is it? See OS/2 impl. */ + if (!pThis->cUsers) + pThis->hPollSet = NIL_RTPOLLSET; + + RTCritSectLeave(&pThis->CritSect); + return fRetEvents; +} + diff --git a/src/VBox/Runtime/r3/win/process-win.cpp b/src/VBox/Runtime/r3/win/process-win.cpp new file mode 100644 index 00000000..18ed9ab0 --- /dev/null +++ b/src/VBox/Runtime/r3/win/process-win.cpp @@ -0,0 +1,2750 @@ +/* $Id: process-win.cpp $ */ +/** @file + * IPRT - Process, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <iprt/asm.h> /* hack */ + +#include <iprt/nt/nt-and-windows.h> +#include <Userenv.h> +#include <tlhelp32.h> +#ifndef IPRT_NO_CRT +# include <process.h> +# include <errno.h> +# include <Strsafe.h> +#endif +#include <LsaLookup.h> +#include <Lmcons.h> + +#define _NTDEF_ /* Prevents redefining (P)UNICODE_STRING. */ +#include <Ntsecapi.h> + +#include <iprt/process.h> +#include "internal-r3-win.h" + +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/string.h> +#include <iprt/socket.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/* kernel32.dll: */ +//typedef DWORD (WINAPI *PFNWTSGETACTIVECONSOLESESSIONID)(VOID); +typedef HANDLE (WINAPI *PFNCREATETOOLHELP32SNAPSHOT)(DWORD, DWORD); +typedef BOOL (WINAPI *PFNPROCESS32FIRSTW)(HANDLE, LPPROCESSENTRY32W); +typedef BOOL (WINAPI *PFNPROCESS32NEXTW)(HANDLE, LPPROCESSENTRY32W); + +/* psapi.dll: */ +typedef BOOL (WINAPI *PFNENUMPROCESSES)(LPDWORD, DWORD, LPDWORD); +typedef DWORD (WINAPI *PFNGETMODULEBASENAMEW)(HANDLE, HMODULE, LPWSTR, DWORD); + +/* advapi32.dll: */ +typedef BOOL (WINAPI *PFNCREATEPROCESSWITHLOGON)(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPCWSTR, LPWSTR, DWORD, + LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); +typedef NTSTATUS (NTAPI *PFNLSALOOKUPNAMES2)(LSA_HANDLE, ULONG, ULONG, PLSA_UNICODE_STRING, + PLSA_REFERENCED_DOMAIN_LIST*, PLSA_TRANSLATED_SID2*); + +/* userenv.dll: */ +typedef BOOL (WINAPI *PFNCREATEENVIRONMENTBLOCK)(LPVOID *, HANDLE, BOOL); +typedef BOOL (WINAPI *PFNPFNDESTROYENVIRONMENTBLOCK)(LPVOID); +typedef BOOL (WINAPI *PFNLOADUSERPROFILEW)(HANDLE, LPPROFILEINFOW); +typedef BOOL (WINAPI *PFNUNLOADUSERPROFILE)(HANDLE, HANDLE); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init once structure. */ +static RTONCE g_rtProcWinInitOnce = RTONCE_INITIALIZER; +/** Critical section protecting the process array. */ +static RTCRITSECT g_CritSect; +/** The number of processes in the array. */ +static uint32_t g_cProcesses; +/** The current allocation size. */ +static uint32_t g_cProcessesAlloc; +/** Array containing the live or non-reaped child processes. */ +static struct RTPROCWINENTRY +{ + /** The process ID. */ + ULONG_PTR pid; + /** The process handle. */ + HANDLE hProcess; +} *g_paProcesses; + +/** Structure for storing a user's account info. + * Must be free'd with rtProcWinFreeAccountInfo(). */ +typedef struct RTPROCWINACCOUNTINFO +{ + /** User name. */ + PRTUTF16 pwszUserName; + /** Domain this account is tied to. Can be NULL if no domain is being used. */ + PRTUTF16 pwszDomain; +} RTPROCWINACCOUNTINFO, *PRTPROCWINACCOUNTINFO; + +/** @name userenv.dll imports (we don't unload it). + * They're all optional. So in addition to using g_rtProcWinResolveOnce, the + * caller must also check if any of the necessary APIs are NULL pointers. + * @{ */ +/** Init once structure for run-as-user functions we need. */ +static RTONCE g_rtProcWinResolveOnce = RTONCE_INITIALIZER; +/* kernel32.dll: */ +static PFNCREATETOOLHELP32SNAPSHOT g_pfnCreateToolhelp32Snapshot = NULL; +static PFNPROCESS32FIRSTW g_pfnProcess32FirstW = NULL; +static PFNPROCESS32NEXTW g_pfnProcess32NextW = NULL; +/* psapi.dll: */ +static PFNGETMODULEBASENAMEW g_pfnGetModuleBaseNameW = NULL; +static PFNENUMPROCESSES g_pfnEnumProcesses = NULL; +/* advapi32.dll: */ +static PFNCREATEPROCESSWITHLOGON g_pfnCreateProcessWithLogonW = NULL; +static decltype(LogonUserW) *g_pfnLogonUserW = NULL; +static decltype(CreateProcessAsUserW) *g_pfnCreateProcessAsUserW = NULL; +/* user32.dll: */ +static decltype(OpenWindowStationW) *g_pfnOpenWindowStationW = NULL; +static decltype(CloseWindowStation) *g_pfnCloseWindowStation = NULL; +/* userenv.dll: */ +static PFNCREATEENVIRONMENTBLOCK g_pfnCreateEnvironmentBlock = NULL; +static PFNPFNDESTROYENVIRONMENTBLOCK g_pfnDestroyEnvironmentBlock = NULL; +static PFNLOADUSERPROFILEW g_pfnLoadUserProfileW = NULL; +static PFNUNLOADUSERPROFILE g_pfnUnloadUserProfile = NULL; +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int rtProcWinFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec, PRTUTF16 *ppwszExec); +static int rtProcWinCreateEnvBlockAndFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec, + PRTUTF16 *ppwszzBlock, PRTUTF16 *ppwszExec); + + +/** + * Clean up the globals. + * + * @param enmReason Ignored. + * @param iStatus Ignored. + * @param pvUser Ignored. + */ +static DECLCALLBACK(void) rtProcWinTerm(RTTERMREASON enmReason, int32_t iStatus, void *pvUser) +{ + NOREF(pvUser); NOREF(iStatus); NOREF(enmReason); + + RTCritSectDelete(&g_CritSect); + + size_t i = g_cProcesses; + while (i-- > 0) + { + CloseHandle(g_paProcesses[i].hProcess); + g_paProcesses[i].hProcess = NULL; + } + RTMemFree(g_paProcesses); + + g_paProcesses = NULL; + g_cProcesses = 0; + g_cProcessesAlloc = 0; +} + + +/** + * Initialize the globals. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int32_t) rtProcWinInitOnce(void *pvUser) +{ + NOREF(pvUser); + + g_cProcesses = 0; + g_cProcessesAlloc = 0; + g_paProcesses = NULL; + int rc = RTCritSectInit(&g_CritSect); + if (RT_SUCCESS(rc)) + { + /** @todo init once, terminate once - this is a generic thing which should + * have some kind of static and simpler setup! */ + rc = RTTermRegisterCallback(rtProcWinTerm, NULL); + if (RT_SUCCESS(rc)) + return rc; + RTCritSectDelete(&g_CritSect); + } + return rc; +} + + +/** + * Gets the process handle for a process from g_paProcesses. + * + * @returns Process handle if found, NULL if not. + * @param pid The process to remove (pid). + */ +static HANDLE rtProcWinFindPid(RTPROCESS pid) +{ + HANDLE hProcess = NULL; + + RTCritSectEnter(&g_CritSect); + uint32_t i = g_cProcesses; + while (i-- > 0) + if (g_paProcesses[i].pid == pid) + { + hProcess = g_paProcesses[i].hProcess; + break; + } + RTCritSectLeave(&g_CritSect); + + return hProcess; +} + + +/** + * Removes a process from g_paProcesses and closes the process handle. + * + * @param pid The process to remove (pid). + */ +static void rtProcWinRemovePid(RTPROCESS pid) +{ + RTCritSectEnter(&g_CritSect); + uint32_t i = g_cProcesses; + while (i-- > 0) + if (g_paProcesses[i].pid == pid) + { + HANDLE hProcess = g_paProcesses[i].hProcess; + + g_cProcesses--; + uint32_t cToMove = g_cProcesses - i; + if (cToMove) + memmove(&g_paProcesses[i], &g_paProcesses[i + 1], cToMove * sizeof(g_paProcesses[0])); + + RTCritSectLeave(&g_CritSect); + CloseHandle(hProcess); + return; + } + RTCritSectLeave(&g_CritSect); +} + + +/** + * Adds a process to g_paProcesses. + * + * @returns IPRT status code. + * @param pid The process id. + * @param hProcess The process handle. + */ +static int rtProcWinAddPid(RTPROCESS pid, HANDLE hProcess) +{ + RTCritSectEnter(&g_CritSect); + + uint32_t i = g_cProcesses; + if (i >= g_cProcessesAlloc) + { + void *pvNew = RTMemRealloc(g_paProcesses, (i + 16) * sizeof(g_paProcesses[0])); + if (RT_UNLIKELY(!pvNew)) + { + RTCritSectLeave(&g_CritSect); + return VERR_NO_MEMORY; + } + g_paProcesses = (struct RTPROCWINENTRY *)pvNew; + g_cProcessesAlloc = i + 16; + } + + g_paProcesses[i].pid = pid; + g_paProcesses[i].hProcess = hProcess; + g_cProcesses = i + 1; + + RTCritSectLeave(&g_CritSect); + return VINF_SUCCESS; +} + + +/** + * Initialize the import APIs for run-as-user and special environment support. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) rtProcWinResolveOnce(void *pvUser) +{ + int rc; + RTLDRMOD hMod; + RT_NOREF_PV(pvUser); + + /* + * kernel32.dll APIs introduced after NT4. + */ + g_pfnCreateToolhelp32Snapshot = (PFNCREATETOOLHELP32SNAPSHOT)GetProcAddress(g_hModKernel32, "CreateToolhelp32Snapshot"); + g_pfnProcess32FirstW = (PFNPROCESS32FIRSTW )GetProcAddress(g_hModKernel32, "Process32FirstW"); + g_pfnProcess32NextW = (PFNPROCESS32NEXTW )GetProcAddress(g_hModKernel32, "Process32NextW"); + + /* + * psapi.dll APIs, if none of the above are available. + */ + if ( !g_pfnCreateToolhelp32Snapshot + || !g_pfnProcess32FirstW + || !g_pfnProcess32NextW) + { + Assert(!g_pfnCreateToolhelp32Snapshot && !g_pfnProcess32FirstW && !g_pfnProcess32NextW); + + rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hMod, "GetModuleBaseNameW", (void **)&g_pfnGetModuleBaseNameW); + AssertStmt(RT_SUCCESS(rc), g_pfnGetModuleBaseNameW = NULL); + + rc = RTLdrGetSymbol(hMod, "EnumProcesses", (void **)&g_pfnEnumProcesses); + AssertStmt(RT_SUCCESS(rc), g_pfnEnumProcesses = NULL); + + RTLdrClose(hMod); + } + } + + /* + * advapi32.dll APIs. + */ + rc = RTLdrLoadSystem("advapi32.dll", true /*fNoUnload*/, &hMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hMod, "CreateProcessWithLogonW", (void **)&g_pfnCreateProcessWithLogonW); + if (RT_FAILURE(rc)) { g_pfnCreateProcessWithLogonW = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT4); } + + rc = RTLdrGetSymbol(hMod, "LogonUserW", (void **)&g_pfnLogonUserW); + if (RT_FAILURE(rc)) { g_pfnLogonUserW = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT350); } + + rc = RTLdrGetSymbol(hMod, "CreateProcessAsUserW", (void **)&g_pfnCreateProcessAsUserW); + if (RT_FAILURE(rc)) { g_pfnCreateProcessAsUserW = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT350); } + + RTLdrClose(hMod); + } + + /* + * user32.dll APIs. + */ + rc = RTLdrLoadSystem("user32.dll", true /*fNoUnload*/, &hMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hMod, "OpenWindowStationW", (void **)&g_pfnOpenWindowStationW); + if (RT_FAILURE(rc)) { g_pfnOpenWindowStationW = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT310); } + + rc = RTLdrGetSymbol(hMod, "CloseWindowStation", (void **)&g_pfnCloseWindowStation); + if (RT_FAILURE(rc)) { g_pfnCloseWindowStation = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT310); } + + RTLdrClose(hMod); + } + + /* + * userenv.dll APIs. + */ + rc = RTLdrLoadSystem("userenv.dll", true /*fNoUnload*/, &hMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hMod, "LoadUserProfileW", (void **)&g_pfnLoadUserProfileW); + if (RT_FAILURE(rc)) { g_pfnLoadUserProfileW = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT4); } + + rc = RTLdrGetSymbol(hMod, "UnloadUserProfile", (void **)&g_pfnUnloadUserProfile); + if (RT_FAILURE(rc)) { g_pfnUnloadUserProfile = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT4); } + + rc = RTLdrGetSymbol(hMod, "CreateEnvironmentBlock", (void **)&g_pfnCreateEnvironmentBlock); + if (RT_FAILURE(rc)) { g_pfnCreateEnvironmentBlock = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT4); } + + rc = RTLdrGetSymbol(hMod, "DestroyEnvironmentBlock", (void **)&g_pfnDestroyEnvironmentBlock); + if (RT_FAILURE(rc)) { g_pfnDestroyEnvironmentBlock = NULL; Assert(g_enmWinVer <= kRTWinOSType_NT4); } + + RTLdrClose(hMod); + } + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess) +{ + return RTProcCreateEx(pszExec, papszArgs, Env, fFlags, + NULL, NULL, NULL, /* standard handles */ + NULL /*pszAsUser*/, NULL /* pszPassword*/, + NULL /*pvExtraData*/, pProcess); +} + + +/** + * The following NT call is for v3.51 and does the equivalent of: + * DuplicateTokenEx(hSrcToken, MAXIMUM_ALLOWED, NULL, + * SecurityIdentification, TokenPrimary, phToken); + */ +static int rtProcWinDuplicateToken(HANDLE hSrcToken, PHANDLE phToken) +{ + int rc; + if (g_pfnNtDuplicateToken) + { + SECURITY_QUALITY_OF_SERVICE SecQoS; + SecQoS.Length = sizeof(SecQoS); + SecQoS.ImpersonationLevel = SecurityIdentification; + SecQoS.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; + SecQoS.EffectiveOnly = FALSE; + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, NULL /*Name*/, 0 /*OBJ_XXX*/, NULL /*Root*/, NULL /*SecDesc*/); + ObjAttr.SecurityQualityOfService = &SecQoS; + + NTSTATUS rcNt = g_pfnNtDuplicateToken(hSrcToken, MAXIMUM_ALLOWED, &ObjAttr, FALSE, TokenPrimary, phToken); + if (NT_SUCCESS(rcNt)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = VERR_SYMBOL_NOT_FOUND; /** @todo do we really need to duplicate the token? */ + return rc; +} + + +/** + * Get the token assigned to the thread indicated by @a hThread. + * + * Only used when RTPROC_FLAGS_AS_IMPERSONATED_TOKEN is in effect and the + * purpose is to get a duplicate the impersonated token of the current thread. + * + * @returns IPRT status code. + * @param hThread The thread handle (current thread). + * @param phToken Where to return the a duplicate of the thread token + * handle on success. (The caller closes it.) + */ +static int rtProcWinGetThreadTokenHandle(HANDLE hThread, PHANDLE phToken) +{ + AssertPtr(phToken); + + int rc; + HANDLE hTokenThread; + if (OpenThreadToken(hThread, + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE + | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, + TRUE /* OpenAsSelf - for impersonation at SecurityIdentification level */, + &hTokenThread)) + { + rc = rtProcWinDuplicateToken(hTokenThread, phToken); + CloseHandle(hTokenThread); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + return rc; +} + + +/** + * Get the token assigned the process indicated by @a hProcess. + * + * Only used when pwszUser is NULL and RTPROC_FLAGS_AS_IMPERSONATED_TOKEN isn't + * set. + * + * @returns IPRT status code. + * @param hProcess The process handle (current process). + * @param phToken Where to return the a duplicate of the thread token + * handle on success. (The caller closes it.) + */ +static int rtProcWinGetProcessTokenHandle(HANDLE hProcess, PHANDLE phToken) +{ + AssertPtr(phToken); + + int rc; + HANDLE hTokenProcess; + if (OpenProcessToken(hProcess, + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE + | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, + &hTokenProcess)) + { + rc = rtProcWinDuplicateToken(hTokenProcess, phToken); /* not sure if this is strictly necessary */ + CloseHandle(hTokenProcess); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + return rc; +} + + +/** + * Get the process token of the process indicated by @a dwPID if the @a pSid and + * @a idSessionDesired matches. + * + * @returns IPRT status code. + * @param dwPid The process identifier. + * @param pSid The secure identifier of the user. + * @param idDesiredSession The session the process candidate should + * preferably belong to, UINT32_MAX if anything + * goes. + * @param phToken Where to return the a duplicate of the process token + * handle on success. (The caller closes it.) + */ +static int rtProcWinGetProcessTokenHandle(DWORD dwPid, PSID pSid, DWORD idDesiredSession, PHANDLE phToken) +{ + AssertPtr(pSid); + AssertPtr(phToken); + + int rc; + HANDLE hProc = OpenProcess(MAXIMUM_ALLOWED, TRUE, dwPid); + if (hProc != NULL) + { + HANDLE hTokenProc; + if (OpenProcessToken(hProc, + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE + | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, + &hTokenProc)) + { + /* + * Query the user SID from the token. + */ + SetLastError(NO_ERROR); + DWORD dwSize = 0; + BOOL fRc = GetTokenInformation(hTokenProc, TokenUser, NULL, 0, &dwSize); + DWORD dwErr = GetLastError(); + if ( !fRc + && dwErr == ERROR_INSUFFICIENT_BUFFER + && dwSize > 0) + { + PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemTmpAllocZ(dwSize); + if (pTokenUser) + { + if (GetTokenInformation(hTokenProc, TokenUser, pTokenUser, dwSize, &dwSize)) + { + /* + * Match token user with the user we're want to create a process as. + */ + if ( IsValidSid(pTokenUser->User.Sid) + && EqualSid(pTokenUser->User.Sid, pSid)) + { + /* + * Do we need to match the session ID? + */ + rc = VINF_SUCCESS; + if (idDesiredSession != UINT32_MAX) + { + DWORD idCurSession = UINT32_MAX; + if (GetTokenInformation(hTokenProc, TokenSessionId, &idCurSession, sizeof(DWORD), &dwSize)) + rc = idDesiredSession == idCurSession ? VINF_SUCCESS : VERR_NOT_FOUND; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + if (RT_SUCCESS(rc)) + { + /* + * Got a match. Duplicate the token. This duplicated token will + * be used for the actual CreateProcessAsUserW() call then. + */ + rc = rtProcWinDuplicateToken(hTokenProc, phToken); + } + } + else + rc = VERR_NOT_FOUND; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pTokenUser); + } + else + rc = VERR_NO_MEMORY; + } + else if (fRc || dwErr == NO_ERROR) + rc = VERR_IPE_UNEXPECTED_STATUS; + else + rc = RTErrConvertFromWin32(dwErr); + CloseHandle(hTokenProc); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + CloseHandle(hProc); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + return rc; +} + + +/** + * Fallback method for rtProcWinFindTokenByProcess that uses the older NT4 + * PSAPI.DLL API. + * + * @returns Success indicator. + * @param papszNames The process candidates, in prioritized order. + * @param pSid The secure identifier of the user. + * @param phToken Where to return the token handle - duplicate, + * caller closes it on success. + * + * @remarks NT4 needs a copy of "PSAPI.dll" (redistributed by Microsoft and not + * part of the OS) in order to get a lookup. If we don't have this DLL + * we are not able to get a token and therefore no UI will be visible. + */ +static bool rtProcWinFindTokenByProcessAndPsApi(const char * const *papszNames, PSID pSid, PHANDLE phToken) +{ + /* + * Load PSAPI.DLL and resolve the two symbols we need. + */ + if ( !g_pfnGetModuleBaseNameW + || !g_pfnEnumProcesses) + return false; + + /* + * Get a list of PID. We retry if it looks like there are more PIDs + * to be returned than what we supplied buffer space for. + */ + bool fFound = false; + int rc = VINF_SUCCESS; + DWORD cbPidsAllocated = 4096; + DWORD cbPidsReturned = 0; /* (MSC maybe used uninitialized) */ + DWORD *paPids; + for (;;) + { + paPids = (DWORD *)RTMemTmpAlloc(cbPidsAllocated); + AssertBreakStmt(paPids, rc = VERR_NO_TMP_MEMORY); + cbPidsReturned = 0; + if (!g_pfnEnumProcesses(paPids, cbPidsAllocated, &cbPidsReturned)) + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailedBreak(("%Rrc\n", rc)); + } + if ( cbPidsReturned < cbPidsAllocated + || cbPidsAllocated >= _512K) + break; + RTMemTmpFree(paPids); + cbPidsAllocated *= 2; + } + if (RT_SUCCESS(rc)) + { + /* + * Search for the process. + * + * We ASSUME that the caller won't be specifying any names longer + * than RTPATH_MAX. + */ + PRTUTF16 pwszProcName = (PRTUTF16)RTMemTmpAllocZ(RTPATH_MAX * sizeof(pwszProcName[0])); + if (pwszProcName) + { + for (size_t i = 0; papszNames[i] && !fFound; i++) + { + const DWORD cPids = cbPidsReturned / sizeof(DWORD); + for (DWORD iPid = 0; iPid < cPids && !fFound; iPid++) + { + HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, paPids[iPid]); + if (hProc) + { + *pwszProcName = '\0'; + DWORD cbRet = g_pfnGetModuleBaseNameW(hProc, 0 /*hModule = exe */, pwszProcName, RTPATH_MAX); + if ( cbRet > 0 + && RTUtf16ICmpAscii(pwszProcName, papszNames[i]) == 0 + && RT_SUCCESS(rtProcWinGetProcessTokenHandle(paPids[iPid], pSid, UINT32_MAX, phToken))) + fFound = true; + CloseHandle(hProc); + } + } + } + RTMemTmpFree(pwszProcName); + } + else + rc = VERR_NO_TMP_MEMORY; + } + RTMemTmpFree(paPids); + + return fFound; +} + + +/** + * Finds a one of the processes in @a papszNames running with user @a pSid and possibly + * in the required windows session. Returns a duplicate handle to its token. + * + * @returns Success indicator. + * @param papszNames The process candidates, in prioritized order. + * @param pSid The secure identifier of the user. + * @param idDesiredSession The session the process candidate should + * belong to if possible, UINT32_MAX if anything + * goes. + * @param phToken Where to return the token handle - duplicate, + * caller closes it on success. + */ +static bool rtProcWinFindTokenByProcess(const char * const *papszNames, PSID pSid, uint32_t idDesiredSession, PHANDLE phToken) +{ + AssertPtr(papszNames); + AssertPtr(pSid); + AssertPtr(phToken); + + bool fFound = false; + + /* + * On modern systems (W2K+) try the Toolhelp32 API first; this is more stable + * and reliable. Fallback to EnumProcess on NT4. + */ + bool fFallback = true; + if (g_pfnProcess32NextW && g_pfnProcess32FirstW && g_pfnCreateToolhelp32Snapshot) + { + HANDLE hSnap = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + Assert(hSnap != INVALID_HANDLE_VALUE); + if (hSnap != INVALID_HANDLE_VALUE) + { + fFallback = false; + for (size_t i = 0; papszNames[i] && !fFound; i++) + { + PROCESSENTRY32W ProcEntry; + ProcEntry.dwSize = sizeof(PROCESSENTRY32); + ProcEntry.szExeFile[0] = '\0'; + if (g_pfnProcess32FirstW(hSnap, &ProcEntry)) + { + do + { + if (RTUtf16ICmpAscii(ProcEntry.szExeFile, papszNames[i]) == 0) + { + int rc = rtProcWinGetProcessTokenHandle(ProcEntry.th32ProcessID, pSid, idDesiredSession, phToken); + if (RT_SUCCESS(rc)) + { + fFound = true; + break; + } + } + } while (g_pfnProcess32NextW(hSnap, &ProcEntry)); + } + else + AssertMsgFailed(("dwErr=%u (%x)\n", GetLastError(), GetLastError())); + } + CloseHandle(hSnap); + } + } + + /* If we couldn't take a process snapshot for some reason or another, fall + back on the NT4 compatible API. */ + if (fFallback) + fFound = rtProcWinFindTokenByProcessAndPsApi(papszNames, pSid, phToken); + return fFound; +} + + +/** + * Logs on a specified user and returns its primary token. + * + * @returns IPRT status code. + * @param pwszUser User name. A domain name can be specified (as part of a UPN, User Principal Name), + * e.g. "joedoe@example.com". + * @param pwszPassword Password. + * @param phToken Pointer to store the logon token. + */ +static int rtProcWinUserLogon(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, HANDLE *phToken) +{ + AssertPtrReturn(pwszUser, VERR_INVALID_POINTER); + AssertPtrReturn(pwszPassword, VERR_INVALID_POINTER); + AssertPtrReturn(phToken, VERR_INVALID_POINTER); + if (!g_pfnLogonUserW) + return VERR_NOT_SUPPORTED; + + /* + * Because we have to deal with http://support.microsoft.com/kb/245683 + * for NULL domain names when running on NT4 here, pass an empty string if so. + * However, passing FQDNs should work! + * + * The SE_TCB_NAME (Policy: Act as part of the operating system) right + * is required on older windows versions (NT4, W2K, possibly XP). + */ + PCRTUTF16 pwszDomainNone = g_enmWinVer < kRTWinOSType_2K ? L"" /* NT4 and older */ : NULL /* Windows 2000 and up */; + BOOL fRc = g_pfnLogonUserW(pwszUser, + /* The domain always is passed as part of the UPN (user name). */ + pwszDomainNone, + pwszPassword, + LOGON32_LOGON_INTERACTIVE, + LOGON32_PROVIDER_DEFAULT, + phToken); + if (fRc) + return VINF_SUCCESS; + + DWORD dwErr = GetLastError(); + int rc = dwErr == ERROR_PRIVILEGE_NOT_HELD ? VERR_PROC_TCB_PRIV_NOT_HELD : RTErrConvertFromWin32(dwErr); + if (rc == VERR_UNRESOLVED_ERROR) + LogRelFunc(("dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc)); + return rc; +} + + +/** + * Returns the environment to use for the child process. + * + * This implements the RTPROC_FLAGS_ENV_CHANGE_RECORD and environment related + * parts of RTPROC_FLAGS_PROFILE. + * + * @returns IPRT status code. + * @param hToken The user token to use if RTPROC_FLAGS_PROFILE is given. + * The caller must have loaded profile for this. + * @param hEnv The environment passed in by the RTProcCreateEx caller. + * @param fFlags The process creation flags passed in by the + * RTProcCreateEx caller (RTPROC_FLAGS_XXX). + * @param phEnv Where to return the environment to use. This can either + * be a newly created environment block or @a hEnv. In the + * former case, the caller must destroy it. + */ +static int rtProcWinCreateEnvFromToken(HANDLE hToken, RTENV hEnv, uint32_t fFlags, PRTENV phEnv) +{ + int rc; + + /* + * Query the environment from the user profile associated with the token if + * the caller has specified it directly or indirectly. + */ + if ( (fFlags & RTPROC_FLAGS_PROFILE) + && ( hEnv == RTENV_DEFAULT + || (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)) ) + { + if (g_pfnCreateEnvironmentBlock && g_pfnDestroyEnvironmentBlock) + { + LPVOID pvEnvBlockProfile = NULL; + if (g_pfnCreateEnvironmentBlock(&pvEnvBlockProfile, hToken, FALSE /* Don't inherit from parent. */)) + { + rc = RTEnvCloneUtf16Block(phEnv, (PCRTUTF16)pvEnvBlockProfile, 0 /*fFlags*/); + if ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) + && RT_SUCCESS(rc) + && hEnv != RTENV_DEFAULT) + { + rc = RTEnvApplyChanges(*phEnv, hEnv); + if (RT_FAILURE(rc)) + RTEnvDestroy(*phEnv); + } + g_pfnDestroyEnvironmentBlock(pvEnvBlockProfile); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VERR_SYMBOL_NOT_FOUND; + } + /* + * We we've got an incoming change record, combine it with the default environment. + */ + else if (hEnv != RTENV_DEFAULT && (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)) + { + rc = RTEnvClone(phEnv, RTENV_DEFAULT); + if (RT_SUCCESS(rc)) + { + rc = RTEnvApplyChanges(*phEnv, hEnv); + if (RT_FAILURE(rc)) + RTEnvDestroy(*phEnv); + } + } + /* + * Otherwise we can return the incoming environment directly. + */ + else + { + *phEnv = hEnv; + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * Figures which privilege we're missing for success application of + * CreateProcessAsUserW. + * + * @returns IPRT error status. + */ +static int rtProcWinFigureWhichPrivilegeNotHeld2(void) +{ + HANDLE hToken; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) + { + static struct + { + const char *pszName; + int rc; + } const s_aPrivileges[] = + { + { SE_TCB_NAME, VERR_PROC_TCB_PRIV_NOT_HELD }, + { SE_ASSIGNPRIMARYTOKEN_NAME, VERR_PROC_APT_PRIV_NOT_HELD }, + { SE_INCREASE_QUOTA_NAME, VERR_PROC_IQ_PRIV_NOT_HELD }, + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_aPrivileges); i++) + { + union + { + TOKEN_PRIVILEGES TokPriv; + char abAlloced[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)]; + } uNew, uOld; + uNew.TokPriv.PrivilegeCount = 1; + uNew.TokPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + AssertContinue(LookupPrivilegeValue(NULL, s_aPrivileges[i].pszName, &uNew.TokPriv.Privileges[0].Luid)); + uOld = uNew; + SetLastError(NO_ERROR); + DWORD cbActual = RT_UOFFSETOF(TOKEN_PRIVILEGES, Privileges[1]); + AdjustTokenPrivileges(hToken, FALSE /*fDisableAllPrivileges*/, &uNew.TokPriv, cbActual, &uOld.TokPriv, &cbActual); + if (GetLastError() != NO_ERROR) + { + CloseHandle(hToken); + return s_aPrivileges[i].rc; + } + if (uOld.TokPriv.Privileges[0].Attributes == 0) + AdjustTokenPrivileges(hToken, FALSE /*fDisableAllPrivileges*/, &uOld.TokPriv, 0, NULL, NULL); + } + AssertFailed(); + CloseHandle(hToken); + } + else + AssertFailed(); + return VERR_PRIVILEGE_NOT_HELD; +} + +#if 0 /* debug code */ + +static char *rtProcWinSidToString(char *psz, PSID pSid) +{ + char *pszRet = psz; + + *psz++ = 'S'; + *psz++ = '-'; + *psz++ = '1'; + *psz++ = '-'; + + PISID pISid = (PISID)pSid; + + psz += RTStrFormatU32(psz, 32, RT_MAKE_U32_FROM_U8(pISid->IdentifierAuthority.Value[5], + pISid->IdentifierAuthority.Value[4], + pISid->IdentifierAuthority.Value[3], + pISid->IdentifierAuthority.Value[2]), + 10, 0, 0, 0); + for (unsigned i = 0; i < pISid->SubAuthorityCount; i++) + { + *psz++ = '-'; + psz += RTStrFormatU32(psz, 32, pISid->SubAuthority[i], 10, 0, 0, 0); + } + *psz++ = '\0'; + return pszRet; +} + +static void rtProcWinLogAcl(PACL pAcl) +{ + if (!pAcl) + RTAssertMsg2("ACL is NULL\n"); + else + { + RTAssertMsg2("AceCount=%d AclSize=%#x AclRevision=%d\n", pAcl->AceCount, pAcl->AclSize, pAcl->AclRevision); + for (uint32_t i = 0; i < pAcl->AceCount; i++) + { + PACE_HEADER pAceHdr = NULL; + if (GetAce(pAcl, i, (PVOID *)&pAceHdr)) + { + RTAssertMsg2(" ACE[%u]: Flags=%#x Type=%#x Size=%#x", i, pAceHdr->AceFlags, pAceHdr->AceType, pAceHdr->AceSize); + char szTmp[256]; + if (pAceHdr->AceType == ACCESS_ALLOWED_ACE_TYPE) + RTAssertMsg2(" Mask=%#x %s\n", ((ACCESS_ALLOWED_ACE *)pAceHdr)->Mask, + rtProcWinSidToString(szTmp, &((ACCESS_ALLOWED_ACE *)pAceHdr)->SidStart)); + else + RTAssertMsg2(" ACE[%u]: Flags=%#x Type=%#x Size=%#x\n", i, pAceHdr->AceFlags, pAceHdr->AceType, pAceHdr->AceSize); + } + } + } +} + +static bool rtProcWinLogSecAttr(HANDLE hUserObj) +{ + /* + * Get the security descriptor for the user interface object. + */ + uint32_t cbSecDesc = _64K; + PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; + DWORD cbNeeded; + AssertReturn(pSecDesc, false); + if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) + { + RTMemTmpFree(pSecDesc); + AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, false); + cbSecDesc = cbNeeded + 128; + pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + AssertReturn(pSecDesc, false); + if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) + { + RTMemTmpFree(pSecDesc); + AssertFailedReturn(false); + } + } + + /* + * Get the discretionary access control list (if we have one). + */ + BOOL fDaclDefaulted; + BOOL fDaclPresent; + PACL pDacl; + if (GetSecurityDescriptorDacl(pSecDesc, &fDaclPresent, &pDacl, &fDaclDefaulted)) + rtProcWinLogAcl(pDacl); + else + RTAssertMsg2("GetSecurityDescriptorDacl failed\n"); + + RTMemFree(pSecDesc); + return true; +} + +#endif /* debug */ + +/** + * Get the user SID from a token. + * + * @returns Pointer to the SID on success. Free by calling RTMemFree. + * @param hToken The token.. + * @param prc Optional return code. + */ +static PSID rtProcWinGetTokenUserSid(HANDLE hToken, int *prc) +{ + int rcIgn; + if (!prc) + prc = &rcIgn; + *prc = VERR_NO_MEMORY; + + /* + * Get the groups associated with the token. We just try a size first then + * reallocates if it's insufficient. + */ + DWORD cbUser = _1K; + PTOKEN_USER pUser = (PTOKEN_USER)RTMemTmpAlloc(cbUser); + AssertReturn(pUser, NULL); + DWORD cbNeeded = 0; + if (!GetTokenInformation(hToken, TokenUser, pUser, cbUser, &cbNeeded)) + { + DWORD dwErr = GetLastError(); + RTMemTmpFree(pUser); + AssertLogRelMsgReturnStmt(dwErr == ERROR_INSUFFICIENT_BUFFER, + ("rtProcWinGetTokenUserSid: GetTokenInformation failed with %u\n", dwErr), + *prc = RTErrConvertFromWin32(dwErr), NULL); + cbUser = cbNeeded + 128; + pUser = (PTOKEN_USER)RTMemTmpAlloc(cbUser); + AssertReturn(pUser, NULL); + if (!GetTokenInformation(hToken, TokenUser, pUser, cbUser, &cbNeeded)) + { + dwErr = GetLastError(); + *prc = RTErrConvertFromWin32(dwErr); + RTMemTmpFree(pUser); + AssertLogRelMsgFailedReturn(("rtProcWinGetTokenUserSid: GetTokenInformation failed with %u\n", dwErr), NULL); + } + } + + DWORD cbSid = GetLengthSid(pUser->User.Sid); + PSID pSidRet = RTMemDup(pUser->User.Sid, cbSid); + Assert(pSidRet); + RTMemTmpFree(pUser); + *prc = VINF_SUCCESS; + return pSidRet; +} + + +#if 0 /* not used */ +/** + * Get the login SID from a token. + * + * @returns Pointer to the SID on success. Free by calling RTMemFree. + * @param hToken The token.. + */ +static PSID rtProcWinGetTokenLogonSid(HANDLE hToken) +{ + /* + * Get the groups associated with the token. We just try a size first then + * reallocates if it's insufficient. + */ + DWORD cbGroups = _1K; + PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbGroups); + AssertReturn(pGroups, NULL); + DWORD cbNeeded = 0; + if (!GetTokenInformation(hToken, TokenGroups, pGroups, cbGroups, &cbNeeded)) + { + RTMemTmpFree(pGroups); + AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL); + cbGroups = cbNeeded + 128; + pGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbGroups); + AssertReturn(pGroups, NULL); + if (!GetTokenInformation(hToken, TokenGroups, pGroups, cbGroups, &cbNeeded)) + { + RTMemTmpFree(pGroups); + AssertFailedReturn(NULL); + } + } + + /* + * Locate the logon sid. + */ + PSID pSidRet = NULL; + uint32_t i = pGroups->GroupCount; + while (i-- > 0) + if ((pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) + { + DWORD cbSid = GetLengthSid(pGroups->Groups[i].Sid); + pSidRet = RTMemDup(pGroups->Groups[i].Sid, cbSid); + break; + } + + RTMemTmpFree(pGroups); + Assert(pSidRet); + return pSidRet; +} +#endif /* unused */ + + +/** + * Retrieves the DACL security descriptor of the give GUI object. + * + * @returns Pointer to the security descriptor. + * @param hUserObj The GUI object handle. + * @param pcbSecDesc Where to return the size of the security descriptor. + * @param ppDacl Where to return the DACL pointer. + * @param pfDaclPresent Where to return the DACL-present indicator. + * @param pDaclSizeInfo Where to return the DACL size information. + */ +static PSECURITY_DESCRIPTOR rtProcWinGetUserObjDacl(HANDLE hUserObj, uint32_t *pcbSecDesc, PACL *ppDacl, + BOOL *pfDaclPresent, ACL_SIZE_INFORMATION *pDaclSizeInfo) +{ + /* + * Get the security descriptor for the user interface object. + */ + uint32_t cbSecDesc = _1K; + PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; + DWORD cbNeeded; + AssertReturn(pSecDesc, NULL); + if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) + { + RTMemTmpFree(pSecDesc); + AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL); + cbSecDesc = cbNeeded + 128; + pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + AssertReturn(pSecDesc, NULL); + if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) + { + RTMemTmpFree(pSecDesc); + AssertFailedReturn(NULL); + } + } + *pcbSecDesc = cbNeeded; + + /* + * Get the discretionary access control list (if we have one). + */ + BOOL fDaclDefaulted; + if (GetSecurityDescriptorDacl(pSecDesc, pfDaclPresent, ppDacl, &fDaclDefaulted)) + { + RT_ZERO(*pDaclSizeInfo); + pDaclSizeInfo->AclBytesInUse = sizeof(ACL); + if ( !*ppDacl + || GetAclInformation(*ppDacl, pDaclSizeInfo, sizeof(*pDaclSizeInfo), AclSizeInformation)) + return pSecDesc; + AssertFailed(); + } + else + AssertFailed(); + RTMemTmpFree(pSecDesc); + return NULL; +} + + +/** + * Copy ACEs from one ACL to another. + * + * @returns true on success, false on failure. + * @param pDst The destination ACL. + * @param pSrc The source ACL. + * @param cAces The number of ACEs to copy. + */ +static bool rtProcWinCopyAces(PACL pDst, PACL pSrc, uint32_t cAces) +{ + for (uint32_t i = 0; i < cAces; i++) + { + PACE_HEADER pAceHdr; + AssertReturn(GetAce(pSrc, i, (PVOID *)&pAceHdr), false); + AssertReturn(AddAce(pDst, ACL_REVISION, MAXDWORD, pAceHdr, pAceHdr->AceSize), false); + } + return true; +} + + +/** + * Adds an access-allowed access control entry to an ACL. + * + * @returns true on success, false on failure. + * @param pDstAcl The ACL. + * @param fAceFlags The ACE flags. + * @param fMask The ACE access mask. + * @param pSid The SID to go with the ACE. + * @param cbSid The size of the SID. + */ +static bool rtProcWinAddAccessAllowedAce(PACL pDstAcl, uint32_t fAceFlags, uint32_t fMask, PSID pSid, uint32_t cbSid) +{ + struct + { + ACCESS_ALLOWED_ACE Core; + DWORD abPadding[128]; /* More than enough, AFAIK. */ + } AceBuf; + RT_ZERO(AceBuf); + uint32_t const cbAllowedAce = RT_UOFFSETOF(ACCESS_ALLOWED_ACE, SidStart) + cbSid; + AssertReturn(cbAllowedAce <= sizeof(AceBuf), false); + + AceBuf.Core.Header.AceSize = cbAllowedAce; + AceBuf.Core.Header.AceType = ACCESS_ALLOWED_ACE_TYPE; + AceBuf.Core.Header.AceFlags = fAceFlags; + AceBuf.Core.Mask = fMask; + AssertReturn(CopySid(cbSid, &AceBuf.Core.SidStart, pSid), false); + + uint32_t i = pDstAcl->AceCount; + while (i-- > 0) + { + PACE_HEADER pAceHdr; + AssertContinue(GetAce(pDstAcl, i, (PVOID *)&pAceHdr)); + if ( pAceHdr->AceSize == cbAllowedAce + && memcmp(pAceHdr, &AceBuf.Core, cbAllowedAce) == 0) + return true; + + } + AssertMsgReturn(AddAce(pDstAcl, ACL_REVISION, MAXDWORD, &AceBuf.Core, cbAllowedAce), ("%u\n", GetLastError()), false); + return true; +} + + +/** All window station rights we know about */ +#define MY_WINSTATION_ALL_RIGHTS ( WINSTA_ACCESSCLIPBOARD | WINSTA_ACCESSGLOBALATOMS | WINSTA_CREATEDESKTOP \ + | WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE | WINSTA_EXITWINDOWS | WINSTA_READATTRIBUTES \ + | WINSTA_READSCREEN | WINSTA_WRITEATTRIBUTES | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER ) +/** All desktop rights we know about */ +#define MY_DESKTOP_ALL_RIGHTS ( DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL \ + | DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | DESKTOP_READOBJECTS \ + | DESKTOP_SWITCHDESKTOP | DESKTOP_WRITEOBJECTS | DELETE | READ_CONTROL | WRITE_DAC \ + | WRITE_OWNER ) +/** Generic rights. */ +#define MY_GENERIC_ALL_RIGHTS ( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL ) + + +/** + * Grants the given SID full access to the given window station. + * + * @returns true on success, false on failure. + * @param hWinStation The window station. + * @param pSid The SID. + */ +static bool rtProcWinAddSidToWinStation(HWINSTA hWinStation, PSID pSid) +{ + bool fRet = false; + + /* + * Get the current DACL. + */ + uint32_t cbSecDesc; + PACL pDacl; + ACL_SIZE_INFORMATION DaclSizeInfo; + BOOL fDaclPresent; + PSECURITY_DESCRIPTOR pSecDesc = rtProcWinGetUserObjDacl(hWinStation, &cbSecDesc, &pDacl, &fDaclPresent, &DaclSizeInfo); + if (pSecDesc) + { + /* + * Create a new DACL. This will contain two extra ACEs. + */ + PSECURITY_DESCRIPTOR pNewSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + if ( pNewSecDesc + && InitializeSecurityDescriptor(pNewSecDesc, SECURITY_DESCRIPTOR_REVISION)) + { + uint32_t const cbSid = GetLengthSid(pSid); + uint32_t const cbNewDacl = DaclSizeInfo.AclBytesInUse + (sizeof(ACCESS_ALLOWED_ACE) + cbSid) * 2; + PACL pNewDacl = (PACL)RTMemTmpAlloc(cbNewDacl); + if ( pNewDacl + && InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION) + && rtProcWinCopyAces(pNewDacl, pDacl, fDaclPresent ? DaclSizeInfo.AceCount : 0)) + { + /* + * Add the two new SID ACEs. + */ + if ( rtProcWinAddAccessAllowedAce(pNewDacl, CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE, + MY_GENERIC_ALL_RIGHTS, pSid, cbSid) + && rtProcWinAddAccessAllowedAce(pNewDacl, NO_PROPAGATE_INHERIT_ACE, MY_WINSTATION_ALL_RIGHTS, pSid, cbSid)) + { + /* + * Now mate the new DECL with the security descriptor and set it. + */ + if (SetSecurityDescriptorDacl(pNewSecDesc, TRUE /*fDaclPresent*/, pNewDacl, FALSE /*fDaclDefaulted*/)) + { + SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; + if (SetUserObjectSecurity(hWinStation, &SecInfo, pNewSecDesc)) + fRet = true; + else + AssertFailed(); + } + else + AssertFailed(); + } + else + AssertFailed(); + } + else + AssertFailed(); + RTMemTmpFree(pNewDacl); + } + else + AssertFailed(); + RTMemTmpFree(pNewSecDesc); + RTMemTmpFree(pSecDesc); + } + return fRet; +} + + +/** + * Grants the given SID full access to the given desktop. + * + * @returns true on success, false on failure. + * @param hDesktop The desktop handle. + * @param pSid The SID. + */ +static bool rtProcWinAddSidToDesktop(HDESK hDesktop, PSID pSid) +{ + bool fRet = false; + + /* + * Get the current DACL. + */ + uint32_t cbSecDesc; + PACL pDacl; + ACL_SIZE_INFORMATION DaclSizeInfo; + BOOL fDaclPresent; + PSECURITY_DESCRIPTOR pSecDesc = rtProcWinGetUserObjDacl(hDesktop, &cbSecDesc, &pDacl, &fDaclPresent, &DaclSizeInfo); + if (pSecDesc) + { + /* + * Create a new DACL. This will contain one extra ACE. + */ + PSECURITY_DESCRIPTOR pNewSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + if ( pNewSecDesc + && InitializeSecurityDescriptor(pNewSecDesc, SECURITY_DESCRIPTOR_REVISION)) + { + uint32_t const cbSid = GetLengthSid(pSid); + uint32_t const cbNewDacl = DaclSizeInfo.AclBytesInUse + (sizeof(ACCESS_ALLOWED_ACE) + cbSid) * 1; + PACL pNewDacl = (PACL)RTMemTmpAlloc(cbNewDacl); + if ( pNewDacl + && InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION) + && rtProcWinCopyAces(pNewDacl, pDacl, fDaclPresent ? DaclSizeInfo.AceCount : 0)) + { + /* + * Add the new SID ACE. + */ + if (rtProcWinAddAccessAllowedAce(pNewDacl, 0 /*fAceFlags*/, MY_DESKTOP_ALL_RIGHTS, pSid, cbSid)) + { + /* + * Now mate the new DECL with the security descriptor and set it. + */ + if (SetSecurityDescriptorDacl(pNewSecDesc, TRUE /*fDaclPresent*/, pNewDacl, FALSE /*fDaclDefaulted*/)) + { + SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; + if (SetUserObjectSecurity(hDesktop, &SecInfo, pNewSecDesc)) + fRet = true; + else + AssertFailed(); + } + else + AssertFailed(); + } + else + AssertFailed(); + } + else + AssertFailed(); + RTMemTmpFree(pNewDacl); + } + else + AssertFailed(); + RTMemTmpFree(pNewSecDesc); + RTMemTmpFree(pSecDesc); + } + return fRet; +} + + +/** + * Preps the window station and desktop for the new app. + * + * EXPERIMENTAL. Thus no return code. + * + * @param hTokenToUse The access token of the new process. + * @param pStartupInfo The startup info (we'll change lpDesktop, maybe). + * @param phWinStationOld Where to return an window station handle to restore. + * Pass this to SetProcessWindowStation if not NULL. + */ +static void rtProcWinStationPrep(HANDLE hTokenToUse, STARTUPINFOW *pStartupInfo, HWINSTA *phWinStationOld) +{ + /** @todo Always mess with the interactive one? Maybe it's not there... */ + *phWinStationOld = GetProcessWindowStation(); + HWINSTA hWinStation0; + if (g_pfnOpenWindowStationW) + hWinStation0 = g_pfnOpenWindowStationW(L"winsta0", FALSE /*fInherit*/, READ_CONTROL | WRITE_DAC); + else + hWinStation0 = OpenWindowStationA("winsta0", FALSE /*fInherit*/, READ_CONTROL | WRITE_DAC); /* (for NT3.1) */ + if (hWinStation0) + { + if (SetProcessWindowStation(hWinStation0)) + { + HDESK hDesktop = OpenDesktop("default", 0 /*fFlags*/, FALSE /*fInherit*/, + READ_CONTROL | WRITE_DAC | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS); + if (hDesktop) + { + /*PSID pSid = rtProcWinGetTokenLogonSid(hTokenToUse); - Better to use the user SID. Avoid overflowing the ACL. */ + PSID pSid = rtProcWinGetTokenUserSid(hTokenToUse, NULL /*prc*/); + if (pSid) + { + if ( rtProcWinAddSidToWinStation(hWinStation0, pSid) + && rtProcWinAddSidToDesktop(hDesktop, pSid)) + { + pStartupInfo->lpDesktop = L"winsta0\\default"; + } + RTMemFree(pSid); + } + CloseDesktop(hDesktop); + } + else + AssertFailed(); + } + else + AssertFailed(); + if (g_pfnCloseWindowStation) + g_pfnCloseWindowStation(hWinStation0); + } + else + AssertFailed(); +} + + +/** + * Extracts the user name + domain from a given UPN (User Principal Name, "joedoe@example.com") or + * Down-Level Logon Name format ("example.com\\joedoe") string. + * + * @return IPRT status code. + * @param pwszString Pointer to string to extract the account info from. + * @param pAccountInfo Where to store the parsed account info. + * Must be free'd with rtProcWinFreeAccountInfo(). + */ +static int rtProcWinParseAccountInfo(PRTUTF16 pwszString, PRTPROCWINACCOUNTINFO pAccountInfo) +{ + AssertPtrReturn(pwszString, VERR_INVALID_POINTER); + AssertPtrReturn(pAccountInfo, VERR_INVALID_POINTER); + + /* + * Note: UPN handling is defined in RFC 822. We only implement very rudimentary parsing for the user + * name and domain fields though. + */ + char *pszString; + int rc = RTUtf16ToUtf8(pwszString, &pszString); + if (RT_SUCCESS(rc)) + { + do + { + /* UPN or FQDN handling needed? */ + /** @todo Add more validation here as needed. Regular expressions would be nice. */ + char *pszDelim = strchr(pszString, '@'); + if (pszDelim) /* UPN name? */ + { + rc = RTStrToUtf16Ex(pszString, pszDelim - pszString, &pAccountInfo->pwszUserName, 0, NULL); + if (RT_FAILURE(rc)) + break; + + rc = RTStrToUtf16Ex(pszDelim + 1, RTSTR_MAX, &pAccountInfo->pwszDomain, 0, NULL); + if (RT_FAILURE(rc)) + break; + } + else if (pszDelim = strchr(pszString, '\\')) /* FQDN name? */ + { + rc = RTStrToUtf16Ex(pszString, pszDelim - pszString, &pAccountInfo->pwszDomain, 0, NULL); + if (RT_FAILURE(rc)) + break; + + rc = RTStrToUtf16Ex(pszDelim + 1, RTSTR_MAX, &pAccountInfo->pwszUserName, 0, NULL); + if (RT_FAILURE(rc)) + break; + } + else + rc = VERR_NOT_SUPPORTED; + + } while (0); + + RTStrFree(pszString); + } + +#ifdef DEBUG + LogRelFunc(("Name : %ls\n", pAccountInfo->pwszUserName)); + LogRelFunc(("Domain: %ls\n", pAccountInfo->pwszDomain)); +#endif + + if (RT_FAILURE(rc)) + LogRelFunc(("Parsing \"%ls\" failed with rc=%Rrc\n", pwszString, rc)); + return rc; +} + + +static void rtProcWinFreeAccountInfo(PRTPROCWINACCOUNTINFO pAccountInfo) +{ + if (!pAccountInfo) + return; + + if (pAccountInfo->pwszUserName) + { + RTUtf16Free(pAccountInfo->pwszUserName); + pAccountInfo->pwszUserName = NULL; + } + + if (pAccountInfo->pwszDomain) + { + RTUtf16Free(pAccountInfo->pwszDomain); + pAccountInfo->pwszDomain = NULL; + } +} + + +/** + * Tries to resolve the name of the SID. + * + * @returns IPRT status code. + * @param pSid The SID to resolve. + * @param ppwszName Where to return the name. Use RTUtf16Free to free. + */ +static int rtProcWinSidToName(PSID pSid, PRTUTF16 *ppwszName) +{ + *ppwszName = NULL; + + /* + * Use large initial buffers here to try avoid having to repeat the call. + */ + DWORD cwcAllocated = 512; + while (cwcAllocated < _32K) + { + PRTUTF16 pwszName = RTUtf16Alloc(cwcAllocated * sizeof(RTUTF16)); + AssertReturn(pwszName, VERR_NO_UTF16_MEMORY); + PRTUTF16 pwszDomain = RTUtf16Alloc(cwcAllocated * sizeof(RTUTF16)); + AssertReturnStmt(pwszDomain, RTUtf16Free(pwszName), VERR_NO_UTF16_MEMORY); + + DWORD cwcName = cwcAllocated; + DWORD cwcDomain = cwcAllocated; + SID_NAME_USE SidNameUse = SidTypeUser; + if (LookupAccountSidW(NULL /*lpSystemName*/, pSid, pwszName, &cwcName, pwszDomain, &cwcDomain, &SidNameUse)) + { + *ppwszName = pwszName; + RTUtf16Free(pwszDomain); /* may need this later. */ + return VINF_SUCCESS; + } + + DWORD const dwErr = GetLastError(); + RTUtf16Free(pwszName); + RTUtf16Free(pwszDomain); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + return RTErrConvertFromWin32(dwErr); + cwcAllocated = RT_MAX(cwcName, cwcDomain) + 1; + } + + return RTErrConvertFromWin32(ERROR_INSUFFICIENT_BUFFER); +} + + +/** + * Tries to resolve the user name for the token. + * + * @returns IPRT status code. + * @param hToken The token. + * @param ppwszUser Where to return the username. Use RTUtf16Free to free. + */ +static int rtProcWinTokenToUsername(HANDLE hToken, PRTUTF16 *ppwszUser) +{ + int rc = VINF_SUCCESS; + PSID pSid = rtProcWinGetTokenUserSid(hToken, &rc); + if (pSid) + { + rc = rtProcWinSidToName(pSid, ppwszUser); + RTMemFree(pSid); + } + else + *ppwszUser = NULL; + return rc; +} + + +/** + * Method \#2. + * + * @note pwszUser can be NULL when RTPROC_FLAGS_AS_IMPERSONATED_TOKEN is set. + */ +static int rtProcWinCreateAsUser2(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 *ppwszExec, PRTUTF16 pwszCmdLine, + RTENV hEnv, DWORD dwCreationFlags, + STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, + uint32_t fFlags, const char *pszExec, uint32_t idDesiredSession, + HANDLE hUserToken) +{ + /* + * So if we want to start a process from a service (RTPROC_FLAGS_SERVICE), + * we have to do the following: + * - Check the credentials supplied and get the user SID. + * - If valid get the correct Explorer/VBoxTray instance corresponding to that + * user. This of course is only possible if that user is logged in (over + * physical console or terminal services). + * - If we found the user's Explorer/VBoxTray app, use and modify the token to + * use it in order to allow the newly started process to access the user's + * desktop. If there's no Explorer/VBoxTray app we cannot display the started + * process (but run it without UI). + * + * The following restrictions apply: + * - A process only can show its UI when the user the process should run + * under is logged in (has a desktop). + * - We do not want to display a process of user A run on the desktop + * of user B on multi session systems. + * + * The following rights are needed in order to use LogonUserW and + * CreateProcessAsUserW, so the local policy has to be modified to: + * - SE_TCB_NAME = Act as part of the operating system + * - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a (process) token object + * - SE_INCREASE_QUOTA_NAME = Increase quotas + * + * We may fail here with ERROR_PRIVILEGE_NOT_HELD. + */ + DWORD dwErr = NO_ERROR; + HANDLE hTokenLogon = INVALID_HANDLE_VALUE; + int rc = VINF_SUCCESS; + if (fFlags & RTPROC_FLAGS_TOKEN_SUPPLIED) + hTokenLogon = hUserToken; + else if (fFlags & RTPROC_FLAGS_AS_IMPERSONATED_TOKEN) + rc = rtProcWinGetThreadTokenHandle(GetCurrentThread(), &hTokenLogon); + else if (pwszUser == NULL) + rc = rtProcWinGetProcessTokenHandle(GetCurrentProcess(), &hTokenLogon); + else + rc = rtProcWinUserLogon(pwszUser, pwszPassword, &hTokenLogon); + if (RT_SUCCESS(rc)) + { + BOOL fRc; + bool fFound = false; + HANDLE hTokenUserDesktop = INVALID_HANDLE_VALUE; + + /* + * If the SERVICE flag is specified, we do something rather ugly to + * make things work at all. We search for a known desktop process + * belonging to the user, grab its token and use it for launching + * the new process. That way the process will have desktop access. + */ + if (fFlags & RTPROC_FLAGS_SERVICE) + { + /* + * For the token search we need a SID. + */ + PSID pSid = rtProcWinGetTokenUserSid(hTokenLogon, &rc); + + /* + * If we got a valid SID, search the running processes. + */ + /* + * If we got a valid SID, search the running processes. + */ + if (pSid) + { + if (IsValidSid(pSid)) + { + /* Array of process names we want to look for. */ + static const char * const s_papszProcNames[] = + { +#ifdef VBOX /* The explorer entry is a fallback in case GA aren't installed. */ + { "VBoxTray.exe" }, +# ifndef IN_GUEST + { "VirtualBox.exe" }, +# endif +#endif + { "explorer.exe" }, + NULL + }; + fFound = rtProcWinFindTokenByProcess(s_papszProcNames, pSid, idDesiredSession, &hTokenUserDesktop); + dwErr = 0; + } + else + { + dwErr = GetLastError(); + LogRelFunc(("SID is invalid: %ld\n", dwErr)); + rc = dwErr != NO_ERROR ? RTErrConvertFromWin32(dwErr) : VERR_INTERNAL_ERROR_3; + } + + RTMemFree(pSid); + } + } + /* else: !RTPROC_FLAGS_SERVICE: Nothing to do here right now. */ + +#if 0 + /* + * If we make LogonUserW to return an impersonation token, enable this + * to convert it into a primary token. + */ + if (!fFound && detect-impersonation-token) + { + HANDLE hNewToken; + if (DuplicateTokenEx(hTokenLogon, MAXIMUM_ALLOWED, NULL /*SecurityAttribs*/, + SecurityIdentification, TokenPrimary, &hNewToken)) + { + CloseHandle(hTokenLogon); + hTokenLogon = hNewToken; + } + else + AssertMsgFailed(("%d\n", GetLastError())); + } +#endif + + if (RT_SUCCESS(rc)) + { + /* + * If we didn't find a matching VBoxTray, just use the token we got + * above from LogonUserW(). This enables us to at least run processes + * with desktop interaction without UI. + */ + HANDLE hTokenToUse = fFound ? hTokenUserDesktop : hTokenLogon; + if ( !(fFlags & RTPROC_FLAGS_PROFILE) + || (g_pfnUnloadUserProfile && g_pfnLoadUserProfileW) ) + { + /* + * Load the profile, if requested. (Must be done prior to creating the enviornment.) + * + * Note! We don't have sufficient rights when impersonating a user, but we can + * ASSUME the user is logged on and has its profile loaded into HKEY_USERS already. + */ + PROFILEINFOW ProfileInfo; + PRTUTF16 pwszUserFree = NULL; + RT_ZERO(ProfileInfo); + /** @todo r=bird: We probably don't need to load anything if pwszUser is NULL... */ + if ((fFlags & (RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_AS_IMPERSONATED_TOKEN)) == RTPROC_FLAGS_PROFILE) + { + if (!pwszUser) + { + Assert(fFlags & RTPROC_FLAGS_AS_IMPERSONATED_TOKEN); + rc = rtProcWinTokenToUsername(hTokenToUse, &pwszUserFree); + pwszUser = pwszUserFree; + } + if (RT_SUCCESS(rc)) + { + ProfileInfo.dwSize = sizeof(ProfileInfo); + ProfileInfo.dwFlags = PI_NOUI; /* Prevents the display of profile error messages. */ + ProfileInfo.lpUserName = pwszUser; + if (!g_pfnLoadUserProfileW(hTokenToUse, &ProfileInfo)) + rc = RTErrConvertFromWin32(GetLastError()); + } + } + if (RT_SUCCESS(rc)) + { + /* + * Create the environment. + */ + RTENV hEnvFinal; + rc = rtProcWinCreateEnvFromToken(hTokenToUse, hEnv, fFlags, &hEnvFinal); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszzBlock; + rc = RTEnvQueryUtf16Block(hEnvFinal, &pwszzBlock); + if (RT_SUCCESS(rc)) + { + rc = rtProcWinFindExe(fFlags, hEnv, pszExec, ppwszExec); + if (RT_SUCCESS(rc)) + { + HWINSTA hOldWinStation = NULL; + if ( !fFound + && g_enmWinVer <= kRTWinOSType_NT4) /** @todo test newer versions... */ + rtProcWinStationPrep(hTokenToUse, pStartupInfo, &hOldWinStation); + + /* + * Useful KB articles: + * http://support.microsoft.com/kb/165194/ + * http://support.microsoft.com/kb/184802/ + * http://support.microsoft.com/kb/327618/ + */ + if (g_pfnCreateProcessAsUserW) + { + fRc = g_pfnCreateProcessAsUserW(hTokenToUse, + *ppwszExec, + pwszCmdLine, + NULL, /* pProcessAttributes */ + NULL, /* pThreadAttributes */ + TRUE, /* fInheritHandles */ + dwCreationFlags, + /** @todo Warn about exceeding 8192 bytes + * on XP and up. */ + pwszzBlock, /* lpEnvironment */ + NULL, /* pCurrentDirectory */ + pStartupInfo, + pProcInfo); + if (fRc) + rc = VINF_SUCCESS; + else + { + dwErr = GetLastError(); + if (dwErr == ERROR_PRIVILEGE_NOT_HELD) + rc = rtProcWinFigureWhichPrivilegeNotHeld2(); + else + rc = RTErrConvertFromWin32(dwErr); + } + } + else + rc = VERR_NOT_SUPPORTED; + + if (hOldWinStation) + SetProcessWindowStation(hOldWinStation); + } + RTEnvFreeUtf16Block(pwszzBlock); + } + + if (hEnvFinal != hEnv) + RTEnvDestroy(hEnvFinal); + } + + if ((fFlags & RTPROC_FLAGS_PROFILE) && ProfileInfo.hProfile) + { + fRc = g_pfnUnloadUserProfile(hTokenToUse, ProfileInfo.hProfile); +#ifdef RT_STRICT + if (!fRc) + { + DWORD dwErr2 = GetLastError(); + AssertMsgFailed(("Unloading user profile failed with error %u (%#x) - Are all handles closed? (dwErr=%u)", + dwErr2, dwErr2, dwErr)); + } +#endif + } + if (pwszUserFree) + RTUtf16Free(pwszUserFree); + } + } + else + rc = VERR_SYMBOL_NOT_FOUND; + } /* Account lookup succeeded? */ + + if (hTokenUserDesktop != INVALID_HANDLE_VALUE) + CloseHandle(hTokenUserDesktop); + if ( !(fFlags & RTPROC_FLAGS_TOKEN_SUPPLIED) + && hTokenLogon != INVALID_HANDLE_VALUE) + CloseHandle(hTokenLogon); + + if (rc == VERR_UNRESOLVED_ERROR) + LogRelFunc(("dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc)); + } + + return rc; +} + + +/** + * Plants a standard handle into a child process on older windows versions. + * + * This is only needed when using CreateProcessWithLogonW on older windows + * versions. It would appear that newer versions of windows does this for us. + * + * @param hSrcHandle The source handle. + * @param hDstProcess The child process handle. + * @param offProcParamMember The offset to RTL_USER_PROCESS_PARAMETERS. + * @param ppvDstProcParamCache Where where cached the address of + * RTL_USER_PROCESS_PARAMETERS in the child. + */ +static void rtProcWinDupStdHandleIntoChild(HANDLE hSrcHandle, HANDLE hDstProcess, uint32_t offProcParamMember, + PVOID *ppvDstProcParamCache) +{ + if (hSrcHandle != NULL && hSrcHandle != INVALID_HANDLE_VALUE) + { + HANDLE hDstHandle; + if (DuplicateHandle(GetCurrentProcess(), hSrcHandle, hDstProcess, &hDstHandle, + 0 /*IgnoredDesiredAccess*/, FALSE /*fInherit*/, DUPLICATE_SAME_ACCESS)) + { + if (hSrcHandle == hDstHandle) + return; + + if (!*ppvDstProcParamCache) + { + PROCESS_BASIC_INFORMATION BasicInfo; + ULONG cbIgn; + NTSTATUS rcNt = NtQueryInformationProcess(hDstProcess, ProcessBasicInformation, + &BasicInfo, sizeof(BasicInfo), &cbIgn); + if (NT_SUCCESS(rcNt)) + { + SIZE_T cbCopied = 0; + if (!ReadProcessMemory(hDstProcess, + (char *)BasicInfo.PebBaseAddress + RT_UOFFSETOF(PEB_COMMON, ProcessParameters), + ppvDstProcParamCache, sizeof(*ppvDstProcParamCache), &cbCopied)) + { + AssertMsgFailed(("PebBaseAddress=%p %d\n", BasicInfo.PebBaseAddress, GetLastError())); + *ppvDstProcParamCache = NULL; + } + } + else + AssertMsgFailed(("rcNt=%#x\n", rcNt)); + } + if (*ppvDstProcParamCache) + { + if (WriteProcessMemory(hDstProcess, (char *)*ppvDstProcParamCache + offProcParamMember, + &hDstHandle, sizeof(hDstHandle), NULL)) + return; + } + + /* + * Close the handle. + */ + HANDLE hSrcHandle2; + if (DuplicateHandle(hDstProcess, hDstHandle, GetCurrentProcess(), &hSrcHandle2, + 0 /*IgnoredDesiredAccess*/, FALSE /*fInherit*/, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) + CloseHandle(hSrcHandle2); + else + AssertMsgFailed(("hDstHandle=%p %u\n", hDstHandle, GetLastError())); + } + else + AssertMsg(GetLastError() == ERROR_INVALID_PARAMETER, ("%u\n", GetLastError())); + } +} + + +/** + * Method \#1. + * + * This method requires Windows 2000 or later. It may fail if the process is + * running under the SYSTEM account (like a service, ERROR_ACCESS_DENIED) on + * newer platforms (however, this works on W2K!). + */ +static int rtProcWinCreateAsUser1(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 *ppwszExec, PRTUTF16 pwszCmdLine, + RTENV hEnv, DWORD dwCreationFlags, + STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, + uint32_t fFlags, const char *pszExec) +{ + /* The CreateProcessWithLogonW API was introduced with W2K and later. It uses a service + for launching the process. */ + if (!g_pfnCreateProcessWithLogonW) + return VERR_SYMBOL_NOT_FOUND; + + /* + * Create the environment block and find the executable first. + * + * We try to skip this when RTPROC_FLAGS_PROFILE is set so we can sidestep + * potential missing TCB privilege issues when calling UserLogonW. At least + * NT4 and W2K requires the trusted code base (TCB) privilege for logon use. + * Passing pwszzBlock=NULL and LOGON_WITH_PROFILE means the child process + * gets the environment specified by the user profile. + */ + int rc; + PRTUTF16 pwszzBlock = NULL; + + /* Eliminating the path search flags simplifies things a little. */ + if ( (fFlags & RTPROC_FLAGS_SEARCH_PATH) + && (RTPathHasPath(pszExec) || RTPathExists(pszExec))) + fFlags &= ~RTPROC_FLAGS_SEARCH_PATH; + + /* + * No profile is simple, as is a user specified environment (no change record). + */ + if ( !(fFlags & RTPROC_FLAGS_PROFILE) + || ( !(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) + && hEnv != RTENV_DEFAULT)) + rc = rtProcWinCreateEnvBlockAndFindExe(fFlags, hEnv, pszExec, &pwszzBlock, ppwszExec); + /* + * Default profile environment without changes or path searching we leave + * to the service that implements the API. + */ + else if ( hEnv == RTENV_DEFAULT + && !(fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_SEARCH_PATH))) + { + pwszzBlock = NULL; + rc = VINF_SUCCESS; + } + /* + * Otherwise, we need to get the user profile environment. + */ + else + { + RTENV hEnvToUse = NIL_RTENV; + HANDLE hTokenLogon = INVALID_HANDLE_VALUE; + rc = rtProcWinUserLogon(pwszUser, pwszPassword, &hTokenLogon); + if (RT_SUCCESS(rc)) + { + /* CreateEnvFromToken docs says we should load the profile, though + we haven't observed any difference when not doing it. Maybe it's + only an issue with roaming profiles or something similar... */ + PROFILEINFOW ProfileInfo; + RT_ZERO(ProfileInfo); + ProfileInfo.dwSize = sizeof(ProfileInfo); + ProfileInfo.lpUserName = pwszUser; + ProfileInfo.dwFlags = PI_NOUI; /* Prevents the display of profile error messages. */ + + if (g_pfnLoadUserProfileW(hTokenLogon, &ProfileInfo)) + { + /* + * Do what we need to do. Don't keep any temp environment object. + */ + rc = rtProcWinCreateEnvFromToken(hTokenLogon, hEnv, fFlags, &hEnvToUse); + if (RT_SUCCESS(rc)) + { + rc = rtProcWinFindExe(fFlags, hEnv, pszExec, ppwszExec); + if (RT_SUCCESS(rc)) + rc = RTEnvQueryUtf16Block(hEnvToUse, &pwszzBlock); + if (hEnvToUse != hEnv) + RTEnvDestroy(hEnvToUse); + } + + if (!g_pfnUnloadUserProfile(hTokenLogon, ProfileInfo.hProfile)) + AssertFailed(); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + if (hTokenLogon != INVALID_HANDLE_VALUE) + CloseHandle(hTokenLogon); + } + } + if (RT_SUCCESS(rc)) + { + /* + * Create the process. + */ + Assert(!(dwCreationFlags & CREATE_SUSPENDED)); + bool const fCreatedSuspended = g_enmWinVer < kRTWinOSType_XP; + BOOL fRc = g_pfnCreateProcessWithLogonW(pwszUser, + NULL, /* lpDomain*/ + pwszPassword, + fFlags & RTPROC_FLAGS_PROFILE ? 1 /*LOGON_WITH_PROFILE*/ : 0, + *ppwszExec, + pwszCmdLine, + dwCreationFlags | (fCreatedSuspended ? CREATE_SUSPENDED : 0), + pwszzBlock, + NULL, /* pCurrentDirectory */ + pStartupInfo, + pProcInfo); + if (fRc) + { + if (!fCreatedSuspended) + rc = VINF_SUCCESS; + else + { + /* + * Duplicate standard handles into the child process, we ignore failures here as it's + * legal to have bad standard handle values and we cannot dup console I/O handles.* + */ + PVOID pvDstProcParamCache = NULL; + rtProcWinDupStdHandleIntoChild(pStartupInfo->hStdInput, pProcInfo->hProcess, + RT_UOFFSETOF(RTL_USER_PROCESS_PARAMETERS, StandardInput), &pvDstProcParamCache); + rtProcWinDupStdHandleIntoChild(pStartupInfo->hStdOutput, pProcInfo->hProcess, + RT_UOFFSETOF(RTL_USER_PROCESS_PARAMETERS, StandardOutput), &pvDstProcParamCache); + rtProcWinDupStdHandleIntoChild(pStartupInfo->hStdError, pProcInfo->hProcess, + RT_UOFFSETOF(RTL_USER_PROCESS_PARAMETERS, StandardError), &pvDstProcParamCache); + + if (ResumeThread(pProcInfo->hThread) != ~(DWORD)0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + if (RT_FAILURE(rc)) + { + TerminateProcess(pProcInfo->hProcess, 127); + CloseHandle(pProcInfo->hThread); + CloseHandle(pProcInfo->hProcess); + } + } + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + if (rc == VERR_UNRESOLVED_ERROR) + LogRelFunc(("CreateProcessWithLogonW failed: dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc)); + } + if (pwszzBlock) + RTEnvFreeUtf16Block(pwszzBlock); + } + return rc; +} + + +static int rtProcWinCreateAsUser(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 *ppwszExec, PRTUTF16 pwszCmdLine, + RTENV hEnv, DWORD dwCreationFlags, + STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, + uint32_t fFlags, const char *pszExec, uint32_t idDesiredSession, + HANDLE hUserToken) +{ + /* + * If we run as a service CreateProcessWithLogon will fail, so don't even + * try it (because of Local System context). If we got an impersonated token + * we should use, we also have to have to skip over this approach. + * Note! This method is very slow on W2K. + */ + if (!(fFlags & (RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_AS_IMPERSONATED_TOKEN | RTPROC_FLAGS_TOKEN_SUPPLIED))) + { + AssertPtr(pwszUser); + int rc = rtProcWinCreateAsUser1(pwszUser, pwszPassword, ppwszExec, pwszCmdLine, + hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags, pszExec); + if (RT_SUCCESS(rc)) + return rc; + } + return rtProcWinCreateAsUser2(pwszUser, pwszPassword, ppwszExec, pwszCmdLine, hEnv, dwCreationFlags, + pStartupInfo, pProcInfo, fFlags, pszExec, idDesiredSession, hUserToken); +} + + +/** + * RTPathTraverseList callback used by rtProcWinFindExe to locate the + * executable. + */ +static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2) +{ + const char *pszExec = (const char *)pvUser1; + char *pszRealExec = (char *)pvUser2; + int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX, RTPATH_STR_F_STYLE_HOST); + if (RT_FAILURE(rc)) + return rc; + if (RTFileExists(pszRealExec)) + return VINF_SUCCESS; + return VERR_TRY_AGAIN; +} + + +/** + * Locate the executable file if necessary. + * + * @returns IPRT status code. + * @param pszExec The UTF-8 executable string passed in by the user. + * @param fFlags The process creation flags pass in by the user. + * @param hEnv The environment to get the path variabel from. + * @param ppwszExec Pointer to the variable pointing to the UTF-16 + * converted string. If we find something, the current + * pointer will be free (RTUtf16Free) and + * replaced by a new one. + */ +static int rtProcWinFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec, PRTUTF16 *ppwszExec) +{ + /* + * Return immediately if we're not asked to search, or if the file has a + * path already or if it actually exists in the current directory. + */ + if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH) + || RTPathHavePath(pszExec) + || RTPathExists(pszExec) ) + return VINF_SUCCESS; + + /* + * Search the Path or PATH variable for the file. + */ + char *pszPath; + if (RTEnvExistEx(hEnv, "PATH")) + pszPath = RTEnvDupEx(hEnv, "PATH"); + else if (RTEnvExistEx(hEnv, "Path")) + pszPath = RTEnvDupEx(hEnv, "Path"); + else + return VERR_FILE_NOT_FOUND; + + char szRealExec[RTPATH_MAX]; + int rc = RTPathTraverseList(pszPath, ';', rtPathFindExec, (void *)pszExec, &szRealExec[0]); + RTStrFree(pszPath); + if (RT_SUCCESS(rc)) + { + /* + * Replace the executable string. + */ + RTPathWinFree(*ppwszExec); + *ppwszExec = NULL; + rc = RTPathWinFromUtf8(ppwszExec, szRealExec, 0 /*fFlags*/); + } + else if (rc == VERR_END_OF_STRING) + rc = VERR_FILE_NOT_FOUND; + return rc; +} + + +/** + * Creates the UTF-16 environment block and, if necessary, find the executable. + * + * @returns IPRT status code. + * @param fFlags The process creation flags pass in by the user. + * @param hEnv The environment handle passed by the user. + * @param pszExec See rtProcWinFindExe. + * @param ppwszzBlock Where RTEnvQueryUtf16Block returns the block. + * @param ppwszExec See rtProcWinFindExe. + */ +static int rtProcWinCreateEnvBlockAndFindExe(uint32_t fFlags, RTENV hEnv, const char *pszExec, + PRTUTF16 *ppwszzBlock, PRTUTF16 *ppwszExec) +{ + int rc; + + /* + * In most cases, we just need to convert the incoming enviornment to a + * UTF-16 environment block. + */ + RTENV hEnvToUse = NIL_RTENV; /* (MSC maybe used uninitialized) */ + if ( !(fFlags & (RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_ENV_CHANGE_RECORD)) + || (hEnv == RTENV_DEFAULT && !(fFlags & RTPROC_FLAGS_PROFILE)) + || (hEnv != RTENV_DEFAULT && !(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)) ) + { + hEnvToUse = hEnv; + rc = VINF_SUCCESS; + } + else if (fFlags & RTPROC_FLAGS_PROFILE) + { + /* + * We need to get the profile environment for the current user. + */ + Assert((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || hEnv == RTENV_DEFAULT); + AssertReturn(g_pfnCreateEnvironmentBlock && g_pfnDestroyEnvironmentBlock, VERR_SYMBOL_NOT_FOUND); + AssertReturn(g_pfnLoadUserProfileW && g_pfnUnloadUserProfile, VERR_SYMBOL_NOT_FOUND); + HANDLE hToken; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &hToken)) + { + rc = rtProcWinCreateEnvFromToken(hToken, hEnv, fFlags, &hEnvToUse); + CloseHandle(hToken); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + /* + * Apply hEnv as a change record on top of the default environment. + */ + Assert(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD); + rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT); + if (RT_SUCCESS(rc)) + { + rc = RTEnvApplyChanges(hEnvToUse, hEnv); + if (RT_FAILURE(rc)) + RTEnvDestroy(hEnvToUse); + } + } + if (RT_SUCCESS(rc)) + { + /* + * Query the UTF-16 environment block and locate the executable (if needed). + */ + rc = RTEnvQueryUtf16Block(hEnvToUse, ppwszzBlock); + if (RT_SUCCESS(rc)) + rc = rtProcWinFindExe(fFlags, hEnvToUse, pszExec, ppwszExec); + + if (hEnvToUse != hEnv) + RTEnvDestroy(hEnvToUse); + } + + return rc; +} + + +RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, + PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser, + const char *pszPassword, void *pvExtraData, PRTPROCESS phProcess) +{ + /* + * Input validation + */ + AssertPtrReturn(pszExec, VERR_INVALID_POINTER); + AssertReturn(*pszExec, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER); + AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER); + AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER); + AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER); + AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER); + + /* Extra data: */ + uint32_t idDesiredSession = UINT32_MAX; + if ( (fFlags & (RTPROC_FLAGS_DESIRED_SESSION_ID | RTPROC_FLAGS_SERVICE)) + == (RTPROC_FLAGS_DESIRED_SESSION_ID | RTPROC_FLAGS_SERVICE)) + { + AssertPtrReturn(pvExtraData, VERR_INVALID_POINTER); + idDesiredSession = *(uint32_t *)pvExtraData; + } + else + AssertReturn(!(fFlags & RTPROC_FLAGS_DESIRED_SESSION_ID), VERR_INVALID_FLAGS); + + HANDLE hUserToken = NULL; + if (fFlags & RTPROC_FLAGS_TOKEN_SUPPLIED) + hUserToken = *(HANDLE *)pvExtraData; + + /* + * Initialize the globals. + */ + int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL); + AssertRCReturn(rc, rc); + if ( pszAsUser + || (fFlags & (RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_AS_IMPERSONATED_TOKEN + | RTPROC_FLAGS_TOKEN_SUPPLIED))) + { + rc = RTOnce(&g_rtProcWinResolveOnce, rtProcWinResolveOnce, NULL); + AssertRCReturn(rc, rc); + } + + /* + * Get the file descriptors for the handles we've been passed. + * + * It seems there is no point in trying to convince a child process's CRT + * that any of the standard file handles is non-TEXT. So, we don't... + */ + STARTUPINFOW StartupInfo; + RT_ZERO(StartupInfo); + StartupInfo.cb = sizeof(StartupInfo); + StartupInfo.dwFlags = STARTF_USESTDHANDLES; +#if 1 /* The CRT should keep the standard handles up to date. */ + StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); +#else + StartupInfo.hStdInput = _get_osfhandle(0); + StartupInfo.hStdOutput = _get_osfhandle(1); + StartupInfo.hStdError = _get_osfhandle(2); +#endif + /* If we want to have a hidden process (e.g. not visible to + * to the user) use the STARTUPINFO flags. */ + if (fFlags & RTPROC_FLAGS_HIDDEN) + { + StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; + StartupInfo.wShowWindow = SW_HIDE; + } + + PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr }; + HANDLE *aphStds[3] = { &StartupInfo.hStdInput, &StartupInfo.hStdOutput, &StartupInfo.hStdError }; + DWORD afInhStds[3] = { 0xffffffff, 0xffffffff, 0xffffffff }; + HANDLE ahStdDups[3] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; + for (int i = 0; i < 3; i++) + { + if (paHandles[i]) + { + AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER); + switch (paHandles[i]->enmType) + { + case RTHANDLETYPE_FILE: + { + HANDLE hNativeFile = paHandles[i]->u.hFile != NIL_RTFILE + ? (HANDLE)RTFileToNative(paHandles[i]->u.hFile) + : INVALID_HANDLE_VALUE; + if ( hNativeFile == *aphStds[i] + && g_enmWinVer == kRTWinOSType_NT310) + continue; + *aphStds[i] = hNativeFile; + break; + } + + case RTHANDLETYPE_PIPE: + *aphStds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE + ? (HANDLE)RTPipeToNative(paHandles[i]->u.hPipe) + : INVALID_HANDLE_VALUE; + if ( g_enmWinVer == kRTWinOSType_NT310 + && *aphStds[i] == INVALID_HANDLE_VALUE) + { + AssertMsgReturn(RTPipeGetCreationInheritability(paHandles[i]->u.hPipe), ("%Rrc %p\n", rc, *aphStds[i]), + VERR_INVALID_STATE); + continue; + } + break; + + case RTHANDLETYPE_SOCKET: + *aphStds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET + ? (HANDLE)RTSocketToNative(paHandles[i]->u.hSocket) + : INVALID_HANDLE_VALUE; + break; + + default: + AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER); + } + + /* Get the inheritability of the handle. */ + if (*aphStds[i] != INVALID_HANDLE_VALUE) + { + if (!g_pfnGetHandleInformation) + afInhStds[i] = 0; /* No handle info on NT 3.1, so ASSUME it is not inheritable. */ + else if (!g_pfnGetHandleInformation(*aphStds[i], &afInhStds[i])) + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailedReturn(("%Rrc aphStds[%d] => %p paHandles[%d]={%d,%p}\n", + rc, i, *aphStds[i], i, paHandles[i]->enmType, paHandles[i]->u.uInt), + rc); + } + } + } + } + + /* + * Set the inheritability any handles we're handing the child. + * + * Note! On NT 3.1 there is no SetHandleInformation, so we have to duplicate + * the handles to make sure they are inherited by the child. + */ + rc = VINF_SUCCESS; + for (int i = 0; i < 3; i++) + if ( (afInhStds[i] != 0xffffffff) + && !(afInhStds[i] & HANDLE_FLAG_INHERIT)) + { + if (!g_pfnSetHandleInformation) + { + if (DuplicateHandle(GetCurrentProcess(), *aphStds[i], GetCurrentProcess(), &ahStdDups[i], + i == 0 ? GENERIC_READ : GENERIC_WRITE, TRUE /*fInheritHandle*/, DUPLICATE_SAME_ACCESS)) + *aphStds[i] = ahStdDups[i]; + else + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailedBreak(("%Rrc aphStds[%u] => %p\n", rc, i, *aphStds[i])); + } + } + else if (!g_pfnSetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + { + rc = RTErrConvertFromWin32(GetLastError()); + if (rc == VERR_INVALID_FUNCTION && g_enmWinVer == kRTWinOSType_NT310) + rc = VINF_SUCCESS; + else + AssertMsgFailedBreak(("%Rrc aphStds[%u] => %p\n", rc, i, *aphStds[i])); + } + } + + /* + * Create the command line and convert the executable name. + */ + PRTUTF16 pwszCmdLine = NULL; /* Shut up, MSC! */ + if (RT_SUCCESS(rc)) + rc = RTGetOptArgvToUtf16String(&pwszCmdLine, papszArgs, + !(fFlags & RTPROC_FLAGS_UNQUOTED_ARGS) + ? RTGETOPTARGV_CNV_QUOTE_MS_CRT : RTGETOPTARGV_CNV_UNQUOTED); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszExec; + rc = RTPathWinFromUtf8(&pwszExec, pszExec, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + /* + * Get going... + */ + PROCESS_INFORMATION ProcInfo; + RT_ZERO(ProcInfo); + DWORD dwCreationFlags = CREATE_UNICODE_ENVIRONMENT; + if (fFlags & RTPROC_FLAGS_DETACHED) + dwCreationFlags |= DETACHED_PROCESS; + if (fFlags & RTPROC_FLAGS_NO_WINDOW) + dwCreationFlags |= CREATE_NO_WINDOW; + + /* + * Only use the normal CreateProcess stuff if we have no user name + * and we are not running from a (Windows) service. Otherwise use + * the more advanced version in rtProcWinCreateAsUser(). + */ + if ( pszAsUser == NULL + && !(fFlags & (RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_AS_IMPERSONATED_TOKEN | RTPROC_FLAGS_TOKEN_SUPPLIED))) + { + /* Create the environment block first. */ + PRTUTF16 pwszzBlock; + rc = rtProcWinCreateEnvBlockAndFindExe(fFlags, hEnv, pszExec, &pwszzBlock, &pwszExec); + if (RT_SUCCESS(rc)) + { + if (CreateProcessW(pwszExec, + pwszCmdLine, + NULL, /* pProcessAttributes */ + NULL, /* pThreadAttributes */ + TRUE, /* fInheritHandles */ + dwCreationFlags, + pwszzBlock, + NULL, /* pCurrentDirectory */ + &StartupInfo, + &ProcInfo)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + RTEnvFreeUtf16Block(pwszzBlock); + } + } + else + { + /* + * Convert the additional parameters and use a helper + * function to do the actual work. + */ + PRTUTF16 pwszUser = NULL; + if (pszAsUser) + rc = RTStrToUtf16(pszAsUser, &pwszUser); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszPassword; + rc = RTStrToUtf16(pszPassword ? pszPassword : "", &pwszPassword); + if (RT_SUCCESS(rc)) + { + rc = rtProcWinCreateAsUser(pwszUser, pwszPassword, &pwszExec, pwszCmdLine, hEnv, dwCreationFlags, + &StartupInfo, &ProcInfo, fFlags, pszExec, idDesiredSession, + hUserToken); + + if (pwszPassword && *pwszPassword) + RTMemWipeThoroughly(pwszPassword, RTUtf16Len(pwszPassword), 5); + RTUtf16Free(pwszPassword); + } + RTUtf16Free(pwszUser); + } + } + if (RT_SUCCESS(rc)) + { + CloseHandle(ProcInfo.hThread); + if (phProcess) + { + /* + * Add the process to the child process list so RTProcWait can reuse and close + * the process handle, unless, of course, the caller has no intention waiting. + */ + if (!(fFlags & RTPROC_FLAGS_NO_WAIT)) + rtProcWinAddPid(ProcInfo.dwProcessId, ProcInfo.hProcess); + else + CloseHandle(ProcInfo.hProcess); + *phProcess = ProcInfo.dwProcessId; + } + else + CloseHandle(ProcInfo.hProcess); + rc = VINF_SUCCESS; + } + RTPathWinFree(pwszExec); + } + RTUtf16Free(pwszCmdLine); + } + + if (g_pfnSetHandleInformation) + { + /* Undo any handle inherit changes. */ + for (int i = 0; i < 3; i++) + if ( (afInhStds[i] != 0xffffffff) + && !(afInhStds[i] & HANDLE_FLAG_INHERIT)) + { + if ( !g_pfnSetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, 0) + && ( GetLastError() != ERROR_INVALID_FUNCTION + || g_enmWinVer != kRTWinOSType_NT310) ) + AssertMsgFailed(("%Rrc %p\n", RTErrConvertFromWin32(GetLastError()), *aphStds[i])); + } + } + else + { + /* Close handles duplicated for correct inheritance. */ + for (int i = 0; i < 3; i++) + if (ahStdDups[i] != INVALID_HANDLE_VALUE) + CloseHandle(ahStdDups[i]); + } + + return rc; +} + + + +RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus) +{ + AssertReturn(!(fFlags & ~(RTPROCWAIT_FLAGS_BLOCK | RTPROCWAIT_FLAGS_NOBLOCK)), VERR_INVALID_PARAMETER); + int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL); + AssertRCReturn(rc, rc); + + /* + * Try find the process among the ones we've spawned, otherwise, attempt + * opening the specified process. + */ + HANDLE hOpenedProc = NULL; + HANDLE hProcess = rtProcWinFindPid(Process); + if (hProcess == NULL) + { + hProcess = hOpenedProc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Process); + if (hProcess == NULL) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_INVALID_PARAMETER) + return VERR_PROCESS_NOT_FOUND; + return RTErrConvertFromWin32(dwErr); + } + } + + /* + * Wait for it to terminate. + */ + DWORD Millies = fFlags == RTPROCWAIT_FLAGS_BLOCK ? INFINITE : 0; + DWORD WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE); + while (WaitRc == WAIT_IO_COMPLETION) + WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE); + switch (WaitRc) + { + /* + * It has terminated. + */ + case WAIT_OBJECT_0: + { + DWORD dwExitCode; + if (GetExitCodeProcess(hProcess, &dwExitCode)) + { + /** @todo the exit code can be special statuses. */ + if (pProcStatus) + { + pProcStatus->enmReason = RTPROCEXITREASON_NORMAL; + pProcStatus->iStatus = (int)dwExitCode; + } + if (hOpenedProc == NULL) + rtProcWinRemovePid(Process); + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + + /* + * It hasn't terminated just yet. + */ + case WAIT_TIMEOUT: + rc = VERR_PROCESS_RUNNING; + break; + + /* + * Something went wrong... + */ + case WAIT_FAILED: + rc = RTErrConvertFromWin32(GetLastError()); + break; + + case WAIT_ABANDONED: + AssertFailed(); + rc = VERR_GENERAL_FAILURE; + break; + + default: + AssertMsgFailed(("WaitRc=%RU32\n", WaitRc)); + rc = VERR_GENERAL_FAILURE; + break; + } + + if (hOpenedProc != NULL) + CloseHandle(hOpenedProc); + return rc; +} + + +RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus) +{ + /** @todo this isn't quite right. */ + return RTProcWait(Process, fFlags, pProcStatus); +} + + +RTR3DECL(int) RTProcTerminate(RTPROCESS Process) +{ + if (Process == NIL_RTPROCESS) + return VINF_SUCCESS; + + int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL); + AssertRCReturn(rc, rc); + + /* + * Try find the process among the ones we've spawned, otherwise, attempt + * opening the specified process. + */ + HANDLE hProcess = rtProcWinFindPid(Process); + if (hProcess != NULL) + { + if (!TerminateProcess(hProcess, 127)) + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, Process); + if (hProcess != NULL) + { + BOOL fRc = TerminateProcess(hProcess, 127); + DWORD dwErr = GetLastError(); + CloseHandle(hProcess); + if (!fRc) + rc = RTErrConvertFromWin32(dwErr); + } + } + return rc; +} + + +RTR3DECL(uint64_t) RTProcGetAffinityMask(void) +{ + DWORD_PTR dwProcessAffinityMask = 0xffffffff; + DWORD_PTR dwSystemAffinityMask; + + BOOL fRc = GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinityMask, &dwSystemAffinityMask); + Assert(fRc); NOREF(fRc); + + return dwProcessAffinityMask; +} + + +RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, size_t *pcbUser) +{ + AssertReturn( (pszUser && cbUser > 0) + || (!pszUser && !cbUser), VERR_INVALID_PARAMETER); + AssertReturn(pcbUser || pszUser, VERR_INVALID_PARAMETER); + + int rc; + if ( hProcess == NIL_RTPROCESS + || hProcess == RTProcSelf()) + { + RTUTF16 wszUsername[UNLEN + 1]; + DWORD cwcUsername = RT_ELEMENTS(wszUsername); + if (GetUserNameW(&wszUsername[0], &cwcUsername)) + { + if (pszUser) + { + rc = RTUtf16ToUtf8Ex(wszUsername, cwcUsername, &pszUser, cbUser, pcbUser); + if (pcbUser) + *pcbUser += 1; + } + else + { + *pcbUser = RTUtf16CalcUtf8Len(wszUsername) + 1; + rc = VERR_BUFFER_OVERFLOW; + } + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VERR_NOT_SUPPORTED; + return rc; +} + + +RTR3DECL(int) RTProcQueryUsernameA(RTPROCESS hProcess, char **ppszUser) +{ + AssertPtrReturn(ppszUser, VERR_INVALID_POINTER); + int rc; + if ( hProcess == NIL_RTPROCESS + || hProcess == RTProcSelf()) + { + RTUTF16 wszUsername[UNLEN + 1]; + DWORD cwcUsername = RT_ELEMENTS(wszUsername); + if (GetUserNameW(&wszUsername[0], &cwcUsername)) + rc = RTUtf16ToUtf8(wszUsername, ppszUser); + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VERR_NOT_SUPPORTED; + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/rtProcInitExePath-win.cpp b/src/VBox/Runtime/r3/win/rtProcInitExePath-win.cpp new file mode 100644 index 00000000..f0f478f6 --- /dev/null +++ b/src/VBox/Runtime/r3/win/rtProcInitExePath-win.cpp @@ -0,0 +1,72 @@ +/* $Id: rtProcInitExePath-win.cpp $ */ +/** @file + * IPRT - rtProcInitName, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <iprt/win/windows.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include "internal/process.h" + + +DECLHIDDEN(int) rtProcInitExePath(char *pszPath, size_t cchPath) +{ + /* + * Query the image name from the dynamic linker, convert and return it. + */ + WCHAR wsz[RTPATH_MAX]; + HMODULE hExe = GetModuleHandle(NULL); + if (GetModuleFileNameW(hExe, wsz, RTPATH_MAX)) + { + int rc = RTUtf16ToUtf8Ex(wsz, RTSTR_MAX, &pszPath, cchPath, NULL); + AssertRCReturn(rc, rc); + return VINF_SUCCESS; + } + + DWORD err = GetLastError(); + int rc = RTErrConvertFromWin32(err); + AssertMsgFailed(("%Rrc %d\n", rc, err)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/sched-win.cpp b/src/VBox/Runtime/r3/win/sched-win.cpp new file mode 100644 index 00000000..7b8397e0 --- /dev/null +++ b/src/VBox/Runtime/r3/win/sched-win.cpp @@ -0,0 +1,333 @@ +/* $Id: sched-win.cpp $ */ +/** @file + * IPRT - Scheduling, Win32. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @def WIN32_SCHED_ENABLED + * Enables the priority scheme. */ +#define WIN32_SCHED_ENABLED + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <iprt/win/windows.h> + +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include "internal/sched.h" +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Configuration of one priority. + */ +typedef struct +{ + /** The priority. */ + RTPROCPRIORITY enmPriority; + /** The name of this priority. */ + const char *pszName; + /** The Win32 process priority class. If ANY_PROCESS_PRIORITY_CLASS the + * process priority class is left unchanged. */ + DWORD dwProcessPriorityClass; + /** Array scheduler attributes corresponding to each of the thread types. */ + struct + { + /** For sanity include the array index. */ + RTTHREADTYPE enmType; + /** The Win32 thread priority. */ + int iThreadPriority; + } aTypes[RTTHREADTYPE_END]; +} PROCPRIORITY; + +/** Matches any process priority class. */ +#define ANY_PROCESS_PRIORITY_CLASS (~0U) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Array of static priority configurations. + */ +static const PROCPRIORITY g_aPriorities[] = +{ + { + RTPROCPRIORITY_FLAT, "Flat", ANY_PROCESS_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_NORMAL } + } + }, + { + RTPROCPRIORITY_LOW, "Low - Below Normal", BELOW_NORMAL_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_HIGHEST }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_HIGHEST } + } + }, + { + RTPROCPRIORITY_LOW, "Low", ANY_PROCESS_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_NORMAL } + } + }, + { + RTPROCPRIORITY_NORMAL, "Normal - Normal", NORMAL_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_HIGHEST } + } + }, + { + RTPROCPRIORITY_NORMAL, "Normal", ANY_PROCESS_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_HIGHEST } + } + }, + { + RTPROCPRIORITY_HIGH, "High - High", HIGH_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_HIGHEST }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_HIGHEST } + } + }, + { + RTPROCPRIORITY_HIGH, "High - Above Normal", ABOVE_NORMAL_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_HIGHEST }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_HIGHEST } + } + }, + { + RTPROCPRIORITY_HIGH, "High", ANY_PROCESS_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_HIGHEST }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_HIGHEST }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_HIGHEST }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_HIGHEST } + } + } +}; + +/** + * The dynamic default priority configuration. + * + * This can be recalulated at runtime depending on what the + * system allow us to do. Presently we don't do this as it's + * generally not a bit issue on Win32 hosts. + */ +static PROCPRIORITY g_aDefaultPriority = +{ + RTPROCPRIORITY_LOW, "Default", ANY_PROCESS_PRIORITY_CLASS, + { + { RTTHREADTYPE_INVALID, ~0 }, + { RTTHREADTYPE_INFREQUENT_POLLER, THREAD_PRIORITY_LOWEST }, + { RTTHREADTYPE_MAIN_HEAVY_WORKER, THREAD_PRIORITY_BELOW_NORMAL }, + { RTTHREADTYPE_EMULATION, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_DEFAULT, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_GUI, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_MAIN_WORKER, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_VRDP_IO, THREAD_PRIORITY_NORMAL }, + { RTTHREADTYPE_DEBUGGER, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_MSG_PUMP, THREAD_PRIORITY_ABOVE_NORMAL }, + { RTTHREADTYPE_IO, THREAD_PRIORITY_HIGHEST }, + { RTTHREADTYPE_TIMER, THREAD_PRIORITY_HIGHEST } + } +}; + + +/** Pointer to the current priority configuration. */ +static const PROCPRIORITY *g_pProcessPriority = &g_aDefaultPriority; + + +/** + * Calculate the scheduling properties for all the threads in the default + * process priority, assuming the current thread have the type enmType. + * + * @returns iprt status code. + * @param enmType The thread type to be assumed for the current thread. + */ +DECLHIDDEN(int) rtSchedNativeCalcDefaultPriority(RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); RT_NOREF_PV(enmType); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) rtProcNativeSetPriority(RTPROCPRIORITY enmPriority) +{ + Assert(enmPriority > RTPROCPRIORITY_INVALID && enmPriority < RTPROCPRIORITY_LAST); RT_NOREF_PV(enmPriority); + + if (enmPriority == RTPROCPRIORITY_DEFAULT) + { + g_pProcessPriority = &g_aDefaultPriority; + return VINF_SUCCESS; + } + + for (size_t i = 0; i < RT_ELEMENTS(g_aPriorities); i++) + if ( g_aPriorities[i].enmPriority == enmPriority + && g_aPriorities[i].dwProcessPriorityClass == ANY_PROCESS_PRIORITY_CLASS) + { + g_pProcessPriority = &g_aPriorities[i]; + return VINF_SUCCESS; + } + + AssertFailedReturn(VERR_INTERNAL_ERROR); +} + + +/** + * Gets the win32 thread handle. + * + * @returns Valid win32 handle for the specified thread. + * @param pThread The thread. + */ +DECLINLINE(HANDLE) rtThreadNativeGetHandle(PRTTHREADINT pThread) +{ + if ((uintptr_t)pThread->Core.Key == GetCurrentThreadId()) + return GetCurrentThread(); + return (HANDLE)pThread->hThread; +} + + +DECLHIDDEN(int) rtThreadNativeSetPriority(PRTTHREADINT pThread, RTTHREADTYPE enmType) +{ + Assert(enmType > RTTHREADTYPE_INVALID && enmType < RTTHREADTYPE_END); + AssertMsg(g_pProcessPriority && g_pProcessPriority->aTypes[enmType].enmType == enmType, + ("enmType=%d entry=%d\n", enmType, g_pProcessPriority->aTypes[enmType].enmType)); + +#ifdef WIN32_SCHED_ENABLED + HANDLE hThread = rtThreadNativeGetHandle(pThread); + if ( hThread == NULL /* No handle for alien threads. */ + || SetThreadPriority(hThread, g_pProcessPriority->aTypes[enmType].iThreadPriority)) + return VINF_SUCCESS; + + DWORD dwLastError = GetLastError(); + int rc = RTErrConvertFromWin32(dwLastError); + AssertMsgFailed(("SetThreadPriority(%p, %d) failed, dwLastError=%d rc=%Rrc\n", + rtThreadNativeGetHandle(pThread), g_pProcessPriority->aTypes[enmType].iThreadPriority, dwLastError, rc)); + return rc; +#else + return VINF_SUCCESS; +#endif +} + diff --git a/src/VBox/Runtime/r3/win/semevent-win.cpp b/src/VBox/Runtime/r3/win/semevent-win.cpp new file mode 100644 index 00000000..be086941 --- /dev/null +++ b/src/VBox/Runtime/r3/win/semevent-win.cpp @@ -0,0 +1,315 @@ +/* $Id: semevent-win.cpp $ */ +/** @file + * IPRT - Event Semaphore, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SEMAPHORE +#include <iprt/win/windows.h> + +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/mem.h> +#include <iprt/thread.h> +#include "internal/magics.h" +#include "internal/mem.h" +#include "internal/strict.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +struct RTSEMEVENTINTERNAL +{ + /** Magic value (RTSEMEVENT_MAGIC). */ + uint32_t u32Magic; + /** The event handle. */ + HANDLE hev; +#ifdef RTSEMEVENT_STRICT + /** Signallers. */ + RTLOCKVALRECSHRD Signallers; + /** Indicates that lock validation should be performed. */ + bool volatile fEverHadSignallers; +#endif + /** The creation flags. */ + uint32_t fFlags; +}; + + + +RTDECL(int) RTSemEventCreate(PRTSEMEVENT phEventSem) +{ + return RTSemEventCreateEx(phEventSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventCreateEx(PRTSEMEVENT phEventSem, uint32_t fFlags, RTLOCKVALCLASS hClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~(RTSEMEVENT_FLAGS_NO_LOCK_VAL | RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)), VERR_INVALID_PARAMETER); + Assert(!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) || (fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL)); + + struct RTSEMEVENTINTERNAL *pThis; + if (!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + pThis = (struct RTSEMEVENTINTERNAL *)RTMemAlloc(sizeof(*pThis)); + else + pThis = (struct RTSEMEVENTINTERNAL *)rtMemBaseAlloc(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + + /* + * Create the semaphore. + * (Auto reset, not signaled, private event object.) + */ + pThis->hev = CreateEvent(NULL, FALSE, FALSE, NULL); + if (pThis->hev != NULL) /* not INVALID_HANDLE_VALUE */ + { + pThis->u32Magic = RTSEMEVENT_MAGIC; + pThis->fFlags = fFlags; +#ifdef RTSEMEVENT_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iSemEventAnon = 0; + RTLockValidatorRecSharedInit(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL), + "RTSemEvent-%u", ASMAtomicIncU32(&s_iSemEventAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENT_FLAGS_NO_LOCK_VAL), + pszNameFmt, va); + va_end(va); + } + pThis->fEverHadSignallers = false; +#else + RT_NOREF_PV(hClass); RT_NOREF_PV(pszNameFmt); +#endif + + *phEventSem = pThis; + return VINF_SUCCESS; + } + + DWORD dwErr = GetLastError(); + if (!(fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + RTMemFree(pThis); + else + rtMemBaseFree(pThis); + return RTErrConvertFromWin32(dwErr); +} + + +RTDECL(int) RTSemEventDestroy(RTSEMEVENT hEventSem) +{ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + if (pThis == NIL_RTSEMEVENT) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENT_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the handle and close the semaphore. + */ + int rc = VINF_SUCCESS; + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTSEMEVENT_MAGIC, RTSEMEVENT_MAGIC), VERR_INVALID_HANDLE); + if (CloseHandle(pThis->hev)) + { +#ifdef RTSEMEVENT_STRICT + RTLockValidatorRecSharedDelete(&pThis->Signallers); +#endif + if (!(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK)) + RTMemFree(pThis); + else + rtMemBaseFree(pThis); + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + AssertMsgFailed(("Destroy hEventSem %p failed, lasterr=%u (%Rrc)\n", pThis, dwErr, rc)); + /* Leak it. */ + } + + return rc; +} + + +RTDECL(int) RTSemEventSignal(RTSEMEVENT hEventSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENT_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSEMEVENT_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckSignaller(&pThis->Signallers, NIL_RTTHREAD); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif + + /* + * Signal the object. + */ + if (SetEvent(pThis->hev)) + return VINF_SUCCESS; + DWORD dwErr = GetLastError(); + AssertMsgFailed(("Signaling hEventSem %p failed, lasterr=%d\n", pThis, dwErr)); + return RTErrConvertFromWin32(dwErr); +} + + +/** Goto avoidance. */ +DECL_FORCE_INLINE(int) rtSemEventWaitHandleStatus(struct RTSEMEVENTINTERNAL *pThis, DWORD rc) +{ + switch (rc) + { + case WAIT_OBJECT_0: return VINF_SUCCESS; + case WAIT_TIMEOUT: return VERR_TIMEOUT; + case WAIT_IO_COMPLETION: return VERR_INTERRUPTED; + case WAIT_ABANDONED: return VERR_SEM_OWNER_DIED; + default: + AssertMsgFailed(("%u\n", rc)); + case WAIT_FAILED: + { + int rc2 = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailed(("Wait on hEventSem %p failed, rc=%d lasterr=%d\n", pThis, rc, GetLastError())); + if (rc2) + return rc2; + + AssertMsgFailed(("WaitForSingleObject(event) -> rc=%d while converted lasterr=%d\n", rc, rc2)); + RT_NOREF_PV(pThis); + return VERR_INTERNAL_ERROR; + } + } +} + + +#undef RTSemEventWaitNoResume +RTDECL(int) RTSemEventWaitNoResume(RTSEMEVENT hEventSem, RTMSINTERVAL cMillies) +{ + /* + * Validate input. + */ + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENT_MAGIC, VERR_INVALID_HANDLE); + + /* + * Wait for condition. + */ +#ifdef RTSEMEVENT_STRICT + RTTHREAD hThreadSelf = !(pThis->fFlags & RTSEMEVENT_FLAGS_BOOTSTRAP_HACK) + ? RTThreadSelfAutoAdopt() + : RTThreadSelf(); + if (pThis->fEverHadSignallers) + { + DWORD rc = WaitForSingleObjectEx(pThis->hev, + 0 /*Timeout*/, + TRUE /*fAlertable*/); + if (rc != WAIT_TIMEOUT || cMillies == 0) + return rtSemEventWaitHandleStatus(pThis, rc); + int rc9 = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, NULL /*pSrcPos*/, false, + cMillies, RTTHREADSTATE_EVENT, true); + if (RT_FAILURE(rc9)) + return rc9; + } +#else + RTTHREAD hThreadSelf = RTThreadSelf(); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT, true); + DWORD rc = WaitForSingleObjectEx(pThis->hev, + cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies, + TRUE /*fAlertable*/); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT); + return rtSemEventWaitHandleStatus(pThis, rc); +} + + +RTDECL(void) RTSemEventSetSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENT_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedResetOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventAddSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENT_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedAddOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventRemoveSignaller(RTSEMEVENT hEventSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTINTERNAL *pThis = hEventSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENT_MAGIC); + + RTLockValidatorRecSharedRemoveOwner(&pThis->Signallers, hThread); +#else + RT_NOREF_PV(hEventSem); RT_NOREF_PV(hThread); +#endif +} + diff --git a/src/VBox/Runtime/r3/win/semeventmulti-win.cpp b/src/VBox/Runtime/r3/win/semeventmulti-win.cpp new file mode 100644 index 00000000..ed37f656 --- /dev/null +++ b/src/VBox/Runtime/r3/win/semeventmulti-win.cpp @@ -0,0 +1,389 @@ +/* $Id: semeventmulti-win.cpp $ */ +/** @file + * IPRT - Multiple Release Event Semaphore, Windows. + * + * @remarks This file is identical to semevent-win.cpp except for the 2nd + * CreateEvent parameter, the reset function and the "Multi" infix. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SEMAPHORE +#include <iprt/win/windows.h> + +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/mem.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include "internal/magics.h" +#include "internal/strict.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +struct RTSEMEVENTMULTIINTERNAL +{ + /** Magic value (RTSEMEVENTMULTI_MAGIC). */ + uint32_t u32Magic; + /** The event handle. */ + HANDLE hev; +#ifdef RTSEMEVENT_STRICT + /** Signallers. */ + RTLOCKVALRECSHRD Signallers; + /** Indicates that lock validation should be performed. */ + bool volatile fEverHadSignallers; +#endif +}; + + + +RTDECL(int) RTSemEventMultiCreate(PRTSEMEVENTMULTI phEventMultiSem) +{ + return RTSemEventMultiCreateEx(phEventMultiSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, NULL); +} + + +RTDECL(int) RTSemEventMultiCreateEx(PRTSEMEVENTMULTI phEventMultiSem, uint32_t fFlags, RTLOCKVALCLASS hClass, + const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + struct RTSEMEVENTMULTIINTERNAL *pThis = (struct RTSEMEVENTMULTIINTERNAL *)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + + /* + * Create the semaphore. + * (Manual reset, not signaled, private event object.) + */ + pThis->hev = CreateEvent(NULL, TRUE, FALSE, NULL); + if (pThis->hev != NULL) /* not INVALID_HANDLE_VALUE */ + { + pThis->u32Magic = RTSEMEVENTMULTI_MAGIC; +#ifdef RTSEMEVENT_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iSemEventMultiAnon = 0; + RTLockValidatorRecSharedInit(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), + "RTSemEventMulti-%u", ASMAtomicIncU32(&s_iSemEventMultiAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(&pThis->Signallers, hClass, RTLOCKVAL_SUB_CLASS_ANY, pThis, + true /*fSignaller*/, !(fFlags & RTSEMEVENTMULTI_FLAGS_NO_LOCK_VAL), + pszNameFmt, va); + va_end(va); + } + pThis->fEverHadSignallers = false; +#else + RT_NOREF_PV(hClass); RT_NOREF_PV(pszNameFmt); +#endif + + *phEventMultiSem = pThis; + return VINF_SUCCESS; + } + + DWORD dwErr = GetLastError(); + RTMemFree(pThis); + return RTErrConvertFromWin32(dwErr); +} + + +RTDECL(int) RTSemEventMultiDestroy(RTSEMEVENTMULTI hEventMultiSem) +{ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + if (pThis == NIL_RTSEMEVENT) /* don't bitch */ + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the handle and close the semaphore. + */ + int rc = VINF_SUCCESS; + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTSEMEVENTMULTI_MAGIC, RTSEMEVENTMULTI_MAGIC), VERR_INVALID_HANDLE); + if (CloseHandle(pThis->hev)) + { +#ifdef RTSEMEVENT_STRICT + RTLockValidatorRecSharedDelete(&pThis->Signallers); +#endif + RTMemFree(pThis); + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + AssertMsgFailed(("Destroy hEventMultiSem %p failed, lasterr=%u (%Rrc)\n", pThis, dwErr, rc)); + /* Leak it. */ + } + + return rc; +} + + +RTDECL(int) RTSemEventMultiSignal(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSEMEVENT_STRICT + if (pThis->fEverHadSignallers) + { + int rc9 = RTLockValidatorRecSharedCheckSignaller(&pThis->Signallers, NIL_RTTHREAD); + if (RT_FAILURE(rc9)) + return rc9; + } +#endif + + /* + * Signal the object. + */ + if (SetEvent(pThis->hev)) + return VINF_SUCCESS; + DWORD dwErr = GetLastError(); + AssertMsgFailed(("Signaling hEventMultiSem %p failed, lasterr=%d\n", pThis, dwErr)); + return RTErrConvertFromWin32(dwErr); +} + + +RTDECL(int) RTSemEventMultiReset(RTSEMEVENTMULTI hEventMultiSem) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); + + /* + * Reset the object. + */ + if (ResetEvent(pThis->hev)) + return VINF_SUCCESS; + DWORD dwErr = GetLastError(); + AssertMsgFailed(("Resetting hEventMultiSem %p failed, lasterr=%d\n", pThis, dwErr)); + return RTErrConvertFromWin32(dwErr); +} + + +/** Goto avoidance. */ +DECL_FORCE_INLINE(int) +rtSemEventWaitHandleStatus(struct RTSEMEVENTMULTIINTERNAL *pThis, uint32_t fFlags, DWORD rc) +{ + switch (rc) + { + case WAIT_OBJECT_0: return VINF_SUCCESS; + case WAIT_TIMEOUT: return VERR_TIMEOUT; + case WAIT_IO_COMPLETION: return fFlags & RTSEMWAIT_FLAGS_RESUME ? VERR_TIMEOUT : VERR_INTERRUPTED; + case WAIT_ABANDONED: return VERR_SEM_OWNER_DIED; + default: + AssertMsgFailed(("%u\n", rc)); + case WAIT_FAILED: + { + int rc2 = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailed(("Wait on hEventMultiSem %p failed, rc=%d lasterr=%d\n", pThis, rc, GetLastError())); + if (rc2) + return rc2; + + AssertMsgFailed(("WaitForSingleObject(event) -> rc=%d while converted lasterr=%d\n", rc, rc2)); + RT_NOREF_PV(pThis); + return VERR_INTERNAL_ERROR; + } + } +} + + +DECLINLINE(int) rtSemEventMultiWinWait(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, + PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate input. + */ + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(RTSEMWAIT_FLAGS_ARE_VALID(fFlags), VERR_INVALID_PARAMETER); + + /* + * Convert the timeout to a millisecond count. + */ + uint64_t uAbsDeadline; + DWORD dwMsTimeout; + if (fFlags & RTSEMWAIT_FLAGS_INDEFINITE) + { + dwMsTimeout = INFINITE; + uAbsDeadline = UINT64_MAX; + } + else + { + if (fFlags & RTSEMWAIT_FLAGS_NANOSECS) + uTimeout = uTimeout < UINT64_MAX - UINT32_C(1000000) / 2 + ? (uTimeout + UINT32_C(1000000) / 2) / UINT32_C(1000000) + : UINT64_MAX / UINT32_C(1000000); + if (fFlags & RTSEMWAIT_FLAGS_ABSOLUTE) + { + uAbsDeadline = uTimeout; + uint64_t u64Now = RTTimeSystemMilliTS(); + if (u64Now < uTimeout) + uTimeout -= u64Now; + else + uTimeout = 0; + } + else if (fFlags & RTSEMWAIT_FLAGS_RESUME) + uAbsDeadline = RTTimeSystemMilliTS() + uTimeout; + else + uAbsDeadline = UINT64_MAX; + + dwMsTimeout = uTimeout < UINT32_MAX + ? (DWORD)uTimeout + : INFINITE; + } + + /* + * Do the wait. + */ + DWORD rc; +#ifdef RTSEMEVENT_STRICT + RTTHREAD hThreadSelf = RTThreadSelfAutoAdopt(); + if (pThis->fEverHadSignallers) + { + do + rc = WaitForSingleObjectEx(pThis->hev, 0 /*Timeout*/, TRUE /*fAlertable*/); + while (rc == WAIT_IO_COMPLETION && (fFlags & RTSEMWAIT_FLAGS_RESUME)); + if (rc != WAIT_TIMEOUT || dwMsTimeout == 0) + return rtSemEventWaitHandleStatus(pThis, fFlags, rc); + int rc9 = RTLockValidatorRecSharedCheckBlocking(&pThis->Signallers, hThreadSelf, pSrcPos, false, + dwMsTimeout, RTTHREADSTATE_EVENT_MULTI, true); + if (RT_FAILURE(rc9)) + return rc9; + } +#else + RTTHREAD hThreadSelf = RTThreadSelf(); + RT_NOREF_PV(pSrcPos); +#endif + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_EVENT_MULTI, true); + rc = WaitForSingleObjectEx(pThis->hev, dwMsTimeout, TRUE /*fAlertable*/); + if (rc == WAIT_IO_COMPLETION && (fFlags & RTSEMWAIT_FLAGS_RESUME)) + { + while ( rc == WAIT_IO_COMPLETION + && RTTimeSystemMilliTS() < uAbsDeadline) + rc = WaitForSingleObjectEx(pThis->hev, dwMsTimeout, TRUE /*fAlertable*/); + + } + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_EVENT_MULTI); + return rtSemEventWaitHandleStatus(pThis, fFlags, rc); +} + + + +#undef RTSemEventMultiWaitEx +RTDECL(int) RTSemEventMultiWaitEx(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout) +{ +#ifndef RTSEMEVENT_STRICT + return rtSemEventMultiWinWait(hEventMultiSem, fFlags, uTimeout, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemEventMultiWinWait(hEventMultiSem, fFlags, uTimeout, &SrcPos); +#endif +} + + +RTDECL(int) RTSemEventMultiWaitExDebug(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, + RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemEventMultiWinWait(hEventMultiSem, fFlags, uTimeout, &SrcPos); +} + + + +RTDECL(void) RTSemEventMultiSetSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedResetOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventMultiSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventMultiAddSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC); + + ASMAtomicWriteBool(&pThis->fEverHadSignallers, true); + RTLockValidatorRecSharedAddOwner(&pThis->Signallers, hThread, NULL); +#else + RT_NOREF_PV(hEventMultiSem); RT_NOREF_PV(hThread); +#endif +} + + +RTDECL(void) RTSemEventMultiRemoveSignaller(RTSEMEVENTMULTI hEventMultiSem, RTTHREAD hThread) +{ +#ifdef RTSEMEVENT_STRICT + struct RTSEMEVENTMULTIINTERNAL *pThis = hEventMultiSem; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTSEMEVENTMULTI_MAGIC); + + RTLockValidatorRecSharedRemoveOwner(&pThis->Signallers, hThread); +#else + RT_NOREF_PV(hEventMultiSem); RT_NOREF_PV(hThread); +#endif +} + diff --git a/src/VBox/Runtime/r3/win/semmutex-win.cpp b/src/VBox/Runtime/r3/win/semmutex-win.cpp new file mode 100644 index 00000000..34aa8bc5 --- /dev/null +++ b/src/VBox/Runtime/r3/win/semmutex-win.cpp @@ -0,0 +1,356 @@ +/* $Id: semmutex-win.cpp $ */ +/** @file + * IPRT - Mutex Semaphores, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SEMAPHORE +#include <iprt/win/windows.h> + +#include <iprt/semaphore.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/lockvalidator.h> +#include <iprt/mem.h> +#include <iprt/thread.h> +#include "internal/magics.h" +#include "internal/strict.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Posix internal representation of a Mutex semaphore. */ +struct RTSEMMUTEXINTERNAL +{ + /** Magic value (RTSEMMUTEX_MAGIC). */ + uint32_t u32Magic; + /** Recursion count. */ + uint32_t volatile cRecursions; + /** The owner thread. */ + RTNATIVETHREAD volatile hNativeOwner; + /** The mutex handle. */ + HANDLE hMtx; +#ifdef RTSEMMUTEX_STRICT + /** Lock validator record associated with this mutex. */ + RTLOCKVALRECEXCL ValidatorRec; +#endif +}; + + + +#undef RTSemMutexCreate +RTDECL(int) RTSemMutexCreate(PRTSEMMUTEX phMutexSem) +{ + return RTSemMutexCreateEx(phMutexSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL); +} + + +RTDECL(int) RTSemMutexCreateEx(PRTSEMMUTEX phMutexSem, uint32_t fFlags, + RTLOCKVALCLASS hClass, uint32_t uSubClass, const char *pszNameFmt, ...) +{ + AssertReturn(!(fFlags & ~RTSEMMUTEX_FLAGS_NO_LOCK_VAL), VERR_INVALID_PARAMETER); + + /* + * Create the semaphore. + */ + int rc; + HANDLE hMtx = CreateMutex(NULL, FALSE, NULL); + if (hMtx) + { + RTSEMMUTEXINTERNAL *pThis = (RTSEMMUTEXINTERNAL *)RTMemAlloc(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTSEMMUTEX_MAGIC; + pThis->hMtx = hMtx; + pThis->hNativeOwner = NIL_RTNATIVETHREAD; + pThis->cRecursions = 0; +#ifdef RTSEMMUTEX_STRICT + if (!pszNameFmt) + { + static uint32_t volatile s_iMutexAnon = 0; + RTLockValidatorRecExclInit(&pThis->ValidatorRec, hClass, uSubClass, pThis, + !(fFlags & RTSEMMUTEX_FLAGS_NO_LOCK_VAL), + "RTSemMutex-%u", ASMAtomicIncU32(&s_iMutexAnon) - 1); + } + else + { + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecExclInitV(&pThis->ValidatorRec, hClass, uSubClass, pThis, + !(fFlags & RTSEMMUTEX_FLAGS_NO_LOCK_VAL), pszNameFmt, va); + va_end(va); + } +#else + RT_NOREF_PV(hClass); RT_NOREF_PV(uSubClass); RT_NOREF_PV(pszNameFmt); +#endif + *phMutexSem = pThis; + return VINF_SUCCESS; + } + + rc = VERR_NO_MEMORY; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + return rc; +} + + +RTDECL(int) RTSemMutexDestroy(RTSEMMUTEX hMutexSem) +{ + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + if (pThis == NIL_RTSEMMUTEX) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + + /* + * Close semaphore handle. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTSEMMUTEX_MAGIC_DEAD, RTSEMMUTEX_MAGIC), VERR_INVALID_HANDLE); + HANDLE hMtx = pThis->hMtx; + ASMAtomicWritePtr(&pThis->hMtx, INVALID_HANDLE_VALUE); + + int rc = VINF_SUCCESS; + if (!CloseHandle(hMtx)) + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailed(("%p rc=%d lasterr=%d\n", pThis->hMtx, rc, GetLastError())); + } + +#ifdef RTSEMMUTEX_STRICT + RTLockValidatorRecExclDelete(&pThis->ValidatorRec); +#endif + RTMemFree(pThis); + return rc; +} + + +RTDECL(uint32_t) RTSemMutexSetSubClass(RTSEMMUTEX hMutexSem, uint32_t uSubClass) +{ +#ifdef RTSEMMUTEX_STRICT + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID); + + return RTLockValidatorRecExclSetSubClass(&pThis->ValidatorRec, uSubClass); +#else + RT_NOREF_PV(hMutexSem); RT_NOREF_PV(uSubClass); + return RTLOCKVAL_SUB_CLASS_INVALID; +#endif +} + + +/** + * Internal worker for RTSemMutexRequestNoResume and it's debug companion. + * + * @returns Same as RTSEmMutexRequestNoResume + * @param hMutexSem The mutex handle. + * @param cMillies The number of milliseconds to wait. + * @param pSrcPos The source position of the caller. + */ +DECL_FORCE_INLINE(int) rtSemMutexRequestNoResume(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + + /* + * Check for recursive entry. + */ + RTNATIVETHREAD hNativeSelf = RTThreadNativeSelf(); + RTNATIVETHREAD hNativeOwner; + ASMAtomicReadHandle(&pThis->hNativeOwner, &hNativeOwner); + if (hNativeOwner == hNativeSelf) + { +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclRecursion(&pThis->ValidatorRec, pSrcPos); + if (RT_FAILURE(rc9)) + return rc9; +#endif + ASMAtomicIncU32(&pThis->cRecursions); + return VINF_SUCCESS; + } + + /* + * Lock mutex semaphore. + */ + RTTHREAD hThreadSelf = NIL_RTTHREAD; + if (cMillies > 0) + { +#ifdef RTSEMMUTEX_STRICT + hThreadSelf = RTThreadSelfAutoAdopt(); + int rc9 = RTLockValidatorRecExclCheckOrderAndBlocking(&pThis->ValidatorRec, hThreadSelf, pSrcPos, true, + cMillies, RTTHREADSTATE_MUTEX, true); + if (RT_FAILURE(rc9)) + return rc9; +#else + hThreadSelf = RTThreadSelf(); + RTThreadBlocking(hThreadSelf, RTTHREADSTATE_MUTEX, true); + RT_NOREF_PV(pSrcPos); +#endif + } + DWORD rc = WaitForSingleObjectEx(pThis->hMtx, + cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies, + TRUE /*fAlertable*/); + RTThreadUnblocked(hThreadSelf, RTTHREADSTATE_MUTEX); + switch (rc) + { + case WAIT_OBJECT_0: +#ifdef RTSEMMUTEX_STRICT + RTLockValidatorRecExclSetOwner(&pThis->ValidatorRec, hThreadSelf, pSrcPos, true); +#endif + ASMAtomicWriteHandle(&pThis->hNativeOwner, hNativeSelf); + ASMAtomicWriteU32(&pThis->cRecursions, 1); + return VINF_SUCCESS; + + case WAIT_TIMEOUT: return VERR_TIMEOUT; + case WAIT_IO_COMPLETION: return VERR_INTERRUPTED; + case WAIT_ABANDONED: return VERR_SEM_OWNER_DIED; + default: + AssertMsgFailed(("%u\n", rc)); + case WAIT_FAILED: + { + int rc2 = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailed(("Wait on hMutexSem %p failed, rc=%d lasterr=%d\n", hMutexSem, rc, GetLastError())); + if (rc2 != VINF_SUCCESS) + return rc2; + + AssertMsgFailed(("WaitForSingleObject(event) -> rc=%d while converted lasterr=%d\n", rc, rc2)); + return VERR_INTERNAL_ERROR; + } + } +} + + +#undef RTSemMutexRequestNoResume +RTDECL(int) RTSemMutexRequestNoResume(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies) +{ +#ifndef RTSEMMUTEX_STRICT + return rtSemMutexRequestNoResume(hMutexSem, cMillies, NULL); +#else + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_NORMAL_API(); + return rtSemMutexRequestNoResume(hMutexSem, cMillies, &SrcPos); +#endif +} + + +RTDECL(int) RTSemMutexRequestNoResumeDebug(RTSEMMUTEX hMutexSem, RTMSINTERVAL cMillies, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_DEBUG_API(); + return rtSemMutexRequestNoResume(hMutexSem, cMillies, &SrcPos); +} + + +RTDECL(int) RTSemMutexRelease(RTSEMMUTEX hMutexSem) +{ + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, VERR_INVALID_HANDLE); + + /* + * Check ownership and recursions. + */ + RTNATIVETHREAD hNativeSelf = RTThreadNativeSelf(); + RTNATIVETHREAD hNativeOwner; + ASMAtomicReadHandle(&pThis->hNativeOwner, &hNativeOwner); + if (RT_UNLIKELY(hNativeOwner != hNativeSelf)) + { + AssertMsgFailed(("Not owner of mutex %p!! hNativeSelf=%RTntrd Owner=%RTntrd cRecursions=%d\n", + pThis, hNativeSelf, hNativeOwner, pThis->cRecursions)); + return VERR_NOT_OWNER; + } + if (pThis->cRecursions > 1) + { +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclUnwind(&pThis->ValidatorRec); + if (RT_FAILURE(rc9)) + return rc9; +#endif + ASMAtomicDecU32(&pThis->cRecursions); + return VINF_SUCCESS; + } + + /* + * Unlock mutex semaphore. + */ +#ifdef RTSEMMUTEX_STRICT + int rc9 = RTLockValidatorRecExclReleaseOwner(&pThis->ValidatorRec, false); + if (RT_FAILURE(rc9)) + return rc9; +#endif + ASMAtomicWriteU32(&pThis->cRecursions, 0); + ASMAtomicWriteHandle(&pThis->hNativeOwner, NIL_RTNATIVETHREAD); + + if (ReleaseMutex(pThis->hMtx)) + return VINF_SUCCESS; + + int rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailed(("%p/%p, rc=%Rrc lasterr=%d\n", pThis, pThis->hMtx, rc, GetLastError())); + return rc; +} + + +RTDECL(bool) RTSemMutexIsOwned(RTSEMMUTEX hMutexSem) +{ + /* + * Validate. + */ + RTSEMMUTEXINTERNAL *pThis = hMutexSem; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTSEMMUTEX_MAGIC, false); + + RTNATIVETHREAD hNativeOwner; + ASMAtomicReadHandle(&pThis->hNativeOwner, &hNativeOwner); + return hNativeOwner != NIL_RTNATIVETHREAD; +} + diff --git a/src/VBox/Runtime/r3/win/serialport-win.cpp b/src/VBox/Runtime/r3/win/serialport-win.cpp new file mode 100644 index 00000000..0f6f04c2 --- /dev/null +++ b/src/VBox/Runtime/r3/win/serialport-win.cpp @@ -0,0 +1,1056 @@ +/* $Id: serialport-win.cpp $ */ +/** @file + * IPRT - Serial Port API, Windows Implementation. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/serialport.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/cdefs.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include "internal/magics.h" + +#include <iprt/win/windows.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal serial port state. + */ +typedef struct RTSERIALPORTINTERNAL +{ + /** Magic value (RTSERIALPORT_MAGIC). */ + uint32_t u32Magic; + /** Flags given while opening the serial port. */ + uint32_t fOpenFlags; + /** The device handle. */ + HANDLE hDev; + /** The overlapped write structure. */ + OVERLAPPED OverlappedWrite; + /** The overlapped read structure. */ + OVERLAPPED OverlappedRead; + /** The overlapped I/O structure when waiting on events. */ + OVERLAPPED OverlappedEvt; + /** The event handle to wait on for the overlapped event operations of the device. */ + HANDLE hEvtDev; + /** The event handle to wait on for the overlapped write operations of the device. */ + HANDLE hEvtWrite; + /** The event handle to wait on for the overlapped read operations of the device. */ + HANDLE hEvtRead; + /** The event handle to wait on for waking up waiting threads externally. */ + HANDLE hEvtIntr; + /** Events currently waited for. */ + uint32_t fEvtMask; + /** Event mask as received by WaitCommEvent(). */ + DWORD dwEventMask; + /** Flag whether a write is currently pending. */ + bool fWritePending; + /** Event query pending. */ + bool fEvtQueryPending; + /** Bounce buffer for writes. */ + uint8_t *pbBounceBuf; + /** Amount of used buffer space. */ + size_t cbBounceBufUsed; + /** Amount of allocated buffer space. */ + size_t cbBounceBufAlloc; + /** The current active port config. */ + DCB PortCfg; +} RTSERIALPORTINTERNAL; +/** Pointer to the internal serial port state. */ +typedef RTSERIALPORTINTERNAL *PRTSERIALPORTINTERNAL; + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The pipe buffer size we prefer. */ +#define RTSERIALPORT_NT_SIZE _32K + + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Updatest the current event mask to wait for. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + * @param fEvtMask The new event mask to change to. + */ +static int rtSerialPortWinUpdateEvtMask(PRTSERIALPORTINTERNAL pThis, uint32_t fEvtMask) +{ + DWORD dwEvtMask = EV_ERR; + + if (fEvtMask & RTSERIALPORT_EVT_F_DATA_RX) + dwEvtMask |= EV_RXCHAR; + if (fEvtMask & RTSERIALPORT_EVT_F_DATA_TX) + dwEvtMask |= EV_TXEMPTY; + if (fEvtMask & RTSERIALPORT_EVT_F_BREAK_DETECTED) + dwEvtMask |= EV_BREAK; + if (fEvtMask & RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED) + dwEvtMask |= EV_CTS | EV_DSR | EV_RING | EV_RLSD; + + int rc = VINF_SUCCESS; + if (!SetCommMask(pThis->hDev, dwEvtMask)) + rc = RTErrConvertFromWin32(GetLastError()); + else + pThis->fEvtMask = fEvtMask; + + return rc; +} + + +/** + * Tries to set the default config on the given serial port. + * + * @returns IPRT status code. + * @param pThis The internal serial port instance data. + */ +static int rtSerialPortSetDefaultCfg(PRTSERIALPORTINTERNAL pThis) +{ + if (!PurgeComm(pThis->hDev, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR)) + return RTErrConvertFromWin32(GetLastError()); + + pThis->PortCfg.DCBlength = sizeof(pThis->PortCfg); + if (!GetCommState(pThis->hDev, &pThis->PortCfg)) + return RTErrConvertFromWin32(GetLastError()); + + pThis->PortCfg.BaudRate = CBR_9600; + pThis->PortCfg.fBinary = TRUE; + pThis->PortCfg.fParity = TRUE; + pThis->PortCfg.fDtrControl = DTR_CONTROL_DISABLE; + pThis->PortCfg.ByteSize = 8; + pThis->PortCfg.Parity = NOPARITY; + pThis->PortCfg.fOutxCtsFlow = FALSE; + pThis->PortCfg.fOutxDsrFlow = FALSE; + pThis->PortCfg.fDsrSensitivity = FALSE; + pThis->PortCfg.fTXContinueOnXoff = TRUE; + pThis->PortCfg.fOutX = FALSE; + pThis->PortCfg.fInX = FALSE; + pThis->PortCfg.fErrorChar = FALSE; + pThis->PortCfg.fNull = FALSE; + pThis->PortCfg.fRtsControl = RTS_CONTROL_DISABLE; + pThis->PortCfg.fAbortOnError = FALSE; + pThis->PortCfg.wReserved = 0; + pThis->PortCfg.XonLim = 5; + pThis->PortCfg.XoffLim = 5; + + int rc = VINF_SUCCESS; + if (!SetCommState(pThis->hDev, &pThis->PortCfg)) + rc = RTErrConvertFromWin32(GetLastError()); + + if (RT_SUCCESS(rc)) + { + /* + * Set timeouts for non blocking mode. + * See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_commtimeouts . + */ + COMMTIMEOUTS ComTimeouts; + RT_ZERO(ComTimeouts); + ComTimeouts.ReadIntervalTimeout = MAXDWORD; + if (!SetCommTimeouts(pThis->hDev, &ComTimeouts)) + rc = RTErrConvertFromWin32(GetLastError()); + } + + return rc; +} + + +/** + * Common worker for handling I/O completion. + * + * This is used by RTSerialPortClose, RTSerialPortWrite and RTPipeSerialPortNB. + * + * @returns IPRT status code. + * @param pThis The pipe instance handle. + */ +static int rtSerialPortWriteCheckCompletion(PRTSERIALPORTINTERNAL pThis) +{ + int rc = VINF_SUCCESS; + DWORD dwRc = WaitForSingleObject(pThis->OverlappedWrite.hEvent, 0); + if (dwRc == WAIT_OBJECT_0) + { + DWORD cbWritten = 0; + if (GetOverlappedResult(pThis->hDev, &pThis->OverlappedWrite, &cbWritten, TRUE)) + { + for (;;) + { + if (cbWritten >= pThis->cbBounceBufUsed) + { + pThis->fWritePending = false; + rc = VINF_SUCCESS; + break; + } + + /* resubmit the remainder of the buffer - can this actually happen? */ + memmove(&pThis->pbBounceBuf[0], &pThis->pbBounceBuf[cbWritten], pThis->cbBounceBufUsed - cbWritten); + rc = ResetEvent(pThis->OverlappedWrite.hEvent); Assert(rc == TRUE); + if (!WriteFile(pThis->hDev, pThis->pbBounceBuf, (DWORD)pThis->cbBounceBufUsed, + &cbWritten, &pThis->OverlappedWrite)) + { + if (GetLastError() == ERROR_IO_PENDING) + rc = VINF_TRY_AGAIN; + else + { + pThis->fWritePending = false; + rc = RTErrConvertFromWin32(GetLastError()); + } + break; + } + Assert(cbWritten > 0); + } + } + else + { + pThis->fWritePending = false; + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else if (dwRc == WAIT_TIMEOUT) + rc = VINF_TRY_AGAIN; + else + { + pThis->fWritePending = false; + if (dwRc == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + return rc; +} + + +/** + * Processes the received Windows comm events and converts them to our format. + * + * @returns IPRT status code. + * @param pThis The pipe instance handle. + * @param dwEventMask Event mask received. + * @param pfEvtsRecv Where to store the converted events. + */ +static int rtSerialPortEvtFlagsProcess(PRTSERIALPORTINTERNAL pThis, DWORD dwEventMask, uint32_t *pfEvtsRecv) +{ + int rc = VINF_SUCCESS; + + if (dwEventMask & EV_RXCHAR) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_DATA_RX; + if (dwEventMask & EV_TXEMPTY) + { + if (pThis->fWritePending) + { + rc = rtSerialPortWriteCheckCompletion(pThis); + if (rc == VINF_SUCCESS) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_DATA_TX; + else + rc = VINF_SUCCESS; + } + else + *pfEvtsRecv |= RTSERIALPORT_EVT_F_DATA_TX; + } + if (dwEventMask & EV_BREAK) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_BREAK_DETECTED; + if (dwEventMask & (EV_CTS | EV_DSR | EV_RING | EV_RLSD)) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED; + + return rc; +} + + +/** + * The internal comm event wait worker. + * + * @returns IPRT status code. + * @param pThis The pipe instance handle. + * @param msTimeout The timeout to wait for. + */ +static int rtSerialPortEvtWaitWorker(PRTSERIALPORTINTERNAL pThis, RTMSINTERVAL msTimeout) +{ + int rc = VINF_SUCCESS; + HANDLE ahWait[2]; + ahWait[0] = pThis->hEvtDev; + ahWait[1] = pThis->hEvtIntr; + + DWORD dwRet = WaitForMultipleObjects(2, ahWait, FALSE, msTimeout == RT_INDEFINITE_WAIT ? INFINITE : msTimeout); + if (dwRet == WAIT_TIMEOUT) + rc = VERR_TIMEOUT; + else if (dwRet == WAIT_FAILED) + rc = RTErrConvertFromWin32(GetLastError()); + else if (dwRet == WAIT_OBJECT_0) + rc = VINF_SUCCESS; + else + { + Assert(dwRet == WAIT_OBJECT_0 + 1); + rc = VERR_INTERRUPTED; + } + + return rc; +} + + +RTDECL(int) RTSerialPortOpen(PRTSERIALPORT phSerialPort, const char *pszPortAddress, uint32_t fFlags) +{ + AssertPtrReturn(phSerialPort, VERR_INVALID_POINTER); + AssertReturn(RT_VALID_PTR(pszPortAddress) && *pszPortAddress != '\0', VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTSERIALPORT_OPEN_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn((fFlags & RTSERIALPORT_OPEN_F_READ) || (fFlags & RTSERIALPORT_OPEN_F_WRITE), + VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PRTSERIALPORTINTERNAL pThis = (PRTSERIALPORTINTERNAL)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTSERIALPORT_MAGIC; + pThis->fOpenFlags = fFlags; + pThis->fEvtMask = 0; + pThis->fWritePending = false; + pThis->fEvtQueryPending = false; + pThis->pbBounceBuf = NULL; + pThis->cbBounceBufUsed = 0; + pThis->cbBounceBufAlloc = 0; + RT_ZERO(pThis->OverlappedEvt); + RT_ZERO(pThis->OverlappedWrite); + RT_ZERO(pThis->OverlappedRead); + pThis->hEvtDev = CreateEvent(NULL, TRUE, FALSE, NULL); + if (pThis->hEvtDev) + { + pThis->OverlappedEvt.hEvent = pThis->hEvtDev, + pThis->hEvtIntr = CreateEvent(NULL, FALSE, FALSE, NULL); + if (pThis->hEvtIntr) + { + pThis->hEvtWrite = CreateEvent(NULL, TRUE, TRUE, NULL); + if (pThis->hEvtWrite) + { + pThis->OverlappedWrite.hEvent = pThis->hEvtWrite; + pThis->hEvtRead = CreateEvent(NULL, TRUE, TRUE, NULL); + if (pThis->hEvtRead) + { + pThis->OverlappedRead.hEvent = pThis->hEvtRead; + DWORD fWinFlags = 0; + + if (fFlags & RTSERIALPORT_OPEN_F_WRITE) + fWinFlags |= GENERIC_WRITE; + if (fFlags & RTSERIALPORT_OPEN_F_READ) + fWinFlags |= GENERIC_READ; + + pThis->hDev = CreateFile(pszPortAddress, + fWinFlags, + 0, /* Must be opened with exclusive access. */ + NULL, /* No SECURITY_ATTRIBUTES structure. */ + OPEN_EXISTING, /* Must use OPEN_EXISTING. */ + FILE_FLAG_OVERLAPPED, /* Overlapped I/O. */ + NULL); + if (pThis->hDev) + { + rc = rtSerialPortSetDefaultCfg(pThis); + if (RT_SUCCESS(rc)) + { + *phSerialPort = pThis; + return rc; + } + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + CloseHandle(pThis->hEvtRead); + } + + CloseHandle(pThis->hEvtWrite); + } + + CloseHandle(pThis->hEvtIntr); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + CloseHandle(pThis->hEvtDev); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTSerialPortClose(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + if (pThis == NIL_RTSERIALPORT) + return VINF_SUCCESS; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + /* + * Do the cleanup. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTSERIALPORT_MAGIC_DEAD, RTSERIALPORT_MAGIC), VERR_INVALID_HANDLE); + + if (pThis->fWritePending) + rtSerialPortWriteCheckCompletion(pThis); + + CloseHandle(pThis->hDev); + CloseHandle(pThis->hEvtDev); + CloseHandle(pThis->hEvtWrite); + CloseHandle(pThis->hEvtRead); + CloseHandle(pThis->hEvtIntr); + pThis->hDev = NULL; + pThis->hEvtDev = NULL; + pThis->hEvtWrite = NULL; + pThis->hEvtRead = NULL; + pThis->hEvtIntr = NULL; + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(RTHCINTPTR) RTSerialPortToNative(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, -1); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, -1); + + return (RTHCINTPTR)pThis->hDev; +} + + +RTDECL(int) RTSerialPortRead(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER); + + /* + * Kick of an overlapped read. + */ + int rc = VINF_SUCCESS; + uint8_t *pbBuf = (uint8_t *)pvBuf; + + while ( cbToRead > 0 + && RT_SUCCESS(rc)) + { + BOOL fSucc = ResetEvent(pThis->OverlappedRead.hEvent); Assert(fSucc == TRUE); RT_NOREF(fSucc); + DWORD cbRead = 0; + if (ReadFile(pThis->hDev, pbBuf, + cbToRead <= ~(DWORD)0 ? (DWORD)cbToRead : ~(DWORD)0, + &cbRead, &pThis->OverlappedRead)) + { + if (pcbRead) + { + *pcbRead = cbRead; + break; + } + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + DWORD dwWait = WaitForSingleObject(pThis->OverlappedRead.hEvent, INFINITE); + if (dwWait == WAIT_OBJECT_0) + { + if (GetOverlappedResult(pThis->hDev, &pThis->OverlappedRead, &cbRead, TRUE /*fWait*/)) + { + if (pcbRead) + { + *pcbRead = cbRead; + break; + } + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + Assert(dwWait == WAIT_FAILED); + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + if (RT_SUCCESS(rc)) + { + cbToRead -= cbRead; + pbBuf += cbRead; + } + } + + return rc; +} + + +RTDECL(int) RTSerialPortReadNB(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + + *pcbRead = 0; + + /* Check whether there is data waiting in the input queue. */ + int rc = VINF_SUCCESS; + COMSTAT ComStat; RT_ZERO(ComStat); + if (ClearCommError(pThis->hDev, NULL, &ComStat)) + { + if (ComStat.cbInQue > 0) + { + DWORD dwToRead = RT_MIN(ComStat.cbInQue, (DWORD)cbToRead); + /* Kick of an overlapped read. It should return immediately */ + BOOL fSucc = ResetEvent(pThis->OverlappedRead.hEvent); Assert(fSucc == TRUE); RT_NOREF(fSucc); + DWORD cbRead = 0; + if ( cbToRead == 0 + || ReadFile(pThis->hDev, pvBuf, dwToRead, + &cbRead, &pThis->OverlappedRead)) + *pcbRead = cbRead; + else if (GetLastError() == ERROR_IO_PENDING) + { + /* This shouldn't actually happen, so turn this into a synchronous read. */ + if (GetOverlappedResult(pThis->hDev, &pThis->OverlappedRead, &cbRead, TRUE /*fWait*/)) + *pcbRead = cbRead; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VINF_TRY_AGAIN; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + return rc; +} + + +RTDECL(int) RTSerialPortWrite(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER); + + /* If I/O is pending, check if it has completed. */ + int rc = VINF_SUCCESS; + if (pThis->fWritePending) + rc = rtSerialPortWriteCheckCompletion(pThis); + if (rc == VINF_SUCCESS) + { + const uint8_t *pbBuf = (const uint8_t *)pvBuf; + + while ( cbToWrite > 0 + && RT_SUCCESS(rc)) + { + BOOL fSucc = ResetEvent(pThis->OverlappedWrite.hEvent); Assert(fSucc == TRUE); RT_NOREF(fSucc); + DWORD cbWritten = 0; + if (WriteFile(pThis->hDev, pbBuf, + cbToWrite <= ~(DWORD)0 ? (DWORD)cbToWrite : ~(DWORD)0, + &cbWritten, &pThis->OverlappedWrite)) + { + if (pcbWritten) + { + *pcbWritten = cbWritten; + break; + } + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + DWORD dwWait = WaitForSingleObject(pThis->OverlappedWrite.hEvent, INFINITE); + if (dwWait == WAIT_OBJECT_0) + { + if (GetOverlappedResult(pThis->hDev, &pThis->OverlappedWrite, &cbWritten, TRUE /*fWait*/)) + { + if (pcbWritten) + { + *pcbWritten = cbWritten; + break; + } + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + Assert(dwWait == WAIT_FAILED); + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + if (RT_SUCCESS(rc)) + { + cbToWrite -= cbWritten; + pbBuf += cbWritten; + } + } + } + + return rc; +} + + +RTDECL(int) RTSerialPortWriteNB(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + + /* If I/O is pending, check if it has completed. */ + int rc = VINF_SUCCESS; + if (pThis->fWritePending) + rc = rtSerialPortWriteCheckCompletion(pThis); + if (rc == VINF_SUCCESS) + { + Assert(!pThis->fWritePending); + + /* Do the bounce buffering. */ + if ( pThis->cbBounceBufAlloc < cbToWrite + && pThis->cbBounceBufAlloc < RTSERIALPORT_NT_SIZE) + { + if (cbToWrite > RTSERIALPORT_NT_SIZE) + cbToWrite = RTSERIALPORT_NT_SIZE; + void *pv = RTMemRealloc(pThis->pbBounceBuf, RT_ALIGN_Z(cbToWrite, _1K)); + if (pv) + { + pThis->pbBounceBuf = (uint8_t *)pv; + pThis->cbBounceBufAlloc = RT_ALIGN_Z(cbToWrite, _1K); + } + else + rc = VERR_NO_MEMORY; + } + else if (cbToWrite > RTSERIALPORT_NT_SIZE) + cbToWrite = RTSERIALPORT_NT_SIZE; + if (RT_SUCCESS(rc) && cbToWrite) + { + memcpy(pThis->pbBounceBuf, pvBuf, cbToWrite); + pThis->cbBounceBufUsed = (uint32_t)cbToWrite; + + /* Submit the write. */ + rc = ResetEvent(pThis->OverlappedWrite.hEvent); Assert(rc == TRUE); + DWORD cbWritten = 0; + if (WriteFile(pThis->hDev, pThis->pbBounceBuf, (DWORD)pThis->cbBounceBufUsed, + &cbWritten, &pThis->OverlappedWrite)) + { + *pcbWritten = RT_MIN(cbWritten, cbToWrite); /* paranoia^3 */ + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + *pcbWritten = cbToWrite; + pThis->fWritePending = true; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (RT_SUCCESS(rc)) + *pcbWritten = 0; + } + else if (RT_SUCCESS(rc)) + *pcbWritten = 0; + + return rc; +} + + +RTDECL(int) RTSerialPortCfgQueryCurrent(RTSERIALPORT hSerialPort, PRTSERIALPORTCFG pCfg) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + pCfg->uBaudRate = pThis->PortCfg.BaudRate; + switch (pThis->PortCfg.Parity) + { + case NOPARITY: + pCfg->enmParity = RTSERIALPORTPARITY_NONE; + break; + case EVENPARITY: + pCfg->enmParity = RTSERIALPORTPARITY_EVEN; + break; + case ODDPARITY: + pCfg->enmParity = RTSERIALPORTPARITY_ODD; + break; + case MARKPARITY: + pCfg->enmParity = RTSERIALPORTPARITY_MARK; + break; + case SPACEPARITY: + pCfg->enmParity = RTSERIALPORTPARITY_SPACE; + break; + default: + AssertFailed(); + return VERR_INTERNAL_ERROR; + } + + switch (pThis->PortCfg.ByteSize) + { + case 5: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_5BITS; + break; + case 6: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_6BITS; + break; + case 7: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_7BITS; + break; + case 8: + pCfg->enmDataBitCount = RTSERIALPORTDATABITS_8BITS; + break; + default: + AssertFailed(); + return VERR_INTERNAL_ERROR; + } + + switch (pThis->PortCfg.StopBits) + { + case ONESTOPBIT: + pCfg->enmStopBitCount = RTSERIALPORTSTOPBITS_ONE; + break; + case ONE5STOPBITS: + pCfg->enmStopBitCount = RTSERIALPORTSTOPBITS_ONEPOINTFIVE; + break; + case TWOSTOPBITS: + pCfg->enmStopBitCount = RTSERIALPORTSTOPBITS_TWO; + break; + default: + AssertFailed(); + return VERR_INTERNAL_ERROR; + } + + return VINF_SUCCESS; +} + + +RTDECL(int) RTSerialPortCfgSet(RTSERIALPORT hSerialPort, PCRTSERIALPORTCFG pCfg, PRTERRINFO pErrInfo) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + RT_NOREF(pErrInfo); + + DCB DcbNew; + memcpy(&DcbNew, &pThis->PortCfg, sizeof(DcbNew)); + DcbNew.BaudRate = pCfg->uBaudRate; + + switch (pCfg->enmParity) + { + case RTSERIALPORTPARITY_NONE: + DcbNew.Parity = NOPARITY; + break; + case RTSERIALPORTPARITY_EVEN: + DcbNew.Parity = EVENPARITY; + break; + case RTSERIALPORTPARITY_ODD: + DcbNew.Parity = ODDPARITY; + break; + case RTSERIALPORTPARITY_MARK: + DcbNew.Parity = MARKPARITY; + break; + case RTSERIALPORTPARITY_SPACE: + DcbNew.Parity = SPACEPARITY; + break; + default: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + + switch (pCfg->enmDataBitCount) + { + case RTSERIALPORTDATABITS_5BITS: + DcbNew.ByteSize = 5; + break; + case RTSERIALPORTDATABITS_6BITS: + DcbNew.ByteSize = 6; + break; + case RTSERIALPORTDATABITS_7BITS: + DcbNew.ByteSize = 7; + break; + case RTSERIALPORTDATABITS_8BITS: + DcbNew.ByteSize = 8; + break; + default: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + + switch (pCfg->enmStopBitCount) + { + case RTSERIALPORTSTOPBITS_ONE: + DcbNew.StopBits = ONESTOPBIT; + break; + case RTSERIALPORTSTOPBITS_ONEPOINTFIVE: + AssertReturn(pCfg->enmDataBitCount == RTSERIALPORTDATABITS_5BITS, VERR_INVALID_PARAMETER); + DcbNew.StopBits = ONE5STOPBITS; + break; + case RTSERIALPORTSTOPBITS_TWO: + AssertReturn(pCfg->enmDataBitCount != RTSERIALPORTDATABITS_5BITS, VERR_INVALID_PARAMETER); + DcbNew.StopBits = TWOSTOPBITS; + break; + default: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + + int rc = VINF_SUCCESS; + if (!SetCommState(pThis->hDev, &DcbNew)) + rc = RTErrConvertFromWin32(GetLastError()); + else + memcpy(&pThis->PortCfg, &DcbNew, sizeof(DcbNew)); + + return rc; +} + + +RTDECL(int) RTSerialPortEvtPoll(RTSERIALPORT hSerialPort, uint32_t fEvtMask, uint32_t *pfEvtsRecv, + RTMSINTERVAL msTimeout) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fEvtMask & ~RTSERIALPORT_EVT_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pfEvtsRecv, VERR_INVALID_POINTER); + + *pfEvtsRecv = 0; + + int rc = VINF_SUCCESS; + if (fEvtMask != pThis->fEvtMask) + { + rc = rtSerialPortWinUpdateEvtMask(pThis, fEvtMask); + if ( RT_SUCCESS(rc) + && pThis->fEvtQueryPending) + { + /* + * Setting a new event mask lets the WaitCommEvent() call finish immediately, + * so clean up and process any events here. + */ + rc = rtSerialPortEvtWaitWorker(pThis, 1); + AssertRC(rc); + + if (pThis->dwEventMask != 0) + { + pThis->fEvtQueryPending = false; + return rtSerialPortEvtFlagsProcess(pThis, pThis->dwEventMask, pfEvtsRecv); + } + } + } + + /* + * EV_RXCHAR is triggered only if a byte is received after the event mask is set, + * not if there is already something in the input buffer. Thatswhy we check the input + * buffer for any stored data and the output buffer whether it is empty and return + * the appropriate flags. + */ + if (RT_SUCCESS(rc)) + { + COMSTAT ComStat; RT_ZERO(ComStat); + if (!ClearCommError(pThis->hDev, NULL, &ComStat)) + return RTErrConvertFromWin32(GetLastError()); + + /* Check whether data is already waiting in the input buffer. */ + if ( (fEvtMask & RTSERIALPORT_EVT_F_DATA_RX) + && ComStat.cbInQue > 0) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_DATA_RX; + + /* Check whether the output buffer is empty. */ + if ( (fEvtMask & RTSERIALPORT_EVT_F_DATA_TX) + && ComStat.cbOutQue == 0) + *pfEvtsRecv |= RTSERIALPORT_EVT_F_DATA_TX; + + /* Return if there is at least one event. */ + if (*pfEvtsRecv != 0) + return VINF_SUCCESS; + } + + if (RT_SUCCESS(rc)) + { + /* Set up a new event wait if there is none pending. */ + if (!pThis->fEvtQueryPending) + { + RT_ZERO(pThis->OverlappedEvt); + pThis->OverlappedEvt.hEvent = pThis->hEvtDev; + + pThis->dwEventMask = 0; + pThis->fEvtQueryPending = true; + if (!WaitCommEvent(pThis->hDev, &pThis->dwEventMask, &pThis->OverlappedEvt)) + { + DWORD dwRet = GetLastError(); + if (dwRet == ERROR_IO_PENDING) + rc = VINF_SUCCESS; + else + { + rc = RTErrConvertFromWin32(GetLastError()); + pThis->fEvtQueryPending = false; + } + } + else + pThis->fEvtQueryPending = false; + } + + if ( RT_SUCCESS(rc) + && pThis->fEvtQueryPending) + rc = rtSerialPortEvtWaitWorker(pThis, msTimeout); + + if (RT_SUCCESS(rc)) + { + pThis->fEvtQueryPending = false; + rc = rtSerialPortEvtFlagsProcess(pThis, pThis->dwEventMask, pfEvtsRecv); + } + } + + return rc; +} + + +RTDECL(int) RTSerialPortEvtPollInterrupt(RTSERIALPORT hSerialPort) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + if (!SetEvent(pThis->hEvtIntr)) + return RTErrConvertFromWin32(GetLastError()); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTSerialPortChgBreakCondition(RTSERIALPORT hSerialPort, bool fSet) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + BOOL fSucc = FALSE; + if (fSet) + fSucc = SetCommBreak(pThis->hDev); + else + fSucc = ClearCommBreak(pThis->hDev); + + int rc = VINF_SUCCESS; + if (!fSucc) + rc = RTErrConvertFromWin32(GetLastError()); + + return rc; +} + + +RTDECL(int) RTSerialPortChgStatusLines(RTSERIALPORT hSerialPort, uint32_t fClear, uint32_t fSet) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + + BOOL fSucc = TRUE; + if (fSet & RTSERIALPORT_CHG_STS_LINES_F_DTR) + fSucc = EscapeCommFunction(pThis->hDev, SETDTR); + if ( fSucc + && (fSet & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + fSucc = EscapeCommFunction(pThis->hDev, SETRTS); + + if ( fSucc + && (fClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + fSucc = EscapeCommFunction(pThis->hDev, CLRDTR); + if ( fSucc + && (fClear & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + fSucc = EscapeCommFunction(pThis->hDev, CLRRTS); + + int rc = VINF_SUCCESS; + if (!fSucc) + rc = RTErrConvertFromWin32(GetLastError()); + + return rc; +} + + +RTDECL(int) RTSerialPortQueryStatusLines(RTSERIALPORT hSerialPort, uint32_t *pfStsLines) +{ + PRTSERIALPORTINTERNAL pThis = hSerialPort; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfStsLines, VERR_INVALID_POINTER); + + *pfStsLines = 0; + + int rc = VINF_SUCCESS; + DWORD fStsLinesQueried = 0; + + /* Get the new state */ + if (GetCommModemStatus(pThis->hDev, &fStsLinesQueried)) + { + *pfStsLines |= (fStsLinesQueried & MS_RLSD_ON) ? RTSERIALPORT_STS_LINE_DCD : 0; + *pfStsLines |= (fStsLinesQueried & MS_RING_ON) ? RTSERIALPORT_STS_LINE_RI : 0; + *pfStsLines |= (fStsLinesQueried & MS_DSR_ON) ? RTSERIALPORT_STS_LINE_DSR : 0; + *pfStsLines |= (fStsLinesQueried & MS_CTS_ON) ? RTSERIALPORT_STS_LINE_CTS : 0; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/shmem-win.cpp b/src/VBox/Runtime/r3/win/shmem-win.cpp new file mode 100644 index 00000000..b97fc796 --- /dev/null +++ b/src/VBox/Runtime/r3/win/shmem-win.cpp @@ -0,0 +1,473 @@ +/* $Id: shmem-win.cpp $ */ +/** @file + * IPRT - Named shared memory object, Windows Implementation. + */ + +/* + * Copyright (C) 2018-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/shmem.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/cdefs.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include "internal/magics.h" +#include "internal/path.h" +#include "internal-r3-win.h" /* For g_enmWinVer + kRTWinOSType_XXX */ + +/* + * Define values ourselves in case the compiling host is too old. + * See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createfilemappinga + * for when these were introduced. + */ +#ifndef PAGE_EXECUTE_READ +# define PAGE_EXECUTE_READ 0x20 +#endif +#ifndef PAGE_EXECUTE_READWRITE +# define PAGE_EXECUTE_READWRITE 0x40 +#endif +#ifndef PAGE_EXECUTE_WRITECOPY +# define PAGE_EXECUTE_WRITECOPY 0x80 +#endif +#ifndef FILE_MAP_EXECUTE +# define FILE_MAP_EXECUTE 0x20 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Shared memory object mapping descriptor. + */ +typedef struct RTSHMEMMAPPINGDESC +{ + /** Number of references held to this mapping, 0 if the descriptor is free. */ + volatile uint32_t cMappings; + /** Pointer to the region mapping. */ + void *pvMapping; + /** Start offset */ + size_t offRegion; + /** Size of the region. */ + size_t cbRegion; + /** Access flags for this region .*/ + uint32_t fFlags; +} RTSHMEMMAPPINGDESC; +/** Pointer to a shared memory object mapping descriptor. */ +typedef RTSHMEMMAPPINGDESC *PRTSHMEMMAPPINGDESC; +/** Pointer to a constant shared memory object mapping descriptor. */ +typedef const RTSHMEMMAPPINGDESC *PCRTSHMEMMAPPINGDESC; + + +/** + * Internal shared memory object state. + */ +typedef struct RTSHMEMINT +{ + /** Magic value (RTSHMEM_MAGIC). */ + uint32_t u32Magic; + /** Flag whether this instance created the named shared memory object. */ + bool fCreate; + /** Handle to the underlying mapping object. */ + HANDLE hShmObj; + /** Size of the mapping object in bytes. */ + size_t cbMax; + /** Overall number of mappings active for this shared memory object. */ + volatile uint32_t cMappings; + /** Maximum number of mapping descriptors allocated. */ + uint32_t cMappingDescsMax; + /** Number of mapping descriptors used. */ + volatile uint32_t cMappingDescsUsed; + /** Array of mapping descriptors - variable in size. */ + RTSHMEMMAPPINGDESC aMappingDescs[1]; +} RTSHMEMINT; +/** Pointer to the internal shared memory object state. */ +typedef RTSHMEMINT *PRTSHMEMINT; + + + + +/** + * Returns a mapping descriptor matching the given region properties or NULL if none was found. + * + * @returns Pointer to the matching mapping descriptor or NULL if not found. + * @param pThis Pointer to the shared memory object instance. + * @param offRegion Offset into the shared memory object to start mapping at. + * @param cbRegion Size of the region to map. + * @param fFlags Desired properties of the mapped region, combination of RTSHMEM_MAP_F_* defines. + */ +DECLINLINE(PRTSHMEMMAPPINGDESC) rtShMemMappingDescFindByProp(PRTSHMEMINT pThis, size_t offRegion, size_t cbRegion, uint32_t fFlags) +{ + for (uint32_t i = 0; i < pThis->cMappingDescsMax; i++) + { + if ( pThis->aMappingDescs[i].offRegion == offRegion + && pThis->aMappingDescs[i].cbRegion == cbRegion + && pThis->aMappingDescs[i].fFlags == fFlags) + return &pThis->aMappingDescs[i]; + } + + return NULL; +} + + +RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, size_t cbMax, uint32_t cMappingsHint) +{ + AssertPtrReturn(phShMem, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTSHMEM_O_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn(cMappingsHint < 64, VERR_OUT_OF_RANGE); + AssertReturn(cbMax > 0 || !(fFlags & RTSHMEM_O_F_CREATE), VERR_NOT_SUPPORTED); + + if (fFlags & RTSHMEM_O_F_TRUNCATE) + return VERR_NOT_SUPPORTED; + + /* + * The executable access was introduced with Windows XP SP2 and Windows Server 2003 SP1, + * PAGE_EXECUTE_WRITECOPY was not available until Windows Vista SP1. + * Allow execute mappings only starting from Windows 7 to keep the checks simple here (lazy coder). + */ + if ( (fFlags & RTSHMEM_O_F_MAYBE_EXEC) + && g_enmWinVer < kRTWinOSType_7) + return VERR_NOT_SUPPORTED; + + cMappingsHint = cMappingsHint == 0 ? 5 : cMappingsHint; + int rc = VINF_SUCCESS; + PRTSHMEMINT pThis = (PRTSHMEMINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTSHMEMINT, aMappingDescs[cMappingsHint])); + if (RT_LIKELY(pThis)) + { + pThis->u32Magic = RTSHMEM_MAGIC; + /*pThis->fCreate = false; */ + /*pThis->cMappings = 0; */ + pThis->cMappingDescsMax = cMappingsHint; + /*pThis->cMappingDescsUsed = 0; */ + /* Construct the filename, always use the local namespace, global requires special privileges. */ + char szName[RTPATH_MAX]; + ssize_t cch = RTStrPrintf2(&szName[0], sizeof(szName), "Local\\%s", pszName); + if (cch > 0) + { + PRTUTF16 pwszName = NULL; + rc = RTStrToUtf16Ex(&szName[0], RTSTR_MAX, &pwszName, 0, NULL); + if (RT_SUCCESS(rc)) + { + if (fFlags & RTSHMEM_O_F_CREATE) + { +#if HC_ARCH_BITS == 64 + DWORD dwSzMaxHigh = cbMax >> 32; +#elif HC_ARCH_BITS == 32 + DWORD dwSzMaxHigh = 0; +#else +# error "Port me" +#endif + DWORD dwSzMaxLow = cbMax & UINT32_C(0xffffffff); + DWORD fProt = 0; + + if (fFlags & RTSHMEM_O_F_MAYBE_EXEC) + { + if ((fFlags & RTSHMEM_O_F_READWRITE) == RTSHMEM_O_F_READ) + fProt |= PAGE_EXECUTE_READ; + else + fProt |= PAGE_EXECUTE_READWRITE; + } + else + { + if ((fFlags & RTSHMEM_O_F_READWRITE) == RTSHMEM_O_F_READ) + fProt |= PAGE_READONLY; + else + fProt |= PAGE_READWRITE; + } + pThis->hShmObj = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, fProt, + dwSzMaxHigh, dwSzMaxLow, pwszName); + } + else + { + DWORD fProt = SECTION_QUERY; + if (fFlags & RTSHMEM_O_F_MAYBE_EXEC) + fProt |= FILE_MAP_EXECUTE; + if (fFlags & RTSHMEM_O_F_READ) + fProt |= FILE_MAP_READ; + if (fFlags & RTSHMEM_O_F_WRITE) + fProt |= FILE_MAP_WRITE; + + pThis->hShmObj = OpenFileMappingW(fProt, FALSE, pwszName); + } + RTUtf16Free(pwszName); + if (pThis->hShmObj != NULL) + { + *phShMem = pThis; + return VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + rc = VERR_BUFFER_OVERFLOW; + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTShMemClose(RTSHMEM hShMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->cMappings, VERR_INVALID_STATE); + + int rc = VINF_SUCCESS; + if (CloseHandle(pThis->hShmObj)) + { + pThis->u32Magic = RTSHMEM_MAGIC_DEAD; + RTMemFree(pThis); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + return rc; +} + + +RTDECL(int) RTShMemDelete(const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(*pszName != '\0', VERR_INVALID_PARAMETER); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(uint32_t) RTShMemRefCount(RTSHMEM hShMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, 0); + + return pThis->cMappings; +} + + +RTDECL(int) RTShMemSetSize(RTSHMEM hShMem, size_t cbMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->cMappings, VERR_INVALID_STATE); + AssertReturn(cbMem, VERR_NOT_SUPPORTED); + + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTShMemQuerySize(RTSHMEM hShMem, size_t *pcbMem) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pcbMem, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + SECTION_BASIC_INFORMATION SecInf; + SIZE_T cbRet; + NTSTATUS rcNt = NtQuerySection(pThis->hShmObj, SectionBasicInformation, &SecInf, sizeof(SecInf), &cbRet); + if (NT_SUCCESS(rcNt)) + { + AssertReturn(cbRet == sizeof(SecInf), VERR_INTERNAL_ERROR); +#if HC_ARCH_BITS == 32 + AssertReturn(SecInf.MaximumSize.HighPart == 0, VERR_INTERNAL_ERROR_2); + *pcbMem = SecInf.MaximumSize.LowPart; +#elif HC_ARCH_BITS == 64 + *pcbMem = SecInf.MaximumSize.QuadPart; +#else +# error "Port me" +#endif + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + return rc; +} + + +RTDECL(int) RTShMemMapRegion(RTSHMEM hShMem, size_t offRegion, size_t cbRegion, uint32_t fFlags, void **ppv) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(ppv, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTSHMEM_MAP_F_VALID_MASK), VERR_INVALID_PARAMETER); + + /* See comment in RTShMemOpen(). */ + if ( (fFlags & RTSHMEM_MAP_F_EXEC) + && g_enmWinVer < kRTWinOSType_7) + return VERR_NOT_SUPPORTED; + + /* Try to find a mapping with compatible parameters first. */ + PRTSHMEMMAPPINGDESC pMappingDesc = NULL; + for (uint32_t iTry = 0; iTry < 10; iTry++) + { + pMappingDesc = rtShMemMappingDescFindByProp(pThis, offRegion, cbRegion, fFlags); + if (!pMappingDesc) + break; + + /* Increase the mapping count and check that the region is still accessible by us. */ + if ( ASMAtomicIncU32(&pMappingDesc->cMappings) > 1 + && pMappingDesc->offRegion == offRegion + && pMappingDesc->cbRegion == cbRegion + && pMappingDesc->fFlags == fFlags) + break; + /* Mapping was freed inbetween, next round. */ + } + + int rc = VINF_SUCCESS; + if (!pMappingDesc) + { + /* Find an empty region descriptor and map the region. */ + for (uint32_t i = 0; i < pThis->cMappingDescsMax && !pMappingDesc; i++) + { + if (!pThis->aMappingDescs[i].cMappings) + { + pMappingDesc = &pThis->aMappingDescs[i]; + + /* Try to grab this one. */ + if (ASMAtomicIncU32(&pMappingDesc->cMappings) == 1) + break; + + /* Somebody raced us, drop reference and continue. */ + ASMAtomicDecU32(&pMappingDesc->cMappings); + pMappingDesc = NULL; + } + } + + if (RT_LIKELY(pMappingDesc)) + { + /* Try to map it. */ + DWORD fProt = 0; + DWORD offLow = offRegion & UINT32_C(0xffffffff); +#if HC_ARCH_BITS == 64 + DWORD offHigh = offRegion >> 32; +#elif HC_ARCH_BITS == 32 + DWORD offHigh = 0; +#else +# error "Port me" +#endif + if (fFlags & RTSHMEM_MAP_F_READ) + fProt |= FILE_MAP_READ; + if (fFlags & RTSHMEM_MAP_F_WRITE) + fProt |= FILE_MAP_WRITE; + if (fFlags & RTSHMEM_MAP_F_EXEC) + fProt |= FILE_MAP_EXECUTE; + if (fFlags & RTSHMEM_MAP_F_COW) + fProt |= FILE_MAP_COPY; + + void *pv = MapViewOfFile(pThis->hShmObj, fProt, offHigh, offLow, cbRegion); + if (pv != NULL) + { + pMappingDesc->pvMapping = pv; + pMappingDesc->offRegion = offRegion; + pMappingDesc->cbRegion = cbRegion; + pMappingDesc->fFlags = fFlags; + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + ASMAtomicDecU32(&pMappingDesc->cMappings); + } + } + else + rc = VERR_SHMEM_MAXIMUM_MAPPINGS_REACHED; + } + + if (RT_SUCCESS(rc)) + { + *ppv = pMappingDesc->pvMapping; + ASMAtomicIncU32(&pThis->cMappings); + } + + return rc; +} + + +RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) +{ + PRTSHMEMINT pThis = hShMem; + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u32Magic == RTSHMEM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pv, VERR_INVALID_PARAMETER); + + /* Find the mapping descriptor by the given region address. */ + PRTSHMEMMAPPINGDESC pMappingDesc = NULL; + for (uint32_t i = 0; i < pThis->cMappingDescsMax && !pMappingDesc; i++) + { + if (pThis->aMappingDescs[i].pvMapping == pv) + { + pMappingDesc = &pThis->aMappingDescs[i]; + break; + } + } + + AssertPtrReturn(pMappingDesc, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + if (!ASMAtomicDecU32(&pMappingDesc->cMappings)) + { + /* Last mapping of this region was unmapped, so do the real unmapping now. */ + if (UnmapViewOfFile(pv)) + { + ASMAtomicDecU32(&pThis->cMappingDescsUsed); + ASMAtomicDecU32(&pThis->cMappings); + } + else + { + ASMAtomicIncU32(&pMappingDesc->cMappings); + rc = RTErrConvertFromWin32(GetLastError()); + } + } + + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/symlink-win.cpp b/src/VBox/Runtime/r3/win/symlink-win.cpp new file mode 100644 index 00000000..a0c5f1e2 --- /dev/null +++ b/src/VBox/Runtime/r3/win/symlink-win.cpp @@ -0,0 +1,367 @@ +/* $Id: symlink-win.cpp $ */ +/** @file + * IPRT - Symbolic Links, Windows. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_SYMLINK +#include <iprt/win/windows.h> + +#include <iprt/symlink.h> +#include "internal-r3-win.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include "internal/path.h" + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct MY_REPARSE_DATA_BUFFER +{ + ULONG ReparseTag; +#define MY_IO_REPARSE_TAG_SYMLINK 0xa000000c +#define MY_IO_REPARSE_TAG_MOUNT_POINT 0xa0000003 + + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; +#define MY_SYMLINK_FLAG_RELATIVE 1 + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} MY_REPARSE_DATA_BUFFER; +#define MY_FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) + + +RTDECL(bool) RTSymlinkExists(const char *pszSymlink) +{ + bool fRc = false; + RTFSOBJINFO ObjInfo; + int rc = RTPathQueryInfoEx(pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + fRc = RTFS_IS_SYMLINK(ObjInfo.Attr.fMode); + + LogFlow(("RTSymlinkExists(%p={%s}): returns %RTbool\n", pszSymlink, pszSymlink, fRc)); + return fRc; +} + + +RTDECL(bool) RTSymlinkIsDangling(const char *pszSymlink) +{ + bool fRc = false; + RTFSOBJINFO ObjInfo; + int rc = RTPathQueryInfoEx(pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + fRc = RTFS_IS_SYMLINK(ObjInfo.Attr.fMode); + if (fRc) + { + rc = RTPathQueryInfoEx(pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + fRc = !RT_SUCCESS_NP(rc); + } + } + + LogFlow(("RTSymlinkIsDangling(%p={%s}): returns %RTbool\n", pszSymlink, pszSymlink, fRc)); + return fRc; +} + + +RTDECL(int) RTSymlinkCreate(const char *pszSymlink, const char *pszTarget, RTSYMLINKTYPE enmType, uint32_t fCreate) +{ + /* + * Validate the input. + */ + AssertReturn(enmType > RTSYMLINKTYPE_INVALID && enmType < RTSYMLINKTYPE_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszSymlink, VERR_INVALID_POINTER); + AssertPtrReturn(pszTarget, VERR_INVALID_POINTER); + RT_NOREF_PV(fCreate); + + /* + * Resolve the API. + */ + typedef BOOLEAN (WINAPI *PFNCREATESYMBOLICLINKW)(LPCWSTR, LPCWSTR, DWORD); + static PFNCREATESYMBOLICLINKW s_pfnCreateSymbolicLinkW = NULL; + static bool s_fTried = FALSE; + if (!s_fTried) + { + PFNCREATESYMBOLICLINKW pfn = (PFNCREATESYMBOLICLINKW)GetProcAddress(g_hModKernel32, "CreateSymbolicLinkW"); + if (pfn) + s_pfnCreateSymbolicLinkW = pfn; + s_fTried = true; + } + if (!s_pfnCreateSymbolicLinkW) + { + LogFlow(("RTSymlinkCreate(%p={%s}, %p={%s}, %d, %#x): returns VERR_NOT_SUPPORTED - Windows API not found\n", + pszSymlink, pszSymlink, pszTarget, pszTarget, enmType, fCreate)); + return VERR_NOT_SUPPORTED; + } + + /* + * Convert the paths. + */ + PRTUTF16 pwszNativeSymlink; + int rc = RTPathWinFromUtf8(&pwszNativeSymlink, pszSymlink, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszNativeTarget; + rc = RTPathWinFromUtf8(&pwszNativeTarget, pszTarget, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + /* The link target path must use backslashes to work reliably. */ + RTUTF16 wc; + PRTUTF16 pwsz = pwszNativeTarget; + while ((wc = *pwsz) != '\0') + { + if (wc == '/') + *pwsz = '\\'; + pwsz++; + } + + /* + * Massage the target path, determin the link type. + */ + size_t cchTarget = strlen(pszTarget); + size_t cchVolSpecTarget = rtPathVolumeSpecLen(pszTarget); +#if 0 /* looks like this isn't needed after all. That makes everything much simper :-) */ + if ( cchTarget > RT_MIN(cchVolSpecTarget, 1) + && RTPATH_IS_SLASH(pszTarget[cchTarget - 1])) + { + size_t cwcNativeTarget = RTUtf16Len(pwszNativeTarget); + size_t offFromEnd = 1; + while ( offFromEnd < cchTarget + && cchTarget - offFromEnd >= cchVolSpecTarget + && RTPATH_IS_SLASH(pszTarget[cchTarget - offFromEnd])) + { + Assert(offFromEnd < cwcNativeTarget); + pwszNativeTarget[cwcNativeTarget - offFromEnd] = 0; + offFromEnd++; + } + } +#endif + + if (enmType == RTSYMLINKTYPE_UNKNOWN) + { + if ( cchTarget > cchVolSpecTarget + && RTPATH_IS_SLASH(pszTarget[cchTarget - 1])) + enmType = RTSYMLINKTYPE_DIR; + else if (cchVolSpecTarget) + { + /** @todo this is subject to sharing violations. */ + DWORD dwAttr = GetFileAttributesW(pwszNativeTarget); + if ( dwAttr != INVALID_FILE_ATTRIBUTES + && (dwAttr & FILE_ATTRIBUTE_DIRECTORY)) + enmType = RTSYMLINKTYPE_DIR; + } + else + { + /** @todo Join the symlink directory with the target and + * look up the attributes on that. -lazy bird. */ + } + } + + /* + * Create the link. + */ + if (s_pfnCreateSymbolicLinkW(pwszNativeSymlink, pwszNativeTarget, enmType == RTSYMLINKTYPE_DIR)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + + RTPathWinFree(pwszNativeTarget); + } + RTPathWinFree(pwszNativeSymlink); + } + + LogFlow(("RTSymlinkCreate(%p={%s}, %p={%s}, %d, %#x): returns %Rrc\n", pszSymlink, pszSymlink, pszTarget, pszTarget, enmType, fCreate, rc)); + return rc; +} + + +RTDECL(int) RTSymlinkDelete(const char *pszSymlink, uint32_t fDelete) +{ + RT_NOREF_PV(fDelete); + + /* + * Convert the path. + */ + PRTUTF16 pwszNativeSymlink; + int rc = RTPathWinFromUtf8(&pwszNativeSymlink, pszSymlink, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + /* + * We have to use different APIs depending on whether this is a + * directory or file link. This means we're subject to one more race + * than on posix at the moment. We could probably avoid this though, + * if we wanted to go talk with the native API layer below Win32... + */ + DWORD dwAttr = GetFileAttributesW(pwszNativeSymlink); + if (dwAttr != INVALID_FILE_ATTRIBUTES) + { + if (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) + { + BOOL fRc; + if (dwAttr & FILE_ATTRIBUTE_DIRECTORY) + fRc = RemoveDirectoryW(pwszNativeSymlink); + else + fRc = DeleteFileW(pwszNativeSymlink); + if (fRc) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = VERR_NOT_SYMLINK; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + RTPathWinFree(pwszNativeSymlink); + } + + LogFlow(("RTSymlinkDelete(%p={%s}, %#x): returns %Rrc\n", pszSymlink, pszSymlink, fDelete, rc)); + return rc; +} + + +RTDECL(int) RTSymlinkRead(const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead) +{ + RT_NOREF_PV(fRead); + + char *pszMyTarget; + int rc = RTSymlinkReadA(pszSymlink, &pszMyTarget); + if (RT_SUCCESS(rc)) + { + rc = RTStrCopy(pszTarget, cbTarget, pszMyTarget); + RTStrFree(pszMyTarget); + } + LogFlow(("RTSymlinkRead(%p={%s}): returns %Rrc\n", pszSymlink, pszSymlink, rc)); + return rc; +} + + +RTDECL(int) RTSymlinkReadA(const char *pszSymlink, char **ppszTarget) +{ + AssertPtr(ppszTarget); + PRTUTF16 pwszNativeSymlink; + int rc = RTPathWinFromUtf8(&pwszNativeSymlink, pszSymlink, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + HANDLE hSymlink = CreateFileW(pwszNativeSymlink, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hSymlink != INVALID_HANDLE_VALUE) + { + DWORD cbReturned = 0; + union + { + MY_REPARSE_DATA_BUFFER Buf; + uint8_t abBuf[16*_1K + sizeof(WCHAR)]; + } u; + if (DeviceIoControl(hSymlink, + MY_FSCTL_GET_REPARSE_POINT, + NULL /*pInBuffer */, + 0 /*cbInBuffer */, + &u.Buf, + sizeof(u) - sizeof(WCHAR), + &cbReturned, + NULL /*pOverlapped*/)) + { + if (u.Buf.ReparseTag == MY_IO_REPARSE_TAG_SYMLINK) + { + PWCHAR pwszTarget = &u.Buf.SymbolicLinkReparseBuffer.PathBuffer[0]; + pwszTarget += u.Buf.SymbolicLinkReparseBuffer.SubstituteNameOffset / 2; + pwszTarget[u.Buf.SymbolicLinkReparseBuffer.SubstituteNameLength / 2] = 0; + if ( !(u.Buf.SymbolicLinkReparseBuffer.Flags & MY_SYMLINK_FLAG_RELATIVE) + && pwszTarget[0] == '\\' + && pwszTarget[1] == '?' + && pwszTarget[2] == '?' + && pwszTarget[3] == '\\' + && pwszTarget[4] != 0 + ) + pwszTarget += 4; + rc = RTUtf16ToUtf8(pwszTarget, ppszTarget); + } + else + rc = VERR_NOT_SYMLINK; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + CloseHandle(hSymlink); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + RTPathWinFree(pwszNativeSymlink); + } + + if (RT_SUCCESS(rc)) + LogFlow(("RTSymlinkReadA(%p={%s},%p): returns %Rrc *ppszTarget=%p:{%s}\n", pszSymlink, pszSymlink, ppszTarget, rc, *ppszTarget, *ppszTarget)); + else + LogFlow(("RTSymlinkReadA(%p={%s},%p): returns %Rrc\n", pszSymlink, pszSymlink, ppszTarget, rc)); + return rc; +} + diff --git a/src/VBox/Runtime/r3/win/system-get-nt-xxx-win.cpp b/src/VBox/Runtime/r3/win/system-get-nt-xxx-win.cpp new file mode 100644 index 00000000..30088667 --- /dev/null +++ b/src/VBox/Runtime/r3/win/system-get-nt-xxx-win.cpp @@ -0,0 +1,68 @@ +/* $Id: system-get-nt-xxx-win.cpp $ */ +/** @file + * IPRT - RTSystemQueryOSInfo, generic stub. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/win/windows.h> + +#include "internal-r3-win.h" +#include <iprt/system.h> +#include <iprt/assert.h> + + +RTDECL(uint32_t) RTSystemGetNtBuildNo(void) +{ + Assert(g_WinOsInfoEx.dwOSVersionInfoSize > 0); + return g_WinOsInfoEx.dwBuildNumber; +} + + +RTDECL(uint64_t) RTSystemGetNtVersion(void) +{ + Assert(g_WinOsInfoEx.dwOSVersionInfoSize > 0); + return RTSYSTEM_MAKE_NT_VERSION(g_WinOsInfoEx.dwMajorVersion, g_WinOsInfoEx.dwMinorVersion, g_WinOsInfoEx.dwBuildNumber); +} + + +RTDECL(uint8_t) RTSystemGetNtProductType(void) +{ + Assert(g_WinOsInfoEx.dwOSVersionInfoSize > 0); + return g_WinOsInfoEx.wProductType; /* It's a byte, not a word as 'w' normally indicates. (Baka Maikurosofuto!) */ +} + diff --git a/src/VBox/Runtime/r3/win/thread-win.cpp b/src/VBox/Runtime/r3/win/thread-win.cpp new file mode 100644 index 00000000..80bd3879 --- /dev/null +++ b/src/VBox/Runtime/r3/win/thread-win.cpp @@ -0,0 +1,588 @@ +/* $Id: thread-win.cpp $ */ +/** @file + * IPRT - Threads, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <iprt/nt/nt-and-windows.h> + +#ifndef IPRT_NO_CRT +# include <errno.h> +# include <process.h> +#endif + +#include <iprt/thread.h> +#include "internal/iprt.h" + +#include <iprt/asm-amd64-x86.h> +#include <iprt/assert.h> +#include <iprt/cpuset.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include "internal/thread.h" +#include "internal-r3-win.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** SetThreadDescription */ +typedef HRESULT (WINAPI *PFNSETTHREADDESCRIPTION)(HANDLE hThread, WCHAR *pwszName); /* Since W10 1607 */ + +/** CoInitializeEx */ +typedef HRESULT (WINAPI *PFNCOINITIALIZEEX)(LPVOID, DWORD); +/** CoUninitialize */ +typedef void (WINAPI *PFNCOUNINITIALIZE)(void); +/** OleUninitialize */ +typedef void (WINAPI *PFNOLEUNINITIALIZE)(void); + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The TLS index allocated for storing the RTTHREADINT pointer. */ +static DWORD g_dwSelfTLS = TLS_OUT_OF_INDEXES; +/** Pointer to SetThreadDescription (KERNEL32.DLL) if available. */ +static PFNSETTHREADDESCRIPTION g_pfnSetThreadDescription = NULL; + +/** Pointer to CoInitializeEx (OLE32.DLL / combase.dll) if available. */ +static PFNCOINITIALIZEEX volatile g_pfnCoInitializeEx = NULL; +/** Pointer to CoUninitialize (OLE32.DLL / combase.dll) if available. */ +static PFNCOUNINITIALIZE volatile g_pfnCoUninitialize = NULL; +/** Pointer to OleUninitialize (OLE32.DLL / combase.dll) if available. */ +static PFNOLEUNINITIALIZE volatile g_pfnOleUninitialize = NULL; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void rtThreadWinTellDebuggerThreadName(uint32_t idThread, const char *pszName); +DECLINLINE(void) rtThreadWinSetThreadName(PRTTHREADINT pThread, DWORD idThread); + + +DECLHIDDEN(int) rtThreadNativeInit(void) +{ + g_dwSelfTLS = TlsAlloc(); + if (g_dwSelfTLS == TLS_OUT_OF_INDEXES) + return VERR_NO_TLS_FOR_SELF; + + g_pfnSetThreadDescription = (PFNSETTHREADDESCRIPTION)GetProcAddress(g_hModKernel32, "SetThreadDescription"); + return VINF_SUCCESS; +} + + +DECLHIDDEN(void) rtThreadNativeReInitObtrusive(void) +{ + /* nothing to do here. */ +} + + +DECLHIDDEN(void) rtThreadNativeDetach(void) +{ + /* + * Deal with alien threads. + */ + PRTTHREADINT pThread = (PRTTHREADINT)TlsGetValue(g_dwSelfTLS); + if ( pThread + && (pThread->fIntFlags & RTTHREADINT_FLAGS_ALIEN)) + { + rtThreadTerminate(pThread, 0); + TlsSetValue(g_dwSelfTLS, NULL); + } +} + + +DECLHIDDEN(void) rtThreadNativeDestroy(PRTTHREADINT pThread) +{ + if (pThread == (PRTTHREADINT)TlsGetValue(g_dwSelfTLS)) + TlsSetValue(g_dwSelfTLS, NULL); + + if ((HANDLE)pThread->hThread != INVALID_HANDLE_VALUE) + { + CloseHandle((HANDLE)pThread->hThread); + pThread->hThread = (uintptr_t)INVALID_HANDLE_VALUE; + } +} + + +DECLHIDDEN(int) rtThreadNativeAdopt(PRTTHREADINT pThread) +{ + if (!TlsSetValue(g_dwSelfTLS, pThread)) + return VERR_FAILED_TO_SET_SELF_TLS; + rtThreadWinSetThreadName(pThread, GetCurrentThreadId()); + return VINF_SUCCESS; +} + + +DECLHIDDEN(void) rtThreadNativeInformDebugger(PRTTHREADINT pThread) +{ + rtThreadWinTellDebuggerThreadName((uint32_t)(uintptr_t)pThread->Core.Key, pThread->szName); +} + + +/** + * Communicates the thread name to the debugger, if we're begin debugged that + * is. + * + * See http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx for debugger + * interface details. + * + * @param idThread The thread ID. UINT32_MAX for current thread. + * @param pszName The name. + */ +static void rtThreadWinTellDebuggerThreadName(uint32_t idThread, const char *pszName) +{ + struct + { + uint32_t uType; + const char *pszName; + uint32_t idThread; + uint32_t fFlags; + } Pkg = { 0x1000, pszName, idThread, 0 }; + __try + { + RaiseException(0x406d1388, 0, sizeof(Pkg)/sizeof(ULONG_PTR), (ULONG_PTR *)&Pkg); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + + } +} + + +/** + * Sets the thread name as best as we can. + */ +DECLINLINE(void) rtThreadWinSetThreadName(PRTTHREADINT pThread, DWORD idThread) +{ + if (g_pfnIsDebuggerPresent && g_pfnIsDebuggerPresent()) + rtThreadWinTellDebuggerThreadName(idThread, &pThread->szName[0]); + + /* The SetThreadDescription API introduced in windows 10 1607 / server 2016 + allows setting the thread name while the debugger isn't attached. Works + with WinDbgX, VisualStudio 2017 v15.6+, and presumeably some recent windbg + version. */ + if (g_pfnSetThreadDescription) + { + /* The name should be ASCII, so we just need to expand 'char' to 'WCHAR'. */ + WCHAR wszName[RTTHREAD_NAME_LEN]; + for (size_t i = 0; i < RTTHREAD_NAME_LEN; i++) + wszName[i] = pThread->szName[i]; + + HRESULT hrc = g_pfnSetThreadDescription(GetCurrentThread(), wszName); + Assert(SUCCEEDED(hrc)); RT_NOREF(hrc); + } +} + + +/** + * Bitch about dangling COM and OLE references, dispose of them + * afterwards so we don't end up deadlocked somewhere below + * OLE32!DllMain. + */ +static void rtThreadNativeUninitComAndOle(void) +{ +#if 1 /* experimental code */ + /* + * Read the counters. + */ + struct MySOleTlsData + { + void *apvReserved0[2]; /**< x86=0x00 W7/64=0x00 */ + DWORD adwReserved0[3]; /**< x86=0x08 W7/64=0x10 */ + void *apvReserved1[1]; /**< x86=0x14 W7/64=0x20 */ + DWORD cComInits; /**< x86=0x18 W7/64=0x28 */ + DWORD cOleInits; /**< x86=0x1c W7/64=0x2c */ + DWORD dwReserved1; /**< x86=0x20 W7/64=0x30 */ + void *apvReserved2[4]; /**< x86=0x24 W7/64=0x38 */ + DWORD adwReserved2[1]; /**< x86=0x34 W7/64=0x58 */ + void *pvCurrentCtx; /**< x86=0x38 W7/64=0x60 */ + IUnknown *pCallState; /**< x86=0x3c W7/64=0x68 */ + } *pOleTlsData = NULL; /* outside the try/except for debugging */ + DWORD cComInits = 0; + DWORD cOleInits = 0; + __try + { + void *pvTeb = NtCurrentTeb(); +# ifdef RT_ARCH_AMD64 + pOleTlsData = *(struct MySOleTlsData **)((uintptr_t)pvTeb + 0x1758); /*TEB.ReservedForOle*/ +# elif RT_ARCH_X86 + pOleTlsData = *(struct MySOleTlsData **)((uintptr_t)pvTeb + 0x0f80); /*TEB.ReservedForOle*/ +# else +# error "Port me!" +# endif + if (pOleTlsData) + { + cComInits = pOleTlsData->cComInits; + cOleInits = pOleTlsData->cOleInits; + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + AssertFailedReturnVoid(); + } + + /* + * Assert sanity. If any of these breaks, the structure layout above is + * probably not correct any longer. + */ + AssertMsgReturnVoid(cComInits < 1000, ("%u (%#x)\n", cComInits, cComInits)); + AssertMsgReturnVoid(cOleInits < 1000, ("%u (%#x)\n", cOleInits, cOleInits)); + AssertMsgReturnVoid(cComInits >= cOleInits, ("cComInits=%#x cOleInits=%#x\n", cComInits, cOleInits)); + + /* + * Do the uninitializing. + */ + if (cComInits) + { + AssertMsgFailed(("cComInits=%u (%#x) cOleInits=%u (%#x) - dangling COM/OLE inits!\n", + cComInits, cComInits, cOleInits, cOleInits)); + + PFNOLEUNINITIALIZE pfnOleUninitialize = g_pfnOleUninitialize; + PFNCOUNINITIALIZE pfnCoUninitialize = g_pfnCoUninitialize; + if (pfnCoUninitialize && pfnOleUninitialize) + { /* likely */ } + else + { + HMODULE hOle32 = GetModuleHandle("ole32.dll"); + AssertReturnVoid(hOle32 != NULL); + + pfnOleUninitialize = (PFNOLEUNINITIALIZE)GetProcAddress(hOle32, "OleUninitialize"); + AssertReturnVoid(pfnOleUninitialize); + + pfnCoUninitialize = (PFNCOUNINITIALIZE)GetProcAddress(hOle32, "CoUninitialize"); + AssertReturnVoid(pfnCoUninitialize); + } + + while (cOleInits-- > 0) + { + pfnOleUninitialize(); + cComInits--; + } + + while (cComInits-- > 0) + pfnCoUninitialize(); + } +#endif +} + + +/** + * Implements the RTTHREADFLAGS_COM_MTA and RTTHREADFLAGS_COM_STA flags. + * + * @returns true if COM uninitialization should be done, false if not. + * @param fFlags The thread flags. + */ +static bool rtThreadNativeWinCoInitialize(unsigned fFlags) +{ + /* + * Resolve the ole32 init and uninit functions dynamically. + */ + PFNCOINITIALIZEEX pfnCoInitializeEx = g_pfnCoInitializeEx; + PFNCOUNINITIALIZE pfnCoUninitialize = g_pfnCoUninitialize; + if (pfnCoInitializeEx && pfnCoUninitialize) + { /* likely */ } + else + { + RTLDRMOD hModOle32 = NIL_RTLDRMOD; + int rc = RTLdrLoadSystem("ole32.dll", true /*fNoUnload*/, &hModOle32); + AssertRCReturn(rc, false); + + PFNOLEUNINITIALIZE pfnOleUninitialize; + pfnOleUninitialize = (PFNOLEUNINITIALIZE)RTLdrGetFunction(hModOle32, "OleUninitialize"); + pfnCoUninitialize = (PFNCOUNINITIALIZE )RTLdrGetFunction(hModOle32, "CoUninitialize"); + pfnCoInitializeEx = (PFNCOINITIALIZEEX )RTLdrGetFunction(hModOle32, "CoInitializeEx"); + + RTLdrClose(hModOle32); + AssertReturn(pfnCoInitializeEx && pfnCoUninitialize, false); + + if (pfnOleUninitialize && !g_pfnOleUninitialize) + g_pfnOleUninitialize = pfnOleUninitialize; + g_pfnCoInitializeEx = pfnCoInitializeEx; + g_pfnCoUninitialize = pfnCoUninitialize; + } + + /* + * Do the initializating. + */ + DWORD fComInit; + if (fFlags & RTTHREADFLAGS_COM_MTA) + fComInit = COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY | COINIT_DISABLE_OLE1DDE; + else + fComInit = COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY; + HRESULT hrc = pfnCoInitializeEx(NULL, fComInit); + AssertMsg(SUCCEEDED(hrc), ("%Rhrc fComInit=%#x\n", hrc, fComInit)); + return SUCCEEDED(hrc); +} + + +/** + * Wrapper which unpacks the param stuff and calls thread function. + */ +#ifndef IPRT_NO_CRT +static unsigned __stdcall rtThreadNativeMain(void *pvArgs) RT_NOTHROW_DEF +#else +static DWORD __stdcall rtThreadNativeMain(void *pvArgs) RT_NOTHROW_DEF +#endif +{ + DWORD dwThreadId = GetCurrentThreadId(); + PRTTHREADINT pThread = (PRTTHREADINT)pvArgs; + + if (!TlsSetValue(g_dwSelfTLS, pThread)) + AssertReleaseMsgFailed(("failed to set self TLS. lasterr=%d thread '%s'\n", GetLastError(), pThread->szName)); + rtThreadWinSetThreadName(pThread, dwThreadId); + + bool fUninitCom = (pThread->fFlags & (RTTHREADFLAGS_COM_MTA | RTTHREADFLAGS_COM_STA)) != 0; + if (fUninitCom) + fUninitCom = rtThreadNativeWinCoInitialize(pThread->fFlags); + + int rc = rtThreadMain(pThread, dwThreadId, &pThread->szName[0]); + + TlsSetValue(g_dwSelfTLS, NULL); /* rtThreadMain already released the structure. */ + + if (fUninitCom && g_pfnCoUninitialize) + g_pfnCoUninitialize(); + + rtThreadNativeUninitComAndOle(); +#ifndef IPRT_NO_CRT + _endthreadex(rc); + return rc; /* not reached */ +#else + for (;;) + ExitThread(rc); +#endif +} + + +DECLHIDDEN(int) rtThreadNativeCreate(PRTTHREADINT pThread, PRTNATIVETHREAD pNativeThread) +{ + AssertReturn(pThread->cbStack < ~(unsigned)0, VERR_INVALID_PARAMETER); + + /* + * If a stack size is given, make sure it's not a multiple of 64KB so that we + * get one or more pages for overflow protection. (ASSUMES 64KB alloc align.) + */ + unsigned cbStack = (unsigned)pThread->cbStack; + if (cbStack > 0 && RT_ALIGN_T(cbStack, _64K, unsigned) == cbStack) + cbStack += PAGE_SIZE; + + /* + * Create the thread. + */ + pThread->hThread = (uintptr_t)INVALID_HANDLE_VALUE; +#ifndef IPRT_NO_CRT + unsigned uThreadId = 0; + uintptr_t hThread = _beginthreadex(NULL /*pSecAttrs*/, cbStack, rtThreadNativeMain, pThread, 0 /*fFlags*/, &uThreadId); + if (hThread != 0 && hThread != ~0U) + { + pThread->hThread = hThread; + *pNativeThread = uThreadId; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +#else + DWORD idThread = 0; + HANDLE hThread = CreateThread(NULL /*pSecAttrs*/, cbStack, rtThreadNativeMain, pThread, 0 /*fFlags*/, &idThread); + if (hThread != NULL) + { + pThread->hThread = (uintptr_t)hThread; + *pNativeThread = idThread; + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(GetLastError()); +#endif +} + + +DECLHIDDEN(bool) rtThreadNativeIsAliveKludge(PRTTHREADINT pThread) +{ + PPEB_COMMON pPeb = NtCurrentPeb(); + if (!pPeb || !pPeb->Ldr || !pPeb->Ldr->ShutdownInProgress) + return true; + DWORD rcWait = WaitForSingleObject((HANDLE)pThread->hThread, 0); + return rcWait != WAIT_OBJECT_0; +} + + +RTDECL(RTTHREAD) RTThreadSelf(void) +{ + PRTTHREADINT pThread = (PRTTHREADINT)TlsGetValue(g_dwSelfTLS); + /** @todo import alien threads ? */ + return pThread; +} + + +#if 0 /* noone is using this ... */ +/** + * Returns the processor number the current thread was running on during this call + * + * @returns processor nr + */ +static int rtThreadGetCurrentProcessorNumber(void) +{ + static bool fInitialized = false; + static DWORD (WINAPI *pfnGetCurrentProcessorNumber)(void) = NULL; + if (!fInitialized) + { + HMODULE hmodKernel32 = GetModuleHandle("kernel32.dll"); + if (hmodKernel32) + pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)(void))GetProcAddress(hmodKernel32, "GetCurrentProcessorNumber"); + fInitialized = true; + } + if (pfnGetCurrentProcessorNumber) + return pfnGetCurrentProcessorNumber(); + return -1; +} +#endif + + +RTR3DECL(int) RTThreadSetAffinity(PCRTCPUSET pCpuSet) +{ + /* The affinity functionality was added in NT 3.50, so we resolve the APIs + dynamically to be able to run on NT 3.1. */ + if (g_pfnSetThreadAffinityMask) + { + DWORD_PTR fNewMask = pCpuSet ? RTCpuSetToU64(pCpuSet) : ~(DWORD_PTR)0; + DWORD_PTR dwRet = g_pfnSetThreadAffinityMask(GetCurrentThread(), fNewMask); + if (dwRet) + return VINF_SUCCESS; + + int iLastError = GetLastError(); + AssertMsgFailed(("SetThreadAffinityMask failed, LastError=%d\n", iLastError)); + return RTErrConvertFromWin32(iLastError); + } + return VERR_NOT_SUPPORTED; +} + + +RTR3DECL(int) RTThreadGetAffinity(PRTCPUSET pCpuSet) +{ + /* The affinity functionality was added in NT 3.50, so we resolve the APIs + dynamically to be able to run on NT 3.1. */ + if ( g_pfnSetThreadAffinityMask + && g_pfnGetProcessAffinityMask) + { + /* + * Haven't found no query api, but the set api returns the old mask, so let's use that. + */ + DWORD_PTR dwIgnored; + DWORD_PTR dwProcAff = 0; + if (g_pfnGetProcessAffinityMask(GetCurrentProcess(), &dwProcAff, &dwIgnored)) + { + HANDLE hThread = GetCurrentThread(); + DWORD_PTR dwRet = g_pfnSetThreadAffinityMask(hThread, dwProcAff); + if (dwRet) + { + DWORD_PTR dwSet = g_pfnSetThreadAffinityMask(hThread, dwRet); + Assert(dwSet == dwProcAff); NOREF(dwRet); + + RTCpuSetFromU64(pCpuSet, (uint64_t)dwSet); + return VINF_SUCCESS; + } + } + + int iLastError = GetLastError(); + AssertMsgFailed(("SetThreadAffinityMask or GetProcessAffinityMask failed, LastError=%d\n", iLastError)); + return RTErrConvertFromWin32(iLastError); + } + return VERR_NOT_SUPPORTED; +} + + +RTR3DECL(int) RTThreadGetExecutionTimeMilli(uint64_t *pKernelTime, uint64_t *pUserTime) +{ + uint64_t u64CreationTime, u64ExitTime, u64KernelTime, u64UserTime; + + if (GetThreadTimes(GetCurrentThread(), (LPFILETIME)&u64CreationTime, (LPFILETIME)&u64ExitTime, (LPFILETIME)&u64KernelTime, (LPFILETIME)&u64UserTime)) + { + *pKernelTime = u64KernelTime / 10000; /* GetThreadTimes returns time in 100 ns units */ + *pUserTime = u64UserTime / 10000; /* GetThreadTimes returns time in 100 ns units */ + return VINF_SUCCESS; + } + + int iLastError = GetLastError(); + AssertMsgFailed(("GetThreadTimes failed, LastError=%d\n", iLastError)); + return RTErrConvertFromWin32(iLastError); +} + + +/** + * Gets the native thread handle for a IPRT thread. + * + * @returns The thread handle. INVALID_HANDLE_VALUE on failure. + * @param hThread The IPRT thread handle. + * + * @note Windows only. + * @note Only valid after parent returns from the thread creation call. + */ +RTDECL(uintptr_t) RTThreadGetNativeHandle(RTTHREAD hThread) +{ + PRTTHREADINT pThread = rtThreadGet(hThread); + if (pThread) + { + uintptr_t hHandle = pThread->hThread; + rtThreadRelease(pThread); + return hHandle; + } + return (uintptr_t)INVALID_HANDLE_VALUE; +} +RT_EXPORT_SYMBOL(RTThreadGetNativeHandle); + + +RTDECL(int) RTThreadPoke(RTTHREAD hThread) +{ + AssertReturn(hThread != RTThreadSelf(), VERR_INVALID_PARAMETER); + if (g_pfnNtAlertThread) + { + PRTTHREADINT pThread = rtThreadGet(hThread); + AssertReturn(pThread, VERR_INVALID_HANDLE); + + NTSTATUS rcNt = g_pfnNtAlertThread((HANDLE)pThread->hThread); + + rtThreadRelease(pThread); + if (NT_SUCCESS(rcNt)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(rcNt); + } + return VERR_NOT_IMPLEMENTED; +} + diff --git a/src/VBox/Runtime/r3/win/thread2-win.cpp b/src/VBox/Runtime/r3/win/thread2-win.cpp new file mode 100644 index 00000000..f9874c96 --- /dev/null +++ b/src/VBox/Runtime/r3/win/thread2-win.cpp @@ -0,0 +1,84 @@ +/* $Id: thread2-win.cpp $ */ +/** @file + * IPRT - Threads part 2, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <iprt/win/windows.h> + +#include <iprt/thread.h> +#include "internal/iprt.h" + +#include <iprt/asm-amd64-x86.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include "internal/thread.h" + + +RTDECL(RTNATIVETHREAD) RTThreadNativeSelf(void) +{ + return (RTNATIVETHREAD)GetCurrentThreadId(); +} + + +RTR3DECL(int) RTThreadSleep(RTMSINTERVAL cMillies) +{ + LogFlow(("RTThreadSleep: cMillies=%d\n", cMillies)); + Sleep(cMillies); + LogFlow(("RTThreadSleep: returning %Rrc (cMillies=%d)\n", VINF_SUCCESS, cMillies)); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTThreadSleepNoLog(RTMSINTERVAL cMillies) +{ + Sleep(cMillies); + return VINF_SUCCESS; +} + + +RTR3DECL(bool) RTThreadYield(void) +{ + uint64_t u64TS = ASMReadTSC(); + Sleep(0); + u64TS = ASMReadTSC() - u64TS; + bool fRc = u64TS > 1500; + LogFlow(("RTThreadYield: returning %d (%llu ticks)\n", fRc, u64TS)); + return fRc; +} + diff --git a/src/VBox/Runtime/r3/win/time-win.cpp b/src/VBox/Runtime/r3/win/time-win.cpp new file mode 100644 index 00000000..09e42d8e --- /dev/null +++ b/src/VBox/Runtime/r3/win/time-win.cpp @@ -0,0 +1,228 @@ +/* $Id: time-win.cpp $ */ +/** @file + * IPRT - Time, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/time.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include "internal/time.h" +#include "internal-r3-win.h" + +/* + * Note! The selected time source be the exact same one as we use in kernel land! + */ +//#define USE_TICK_COUNT +//#define USE_PERFORMANCE_COUNTER +//# define USE_FILE_TIME +//#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) +# define USE_INTERRUPT_TIME +//#else +//# define USE_TICK_COUNT +//#endif + + + +DECLINLINE(uint64_t) rtTimeGetSystemNanoTS(void) +{ +#if defined USE_TICK_COUNT + /* + * This would work if it didn't flip over every 49 (or so) days. + */ + return (uint64_t)GetTickCount() * RT_NS_1MS_64; + +#elif defined USE_PERFORMANCE_COUNTER + /* + * Slow and not derived from InterruptTime. + */ + static LARGE_INTEGER llFreq; + static unsigned uMult; + if (!llFreq.QuadPart) + { + if (!QueryPerformanceFrequency(&llFreq)) + return (uint64_t)GetTickCount() * RT_NS_1MS_64; + llFreq.QuadPart /= 1000; + uMult = 1000000; /* no math genius, but this seemed to help avoiding floating point. */ + } + + LARGE_INTEGER ll; + if (QueryPerformanceCounter(&ll)) + return (ll.QuadPart * uMult) / llFreq.QuadPart; + return (uint64_t)GetTickCount() * RT_NS_1MS_64; + +#elif defined USE_FILE_TIME + /* + * This is SystemTime not InterruptTime. + */ + uint64_t u64; /* manual say larger integer, should be safe to assume it's the same. */ + GetSystemTimeAsFileTime((LPFILETIME)&u64); + return u64 * 100; + +#elif defined USE_INTERRUPT_TIME + /* + * Use interrupt time if we can (not possible on NT 3.1). + * Note! We cannot entirely depend on g_enmWinVer here as we're likely to + * get called before IPRT is initialized. Ditto g_hModNtDll. + */ + static PFNRTLGETINTERRUPTTIMEPRECISE s_pfnRtlGetInterruptTimePrecise = NULL; + static int volatile s_iCanUseUserSharedData = -1; + int iCanUseUserSharedData = s_iCanUseUserSharedData; + if (iCanUseUserSharedData != -1) + { /* likely */ } + else + { + /* We may be called before g_enmWinVer has been initialized. */ + if (g_enmWinVer != kRTWinOSType_UNKNOWN) + iCanUseUserSharedData = g_enmWinVer > kRTWinOSType_NT310; + else + { + DWORD dwVer = GetVersion(); + iCanUseUserSharedData = (dwVer & 0xff) != 3 || ((dwVer >> 8) & 0xff) >= 50; + } + if (iCanUseUserSharedData != 0) + { + FARPROC pfn = GetProcAddress(g_hModNtDll ? g_hModNtDll : GetModuleHandleW(L"ntdll"), "RtlGetInterruptTimePrecise"); + if (pfn != NULL) + { + ASMAtomicWritePtr(&s_pfnRtlGetInterruptTimePrecise, pfn); + iCanUseUserSharedData = 42; + } + } + s_iCanUseUserSharedData = iCanUseUserSharedData; + } + + if (iCanUseUserSharedData != 0) + { + LARGE_INTEGER Time; + if (iCanUseUserSharedData == 42) + { + uint64_t iIgnored; + Time.QuadPart = s_pfnRtlGetInterruptTimePrecise(&iIgnored); + } + else + { + PKUSER_SHARED_DATA pUserSharedData = (PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA; + do + { + Time.HighPart = pUserSharedData->InterruptTime.High1Time; + Time.LowPart = pUserSharedData->InterruptTime.LowPart; + } while (pUserSharedData->InterruptTime.High2Time != Time.HighPart); + } + + return (uint64_t)Time.QuadPart * 100; + } + + return (uint64_t)GetTickCount() * RT_NS_1MS_64; + +#else +# error "Must select a method bright guy!" +#endif +} + + +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return rtTimeGetSystemNanoTS(); +} + + +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return rtTimeGetSystemNanoTS() / RT_NS_1MS; +} + + +RTDECL(PRTTIMESPEC) RTTimeNow(PRTTIMESPEC pTime) +{ + uint64_t u64; + AssertCompile(sizeof(u64) == sizeof(FILETIME)); + if (g_pfnGetSystemTimeAsFileTime) + g_pfnGetSystemTimeAsFileTime((LPFILETIME)&u64); + else + { + SYSTEMTIME SysTime = {0}; + GetSystemTime(&SysTime); + BOOL fRet = SystemTimeToFileTime(&SysTime, (LPFILETIME)&u64); + Assert(fRet); RT_NOREF(fRet); + } + return RTTimeSpecSetNtTime(pTime, u64); + +} + + +RTDECL(PRTTIMESPEC) RTTimeLocalNow(PRTTIMESPEC pTime) +{ + uint64_t u64Local; + if (g_pfnGetSystemTimeAsFileTime) + { + uint64_t u64; + AssertCompile(sizeof(u64) == sizeof(FILETIME)); + g_pfnGetSystemTimeAsFileTime((LPFILETIME)&u64); + if (!FileTimeToLocalFileTime((FILETIME const *)&u64, (LPFILETIME)&u64Local)) + u64Local = u64; + } + else + { + SYSTEMTIME SysTime = {0}; + GetLocalTime(&SysTime); + BOOL fRet = SystemTimeToFileTime(&SysTime, (LPFILETIME)&u64Local); + Assert(fRet); RT_NOREF(fRet); + } + return RTTimeSpecSetNtTime(pTime, u64Local); +} + + +RTDECL(int64_t) RTTimeLocalDeltaNano(void) +{ + /* + * UTC = local + Tzi.Bias; + * The bias is given in minutes. + */ + TIME_ZONE_INFORMATION Tzi; + Tzi.Bias = 0; + if (GetTimeZoneInformation(&Tzi) != TIME_ZONE_ID_INVALID) + return -(int64_t)Tzi.Bias * 60 * RT_NS_1SEC_64; + return 0; +} + diff --git a/src/VBox/Runtime/r3/win/time2-win.cpp b/src/VBox/Runtime/r3/win/time2-win.cpp new file mode 100644 index 00000000..d4f0db06 --- /dev/null +++ b/src/VBox/Runtime/r3/win/time2-win.cpp @@ -0,0 +1,164 @@ +/* $Id: time2-win.cpp $ */ +/** @file + * IPRT - Time, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#include <iprt/win/windows.h> + +#include <iprt/time.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include "internal/time.h" + +#include "internal-r3-win.h" + + +RTDECL(int) RTTimeSet(PCRTTIMESPEC pTime) +{ + FILETIME FileTime; + SYSTEMTIME SysTime; + if (FileTimeToSystemTime(RTTimeSpecGetNtFileTime(pTime, &FileTime), &SysTime)) + { + if (SetSystemTime(&SysTime)) + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(GetLastError()); +} + + +RTDECL(PRTTIME) RTTimeLocalExplode(PRTTIME pTime, PCRTTIMESPEC pTimeSpec) +{ + RTTIMESPEC LocalTime; + if (g_pfnSystemTimeToTzSpecificLocalTime) + { + /* + * FileTimeToLocalFileTime does not do the right thing, so we'll have + * to convert to system time and SystemTimeToTzSpecificLocalTime instead. + * + * Note! FileTimeToSystemTime drops resoultion down to milliseconds, thus + * we have to do the offUTC calculation using milliseconds and adjust + * u32Nanosecons by sub milliseconds digits. + */ + SYSTEMTIME SystemTimeIn; + FILETIME FileTime; + if (FileTimeToSystemTime(RTTimeSpecGetNtFileTime(pTimeSpec, &FileTime), &SystemTimeIn)) + { + SYSTEMTIME SystemTimeOut; + if (g_pfnSystemTimeToTzSpecificLocalTime(NULL /* use current TZI */, &SystemTimeIn, &SystemTimeOut)) + { + if (SystemTimeToFileTime(&SystemTimeOut, &FileTime)) + { + RTTimeSpecSetNtFileTime(&LocalTime, &FileTime); + pTime = RTTimeExplode(pTime, &LocalTime); + if (pTime) + { + pTime->fFlags = (pTime->fFlags & ~RTTIME_FLAGS_TYPE_MASK) | RTTIME_FLAGS_TYPE_LOCAL; + pTime->offUTC = (RTTimeSpecGetMilli(&LocalTime) - RTTimeSpecGetMilli(pTimeSpec)) / RT_MS_1MIN; + pTime->u32Nanosecond += RTTimeSpecGetNano(pTimeSpec) % RT_NS_1MS; + } + return pTime; + } + } + } + } + + /* + * The fallback is to use the current offset. + * (A better fallback would be to use the offset of the same time of the year.) + */ + LocalTime = *pTimeSpec; + int64_t cNsUtcOffset = RTTimeLocalDeltaNano(); + RTTimeSpecAddNano(&LocalTime, cNsUtcOffset); + pTime = RTTimeExplode(pTime, &LocalTime); + if (pTime) + { + pTime->fFlags = (pTime->fFlags & ~RTTIME_FLAGS_TYPE_MASK) | RTTIME_FLAGS_TYPE_LOCAL; + pTime->offUTC = cNsUtcOffset / RT_NS_1MIN; + } + return pTime; +} + + +/** + * Gets the delta between UTC and local time at the given time. + * + * @code + * RTTIMESPEC LocalTime; + * RTTimeNow(&LocalTime); + * RTTimeSpecAddNano(&LocalTime, RTTimeLocalDeltaNanoFor(&LocalTime)); + * @endcode + * + * @param pTimeSpec The time spec giving the time to get the delta for. + * @returns Returns the nanosecond delta between UTC and local time. + */ +RTDECL(int64_t) RTTimeLocalDeltaNanoFor(PCRTTIMESPEC pTimeSpec) +{ + RTTIMESPEC LocalTime; + if (g_pfnSystemTimeToTzSpecificLocalTime) + { + /* + * FileTimeToLocalFileTime does not do the right thing, so we'll have + * to convert to system time and SystemTimeToTzSpecificLocalTime instead. + * + * Note! FileTimeToSystemTime drops resoultion down to milliseconds, thus + * we have to do the offUTC calculation using milliseconds and adjust + * u32Nanosecons by sub milliseconds digits. + */ + SYSTEMTIME SystemTimeIn; + FILETIME FileTime; + if (FileTimeToSystemTime(RTTimeSpecGetNtFileTime(pTimeSpec, &FileTime), &SystemTimeIn)) + { + SYSTEMTIME SystemTimeOut; + if (g_pfnSystemTimeToTzSpecificLocalTime(NULL /* use current TZI */, &SystemTimeIn, &SystemTimeOut)) + { + if (SystemTimeToFileTime(&SystemTimeOut, &FileTime)) + { + RTTimeSpecSetNtFileTime(&LocalTime, &FileTime); + + return (RTTimeSpecGetMilli(&LocalTime) - RTTimeSpecGetMilli(pTimeSpec)) * RT_NS_1MS; + } + } + } + } + + return RTTimeLocalDeltaNano(); +} + diff --git a/src/VBox/Runtime/r3/win/timer-win.cpp b/src/VBox/Runtime/r3/win/timer-win.cpp new file mode 100644 index 00000000..f8d4a520 --- /dev/null +++ b/src/VBox/Runtime/r3/win/timer-win.cpp @@ -0,0 +1,520 @@ +/* $Id: timer-win.cpp $ */ +/** @file + * IPRT - Timer. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIMER +#define _WIN32_WINNT 0x0500 +#include <iprt/win/windows.h> + +#include <iprt/timer.h> +#ifdef USE_CATCH_UP +# include <iprt/time.h> +#endif +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/asm.h> +#include <iprt/semaphore.h> +#include <iprt/err.h> +#include "internal/magics.h" +#include "internal-r3-win.h" + + +/** Define the flag for creating a manual reset timer if not available in the SDK we are compiling with. */ +#ifndef CREATE_WAITABLE_TIMER_MANUAL_RESET +# define CREATE_WAITABLE_TIMER_MANUAL_RESET 0x00000001 +#endif +/** Define the flag for high resolution timers, available since Windows 10 RS4 if not available. */ +#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION +# define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 +#endif + + +RT_C_DECLS_BEGIN +/* from sysinternals. */ +NTSYSAPI LONG NTAPI NtSetTimerResolution(IN ULONG DesiredResolution, IN BOOLEAN SetResolution, OUT PULONG CurrentResolution); +NTSYSAPI LONG NTAPI NtQueryTimerResolution(OUT PULONG MaximumResolution, OUT PULONG MinimumResolution, OUT PULONG CurrentResolution); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The internal representation of a timer handle. + */ +typedef struct RTTIMER +{ + /** Magic. + * This is RTTIMER_MAGIC, but changes to something else before the timer + * is destroyed to indicate clearly that thread should exit. */ + uint32_t volatile u32Magic; + /** Flag indicating the timer is suspended. */ + bool volatile fSuspended; + /** Flag indicating that the timer has been destroyed. */ + bool volatile fDestroyed; + /** User argument. */ + void *pvUser; + /** Callback. */ + PFNRTTIMER pfnTimer; + /** The current tick. */ + uint64_t iTick; + /** The timer interval. 0 if one-shot. */ + uint64_t u64NanoInterval; + /** The first shot interval. 0 if ASAP. */ + uint64_t volatile u64NanoFirst; + /** Time handle. */ + HANDLE hTimer; + /** USE_CATCH_UP: ns time of the next tick. + * !USE_CATCH_UP: -uMilliesInterval * 10000 */ + LARGE_INTEGER llNext; + /** The thread handle of the timer thread. */ + RTTHREAD Thread; + /** Event semaphore on which the thread is blocked. */ + RTSEMEVENT Event; + /** The error/status of the timer. + * Initially -1, set to 0 when the timer have been successfully started, and + * to errno on failure in starting the timer. */ + volatile int iError; +} RTTIMER; + + + +/** + * Timer thread. + */ +static DECLCALLBACK(int) rttimerCallback(RTTHREAD hThreadSelf, void *pvArg) +{ + PRTTIMER pTimer = (PRTTIMER)(void *)pvArg; + Assert(pTimer->u32Magic == RTTIMER_MAGIC); + + /* + * Bounce our priority up quite a bit. + */ + if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) + { + int rc = GetLastError(); + AssertMsgFailed(("Failed to set priority class lasterror %d.\n", rc)); + pTimer->iError = RTErrConvertFromWin32(rc); + RTThreadUserSignal(hThreadSelf); + return rc; + } + + /* + * The work loop. + */ + RTThreadUserSignal(hThreadSelf); + + while ( !pTimer->fDestroyed + && pTimer->u32Magic == RTTIMER_MAGIC) + { + /* + * Wait for a start or destroy event. + */ + if (pTimer->fSuspended) + { + int rc = RTSemEventWait(pTimer->Event, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc) && rc != VERR_INTERRUPTED) + { + AssertRC(rc); + if (pTimer->fDestroyed) + continue; + RTThreadSleep(1000); /* Don't cause trouble! */ + } + if ( pTimer->fSuspended + || pTimer->fDestroyed) + continue; + } + + /* + * Start the waitable timer. + */ + pTimer->llNext.QuadPart = -(int64_t)pTimer->u64NanoInterval / 100; + LARGE_INTEGER ll; + if (pTimer->u64NanoFirst) + { + GetSystemTimeAsFileTime((LPFILETIME)&ll); + ll.QuadPart += pTimer->u64NanoFirst / 100; + pTimer->u64NanoFirst = 0; + } + else + ll.QuadPart = -(int64_t)pTimer->u64NanoInterval / 100; + if (!SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE)) + { + ASMAtomicXchgBool(&pTimer->fSuspended, true); + int rc = GetLastError(); + AssertMsgFailed(("Failed to set timer, lasterr %d.\n", rc)); + pTimer->iError = RTErrConvertFromWin32(rc); + RTThreadUserSignal(hThreadSelf); + continue; /* back to suspended mode. */ + } + pTimer->iError = 0; + RTThreadUserSignal(hThreadSelf); + + /* + * Timer Service Loop. + */ + do + { + int rc = WaitForSingleObjectEx(pTimer->hTimer, INFINITE, FALSE); + if (pTimer->u32Magic != RTTIMER_MAGIC) + break; + if (rc == WAIT_OBJECT_0) + { + /* + * Callback the handler. + */ + pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick); + + /* + * Rearm the timer handler. + */ + ll = pTimer->llNext; + BOOL fRc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE); + AssertMsg(fRc || pTimer->u32Magic != RTTIMER_MAGIC, ("last error %d\n", GetLastError())); NOREF(fRc); + } + else + { + /* + * We failed during wait, so just signal the destructor and exit. + */ + int rc2 = GetLastError(); + RTThreadUserSignal(hThreadSelf); + AssertMsgFailed(("Wait on hTimer failed, rc=%d lasterr=%d\n", rc, rc2)); NOREF(rc2); + return -1; + } + } while (RT_LIKELY( !pTimer->fSuspended + && !pTimer->fDestroyed + && pTimer->u32Magic == RTTIMER_MAGIC)); + + /* + * Disable the timer. + */ + int rc = CancelWaitableTimer (pTimer->hTimer); RT_NOREF(rc); + AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError())); + + /* + * ACK any pending suspend request. + */ + if (!pTimer->fDestroyed) + { + pTimer->iError = 0; + RTThreadUserSignal(hThreadSelf); + } + } + + /* + * Exit. + */ + pTimer->iError = 0; + RTThreadUserSignal(hThreadSelf); + return VINF_SUCCESS; +} + + +/** + * Tries to set the NT timer resolution to a value matching the given timer interval. + * + * @returns IPRT status code. + * @param u64NanoInterval The timer interval in nano seconds. + */ +static int rtTimerNtSetTimerResolution(uint64_t u64NanoInterval) +{ + /* + * On windows we'll have to set the timer resolution before + * we start the timer. + */ + ULONG ulMax = UINT32_MAX; + ULONG ulMin = UINT32_MAX; + ULONG ulCur = UINT32_MAX; + ULONG ulReq = (ULONG)(u64NanoInterval / 100); + NtQueryTimerResolution(&ulMax, &ulMin, &ulCur); + Log(("NtQueryTimerResolution -> ulMax=%lu00ns ulMin=%lu00ns ulCur=%lu00ns\n", ulMax, ulMin, ulCur)); + if (ulCur > ulMin && ulCur > ulReq) + { + ulReq = RT_MIN(ulMin, ulReq); + if (NtSetTimerResolution(ulReq, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to %lu*100ns.\n", ulReq)); + else if (NtSetTimerResolution(10000, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to 1ms.\n")); + else if (NtSetTimerResolution(20000, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to 2ms.\n")); + else if (NtSetTimerResolution(40000, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to 4ms.\n")); + else if (ulMin <= 50000 && NtSetTimerResolution(ulMin, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to %lu *100ns.\n", ulMin)); + else + { + AssertMsgFailed(("Failed to configure timer resolution!\n")); + return VERR_INTERNAL_ERROR; + } + } + + return VINF_SUCCESS; +} + + +RTDECL(int) RTTimerCreateEx(PRTTIMER *ppTimer, uint64_t u64NanoInterval, uint32_t fFlags, PFNRTTIMER pfnTimer, void *pvUser) +{ + /* + * We don't support the fancy MP features. + */ + if (fFlags & RTTIMER_FLAGS_CPU_SPECIFIC) + return VERR_NOT_SUPPORTED; + + /* + * Create new timer. + */ + int rc = VERR_IPE_UNINITIALIZED_STATUS; + PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer)); + if (pTimer) + { + pTimer->u32Magic = RTTIMER_MAGIC; + pTimer->fSuspended = true; + pTimer->fDestroyed = false; + pTimer->Thread = NIL_RTTHREAD; + pTimer->pfnTimer = pfnTimer; + pTimer->pvUser = pvUser; + pTimer->u64NanoInterval = u64NanoInterval; + + rc = RTSemEventCreate(&pTimer->Event); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /* + * Create Win32 waitable timer. + * We will first try the undocumented CREATE_WAITABLE_TIMER_HIGH_RESOLUTION which + * exists since some Windows 10 version (RS4). If this fails we resort to the old + * method of setting the timer resolution before creating a timer which will probably + * not give us the accuracy for intervals below the system tick resolution. + */ + pTimer->iError = 0; + if (g_pfnCreateWaitableTimerExW) + pTimer->hTimer = g_pfnCreateWaitableTimerExW(NULL, NULL, + CREATE_WAITABLE_TIMER_MANUAL_RESET | CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + TIMER_ALL_ACCESS); + if (!pTimer->hTimer) + { + rc = rtTimerNtSetTimerResolution(u64NanoInterval); + if (RT_SUCCESS(rc)) + pTimer->hTimer = CreateWaitableTimer(NULL, TRUE, NULL); + } + + if (pTimer->hTimer) + { + /* + * Kick off the timer thread. + */ + rc = RTThreadCreate(&pTimer->Thread, rttimerCallback, pTimer, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "Timer"); + if (RT_SUCCESS(rc)) + { + /* + * Wait for the timer to successfully create the timer + * If we don't get a response in 10 secs, then we assume we're screwed. + */ + rc = RTThreadUserWait(pTimer->Thread, 10000); + if (RT_SUCCESS(rc)) + { + rc = pTimer->iError; + if (RT_SUCCESS(rc)) + { + *ppTimer = pTimer; + return VINF_SUCCESS; + } + } + + /* bail out */ + ASMAtomicXchgBool(&pTimer->fDestroyed, true); + ASMAtomicXchgU32(&pTimer->u32Magic, ~RTTIMER_MAGIC); + RTThreadWait(pTimer->Thread, 45*1000, NULL); + CancelWaitableTimer(pTimer->hTimer); + } + CloseHandle(pTimer->hTimer); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + RTSemEventDestroy(pTimer->Event); + pTimer->Event = NIL_RTSEMEVENT; + } + + RTMemFree(pTimer); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +RTR3DECL(int) RTTimerDestroy(PRTTIMER pTimer) +{ + /* NULL is ok. */ + if (!pTimer) + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + AssertPtrReturn(pTimer, VERR_INVALID_HANDLE); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR); + + /* + * Signal that we want the thread to exit. + */ + ASMAtomicWriteBool(&pTimer->fDestroyed, true); + ASMAtomicWriteU32(&pTimer->u32Magic, ~RTTIMER_MAGIC); + + /* + * Suspend the timer if it's running. + */ + if (!pTimer->fSuspended) + { + LARGE_INTEGER ll = {0}; + ll.LowPart = 100; + rc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE); + AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError())); + } + + rc = RTSemEventSignal(pTimer->Event); + AssertRC(rc); + + /* + * Wait for the thread to exit. + * And if it don't wanna exit, we'll get kill it. + */ + rc = RTThreadWait(pTimer->Thread, 30 * 1000, NULL); + if (RT_FAILURE(rc)) + TerminateThread((HANDLE)RTThreadGetNative(pTimer->Thread), UINT32_MAX); + + /* + * Free resource. + */ + rc = CloseHandle(pTimer->hTimer); + AssertMsg(rc, ("CloseHandle lasterr=%d\n", GetLastError())); + + RTSemEventDestroy(pTimer->Event); + pTimer->Event = NIL_RTSEMEVENT; + + RTMemFree(pTimer); + return rc; +} + + +RTDECL(int) RTTimerStart(PRTTIMER pTimer, uint64_t u64First) +{ + /* + * Validate input. + */ + AssertPtrReturn(pTimer, VERR_INVALID_POINTER); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR); + + RTThreadUserReset(pTimer->Thread); + + /* + * Already running? + */ + if (!ASMAtomicXchgBool(&pTimer->fSuspended, false)) + return VERR_TIMER_ACTIVE; + LogFlow(("RTTimerStart: pTimer=%p u64First=%llu u64NanoInterval=%llu\n", pTimer, u64First, pTimer->u64NanoInterval)); + + /* + * Tell the thread to start servicing the timer. + * Wait for it to ACK the request to avoid reset races. + */ + ASMAtomicUoWriteU64(&pTimer->u64NanoFirst, u64First); + ASMAtomicUoWriteU64(&pTimer->iTick, 0); + int rc = RTSemEventSignal(pTimer->Event); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserWait(pTimer->Thread, 45*1000); + AssertRC(rc); + RTThreadUserReset(pTimer->Thread); + } + else + AssertRC(rc); + + if (RT_FAILURE(rc)) + ASMAtomicXchgBool(&pTimer->fSuspended, true); + return rc; +} + + +RTDECL(int) RTTimerStop(PRTTIMER pTimer) +{ + /* + * Validate input. + */ + AssertPtrReturn(pTimer, VERR_INVALID_POINTER); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); + + RTThreadUserReset(pTimer->Thread); + + /* + * Already running? + */ + if (ASMAtomicXchgBool(&pTimer->fSuspended, true)) + return VERR_TIMER_SUSPENDED; + LogFlow(("RTTimerStop: pTimer=%p\n", pTimer)); + + /* + * Tell the thread to stop servicing the timer. + */ + int rc = VINF_SUCCESS; + if (RTThreadSelf() != pTimer->Thread) + { + LARGE_INTEGER ll = {0}; + ll.LowPart = 100; + rc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE); + AssertMsg(rc, ("SetWaitableTimer lasterr=%d\n", GetLastError())); + rc = RTThreadUserWait(pTimer->Thread, 45*1000); + AssertRC(rc); + RTThreadUserReset(pTimer->Thread); + } + + return rc; +} + + +RTDECL(int) RTTimerChangeInterval(PRTTIMER pTimer, uint64_t u64NanoInterval) +{ + AssertPtrReturn(pTimer, VERR_INVALID_POINTER); + AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC); + NOREF(u64NanoInterval); + return VERR_NOT_SUPPORTED; +} diff --git a/src/VBox/Runtime/r3/win/tls-win.cpp b/src/VBox/Runtime/r3/win/tls-win.cpp new file mode 100644 index 00000000..5145a266 --- /dev/null +++ b/src/VBox/Runtime/r3/win/tls-win.cpp @@ -0,0 +1,230 @@ +/* $Id: tls-win.cpp $ */ +/** @file + * IPRT - Thread Local Storage (TLS), Win32. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_THREAD +#include <iprt/win/windows.h> + +#include <iprt/thread.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTTLSWINDTOR +{ + RTLISTNODE ListEntry; + DWORD iTls; + PFNRTTLSDTOR pfnDestructor; +} RTTLSWINDTOR; +typedef RTTLSWINDTOR *PRTTLSWINDTOR; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init once for the list and critical section. */ +static RTONCE g_Once = RTONCE_INITIALIZER; +/** Critical section protecting the TLS destructor list. */ +static RTCRITSECTRW g_CritSect; +/** List of TLS destrictors (RTTLSWINDTOR). */ +static RTLISTANCHOR g_TlsDtorHead; +/** Number of desturctors in the list (helps putting of initialization). */ +static uint32_t volatile g_cTlsDtors = 0; + + +/** + * @callback_method_impl{FNRTONCE} + */ +static DECLCALLBACK(int32_t) rtTlsWinInitLock(void *pvUser) +{ + RT_NOREF(pvUser); + RTListInit(&g_TlsDtorHead); + return RTCritSectRwInit(&g_CritSect); +} + + +RTR3DECL(RTTLS) RTTlsAlloc(void) +{ + AssertCompile(sizeof(RTTLS) >= sizeof(DWORD)); + DWORD iTls = TlsAlloc(); + return iTls != TLS_OUT_OF_INDEXES ? (RTTLS)iTls : NIL_RTTLS; +} + + +RTR3DECL(int) RTTlsAllocEx(PRTTLS piTls, PFNRTTLSDTOR pfnDestructor) +{ + int rc; + if (!pfnDestructor) + { + DWORD iTls = TlsAlloc(); + if (iTls != TLS_OUT_OF_INDEXES) + { + Assert((RTTLS)iTls != NIL_RTTLS); + *piTls = (RTTLS)iTls; + Assert((DWORD)*piTls == iTls); + rc = VINF_SUCCESS; + } + else + rc = VERR_NO_MEMORY; + } + else + { + rc = RTOnce(&g_Once, rtTlsWinInitLock, NULL); + if (RT_SUCCESS(rc)) + { + PRTTLSWINDTOR pDtor = (PRTTLSWINDTOR)RTMemAlloc(sizeof(*pDtor)); + if (pDtor) + { + DWORD iTls = TlsAlloc(); + if (iTls != TLS_OUT_OF_INDEXES) + { + Assert((RTTLS)iTls != NIL_RTTLS); + *piTls = (RTTLS)iTls; + Assert((DWORD)*piTls == iTls); + + /* + * Add the destructor to the list. We keep it sorted. + */ + pDtor->iTls = iTls; + pDtor->pfnDestructor = pfnDestructor; + RTCritSectRwEnterExcl(&g_CritSect); + RTListAppend(&g_TlsDtorHead, &pDtor->ListEntry); + ASMAtomicIncU32(&g_cTlsDtors); + RTCritSectRwLeaveExcl(&g_CritSect); + + rc = VINF_SUCCESS; + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NO_MEMORY; + } + } + return rc; +} + + +RTR3DECL(int) RTTlsFree(RTTLS iTls) +{ + if (iTls == NIL_RTTLS) + return VINF_SUCCESS; + if (TlsFree((DWORD)iTls)) + { + if (ASMAtomicReadU32(&g_cTlsDtors) > 0) + { + RTCritSectRwEnterExcl(&g_CritSect); + PRTTLSWINDTOR pDtor; + RTListForEach(&g_TlsDtorHead, pDtor, RTTLSWINDTOR, ListEntry) + { + if (pDtor->iTls == (DWORD)iTls) + { + RTListNodeRemove(&pDtor->ListEntry); + ASMAtomicDecU32(&g_cTlsDtors); + RTMemFree(pDtor); + break; + } + } + RTCritSectRwLeaveExcl(&g_CritSect); + } + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(GetLastError()); +} + + +RTR3DECL(void *) RTTlsGet(RTTLS iTls) +{ + return TlsGetValue((DWORD)iTls); +} + + +RTR3DECL(int) RTTlsGetEx(RTTLS iTls, void **ppvValue) +{ + void *pv = TlsGetValue((DWORD)iTls); + if (pv) + { + *ppvValue = pv; + return VINF_SUCCESS; + } + + /* TlsGetValue always updates last error */ + *ppvValue = NULL; + return RTErrConvertFromWin32(GetLastError()); +} + + +RTR3DECL(int) RTTlsSet(RTTLS iTls, void *pvValue) +{ + if (TlsSetValue((DWORD)iTls, pvValue)) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + + +/** + * Called by dllmain-win.cpp when a thread detaches. + */ +DECLHIDDEN(void) rtThreadWinTlsDestruction(void) +{ + if (ASMAtomicReadU32(&g_cTlsDtors) > 0) + { + RTCritSectRwEnterShared(&g_CritSect); + PRTTLSWINDTOR pDtor; + RTListForEach(&g_TlsDtorHead, pDtor, RTTLSWINDTOR, ListEntry) + { + void *pvValue = TlsGetValue(pDtor->iTls); + if (pvValue != NULL) + { + pDtor->pfnDestructor(pvValue); + TlsSetValue(pDtor->iTls, NULL); + } + } + RTCritSectRwLeaveShared(&g_CritSect); + } +} + diff --git a/src/VBox/Runtime/r3/win/tpm-win.cpp b/src/VBox/Runtime/r3/win/tpm-win.cpp new file mode 100644 index 00000000..3ee0e46b --- /dev/null +++ b/src/VBox/Runtime/r3/win/tpm-win.cpp @@ -0,0 +1,307 @@ +/* $Id: tpm-win.cpp $ */ +/** @file + * IPRT - Trusted Platform Module (TPM) access, Windows variant. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/tpm.h> + +#include <iprt/assertcompile.h> +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/mem.h> +#include <iprt/once.h> + +#include "internal-r3-win.h" + +#include <iprt/win/windows.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* tbs.dll: */ +typedef struct TBS_CONTEXT_PARAMS2 +{ + UINT32 version; + union + { + struct + { + UINT32 requestRaw: 1; + UINT32 includeTpm12: 1; + UINT32 includeTpm20: 1; + } Fields; + + UINT32 asUINT32; + } u; +} TBS_CONTEXT_PARAMS2; + +typedef struct TBS_DEVICE_INFO +{ + UINT32 structVersion; + UINT32 tpmVersion; + UINT32 tpmInterfaceType; + UINT32 tpmImpRevision; +} TBS_DEVICE_INFO; + +#define TPM_VERSION_12 1 +#define TPM_VERSION_20 2 + +#define TBS_SUCCESS S_OK +#define TBS_COMMAND_PRIORITY_NORMAL 200 + +typedef UINT32 TBS_RESULT; +typedef void *TBS_HCONTEXT; +typedef TBS_RESULT (WINAPI *PFNTBSI_CONTEXT_CREATE)(const TBS_CONTEXT_PARAMS2 *, TBS_HCONTEXT *); +typedef TBS_RESULT (WINAPI *PFNTBSI_CONTEXT_CLOSE)(TBS_HCONTEXT); +typedef TBS_RESULT (WINAPI *PFNTBSI_GET_DEVICE_INFO)(UINT32, TBS_DEVICE_INFO *); +typedef TBS_RESULT (WINAPI *PFNTBSI_CANCEL_COMMANDS)(TBS_HCONTEXT); +typedef TBS_RESULT (WINAPI *PFNTBSI_SUBMIT_COMMANDS)(TBS_HCONTEXT, UINT32, UINT32, const BYTE *, UINT32, PBYTE, PUINT32); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal TPM instance data. + */ +typedef struct RTTPMINT +{ + /** Handle to the TPM context. */ + TBS_HCONTEXT hCtx; + /** The deduced TPM version. */ + RTTPMVERSION enmTpmVers; +} RTTPMINT; +/** Pointer to the internal TPM instance data. */ +typedef RTTPMINT *PRTTPMINT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init once structure. */ +static RTONCE g_rtTpmWinInitOnce = RTONCE_INITIALIZER; +/* tbs.dll: */ +static PFNTBSI_CONTEXT_CREATE g_pfnTbsiContextCreate = NULL; +static PFNTBSI_CONTEXT_CLOSE g_pfnTbsiContextClose = NULL; +static PFNTBSI_GET_DEVICE_INFO g_pfnTbsiGetDeviceInfo = NULL; +static PFNTBSI_CANCEL_COMMANDS g_pfnTbsiCancelCommands = NULL; +static PFNTBSI_SUBMIT_COMMANDS g_pfnTbsiSubmitCommands = NULL; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Initialize the globals. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int32_t) rtTpmWinInitOnce(void *pvUser) +{ + RT_NOREF(pvUser); + RTLDRMOD hMod; + + int rc = RTLdrLoadSystem("tbs.dll", true /*fNoUnload*/, &hMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hMod, "Tbsi_Context_Create", (void **)&g_pfnTbsiContextCreate); + if (RT_FAILURE(rc)) return rc; + + rc = RTLdrGetSymbol(hMod, "Tbsip_Context_Close", (void **)&g_pfnTbsiContextClose); + if (RT_FAILURE(rc)) return rc; + + rc = RTLdrGetSymbol(hMod, "Tbsip_Cancel_Commands", (void **)&g_pfnTbsiCancelCommands); + if (RT_FAILURE(rc)) return rc; + + rc = RTLdrGetSymbol(hMod, "Tbsip_Submit_Command", (void **)&g_pfnTbsiSubmitCommands); + if (RT_FAILURE(rc)) return rc; + + rc = RTLdrGetSymbol(hMod, "Tbsi_GetDeviceInfo", (void **)&g_pfnTbsiGetDeviceInfo); + if (RT_FAILURE(rc)) { g_pfnTbsiGetDeviceInfo = NULL; Assert(g_enmWinVer < kRTWinOSType_8); } + + RTLdrClose(hMod); + } + + return rc; +} + + +RTDECL(int) RTTpmOpen(PRTTPM phTpm, uint32_t idTpm) +{ + AssertPtrReturn(phTpm, VERR_INVALID_POINTER); + if (idTpm == RTTPM_ID_DEFAULT) + idTpm = 0; + + AssertReturn(idTpm == 0, VERR_NOT_SUPPORTED); + + /* + * Initialize the globals. + */ + int rc = RTOnce(&g_rtTpmWinInitOnce, rtTpmWinInitOnce, NULL); + AssertRCReturn(rc, rc); + + PRTTPMINT pThis = (PRTTPMINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + TBS_CONTEXT_PARAMS2 CtxParams; RT_ZERO(CtxParams); + + CtxParams.version = TPM_VERSION_12; + if (g_pfnTbsiGetDeviceInfo) + { + /* TPM2 support is available starting with Win8 which has Tbsi_GetDeviceInfo available. */ + TBS_DEVICE_INFO DevInfo; RT_ZERO(DevInfo); + + DevInfo.structVersion = TPM_VERSION_20; + TBS_RESULT rcTbs = g_pfnTbsiGetDeviceInfo(sizeof(DevInfo), &DevInfo); + if (rcTbs == TBS_SUCCESS) + { + CtxParams.version = TPM_VERSION_20; + if (DevInfo.tpmVersion == TPM_VERSION_20) + { + pThis->enmTpmVers = RTTPMVERSION_2_0; + CtxParams.u.Fields.includeTpm20 = 1; + } + else + { + Assert(DevInfo.tpmVersion == TPM_VERSION_12); + pThis->enmTpmVers = RTTPMVERSION_1_2; + CtxParams.u.Fields.includeTpm12 = 1; + } + } + else + rc = VERR_NOT_FOUND; + } + else + pThis->enmTpmVers = RTTPMVERSION_1_2; + + if (RT_SUCCESS(rc)) + { + TBS_RESULT rcTbs = g_pfnTbsiContextCreate(&CtxParams, &pThis->hCtx); + if (rcTbs == TBS_SUCCESS) + { + *phTpm = pThis; + return VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; + } + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTTpmClose(RTTPM hTpm) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + TBS_RESULT rcTbs = g_pfnTbsiContextClose(pThis->hCtx); + Assert(rcTbs == TBS_SUCCESS); RT_NOREF(rcTbs); + + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(RTTPMVERSION) RTTpmGetVersion(RTTPM hTpm) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, RTTPMVERSION_INVALID); + return pThis->enmTpmVers; +} + + +RTDECL(uint32_t) RTTpmGetLocalityMax(RTTPM hTpm) +{ + RT_NOREF(hTpm); + return 0; /* Only TPM locality 0 is supported. */ +} + + +RTDECL(int) RTTpmReqCancel(RTTPM hTpm) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + TBS_RESULT rcTbs = g_pfnTbsiCancelCommands(pThis->hCtx); + if (rcTbs != TBS_SUCCESS) + return VERR_DEV_IO_ERROR; + + return VINF_SUCCESS; +} + + +RTDECL(int) RTTpmReqExec(RTTPM hTpm, uint8_t bLoc, const void *pvReq, size_t cbReq, + void *pvResp, size_t cbRespMax, size_t *pcbResp) +{ + PRTTPMINT pThis = hTpm; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pvReq, VERR_INVALID_POINTER); + AssertPtrReturn(pvResp, VERR_INVALID_POINTER); + AssertReturn(cbReq && cbRespMax, VERR_INVALID_PARAMETER); + AssertReturn(cbReq == (UINT32)cbReq && cbRespMax == (UINT32)cbRespMax, VERR_BUFFER_OVERFLOW); + AssertReturn(bLoc == 0, VERR_NOT_SUPPORTED); /* TBS doesn't support another locality than 0. */ + + UINT32 cbResult = (UINT32)cbRespMax; + TBS_RESULT rcTbs = g_pfnTbsiSubmitCommands(pThis->hCtx, 0 /*Locality*/, TBS_COMMAND_PRIORITY_NORMAL, + (const BYTE *)pvReq, (UINT32)cbReq, (BYTE *)pvResp, &cbResult); + if (rcTbs == TBS_SUCCESS) + { + if (pcbResp) + *pcbResp = cbResult; + return VINF_SUCCESS; + } + + return VERR_DEV_IO_ERROR; +} + diff --git a/src/VBox/Runtime/r3/win/utf16locale-win.cpp b/src/VBox/Runtime/r3/win/utf16locale-win.cpp new file mode 100644 index 00000000..d282572a --- /dev/null +++ b/src/VBox/Runtime/r3/win/utf16locale-win.cpp @@ -0,0 +1,58 @@ +/* $Id: utf16locale-win.cpp $ */ +/** @file + * IPRT - UTF-16 Locale Specific Manipulation, Win32. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_UTF16 +#include <iprt/win/windows.h> + +#include <iprt/utf16.h> + + +RTDECL(int) RTUtf16LocaleICmp(PCRTUTF16 pusz1, PCRTUTF16 pusz2) +{ + if (pusz1 == pusz2) + return 0; + if (pusz1 == NULL) + return -1; + if (pusz2 == NULL) + return 1; + + return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, pusz1, -1, pusz2, -1) - 2; +} + diff --git a/src/VBox/Runtime/r3/win/utf8-win.cpp b/src/VBox/Runtime/r3/win/utf8-win.cpp new file mode 100644 index 00000000..7defa137 --- /dev/null +++ b/src/VBox/Runtime/r3/win/utf8-win.cpp @@ -0,0 +1,204 @@ +/* $Id: utf8-win.cpp $ */ +/** @file + * IPRT - UTF8 helpers. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_UTF8 +#include <iprt/win/windows.h> + +#include <iprt/string.h> +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/utf16.h> + + + +RTR3DECL(int) RTStrUtf8ToCurrentCPTag(char **ppszString, const char *pszString, const char *pszTag) +{ + return RTStrUtf8ToCurrentCPExTag(ppszString, pszString, RTSTR_MAX, pszTag); +} + + +RTR3DECL(int) RTStrUtf8ToCurrentCPExTag(char **ppszString, const char *pszString, size_t cchString, const char *pszTag) +{ + Assert(ppszString); + Assert(pszString); + *ppszString = NULL; + + /* + * If the ANSI codepage (CP_ACP) is UTF-8, no translation is needed. + * Same goes for empty strings. + */ + if ( cchString == 0 + || *pszString == '\0') + return RTStrDupNExTag(ppszString, pszString, 0, pszTag); + if (GetACP() == CP_UTF8) + { + int rc = RTStrValidateEncodingEx(pszString, cchString, 0); + AssertRCReturn(rc, rc); + return RTStrDupNExTag(ppszString, pszString, cchString, pszTag); + } + + /* + * Convert to wide char first. + */ + PRTUTF16 pwszString = NULL; + int rc = RTStrToUtf16Ex(pszString, cchString, &pwszString, 0, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * First calc result string length. + */ + int cbResult = WideCharToMultiByte(CP_ACP, 0, pwszString, -1, NULL, 0, NULL, NULL); + if (cbResult > 0) + { + /* + * Alloc space for result buffer. + */ + LPSTR lpString = (LPSTR)RTMemTmpAllocTag(cbResult, pszTag); + if (lpString) + { + /* + * Do the translation. + */ + if (WideCharToMultiByte(CP_ACP, 0, pwszString, -1, lpString, cbResult, NULL, NULL) > 0) + { + /* ok */ + *ppszString = lpString; + RTMemTmpFree(pwszString); + return VINF_SUCCESS; + } + + /* translation error */ + int iLastErr = GetLastError(); + AssertMsgFailed(("Unicode to ACP translation failed. lasterr=%d\n", iLastErr)); + rc = RTErrConvertFromWin32(iLastErr); + } + else + rc = VERR_NO_TMP_MEMORY; + RTMemTmpFree(lpString); + } + else + { + /* translation error */ + int iLastErr = GetLastError(); + AssertMsgFailed(("Unicode to ACP translation failed lasterr=%d\n", iLastErr)); + rc = RTErrConvertFromWin32(iLastErr); + } + RTMemTmpFree(pwszString); + return rc; +} + +static int rtStrCPToUtf8Tag(char **ppszString, const char *pszString, uint32_t uCodePage, const char *pszTag) +{ + Assert(ppszString); + Assert(pszString); + *ppszString = NULL; + + /* + * If the ANSI codepage (CP_ACP) is UTF-8, no translation is needed. + * Same goes for empty strings. + */ + if (*pszString == '\0') + return RTStrDupExTag(ppszString, pszString, pszTag); + if (GetACP() == CP_UTF8) + { + int rc = RTStrValidateEncoding(pszString); + AssertRCReturn(rc, rc); + return RTStrDupExTag(ppszString, pszString, pszTag); + } + + /** @todo is there a quicker way? Currently: ACP -> UTF-16 -> UTF-8 */ + + /* + * First calc result string length. + */ + int rc; + int cwc = MultiByteToWideChar((UINT)uCodePage, 0, pszString, -1, NULL, 0); + if (cwc > 0) + { + /* + * Alloc space for result buffer. + */ + PRTUTF16 pwszString = (PRTUTF16)RTMemTmpAlloc(cwc * sizeof(RTUTF16)); + if (pwszString) + { + /* + * Do the translation. + */ + if (MultiByteToWideChar((UINT)uCodePage, 0, pszString, -1, pwszString, cwc) > 0) + { + /* + * Now we got UTF-16, convert it to UTF-8 + */ + rc = RTUtf16ToUtf8(pwszString, ppszString); + RTMemTmpFree(pwszString); + return rc; + } + RTMemTmpFree(pwszString); + /* translation error */ + int iLastErr = GetLastError(); + AssertMsgFailed(("ACP to Unicode translation failed. lasterr=%d\n", iLastErr)); + rc = RTErrConvertFromWin32(iLastErr); + } + else + rc = VERR_NO_TMP_MEMORY; + } + else + { + /* translation error */ + int iLastErr = GetLastError(); + AssertMsgFailed(("Unicode to ACP translation failed lasterr=%d\n", iLastErr)); + rc = RTErrConvertFromWin32(iLastErr); + } + return rc; +} + + +RTR3DECL(int) RTStrCurrentCPToUtf8Tag(char **ppszString, const char *pszString, const char *pszTag) +{ + return rtStrCPToUtf8Tag(ppszString, pszString, CP_ACP, pszTag); +} + + +RTR3DECL(int) RTStrConsoleCPToUtf8Tag(char **ppszString, const char *pszString, const char *pszTag) +{ + return rtStrCPToUtf8Tag(ppszString, pszString, GetConsoleCP(), pszTag); +} diff --git a/src/VBox/Runtime/r3/win/uuid-win.cpp b/src/VBox/Runtime/r3/win/uuid-win.cpp new file mode 100644 index 00000000..eebeb0c0 --- /dev/null +++ b/src/VBox/Runtime/r3/win/uuid-win.cpp @@ -0,0 +1,195 @@ +/* $Id: uuid-win.cpp $ */ +/** @file + * IPRT - UUID, Windows implementation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_UUID +#include <iprt/win/windows.h> + +#include <iprt/uuid.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/errcore.h> + + +RTDECL(int) RTUuidClear(PRTUUID pUuid) +{ + /* check params */ + AssertPtrReturn(pUuid, VERR_INVALID_POINTER); + + return RTErrConvertFromWin32(UuidCreateNil((UUID *)pUuid)); +} + + +RTDECL(bool) RTUuidIsNull(PCRTUUID pUuid) +{ + /* check params */ + AssertPtrReturn(pUuid, true); + + RPC_STATUS status; + return !!UuidIsNil((UUID *)pUuid, &status); +} + + +RTDECL(int) RTUuidCompare(PCRTUUID pUuid1, PCRTUUID pUuid2) +{ + /* + * Special cases. + */ + if (pUuid1 == pUuid2) + return 0; + if (!pUuid1) + return RTUuidIsNull(pUuid2) ? 0 : -1; + if (!pUuid2) + return RTUuidIsNull(pUuid1) ? 0 : 1; + AssertPtrReturn(pUuid1, -1); + AssertPtrReturn(pUuid2, 1); + + /* + * Hand the rest to the Windows API. + */ + RPC_STATUS status; + return UuidCompare((UUID *)pUuid1, (UUID *)pUuid2, &status); +} + + +RTDECL(int) RTUuidCompareStr(PCRTUUID pUuid1, const char *pszString2) +{ + /* check params */ + AssertPtrReturn(pUuid1, -1); + AssertPtrReturn(pszString2, 1); + + /* + * Try convert the string to a UUID and then compare the two. + */ + RTUUID Uuid2; + int rc = RTUuidFromStr(&Uuid2, pszString2); + AssertRCReturn(rc, 1); + + return RTUuidCompare(pUuid1, &Uuid2); +} + + +RTDECL(int) RTUuidCompare2Strs(const char *pszString1, const char *pszString2) +{ + RTUUID Uuid1; + RTUUID Uuid2; + int rc; + + /* check params */ + AssertPtrReturn(pszString1, -1); + AssertPtrReturn(pszString2, 1); + + /* + * Try convert the strings to UUIDs and then compare them. + */ + rc = RTUuidFromStr(&Uuid1, pszString1); + AssertRCReturn(rc, -1); + + rc = RTUuidFromStr(&Uuid2, pszString2); + AssertRCReturn(rc, 1); + + return RTUuidCompare(&Uuid1, &Uuid2); +} + + +RTDECL(int) RTUuidToStr(PCRTUUID pUuid, char *pszString, size_t cchString) +{ + /* check params */ + AssertPtrReturn(pUuid, VERR_INVALID_POINTER); + AssertPtrReturn(pszString, VERR_INVALID_POINTER); + AssertReturn(cchString >= RTUUID_STR_LENGTH, VERR_INVALID_PARAMETER); + + /* + * Try convert it. + * + * The API allocates a new string buffer for us, so we can do our own + * buffer overflow handling. + */ + RPC_STATUS Status; + unsigned char *pszTmpStr = NULL; +#ifdef RPC_UNICODE_SUPPORTED + /* always use ASCII version! */ + Status = UuidToStringA((UUID *)pUuid, &pszTmpStr); +#else + Status = UuidToString((UUID *)pUuid, &pszTmpStr); +#endif + if (Status != RPC_S_OK) + return RTErrConvertFromWin32(Status); + + /* copy it. */ + int rc = VINF_SUCCESS; + size_t cchTmpStr = strlen((char *)pszTmpStr); + if (cchTmpStr < cchString) + memcpy(pszString, pszTmpStr, cchTmpStr + 1); + else + { + AssertFailed(); + rc = ERROR_BUFFER_OVERFLOW; + } + + /* free buffer */ +#ifdef RPC_UNICODE_SUPPORTED + /* always use ASCII version! */ + RpcStringFreeA(&pszTmpStr); +#else + RpcStringFree(&pszTmpStr); +#endif + + /* all done */ + return rc; +} + + +RTDECL(int) RTUuidFromStr(PRTUUID pUuid, const char *pszString) +{ + /* check params */ + AssertPtrReturn(pUuid, VERR_INVALID_POINTER); + AssertPtrReturn(pszString, VERR_INVALID_POINTER); + + RPC_STATUS rc; +#ifdef RPC_UNICODE_SUPPORTED + /* always use ASCII version! */ + rc = UuidFromStringA((unsigned char *)pszString, (UUID *)pUuid); +#else + rc = UuidFromString((unsigned char *)pszString, (UUID *)pUuid); +#endif + + return RTErrConvertFromWin32(rc); +} + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-100.h b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-100.h new file mode 100644 index 00000000..1708ee52 --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-100.h @@ -0,0 +1,43 @@ + +COMMENT("XP SP2 / W2K3 SP1 / VISTA") +MAKE_IMPORT_ENTRY(6,0, DecodePointer, 4) +MAKE_IMPORT_ENTRY(6,0, EncodePointer, 4) +COMMENT("XP") +MAKE_IMPORT_ENTRY(5,1, CreateIoCompletionPort, 16) +MAKE_IMPORT_ENTRY(5,1, GetQueuedCompletionStatus, 20) +MAKE_IMPORT_ENTRY(5,1, HeapSetInformation, 16) +MAKE_IMPORT_ENTRY(5,1, HeapQueryInformation, 20) +MAKE_IMPORT_ENTRY(5,1, InitializeSListHead, 4) +MAKE_IMPORT_ENTRY(5,1, InterlockedFlushSList, 4) +MAKE_IMPORT_ENTRY(5,1, InterlockedPopEntrySList, 4) +MAKE_IMPORT_ENTRY(5,1, InterlockedPushEntrySList, 8) +MAKE_IMPORT_ENTRY(5,1, PostQueuedCompletionStatus, 16) +MAKE_IMPORT_ENTRY(5,1, QueryDepthSList, 4) +COMMENT("W2K") +MAKE_IMPORT_ENTRY(5,0, CreateTimerQueue, 0) +MAKE_IMPORT_ENTRY(5,0, CreateTimerQueueTimer, 28) +MAKE_IMPORT_ENTRY(5,0, DeleteTimerQueueTimer, 12) +MAKE_IMPORT_ENTRY(5,0, VerSetConditionMask, 16) +COMMENT("NT 4 SP4+") +MAKE_IMPORT_ENTRY(5,0, VerifyVersionInfoA, 16) +COMMENT("NT 4 SP3+") +MAKE_IMPORT_ENTRY(5,0, InitializeCriticalSectionAndSpinCount, 8) +COMMENT("NT 4") +MAKE_IMPORT_ENTRY(4,0, IsProcessorFeaturePresent, 4) +MAKE_IMPORT_ENTRY(4,0, CancelIo, 4) +COMMENT("NT 3.51") +MAKE_IMPORT_ENTRY(3,51, IsDebuggerPresent, 0) +MAKE_IMPORT_ENTRY(3,51, GetSystemTimeAsFileTime, 4) +COMMENT("NT 3.50") +MAKE_IMPORT_ENTRY(3,50, GetVersionExA, 4) +MAKE_IMPORT_ENTRY(3,50, GetVersionExW, 4) +MAKE_IMPORT_ENTRY(3,50, GetEnvironmentStringsW, 0) +MAKE_IMPORT_ENTRY(3,50, FreeEnvironmentStringsW, 4) +MAKE_IMPORT_ENTRY(3,50, GetLocaleInfoA, 16) +MAKE_IMPORT_ENTRY(3,50, EnumSystemLocalesA, 8) +MAKE_IMPORT_ENTRY(3,50, IsValidLocale, 8) +MAKE_IMPORT_ENTRY(3,50, SetThreadAffinityMask, 8) +MAKE_IMPORT_ENTRY(3,50, GetProcessAffinityMask, 12) +MAKE_IMPORT_ENTRY(3,50, GetHandleInformation, 8) +MAKE_IMPORT_ENTRY(3,50, SetHandleInformation, 12) + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-141.h b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-141.h new file mode 100644 index 00000000..a65e6392 --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-141.h @@ -0,0 +1,42 @@ +MAKE_IMPORT_ENTRY(6,0, DecodePointer, 4) +MAKE_IMPORT_ENTRY(6,0, EncodePointer, 4) +MAKE_IMPORT_ENTRY(5,1, InitializeSListHead, 4) +MAKE_IMPORT_ENTRY(5,1, GetModuleHandleExW, 12) +MAKE_IMPORT_ENTRY(5,0, VerifyVersionInfoA, 16) +MAKE_IMPORT_ENTRY(5,0, SetFilePointerEx, 20) +MAKE_IMPORT_ENTRY(5,0, GetFileSizeEx, 8) +MAKE_IMPORT_ENTRY(5,0, InitializeCriticalSectionAndSpinCount, 8) +MAKE_IMPORT_ENTRY(4,0, FindFirstFileExW, 24) +MAKE_IMPORT_ENTRY(4,0, IsProcessorFeaturePresent, 4) +MAKE_IMPORT_ENTRY(4,0, CancelIo, 4) +MAKE_IMPORT_ENTRY(3,51, FreeLibraryAndExitThread, 8) +MAKE_IMPORT_ENTRY(3,51, IsDebuggerPresent, 0) +MAKE_IMPORT_ENTRY(3,51, GetSystemTimeAsFileTime, 4) +MAKE_IMPORT_ENTRY(3,50, EnumSystemLocalesW, 8) +MAKE_IMPORT_ENTRY(3,50, GetVersionExA, 4) +MAKE_IMPORT_ENTRY(3,50, GetVersionExW, 4) +MAKE_IMPORT_ENTRY(3,50, GetEnvironmentStringsW, 0) +MAKE_IMPORT_ENTRY(3,50, FreeEnvironmentStringsW, 4) +MAKE_IMPORT_ENTRY(3,50, IsValidLocale, 8) +MAKE_IMPORT_ENTRY(3,50, SetThreadAffinityMask, 8) +MAKE_IMPORT_ENTRY(3,50, GetProcessAffinityMask, 12) +MAKE_IMPORT_ENTRY(3,50, GetHandleInformation, 8) +MAKE_IMPORT_ENTRY(3,50, SetHandleInformation, 12) + +COMMENT("Not needed (usually)") +MAKE_IMPORT_ENTRY(5,1, HeapSetInformation, 16) +MAKE_IMPORT_ENTRY(5,1, HeapQueryInformation, 20) +MAKE_IMPORT_ENTRY(5,1, InterlockedFlushSList, 4) +MAKE_IMPORT_ENTRY(5,1, InterlockedPopEntrySList, 4) +MAKE_IMPORT_ENTRY(5,1, InterlockedPushEntrySList, 8) +MAKE_IMPORT_ENTRY(5,1, QueryDepthSList, 4) +MAKE_IMPORT_ENTRY(5,0, CreateTimerQueue, 0) +MAKE_IMPORT_ENTRY(5,0, CreateTimerQueueTimer, 28) +MAKE_IMPORT_ENTRY(5,0, DeleteTimerQueueTimer, 12) +MAKE_IMPORT_ENTRY(5,0, VerSetConditionMask, 16) +MAKE_IMPORT_ENTRY(3,51, PostQueuedCompletionStatus, 16) +MAKE_IMPORT_ENTRY(3,51, CreateIoCompletionPort, 16) +MAKE_IMPORT_ENTRY(3,51, GetQueuedCompletionStatus, 20) +MAKE_IMPORT_ENTRY(3,50, GetLocaleInfoA, 16) +MAKE_IMPORT_ENTRY(3,50, EnumSystemLocalesA, 8) + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm new file mode 100644 index 00000000..eb1449c3 --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm @@ -0,0 +1,56 @@ +; $Id: vcc-fakes-kernel32-A.asm $ +;; @file +; IPRT - Wrappers for kernel32 APIs missing in NT4 and earlier. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "vcc-fakes.mac" + +%define FAKE_MODULE_NAME kernel32 + +BEGINDATA +GLOBALNAME vcc100_kernel32_fakes_asm + +%ifdef VCC_FAKES_TARGET_VCC100 + %include "vcc-fakes-kernel32-100.h" +%elifdef VCC_FAKES_TARGET_VCC140 + %include "vcc-fakes-kernel32-141.h" +%elifdef VCC_FAKES_TARGET_VCC141 + %include "vcc-fakes-kernel32-141.h" +%elifdef VCC_FAKES_TARGET_VCC142 + %include "vcc-fakes-kernel32-141.h" +%else + %error "PORT ME!" +%endif + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32.cpp b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32.cpp new file mode 100644 index 00000000..697f22ca --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32.cpp @@ -0,0 +1,891 @@ +/* $Id: vcc-fakes-kernel32.cpp $ */ +/** @file + * IPRT - Tricks to make the Visual C++ 2010 CRT work on NT4, W2K and XP. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/types.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#ifdef DEBUG +# include <stdio.h> /* _snprintf */ +#endif + +#ifndef RT_ARCH_X86 +# error "This code is X86 only" +#endif + +#include <iprt/nt/nt-and-windows.h> + +#include "vcc-fakes.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifndef HEAP_STANDARD +# define HEAP_STANDARD 0 +#endif + + +/** Declare a kernel32 API. + * @note We are not exporting them as that causes duplicate symbol troubles in + * the OpenGL bits. */ +#define DECL_KERNEL32(a_Type) extern "C" a_Type WINAPI + +#if defined(WDK_NTDDI_VERSION) && defined(NTDDI_WIN10) +# if WDK_NTDDI_VERSION >= NTDDI_WIN10 /* In Windows 10 SDK the 'Sequence' field has been renamed to 'CpuId'. */ +# define SLIST_HEADER_SEQUENCE_NOW_CALLED_CPUID +# endif +#endif + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static volatile bool g_fInitialized = false; +#define MAKE_IMPORT_ENTRY(a_uMajorVer, a_uMinorVer, a_Name, a_cb) DECLARE_FUNCTION_POINTER(a_Name, a_cb) +#ifdef VCC_FAKES_TARGET_VCC100 +# include "vcc-fakes-kernel32-100.h" +#elif defined(VCC_FAKES_TARGET_VCC140) +# include "vcc-fakes-kernel32-141.h" +#elif defined(VCC_FAKES_TARGET_VCC141) +# include "vcc-fakes-kernel32-141.h" +#elif defined(VCC_FAKES_TARGET_VCC142) +# include "vcc-fakes-kernel32-141.h" +#else +# error "Port me!" +#endif + + +static BOOL FakeSetLastErrorFromNtStatus(NTSTATUS rcNt) +{ + DWORD dwErr; + switch (rcNt) + { + case STATUS_INVALID_PARAMETER: + case STATUS_INVALID_PARAMETER_1: + case STATUS_INVALID_PARAMETER_2: + case STATUS_INVALID_PARAMETER_3: + case STATUS_INVALID_PARAMETER_4: + case STATUS_INVALID_PARAMETER_5: + case STATUS_INVALID_PARAMETER_6: + case STATUS_INVALID_PARAMETER_7: + case STATUS_INVALID_PARAMETER_8: + case STATUS_INVALID_PARAMETER_9: + case STATUS_INVALID_PARAMETER_10: + case STATUS_INVALID_PARAMETER_11: + case STATUS_INVALID_PARAMETER_12: + dwErr = ERROR_INVALID_PARAMETER; + break; + + case STATUS_INVALID_HANDLE: + dwErr = ERROR_INVALID_HANDLE; + break; + + case STATUS_ACCESS_DENIED: + dwErr = ERROR_ACCESS_DENIED; + break; + + default: + dwErr = ERROR_INVALID_PARAMETER; + break; + } + SetLastError(dwErr); + return FALSE; +} + + + +DECL_KERNEL32(PVOID) Fake_DecodePointer(PVOID pvEncoded) +{ + return pvEncoded; +} + + +DECL_KERNEL32(PVOID) Fake_EncodePointer(PVOID pvNative) +{ + return pvNative; +} + + +DECL_KERNEL32(BOOL) Fake_InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION pCritSect, DWORD cSpin) +{ + RT_NOREF(cSpin); + InitializeCriticalSection(pCritSect); + return TRUE; +} + + +DECL_KERNEL32(HANDLE) Fake_CreateIoCompletionPort(HANDLE hFile, HANDLE hExistingCompletionPort, ULONG_PTR uCompletionKey, + DWORD cConcurrentThreads) +{ + RT_NOREF(hFile, hExistingCompletionPort, uCompletionKey, cConcurrentThreads); + SetLastError(ERROR_NOT_SUPPORTED); + return NULL; +} + + +DECL_KERNEL32(BOOL) Fake_GetQueuedCompletionStatus(HANDLE hCompletionPort, PDWORD_PTR pcbTransfered, PULONG_PTR puCompletionKey, + LPOVERLAPPED *ppOverlapped, DWORD cMs) +{ + RT_NOREF(hCompletionPort, pcbTransfered, puCompletionKey, ppOverlapped, cMs); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + + +DECL_KERNEL32(BOOL) Fake_PostQueuedCompletionStatus(HANDLE hCompletionPort, DWORD cbTransfered, ULONG_PTR uCompletionKey, + LPOVERLAPPED pOverlapped) +{ + RT_NOREF(hCompletionPort, cbTransfered, uCompletionKey, pOverlapped); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + + +DECL_KERNEL32(BOOL) Fake_HeapSetInformation(HANDLE hHeap, HEAP_INFORMATION_CLASS enmInfoClass, PVOID pvBuf, SIZE_T cbBuf) +{ + RT_NOREF(hHeap); + if (enmInfoClass == HeapCompatibilityInformation) + { + if ( cbBuf != sizeof(ULONG) + || !pvBuf + || *(PULONG)pvBuf == HEAP_STANDARD + ) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + return TRUE; + } + + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; +} + + +DECL_KERNEL32(BOOL) Fake_HeapQueryInformation(HANDLE hHeap, HEAP_INFORMATION_CLASS enmInfoClass, + PVOID pvBuf, SIZE_T cbBuf, PSIZE_T pcbRet) +{ + RT_NOREF(hHeap); + if (enmInfoClass == HeapCompatibilityInformation) + { + *pcbRet = sizeof(ULONG); + if (cbBuf < sizeof(ULONG) || !pvBuf) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + *(PULONG)pvBuf = HEAP_STANDARD; + return TRUE; + } + + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; +} + + +/* These are used by INTEL\mt_obj\Timer.obj: */ + +DECL_KERNEL32(HANDLE) Fake_CreateTimerQueue(void) +{ + SetLastError(ERROR_NOT_SUPPORTED); + return NULL; +} + +DECL_KERNEL32(BOOL) Fake_CreateTimerQueueTimer(PHANDLE phTimer, HANDLE hTimerQueue, WAITORTIMERCALLBACK pfnCallback, PVOID pvUser, + DWORD msDueTime, DWORD msPeriod, ULONG fFlags) +{ + NOREF(phTimer); NOREF(hTimerQueue); NOREF(pfnCallback); NOREF(pvUser); NOREF(msDueTime); NOREF(msPeriod); NOREF(fFlags); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + +DECL_KERNEL32(BOOL) Fake_DeleteTimerQueueTimer(HANDLE hTimerQueue, HANDLE hTimer, HANDLE hEvtCompletion) +{ + NOREF(hTimerQueue); NOREF(hTimer); NOREF(hEvtCompletion); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + +/* This is used by several APIs. */ + +DECL_KERNEL32(VOID) Fake_InitializeSListHead(PSLIST_HEADER pHead) +{ + pHead->Alignment = 0; +} + + +DECL_KERNEL32(PSLIST_ENTRY) Fake_InterlockedFlushSList(PSLIST_HEADER pHead) +{ + PSLIST_ENTRY pRet = NULL; + if (pHead->Next.Next) + { + for (;;) + { + SLIST_HEADER OldHead = *pHead; + SLIST_HEADER NewHead; + NewHead.Alignment = 0; +#ifdef SLIST_HEADER_SEQUENCE_NOW_CALLED_CPUID + NewHead.CpuId = OldHead.CpuId + 1; +#else + NewHead.Sequence = OldHead.Sequence + 1; +#endif + if (ASMAtomicCmpXchgU64(&pHead->Alignment, NewHead.Alignment, OldHead.Alignment)) + { + pRet = OldHead.Next.Next; + break; + } + } + } + return pRet; +} + +DECL_KERNEL32(PSLIST_ENTRY) Fake_InterlockedPopEntrySList(PSLIST_HEADER pHead) +{ + PSLIST_ENTRY pRet = NULL; + for (;;) + { + SLIST_HEADER OldHead = *pHead; + pRet = OldHead.Next.Next; + if (pRet) + { + SLIST_HEADER NewHead; + __try + { + NewHead.Next.Next = pRet->Next; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + continue; + } + NewHead.Depth = OldHead.Depth - 1; +#ifdef SLIST_HEADER_SEQUENCE_NOW_CALLED_CPUID + NewHead.CpuId = OldHead.CpuId + 1; +#else + NewHead.Sequence = OldHead.Sequence + 1; +#endif + if (ASMAtomicCmpXchgU64(&pHead->Alignment, NewHead.Alignment, OldHead.Alignment)) + break; + } + else + break; + } + return pRet; +} + +DECL_KERNEL32(PSLIST_ENTRY) Fake_InterlockedPushEntrySList(PSLIST_HEADER pHead, PSLIST_ENTRY pEntry) +{ + PSLIST_ENTRY pRet = NULL; + for (;;) + { + SLIST_HEADER OldHead = *pHead; + pRet = OldHead.Next.Next; + pEntry->Next = pRet; + SLIST_HEADER NewHead; + NewHead.Next.Next = pEntry; + NewHead.Depth = OldHead.Depth + 1; +#ifdef SLIST_HEADER_SEQUENCE_NOW_CALLED_CPUID + NewHead.CpuId = OldHead.CpuId + 1; +#else + NewHead.Sequence = OldHead.Sequence + 1; +#endif + if (ASMAtomicCmpXchgU64(&pHead->Alignment, NewHead.Alignment, OldHead.Alignment)) + break; + } + return pRet; +} + +DECL_KERNEL32(WORD) Fake_QueryDepthSList(PSLIST_HEADER pHead) +{ + return pHead->Depth; +} + + +/* curl drags these in: */ +DECL_KERNEL32(BOOL) Fake_VerifyVersionInfoA(LPOSVERSIONINFOEXA pInfo, DWORD fTypeMask, DWORDLONG fConditionMask) +{ + OSVERSIONINFOEXA VerInfo; + RT_ZERO(VerInfo); + VerInfo.dwOSVersionInfoSize = sizeof(VerInfo); + if (!GetVersionExA((OSVERSIONINFO *)&VerInfo)) + { + RT_ZERO(VerInfo); + VerInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + BOOL fRet = GetVersionExA((OSVERSIONINFO *)&VerInfo); + if (fRet) + { /* likely */ } + else + { + MY_ASSERT(false, "VerifyVersionInfoA: #1"); + return FALSE; + } + } + + BOOL fRet = TRUE; + for (unsigned i = 0; i < 8 && fRet; i++) + if (fTypeMask & RT_BIT_32(i)) + { + uint32_t uLeft, uRight; + switch (RT_BIT_32(i)) + { +#define MY_CASE(a_Num, a_Member) case a_Num: uLeft = VerInfo.a_Member; uRight = pInfo->a_Member; break + MY_CASE(VER_MINORVERSION, dwMinorVersion); + MY_CASE(VER_MAJORVERSION, dwMajorVersion); + MY_CASE(VER_BUILDNUMBER, dwBuildNumber); + MY_CASE(VER_PLATFORMID, dwPlatformId); + MY_CASE(VER_SERVICEPACKMINOR, wServicePackMinor); + MY_CASE(VER_SERVICEPACKMAJOR, wServicePackMinor); + MY_CASE(VER_SUITENAME, wSuiteMask); + MY_CASE(VER_PRODUCT_TYPE, wProductType); +#undef MY_CASE + default: uLeft = uRight = 0; MY_ASSERT(false, "VerifyVersionInfoA: #2"); + } + switch ((uint8_t)(fConditionMask >> (i*8))) + { + case VER_EQUAL: fRet = uLeft == uRight; break; + case VER_GREATER: fRet = uLeft > uRight; break; + case VER_GREATER_EQUAL: fRet = uLeft >= uRight; break; + case VER_LESS: fRet = uLeft < uRight; break; + case VER_LESS_EQUAL: fRet = uLeft <= uRight; break; + case VER_AND: fRet = (uLeft & uRight) == uRight; break; + case VER_OR: fRet = (uLeft & uRight) != 0; break; + default: fRet = FALSE; MY_ASSERT(false, "VerifyVersionInfoA: #3"); break; + } + } + + return fRet; +} + + +DECL_KERNEL32(ULONGLONG) Fake_VerSetConditionMask(ULONGLONG fConditionMask, DWORD fTypeMask, BYTE bOperator) +{ + for (unsigned i = 0; i < 8; i++) + if (fTypeMask & RT_BIT_32(i)) + { + uint64_t fMask = UINT64_C(0xff) << (i*8); + fConditionMask &= ~fMask; + fConditionMask |= (uint64_t)bOperator << (i*8); + + } + return fConditionMask; +} + + +#if VCC_FAKES_TARGET >= 140 +/** @since 5.0 (windows 2000) */ +DECL_KERNEL32(BOOL) Fake_GetModuleHandleExW(DWORD dwFlags, LPCWSTR pwszModuleName, HMODULE *phModule) +{ + HMODULE hmod; + if (dwFlags & GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS) + { + /** @todo search the loader list. */ + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; + } + else + { + hmod = GetModuleHandleW(pwszModuleName); + if (!hmod) + return FALSE; + } + + /* + * Get references to the module. + */ + if ( !(dwFlags & (GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_PIN)) + && GetModuleHandleW(NULL) != hmod) + { + WCHAR wszModule[MAX_PATH]; + if (GetModuleFileNameW(hmod, wszModule, RT_ELEMENTS(wszModule)) > 0) + { + if (dwFlags & GET_MODULE_HANDLE_EX_FLAG_PIN) + { + uint32_t cRefs = 32; + while (cRefs-- > 0) + LoadLibraryW(wszModule); + } + else if (!LoadLibraryW(wszModule)) + return FALSE; + } + } + + *phModule = hmod; + return TRUE; +} +#endif /* VCC_FAKES_TARGET >= 141 */ + + +#if VCC_FAKES_TARGET >= 140 +/** @since 5.0 (windows 2000) */ +DECL_KERNEL32(BOOL) Fake_SetFilePointerEx(HANDLE hFile, LARGE_INTEGER offDistanceToMove, + PLARGE_INTEGER pNewFilePointer, DWORD dwMoveMethod) +{ + NTSTATUS rcNt; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + FILE_POSITION_INFORMATION PosInfo; + switch (dwMoveMethod) + { + case FILE_BEGIN: + PosInfo.CurrentByteOffset = offDistanceToMove; + break; + + case FILE_CURRENT: + PosInfo.CurrentByteOffset.QuadPart = INT64_MAX; + rcNt = NtQueryInformationFile(hFile, &Ios, &PosInfo, sizeof(PosInfo), FilePositionInformation); + if (NT_SUCCESS(rcNt)) + { + PosInfo.CurrentByteOffset.QuadPart += offDistanceToMove.QuadPart; + break; + } + return FakeSetLastErrorFromNtStatus(rcNt); + + case FILE_END: + { + FILE_STANDARD_INFO StdInfo = {{0}}; + rcNt = NtQueryInformationFile(hFile, &Ios, &StdInfo, sizeof(StdInfo), FileStandardInformation); + if (NT_SUCCESS(rcNt)) + { + PosInfo.CurrentByteOffset.QuadPart = offDistanceToMove.QuadPart + StdInfo.EndOfFile.QuadPart; + break; + } + return FakeSetLastErrorFromNtStatus(rcNt); + } + + default: + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + rcNt = NtSetInformationFile(hFile, &Ios, &PosInfo, sizeof(PosInfo), FilePositionInformation); + if (NT_SUCCESS(rcNt)) + { + if (pNewFilePointer) + *pNewFilePointer = PosInfo.CurrentByteOffset; + return TRUE; + } + return FakeSetLastErrorFromNtStatus(rcNt); +} +#endif /* VCC_FAKES_TARGET >= 140 */ + + +#if VCC_FAKES_TARGET >= 140 +/** @since 5.0 (windows 2000) */ +DECL_KERNEL32(BOOL) Fake_GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER pcbFile) +{ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + FILE_STANDARD_INFO StdInfo = {{0}}; + NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, &StdInfo, sizeof(StdInfo), FileStandardInformation); + if (NT_SUCCESS(rcNt)) + { + *pcbFile = StdInfo.EndOfFile; + return TRUE; + } + return FakeSetLastErrorFromNtStatus(rcNt); +} +#endif /* VCC_FAKES_TARGET >= 140 */ + + + + +/* + * NT 3.51 stuff. + */ + +#if VCC_FAKES_TARGET >= 140 +/** @since 4.0 */ +DECL_KERNEL32(HANDLE) Fake_FindFirstFileExW(LPCWSTR pwszFileName, FINDEX_INFO_LEVELS enmInfoLevel, LPVOID pvFindFileData, + FINDEX_SEARCH_OPS enmSearchOp, LPVOID pvSearchFilter, DWORD dwAdditionalFlags) +{ + // STL: FindFirstFileExW(, FindExInfoBasic, , FindExSearchNameMatch, NULL, 0); + // CRT/_findfile: FindFirstFileExW(, FindExInfoStandard, , FindExSearchNameMatch, NULL, 0); + // CRT/argv_wildcards: FindFirstFileExW(, FindExInfoStandard, , FindExSearchNameMatch, NULL, 0); + MY_ASSERT_STMT_RETURN(dwAdditionalFlags == 0, SetLastError(ERROR_INVALID_PARAMETER), INVALID_HANDLE_VALUE); + MY_ASSERT_STMT_RETURN(pvSearchFilter == NULL, SetLastError(ERROR_INVALID_PARAMETER), INVALID_HANDLE_VALUE); + MY_ASSERT_STMT_RETURN(enmSearchOp == FindExSearchNameMatch, SetLastError(ERROR_INVALID_PARAMETER), INVALID_HANDLE_VALUE); + MY_ASSERT_STMT_RETURN(enmInfoLevel == FindExInfoStandard || enmInfoLevel == FindExInfoBasic, + SetLastError(ERROR_INVALID_PARAMETER), INVALID_HANDLE_VALUE); + + return FindFirstFileW(pwszFileName, (WIN32_FIND_DATAW *)pvFindFileData); +} +#endif /* VCC_FAKES_TARGET >= 140 */ + + +DECL_KERNEL32(BOOL) Fake_IsProcessorFeaturePresent(DWORD enmProcessorFeature) +{ + /* Could make more of an effort here... */ + RT_NOREF(enmProcessorFeature); + return FALSE; +} + + +DECL_KERNEL32(BOOL) Fake_CancelIo(HANDLE hHandle) +{ + /* All NT versions the NTDLL API this corresponds to. */ + RESOLVE_NTDLL_API(NtCancelIoFile); + if (pfnNtCancelIoFile) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = pfnNtCancelIoFile(hHandle, &Ios); + if (RT_SUCCESS(rcNt)) + return TRUE; + if (rcNt == STATUS_INVALID_HANDLE) + SetLastError(ERROR_INVALID_HANDLE); + else + SetLastError(ERROR_INVALID_FUNCTION); + } + else + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + + +/* + * NT 3.50 stuff. + */ + +#if VCC_FAKES_TARGET >= 140 +/** @since 3.51 */ +DECL_KERNEL32(VOID) Fake_FreeLibraryAndExitThread(HMODULE hLibModule, DWORD dwExitCode) +{ + if (hLibModule) + FreeModule(hLibModule); + ExitThread(dwExitCode); +} +#endif /* VCC_FAKES_TARGET >= 140 */ + + +DECL_KERNEL32(BOOL) Fake_IsDebuggerPresent(VOID) +{ + /* Fallback: */ + return FALSE; +} + + +DECL_KERNEL32(VOID) Fake_GetSystemTimeAsFileTime(LPFILETIME pTime) +{ + DWORD dwVersion = GetVersion(); + if ( (dwVersion & 0xff) > 3 + || ( (dwVersion & 0xff) == 3 + && ((dwVersion >> 8) & 0xff) >= 50) ) + { + PKUSER_SHARED_DATA pUsd = (PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA; + + /* use interrupt time */ + LARGE_INTEGER Time; + do + { + Time.HighPart = pUsd->SystemTime.High1Time; + Time.LowPart = pUsd->SystemTime.LowPart; + } while (pUsd->SystemTime.High2Time != Time.HighPart); + + pTime->dwHighDateTime = Time.HighPart; + pTime->dwLowDateTime = Time.LowPart; + } + else + { + /* NT 3.1 didn't have a KUSER_SHARED_DATA nor a GetSystemTimeAsFileTime export. */ + SYSTEMTIME SystemTime; + GetSystemTime(&SystemTime); + BOOL fRet = SystemTimeToFileTime(&SystemTime, pTime); + if (fRet) + { /* likely */ } + else + { + MY_ASSERT(false, "GetSystemTimeAsFileTime: #2"); + pTime->dwHighDateTime = 0; + pTime->dwLowDateTime = 0; + } + } +} + + +/* + * NT 3.1 stuff. + */ + +DECL_KERNEL32(BOOL) Fake_GetVersionExA(LPOSVERSIONINFOA pInfo) +{ + DWORD dwVersion = GetVersion(); + + /* Common fields: */ + pInfo->dwMajorVersion = dwVersion & 0xff; + pInfo->dwMinorVersion = (dwVersion >> 8) & 0xff; + if (!(dwVersion & RT_BIT_32(31))) + pInfo->dwBuildNumber = dwVersion >> 16; + else + pInfo->dwBuildNumber = 511; + pInfo->dwPlatformId = VER_PLATFORM_WIN32_NT; +/** @todo get CSD from registry. */ + pInfo->szCSDVersion[0] = '\0'; + + /* OSVERSIONINFOEX fields: */ + if (pInfo->dwOSVersionInfoSize > sizeof((*pInfo))) + { + LPOSVERSIONINFOEXA pInfoEx = (LPOSVERSIONINFOEXA)pInfo; + if (pInfoEx->dwOSVersionInfoSize > RT_UOFFSETOF(OSVERSIONINFOEXA, wServicePackMinor)) + { + pInfoEx->wServicePackMajor = 0; + pInfoEx->wServicePackMinor = 0; + } + if (pInfoEx->dwOSVersionInfoSize > RT_UOFFSETOF(OSVERSIONINFOEXA, wSuiteMask)) + pInfoEx->wSuiteMask = 0; + if (pInfoEx->dwOSVersionInfoSize > RT_UOFFSETOF(OSVERSIONINFOEXA, wProductType)) + pInfoEx->wProductType = VER_NT_WORKSTATION; + if (pInfoEx->wReserved > RT_UOFFSETOF(OSVERSIONINFOEXA, wProductType)) + pInfoEx->wReserved = 0; + } + + return TRUE; +} + + +DECL_KERNEL32(BOOL) Fake_GetVersionExW(LPOSVERSIONINFOW pInfo) +{ + DWORD dwVersion = GetVersion(); + + /* Common fields: */ + pInfo->dwMajorVersion = dwVersion & 0xff; + pInfo->dwMinorVersion = (dwVersion >> 8) & 0xff; + if (!(dwVersion & RT_BIT_32(31))) + pInfo->dwBuildNumber = dwVersion >> 16; + else + pInfo->dwBuildNumber = 511; + pInfo->dwPlatformId = VER_PLATFORM_WIN32_NT; +/** @todo get CSD from registry. */ + pInfo->szCSDVersion[0] = '\0'; + + /* OSVERSIONINFOEX fields: */ + if (pInfo->dwOSVersionInfoSize > sizeof((*pInfo))) + { + LPOSVERSIONINFOEXW pInfoEx = (LPOSVERSIONINFOEXW)pInfo; + if (pInfoEx->dwOSVersionInfoSize > RT_UOFFSETOF(OSVERSIONINFOEXW, wServicePackMinor)) + { + pInfoEx->wServicePackMajor = 0; + pInfoEx->wServicePackMinor = 0; + } + if (pInfoEx->dwOSVersionInfoSize > RT_UOFFSETOF(OSVERSIONINFOEXW, wSuiteMask)) + pInfoEx->wSuiteMask = 0; + if (pInfoEx->dwOSVersionInfoSize > RT_UOFFSETOF(OSVERSIONINFOEXW, wProductType)) + pInfoEx->wProductType = VER_NT_WORKSTATION; + if (pInfoEx->wReserved > RT_UOFFSETOF(OSVERSIONINFOEXW, wProductType)) + pInfoEx->wReserved = 0; + } + + return TRUE; +} + + +DECL_KERNEL32(LPWCH) Fake_GetEnvironmentStringsW(void) +{ + /* + * Environment is ANSI in NT 3.1. We should only be here for NT 3.1. + * For now, don't try do a perfect job converting it, just do it. + */ + char *pszzEnv = (char *)RTNtCurrentPeb()->ProcessParameters->Environment; + size_t offEnv = 0; + while (pszzEnv[offEnv] != '\0') + { + size_t cchLen = strlen(&pszzEnv[offEnv]); + offEnv += cchLen + 1; + } + size_t const cchEnv = offEnv + 1; + + PRTUTF16 pwszzEnv = (PRTUTF16)HeapAlloc(GetProcessHeap(), 0, cchEnv * sizeof(RTUTF16)); + if (!pwszzEnv) + return NULL; + for (offEnv = 0; offEnv < cchEnv; offEnv++) + { + unsigned char ch = pwszzEnv[offEnv]; + if (!(ch & 0x80)) + pwszzEnv[offEnv] = ch; + else + pwszzEnv[offEnv] = '_'; + } + return pwszzEnv; +} + + +DECL_KERNEL32(BOOL) Fake_FreeEnvironmentStringsW(LPWCH pwszzEnv) +{ + if (pwszzEnv) + HeapFree(GetProcessHeap(), 0, pwszzEnv); + return TRUE; +} + + +DECL_KERNEL32(int) Fake_GetLocaleInfoA(LCID idLocale, LCTYPE enmType, LPSTR pData, int cchData) +{ + NOREF(idLocale); NOREF(enmType); NOREF(pData); NOREF(cchData); + //MY_ASSERT(false, "GetLocaleInfoA: idLocale=%#x enmType=%#x cchData=%#x", idLocale, enmType, cchData); + MY_ASSERT(false, "GetLocaleInfoA"); + SetLastError(ERROR_NOT_SUPPORTED); + return 0; +} + + +#if VCC_FAKES_TARGET >= 140 +/** @since 3.51 */ +DECL_KERNEL32(BOOL) Fake_EnumSystemLocalesW(LOCALE_ENUMPROCW pfnLocaleEnum, DWORD dwFlags) +{ + RT_NOREF(pfnLocaleEnum, dwFlags); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} +#endif /* VCC_FAKES_TARGET >= 140 */ + + +DECL_KERNEL32(BOOL) Fake_EnumSystemLocalesA(LOCALE_ENUMPROCA pfnCallback, DWORD fFlags) +{ + NOREF(pfnCallback); NOREF(fFlags); + //MY_ASSERT(false, "EnumSystemLocalesA: pfnCallback=%p fFlags=%#x", pfnCallback, fFlags); + MY_ASSERT(false, "EnumSystemLocalesA"); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + + +DECL_KERNEL32(BOOL) Fake_IsValidLocale(LCID idLocale, DWORD fFlags) +{ + NOREF(idLocale); NOREF(fFlags); + //MY_ASSERT(false, "IsValidLocale: idLocale fFlags=%#x", idLocale, fFlags); + MY_ASSERT(false, "IsValidLocale"); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + + +DECL_KERNEL32(DWORD_PTR) Fake_SetThreadAffinityMask(HANDLE hThread, DWORD_PTR fAffinityMask) +{ + SYSTEM_INFO SysInfo; + GetSystemInfo(&SysInfo); + //MY_ASSERT(false, "SetThreadAffinityMask: hThread=%p fAffinityMask=%p SysInfo.dwActiveProcessorMask=%p", + // hThread, fAffinityMask, SysInfo.dwActiveProcessorMask); + MY_ASSERT(false, "SetThreadAffinityMask"); + if ( SysInfo.dwActiveProcessorMask == fAffinityMask + || fAffinityMask == ~(DWORD_PTR)0) + return fAffinityMask; + + SetLastError(ERROR_NOT_SUPPORTED); + RT_NOREF(hThread); + return 0; +} + + +DECL_KERNEL32(BOOL) Fake_GetProcessAffinityMask(HANDLE hProcess, PDWORD_PTR pfProcessAffinityMask, PDWORD_PTR pfSystemAffinityMask) +{ + SYSTEM_INFO SysInfo; + GetSystemInfo(&SysInfo); + //MY_ASSERT(false, "GetProcessAffinityMask: SysInfo.dwActiveProcessorMask=%p", SysInfo.dwActiveProcessorMask); + MY_ASSERT(false, "GetProcessAffinityMask"); + if (pfProcessAffinityMask) + *pfProcessAffinityMask = SysInfo.dwActiveProcessorMask; + if (pfSystemAffinityMask) + *pfSystemAffinityMask = SysInfo.dwActiveProcessorMask; + RT_NOREF(hProcess); + return TRUE; +} + + +DECL_KERNEL32(BOOL) Fake_GetHandleInformation(HANDLE hObject, DWORD *pfFlags) +{ + OBJECT_HANDLE_FLAG_INFORMATION Info = { 0, 0 }; + DWORD cbRet = sizeof(Info); + NTSTATUS rcNt = NtQueryObject(hObject, ObjectHandleFlagInformation, &Info, sizeof(Info), &cbRet); + if (NT_SUCCESS(rcNt)) + { + *pfFlags = (Info.Inherit ? HANDLE_FLAG_INHERIT : 0) + | (Info.ProtectFromClose ? HANDLE_FLAG_PROTECT_FROM_CLOSE : 0); + return TRUE; + } + *pfFlags = 0; + //MY_ASSERT(rcNt == STATUS_INVALID_HANDLE, "rcNt=%#x", rcNt); + MY_ASSERT(rcNt == STATUS_INVALID_HANDLE || rcNt == STATUS_INVALID_INFO_CLASS, "GetHandleInformation"); + SetLastError(rcNt == STATUS_INVALID_HANDLE ? ERROR_INVALID_HANDLE : ERROR_INVALID_FUNCTION); /* see also process-win.cpp */ + return FALSE; +} + + +DECL_KERNEL32(BOOL) Fake_SetHandleInformation(HANDLE hObject, DWORD fMask, DWORD fFlags) +{ + NOREF(hObject); NOREF(fMask); NOREF(fFlags); + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; +} + + + +/** + * Resolves all the APIs ones and for all, updating the fake IAT entries. + */ +DECLASM(void) FakeResolve_kernel32(void) +{ + CURRENT_VERSION_VARIABLE(); + + HMODULE hmod = GetModuleHandleW(L"kernel32"); + MY_ASSERT(hmod != NULL, "kernel32"); + +#undef MAKE_IMPORT_ENTRY +#define MAKE_IMPORT_ENTRY(a_uMajorVer, a_uMinorVer, a_Name, a_cb) RESOLVE_IMPORT(a_uMajorVer, a_uMinorVer, a_Name, a_cb) +#ifdef VCC_FAKES_TARGET_VCC100 +# include "vcc-fakes-kernel32-100.h" +#elif defined(VCC_FAKES_TARGET_VCC140) +# include "vcc-fakes-kernel32-141.h" +#elif defined(VCC_FAKES_TARGET_VCC141) +# include "vcc-fakes-kernel32-141.h" +#elif defined(VCC_FAKES_TARGET_VCC142) +# include "vcc-fakes-kernel32-141.h" +#else +# error "Port me!" +#endif + + g_fInitialized = true; +} + + +/* Dummy to force dragging in this object in the link, so the linker + won't accidentally use the symbols from kernel32. */ +extern "C" int vcc100_kernel32_fakes_cpp(void) +{ + return 42; +} + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-msvcrt.cpp b/src/VBox/Runtime/r3/win/vcc-fakes-msvcrt.cpp new file mode 100644 index 00000000..9aaf2d4d --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-msvcrt.cpp @@ -0,0 +1,93 @@ +/* $Id: vcc-fakes-msvcrt.cpp $ */ +/** @file + * IPRT - Tricks to make the Visual C++ 2010 CRT work on NT4, W2K and XP. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <stdlib.h> + + +#ifndef RT_ARCH_X86 +# error "This code is X86 only" +#endif + + + +/* This one is static in libcmt, fortunately no rocket science. */ +extern "C" void __cdecl my_initterm(PFNRT *papfnStart, PFNRT *papfnEnd) +{ + for (; (uintptr_t)papfnStart < (uintptr_t)papfnEnd; papfnStart++) + if (*papfnStart) + (*papfnStart)(); +} + +extern "C" PFNRT __cdecl my_dllonexit(PFNRT pfnToAdd, PFNRT **ppapfnStart, PFNRT **ppapfnEnd) +{ + /* This is _very_ crude, but it'll probably do for our purposes... */ + size_t cItems = *ppapfnEnd - *ppapfnStart; + *ppapfnStart = (PFNRT *)realloc(*ppapfnStart, (cItems + 1) * sizeof(**ppapfnStart)); + (*ppapfnStart)[cItems] = pfnToAdd; + *ppapfnEnd = &(*ppapfnStart)[cItems + 1]; + return pfnToAdd; +} + +extern "C" int _newmode; +extern "C" int __cdecl __setargv(void); +extern "C" int __cdecl _setargv(void); + +extern "C" int __cdecl my_getmainargs(int *pcArgs, char ***ppapszArgs, char ***ppapszEnv, int fDoWildcardExp, int *pfNewMode) +{ + _newmode = *pfNewMode; + + Assert(!fDoWildcardExp); + int rc = _setargv(); + if (rc >= 0) + { + *pcArgs = __argc; + *ppapszArgs = __argv; + *ppapszEnv = _environ; + } + return rc; +} + +extern "C" void __cdecl my_setusermatherr(PFNRT pfnIgnore) +{ + RT_NOREF(pfnIgnore); + /* pure stub. */ +} + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm new file mode 100644 index 00000000..4eb482b2 --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm @@ -0,0 +1,57 @@ +; $Id: vcc-fakes-ntdll-A.asm $ +;; @file +; IPRT - Wrappers for ntdll APIs misisng NT4. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "iprt/asmdefs.mac" + +%ifndef RT_ARCH_X86 + %error "This is x86 only code. +%endif + + +%macro MAKE_IMPORT_ENTRY 2 +extern _ %+ %1 %+ @ %+ %2 +global __imp__ %+ %1 %+ @ %+ %2 +__imp__ %+ %1 %+ @ %+ %2: + dd _ %+ %1 %+ @ %+ %2 + +%endmacro + + +BEGINDATA +GLOBALNAME vcc100_ntdll_fakes_asm + +MAKE_IMPORT_ENTRY RtlGetLastWin32Error, 0 + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ntdll.cpp b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll.cpp new file mode 100644 index 00000000..9aa5dd01 --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll.cpp @@ -0,0 +1,84 @@ +/* $Id: vcc-fakes-ntdll.cpp $ */ +/** @file + * IPRT - Tricks to make the Visual C++ 2010 CRT work on NT4, W2K and XP - NTDLL.DLL. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#ifndef RT_ARCH_X86 +# error "This code is X86 only" +#endif + +#include <iprt/win/windows.h> + + + +/** Dynamically resolves an kernel32 API. */ +#define RESOLVE_ME(ApiNm) \ + static bool volatile s_fInitialized = false; \ + static decltype(ApiNm) *s_pfnApi = NULL; \ + decltype(ApiNm) *pfnApi; \ + if (s_fInitialized) \ + pfnApi = s_pfnApi; \ + else \ + { \ + pfnApi = (decltype(pfnApi))GetProcAddress(GetModuleHandleW(L"ntdll.dll"), #ApiNm); \ + s_pfnApi = pfnApi; \ + s_fInitialized = true; \ + } do {} while (0) \ + + +extern "C" +__declspec(dllexport) +ULONG WINAPI RtlGetLastWin32Error(VOID) +{ + RESOLVE_ME(RtlGetLastWin32Error); + if (pfnApi) + return pfnApi(); + return GetLastError(); +} + + +/* Dummy to force dragging in this object in the link, so the linker + won't accidentally use the symbols from kernel32. */ +extern "C" int vcc100_ntdll_fakes_cpp(void) +{ + return 42; +} + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm new file mode 100644 index 00000000..ddf7491b --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm @@ -0,0 +1,59 @@ +; $Id: vcc-fakes-shell32-A.asm $ +;; @file +; IPRT - Wrappers for shell32 APIs missing in NT 4 and earlier. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" + +%ifndef RT_ARCH_X86 + %error "This is x86 only code. +%endif + + +%macro MAKE_IMPORT_ENTRY 2 +extern _ %+ %1 %+ @ %+ %2 +global __imp__ %+ %1 %+ @ %+ %2 +__imp__ %+ %1 %+ @ %+ %2: + dd _ %+ %1 %+ @ %+ %2 + +%endmacro + + +BEGINDATA +GLOBALNAME vcc100_shell32_fakes_asm + +; NT 3.1 +MAKE_IMPORT_ENTRY CommandLineToArgvW, 8 + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-shell32.cpp b/src/VBox/Runtime/r3/win/vcc-fakes-shell32.cpp new file mode 100644 index 00000000..bd82befa --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-shell32.cpp @@ -0,0 +1,103 @@ +/* $Id: vcc-fakes-shell32.cpp $ */ +/** @file + * IPRT - Tricks to make the Visual C++ 2010 CRT work on NT4, W2K and XP. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define RT_NO_STRICT /* Minimal deps so that it works on NT 3.51 too. */ +#include <iprt/types.h> +#include <iprt/assert.h> +#include <iprt/string.h> + +#ifndef RT_ARCH_X86 +# error "This code is X86 only" +#endif + +#define CommandLineToArgvW Ignore_CommandLineToArgvW + +#include <iprt/nt/nt-and-windows.h> + +#undef CommandLineToArgvW + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Dynamically resolves an kernel32 API. */ +#define RESOLVE_ME(ApiNm) \ + static decltype(ShellExecuteW) * volatile s_pfnInitialized = NULL; \ + static decltype(ApiNm) *s_pfnApi = NULL; \ + decltype(ApiNm) *pfnApi; \ + if (s_pfnInitialized) \ + pfnApi = s_pfnApi; \ + else \ + { \ + pfnApi = (decltype(pfnApi))GetProcAddress(GetModuleHandleW(L"shell32"), #ApiNm); \ + s_pfnApi = pfnApi; \ + s_pfnInitialized = ShellExecuteW; /* ensures shell32 is loaded */ \ + } do {} while (0) + + +/** Declare a shell32 API. + * @note We are not exporting them as that causes duplicate symbol troubles in + * the OpenGL bits. */ +#define DECL_SHELL32(a_Type) extern "C" a_Type WINAPI + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +DECL_SHELL32(LPWSTR *) CommandLineToArgvW(LPCWSTR pwszCmdLine, int *pcArgs) +{ + RESOLVE_ME(CommandLineToArgvW); + if (pfnApi) + return pfnApi(pwszCmdLine, pcArgs); + + *pcArgs = 0; + return NULL; +} + + +/* Dummy to force dragging in this object in the link, so the linker + won't accidentally use the symbols from shell32. */ +extern "C" int vcc100_shell32_fakes_cpp(void) +{ + return 42; +} + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm new file mode 100644 index 00000000..2158917a --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm @@ -0,0 +1,58 @@ +; $Id: vcc-fakes-ws2_32-A.asm $ +;; @file +; IPRT - Wrappers for ws2_32 APIs misisng NT4. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "iprt/asmdefs.mac" + +%ifndef RT_ARCH_X86 + %error "This is x86 only code. +%endif + + +%macro MAKE_IMPORT_ENTRY 2 +extern _ %+ %1 %+ @ %+ %2 +global __imp__ %+ %1 %+ @ %+ %2 +__imp__ %+ %1 %+ @ %+ %2: + dd _ %+ %1 %+ @ %+ %2 + +%endmacro + + +BEGINDATA +GLOBALNAME vcc100_ws2_32_fakes_asm + +MAKE_IMPORT_ENTRY getaddrinfo, 16 +MAKE_IMPORT_ENTRY freeaddrinfo, 4 + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32.cpp b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32.cpp new file mode 100644 index 00000000..b12f253e --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32.cpp @@ -0,0 +1,113 @@ +/* $Id: vcc-fakes-ws2_32.cpp $ */ +/** @file + * IPRT - Tricks to make the Visual C++ 2010 CRT work on NT4, W2K and XP - WS2_32.DLL. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/types.h> +#include <iprt/asm.h> +#include <iprt/string.h> + +#ifndef RT_ARCH_X86 +# error "This code is X86 only" +#endif + +#define getaddrinfo Ignore_getaddrinfo +#define freeaddrinfo Ignore_freeaddrinfo + +#include <iprt/win/winsock2.h> +#include <iprt/win/ws2tcpip.h> + +#undef getaddrinfo +#undef freeaddrinfo + + +/** Dynamically resolves an kernel32 API. */ +#define RESOLVE_ME(ApiNm) \ + static bool volatile s_fInitialized = false; \ + static decltype(ApiNm) *s_pfnApi = NULL; \ + decltype(ApiNm) *pfnApi; \ + if (s_fInitialized) \ + pfnApi = s_pfnApi; \ + else \ + { \ + pfnApi = (decltype(pfnApi))GetProcAddress(GetModuleHandleW(L"wc2_32.dll"), #ApiNm); \ + s_pfnApi = pfnApi; \ + s_fInitialized = true; \ + } do {} while (0) \ + + +extern "C" +__declspec(dllexport) +int WINAPI getaddrinfo(const char *pszNodeName, const char *pszServiceName, const struct addrinfo *pHints, + struct addrinfo **ppResults) +{ + RESOLVE_ME(getaddrinfo); + if (pfnApi) + return pfnApi(pszNodeName, pszServiceName, pHints, ppResults); + + /** @todo fallback */ + WSASetLastError(WSAEAFNOSUPPORT); + return WSAEAFNOSUPPORT; +} + + + +extern "C" +__declspec(dllexport) +void WINAPI freeaddrinfo(struct addrinfo *pResults) +{ + RESOLVE_ME(freeaddrinfo); + if (pfnApi) + pfnApi(pResults); + else + { + Assert(!pResults); + /** @todo fallback */ + } +} + + + +/* Dummy to force dragging in this object in the link, so the linker + won't accidentally use the symbols from kernel32. */ +extern "C" int vcc100_ws2_32_fakes_cpp(void) +{ + return 42; +} + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes.h b/src/VBox/Runtime/r3/win/vcc-fakes.h new file mode 100644 index 00000000..6e72e2b8 --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes.h @@ -0,0 +1,155 @@ +/* $Id: vcc-fakes.h $ */ +/** @file + * IPRT - Common macros for the Visual C++ 2010+ CRT import fakes. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_win_vcc_fakes_h +#define IPRT_INCLUDED_SRC_r3_win_vcc_fakes_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef RT_STRICT +# include <stdio.h> /* _snprintf */ +#endif + + +/** @def MY_ASSERT + * We use a special assertion macro here to avoid dragging in IPRT bits in + * places which cannot handle it (direct GA 3D bits or something like that). + * + * Turns out snprintf is off limits too. Needs Locale info and runs out of + * stack + */ +#ifdef RT_STRICT +# if 1 +# define MY_ASSERT(a_Expr, g_szMsg) \ + do { \ + if ((a_Expr)) \ + { /*likely*/ } \ + else \ + { \ + OutputDebugStringA("Assertion failed on line " RT_XSTR(__LINE__) ": " RT_XSTR(a_Expr) "\n"); \ + OutputDebugStringA("Assertion message: " g_szMsg "\n"); \ + RT_BREAKPOINT(); \ + } \ + } while (0) +# define MY_ASSERT_STMT_RETURN(a_Expr, a_Stmt, a_rc) \ + do { \ + if (a_Expr) \ + { /* likely */ } \ + else \ + { \ + OutputDebugStringA("Assertion failed on line " RT_XSTR(__LINE__) ": " RT_XSTR(a_Expr) "\n"); \ + RT_BREAKPOINT(); \ + a_Stmt; \ + return (a_rc); \ + } \ + } while (0) +# else +# define MY_ASSERT(a_Expr, ...) \ + do { \ + if ((a_Expr)) \ + { /*likely*/ } \ + else \ + { \ + char szTmp[256]; \ + _snprintf(szTmp, sizeof(szTmp), "Assertion failed on line %u in '%s': %s", __LINE__, __PRETTY_FUNCTION__, #a_Expr); \ + OutputDebugStringA(szTmp); \ + _snprintf(szTmp, sizeof(szTmp), __VA_ARGS__); \ + OutputDebugStringA(szTmp); \ + RT_BREAKPOINT(); \ + } \ + } while (0) +# endif +#else +# define MY_ASSERT(a_Expr, ...) do { } while (0) +# define MY_ASSERT_STMT_RETURN(a_Expr, a_Stmt, a_rc) \ + do { if (a_Expr) { /* likely */ } else { a_Stmt; return (a_rc); }} while (0) +#endif + + +/** Dynamically resolves an NTDLL API we need. */ +#define RESOLVE_NTDLL_API(ApiNm) \ + static bool volatile s_fInitialized##ApiNm = false; \ + static decltype(ApiNm) *s_pfn##ApiNm = NULL; \ + decltype(ApiNm) *pfn##ApiNm; \ + if (s_fInitialized##ApiNm) \ + pfn##ApiNm = s_pfn##ApiNm; \ + else \ + { \ + pfn##ApiNm = (decltype(pfn##ApiNm))GetProcAddress(GetModuleHandleW(L"ntdll"), #ApiNm); \ + s_pfn##ApiNm = pfn##ApiNm; \ + s_fInitialized##ApiNm = true; \ + } do {} while (0) + + +/** Declare a kernel32 API. + * @note We are not exporting them as that causes duplicate symbol troubles in + * the OpenGL bits. */ +#define DECL_KERNEL32(a_Type) extern "C" a_Type WINAPI + + +/** Ignore comments. */ +#define COMMENT(a_Text) + +/** Used for MAKE_IMPORT_ENTRY when declaring external g_pfnXxxx variables. */ +#define DECLARE_FUNCTION_POINTER(a_Name, a_cb) extern "C" decltype(a_Name) *RT_CONCAT(g_pfn, a_Name); + +/** Used in the InitFakes method to decl are uCurVersion used by assertion. */ +#ifdef RT_STRICT +# define CURRENT_VERSION_VARIABLE() \ + unsigned uCurVersion = GetVersion(); \ + uCurVersion = ((uCurVersion & 0xff) << 8) | ((uCurVersion >> 8) & 0xff) +#else +# define CURRENT_VERSION_VARIABLE() (void)0 +#endif + + +/** Used for MAKE_IMPORT_ENTRY when resolving an import. */ +#define RESOLVE_IMPORT(a_uMajorVer, a_uMinorVer, a_Name, a_cb) \ + do { \ + FARPROC pfnApi = GetProcAddress(hmod, #a_Name); \ + if (pfnApi) \ + RT_CONCAT(g_pfn, a_Name) = (decltype(a_Name) *)pfnApi; \ + else \ + { \ + MY_ASSERT(uCurVersion < (((a_uMajorVer) << 8) | (a_uMinorVer)), #a_Name); \ + RT_CONCAT(g_pfn, a_Name) = RT_CONCAT(Fake_,a_Name); \ + } \ + } while (0); + + +#endif /* !IPRT_INCLUDED_SRC_r3_win_vcc_fakes_h */ + diff --git a/src/VBox/Runtime/r3/win/vcc-fakes.mac b/src/VBox/Runtime/r3/win/vcc-fakes.mac new file mode 100644 index 00000000..37c51543 --- /dev/null +++ b/src/VBox/Runtime/r3/win/vcc-fakes.mac @@ -0,0 +1,76 @@ +; $Id: vcc-fakes.mac $ +;; @file +; IPRT - Common macros for the Visual C++ 2010+ CRT import fakes. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" + +%ifndef RT_ARCH_X86 + %error "This is x86 only code. +%endif + + +;; +; Fakes an API import table entry. +; +; @param 1 The function name +; @param 2 Number of bytes of parameters for x86. +%macro MAKE_IMPORT_ENTRY_INTERNAL 2 + +BEGINDATA +extern _FakeResolve_ %+ FAKE_MODULE_NAME + +extern _Fake_ %+ %1 %+ @ %+ %2 +global __imp__ %+ %1 %+ @ %+ %2 ; The import address table (IAT) entry name. +__imp__ %+ %1 %+ @ %+ %2: +global _g_pfn %+ %1 ; C accessible label. +_g_pfn %+ %1: + dd FakeLazyInit_ %+ %1 + +BEGINCODE +FakeLazyInit_ %+ %1: + pusha + call _FakeResolve_ %+ FAKE_MODULE_NAME + popa +global _ %+ %1 %+ @ %+ %2 ; The imported function stub. +_ %+ %1 %+ @ %+ %2: + jmp [_g_pfn %+ %1] + + +%endmacro + +%define MAKE_IMPORT_ENTRY(a_uMajorVer, a_uMinorVer, a_Name, a_cbParams) MAKE_IMPORT_ENTRY_INTERNAL a_Name, a_cbParams +%define COMMENT(a_Text) + diff --git a/src/VBox/Runtime/r3/xml.cpp b/src/VBox/Runtime/r3/xml.cpp new file mode 100644 index 00000000..a6661760 --- /dev/null +++ b/src/VBox/Runtime/r3/xml.cpp @@ -0,0 +1,2357 @@ +/* $Id: xml.cpp $ */ +/** @file + * IPRT - XML Manipulation API. + * + * @note Not available in no-CRT mode because it relies too heavily on + * exceptions, as well as using std::list and std::map. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cpp/xml.h> + +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/cpp/lock.h> + +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/globals.h> +#include <libxml/xmlIO.h> +#include <libxml/xmlsave.h> +#include <libxml/uri.h> + +#include <libxml/xmlschemas.h> + +#include <map> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Global module initialization structure. This is to wrap non-reentrant bits + * of libxml, among other things. + * + * The constructor and destructor of this structure are used to perform global + * module initialization and cleanup. There must be only one global variable of + * this structure. + */ +static class Global +{ +public: + + Global() + { + /* Check the parser version. The docs say it will kill the app if + * there is a serious version mismatch, but I couldn't find it in the + * source code (it only prints the error/warning message to the console) so + * let's leave it as is for informational purposes. */ + LIBXML_TEST_VERSION + + /* Init libxml */ + xmlInitParser(); + + /* Save the default entity resolver before someone has replaced it */ + sxml.defaultEntityLoader = xmlGetExternalEntityLoader(); + } + + ~Global() + { + /* Shutdown libxml */ + xmlCleanupParser(); + } + + struct + { + xmlExternalEntityLoader defaultEntityLoader; + + /** Used to provide some thread safety missing in libxml2 (see e.g. + * XmlTreeBackend::read()) */ + RTCLockMtx lock; + } + sxml; /* XXX naming this xml will break with gcc-3.3 */ +} gGlobal; + + + +namespace xml +{ + +//////////////////////////////////////////////////////////////////////////////// +// +// Exceptions +// +//////////////////////////////////////////////////////////////////////////////// + +LogicError::LogicError(RT_SRC_POS_DECL) + : RTCError(NULL) +{ + char *msg = NULL; + RTStrAPrintf(&msg, "In '%s', '%s' at #%d", + pszFunction, pszFile, iLine); + setWhat(msg); + RTStrFree(msg); +} + +XmlError::XmlError(xmlErrorPtr aErr) +{ + if (!aErr) + throw EInvalidArg(RT_SRC_POS); + + char *msg = Format(aErr); + setWhat(msg); + RTStrFree(msg); +} + +/** + * Composes a single message for the given error. The caller must free the + * returned string using RTStrFree() when no more necessary. + */ +/* static */ char *XmlError::Format(xmlErrorPtr aErr) +{ + const char *msg = aErr->message ? aErr->message : "<none>"; + size_t msgLen = strlen(msg); + /* strip spaces, trailing EOLs and dot-like char */ + while (msgLen && strchr(" \n.?!", msg [msgLen - 1])) + --msgLen; + + char *finalMsg = NULL; + RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d", + msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2); + + return finalMsg; +} + +EIPRTFailure::EIPRTFailure(int aRC, const char *pszContextFmt, ...) + : RuntimeError(NULL) + , mRC(aRC) +{ + va_list va; + va_start(va, pszContextFmt); + m_strMsg.printfVNoThrow(pszContextFmt, va); + va_end(va); + m_strMsg.appendPrintfNoThrow(" %Rrc (%Rrs)", aRC, aRC); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// File Class +// +////////////////////////////////////////////////////////////////////////////// + +struct File::Data +{ + Data(RTFILE a_hHandle, const char *a_pszFilename, bool a_fFlushOnClose) + : strFileName(a_pszFilename) + , handle(a_hHandle) + , opened(a_hHandle != NIL_RTFILE) + , flushOnClose(a_fFlushOnClose) + { } + + ~Data() + { + if (flushOnClose) + { + RTFileFlush(handle); + if (!strFileName.isEmpty()) + RTDirFlushParent(strFileName.c_str()); + } + + if (opened) + { + RTFileClose(handle); + handle = NIL_RTFILE; + opened = false; + } + } + + RTCString strFileName; + RTFILE handle; + bool opened : 1; + bool flushOnClose : 1; +}; + +File::File(Mode aMode, const char *aFileName, bool aFlushIt /* = false */) + : m(NULL) +{ + /* Try open the file first, as the destructor will not be invoked if we throw anything from here. For details see: + https://stackoverflow.com/questions/9971782/destructor-not-invoked-when-an-exception-is-thrown-in-the-constructor */ + uint32_t flags = 0; + const char *pcszMode = "???"; + switch (aMode) + { + /** @todo change to RTFILE_O_DENY_WRITE where appropriate. */ + case Mode_Read: + flags = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE; + pcszMode = "reading"; + break; + case Mode_WriteCreate: // fail if file exists + flags = RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE; + pcszMode = "writing"; + break; + case Mode_Overwrite: // overwrite if file exists + flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE; + pcszMode = "overwriting"; + break; + case Mode_ReadWrite: + flags = RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE; + pcszMode = "reading/writing"; + break; + } + RTFILE hFile = NIL_RTFILE; + int vrc = RTFileOpen(&hFile, aFileName, flags); + if (RT_FAILURE(vrc)) + throw EIPRTFailure(vrc, "Runtime error opening '%s' for %s", aFileName, pcszMode); + + /* Now we can create the data and stuff: */ + try + { + m = new Data(hFile, aFileName, aFlushIt && (flags & RTFILE_O_ACCESS_MASK) != RTFILE_O_READ); + } + catch (std::bad_alloc &) + { + RTFileClose(hFile); + throw; + } +} + +File::File(RTFILE aHandle, const char *aFileName /* = NULL */, bool aFlushIt /* = false */) + : m(NULL) +{ + if (aHandle == NIL_RTFILE) + throw EInvalidArg(RT_SRC_POS); + + m = new Data(aHandle, aFileName, aFlushIt); + + setPos(0); +} + +File::~File() +{ + if (m) + { + delete m; + m = NULL; + } +} + +const char *File::uri() const +{ + return m->strFileName.c_str(); +} + +uint64_t File::pos() const +{ + uint64_t p = 0; + int vrc = RTFileSeek(m->handle, 0, RTFILE_SEEK_CURRENT, &p); + if (RT_SUCCESS(vrc)) + return p; + + throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str()); +} + +void File::setPos(uint64_t aPos) +{ + uint64_t p = 0; + unsigned method = RTFILE_SEEK_BEGIN; + int vrc = VINF_SUCCESS; + + /* check if we overflow int64_t and move to INT64_MAX first */ + if ((int64_t)aPos < 0) + { + vrc = RTFileSeek(m->handle, INT64_MAX, method, &p); + aPos -= (uint64_t)INT64_MAX; + method = RTFILE_SEEK_CURRENT; + } + /* seek the rest */ + if (RT_SUCCESS(vrc)) + vrc = RTFileSeek(m->handle, (int64_t) aPos, method, &p); + if (RT_SUCCESS(vrc)) + return; + + throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str()); +} + +int File::read(char *aBuf, int aLen) +{ + size_t len = aLen; + int vrc = RTFileRead(m->handle, aBuf, len, &len); + if (RT_SUCCESS(vrc)) + return (int)len; + + throw EIPRTFailure(vrc, "Runtime error reading from file '%s'", m->strFileName.c_str()); +} + +int File::write(const char *aBuf, int aLen) +{ + size_t len = aLen; + int vrc = RTFileWrite(m->handle, aBuf, len, &len); + if (RT_SUCCESS(vrc)) + return (int)len; + + throw EIPRTFailure(vrc, "Runtime error writing to file '%s'", m->strFileName.c_str()); +} + +void File::truncate() +{ + int vrc = RTFileSetSize(m->handle, pos()); + if (RT_SUCCESS(vrc)) + return; + + throw EIPRTFailure(vrc, "Runtime error truncating file '%s'", m->strFileName.c_str()); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// MemoryBuf Class +// +////////////////////////////////////////////////////////////////////////////// + +struct MemoryBuf::Data +{ + Data() + : buf(NULL), len(0), uri(NULL), pos(0) {} + + const char *buf; + size_t len; + char *uri; + + size_t pos; +}; + +MemoryBuf::MemoryBuf(const char *aBuf, size_t aLen, const char *aURI /* = NULL */) + : m(new Data()) +{ + if (aBuf == NULL) + throw EInvalidArg(RT_SRC_POS); + + m->buf = aBuf; + m->len = aLen; + m->uri = RTStrDup(aURI); +} + +MemoryBuf::~MemoryBuf() +{ + RTStrFree(m->uri); +} + +const char *MemoryBuf::uri() const +{ + return m->uri; +} + +uint64_t MemoryBuf::pos() const +{ + return m->pos; +} + +void MemoryBuf::setPos(uint64_t aPos) +{ + size_t off = (size_t)aPos; + if ((uint64_t) off != aPos) + throw EInvalidArg(); + + if (off > m->len) + throw EInvalidArg(); + + m->pos = off; +} + +int MemoryBuf::read(char *aBuf, int aLen) +{ + if (m->pos >= m->len) + return 0 /* nothing to read */; + + size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos; + memcpy(aBuf, m->buf + m->pos, len); + m->pos += len; + + return (int)len; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// GlobalLock class +// +//////////////////////////////////////////////////////////////////////////////// + +struct GlobalLock::Data +{ + PFNEXTERNALENTITYLOADER pfnOldLoader; + RTCLock lock; + + Data() + : pfnOldLoader(NULL) + , lock(gGlobal.sxml.lock) + { + } +}; + +GlobalLock::GlobalLock() + : m(new Data()) +{ +} + +GlobalLock::~GlobalLock() +{ + if (m->pfnOldLoader) + xmlSetExternalEntityLoader(m->pfnOldLoader); + delete m; + m = NULL; +} + +void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pfnLoader) +{ + m->pfnOldLoader = (PFNEXTERNALENTITYLOADER)xmlGetExternalEntityLoader(); + xmlSetExternalEntityLoader(pfnLoader); +} + +// static +xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI, + const char *aID, + xmlParserCtxt *aCtxt) +{ + return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt); +} + + + +//////////////////////////////////////////////////////////////////////////////// +// +// Node class +// +//////////////////////////////////////////////////////////////////////////////// + +Node::Node(EnumType type, + Node *pParent, + PRTLISTANCHOR pListAnchor, + xmlNode *pLibNode, + xmlAttr *pLibAttr) + : m_Type(type) + , m_pParent(pParent) + , m_pLibNode(pLibNode) + , m_pLibAttr(pLibAttr) + , m_pcszNamespacePrefix(NULL) + , m_pcszNamespaceHref(NULL) + , m_pcszName(NULL) + , m_pParentListAnchor(pListAnchor) +{ + RTListInit(&m_listEntry); +} + +Node::~Node() +{ +} + +/** + * Returns the name of the node, which is either the element name or + * the attribute name. For other node types it probably returns NULL. + * @return + */ +const char *Node::getName() const +{ + return m_pcszName; +} + +/** + * Returns the name of the node, which is either the element name or + * the attribute name. For other node types it probably returns NULL. + * @return + */ +const char *Node::getPrefix() const +{ + return m_pcszNamespacePrefix; +} + +/** + * Returns the XML namespace URI, which is the attribute name. For other node types it probably + * returns NULL. + * @return + */ +const char *Node::getNamespaceURI() const +{ + return m_pcszNamespaceHref; +} + +/** + * Variant of nameEquals that checks the namespace as well. + * @param pcszNamespace + * @param pcsz + * @return + */ +bool Node::nameEqualsNS(const char *pcszNamespace, const char *pcsz) const +{ + if (m_pcszName == pcsz) + return true; + if (m_pcszName == NULL) + return false; + if (pcsz == NULL) + return false; + if (strcmp(m_pcszName, pcsz)) + return false; + + // name matches: then check namespaces as well + if (!pcszNamespace) + return true; + // caller wants namespace: + if (!m_pcszNamespacePrefix) + // but node has no namespace: + return false; + return !strcmp(m_pcszNamespacePrefix, pcszNamespace); +} + +/** + * Variant of nameEquals that checks the namespace as well. + * + * @returns true if equal, false if not. + * @param pcsz The element name. + * @param cchMax The maximum number of character from @a pcsz to + * match. + * @param pcszNamespace The name space prefix or NULL (default). + */ +bool Node::nameEqualsN(const char *pcsz, size_t cchMax, const char *pcszNamespace /* = NULL*/) const +{ + /* Match the name. */ + if (!m_pcszName) + return false; + if (!pcsz || cchMax == 0) + return false; + if (strncmp(m_pcszName, pcsz, cchMax)) + return false; + if (strlen(m_pcszName) > cchMax) + return false; + + /* Match name space. */ + if (!pcszNamespace) + return true; /* NULL, anything goes. */ + if (!m_pcszNamespacePrefix) + return false; /* Element has no namespace. */ + return !strcmp(m_pcszNamespacePrefix, pcszNamespace); +} + +/** + * Returns the value of a node. If this node is an attribute, returns + * the attribute value; if this node is an element, then this returns + * the element text content. + * @return + */ +const char *Node::getValue() const +{ + if ( m_pLibAttr + && m_pLibAttr->children + ) + // libxml hides attribute values in another node created as a + // single child of the attribute node, and it's in the content field + return (const char *)m_pLibAttr->children->content; + + if ( m_pLibNode + && m_pLibNode->children) + return (const char *)m_pLibNode->children->content; + + return NULL; +} + +/** + * Returns the value of a node. If this node is an attribute, returns + * the attribute value; if this node is an element, then this returns + * the element text content. + * @return + * @param cchValueLimit If the length of the returned value exceeds this + * limit a EIPRTFailure exception will be thrown. + */ +const char *Node::getValueN(size_t cchValueLimit) const +{ + if ( m_pLibAttr + && m_pLibAttr->children + ) + { + // libxml hides attribute values in another node created as a + // single child of the attribute node, and it's in the content field + AssertStmt(strlen((const char *)m_pLibAttr->children->content) <= cchValueLimit, throw EIPRTFailure(VERR_BUFFER_OVERFLOW, "Attribute '%s' exceeds limit of %zu bytes", m_pcszName, cchValueLimit)); + return (const char *)m_pLibAttr->children->content; + } + + if ( m_pLibNode + && m_pLibNode->children) + { + AssertStmt(strlen((const char *)m_pLibNode->children->content) <= cchValueLimit, throw EIPRTFailure(VERR_BUFFER_OVERFLOW, "Element '%s' exceeds limit of %zu bytes", m_pcszName, cchValueLimit)); + return (const char *)m_pLibNode->children->content; + } + + return NULL; +} + +/** + * Copies the value of a node into the given integer variable. + * Returns TRUE only if a value was found and was actually an + * integer of the given type. + * @return + */ +bool Node::copyValue(int32_t &i) const +{ + const char *pcsz; + if ( ((pcsz = getValue())) + && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i)) + ) + return true; + + return false; +} + +/** + * Copies the value of a node into the given integer variable. + * Returns TRUE only if a value was found and was actually an + * integer of the given type. + * @return + */ +bool Node::copyValue(uint32_t &i) const +{ + const char *pcsz; + if ( ((pcsz = getValue())) + && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i)) + ) + return true; + + return false; +} + +/** + * Copies the value of a node into the given integer variable. + * Returns TRUE only if a value was found and was actually an + * integer of the given type. + * @return + */ +bool Node::copyValue(int64_t &i) const +{ + const char *pcsz; + if ( ((pcsz = getValue())) + && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i)) + ) + return true; + + return false; +} + +/** + * Copies the value of a node into the given integer variable. + * Returns TRUE only if a value was found and was actually an + * integer of the given type. + * @return + */ +bool Node::copyValue(uint64_t &i) const +{ + const char *pcsz; + if ( ((pcsz = getValue())) + && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i)) + ) + return true; + + return false; +} + +/** + * Returns the line number of the current node in the source XML file. + * Useful for error messages. + * @return + */ +int Node::getLineNumber() const +{ + if (m_pLibAttr) + return m_pParent->m_pLibNode->line; + + return m_pLibNode->line; +} + +/** + * Private element constructor. + * + * @param pElmRoot Pointer to the root element. + * @param pParent Pointer to the parent element (always an ElementNode, + * despite the type). NULL for the root node. + * @param pListAnchor Pointer to the m_children member of the parent. NULL + * for the root node. + * @param pLibNode Pointer to the libxml2 node structure. + */ +ElementNode::ElementNode(const ElementNode *pElmRoot, + Node *pParent, + PRTLISTANCHOR pListAnchor, + xmlNode *pLibNode) + : Node(IsElement, + pParent, + pListAnchor, + pLibNode, + NULL) +{ + m_pElmRoot = pElmRoot ? pElmRoot : this; // If NULL is passed, then this is the root element. + m_pcszName = (const char *)pLibNode->name; + + if (pLibNode->ns) + { + m_pcszNamespacePrefix = (const char *)m_pLibNode->ns->prefix; + m_pcszNamespaceHref = (const char *)m_pLibNode->ns->href; + } + + RTListInit(&m_children); + RTListInit(&m_attributes); +} + +ElementNode::~ElementNode() +{ + Node *pCur, *pNext; + RTListForEachSafeCpp(&m_children, pCur, pNext, Node, m_listEntry) + { + delete pCur; + } + RTListInit(&m_children); + + RTListForEachSafeCpp(&m_attributes, pCur, pNext, Node, m_listEntry) + { + delete pCur; + } + RTListInit(&m_attributes); +} + + +ElementNode const *ElementNode::getNextTreeElement(ElementNode const *pElmRoot /*= NULL */) const +{ + /* + * Consider children first. + */ + ElementNode const *pChild = getFirstChildElement(); + if (pChild) + return pChild; + + /* + * Then siblings, aunts and uncles. + */ + ElementNode const *pCur = this; + do + { + ElementNode const *pSibling = pCur->getNextSibilingElement(); + if (pSibling != NULL) + return pSibling; + + pCur = static_cast<const xml::ElementNode *>(pCur->m_pParent); + Assert(pCur || pCur == pElmRoot); + } while (pCur != pElmRoot); + + return NULL; +} + + +/** + * Private implementation. + * + * @param pElmRoot The root element. + */ +/*static*/ void ElementNode::buildChildren(ElementNode *pElmRoot) // protected +{ + for (ElementNode *pCur = pElmRoot; pCur; pCur = pCur->getNextTreeElement(pElmRoot)) + { + /* + * Go thru this element's attributes creating AttributeNodes for them. + */ + for (xmlAttr *pLibAttr = pCur->m_pLibNode->properties; pLibAttr; pLibAttr = pLibAttr->next) + { + AttributeNode *pNew = new AttributeNode(pElmRoot, pCur, &pCur->m_attributes, pLibAttr); + RTListAppend(&pCur->m_attributes, &pNew->m_listEntry); + } + + /* + * Go thru this element's child elements (element and text nodes). + */ + for (xmlNodePtr pLibNode = pCur->m_pLibNode->children; pLibNode; pLibNode = pLibNode->next) + { + Node *pNew; + if (pLibNode->type == XML_ELEMENT_NODE) + pNew = new ElementNode(pElmRoot, pCur, &pCur->m_children, pLibNode); + else if (pLibNode->type == XML_TEXT_NODE) + pNew = new ContentNode(pCur, &pCur->m_children, pLibNode); + else + continue; + RTListAppend(&pCur->m_children, &pNew->m_listEntry); + } + } +} + + +/** + * Builds a list of direct child elements of the current element that + * match the given string; if pcszMatch is NULL, all direct child + * elements are returned. + * @param children out: list of nodes to which children will be appended. + * @param pcszMatch in: match string, or NULL to return all children. + * @return Number of items appended to the list (0 if none). + */ +int ElementNode::getChildElements(ElementNodesList &children, + const char *pcszMatch /*= NULL*/) + const +{ + int i = 0; + Node *p; + RTListForEachCpp(&m_children, p, Node, m_listEntry) + { + // export this child node if ... + if (p->isElement()) + if ( !pcszMatch // ... the caller wants all nodes or ... + || !strcmp(pcszMatch, p->getName()) // ... the element name matches. + ) + { + children.push_back(static_cast<ElementNode *>(p)); + ++i; + } + } + return i; +} + +/** + * Returns the first child element whose name matches pcszMatch. + * + * @param pcszNamespace Namespace prefix (e.g. "vbox") or NULL to match any namespace. + * @param pcszMatch Element name to match. + * @return + */ +const ElementNode *ElementNode::findChildElementNS(const char *pcszNamespace, const char *pcszMatch) const +{ + Node *p; + RTListForEachCpp(&m_children, p, Node, m_listEntry) + { + if (p->isElement()) + { + ElementNode *pelm = static_cast<ElementNode*>(p); + if (pelm->nameEqualsNS(pcszNamespace, pcszMatch)) + return pelm; + } + } + return NULL; +} + +/** + * Returns the first child element whose "id" attribute matches pcszId. + * @param pcszId identifier to look for. + * @return child element or NULL if not found. + */ +const ElementNode *ElementNode::findChildElementFromId(const char *pcszId) const +{ + const Node *p; + RTListForEachCpp(&m_children, p, Node, m_listEntry) + { + if (p->isElement()) + { + const ElementNode *pElm = static_cast<const ElementNode *>(p); + const AttributeNode *pAttr = pElm->findAttribute("id"); + if (pAttr && !strcmp(pAttr->getValue(), pcszId)) + return pElm; + } + } + return NULL; +} + + +const ElementNode *ElementNode::findChildElementP(const char *pcszPath, const char *pcszNamespace /*= NULL*/) const +{ + size_t cchThis = strchr(pcszPath, '/') - pcszPath; + if (cchThis == (size_t)((const char *)0 - pcszPath)) + return findChildElementNS(pcszNamespace, pcszPath); + + /** @todo Can be done without recursion as we have both sibling lists and parent + * pointers in this variant. */ + const Node *p; + RTListForEachCpp(&m_children, p, Node, m_listEntry) + { + if (p->isElement()) + { + const ElementNode *pElm = static_cast<const ElementNode *>(p); + if (pElm->nameEqualsN(pcszPath, cchThis, pcszNamespace)) + { + pElm = findChildElementP(pcszPath + cchThis, pcszNamespace); + if (pElm) + return pElm; + } + } + } + + return NULL; +} + +const ElementNode *ElementNode::getFirstChildElement() const +{ + const Node *p; + RTListForEachCpp(&m_children, p, Node, m_listEntry) + { + if (p->isElement()) + return static_cast<const ElementNode *>(p); + } + return NULL; +} + +const ElementNode *ElementNode::getLastChildElement() const +{ + const Node *p; + RTListForEachReverseCpp(&m_children, p, Node, m_listEntry) + { + if (p->isElement()) + return static_cast<const ElementNode *>(p); + } + return NULL; +} + +const ElementNode *ElementNode::getPrevSibilingElement() const +{ + if (!m_pParent) + return NULL; + const Node *pSibling = this; + for (;;) + { + pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry); + if (!pSibling) + return NULL; + if (pSibling->isElement()) + return static_cast<const ElementNode *>(pSibling); + } +} + +const ElementNode *ElementNode::getNextSibilingElement() const +{ + if (!m_pParent) + return NULL; + const Node *pSibling = this; + for (;;) + { + pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry); + if (!pSibling) + return NULL; + if (pSibling->isElement()) + return static_cast<const ElementNode *>(pSibling); + } +} + +const ElementNode *ElementNode::findPrevSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const +{ + if (!m_pParent) + return NULL; + const Node *pSibling = this; + for (;;) + { + pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry); + if (!pSibling) + return NULL; + if (pSibling->isElement()) + { + const ElementNode *pElem = static_cast<const ElementNode *>(pSibling); + if (pElem->nameEqualsNS(pcszNamespace, pcszMatch)) + return pElem; + } + } +} + +const ElementNode *ElementNode::findNextSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const +{ + if (!m_pParent) + return NULL; + const Node *pSibling = this; + for (;;) + { + pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry); + if (!pSibling) + return NULL; + if (pSibling->isElement()) + { + const ElementNode *pElem = static_cast<const ElementNode *>(pSibling); + if (pElem->nameEqualsNS(pcszNamespace, pcszMatch)) + return pElem; + } + } +} + + +/** + * Looks up the given attribute node in this element's attribute map. + * + * @param pcszMatch The name of the attribute to find. + * @param pcszNamespace The attribute name space prefix or NULL. + */ +const AttributeNode *ElementNode::findAttribute(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const +{ + AttributeNode *p; + RTListForEachCpp(&m_attributes, p, AttributeNode, m_listEntry) + { + if (p->nameEqualsNS(pcszNamespace, pcszMatch)) + return p; + } + return NULL; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as a string. + * + * @param pcszMatch Name of attribute to find. + * @param ppcsz Where to return the attribute. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValue(const char *pcszMatch, const char **ppcsz, const char *pcszNamespace /*= NULL*/) const +{ + const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace); + if (pAttr) + { + *ppcsz = pAttr->getValue(); + return true; + } + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as a string. + * + * @param pcszMatch Name of attribute to find. + * @param pStr Pointer to the string object that should receive the + * attribute value. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + * + * @throws Whatever the string class may throw on assignment. + */ +bool ElementNode::getAttributeValue(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const +{ + const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace); + if (pAttr) + { + *pStr = pAttr->getValue(); + return true; + } + + return false; +} + +/** + * Like getAttributeValue (ministring variant), but makes sure that all backslashes + * are converted to forward slashes. + * + * @param pcszMatch Name of attribute to find. + * @param pStr Pointer to the string object that should + * receive the attribute path value. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValuePath(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const +{ + if (getAttributeValue(pcszMatch, pStr, pcszNamespace)) + { + pStr->findReplace('\\', '/'); + return true; + } + + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as a signed 32-bit integer. + * + * @param pcszMatch Name of attribute to find. + * @param piValue Where to return the value. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValue(const char *pcszMatch, int32_t *piValue, const char *pcszNamespace /*= NULL*/) const +{ + const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace); + if (pcsz) + { + int rc = RTStrToInt32Ex(pcsz, NULL, 0, piValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as an unsigned 32-bit integer. + * + * @param pcszMatch Name of attribute to find. + * @param puValue Where to return the value. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t *puValue, const char *pcszNamespace /*= NULL*/) const +{ + const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace); + if (pcsz) + { + int rc = RTStrToUInt32Ex(pcsz, NULL, 0, puValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as a signed 64-bit integer. + * + * @param pcszMatch Name of attribute to find. + * @param piValue Where to return the value. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t *piValue, const char *pcszNamespace /*= NULL*/) const +{ + const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace); + if (pcsz) + { + int rc = RTStrToInt64Ex(pcsz, NULL, 0, piValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as an unsigned 64-bit integer. + * + * @param pcszMatch Name of attribute to find. + * @param puValue Where to return the value. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t *puValue, const char *pcszNamespace /*= NULL*/) const +{ + const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace); + if (pcsz) + { + int rc = RTStrToUInt64Ex(pcsz, NULL, 0, puValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as a boolean. This accepts "true", "false", + * "yes", "no", "1" or "0" as valid values. + * + * @param pcszMatch Name of attribute to find. + * @param pfValue Where to return the value. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValue(const char *pcszMatch, bool *pfValue, const char *pcszNamespace /*= NULL*/) const +{ + const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace); + if (pcsz) + { + if ( !strcmp(pcsz, "true") + || !strcmp(pcsz, "yes") + || !strcmp(pcsz, "1") + ) + { + *pfValue = true; + return true; + } + if ( !strcmp(pcsz, "false") + || !strcmp(pcsz, "no") + || !strcmp(pcsz, "0") + ) + { + *pfValue = false; + return true; + } + } + + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as a string. + * + * @param pcszMatch Name of attribute to find. + * @param ppcsz Where to return the attribute. + * @param cchValueLimit If the length of the returned value exceeds this + * limit a EIPRTFailure exception will be thrown. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValueN(const char *pcszMatch, const char **ppcsz, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const +{ + const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace); + if (pAttr) + { + *ppcsz = pAttr->getValueN(cchValueLimit); + return true; + } + return false; +} + +/** + * Convenience method which attempts to find the attribute with the given + * name and returns its value as a string. + * + * @param pcszMatch Name of attribute to find. + * @param pStr Pointer to the string object that should receive the + * attribute value. + * @param cchValueLimit If the length of the returned value exceeds this + * limit a EIPRTFailure exception will be thrown. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + * + * @throws Whatever the string class may throw on assignment. + */ +bool ElementNode::getAttributeValueN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const +{ + const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace); + if (pAttr) + { + *pStr = pAttr->getValueN(cchValueLimit); + return true; + } + + return false; +} + +/** + * Like getAttributeValue (ministring variant), but makes sure that all backslashes + * are converted to forward slashes. + * + * @param pcszMatch Name of attribute to find. + * @param pStr Pointer to the string object that should + * receive the attribute path value. + * @param cchValueLimit If the length of the returned value exceeds this + * limit a EIPRTFailure exception will be thrown. + * @param pcszNamespace The attribute name space prefix or NULL. + * @returns Boolean success indicator. + */ +bool ElementNode::getAttributeValuePathN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const +{ + if (getAttributeValueN(pcszMatch, pStr, cchValueLimit, pcszNamespace)) + { + pStr->findReplace('\\', '/'); + return true; + } + + return false; +} + + +bool ElementNode::getElementValue(int32_t *piValue) const +{ + const char *pszValue = getValue(); + if (pszValue) + { + int rc = RTStrToInt32Ex(pszValue, NULL, 0, piValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +bool ElementNode::getElementValue(uint32_t *puValue) const +{ + const char *pszValue = getValue(); + if (pszValue) + { + int rc = RTStrToUInt32Ex(pszValue, NULL, 0, puValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +bool ElementNode::getElementValue(int64_t *piValue) const +{ + const char *pszValue = getValue(); + if (pszValue) + { + int rc = RTStrToInt64Ex(pszValue, NULL, 0, piValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +bool ElementNode::getElementValue(uint64_t *puValue) const +{ + const char *pszValue = getValue(); + if (pszValue) + { + int rc = RTStrToUInt64Ex(pszValue, NULL, 0, puValue); + if (rc == VINF_SUCCESS) + return true; + } + return false; +} + +bool ElementNode::getElementValue(bool *pfValue) const +{ + const char *pszValue = getValue(); + if (pszValue) + { + if ( !strcmp(pszValue, "true") + || !strcmp(pszValue, "yes") + || !strcmp(pszValue, "1") + ) + { + *pfValue = true; + return true; + } + if ( !strcmp(pszValue, "false") + || !strcmp(pszValue, "no") + || !strcmp(pszValue, "0") + ) + { + *pfValue = true; + return true; + } + } + return false; +} + + +/** + * Creates a new child element node and appends it to the list + * of children in "this". + * + * @param pcszElementName + * @return + */ +ElementNode *ElementNode::createChild(const char *pcszElementName) +{ + // we must be an element, not an attribute + if (!m_pLibNode) + throw ENodeIsNotElement(RT_SRC_POS); + + // libxml side: create new node + xmlNode *pLibNode; + if (!(pLibNode = xmlNewNode(NULL, // namespace + (const xmlChar*)pcszElementName))) + throw std::bad_alloc(); + xmlAddChild(m_pLibNode, pLibNode); + + // now wrap this in C++ + ElementNode *p = new ElementNode(m_pElmRoot, this, &m_children, pLibNode); + RTListAppend(&m_children, &p->m_listEntry); + + return p; +} + + +/** + * Creates a content node and appends it to the list of children + * in "this". + * + * @param pcszContent + * @return + */ +ContentNode *ElementNode::addContent(const char *pcszContent) +{ + // libxml side: create new node + xmlNode *pLibNode = xmlNewText((const xmlChar*)pcszContent); + if (!pLibNode) + throw std::bad_alloc(); + xmlAddChild(m_pLibNode, pLibNode); + + // now wrap this in C++ + ContentNode *p = new ContentNode(this, &m_children, pLibNode); + RTListAppend(&m_children, &p->m_listEntry); + + return p; +} + +/** + * Changes the contents of node and appends it to the list of + * children + * + * @param pcszContent + * @return + */ +ContentNode *ElementNode::setContent(const char *pcszContent) +{ +// 1. Update content + xmlNodeSetContent(m_pLibNode, (const xmlChar*)pcszContent); + +// 2. Remove Content node from the list + /* Check that the order is right. */ + xml::Node * pNode; + RTListForEachCpp(&m_children, pNode, xml::Node, m_listEntry) + { + bool fLast = RTListNodeIsLast(&m_children, &pNode->m_listEntry); + + if (pNode->isContent()) + { + RTListNodeRemove(&pNode->m_listEntry); + } + + if (fLast) + break; + } + +// 3. Create a new node and append to the list + // now wrap this in C++ + ContentNode *pCNode = new ContentNode(this, &m_children, m_pLibNode); + RTListAppend(&m_children, &pCNode->m_listEntry); + + return pCNode; +} + +/** + * Sets the given attribute; overloaded version for const char *. + * + * If an attribute with the given name exists, it is overwritten, + * otherwise a new attribute is created. Returns the attribute node + * that was either created or changed. + * + * @param pcszName The attribute name. + * @param pcszValue The attribute value. + * @return Pointer to the attribute node that was created or modified. + */ +AttributeNode *ElementNode::setAttribute(const char *pcszName, const char *pcszValue) +{ + /* + * Do we already have an attribute and should we just update it? + */ + AttributeNode *pAttr; + RTListForEachCpp(&m_attributes, pAttr, AttributeNode, m_listEntry) + { + if (pAttr->nameEquals(pcszName)) + { + /* Overwrite existing libxml attribute node ... */ + xmlAttrPtr pLibAttr = xmlSetProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue); + + /* ... and update our C++ wrapper in case the attrib pointer changed. */ + pAttr->m_pLibAttr = pLibAttr; + return pAttr; + } + } + + /* + * No existing attribute, create a new one. + */ + /* libxml side: xmlNewProp creates an attribute. */ + xmlAttr *pLibAttr = xmlNewProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue); + + /* C++ side: Create an attribute node around it. */ + pAttr = new AttributeNode(m_pElmRoot, this, &m_attributes, pLibAttr); + RTListAppend(&m_attributes, &pAttr->m_listEntry); + + return pAttr; +} + +/** + * Like setAttribute (ministring variant), but replaces all backslashes with forward slashes + * before calling that one. + * @param pcszName + * @param strValue + * @return + */ +AttributeNode* ElementNode::setAttributePath(const char *pcszName, const RTCString &strValue) +{ + RTCString strTemp(strValue); + strTemp.findReplace('\\', '/'); + return setAttribute(pcszName, strTemp.c_str()); +} + +/** + * Sets the given attribute; overloaded version for int32_t. + * + * If an attribute with the given name exists, it is overwritten, + * otherwise a new attribute is created. Returns the attribute node + * that was either created or changed. + * + * @param pcszName + * @param i + * @return + */ +AttributeNode* ElementNode::setAttribute(const char *pcszName, int32_t i) +{ + char szValue[12]; // negative sign + 10 digits + \0 + RTStrPrintf(szValue, sizeof(szValue), "%RI32", i); + AttributeNode *p = setAttribute(pcszName, szValue); + return p; +} + +/** + * Sets the given attribute; overloaded version for uint32_t. + * + * If an attribute with the given name exists, it is overwritten, + * otherwise a new attribute is created. Returns the attribute node + * that was either created or changed. + * + * @param pcszName + * @param u + * @return + */ +AttributeNode* ElementNode::setAttribute(const char *pcszName, uint32_t u) +{ + char szValue[11]; // 10 digits + \0 + RTStrPrintf(szValue, sizeof(szValue), "%RU32", u); + AttributeNode *p = setAttribute(pcszName, szValue); + return p; +} + +/** + * Sets the given attribute; overloaded version for int64_t. + * + * If an attribute with the given name exists, it is overwritten, + * otherwise a new attribute is created. Returns the attribute node + * that was either created or changed. + * + * @param pcszName + * @param i + * @return + */ +AttributeNode* ElementNode::setAttribute(const char *pcszName, int64_t i) +{ + char szValue[21]; // negative sign + 19 digits + \0 + RTStrPrintf(szValue, sizeof(szValue), "%RI64", i); + AttributeNode *p = setAttribute(pcszName, szValue); + return p; +} + +/** + * Sets the given attribute; overloaded version for uint64_t. + * + * If an attribute with the given name exists, it is overwritten, + * otherwise a new attribute is created. Returns the attribute node + * that was either created or changed. + * + * @param pcszName + * @param u + * @return + */ +AttributeNode* ElementNode::setAttribute(const char *pcszName, uint64_t u) +{ + char szValue[21]; // 20 digits + \0 + RTStrPrintf(szValue, sizeof(szValue), "%RU64", u); + AttributeNode *p = setAttribute(pcszName, szValue); + return p; +} + +/** + * Sets the given attribute to the given uint32_t, outputs a hexadecimal string. + * + * If an attribute with the given name exists, it is overwritten, + * otherwise a new attribute is created. Returns the attribute node + * that was either created or changed. + * + * @param pcszName + * @param u + * @return + */ +AttributeNode* ElementNode::setAttributeHex(const char *pcszName, uint32_t u) +{ + char szValue[11]; // "0x" + 8 digits + \0 + RTStrPrintf(szValue, sizeof(szValue), "0x%RX32", u); + AttributeNode *p = setAttribute(pcszName, szValue); + return p; +} + +/** + * Sets the given attribute; overloaded version for bool. + * + * If an attribute with the given name exists, it is overwritten, + * otherwise a new attribute is created. Returns the attribute node + * that was either created or changed. + * + * @param pcszName The attribute name. + * @param f The attribute value. + * @return + */ +AttributeNode* ElementNode::setAttribute(const char *pcszName, bool f) +{ + return setAttribute(pcszName, (f) ? "true" : "false"); +} + +/** + * Private constructor for a new attribute node. + * + * @param pElmRoot Pointer to the root element. Needed for getting the + * default name space. + * @param pParent Pointer to the parent element (always an ElementNode, + * despite the type). NULL for the root node. + * @param pListAnchor Pointer to the m_children member of the parent. NULL + * for the root node. + * @param pLibAttr Pointer to the libxml2 attribute structure. + */ +AttributeNode::AttributeNode(const ElementNode *pElmRoot, + Node *pParent, + PRTLISTANCHOR pListAnchor, + xmlAttr *pLibAttr) + : Node(IsAttribute, + pParent, + pListAnchor, + NULL, + pLibAttr) +{ + m_pcszName = (const char *)pLibAttr->name; + RT_NOREF_PV(pElmRoot); + + if ( pLibAttr->ns + && pLibAttr->ns->prefix) + { + m_pcszNamespacePrefix = (const char *)pLibAttr->ns->prefix; + m_pcszNamespaceHref = (const char *)pLibAttr->ns->href; + } +} + +ContentNode::ContentNode(Node *pParent, PRTLISTANCHOR pListAnchor, xmlNode *pLibNode) + : Node(IsContent, + pParent, + pListAnchor, + pLibNode, + NULL) +{ +} + +/* + * NodesLoop + * + */ + +struct NodesLoop::Data +{ + ElementNodesList listElements; + ElementNodesList::const_iterator it; +}; + +NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */) +{ + m = new Data; + node.getChildElements(m->listElements, pcszMatch); + m->it = m->listElements.begin(); +} + +NodesLoop::~NodesLoop() +{ + delete m; +} + + +/** + * Handy convenience helper for looping over all child elements. Create an + * instance of NodesLoop on the stack and call this method until it returns + * NULL, like this: + * <code> + * xml::ElementNode node; // should point to an element + * xml::NodesLoop loop(node, "child"); // find all "child" elements under node + * const xml::ElementNode *pChild = NULL; + * while (pChild = loop.forAllNodes()) + * ...; + * </code> + * @return + */ +const ElementNode* NodesLoop::forAllNodes() const +{ + const ElementNode *pNode = NULL; + + if (m->it != m->listElements.end()) + { + pNode = *(m->it); + ++(m->it); + } + + return pNode; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Document class +// +//////////////////////////////////////////////////////////////////////////////// + +struct Document::Data +{ + xmlDocPtr plibDocument; + ElementNode *pRootElement; + ElementNode *pComment; + + Data() + { + plibDocument = NULL; + pRootElement = NULL; + pComment = NULL; + } + + ~Data() + { + reset(); + } + + void reset() + { + if (plibDocument) + { + xmlFreeDoc(plibDocument); + plibDocument = NULL; + } + if (pRootElement) + { + delete pRootElement; + pRootElement = NULL; + } + if (pComment) + { + delete pComment; + pComment = NULL; + } + } + + void copyFrom(const Document::Data *p) + { + if (p->plibDocument) + { + plibDocument = xmlCopyDoc(p->plibDocument, + 1); // recursive == copy all + } + } +}; + +Document::Document() + : m(new Data) +{ +} + +Document::Document(const Document &x) + : m(new Data) +{ + m->copyFrom(x.m); +} + +Document& Document::operator=(const Document &x) +{ + m->reset(); + m->copyFrom(x.m); + return *this; +} + +Document::~Document() +{ + delete m; +} + +/** + * private method to refresh all internal structures after the internal pDocument + * has changed. Called from XmlFileParser::read(). m->reset() must have been + * called before to make sure all members except the internal pDocument are clean. + */ +void Document::refreshInternals() // private +{ + m->pRootElement = new ElementNode(NULL, NULL, NULL, xmlDocGetRootElement(m->plibDocument)); + + ElementNode::buildChildren(m->pRootElement); +} + +/** + * Returns the root element of the document, or NULL if the document is empty. + * Const variant. + * @return + */ +const ElementNode *Document::getRootElement() const +{ + return m->pRootElement; +} + +/** + * Returns the root element of the document, or NULL if the document is empty. + * Non-const variant. + * @return + */ +ElementNode *Document::getRootElement() +{ + return m->pRootElement; +} + +/** + * Creates a new element node and sets it as the root element. + * + * This will only work if the document is empty; otherwise EDocumentNotEmpty is + * thrown. + */ +ElementNode *Document::createRootElement(const char *pcszRootElementName, + const char *pcszComment /* = NULL */) +{ + if (m->pRootElement || m->plibDocument) + throw EDocumentNotEmpty(RT_SRC_POS); + + // libxml side: create document, create root node + m->plibDocument = xmlNewDoc((const xmlChar *)"1.0"); + xmlNode *plibRootNode = xmlNewNode(NULL /*namespace*/ , (const xmlChar *)pcszRootElementName); + if (!plibRootNode) + throw std::bad_alloc(); + xmlDocSetRootElement(m->plibDocument, plibRootNode); + + // now wrap this in C++ + m->pRootElement = new ElementNode(NULL, NULL, NULL, plibRootNode); + + // add document global comment if specified + if (pcszComment != NULL) + { + xmlNode *pComment = xmlNewDocComment(m->plibDocument, (const xmlChar *)pcszComment); + if (!pComment) + throw std::bad_alloc(); + xmlAddPrevSibling(plibRootNode, pComment); + + // now wrap this in C++ + m->pComment = new ElementNode(NULL, NULL, NULL, pComment); + } + + return m->pRootElement; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// XmlParserBase class +// +//////////////////////////////////////////////////////////////////////////////// + +static void xmlParserBaseGenericError(void *pCtx, const char *pszMsg, ...) RT_NOTHROW_DEF +{ + NOREF(pCtx); + va_list args; + va_start(args, pszMsg); + RTLogRelPrintfV(pszMsg, args); + va_end(args); +} + +static void xmlParserBaseStructuredError(void *pCtx, xmlErrorPtr error) RT_NOTHROW_DEF +{ + NOREF(pCtx); + /* we expect that there is always a trailing NL */ + LogRel(("XML error at '%s' line %d: %s", error->file, error->line, error->message)); +} + +XmlParserBase::XmlParserBase() +{ + m_ctxt = xmlNewParserCtxt(); + if (m_ctxt == NULL) + throw std::bad_alloc(); + /* per-thread so it must be here */ + xmlSetGenericErrorFunc(NULL, xmlParserBaseGenericError); + xmlSetStructuredErrorFunc(NULL, xmlParserBaseStructuredError); +} + +XmlParserBase::~XmlParserBase() +{ + xmlSetStructuredErrorFunc(NULL, NULL); + xmlSetGenericErrorFunc(NULL, NULL); + xmlFreeParserCtxt (m_ctxt); + m_ctxt = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// XmlMemParser class +// +//////////////////////////////////////////////////////////////////////////////// + +XmlMemParser::XmlMemParser() + : XmlParserBase() +{ +} + +XmlMemParser::~XmlMemParser() +{ +} + +/** + * Parse the given buffer and fills the given Document object with its contents. + * Throws XmlError on parsing errors. + * + * The document that is passed in will be reset before being filled if not empty. + * + * @param pvBuf Memory buffer to parse. + * @param cbSize Size of the memory buffer. + * @param strFilename Refernece to the name of the file we're parsing. + * @param doc Reference to the output document. This will be reset + * and filled with data according to file contents. + */ +void XmlMemParser::read(const void *pvBuf, size_t cbSize, + const RTCString &strFilename, + Document &doc) +{ + GlobalLock lock; +// global.setExternalEntityLoader(ExternalEntityLoader); + + const char *pcszFilename = strFilename.c_str(); + + doc.m->reset(); + const int options = XML_PARSE_NOBLANKS /* remove blank nodes */ + | XML_PARSE_NONET /* forbit any network access */ +#if LIBXML_VERSION >= 20700 + | XML_PARSE_HUGE /* don't restrict the node depth + to 256 (bad for snapshots!) */ +#endif + ; + if (!(doc.m->plibDocument = xmlCtxtReadMemory(m_ctxt, + (const char*)pvBuf, + (int)cbSize, + pcszFilename, + NULL, // encoding = auto + options))) + throw XmlError(xmlCtxtGetLastError(m_ctxt)); + + doc.refreshInternals(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// XmlMemWriter class +// +//////////////////////////////////////////////////////////////////////////////// + +XmlMemWriter::XmlMemWriter() + : m_pBuf(0) +{ +} + +XmlMemWriter::~XmlMemWriter() +{ + if (m_pBuf) + xmlFree(m_pBuf); +} + +void XmlMemWriter::write(const Document &doc, void **ppvBuf, size_t *pcbSize) +{ + if (m_pBuf) + { + xmlFree(m_pBuf); + m_pBuf = 0; + } + int size; + xmlDocDumpFormatMemory(doc.m->plibDocument, (xmlChar**)&m_pBuf, &size, 1); + *ppvBuf = m_pBuf; + *pcbSize = size; +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// XmlStringWriter class +// +//////////////////////////////////////////////////////////////////////////////// + +XmlStringWriter::XmlStringWriter() + : m_pStrDst(NULL), m_fOutOfMemory(false) +{ +} + +int XmlStringWriter::write(const Document &rDoc, RTCString *pStrDst) +{ + /* + * Clear the output string and take the global libxml2 lock so we can + * safely configure the output formatting. + */ + pStrDst->setNull(); + + GlobalLock lock; + + xmlIndentTreeOutput = 1; + xmlTreeIndentString = " "; + xmlSaveNoEmptyTags = 0; + + /* + * Do a pass to calculate the size. + */ + size_t cbOutput = 1; /* zero term */ + + xmlSaveCtxtPtr pSaveCtx= xmlSaveToIO(WriteCallbackForSize, CloseCallback, &cbOutput, NULL /*pszEncoding*/, XML_SAVE_FORMAT); + if (!pSaveCtx) + return VERR_NO_MEMORY; + + long rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument); + xmlSaveClose(pSaveCtx); + if (rcXml == -1) + return VERR_GENERAL_FAILURE; + + /* + * Try resize the string. + */ + int rc = pStrDst->reserveNoThrow(cbOutput); + if (RT_SUCCESS(rc)) + { + /* + * Do the real run where we feed output to the string. + */ + m_pStrDst = pStrDst; + m_fOutOfMemory = false; + pSaveCtx = xmlSaveToIO(WriteCallbackForReal, CloseCallback, this, NULL /*pszEncoding*/, XML_SAVE_FORMAT); + if (pSaveCtx) + { + rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument); + xmlSaveClose(pSaveCtx); + m_pStrDst = NULL; + if (rcXml != -1) + { + if (!m_fOutOfMemory) + return VINF_SUCCESS; + + rc = VERR_NO_STR_MEMORY; + } + else + rc = VERR_GENERAL_FAILURE; + } + else + rc = VERR_NO_MEMORY; + pStrDst->setNull(); + m_pStrDst = NULL; + } + return rc; +} + +/*static*/ int XmlStringWriter::WriteCallbackForSize(void *pvUser, const char *pachBuf, int cbToWrite) RT_NOTHROW_DEF +{ + if (cbToWrite > 0) + *(size_t *)pvUser += (unsigned)cbToWrite; + RT_NOREF(pachBuf); + return cbToWrite; +} + +/*static*/ int XmlStringWriter::WriteCallbackForReal(void *pvUser, const char *pachBuf, int cbToWrite) RT_NOTHROW_DEF +{ + XmlStringWriter *pThis = static_cast<XmlStringWriter*>(pvUser); + if (!pThis->m_fOutOfMemory) + { + if (cbToWrite > 0) + { + try + { + pThis->m_pStrDst->append(pachBuf, (size_t)cbToWrite); + } + catch (std::bad_alloc &) + { + pThis->m_fOutOfMemory = true; + return -1; + } + } + return cbToWrite; + } + return -1; /* failure */ +} + +/*static*/ int XmlStringWriter::CloseCallback(void *pvUser) RT_NOTHROW_DEF +{ + /* Nothing to do here. */ + RT_NOREF(pvUser); + return 0; +} + + + +//////////////////////////////////////////////////////////////////////////////// +// +// XmlFileParser class +// +//////////////////////////////////////////////////////////////////////////////// + +struct XmlFileParser::Data +{ + RTCString strXmlFilename; + + Data() + { + } + + ~Data() + { + } +}; + +XmlFileParser::XmlFileParser() + : XmlParserBase(), + m(new Data()) +{ +} + +XmlFileParser::~XmlFileParser() +{ + delete m; + m = NULL; +} + +struct IOContext +{ + File file; + RTCString error; + + IOContext(const char *pcszFilename, File::Mode mode, bool fFlush = false) + : file(mode, pcszFilename, fFlush) + { + } + + void setError(const RTCError &x) + { + error = x.what(); + } + + void setError(const std::exception &x) + { + error = x.what(); + } + +private: + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(IOContext); /* (shuts up C4626 and C4625 MSC warnings) */ +}; + +struct ReadContext : IOContext +{ + ReadContext(const char *pcszFilename) + : IOContext(pcszFilename, File::Mode_Read) + { + } + +private: + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(ReadContext); /* (shuts up C4626 and C4625 MSC warnings) */ +}; + +struct WriteContext : IOContext +{ + WriteContext(const char *pcszFilename, bool fFlush) + : IOContext(pcszFilename, File::Mode_Overwrite, fFlush) + { + } + +private: + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(WriteContext); /* (shuts up C4626 and C4625 MSC warnings) */ +}; + +/** + * Reads the given file and fills the given Document object with its contents. + * Throws XmlError on parsing errors. + * + * The document that is passed in will be reset before being filled if not empty. + * + * @param strFilename in: name fo file to parse. + * @param doc out: document to be reset and filled with data according to file contents. + */ +void XmlFileParser::read(const RTCString &strFilename, + Document &doc) +{ + GlobalLock lock; +// global.setExternalEntityLoader(ExternalEntityLoader); + + m->strXmlFilename = strFilename; + const char *pcszFilename = strFilename.c_str(); + + ReadContext context(pcszFilename); + doc.m->reset(); + const int options = XML_PARSE_NOBLANKS /* remove blank nodes */ + | XML_PARSE_NONET /* forbit any network access */ +#if LIBXML_VERSION >= 20700 + | XML_PARSE_HUGE /* don't restrict the node depth + to 256 (bad for snapshots!) */ +#endif + ; + if (!(doc.m->plibDocument = xmlCtxtReadIO(m_ctxt, + ReadCallback, + CloseCallback, + &context, + pcszFilename, + NULL, // encoding = auto + options))) + throw XmlError(xmlCtxtGetLastError(m_ctxt)); + + doc.refreshInternals(); +} + +/*static*/ int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen) RT_NOTHROW_DEF +{ + ReadContext *pContext = static_cast<ReadContext*>(aCtxt); + + /* To prevent throwing exceptions while inside libxml2 code, we catch + * them and forward to our level using a couple of variables. */ + + try + { + return pContext->file.read(aBuf, aLen); + } + catch (const xml::EIPRTFailure &err) { pContext->setError(err); } + catch (const RTCError &err) { pContext->setError(err); } + catch (const std::exception &err) { pContext->setError(err); } + catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); } + + return -1 /* failure */; +} + +/*static*/ int XmlFileParser::CloseCallback(void *aCtxt) RT_NOTHROW_DEF +{ + /// @todo to be written + NOREF(aCtxt); + + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// XmlFileWriter class +// +//////////////////////////////////////////////////////////////////////////////// + +struct XmlFileWriter::Data +{ + Document *pDoc; +}; + +XmlFileWriter::XmlFileWriter(Document &doc) +{ + m = new Data(); + m->pDoc = &doc; +} + +XmlFileWriter::~XmlFileWriter() +{ + delete m; +} + +void XmlFileWriter::writeInternal(const char *pcszFilename, bool fSafe) +{ + WriteContext context(pcszFilename, fSafe); + + GlobalLock lock; + + /* serialize to the stream */ + xmlIndentTreeOutput = 1; + xmlTreeIndentString = " "; + xmlSaveNoEmptyTags = 0; + + xmlSaveCtxtPtr saveCtxt; + if (!(saveCtxt = xmlSaveToIO(WriteCallback, + CloseCallback, + &context, + NULL, + XML_SAVE_FORMAT))) + throw xml::LogicError(RT_SRC_POS); + + long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument); + if (rc == -1) + { + /* look if there was a forwarded exception from the lower level */ +// if (m->trappedErr.get() != NULL) +// m->trappedErr->rethrow(); + + /* there must be an exception from the Output implementation, + * otherwise the save operation must always succeed. */ + throw xml::LogicError(RT_SRC_POS); + } + + xmlSaveClose(saveCtxt); +} + +void XmlFileWriter::write(const char *pcszFilename, bool fSafe) +{ + if (!fSafe) + writeInternal(pcszFilename, fSafe); + else + { + /* Empty string and directory spec must be avoid. */ + if (RTPathFilename(pcszFilename) == NULL) + throw xml::LogicError(RT_SRC_POS); + + /* Construct both filenames first to ease error handling. */ + char szTmpFilename[RTPATH_MAX]; + int rc = RTStrCopy(szTmpFilename, sizeof(szTmpFilename) - strlen(s_pszTmpSuff), pcszFilename); + if (RT_FAILURE(rc)) + throw EIPRTFailure(rc, "RTStrCopy"); + strcat(szTmpFilename, s_pszTmpSuff); + + char szPrevFilename[RTPATH_MAX]; + rc = RTStrCopy(szPrevFilename, sizeof(szPrevFilename) - strlen(s_pszPrevSuff), pcszFilename); + if (RT_FAILURE(rc)) + throw EIPRTFailure(rc, "RTStrCopy"); + strcat(szPrevFilename, s_pszPrevSuff); + + /* Write the XML document to the temporary file. */ + writeInternal(szTmpFilename, fSafe); + + /* Make a backup of any existing file (ignore failure). */ + uint64_t cbPrevFile; + rc = RTFileQuerySizeByPath(pcszFilename, &cbPrevFile); + if (RT_SUCCESS(rc) && cbPrevFile >= 16) + RTFileRename(pcszFilename, szPrevFilename, RTPATHRENAME_FLAGS_REPLACE); + + /* Commit the temporary file. Just leave the tmp file behind on failure. */ + rc = RTFileRename(szTmpFilename, pcszFilename, RTPATHRENAME_FLAGS_REPLACE); + if (RT_FAILURE(rc)) + throw EIPRTFailure(rc, "Failed to replace '%s' with '%s'", pcszFilename, szTmpFilename); + + /* Flush the directory changes (required on linux at least). */ + RTPathStripFilename(szTmpFilename); + rc = RTDirFlush(szTmpFilename); + AssertMsg(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, ("%Rrc\n", rc)); + } +} + +/*static*/ int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen) RT_NOTHROW_DEF +{ + WriteContext *pContext = static_cast<WriteContext*>(aCtxt); + + /* To prevent throwing exceptions while inside libxml2 code, we catch + * them and forward to our level using a couple of variables. */ + try + { + return pContext->file.write(aBuf, aLen); + } + catch (const xml::EIPRTFailure &err) { pContext->setError(err); } + catch (const RTCError &err) { pContext->setError(err); } + catch (const std::exception &err) { pContext->setError(err); } + catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); } + + return -1 /* failure */; +} + +/*static*/ int XmlFileWriter::CloseCallback(void *aCtxt) RT_NOTHROW_DEF +{ + /// @todo to be written + NOREF(aCtxt); + + return -1; +} + +/*static*/ const char * const XmlFileWriter::s_pszTmpSuff = "-tmp"; +/*static*/ const char * const XmlFileWriter::s_pszPrevSuff = "-prev"; + + +} // end namespace xml + |