summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/misc
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/misc')
-rw-r--r--src/VBox/Runtime/common/misc/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp52
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp50
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp50
-rw-r--r--src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp364
-rw-r--r--src/VBox/Runtime/common/misc/RTFileOpenF.cpp54
-rw-r--r--src/VBox/Runtime/common/misc/RTFileOpenV.cpp58
-rw-r--r--src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp65
-rw-r--r--src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp60
-rw-r--r--src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp58
-rw-r--r--src/VBox/Runtime/common/misc/assert.cpp348
-rw-r--r--src/VBox/Runtime/common/misc/buildconfig.cpp152
-rw-r--r--src/VBox/Runtime/common/misc/cidr.cpp129
-rw-r--r--src/VBox/Runtime/common/misc/circbuf.cpp262
-rw-r--r--src/VBox/Runtime/common/misc/expreval.cpp2740
-rw-r--r--src/VBox/Runtime/common/misc/getopt.cpp922
-rw-r--r--src/VBox/Runtime/common/misc/getoptargv.cpp654
-rw-r--r--src/VBox/Runtime/common/misc/handle.cpp85
-rw-r--r--src/VBox/Runtime/common/misc/handletable.cpp234
-rw-r--r--src/VBox/Runtime/common/misc/handletable.h257
-rw-r--r--src/VBox/Runtime/common/misc/handletablectx.cpp339
-rw-r--r--src/VBox/Runtime/common/misc/handletablesimple.cpp314
-rw-r--r--src/VBox/Runtime/common/misc/inifile.cpp733
-rw-r--r--src/VBox/Runtime/common/misc/json.cpp1914
-rw-r--r--src/VBox/Runtime/common/misc/lockvalidator.cpp4482
-rw-r--r--src/VBox/Runtime/common/misc/message.cpp266
-rw-r--r--src/VBox/Runtime/common/misc/messagerefentry.cpp332
-rw-r--r--src/VBox/Runtime/common/misc/once.cpp450
-rw-r--r--src/VBox/Runtime/common/misc/req.cpp537
-rw-r--r--src/VBox/Runtime/common/misc/reqpool.cpp1299
-rw-r--r--src/VBox/Runtime/common/misc/reqqueue.cpp465
-rw-r--r--src/VBox/Runtime/common/misc/sanity-c.c37
-rw-r--r--src/VBox/Runtime/common/misc/sanity-cpp.cpp38
-rw-r--r--src/VBox/Runtime/common/misc/sanity.h225
-rw-r--r--src/VBox/Runtime/common/misc/semspingpong.cpp218
-rw-r--r--src/VBox/Runtime/common/misc/setjmp.asm148
-rw-r--r--src/VBox/Runtime/common/misc/sg.cpp507
-rw-r--r--src/VBox/Runtime/common/misc/term.cpp252
-rw-r--r--src/VBox/Runtime/common/misc/thread.cpp1446
-rw-r--r--src/VBox/Runtime/common/misc/uri.cpp1181
-rw-r--r--src/VBox/Runtime/common/misc/zero-alt.S118
-rw-r--r--src/VBox/Runtime/common/misc/zero.asm83
-rw-r--r--src/VBox/Runtime/common/misc/zero.cpp52
47 files changed, 22250 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/misc/Makefile.kup b/src/VBox/Runtime/common/misc/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/Makefile.kup
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp
new file mode 100644
index 00000000..ab6a7283
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp
@@ -0,0 +1,52 @@
+/* $Id: RTAssertMsg1Weak.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg1Weak.
+ */
+
+/*
+ * 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/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ RTAssertMsg1(pszExpr, uLine, pszFile, pszFunction);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg1Weak);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp
new file mode 100644
index 00000000..0369d8ce
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2.
+ */
+
+/*
+ * 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/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2V(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp
new file mode 100644
index 00000000..b9a8be4b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2Add.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2Add.
+ */
+
+/*
+ * 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/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2Add(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2AddV(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2Add);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp
new file mode 100644
index 00000000..335d408d
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2AddWeak.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2AddWeak.
+ */
+
+/*
+ * 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/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2AddWeak(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2AddWeakV(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2AddWeak);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp
new file mode 100644
index 00000000..aafd9e68
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp
@@ -0,0 +1,50 @@
+/* $Id: RTAssertMsg2AddWeakV.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2AddWeakV.
+ */
+
+/*
+ * 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/assert.h>
+#include "internal/iprt.h"
+
+
+RTDECL(void) RTAssertMsg2AddWeakV(const char *pszFormat, va_list va)
+{
+ RTAssertMsg2AddV(pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2AddWeakV);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp
new file mode 100644
index 00000000..7a566e56
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2Weak.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2Weak.
+ */
+
+/*
+ * 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/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2Weak(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2WeakV(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2Weak);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp
new file mode 100644
index 00000000..ccd56b73
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp
@@ -0,0 +1,50 @@
+/* $Id: RTAssertMsg2WeakV.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2WeakV.
+ */
+
+/*
+ * 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/assert.h>
+#include "internal/iprt.h"
+
+
+RTDECL(void) RTAssertMsg2WeakV(const char *pszFormat, va_list va)
+{
+ RTAssertMsg2V(pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2WeakV);
+
diff --git a/src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp b/src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp
new file mode 100644
index 00000000..1b6dcd3c
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp
@@ -0,0 +1,364 @@
+/* $Id: RTFileModeToFlags.cpp $ */
+/** @file
+ * IPRT - RTFileModeToFlags.
+ */
+
+/*
+ * 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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTR3DECL(int) RTFileModeToFlags(const char *pszMode, uint64_t *pfMode)
+{
+ AssertPtrReturn(pszMode, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfMode, VERR_INVALID_POINTER);
+
+ const char *pszCur = pszMode;
+ if (*pszCur == '\0')
+ return VERR_INVALID_PARAMETER;
+
+ uint64_t fMode = 0;
+ char chPrev = '\0';
+ while ( pszCur
+ && *pszCur != '\0')
+ {
+ bool fSkip = false;
+ switch (*pszCur)
+ {
+ /* Opens an existing file for writing and places the
+ * file pointer at the end of the file. The file is
+ * created if it does not exist. */
+ case 'a':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_OPEN_CREATE
+ | RTFILE_O_WRITE
+ | RTFILE_O_APPEND;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ case 'b': /* Binary mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ /* Creates a file or open an existing one for
+ * writing only. The file pointer will be placed
+ * at the beginning of the file.*/
+ case 'c':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_OPEN_CREATE
+ | RTFILE_O_WRITE;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ /* Opens an existing file for reading and places the
+ * file pointer at the beginning of the file. If the
+ * file does not exist an error will be returned. */
+ case 'r':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_OPEN
+ | RTFILE_O_READ;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ case 't': /* Text mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ /* Creates a new file or replaces an existing one
+ * for writing. Places the file pointer at the beginning.
+ * An existing file will be truncated to 0 bytes. */
+ case 'w':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_CREATE_REPLACE
+ | RTFILE_O_WRITE
+ | RTFILE_O_TRUNCATE;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ /* Creates a new file and opens it for writing. Places
+ * the file pointer at the beginning. If the file
+ * exists an error will be returned. */
+ case 'x':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_CREATE
+ | RTFILE_O_WRITE;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ case '+':
+ {
+ switch (chPrev)
+ {
+ case 'a':
+ case 'c':
+ case 'w':
+ case 'x':
+ /* Also open / create file with read access. */
+ fMode |= RTFILE_O_READ;
+ break;
+
+ case 'r':
+ /* Also open / create file with write access. */
+ fMode |= RTFILE_O_WRITE;
+ break;
+
+ case 'b':
+ case 't':
+ /* Silently eat skipped parameters. */
+ fSkip = true;
+ break;
+
+ case 0: /* No previous character yet. */
+ case '+':
+ /* Eat plusses which don't belong to a command. */
+ fSkip = true;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ break;
+ }
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (!fSkip)
+ chPrev = *pszCur;
+ pszCur++;
+ }
+
+ /* No action mask set? */
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ return VERR_INVALID_PARAMETER;
+
+ /** @todo Handle sharing mode */
+ fMode |= RTFILE_O_DENY_NONE;
+
+ /* Return. */
+ *pfMode = fMode;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTFileModeToFlags);
+
+
+RTR3DECL(int) RTFileModeToFlagsEx(const char *pszAccess, const char *pszDisposition,
+ const char *pszSharing, uint64_t *pfMode)
+{
+ AssertPtrReturn(pszAccess, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDisposition, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszSharing, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfMode, VERR_INVALID_POINTER);
+
+ const char *pszCur = pszAccess;
+ if (*pszCur == '\0')
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Handle access mode.
+ */
+ uint64_t fMode = 0;
+ char chPrev = '\0';
+ while ( pszCur
+ && *pszCur != '\0')
+ {
+ bool fSkip = false;
+ switch (*pszCur)
+ {
+ case 'b': /* Binary mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ case 'r': /* Read. */
+ fMode |= RTFILE_O_READ;
+ break;
+
+ case 't': /* Text mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ case 'w': /* Write. */
+ fMode |= RTFILE_O_WRITE;
+ break;
+
+ case 'a': /* Append. */
+ fMode |= RTFILE_O_WRITE | RTFILE_O_APPEND;
+ break;
+
+ case '+':
+ {
+ switch (chPrev)
+ {
+ case 'w':
+ case 'a':
+ /* Also use read access in write mode. */
+ fMode |= RTFILE_O_READ;
+ break;
+
+ case 'r':
+ /* Also use write access in read mode. */
+ fMode |= RTFILE_O_WRITE;
+ break;
+
+ case 'b':
+ case 't':
+ /* Silently eat skipped parameters. */
+ fSkip = true;
+ break;
+
+ case 0: /* No previous character yet. */
+ case '+':
+ /* Eat plusses which don't belong to a command. */
+ fSkip = true;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ break;
+ }
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (!fSkip)
+ chPrev = *pszCur;
+ pszCur++;
+ }
+
+ /*
+ * Handle disposition.
+ */
+ pszCur = pszDisposition;
+
+ /* Create a new file, always, overwrite an existing file. */
+ if ( !RTStrCmp(pszCur, "ca")
+ || !RTStrCmp(pszCur, "create-replace"))
+ fMode |= RTFILE_O_CREATE_REPLACE;
+ /* Create a new file if it does not exist, fail if exist. */
+ else if ( !RTStrCmp(pszCur, "ce")
+ || !RTStrCmp(pszCur, "create"))
+ fMode |= RTFILE_O_CREATE;
+ /* Open existing file, create file if does not exist. */
+ else if ( !RTStrCmp(pszCur, "oc")
+ || !RTStrCmp(pszCur, "open-create"))
+ fMode |= RTFILE_O_OPEN_CREATE;
+ /* Open existing file and place the file pointer at the end of the file, if
+ * opened with write access. Create the file if does not exist.
+ * Note! This mode is ill conceived as the appending is a accesss mode not open disposition. */
+ else if ( !RTStrCmp(pszCur, "oa")
+ || !RTStrCmp(pszCur, "open-append"))
+ fMode |= RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND;
+ /* Open existing, fail if does not exist. */
+ else if ( !RTStrCmp(pszCur, "oe")
+ || !RTStrCmp(pszCur, "open"))
+ fMode |= RTFILE_O_OPEN;
+ /* Open and truncate existing, fail of not exist. */
+ else if ( !RTStrCmp(pszCur, "ot")
+ || !RTStrCmp(pszCur, "open-truncate"))
+ fMode |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE;
+ else
+ return VERR_INVALID_PARAMETER;
+
+ /* No action mask set? */
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Sharing mode.
+ */
+ if (!pszSharing || !*pszSharing)
+ fMode |= RTFILE_O_DENY_NONE;
+ else
+ {
+ do
+ {
+ if (pszSharing[0] == 'n')
+ {
+ if (pszSharing[1] == 'r') /* nr (no other readers) */
+ {
+ if (pszSharing[2] == 'w') /* nrw (no other readers or writers) */
+ {
+ fMode |= RTFILE_O_DENY_READWRITE;
+ pszSharing += 3;
+ }
+ else
+ {
+ fMode |= RTFILE_O_DENY_READ;
+ pszSharing += 2;
+ }
+ }
+ else if (pszSharing[1] == 'w') /* nw (no other writers) */
+ {
+ fMode |= RTFILE_O_DENY_WRITE;
+ pszSharing += 2;
+ }
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+ else if (pszSharing[0] == 'd') /* d (don't deny delete) */
+ {
+ fMode |= RTFILE_O_DENY_WRITE;
+ pszSharing++;
+ }
+ else
+ return VERR_INVALID_PARAMETER;
+ } while (*pszSharing != '\0');
+ }
+
+ /* Return. */
+ *pfMode = fMode;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTFileModeToFlagsEx);
+
diff --git a/src/VBox/Runtime/common/misc/RTFileOpenF.cpp b/src/VBox/Runtime/common/misc/RTFileOpenF.cpp
new file mode 100644
index 00000000..b6e01f0c
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTFileOpenF.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTFileOpenF.cpp $ */
+/** @file
+ * IPRT - RTFileOpenF.
+ */
+
+/*
+ * 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/file.h>
+#include "internal/iprt.h"
+
+
+RTR3DECL(int) RTFileOpenF(PRTFILE pFile, uint64_t fOpen, const char *pszFilenameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszFilenameFmt);
+ int rc = RTFileOpenV(pFile, fOpen, pszFilenameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTFileOpenF);
+
diff --git a/src/VBox/Runtime/common/misc/RTFileOpenV.cpp b/src/VBox/Runtime/common/misc/RTFileOpenV.cpp
new file mode 100644
index 00000000..f54f31d1
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTFileOpenV.cpp
@@ -0,0 +1,58 @@
+/* $Id: RTFileOpenV.cpp $ */
+/** @file
+ * IPRT - RTFileOpenV.
+ */
+
+/*
+ * 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/file.h>
+#include "internal/iprt.h"
+
+#include <iprt/err.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+
+RTR3DECL(int) RTFileOpenV(PRTFILE pFile, uint64_t fOpen, const char *pszFilenameFmt, va_list va)
+{
+ char szFilename[RTPATH_MAX];
+ size_t cchFilename = RTStrPrintfV(szFilename, sizeof(szFilename), pszFilenameFmt, va);
+ if (cchFilename >= sizeof(szFilename) - 1)
+ return VERR_FILENAME_TOO_LONG;
+ return RTFileOpen(pFile, szFilename, fOpen);
+}
+RT_EXPORT_SYMBOL(RTFileOpenV);
+
diff --git a/src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp b/src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp
new file mode 100644
index 00000000..901ebd19
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp
@@ -0,0 +1,65 @@
+/* $Id: RTMemWipeThoroughly.cpp $ */
+/** @file
+ * IPRT - RTMemWipeThoroughly.
+ */
+
+/*
+ * 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/mem.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+
+
+RTDECL(void) RTMemWipeThoroughly(void *pv, size_t cb, size_t cMinPasses) RT_NO_THROW_DEF
+{
+ size_t cPasses = RT_MIN(cMinPasses, 6);
+
+ do
+ {
+ memset(pv, 0xff, cb);
+ ASMMemoryFence();
+
+ memset(pv, 0x00, cb);
+ ASMMemoryFence();
+
+ RTRandBytes(pv, cb);
+ ASMMemoryFence();
+ } while (cPasses-- > 0);
+}
+
diff --git a/src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp b/src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp
new file mode 100644
index 00000000..95ef7e8e
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp
@@ -0,0 +1,60 @@
+/* $Id: RTSystemFirmwareTypeName.cpp $ */
+/** @file
+ * IPRT - RTSystemFirmwareTypeName.
+ */
+
+/*
+ * 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>
+
+
+RTDECL(const char *) RTSystemFirmwareTypeName(RTSYSFWTYPE enmType)
+{
+ switch (enmType)
+ {
+ case RTSYSFWTYPE_INVALID: return "Invalid";
+ case RTSYSFWTYPE_UNKNOWN: return "Unknown";
+ case RTSYSFWTYPE_BIOS: return "BIOS";
+ case RTSYSFWTYPE_UEFI: return "UEFI";
+ case RTSYSFWTYPE_END:
+ case RTSYSFWTYPE_32_BIT_HACK:
+ break;
+ }
+ return "bad-firmware-type";
+}
+RT_EXPORT_SYMBOL(RTSystemFirmwareTypeName);
+
diff --git a/src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp b/src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp
new file mode 100644
index 00000000..3e12048d
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp
@@ -0,0 +1,58 @@
+/* $Id: RTSystemIsInsideVM-amd64-x86.cpp $ */
+/** @file
+ * IPRT -
+ */
+
+/*
+ * 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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/system.h>
+
+#include <iprt/asm-amd64-x86.h>
+#include <iprt/x86.h>
+
+
+RTDECL(bool) RTSystemIsInsideVM(void)
+{
+ if (ASMHasCpuId())
+ {
+ if (ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_HVP)
+ return true;
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTSystemIsInsideVM);
+
diff --git a/src/VBox/Runtime/common/misc/assert.cpp b/src/VBox/Runtime/common/misc/assert.cpp
new file mode 100644
index 00000000..e09d9b47
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/assert.cpp
@@ -0,0 +1,348 @@
+/* $Id: assert.cpp $ */
+/** @file
+ * IPRT - Assertions, common 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 <iprt/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#ifdef IPRT_WITH_ASSERT_STACK
+# ifndef IN_RING3
+# error "IPRT_WITH_ASSERT_STACK is only for ring-3 at present."
+# endif
+# include <iprt/dbg.h>
+#endif
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+#include <iprt/stdarg.h>
+#ifdef IN_RING3
+# include <iprt/env.h>
+# ifndef IPRT_NO_CRT
+# include <stdio.h>
+# endif
+# ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include "../../r3/win/internal-r3-win.h"
+# endif
+#endif
+#include "internal/assert.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The last assertion message, 1st part. */
+RTDATADECL(char) g_szRTAssertMsg1[1024];
+RT_EXPORT_SYMBOL(g_szRTAssertMsg1);
+/** The last assertion message, 2nd part. */
+RTDATADECL(char) g_szRTAssertMsg2[4096];
+RT_EXPORT_SYMBOL(g_szRTAssertMsg2);
+#ifdef IPRT_WITH_ASSERT_STACK
+/** The last assertion message, stack part. */
+RTDATADECL(char) g_szRTAssertStack[4096];
+RT_EXPORT_SYMBOL(g_szRTAssertStack);
+#endif
+/** The length of the g_szRTAssertMsg2 content.
+ * @remarks Race. */
+static uint32_t volatile g_cchRTAssertMsg2;
+/** The last assertion message, expression. */
+RTDATADECL(const char * volatile) g_pszRTAssertExpr;
+RT_EXPORT_SYMBOL(g_pszRTAssertExpr);
+/** The last assertion message, function name. */
+RTDATADECL(const char * volatile) g_pszRTAssertFunction;
+RT_EXPORT_SYMBOL(g_pszRTAssertFunction);
+/** The last assertion message, file name. */
+RTDATADECL(const char * volatile) g_pszRTAssertFile;
+RT_EXPORT_SYMBOL(g_pszRTAssertFile);
+/** The last assertion message, line number. */
+RTDATADECL(uint32_t volatile) g_u32RTAssertLine;
+RT_EXPORT_SYMBOL(g_u32RTAssertLine);
+
+
+/** Set if assertions are quiet. */
+static bool volatile g_fQuiet = false;
+/** Set if assertions may panic. */
+static bool volatile g_fMayPanic = true;
+
+
+RTDECL(bool) RTAssertSetQuiet(bool fQuiet)
+{
+ return ASMAtomicXchgBool(&g_fQuiet, fQuiet);
+}
+RT_EXPORT_SYMBOL(RTAssertSetQuiet);
+
+
+RTDECL(bool) RTAssertAreQuiet(void)
+{
+ return ASMAtomicUoReadBool(&g_fQuiet);
+}
+RT_EXPORT_SYMBOL(RTAssertAreQuiet);
+
+
+RTDECL(bool) RTAssertSetMayPanic(bool fMayPanic)
+{
+ return ASMAtomicXchgBool(&g_fMayPanic, fMayPanic);
+}
+RT_EXPORT_SYMBOL(RTAssertSetMayPanic);
+
+
+RTDECL(bool) RTAssertMayPanic(void)
+{
+ return ASMAtomicUoReadBool(&g_fMayPanic);
+}
+RT_EXPORT_SYMBOL(RTAssertMayPanic);
+
+
+RTDECL(void) RTAssertMsg1(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ /*
+ * Fill in the globals.
+ */
+ ASMAtomicUoWritePtr(&g_pszRTAssertExpr, pszExpr);
+ ASMAtomicUoWritePtr(&g_pszRTAssertFile, pszFile);
+ ASMAtomicUoWritePtr(&g_pszRTAssertFunction, pszFunction);
+ ASMAtomicUoWriteU32(&g_u32RTAssertLine, uLine);
+ RTStrPrintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1),
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ pszExpr, pszFile, uLine, pszFunction);
+
+ /*
+ * If not quiet, make noise.
+ */
+ if (!RTAssertAreQuiet())
+ {
+ RTERRVARS SavedErrVars;
+ RTErrVarsSave(&SavedErrVars);
+
+#ifdef IPRT_WITH_ASSERT_STACK
+ /* The stack dump. */
+ static volatile bool s_fDumpingStackAlready = false; /* for simple recursion prevention */
+ char szStack[sizeof(g_szRTAssertStack)];
+ size_t cchStack = 0;
+# if defined(IN_RING3) && defined(RT_OS_WINDOWS) /** @todo make this stack on/off thing more modular. */
+ bool fStack = (!g_pfnIsDebuggerPresent || !g_pfnIsDebuggerPresent()) && !RTEnvExist("IPRT_ASSERT_NO_STACK");
+# elif defined(IN_RING3)
+ bool fStack = !RTEnvExist("IPRT_ASSERT_NO_STACK");
+# else
+ bool fStack = true;
+# endif
+ szStack[0] = '\0';
+ if (fStack && !s_fDumpingStackAlready)
+ {
+ s_fDumpingStackAlready = true;
+ cchStack = RTDbgStackDumpSelf(szStack, sizeof(szStack), 0);
+ s_fDumpingStackAlready = false;
+ }
+ memcpy(g_szRTAssertStack, szStack, cchStack + 1);
+#endif
+
+#ifdef IN_RING0
+# ifdef IN_GUEST_R0
+ RTLogBackdoorPrintf("\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ pszExpr, pszFile, uLine, pszFunction);
+# endif
+ /** @todo fully integrate this with the logger... play safe a bit for now. */
+ rtR0AssertNativeMsg1(pszExpr, uLine, pszFile, pszFunction);
+
+#else /* !IN_RING0 */
+
+
+# if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT)) /* ugly */
+ if (g_pfnRTLogAssert)
+ g_pfnRTLogAssert(
+# else
+ RTLogAssert(
+# endif
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n"
+# ifdef IPRT_WITH_ASSERT_STACK
+ "Stack :\n%s\n"
+# endif
+ , pszExpr, pszFile, uLine, pszFunction
+# ifdef IPRT_WITH_ASSERT_STACK
+ , szStack
+# endif
+ );
+
+# ifdef IN_RING3
+ /* print to stderr, helps user and gdb debugging. */
+# ifndef IPRT_NO_CRT
+ fprintf(stderr,
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ RT_VALID_PTR(pszExpr) ? pszExpr : "<none>",
+ RT_VALID_PTR(pszFile) ? pszFile : "<none>",
+ uLine,
+ RT_VALID_PTR(pszFunction) ? pszFunction : "");
+# ifdef IPRT_WITH_ASSERT_STACK
+ fprintf(stderr, "Stack :\n%s\n", szStack);
+# endif
+ fflush(stderr);
+# else
+ char szMsg[2048];
+ size_t cchMsg = RTStrPrintf(szMsg, sizeof(szMsg),
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ RT_VALID_PTR(pszExpr) ? pszExpr : "<none>",
+ RT_VALID_PTR(pszFile) ? pszFile : "<none>",
+ uLine,
+ RT_VALID_PTR(pszFunction) ? pszFunction : "");
+ RTLogWriteStdErr(szMsg, cchMsg);
+# ifdef IPRT_WITH_ASSERT_STACK
+ RTLogWriteStdErr(RT_STR_TUPLE("Stack :\n"));
+ RTLogWriteStdErr(szStack, strlen(szStack));
+ RTLogWriteStdErr(RT_STR_TUPLE("\n"));
+# endif
+# endif
+# endif
+#endif /* !IN_RING0 */
+
+ RTErrVarsRestore(&SavedErrVars);
+ }
+}
+RT_EXPORT_SYMBOL(RTAssertMsg1);
+
+
+/**
+ * Worker for RTAssertMsg2V and RTAssertMsg2AddV
+ *
+ * @param fInitial True if it's RTAssertMsg2V, otherwise false.
+ * @param pszFormat The message format string.
+ * @param va The format arguments.
+ */
+static void rtAssertMsg2Worker(bool fInitial, const char *pszFormat, va_list va)
+{
+ va_list vaCopy;
+ size_t cch;
+
+ /*
+ * The global first.
+ */
+ if (fInitial)
+ {
+ va_copy(vaCopy, va);
+ cch = RTStrPrintfV(g_szRTAssertMsg2, sizeof(g_szRTAssertMsg2), pszFormat, vaCopy);
+ ASMAtomicWriteU32(&g_cchRTAssertMsg2, (uint32_t)cch);
+ va_end(vaCopy);
+ }
+ else
+ {
+ cch = ASMAtomicReadU32(&g_cchRTAssertMsg2);
+ if (cch < sizeof(g_szRTAssertMsg2) - 4)
+ {
+ va_copy(vaCopy, va);
+ cch += RTStrPrintfV(&g_szRTAssertMsg2[cch], sizeof(g_szRTAssertMsg2) - cch, pszFormat, vaCopy);
+ ASMAtomicWriteU32(&g_cchRTAssertMsg2, (uint32_t)cch);
+ va_end(vaCopy);
+ }
+ }
+
+ /*
+ * If not quiet, make some noise.
+ */
+ if (!RTAssertAreQuiet())
+ {
+ RTERRVARS SavedErrVars;
+ RTErrVarsSave(&SavedErrVars);
+
+#ifdef IN_RING0
+# ifdef IN_GUEST_R0
+ va_copy(vaCopy, va);
+ RTLogBackdoorPrintfV(pszFormat, vaCopy);
+ va_end(vaCopy);
+# endif
+ /** @todo fully integrate this with the logger... play safe a bit for now. */
+ rtR0AssertNativeMsg2V(fInitial, pszFormat, va);
+
+#else /* !IN_RING0 */
+
+# if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+ if (g_pfnRTLogAssert)
+# endif
+ {
+ va_copy(vaCopy, va);
+# if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+ g_pfnRTLogAssertV(pszFormat, vaCopy);
+# else
+ RTLogAssertV(pszFormat, vaCopy);
+# endif
+ va_end(vaCopy);
+ }
+
+# ifdef IN_RING3
+ /* print to stderr, helps user and gdb debugging. */
+ char szMsg[sizeof(g_szRTAssertMsg2)];
+ va_copy(vaCopy, va);
+ size_t cchMsg = RTStrPrintfV(szMsg, sizeof(szMsg), pszFormat, vaCopy);
+ va_end(vaCopy);
+# ifndef IPRT_NO_CRT
+ fwrite(szMsg, 1, cchMsg, stderr);
+ fflush(stderr);
+# else
+ RTLogWriteStdErr(szMsg, cchMsg);
+# endif
+# endif
+#endif /* !IN_RING0 */
+
+ RTErrVarsRestore(&SavedErrVars);
+ }
+}
+
+
+RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va)
+{
+ rtAssertMsg2Worker(true /*fInitial*/, pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2V);
+
+
+RTDECL(void) RTAssertMsg2AddV(const char *pszFormat, va_list va)
+{
+ rtAssertMsg2Worker(false /*fInitial*/, pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2AddV);
+
diff --git a/src/VBox/Runtime/common/misc/buildconfig.cpp b/src/VBox/Runtime/common/misc/buildconfig.cpp
new file mode 100644
index 00000000..fa7abf00
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/buildconfig.cpp
@@ -0,0 +1,152 @@
+/* $Id: buildconfig.cpp $ */
+/** @file
+ * IPRT - Build Configuration Information.
+ */
+
+/*
+ * 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 "internal/iprt.h"
+#include <iprt/buildconfig.h>
+
+
+
+#ifdef IPRT_BLDCFG_SCM_REV
+RTDECL(uint32_t) RTBldCfgRevision(void)
+{
+ return IPRT_BLDCFG_SCM_REV;
+}
+
+
+RTDECL(const char *) RTBldCfgRevisionStr(void)
+{
+ return RT_XSTR(IPRT_BLDCFG_SCM_REV);
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_STRING
+RTDECL(const char *) RTBldCfgVersion(void)
+{
+ return IPRT_BLDCFG_VERSION_STRING;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_MAJOR
+RTDECL(uint32_t) RTBldCfgVersionMajor(void)
+{
+ return IPRT_BLDCFG_VERSION_MAJOR;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_MINOR
+RTDECL(uint32_t) RTBldCfgVersionMinor(void)
+{
+ return IPRT_BLDCFG_VERSION_MINOR;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_BUILD
+RTDECL(uint32_t) RTBldCfgVersionBuild(void)
+{
+ return IPRT_BLDCFG_VERSION_BUILD;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_TARGET
+RTDECL(const char *) RTBldCfgTarget(void)
+{
+ return IPRT_BLDCFG_TARGET;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_TARGET_ARCH
+RTDECL(const char *) RTBldCfgTargetArch(void)
+{
+ return IPRT_BLDCFG_TARGET_ARCH;
+}
+#endif
+
+
+#if defined(IPRT_BLDCFG_TARGET) && defined(IPRT_BLDCFG_TARGET_ARCH)
+RTDECL(const char *) RTBldCfgTargetDotArch(void)
+{
+ return IPRT_BLDCFG_TARGET "." IPRT_BLDCFG_TARGET_ARCH;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_TYPE
+RTDECL(const char *) RTBldCfgType(void)
+{
+ return IPRT_BLDCFG_TYPE;
+}
+#endif
+
+
+RTDECL(const char *) RTBldCfgCompiler(void)
+{
+#ifdef IPRT_BLDCFG_COMPILER
+ return IPRT_BLDCFG_COMPILER;
+#elif defined(__INTEL_COMPILER)
+ return "intel";
+#elif defined(__GNUC__)
+ return "gcc";
+#elif defined(__llvm__)
+ return "llvm";
+#elif defined(__SUNPRO_CC) || defined(__SUNPRO_C)
+ return "sunpro";
+#elif defined(__IBMCPP__) || defined(__IBMC__)
+# if defined(__COMPILER_VER__)
+ return "ibmzosc";
+# elif defined(__xlC__) || defined(__xlc__)
+ return "ibmxlc";
+# else
+ return "vac";
+# endif
+#elif defined(_MSC_VER)
+ return "vcc";
+#elif defined(__WATCOMC__)
+ return "watcom";
+#else
+# error "Unknown compiler"
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/misc/cidr.cpp b/src/VBox/Runtime/common/misc/cidr.cpp
new file mode 100644
index 00000000..63ac220b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/cidr.cpp
@@ -0,0 +1,129 @@
+/* $Id: cidr.cpp $ */
+/** @file
+ * IPRT - IPv4 address parsing.
+ */
+
+/*
+ * 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/cidr.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+
+
+RTDECL(int) RTCidrStrToIPv4(const char *pszAddress, PRTNETADDRIPV4 pNetwork, PRTNETADDRIPV4 pNetmask)
+{
+ uint8_t cBits;
+ uint8_t addr[4];
+ uint32_t u32Netmask;
+ uint32_t u32Network;
+ const char *psz = pszAddress;
+ const char *pszNetmask;
+ char *pszNext;
+ int rc = VINF_SUCCESS;
+ int cDelimiter = 0;
+ int cDelimiterLimit = 0;
+
+ AssertPtrReturn(pszAddress, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pNetwork, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pNetmask, VERR_INVALID_PARAMETER);
+
+ pszNetmask = RTStrStr(psz, "/");
+ *(uint32_t *)addr = 0;
+ if (!pszNetmask)
+ cBits = 32;
+ else
+ {
+ rc = RTStrToUInt8Ex(pszNetmask + 1, &pszNext, 10, &cBits);
+ if ( RT_FAILURE(rc)
+ || cBits > 32
+ || rc != VINF_SUCCESS) /* No trailing symbols are acceptable after the digit */
+ return VERR_INVALID_PARAMETER;
+ }
+ u32Netmask = ~(uint32_t)((1<< (32 - cBits)) - 1);
+
+ if (cBits <= 8)
+ cDelimiterLimit = 0;
+ else if (cBits <= 16)
+ cDelimiterLimit = 1;
+ else if (cBits <= 24)
+ cDelimiterLimit = 2;
+ else if (cBits <= 32)
+ cDelimiterLimit = 3;
+
+ for (;;)
+ {
+ rc = RTStrToUInt8Ex(psz, &pszNext, 10, &addr[cDelimiter]);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG)
+ return VERR_INVALID_PARAMETER;
+
+ if (*pszNext == '.')
+ cDelimiter++;
+ else if ( cDelimiter >= cDelimiterLimit
+ && ( *pszNext == '\0'
+ || *pszNext == '/'))
+ break;
+ else
+ return VERR_INVALID_PARAMETER;
+
+ if (cDelimiter > 3)
+ /* not more than four octets */
+ return VERR_INVALID_PARAMETER;
+
+ psz = pszNext + 1;
+ }
+ u32Network = RT_MAKE_U32_FROM_U8(addr[3], addr[2], addr[1], addr[0]);
+
+ /* Corner case: see RFC 790 page 2 and RFC 4632 page 6. */
+ if ( addr[0] == 0
+ && ( *(uint32_t *)addr != 0
+ || u32Netmask == (uint32_t)~0))
+ return VERR_INVALID_PARAMETER;
+
+ if ((u32Network & ~u32Netmask) != 0)
+ return VERR_INVALID_PARAMETER;
+
+ pNetmask->u = u32Netmask;
+ pNetwork->u = u32Network;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTCidrStrToIPv4);
+
diff --git a/src/VBox/Runtime/common/misc/circbuf.cpp b/src/VBox/Runtime/common/misc/circbuf.cpp
new file mode 100644
index 00000000..72c95690
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/circbuf.cpp
@@ -0,0 +1,262 @@
+/* $Id: circbuf.cpp $ */
+/** @file
+ * IPRT - Lock Free Circular Buffer
+ */
+
+/*
+ * 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/circbuf.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** @todo r=bird: this is missing docs and magic. */
+typedef struct RTCIRCBUF
+{
+ /** The current read position in the buffer. */
+ size_t offRead;
+ /** Is a read block acquired currently? */
+ bool fReading;
+ /** Is a write block acquired currently? */
+ bool fWriting;
+ /** The current write position in the buffer. */
+ size_t offWrite;
+ /** How much space of the buffer is currently in use. */
+ volatile size_t cbUsed;
+ /** How big is the buffer. */
+ size_t cbBuf;
+ /** The buffer itself. */
+ void *pvBuf;
+} RTCIRCBUF, *PRTCIRCBUF;
+
+
+RTDECL(int) RTCircBufCreate(PRTCIRCBUF *ppBuf, size_t cbSize)
+{
+ /* Validate input. */
+ AssertPtrReturn(ppBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
+
+ PRTCIRCBUF pTmpBuf;
+ pTmpBuf = (PRTCIRCBUF)RTMemAllocZ(sizeof(RTCIRCBUF));
+ if (!pTmpBuf)
+ return VERR_NO_MEMORY;
+
+ pTmpBuf->pvBuf = RTMemAlloc(cbSize);
+ if (pTmpBuf->pvBuf)
+ {
+ pTmpBuf->cbBuf = cbSize;
+ *ppBuf = pTmpBuf;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pTmpBuf);
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(void) RTCircBufDestroy(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ if (!pBuf)
+ return;
+ AssertPtr(pBuf);
+ RTMemFree(pBuf->pvBuf);
+ RTMemFree(pBuf);
+}
+
+
+RTDECL(void) RTCircBufReset(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+
+ pBuf->offRead = 0;
+ pBuf->offWrite = 0;
+ pBuf->cbUsed = 0;
+ pBuf->fReading = false;
+ pBuf->fWriting = false;
+}
+
+
+RTDECL(size_t) RTCircBufFree(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return pBuf->cbBuf - ASMAtomicReadZ(&pBuf->cbUsed);
+}
+
+
+RTDECL(size_t) RTCircBufUsed(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadZ(&pBuf->cbUsed);
+}
+
+RTDECL(size_t) RTCircBufSize(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return pBuf->cbBuf;
+}
+
+RTDECL(bool) RTCircBufIsReading(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadBool(&pBuf->fReading);
+}
+
+RTDECL(bool) RTCircBufIsWriting(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadBool(&pBuf->fWriting);
+}
+
+RTDECL(size_t) RTCircBufOffsetRead(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadZ(&pBuf->offRead);
+}
+
+RTDECL(size_t) RTCircBufOffsetWrite(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadZ(&pBuf->offWrite);
+}
+
+RTDECL(void) RTCircBufAcquireReadBlock(PRTCIRCBUF pBuf, size_t cbReqSize, void **ppvStart, size_t *pcbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+ Assert(cbReqSize > 0);
+ AssertPtr(ppvStart);
+ AssertPtr(pcbSize);
+
+ *ppvStart = 0;
+ *pcbSize = 0;
+
+ /* How much is in use? */
+ size_t cbUsed = ASMAtomicReadZ(&pBuf->cbUsed);
+ if (cbUsed > 0)
+ {
+ /* Get the size out of the requested size, the read block till the end
+ * of the buffer & the currently used size. */
+ size_t cbSize = RT_MIN(cbReqSize, RT_MIN(pBuf->cbBuf - pBuf->offRead, cbUsed));
+ if (cbSize > 0)
+ {
+ /* Return the pointer address which point to the current read
+ * position. */
+ *ppvStart = (char *)pBuf->pvBuf + pBuf->offRead;
+ *pcbSize = cbSize;
+
+ ASMAtomicWriteBool(&pBuf->fReading, true);
+ }
+ }
+}
+
+
+RTDECL(void) RTCircBufReleaseReadBlock(PRTCIRCBUF pBuf, size_t cbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+
+ /* Split at the end of the buffer. */
+ pBuf->offRead = (pBuf->offRead + cbSize) % pBuf->cbBuf;
+
+ ASMAtomicSubZ(&pBuf->cbUsed, cbSize);
+ ASMAtomicWriteBool(&pBuf->fReading, false);
+}
+
+
+RTDECL(void) RTCircBufAcquireWriteBlock(PRTCIRCBUF pBuf, size_t cbReqSize, void **ppvStart, size_t *pcbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+ Assert(cbReqSize > 0);
+ AssertPtr(ppvStart);
+ AssertPtr(pcbSize);
+
+ *ppvStart = 0;
+ *pcbSize = 0;
+
+ /* How much is free? */
+ size_t cbFree = pBuf->cbBuf - ASMAtomicReadZ(&pBuf->cbUsed);
+ if (cbFree > 0)
+ {
+ /* Get the size out of the requested size, then write block till the end
+ * of the buffer & the currently free size. */
+ size_t cbSize = RT_MIN(cbReqSize, RT_MIN(pBuf->cbBuf - pBuf->offWrite, cbFree));
+ if (cbSize > 0)
+ {
+ /* Return the pointer address which point to the current write
+ * position. */
+ *ppvStart = (char*)pBuf->pvBuf + pBuf->offWrite;
+ *pcbSize = cbSize;
+
+ ASMAtomicWriteBool(&pBuf->fWriting, true);
+ }
+ }
+}
+
+
+RTDECL(void) RTCircBufReleaseWriteBlock(PRTCIRCBUF pBuf, size_t cbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+
+ /* Split at the end of the buffer. */
+ pBuf->offWrite = (pBuf->offWrite + cbSize) % pBuf->cbBuf;
+
+ ASMAtomicAddZ(&pBuf->cbUsed, cbSize);
+ ASMAtomicWriteBool(&pBuf->fWriting, false);
+}
+
diff --git a/src/VBox/Runtime/common/misc/expreval.cpp b/src/VBox/Runtime/common/misc/expreval.cpp
new file mode 100644
index 00000000..1099db46
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/expreval.cpp
@@ -0,0 +1,2740 @@
+/* $Id: expreval.cpp $ */
+/** @file
+ * expreval - Expressions evaluator.
+ */
+
+/*
+ * 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/expreval.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max length of a string representation of a number. */
+#define EXPR_NUM_LEN ((sizeof("-9223372036854775802") + 4) & ~3)
+
+/** The max operator stack depth. */
+#define EXPR_MAX_OPERATORS 72
+/** The max operand depth. */
+#define EXPR_MAX_OPERANDS 128
+/** the max variable recursion. */
+#define EXPR_MAX_VAR_RECURSION 20
+
+/** Check if @a a_ch is a valid separator for a alphabetical binary
+ * operator, omitting isspace. */
+#define EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch) \
+ (RT_C_IS_PUNCT((a_ch)) && (a_ch) != '@' && (a_ch) != '_')
+
+/** Check if @a a_ch is a valid separator for a alphabetical binary operator. */
+#define EXPR_IS_OP_SEPARATOR(a_ch) \
+ (RT_C_IS_SPACE((a_ch)) || EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The 64-bit signed integer type we're using. */
+typedef int64_t EXPRINT64;
+
+/** Pointer to a evaluator instance. */
+typedef struct EXPR *PEXPR;
+
+
+/**
+ * Operand variable type.
+ */
+typedef enum
+{
+ /** Invalid zero entry. */
+ kExprVar_Invalid = 0,
+ /** A number. */
+ kExprVar_Num,
+ /** A string in need of expanding (perhaps). */
+ kExprVar_String,
+ /** A simple string that doesn't need expanding. */
+ kExprVar_SimpleString,
+ /** A quoted string in need of expanding (perhaps). */
+ kExprVar_QuotedString,
+ /** A simple quoted string that doesn't need expanding. */
+ kExprVar_QuotedSimpleString,
+ /** The end of the valid variable types. */
+ kExprVar_End
+} EXPRVARTYPE;
+
+/**
+ * Operand variable.
+ */
+typedef struct
+{
+ /** The variable type. */
+ EXPRVARTYPE enmType;
+ /** The variable. */
+ union
+ {
+ /** Pointer to the string. */
+ char *psz;
+ /** The variable. */
+ EXPRINT64 i;
+ } uVal;
+} EXPRVAR;
+/** Pointer to a operand variable. */
+typedef EXPRVAR *PEXPRVAR;
+/** Pointer to a const operand variable. */
+typedef EXPRVAR const *PCEXPRVAR;
+
+/**
+ * Operator return statuses.
+ */
+typedef enum
+{
+ kExprRet_Error = -1,
+ kExprRet_Ok = 0,
+ kExprRet_Operator,
+ kExprRet_Operand,
+ kExprRet_EndOfExpr,
+ kExprRet_End
+} EXPRRET;
+
+/**
+ * Operator.
+ */
+typedef struct
+{
+ /** The operator. */
+ char szOp[11];
+ /** The length of the operator string. */
+ uint8_t cchOp;
+ /** The pair operator.
+ * This is used with '(' and '?'. */
+ char chPair;
+ /** The precedence. Higher means higher. */
+ char iPrecedence;
+ /** The number of arguments it takes. */
+ signed char cArgs;
+ /** Pointer to the method implementing the operator. */
+ EXPRRET (*pfn)(PEXPR pThis);
+} EXPROP;
+/** Pointer to a const operator. */
+typedef EXPROP const *PCEXPROP;
+
+
+/** Magic value for RTEXPREVALINT::u32Magic.
+ * @todo fixme */
+#define RTEXPREVAL_MAGIC UINT32_C(0x12345678)
+
+/**
+ * Expression evaluator instance.
+ */
+typedef struct RTEXPREVALINT
+{
+ /** Magic number (RTEXPREVAL_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** RTEXPREVAL_XXX. */
+ uint64_t fFlags;
+ /** Name for logging purposes (copy) */
+ char *pszName;
+ /** User argument to callbacks. */
+ void *pvUser;
+ /** Callback for getting variables or checking if they exists. */
+ PFNRTEXPREVALQUERYVARIABLE pfnQueryVariable;
+} RTEXPREVALINT;
+
+/**
+ * An expression being evaluated.
+ */
+typedef struct EXPR
+{
+ /** The full expression. */
+ const char *pszExpr;
+ /** The current location. */
+ const char *psz;
+ /** Error info keeper. */
+ PRTERRINFO pErrInfo;
+ /** Pointer to the instance we evaluating under. */
+ RTEXPREVALINT *pEvaluator;
+ /** Pending binary operator. */
+ PCEXPROP pPending;
+ /** Top of the operator stack. */
+ int iOp;
+ /** Top of the operand stack. */
+ int iVar;
+ /** The operator stack. */
+ PCEXPROP apOps[EXPR_MAX_OPERATORS];
+ /** The operand stack. */
+ EXPRVAR aVars[EXPR_MAX_OPERANDS];
+} EXPR;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Operator start character map.
+ * This indicates which characters that are starting operators and which aren't.
+ *
+ * Bit 0: Indicates that this char is used in operators.
+ * Bit 1: When bit 0 is clear, this indicates whitespace.
+ * When bit 1 is set, this indicates whether the operator can be used
+ * immediately next to an operand without any clear separation.
+ * Bits 2 thru 7: Index into g_aExprOps of the first operator starting with
+ * this character.
+ */
+static uint8_t g_abOpStartCharMap[256] = {0};
+/** Whether we've initialized the map. */
+static int g_fExprInitializedMap = 0;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void expr_unget_op(PEXPR pThis);
+static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis);
+
+
+
+
+/**
+ * Displays an error message.
+ *
+ * The total string length must not exceed 256 bytes.
+ *
+ * @returns kExprRet_Error
+ * @param pThis The evaluator instance.
+ * @param pszError The message format string.
+ * @param ... The message format args.
+ */
+static EXPRRET expr_error(PEXPR pThis, const char *pszError, ...)
+{
+ va_list va;
+ va_start(va, pszError);
+ RTErrInfoSetV(pThis->pErrInfo, VERR_PARSE_ERROR, pszError, va);
+ va_end(va);
+ return kExprRet_Error;
+}
+
+
+/**
+ * Converts a number to a string.
+ *
+ * @returns pszDst.
+ * @param pszDst The string buffer to write into. Assumes length of EXPR_NUM_LEN.
+ * @param iSrc The number to convert.
+ */
+static char *expr_num_to_string(char *pszDst, EXPRINT64 iSrc)
+{
+ char szTmp[64]; /* RTStrFormatNumber assumes this as a minimum size. */
+ AssertCompile(EXPR_NUM_LEN < sizeof(szTmp));
+ size_t cchTmp = RTStrFormatNumber(szTmp, iSrc, 10 /*uBase*/, 0 /*cchWidth*/, 0 /*cchPrecision*/,
+ RTSTR_F_64BIT | RTSTR_F_VALSIGNED);
+ return (char *)memcpy(pszDst, szTmp, cchTmp + 1);
+}
+
+
+/**
+ * Attempts to convert a (simple) string into a number.
+ *
+ * @returns status code.
+ * @param pThis The evaluator instance.
+ * @param piDst Where to store the numeric value on success.
+ * @param pszSrc The string to try convert.
+ * @param fQuiet Whether we should be quiet or grumpy on failure.
+ */
+static EXPRRET expr_string_to_num(PEXPR pThis, EXPRINT64 *piDst, const char *pszSrc, int fQuiet)
+{
+ EXPRRET rc = kExprRet_Ok;
+ char const *psz = pszSrc;
+ EXPRINT64 i;
+ unsigned uBase;
+ int fNegative;
+
+ /*
+ * Skip blanks.
+ */
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+ const char *const pszFirst = psz;
+
+ /*
+ * Check for '-'.
+ *
+ * At this point we will not need to deal with operators, this is
+ * just an indicator of negative numbers. If some operator ends up
+ * here it's because it came from a string expansion and thus shall
+ * not be interpreted. If this turns out to be an stupid restriction
+ * it can be fixed, but for now it stays like this.
+ */
+ fNegative = *psz == '-';
+ if (fNegative)
+ psz++;
+
+ /*
+ * Determin base.
+ * Recognize some exsotic prefixes here in addition to the two standard ones.
+ */
+ uint64_t const fFlags = pThis->pEvaluator->fFlags;
+ uBase = fFlags & RTEXPREVAL_F_DEFAULT_BASE_16 ? 16 : 10;
+ char const ch0 = psz[0];
+ if (ch0 == '0')
+ {
+ char const ch1 = psz[1];
+ switch (ch1)
+ {
+ case '\0':
+ break;
+
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* C-style octal */
+ if (fFlags & RTEXPREVAL_F_C_OCTAL)
+ {
+ uBase = 8;
+ psz++;
+ }
+ break;
+
+ case '8':
+ case '9':
+ break;
+
+ case 'x':
+ case 'X':
+ uBase = 16;
+ psz += 2;
+ break;
+ case 'y': case 'Y': /* windbg, VBoxDbg */
+ case 'b': case 'B': /* python and others */
+ uBase = 2;
+ psz += 2;
+ break;
+ case 'n': case 'N': /* windbg */
+ case 'i': case 'I': /* VBoxDbg */
+ uBase = 10;
+ psz += 2;
+ break;
+ case 't': case 'T': /* windbg, VBoxDbg */
+ case 'o': case 'O': /* python and others */
+ uBase = 8;
+ psz += 2;
+ break;
+ }
+ }
+
+ /*
+ * Convert digits.
+ */
+ i = 0;
+ for (;;)
+ {
+ unsigned iDigit;
+ int ch = *psz;
+ switch (ch)
+ {
+ case '0': iDigit = 0; break;
+ case '1': iDigit = 1; break;
+ case '2': iDigit = 2; break;
+ case '3': iDigit = 3; break;
+ case '4': iDigit = 4; break;
+ case '5': iDigit = 5; break;
+ case '6': iDigit = 6; break;
+ case '7': iDigit = 7; break;
+ case '8': iDigit = 8; break;
+ case '9': iDigit = 9; break;
+ case 'a':
+ case 'A': iDigit = 10; break;
+ case 'b':
+ case 'B': iDigit = 11; break;
+ case 'c':
+ case 'C': iDigit = 12; break;
+ case 'd':
+ case 'D': iDigit = 13; break;
+ case 'e':
+ case 'E': iDigit = 14; break;
+ case 'F': iDigit = 15; break;
+ case 'f':
+ /* Make 'false' -> 0: */
+ if ( psz != pszFirst
+ || strncmp(psz + 1, RT_STR_TUPLE("alse")) != 0)
+ {
+ iDigit = 15;
+ break;
+ }
+ psz += sizeof("false") - 1;
+ RT_FALL_THROUGH();
+
+ default:
+ /* Make 'true' evaluate to 1: */
+ if (psz == pszFirst && strncmp(psz, RT_STR_TUPLE("true")) == 0)
+ {
+ psz += sizeof("true") - 1;
+ i = 1;
+ }
+
+ /*
+ * Is the rest white space?
+ */
+ while (RT_C_IS_SPACE(*psz))
+ psz++;
+ if (*psz != '\0')
+ {
+ iDigit = uBase;
+ break;
+ }
+ RT_FALL_THROUGH();
+
+ case '\0':
+ if (fNegative)
+ i = -i;
+ *piDst = i;
+ return rc;
+ }
+ if (iDigit >= uBase)
+ {
+ if (fNegative)
+ i = -i;
+ *piDst = i;
+ if (!fQuiet)
+ expr_error(pThis, "Invalid %u-base number \"%.80s\"", uBase, pszSrc);
+ return kExprRet_Error;
+ }
+
+ /* add the digit and advance */
+ /** @todo check for overflow? */
+ i *= uBase;
+ i += iDigit;
+ psz++;
+ }
+ /* not reached */
+}
+
+
+/**
+ * Checks if the variable is a string or not.
+ *
+ * @returns 1 if it's a string, 0 otherwise.
+ * @param pVar The variable.
+ */
+static int expr_var_is_string(PCEXPRVAR pVar)
+{
+ return pVar->enmType >= kExprVar_String;
+}
+
+
+/**
+ * Checks if the variable contains a string that was quoted
+ * in the expression.
+ *
+ * @returns 1 if if was a quoted string, otherwise 0.
+ * @param pVar The variable.
+ */
+static int expr_var_was_quoted(PCEXPRVAR pVar)
+{
+ return pVar->enmType >= kExprVar_QuotedString;
+}
+
+
+/**
+ * Deletes a variable.
+ *
+ * @param pVar The variable.
+ */
+static void expr_var_delete(PEXPRVAR pVar)
+{
+ if (expr_var_is_string(pVar))
+ {
+ RTMemTmpFree(pVar->uVal.psz);
+ pVar->uVal.psz = NULL;
+ }
+ pVar->enmType = kExprVar_Invalid;
+}
+
+
+/**
+ * Initializes a new variables with a sub-string value.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pThis The evaluator expression instance.
+ * @param pVar The new variable.
+ * @param psz The start of the string value.
+ * @param cch The number of chars to copy.
+ * @param enmType The string type.
+ */
+static EXPRRET expr_var_init_substring(PEXPR pThis, PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
+{
+ /* convert string needing expanding into simple ones if possible. */
+ if ( enmType == kExprVar_String
+ && !memchr(psz, '$', cch))
+ enmType = kExprVar_SimpleString;
+ else if ( enmType == kExprVar_QuotedString
+ && !memchr(psz, '$', cch))
+ enmType = kExprVar_QuotedSimpleString;
+
+ pVar->enmType = enmType;
+ pVar->uVal.psz = (char *)RTMemTmpAlloc(cch + 1);
+ if (RT_LIKELY(pVar->uVal.psz))
+ {
+ memcpy(pVar->uVal.psz, psz, cch);
+ pVar->uVal.psz[cch] = '\0';
+ return kExprRet_Ok;
+ }
+ pVar->enmType = kExprVar_End;
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cch + 1);
+ return kExprRet_Error;
+}
+
+
+#if 0 /* unused */
+/**
+ * Initializes a new variables with a string value.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pVar The new variable.
+ * @param psz The string value.
+ * @param enmType The string type.
+ */
+static EXPRRET expr_var_init_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
+{
+ return expr_var_init_substring(pVar, psz, strlen(psz), enmType);
+}
+
+
+/**
+ * Assigns a sub-string value to a variable.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pVar The new variable.
+ * @param psz The start of the string value.
+ * @param cch The number of chars to copy.
+ * @param enmType The string type.
+ */
+static void expr_var_assign_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
+{
+ expr_var_delete(pVar);
+ return expr_var_init_substring(pVar, psz, cch, enmType);
+}
+
+
+/**
+ * Assignes a string value to a variable.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pVar The variable.
+ * @param psz The string value.
+ * @param enmType The string type.
+ */
+static void expr_var_assign_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
+{
+ expr_var_delete(pVar);
+ return expr_var_init_string(pVar, psz, enmType);
+}
+#endif /* unused */
+
+
+/**
+ * Finds the end of the current variable expansion, taking nested expansion
+ * into account.
+ *
+ * This is somewhat similar to the code down in expr_get_unary_or_operand.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pThis The evaluator expression instance.
+ * @param pchSrc Pointer to the dollar of the variable expansion.
+ * @param cchSrc The length of the variable expansion expression.
+ * @param pcchVarRef Where to return the length of the variable expansion.
+ * @param pfNested Where to return whether it's a nested (@c true) or plain
+ * one.
+ */
+static EXPRRET expr_expand_find_end(PEXPR pThis, const char *pchSrc, size_t cchSrc, size_t *pcchVarRef, bool *pfNested)
+{
+ const char * const pchStart = pchSrc;
+
+ /*
+ * Push the initial expression.
+ */
+ Assert(cchSrc >= 2);
+ Assert(pchSrc[0] == '$');
+ Assert(pchSrc[1] == '{');
+ unsigned cPars = 1;
+ pchSrc += 2;
+ cchSrc -= 2;
+
+ /*
+ * Parse the rest of the string till we've back at cPars == 0.
+ */
+ *pfNested = false;
+ while (cchSrc > 0)
+ {
+ char const ch = *pchSrc;
+ if ( ch == '$'
+ && cchSrc >= 2
+ && pchSrc[1] == '{')
+ {
+ if (cPars < EXPR_MAX_VAR_RECURSION)
+ cPars++;
+ else
+ {
+ *pcchVarRef = 0;
+ return expr_error(pThis, "Too deep nesting of variable expansions");
+ }
+ *pfNested = true;
+ pchSrc += 2;
+ cchSrc -= 2;
+ }
+ else
+ {
+ pchSrc += 1;
+ cchSrc -= 1;
+ if (ch == '}')
+ if (--cPars == 0)
+ {
+ *pcchVarRef = pchSrc - pchStart;
+ return kExprRet_Ok;
+ }
+ }
+ }
+ *pcchVarRef = 0;
+ return expr_error(pThis, "Unbalanced variable expansions: %.*s", pchStart, pchSrc - pchStart);
+}
+
+
+/**
+ * Returns the given string with all variables references replaced.
+ *
+ * @returns Pointer to expanded string on success (RTMemTmpFree), NULL on
+ * failure (error already set).
+ * @param pThis The evaluator expression instance.
+ * @param pchSrc The string to expand.
+ * @param cchSrc The length of the string to expand.
+ * @param cDepth The recursion depth, starting at zero.
+ */
+static char *expr_expand_string(PEXPR pThis, const char *pchSrc, size_t cchSrc, unsigned cDepth)
+{
+ if (cDepth < EXPR_MAX_VAR_RECURSION)
+ {
+ size_t cbRetAlloc = RT_ALIGN_Z(cchSrc + 1 + 16, 16);
+ char *pszRet = (char *)RTMemTmpAlloc(cbRetAlloc);
+ if (pszRet)
+ {
+ size_t offRet = 0;
+ while (cchSrc > 0)
+ {
+ /*
+ * Look for the next potential variable reference.
+ */
+ const char *pchDollar = (const char *)memchr(pchSrc, '$', cchSrc);
+ size_t cchPlain = pchDollar ? pchDollar - pchSrc : cchSrc;
+ size_t cchNext = cchPlain;
+
+ if (pchDollar)
+ {
+ /* Treat lone $ w/o a following { as plain text. */
+ if ( cchPlain + 1 >= cchSrc
+ && pchDollar[0] == '$'
+ && ( cchPlain + 1 == cchSrc
+ || pchDollar[1] != '{') )
+ {
+ cchPlain += 1;
+ cchNext += 1;
+ pchDollar += 1;
+ }
+ /* Eat up escaped dollars: $$ -> $ */
+ else
+ while (cchNext + 2 <= cchSrc && pchDollar[1] == '$' && pchDollar[0] == '$')
+ {
+ cchPlain += 1;
+ cchNext += 2;
+ pchDollar += 2;
+ }
+ }
+
+ /* Finally copy out plain text.*/
+ if (cchPlain > 0)
+ {
+ if (cchPlain >= cbRetAlloc - offRet)
+ {
+ size_t const cbNeeded = RT_ALIGN_Z(offRet + cchPlain + (!pchDollar ? 1 : offRet <= 64 ? 16 : 64), 16);
+ void *pvNew = RTMemTmpAlloc(cbNeeded);
+ if (pvNew)
+ memcpy(pvNew, pszRet, offRet);
+ RTMemTmpFree(pszRet);
+ pszRet = (char *)pvNew;
+ if (pvNew)
+ cbRetAlloc = cbNeeded;
+ else
+ {
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbNeeded);
+ return NULL;
+ }
+ }
+
+ memcpy(&pszRet[offRet], pchSrc, cchPlain);
+ offRet += cchPlain;
+ pszRet[offRet] = '\0';
+ pchSrc += cchNext;
+ cchSrc -= cchNext;
+ if (!cchSrc)
+ break;
+
+ /* If we don't have ${, just loop. */
+ if ( cchSrc < 2
+ || pchSrc[0] != '$'
+ || pchSrc[1] != '{')
+ continue;
+ }
+
+ /*
+ * If we get down here we have a ${ or $( at pchSrc. The fun part now is
+ * finding the end of it and recursively dealing with any sub-expansions first.
+ */
+ Assert(pchSrc[0] == '$' && pchSrc[1] == '{');
+ size_t cchVarRef;
+ bool fNested;
+ if (expr_expand_find_end(pThis, pchSrc, cchSrc, &cchVarRef, &fNested) == kExprRet_Ok)
+ {
+ /* Lookup the variable. Simple when it's a plain one, for nested ones we
+ first have to expand the variable name itself before looking it up. */
+ char *pszValue;
+ int vrc;
+ if (!fNested)
+ vrc = pThis->pEvaluator->pfnQueryVariable(&pchSrc[2], cchSrc - 3, pThis->pEvaluator->pvUser, &pszValue);
+ else
+ {
+ char *pszName = expr_expand_string(pThis, &pchSrc[2], cchSrc - 3, cDepth + 1);
+ if (!pszName)
+ {
+ RTMemTmpFree(pszRet);
+ return NULL;
+ }
+ vrc = pThis->pEvaluator->pfnQueryVariable(pszName, strlen(pszName), pThis->pEvaluator->pvUser, &pszValue);
+ RTMemTmpFree(pszName);
+ }
+
+ /* Treat variables that aren't found as empty strings for now.
+ This may need to become configurable later. */
+ char *pszValueFree = pszValue;
+ static char s_szNotFound[] = "";
+ if (vrc == VERR_NOT_FOUND)
+ {
+ pszValue = s_szNotFound;
+ vrc = VINF_SUCCESS;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Append the value to the return string.
+ */
+ size_t cchValue = strlen(pszValue);
+ if (cchValue > 0)
+ {
+ if (cchValue >= cbRetAlloc - offRet)
+ {
+ size_t const cbNeeded = RT_ALIGN_Z(offRet + cchValue + (!pchDollar ? 1 : offRet <= 64 ? 16 : 64),
+ 16);
+ void *pvNew = RTMemTmpAlloc(cbNeeded);
+ if (pvNew)
+ memcpy(pvNew, pszRet, offRet);
+ RTMemTmpFree(pszRet);
+ pszRet = (char *)pvNew;
+ if (pvNew)
+ cbRetAlloc = cbNeeded;
+ else
+ {
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbNeeded);
+ RTStrFree(pszValueFree);
+ return NULL;
+ }
+ }
+
+ memcpy(&pszRet[offRet], pszValue, cchValue);
+ offRet += cchValue;
+ pszRet[offRet] = '\0';
+ }
+ pchSrc += cchVarRef;
+ cchSrc -= cchVarRef;
+ RTStrFree(pszValueFree);
+ continue;
+ }
+ }
+ RTMemTmpFree(pszRet);
+ return NULL;
+ }
+ return pszRet;
+ }
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbRetAlloc);
+ }
+ else
+ RTErrInfoSet(pThis->pErrInfo, VERR_TOO_MUCH_DATA, "Too deeply nested variable expression");
+ return NULL;
+}
+
+
+/**
+ * Simplifies a string variable.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pThis The evaluator expression instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_make_simple_string(PEXPR pThis, PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ {
+ char *psz = (char *)RTMemTmpAlloc(EXPR_NUM_LEN);
+ if (psz)
+ {
+ expr_num_to_string(psz, pVar->uVal.i);
+ pVar->uVal.psz = psz;
+ pVar->enmType = kExprVar_SimpleString;
+ }
+ else
+ {
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", EXPR_NUM_LEN);
+ return kExprRet_Error;
+ }
+ break;
+ }
+
+ case kExprVar_String:
+ case kExprVar_QuotedString:
+ {
+ Assert(strchr(pVar->uVal.psz, '$'));
+ char *psz = expr_expand_string(pThis, pVar->uVal.psz, strlen(pVar->uVal.psz), 0);
+ if (psz)
+ {
+ RTMemTmpFree(pVar->uVal.psz);
+ pVar->uVal.psz = psz;
+
+ pVar->enmType = pVar->enmType == kExprVar_String
+ ? kExprVar_SimpleString
+ : kExprVar_QuotedSimpleString;
+ }
+ else
+ return kExprRet_Error;
+ break;
+ }
+
+ case kExprVar_SimpleString:
+ case kExprVar_QuotedSimpleString:
+ /* nothing to do. */
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pVar->enmType));
+ }
+ return kExprRet_Ok;
+}
+
+
+#if 0 /* unused */
+/**
+ * Turns a variable into a string value.
+ *
+ * @param pVar The variable.
+ */
+static void expr_var_make_string(PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ expr_var_make_simple_string(pVar);
+ break;
+
+ case kExprVar_String:
+ case kExprVar_SimpleString:
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ /* nothing to do. */
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pVar->enmType));
+ }
+}
+#endif /* unused */
+
+
+/**
+ * Initializes a new variables with a integer value.
+ *
+ * @param pVar The new variable.
+ * @param i The integer value.
+ */
+static void expr_var_init_num(PEXPRVAR pVar, EXPRINT64 i)
+{
+ pVar->enmType = kExprVar_Num;
+ pVar->uVal.i = i;
+}
+
+
+/**
+ * Assigns a integer value to a variable.
+ *
+ * @param pVar The variable.
+ * @param i The integer value.
+ */
+static void expr_var_assign_num(PEXPRVAR pVar, EXPRINT64 i)
+{
+ expr_var_delete(pVar);
+ expr_var_init_num(pVar, i);
+}
+
+
+/**
+ * Turns the variable into a number.
+ *
+ * @returns status code.
+ * @param pThis The evaluator instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_make_num(PEXPR pThis, PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ /* nothing to do. */
+ break;
+
+ case kExprVar_String:
+ {
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ return rc;
+ RT_FALL_THROUGH();
+ }
+ case kExprVar_SimpleString:
+ {
+ EXPRINT64 i;
+ EXPRRET rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 0 /* fQuiet */);
+ if (rc < kExprRet_Ok)
+ return rc;
+ expr_var_assign_num(pVar, i);
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ return expr_error(pThis, "Cannot convert a quoted string to a number");
+
+ default:
+ AssertMsgFailedReturn(("%d\n", pVar->enmType), kExprRet_Error);
+ }
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Try to turn the variable into a number.
+ *
+ * @returns status code.
+ * @param pThis The instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_try_make_num(PEXPR pThis, PEXPRVAR pVar)
+{
+ EXPRRET rc;
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ /* nothing to do. */
+ break;
+
+ case kExprVar_String:
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ return rc;
+ RT_FALL_THROUGH();
+ case kExprVar_SimpleString:
+ {
+ EXPRINT64 i;
+ rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 1 /* fQuiet */);
+ if (rc < kExprRet_Ok)
+ return rc;
+ expr_var_assign_num(pVar, i);
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ /* can't do this */
+ return kExprRet_Error;
+
+ default:
+ AssertMsgFailedReturn(("%d\n", pVar->enmType), kExprRet_Error);
+ }
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Initializes a new variables with a boolean value.
+ *
+ * @param pVar The new variable.
+ * @param f The boolean value.
+ */
+static void expr_var_init_bool(PEXPRVAR pVar, int f)
+{
+ pVar->enmType = kExprVar_Num;
+ pVar->uVal.i = !!f;
+}
+
+
+/**
+ * Assigns a boolean value to a variable.
+ *
+ * @param pVar The variable.
+ * @param f The boolean value.
+ */
+static void expr_var_assign_bool(PEXPRVAR pVar, int f)
+{
+ expr_var_delete(pVar);
+ expr_var_init_bool(pVar, f);
+}
+
+
+/**
+ * Turns the variable into an boolean.
+ *
+ * @returns the boolean interpretation.
+ * @param pThis The instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_make_bool(PEXPR pThis, PEXPRVAR pVar)
+{
+ EXPRRET rc = kExprRet_Ok;
+
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ pVar->uVal.i = !!pVar->uVal.i;
+ break;
+
+ case kExprVar_String:
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ break;
+ RT_FALL_THROUGH();
+ case kExprVar_SimpleString:
+ {
+ /*
+ * Try convert it to a number. If that fails, check for 'true' or
+ * 'false', if neither then use python / GNU make logic wrt strings.
+ */
+ EXPRINT64 iVal;
+ char const *psz = pVar->uVal.psz;
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+ if ( *psz
+ && expr_string_to_num(pThis, &iVal, psz, 1 /* fQuiet */) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar, iVal != 0);
+ else if ( strncmp(psz, RT_STR_TUPLE("true")) == 0
+ && *RTStrStripL(&psz[sizeof("true") - 1]) == '\0')
+ expr_var_assign_bool(pVar, true);
+ else if ( strncmp(psz, RT_STR_TUPLE("false")) == 0
+ && *RTStrStripL(&psz[sizeof("false") - 1]) == '\0')
+ expr_var_assign_bool(pVar, false);
+ else
+ expr_var_assign_bool(pVar, *psz != '\0');
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ break;
+ RT_FALL_THROUGH();
+ case kExprVar_QuotedSimpleString:
+ /*
+ * Use python / GNU make boolean logic: non-empty string means true.
+ * No stripping here, as the string is quoted as should be taken exactly as given.
+ */
+ expr_var_assign_bool(pVar, *pVar->uVal.psz != '\0');
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pVar->enmType));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Pops a varable off the stack and deletes it.
+ * @param pThis The evaluator instance.
+ */
+static void expr_pop_and_delete_var(PEXPR pThis)
+{
+ expr_var_delete(&pThis->aVars[pThis->iVar]);
+ pThis->iVar--;
+}
+
+
+
+/**
+ * Tries to make the variables the same type.
+ *
+ * This will not convert numbers to strings, unless one of them
+ * is a quoted string.
+ *
+ * this will try convert both to numbers if neither is quoted. Both
+ * conversions will have to suceed for this to be commited.
+ *
+ * All strings will be simplified.
+ *
+ * @returns status code. Done complaining on failure.
+ *
+ * @param pThis The evaluator instance.
+ * @param pVar1 The first variable.
+ * @param pVar2 The second variable.
+ * @param pszOp The operator requesting this (for errors).
+ */
+static EXPRRET expr_var_unify_types(PEXPR pThis, PEXPRVAR pVar1, PEXPRVAR pVar2, const char *pszOp)
+{
+/** @todo Add flag for selecting preference here when forcing types */
+
+
+ /*
+ * Try make the variables the same type before comparing.
+ */
+ if ( !expr_var_was_quoted(pVar1)
+ && !expr_var_was_quoted(pVar2))
+ {
+ if ( expr_var_is_string(pVar1)
+ || expr_var_is_string(pVar2))
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_try_make_num(pThis, pVar2);
+ else if (!expr_var_is_string(pVar2))
+ expr_var_try_make_num(pThis, pVar1);
+ else
+ {
+ /*
+ * Both are strings, simplify them then see if both can be made into numbers.
+ */
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ {
+ EXPRINT64 iVar1;
+ EXPRINT64 iVar2;
+ if ( expr_string_to_num(pThis, &iVar1, pVar1->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok
+ && expr_string_to_num(pThis, &iVar2, pVar2->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok)
+ {
+ expr_var_assign_num(pVar1, iVar1);
+ expr_var_assign_num(pVar2, iVar2);
+ }
+ }
+ else
+ return rc;
+ }
+ }
+ }
+ else
+ {
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ { /* likely */ }
+ else
+ return rc;
+ }
+
+ /*
+ * Complain if they aren't the same type now.
+ */
+ if (expr_var_is_string(pVar1) != expr_var_is_string(pVar2))
+ return expr_error(pThis, "Unable to unify types for \"%s\"", pszOp);
+ return kExprRet_Ok;
+}
+
+
+
+/*********************************************************************************************************************************
+* Operators *
+*********************************************************************************************************************************/
+
+/**
+ * Is variable defined, unary.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_defined(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ {
+ int vrc = pThis->pEvaluator->pfnQueryVariable(pVar->uVal.psz, strlen(pVar->uVal.psz), pThis->pEvaluator->pvUser, NULL);
+ expr_var_assign_bool(pVar, vrc != VERR_NOT_FOUND);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Does file(/dir/whatever) exist, unary.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_exists(PEXPR pThis)
+{
+ EXPRRET rc;
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ if (pThis->pEvaluator->fFlags & RTEXPREVAL_F_EXISTS_OP)
+ {
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ expr_var_assign_bool(pVar, RTPathExists(pVar->uVal.psz) == 0);
+ }
+ else
+ rc = expr_error(pThis, "The 'exists' operator is not accessible");
+
+ return rc;
+}
+
+
+/**
+ * Convert to boolean.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bool(PEXPR pThis)
+{
+ return expr_var_make_bool(pThis, &pThis->aVars[pThis->iVar]);
+}
+
+
+/**
+ * Convert to number, works on quoted strings too.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_num(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ /* unquote the string */
+ if (pVar->enmType == kExprVar_QuotedSimpleString)
+ pVar->enmType = kExprVar_SimpleString;
+ else if (pVar->enmType == kExprVar_QuotedString)
+ pVar->enmType = kExprVar_String;
+
+ return expr_var_make_num(pThis, pVar);
+}
+
+
+/**
+ * Performs a strlen() on the simplified/converted string argument.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_strlen(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ expr_var_assign_num(pVar, strlen(pVar->uVal.psz));
+
+ return rc;
+}
+
+
+/**
+ * Convert to string (simplified and quoted)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_str(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ pVar->enmType = kExprVar_QuotedSimpleString;
+
+ return rc;
+}
+
+
+/**
+ * Pluss (dummy / make_integer)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_pluss(PEXPR pThis)
+{
+ return expr_var_make_num(pThis, &pThis->aVars[pThis->iVar]);
+}
+
+
+/**
+ * Minus (negate)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_minus(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_num(pThis, pVar);
+ if (rc >= kExprRet_Ok)
+ pVar->uVal.i = -pVar->uVal.i;
+
+ return rc;
+}
+
+
+
+/**
+ * Bitwise NOT.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_not(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_num(pThis, pVar);
+ if (rc >= kExprRet_Ok)
+ pVar->uVal.i = ~pVar->uVal.i;
+
+ return rc;
+}
+
+
+/**
+ * Logical NOT.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_not(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_bool(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ pVar->uVal.i = !pVar->uVal.i;
+
+ return rc;
+}
+
+
+/**
+ * Multiplication.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_multiply(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i *= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+
+/**
+ * Division.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_divide(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i /= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+
+/**
+ * Modulus.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_modulus(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i %= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Addition (numeric).
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_add(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i += pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Subtract (numeric).
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_sub(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i -= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise left shift.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_shift_left(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i <<= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise right shift.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_shift_right(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i >>= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than or equal, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_less_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vle");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than or equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_less_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "<=");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_less_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vlt");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_less_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "<");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater or equal than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_greater_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vge");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater or equal than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_greater_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, ">=");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_greater_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vgt");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_greater_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, ">");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Equal, version strings.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_equal(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ int const fIsString1 = expr_var_is_string(pVar1);
+
+ /*
+ * The same type?
+ */
+ if (fIsString1 == expr_var_is_string(pVar2))
+ {
+ if (!fIsString1)
+ /* numbers are simple */
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ /* try a normal string compare. */
+ rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ {
+ if (!RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz))
+ expr_var_assign_bool(pVar1, 1);
+ /* try convert and compare as number instead. */
+ else if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ /* ok, they really aren't equal. */
+ else
+ expr_var_assign_bool(pVar1, 0);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * If the type differs, there are now two options:
+ * 1. Try convert the string to a valid number and compare the numbers.
+ * 2. Convert the non-string to a number and compare the strings.
+ */
+ if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) == 0);
+ }
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Not equal, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_not_equal(PEXPR pThis)
+{
+ EXPRRET rc = expr_op_ver_equal(pThis);
+ if (rc >= kExprRet_Ok)
+ rc = expr_op_logical_not(pThis);
+ return rc;
+}
+
+
+/**
+ * Equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_equal(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ int const fIsString1 = expr_var_is_string(pVar1);
+
+ /*
+ * The same type?
+ */
+ if (fIsString1 == expr_var_is_string(pVar2))
+ {
+ if (!fIsString1)
+ /* numbers are simple */
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ /* try a normal string compare. */
+ rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ {
+ if (!strcmp(pVar1->uVal.psz, pVar2->uVal.psz))
+ expr_var_assign_bool(pVar1, 1);
+ /* try convert and compare as number instead. */
+ else if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ /* ok, they really aren't equal. */
+ else
+ expr_var_assign_bool(pVar1, 0);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * If the type differs, there are now two options:
+ * 1. Convert the string to a valid number and compare the numbers.
+ * 2. Convert an empty string to a 'false' boolean value and compare
+ * numerically. This one is a bit questionable, so we don't try this.
+ */
+ /** @todo this needs to be redone, both because we're hiding alloc errors
+ * here but also because this should be controlled by a flag. */
+ if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ rc = expr_error(pThis, "Cannot compare strings and numbers");
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Not equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_not_equal(PEXPR pThis)
+{
+ EXPRRET rc = expr_op_equal(pThis);
+ if (rc >= kExprRet_Ok)
+ rc = expr_op_logical_not(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise AND.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_and(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i &= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise XOR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_xor(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i ^= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise OR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_or(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i |= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Logical AND.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_and(PEXPR pThis)
+{
+ bool fResult = false;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_bool(pThis, pVar1);
+ if ( rc == kExprRet_Ok
+ && pVar1->uVal.i != 0)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_bool(pThis, pVar2);
+ if (rc == kExprRet_Ok && pVar2->uVal.i != 0)
+ fResult = true;
+ }
+ expr_var_assign_bool(pVar1, fResult);
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Logical OR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_or(PEXPR pThis)
+{
+ bool fResult = false;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_bool(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ {
+ if (pVar1->uVal.i)
+ fResult = true;
+ else
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_bool(pThis, pVar2);
+ if (rc == kExprRet_Ok && pVar2->uVal.i != 0)
+ fResult = true;
+ }
+ }
+ expr_var_assign_bool(pVar1, fResult);
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Left parenthesis.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_left_parenthesis(PEXPR pThis)
+{
+ /*
+ * There should be a right parenthesis operator lined up for us now,
+ * eat it. If not found there is an inbalance.
+ */
+ EXPRRET rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if ( rc == kExprRet_Operator
+ && pThis->apOps[pThis->iOp]->szOp[0] == ')')
+ {
+ /* pop it and get another one which we can leave pending. */
+ pThis->iOp--;
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc >= kExprRet_Ok)
+ expr_unget_op(pThis);
+ }
+ else
+ rc = expr_error(pThis, "Missing ')'");
+
+ return rc;
+}
+
+
+/**
+ * Right parenthesis, dummy that's never actually called.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_right_parenthesis(PEXPR pThis)
+{
+ RT_NOREF_PV(pThis);
+ AssertFailed();
+ return kExprRet_Ok;
+}
+
+
+
+
+
+/**
+ * The operator table.
+ *
+ * This table is NOT ordered by precedence, but for linear search
+ * allowing for first match to return the correct operator. This
+ * means that || must come before |, or else | will match all.
+ */
+static const EXPROP g_aExprOps[] =
+{
+#define EXPR_OP(szOp, iPrecedence, cArgs, pfn) { szOp, sizeof(szOp) - 1, '\0', iPrecedence, cArgs, pfn }
+ /* Name, iPrecedence, cArgs, pfn */
+ EXPR_OP("defined", 90, 1, expr_op_defined),
+ EXPR_OP("exists", 90, 1, expr_op_exists),
+ EXPR_OP("bool", 90, 1, expr_op_bool),
+ EXPR_OP("num", 90, 1, expr_op_num),
+ EXPR_OP("strlen", 90, 1, expr_op_strlen),
+ EXPR_OP("str", 90, 1, expr_op_str),
+ EXPR_OP("+", 80, 1, expr_op_pluss),
+ EXPR_OP("-", 80, 1, expr_op_minus),
+ EXPR_OP("~", 80, 1, expr_op_bitwise_not),
+ EXPR_OP("*", 75, 2, expr_op_multiply),
+ EXPR_OP("/", 75, 2, expr_op_divide),
+ EXPR_OP("%", 75, 2, expr_op_modulus),
+ EXPR_OP("+", 70, 2, expr_op_add),
+ EXPR_OP("-", 70, 2, expr_op_sub),
+ EXPR_OP("<<", 65, 2, expr_op_shift_left),
+ EXPR_OP(">>", 65, 2, expr_op_shift_right),
+ EXPR_OP("<=", 60, 2, expr_op_less_or_equal_than),
+ EXPR_OP("<", 60, 2, expr_op_less_than),
+ EXPR_OP(">=", 60, 2, expr_op_greater_or_equal_than),
+ EXPR_OP(">", 60, 2, expr_op_greater_than),
+ EXPR_OP("vle", 60, 2, expr_op_ver_less_or_equal_than),
+ EXPR_OP("vlt", 60, 2, expr_op_ver_less_than),
+ EXPR_OP("vge", 60, 2, expr_op_ver_greater_or_equal_than),
+ EXPR_OP("vgt", 60, 2, expr_op_ver_greater_than),
+ EXPR_OP("==", 55, 2, expr_op_equal),
+ EXPR_OP("veq", 55, 2, expr_op_ver_equal),
+ EXPR_OP("!=", 55, 2, expr_op_not_equal),
+ EXPR_OP("vne", 55, 2, expr_op_ver_not_equal),
+ EXPR_OP("!", 80, 1, expr_op_logical_not),
+ EXPR_OP("^", 45, 2, expr_op_bitwise_xor),
+ EXPR_OP("&&", 35, 2, expr_op_logical_and),
+ EXPR_OP("&", 50, 2, expr_op_bitwise_and),
+ EXPR_OP("||", 30, 2, expr_op_logical_or),
+ EXPR_OP("|", 40, 2, expr_op_bitwise_or),
+ { "(", 1, ')', 10, 1, expr_op_left_parenthesis },
+ { ")", 1, '(', 10, 0, expr_op_right_parenthesis },
+ /* { "?", 1, ':', 5, 2, expr_op_question },
+ { ":", 1, '?', 5, 2, expr_op_colon }, -- too weird for now. */
+#undef EXPR_OP
+};
+
+/** Dummy end of expression fake. */
+static const EXPROP g_ExprEndOfExpOp =
+{
+ "", 0, '\0', 0, 0, NULL
+};
+
+
+/**
+ * Initializes the opcode character map if necessary.
+ */
+static void expr_map_init(void)
+{
+ unsigned i;
+ if (g_fExprInitializedMap)
+ return;
+
+ /*
+ * Initialize it.
+ */
+ for (i = 0; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
+ {
+ unsigned int ch = (unsigned int)g_aExprOps[i].szOp[0];
+ if (!g_abOpStartCharMap[ch])
+ {
+ g_abOpStartCharMap[ch] = (i << 2) | 1;
+ if (!RT_C_IS_ALPHA(ch))
+ g_abOpStartCharMap[ch] |= 2; /* Need no clear separation from operands. */
+ }
+ }
+
+ /* whitespace (assumes C-like locale because I'm lazy): */
+#define SET_WHITESPACE(a_ch) do { \
+ Assert(g_abOpStartCharMap[(unsigned char)(a_ch)] == 0); \
+ g_abOpStartCharMap[(unsigned char)(a_ch)] |= 2; \
+ } while (0)
+ SET_WHITESPACE(' ');
+ SET_WHITESPACE('\t');
+ SET_WHITESPACE('\n');
+ SET_WHITESPACE('\r');
+ SET_WHITESPACE('\v');
+ SET_WHITESPACE('\f');
+
+ g_fExprInitializedMap = 1;
+}
+
+
+/**
+ * Looks up a character in the map.
+ *
+ * @returns the value for that char, see g_abOpStartCharMap for details.
+ * @param ch The character.
+ */
+DECLINLINE(unsigned char) expr_map_get(char ch)
+{
+ return g_abOpStartCharMap[(unsigned char)ch];
+}
+
+
+/**
+ * Searches the operator table given a potential operator start char.
+ *
+ * @returns Pointer to the matching operator. NULL if not found.
+ * @param psz Pointer to what can be an operator.
+ * @param uchVal The expr_map_get value.
+ * @param fUnary Whether it must be an unary operator or not.
+ */
+static PCEXPROP expr_lookup_op(char const *psz, unsigned char uchVal, int fUnary)
+{
+ char ch = *psz;
+ unsigned i;
+ Assert((uchVal & 2) == (RT_C_IS_ALPHA(ch) ? 0 : 2));
+
+ for (i = uchVal >> 2; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
+ {
+ /* compare the string... */
+ if (g_aExprOps[i].szOp[0] != ch)
+ continue;
+ switch (g_aExprOps[i].cchOp)
+ {
+ case 1:
+ break;
+ case 2:
+ if (g_aExprOps[i].szOp[1] != psz[1])
+ continue;
+ break;
+ default:
+ if (strncmp(&g_aExprOps[i].szOp[1], psz + 1, g_aExprOps[i].cchOp - 1))
+ continue;
+ break;
+ }
+
+ /* ... and the operator type. */
+ if (fUnary == (g_aExprOps[i].cArgs == 1))
+ {
+ /* Check if we've got the needed operand separation: */
+ if ( (uchVal & 2)
+ || EXPR_IS_OP_SEPARATOR(psz[g_aExprOps[i].cchOp]))
+ {
+ /* got a match! */
+ return &g_aExprOps[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Ungets a binary operator.
+ *
+ * The operator is poped from the stack and put in the pending position.
+ *
+ * @param pThis The evaluator instance.
+ */
+static void expr_unget_op(PEXPR pThis)
+{
+ Assert(pThis->pPending == NULL);
+ Assert(pThis->iOp >= 0);
+
+ pThis->pPending = pThis->apOps[pThis->iOp];
+ pThis->apOps[pThis->iOp] = NULL;
+ pThis->iOp--;
+}
+
+
+
+/**
+ * Get the next token, it should be a binary operator, or the end of
+ * the expression, or a right parenthesis.
+ *
+ * The operator is pushed onto the stack and the status code indicates
+ * which of the two we found.
+ *
+ * @returns status code. Will grumble on failure.
+ * @retval kExprRet_EndOfExpr if we encountered the end of the expression.
+ * @retval kExprRet_Operator if we encountered a binary operator or right
+ * parenthesis. It's on the operator stack.
+ *
+ * @param pThis The evaluator instance.
+ */
+static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis)
+{
+ /*
+ * See if there is anything pending first.
+ */
+ PCEXPROP pOp = pThis->pPending;
+ if (pOp)
+ pThis->pPending = NULL;
+ else
+ {
+ /*
+ * Eat more of the expression.
+ */
+ char const *psz = pThis->psz;
+
+ /* spaces */
+ unsigned char uchVal;
+ char ch;
+ while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
+ psz++;
+
+ /* see what we've got. */
+ if (ch)
+ {
+ if (uchVal & 1)
+ pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
+ if (!pOp)
+ return expr_error(pThis, "Expected binary operator, found \"%.42s\"...", psz);
+ psz += pOp->cchOp;
+ }
+ else
+ pOp = &g_ExprEndOfExpOp;
+ pThis->psz = psz;
+ }
+
+ /*
+ * Push it.
+ */
+ if (pThis->iOp >= EXPR_MAX_OPERATORS - 1)
+ return expr_error(pThis, "Operator stack overflow");
+ pThis->apOps[++pThis->iOp] = pOp;
+
+ return pOp->iPrecedence
+ ? kExprRet_Operator
+ : kExprRet_EndOfExpr;
+}
+
+
+
+/**
+ * Get the next token, it should be an unary operator or an operand.
+ *
+ * This will fail if encountering the end of the expression since
+ * it is implied that there should be something more.
+ *
+ * The token is pushed onto the respective stack and the status code
+ * indicates which it is.
+ *
+ * @returns status code. On failure we'll be done bitching already.
+ * @retval kExprRet_Operator if we encountered an unary operator.
+ * It's on the operator stack.
+ * @retval kExprRet_Operand if we encountered an operand operator.
+ * It's on the operand stack.
+ *
+ * @param pThis The evaluator instance.
+ */
+static EXPRRET expr_get_unary_or_operand(PEXPR pThis)
+{
+ EXPRRET rc;
+ unsigned char uchVal;
+ PCEXPROP pOp;
+ char const *psz = pThis->psz;
+ char ch;
+
+ /*
+ * Eat white space and make sure there is something after it.
+ */
+ while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
+ psz++;
+ if (ch == '\0')
+ return expr_error(pThis, "Unexpected end of expression");
+
+ /*
+ * Is it an operator?
+ */
+ pOp = NULL;
+ if (uchVal & 1)
+ pOp = expr_lookup_op(psz, uchVal, 1 /* fUnary */);
+ if (pOp)
+ {
+ /*
+ * Push the operator onto the stack.
+ */
+ if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
+ {
+ pThis->apOps[++pThis->iOp] = pOp;
+ rc = kExprRet_Operator;
+ }
+ else
+ rc = expr_error(pThis, "Operator stack overflow");
+ psz += pOp->cchOp;
+ }
+ else if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
+ {
+ /*
+ * It's an operand. Figure out where it ends and
+ * push it onto the stack.
+ */
+ const char *pszStart;
+
+ rc = kExprRet_Ok;
+ if (ch == '"')
+ {
+ pszStart = ++psz;
+ while ((ch = *psz) != '\0' && ch != '"')
+ psz++;
+ rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedString);
+ if (ch != '\0')
+ psz++;
+ }
+ else if (ch == '\'')
+ {
+ pszStart = ++psz;
+ while ((ch = *psz) != '\0' && ch != '\'')
+ psz++;
+ rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart,
+ kExprVar_QuotedSimpleString);
+ if (ch != '\0')
+ psz++;
+ }
+ else
+ {
+ unsigned cPars = 0;
+ pszStart = psz;
+ while ((ch = *psz) != '\0')
+ {
+ /* ${asdf} needs special handling. */
+ if ( ch == '$'
+ && psz[1] == '{')
+ {
+ psz++;
+ if (cPars < EXPR_MAX_VAR_RECURSION)
+ ++cPars;
+ else
+ {
+ rc = expr_error(pThis, "Too deep nesting of variable expansions");
+ break;
+ }
+ }
+ else if (ch == '}')
+ {
+ if (cPars > 0)
+ cPars--;
+ }
+ else if (cPars == 0)
+ {
+ uchVal = expr_map_get(ch);
+ if (uchVal == 0)
+ { /*likely*/ }
+ else if ((uchVal & 3) == 2 /*isspace*/)
+ break;
+ else if ( (uchVal & 1)
+ && psz != pszStart /* not at the start */
+ && ( (uchVal & 2) /* operator without separator needs */
+ || EXPR_IS_OP_SEPARATOR_NO_SPACE(psz[-1])))
+ {
+ pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
+ if (pOp)
+ break;
+ }
+ }
+
+ /* next */
+ psz++;
+ }
+
+ if (rc == kExprRet_Ok)
+ rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_String);
+ }
+ }
+ else
+ rc = expr_error(pThis, "Operand stack overflow");
+ pThis->psz = psz;
+
+ return rc;
+}
+
+
+/**
+ * Evaluates the current expression.
+ *
+ * @returns status code.
+ *
+ * @param pThis The instance.
+ */
+static EXPRRET expr_eval(PEXPR pThis)
+{
+ EXPRRET rc;
+ PCEXPROP pOp;
+
+ /*
+ * The main loop.
+ */
+ for (;;)
+ {
+ /*
+ * Eat unary operators until we hit an operand.
+ */
+ do
+ rc = expr_get_unary_or_operand(pThis);
+ while (rc == kExprRet_Operator);
+ if (rc < kExprRet_Ok)
+ break;
+
+ /*
+ * Look for a binary operator, right parenthesis or end of expression.
+ */
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ expr_unget_op(pThis);
+
+ /*
+ * Pop operators and apply them.
+ *
+ * Parenthesis will be handed via precedence, where the left parenthesis
+ * will go pop the right one and make another operator pending.
+ */
+ while ( pThis->iOp >= 0
+ && pThis->apOps[pThis->iOp]->iPrecedence >= pThis->pPending->iPrecedence)
+ {
+ pOp = pThis->apOps[pThis->iOp--];
+ Assert(pThis->iVar + 1 >= pOp->cArgs);
+ rc = pOp->pfn(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ }
+ if (rc < kExprRet_Ok)
+ break;
+
+ /*
+ * Get the next binary operator or end of expression.
+ * There should be no right parenthesis here.
+ */
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ pOp = pThis->apOps[pThis->iOp];
+ if (!pOp->iPrecedence)
+ break; /* end of expression */
+ if (!pOp->cArgs)
+ {
+ rc = expr_error(pThis, "Unexpected \"%s\"", pOp->szOp);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destroys the given instance.
+ *
+ * @param pThis The instance to destroy.
+ */
+static void expr_destroy(PEXPR pThis)
+{
+ while (pThis->iVar >= 0)
+ {
+ expr_var_delete(pThis->aVars);
+ pThis->iVar--;
+ }
+ RTMemTmpFree(pThis);
+}
+
+
+/**
+ * Instantiates an expression evaluator.
+ *
+ * @returns The instance.
+ */
+static PEXPR expr_create(RTEXPREVALINT *pThis, const char *pch, size_t cch, PRTERRINFO pErrInfo)
+{
+ cch = RTStrNLen(pch, cch);
+
+ PEXPR pExpr = (PEXPR)RTMemTmpAllocZ(sizeof(*pExpr) + cch + 1);
+ if (pExpr)
+ {
+ pExpr->psz = pExpr->pszExpr = (char *)memcpy(pExpr + 1, pch, cch);
+ pExpr->pErrInfo = pErrInfo;
+ pExpr->pEvaluator = pThis;
+ pExpr->pPending = NULL;
+ pExpr->iVar = -1;
+ pExpr->iOp = -1;
+
+ expr_map_init();
+ }
+ return pExpr;
+}
+
+
+
+/*********************************************************************************************************************************
+* API *
+*********************************************************************************************************************************/
+
+/** @callback_method_impl{PFNRTEXPREVALQUERYVARIABLE, Stub} */
+static DECLCALLBACK(int) rtExprEvalDummyQueryVariable(const char *pchName, size_t cchName, void *pvUser, char **ppszValue)
+{
+ RT_NOREF(pchName, cchName, pvUser);
+ if (ppszValue)
+ *ppszValue = NULL;
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTExprEvalCreate(PRTEXPREVAL phEval, uint64_t fFlags, const char *pszName,
+ void *pvUser, PFNRTEXPREVALQUERYVARIABLE pfnQueryVariable)
+{
+ AssertPtrReturn(phEval, VERR_INVALID_POINTER);
+ *phEval = NULL;
+ AssertPtrNullReturn(pfnQueryVariable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~0), VERR_INVALID_FLAGS);
+
+ char *pszNameCopy = RTStrDup(pszName);
+ if (pszNameCopy)
+ {
+ RTEXPREVALINT *pThis = (RTEXPREVALINT *)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->u32Magic = RTEXPREVAL_MAGIC;
+ pThis->cRefs = 1;
+ pThis->fFlags = fFlags;
+ pThis->pszName = pszNameCopy;
+ pThis->pvUser = pvUser;
+ pThis->pfnQueryVariable = pfnQueryVariable ? pfnQueryVariable : rtExprEvalDummyQueryVariable;
+ *phEval = pThis;
+ return VINF_SUCCESS;
+
+ }
+
+ RTStrFree(pszNameCopy);
+ return VERR_NO_MEMORY;
+ }
+ return VERR_NO_STR_MEMORY;
+}
+
+
+RTDECL(uint32_t) RTExprEvalRetain(RTEXPREVAL hEval)
+{
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, UINT32_MAX);
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < 512);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTExprEvalRelease(RTEXPREVAL hEval)
+{
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, UINT32_MAX);
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 512);
+ if (cRefs == 0)
+ {
+ pThis->u32Magic = ~RTEXPREVAL_MAGIC;
+ if (pThis->pszName)
+ {
+ RTStrFree(pThis->pszName);
+ pThis->pszName = NULL;
+ }
+ RTMemFree(pThis);
+ return 0;
+ }
+ return cRefs;
+}
+
+
+RTDECL(int) RTExprEvalToBool(RTEXPREVAL hEval, const char *pch, size_t cch, bool *pfResult, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(pfResult, VERR_INVALID_POINTER);
+ *pfResult = false;
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Instantiate the expression evaluator and let it have a go at it.
+ */
+ int rc;
+ PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo);
+ if (pExpr)
+ {
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to boolean and
+ * set our return value accordingly.
+ */
+ if ( expr_var_make_bool(pExpr, &pExpr->aVars[0]) == kExprRet_Ok
+ && pExpr->aVars[0].uVal.i)
+ *pfResult = true;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_PARSE_ERROR; /** @todo better errors? */
+ expr_destroy(pExpr);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTExprEvalToInteger(RTEXPREVAL hEval, const char *pch, size_t cch, int64_t *piResult, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(piResult, VERR_INVALID_POINTER);
+ *piResult = INT64_MAX;
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Instantiate the expression evaluator and let it have a go at it.
+ */
+ int rc;
+ PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo);
+ if (pExpr)
+ {
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to boolean and
+ * set our return value accordingly.
+ */
+ PEXPRVAR pVar = &pExpr->aVars[0];
+ EXPRRET rcExpr = expr_var_make_num(pExpr, pVar);
+ if (rcExpr >= kExprRet_Ok)
+ {
+ *piResult = pVar->uVal.i;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_PARSE_ERROR; /** @todo better error! */
+ }
+ else
+ rc = VERR_PARSE_ERROR; /** @todo better errors? */
+ expr_destroy(pExpr);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTExprEvalToString(RTEXPREVAL hEval, const char *pch, size_t cch, char **ppszResult, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(ppszResult, VERR_INVALID_POINTER);
+ *ppszResult = NULL;
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Instantiate the expression evaluator and let it have a go at it.
+ */
+ int rc;
+ PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo);
+ if (pExpr)
+ {
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to a string
+ * and copy it out the variable buffer.
+ */
+ PEXPRVAR pVar = &pExpr->aVars[0];
+ if (expr_var_make_simple_string(pExpr, pVar) == kExprRet_Ok)
+ rc = RTStrDupEx(ppszResult, pVar->uVal.psz);
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ else
+ rc = VERR_PARSE_ERROR;
+ expr_destroy(pExpr);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/misc/getopt.cpp b/src/VBox/Runtime/common/misc/getopt.cpp
new file mode 100644
index 00000000..1a079546
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/getopt.cpp
@@ -0,0 +1,922 @@
+/* $Id: getopt.cpp $ */
+/** @file
+ * IPRT - Command Line Parsing
+ */
+
+/*
+ * 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/cidr.h>
+#include <iprt/net.h> /* must come before getopt.h */
+#include <iprt/getopt.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef IN_RT_STATIC /* We don't need full unicode case insensitive if we ASSUME basic latin only. */
+# define RTStrICmp RTStrICmpAscii
+# define RTStrNICmp RTStrNICmpAscii
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Standard options that gets included unless RTGETOPTINIT_FLAGS_NO_STD_OPTS is
+ * set.
+ */
+static RTGETOPTDEF const g_aStdOptions[] =
+{
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "-help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "-version", 'V', RTGETOPT_REQ_NOTHING },
+};
+/** The index of --help in g_aStdOptions. Used for some trickery. */
+#define RTGETOPT_STD_OPTIONS_HELP_IDX 0
+
+
+
+RTDECL(int) RTGetOptInit(PRTGETOPTSTATE pState, int argc, char **argv,
+ PCRTGETOPTDEF paOptions, size_t cOptions,
+ int iFirst, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~(RTGETOPTINIT_FLAGS_OPTS_FIRST | RTGETOPTINIT_FLAGS_NO_STD_OPTS)), VERR_INVALID_PARAMETER);
+
+ pState->argv = argv;
+ pState->argc = argc;
+ pState->paOptions = paOptions;
+ pState->cOptions = cOptions;
+ pState->iNext = iFirst;
+ pState->pszNextShort = NULL;
+ pState->pDef = NULL;
+ pState->uIndex = UINT32_MAX;
+ pState->fFlags = fFlags;
+ pState->cNonOptions = 0;
+
+#ifdef RT_STRICT
+ /* validate the options. */
+ for (size_t i = 0; i < cOptions; i++)
+ {
+ Assert(!(paOptions[i].fFlags & ~RTGETOPT_VALID_MASK));
+ Assert( !(paOptions[i].fFlags & (RTGETOPT_FLAG_INDEX_DEF_MASK | RTGETOPT_FLAG_INDEX_DEF_DASH))
+ || (paOptions[i].fFlags & RTGETOPT_FLAG_INDEX) );
+ Assert(paOptions[i].iShort > 0);
+ Assert(paOptions[i].iShort != VINF_GETOPT_NOT_OPTION);
+ Assert(paOptions[i].iShort != '-');
+ if (paOptions[i].fFlags & RTGETOPT_FLAG_ICASE)
+ {
+ const char *psz = paOptions[i].pszLong;
+ unsigned char ch;
+ while ((ch = *psz++) != '\0')
+ Assert(ch <= 0x7f); /* ASSUMPTION that we can use RTStrICmpAscii and RTStrNICmpAscii. */
+ }
+ }
+#endif
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTGetOptInit);
+
+#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
+
+/**
+ * Converts an stringified IPv4 address into the RTNETADDRIPV4 representation.
+ *
+ * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
+ * failure.
+ *
+ * @param pszValue The value to convert.
+ * @param pAddr Where to store the result.
+ */
+static int rtgetoptConvertIPv4Addr(const char *pszValue, PRTNETADDRIPV4 pAddr)
+{
+ if (RT_FAILURE(RTNetStrToIPv4Addr(pszValue, pAddr)))
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts an stringified Ethernet MAC address into the RTMAC representation.
+ *
+ * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
+ * failure.
+ *
+ * @param pszValue The value to convert.
+ * @param pAddr Where to store the result.
+ */
+static int rtgetoptConvertMacAddr(const char *pszValue, PRTMAC pAddr)
+{
+
+ int rc = RTNetStrToMacAddr(pszValue, pAddr);
+ if (RT_FAILURE(rc))
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
+
+/**
+ * Searches for a long option.
+ *
+ * @returns Pointer to a matching option.
+ * @param pszOption The alleged long option.
+ * @param paOptions Option array.
+ * @param cOptions Number of items in the array.
+ * @param fFlags Init flags.
+ */
+static PCRTGETOPTDEF rtGetOptSearchLong(const char *pszOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
+{
+ PCRTGETOPTDEF pOpt = paOptions;
+ while (cOptions-- > 0)
+ {
+ if (pOpt->pszLong)
+ {
+ uint32_t const fOptFlags = pOpt->fFlags;
+ if ((fOptFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
+ {
+ /*
+ * A value is required with the argument. We're trying to be
+ * understanding here and will permit any of the following:
+ * --long12:value, --long12=value, --long12 value,
+ * --long:value, --long=value, --long value,
+ *
+ * If the option is index, then all trailing chars must be
+ * digits. For error reporting reasons we also match where
+ * there is no index.
+ */
+ size_t cchLong = strlen(pOpt->pszLong);
+ if ( !strncmp(pszOption, pOpt->pszLong, cchLong)
+ || ( (fOptFlags & RTGETOPT_FLAG_ICASE)
+ && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong)))
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszOption[cchLong] == '-'
+ && RT_C_IS_DIGIT(pszOption[cchLong + 1])) /* given "--long" we match "--long-1" but not "--long-". */
+ cchLong++;
+ if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ while (RT_C_IS_DIGIT(pszOption[cchLong]))
+ cchLong++;
+ if ( pszOption[cchLong] == '\0'
+ || pszOption[cchLong] == ':'
+ || pszOption[cchLong] == '=')
+ return pOpt;
+ }
+ }
+ else if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ {
+ /*
+ * The option takes an index but no value.
+ * As above, we also match where there is no index.
+ */
+ size_t cchLong = strlen(pOpt->pszLong);
+ if ( !strncmp(pszOption, pOpt->pszLong, cchLong)
+ || ( (fOptFlags & RTGETOPT_FLAG_ICASE)
+ && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong)))
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszOption[cchLong] == '-'
+ && RT_C_IS_DIGIT(pszOption[cchLong + 1]))
+ cchLong++;
+ while (RT_C_IS_DIGIT(pszOption[cchLong]))
+ cchLong++;
+ if (pszOption[cchLong] == '\0')
+ return pOpt;
+ }
+ }
+ else if ( !strcmp(pszOption, pOpt->pszLong)
+ || ( (fOptFlags & RTGETOPT_FLAG_ICASE)
+ && !RTStrICmp(pszOption, pOpt->pszLong)))
+ return pOpt;
+ }
+ pOpt++;
+ }
+
+ if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
+ if ( !strcmp(pszOption, g_aStdOptions[i].pszLong)
+ || ( g_aStdOptions[i].fFlags & RTGETOPT_FLAG_ICASE
+ && !RTStrICmp(pszOption, g_aStdOptions[i].pszLong)))
+ return &g_aStdOptions[i];
+
+ return NULL;
+}
+
+
+/**
+ * Searches for a matching short option.
+ *
+ * @returns Pointer to a matching option.
+ * @param chOption The option char.
+ * @param paOptions Option array.
+ * @param cOptions Number of items in the array.
+ * @param fFlags Init flags.
+ */
+static PCRTGETOPTDEF rtGetOptSearchShort(int chOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
+{
+ PCRTGETOPTDEF pOpt = paOptions;
+ while (cOptions-- > 0)
+ {
+ if (pOpt->iShort == chOption)
+ return pOpt;
+ pOpt++;
+ }
+
+ if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
+ if (g_aStdOptions[i].iShort == chOption)
+ return &g_aStdOptions[i];
+ if (chOption == '?')
+ return &g_aStdOptions[RTGETOPT_STD_OPTIONS_HELP_IDX];
+ }
+ return NULL;
+}
+
+
+/**
+ * Value string -> Value union.
+ *
+ * @returns IPRT status code.
+ * @param fFlags The value flags.
+ * @param pszValue The value string.
+ * @param pValueUnion Where to return the processed value.
+ */
+static int rtGetOptProcessValue(uint32_t fFlags, const char *pszValue, PRTGETOPTUNION pValueUnion)
+{
+ /*
+ * Transform into a option value as requested.
+ * If decimal conversion fails, we'll check for "0x<xdigit>" and
+ * try a 16 based conversion. We will not interpret any of the
+ * generic ints as octals.
+ */
+ uint32_t const fSwitchValue = fFlags & ( RTGETOPT_REQ_MASK
+ | RTGETOPT_FLAG_HEX
+ | RTGETOPT_FLAG_DEC
+ | RTGETOPT_FLAG_OCT);
+ switch (fSwitchValue)
+ {
+ case RTGETOPT_REQ_STRING:
+ pValueUnion->psz = pszValue;
+ break;
+
+ case RTGETOPT_REQ_BOOL:
+ if ( !RTStrICmp(pszValue, "true")
+ || !RTStrICmp(pszValue, "t")
+ || !RTStrICmp(pszValue, "yes")
+ || !RTStrICmp(pszValue, "y")
+ || !RTStrICmp(pszValue, "enabled")
+ || !RTStrICmp(pszValue, "enable")
+ || !RTStrICmp(pszValue, "en")
+ || !RTStrICmp(pszValue, "e")
+ || !RTStrICmp(pszValue, "on")
+ || !RTStrCmp(pszValue, "1")
+ )
+ pValueUnion->f = true;
+ else if ( !RTStrICmp(pszValue, "false")
+ || !RTStrICmp(pszValue, "f")
+ || !RTStrICmp(pszValue, "no")
+ || !RTStrICmp(pszValue, "n")
+ || !RTStrICmp(pszValue, "disabled")
+ || !RTStrICmp(pszValue, "disable")
+ || !RTStrICmp(pszValue, "dis")
+ || !RTStrICmp(pszValue, "d")
+ || !RTStrICmp(pszValue, "off")
+ || !RTStrCmp(pszValue, "0")
+ )
+ pValueUnion->f = false;
+ else
+ {
+ pValueUnion->psz = pszValue;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ break;
+
+ case RTGETOPT_REQ_BOOL_ONOFF:
+ if (!RTStrICmp(pszValue, "on"))
+ pValueUnion->f = true;
+ else if (!RTStrICmp(pszValue, "off"))
+ pValueUnion->f = false;
+ else
+ {
+ pValueUnion->psz = pszValue;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ break;
+
+#define MY_INT_CASE(req, type, memb, convfn) \
+ case req: \
+ { \
+ type Value; \
+ if ( convfn(pszValue, 10, &Value) != VINF_SUCCESS \
+ && ( pszValue[0] != '0' \
+ || (pszValue[1] != 'x' && pszValue[1] != 'X') \
+ || !RT_C_IS_XDIGIT(pszValue[2]) \
+ || convfn(pszValue, 16, &Value) != VINF_SUCCESS ) ) \
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
+ pValueUnion->memb = Value; \
+ break; \
+ }
+#define MY_BASE_INT_CASE(req, type, memb, convfn, base) \
+ case req: \
+ { \
+ type Value; \
+ if (convfn(pszValue, base, &Value) != VINF_SUCCESS) \
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
+ pValueUnion->memb = Value; \
+ break; \
+ }
+
+ MY_INT_CASE(RTGETOPT_REQ_INT8, int8_t, i8, RTStrToInt8Full)
+ MY_INT_CASE(RTGETOPT_REQ_INT16, int16_t, i16, RTStrToInt16Full)
+ MY_INT_CASE(RTGETOPT_REQ_INT32, int32_t, i32, RTStrToInt32Full)
+ MY_INT_CASE(RTGETOPT_REQ_INT64, int64_t, i64, RTStrToInt64Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT8, uint8_t, u8, RTStrToUInt8Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT16, uint16_t, u16, RTStrToUInt16Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT32, uint32_t, u32, RTStrToUInt32Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT64, uint64_t, u64, RTStrToUInt64Full)
+
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_HEX, int8_t, i8, RTStrToInt8Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_HEX, int16_t, i16, RTStrToInt16Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_HEX, int32_t, i32, RTStrToInt32Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_HEX, int64_t, i64, RTStrToInt64Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_HEX, uint8_t, u8, RTStrToUInt8Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_HEX, uint16_t, u16, RTStrToUInt16Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX, uint32_t, u32, RTStrToUInt32Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX, uint64_t, u64, RTStrToUInt64Full, 16)
+
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_DEC, int8_t, i8, RTStrToInt8Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_DEC, int16_t, i16, RTStrToInt16Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_DEC, int32_t, i32, RTStrToInt32Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_DEC, int64_t, i64, RTStrToInt64Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_DEC, uint8_t, u8, RTStrToUInt8Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_DEC, uint16_t, u16, RTStrToUInt16Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_DEC, uint32_t, u32, RTStrToUInt32Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_DEC, uint64_t, u64, RTStrToUInt64Full, 10)
+
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_OCT, int8_t, i8, RTStrToInt8Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_OCT, int16_t, i16, RTStrToInt16Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_OCT, int32_t, i32, RTStrToInt32Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_OCT, int64_t, i64, RTStrToInt64Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_OCT, uint8_t, u8, RTStrToUInt8Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_OCT, uint16_t, u16, RTStrToUInt16Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT, uint32_t, u32, RTStrToUInt32Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_OCT, uint64_t, u64, RTStrToUInt64Full, 8)
+
+#undef MY_INT_CASE
+#undef MY_BASE_INT_CASE
+
+#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
+
+ case RTGETOPT_REQ_IPV4ADDR:
+ {
+ RTNETADDRIPV4 Addr;
+ if (rtgetoptConvertIPv4Addr(pszValue, &Addr) != VINF_SUCCESS)
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->IPv4Addr = Addr;
+ break;
+ }
+
+ case RTGETOPT_REQ_IPV4CIDR:
+ {
+ RTNETADDRIPV4 network;
+ RTNETADDRIPV4 netmask;
+ if (RT_FAILURE(RTCidrStrToIPv4(pszValue, &network, &netmask)))
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->CidrIPv4.IPv4Network.u = network.u;
+ pValueUnion->CidrIPv4.IPv4Netmask.u = netmask.u;
+ break;
+ }
+
+ case RTGETOPT_REQ_MACADDR:
+ {
+ RTMAC Addr;
+ if (rtgetoptConvertMacAddr(pszValue, &Addr) != VINF_SUCCESS)
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->MacAddr = Addr;
+ break;
+ }
+
+#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
+
+ case RTGETOPT_REQ_UUID:
+ {
+ RTUUID Uuid;
+ if (RTUuidFromStr(&Uuid, pszValue) != VINF_SUCCESS)
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->Uuid = Uuid;
+ break;
+ }
+
+#define MY_INT_PAIR_CASE(a_fReqValue, a_fReqValueOptional, a_Type, a_MemberPrefix, a_fnConv, a_ConvBase, a_DefaultValue) \
+ case a_fReqValue: \
+ case a_fReqValueOptional: \
+ { \
+ /* First value: */ \
+ a_Type Value1; \
+ char *pszNext = NULL; \
+ unsigned uBase = pszValue[0] == '0' \
+ && (pszValue[1] == 'x' || pszValue[1] == 'X') \
+ && RT_C_IS_XDIGIT(pszValue[2]) \
+ ? 16 : a_ConvBase; \
+ int rc = a_fnConv(pszValue, &pszNext, uBase, &Value1); \
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES) \
+ { \
+ /* The second value, could be optional: */ \
+ a_Type Value2 = a_DefaultValue; \
+ pszValue = pszNext;\
+ if (pszValue) \
+ { \
+ while (RT_C_IS_BLANK(*pszValue)) \
+ pszValue++; \
+ if (*pszValue == ':' || *pszValue == '/' || *pszValue == '|') \
+ do pszValue++; \
+ while (RT_C_IS_BLANK(*pszValue)); \
+ if (pszValue != pszNext) \
+ { \
+ uBase = pszValue[0] == '0' \
+ && (pszValue[1] == 'x' || pszValue[1] == 'X') \
+ && RT_C_IS_XDIGIT(pszValue[2]) \
+ ? 16 : a_ConvBase; \
+ rc = a_fnConv(pszValue, &pszNext, uBase, &Value2); \
+ if (rc == VINF_SUCCESS) \
+ { /* likely */ } \
+ else \
+ AssertMsgFailedReturn(("z rc=%Rrc: '%s' '%s' uBase=%d\n", rc, pszValue, pszNext, uBase), \
+ VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
+ } \
+ else if (fSwitchValue != (a_fReqValueOptional)) \
+ AssertMsgFailedReturn(("x\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
+ } \
+ else if (fSwitchValue != (a_fReqValueOptional)) \
+ AssertMsgFailedReturn(("y\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
+ pValueUnion->a_MemberPrefix##Second = Value2; \
+ pValueUnion->a_MemberPrefix##First = Value1; \
+ break; \
+ } \
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
+ }
+
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 16, UINT32_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 8, UINT32_MAX)
+
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 16, UINT64_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 8, UINT64_MAX)
+
+ default:
+ AssertMsgFailed(("f=%#x\n", fFlags));
+ return VERR_INTERNAL_ERROR;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Moves one argv option entries.
+ *
+ * @param papszTo Destination.
+ * @param papszFrom Source.
+ */
+static void rtGetOptMoveArgvEntries(char **papszTo, char **papszFrom)
+{
+ if (papszTo != papszFrom)
+ {
+ Assert((uintptr_t)papszTo < (uintptr_t)papszFrom);
+ char * const pszMoved = papszFrom[0];
+ memmove(&papszTo[1], &papszTo[0], (uintptr_t)papszFrom - (uintptr_t)papszTo);
+ papszTo[0] = pszMoved;
+ }
+}
+
+
+RTDECL(int) RTGetOpt(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion)
+{
+ /*
+ * Reset the variables kept in state.
+ */
+ pState->pDef = NULL;
+ pState->uIndex = UINT32_MAX;
+
+ /*
+ * Make sure the union is completely cleared out, whatever happens below.
+ */
+ pValueUnion->u64 = 0;
+ pValueUnion->pDef = NULL;
+
+ /*
+ * The next option.
+ */
+ bool fShort;
+ int iThis;
+ const char *pszArgThis;
+ PCRTGETOPTDEF pOpt;
+
+ if (pState->pszNextShort)
+ {
+ /*
+ * We've got short options left over from the previous call.
+ */
+ pOpt = rtGetOptSearchShort(*pState->pszNextShort, pState->paOptions, pState->cOptions, pState->fFlags);
+ if (!pOpt)
+ {
+ pValueUnion->psz = pState->pszNextShort;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ pState->pszNextShort++;
+ pszArgThis = pState->pszNextShort - 2;
+ iThis = pState->iNext;
+ fShort = true;
+ }
+ else
+ {
+ /*
+ * Pop off the next argument. Sorting options and dealing with the
+ * dash-dash makes this a little extra complicated.
+ */
+ for (;;)
+ {
+ if (pState->iNext >= pState->argc)
+ return 0;
+
+ if (pState->cNonOptions)
+ {
+ if (pState->cNonOptions == INT32_MAX)
+ {
+ pValueUnion->psz = pState->argv[pState->iNext++];
+ return VINF_GETOPT_NOT_OPTION;
+ }
+
+ if (pState->iNext + pState->cNonOptions >= pState->argc)
+ {
+ pState->cNonOptions = INT32_MAX;
+ continue;
+ }
+ }
+
+ iThis = pState->iNext++;
+ pszArgThis = pState->argv[iThis + pState->cNonOptions];
+
+ /*
+ * Do a long option search first and then a short option one.
+ * This way we can make sure single dash long options doesn't
+ * get mixed up with short ones.
+ */
+ pOpt = rtGetOptSearchLong(pszArgThis, pState->paOptions, pState->cOptions, pState->fFlags);
+ if ( !pOpt
+ && pszArgThis[0] == '-'
+ && pszArgThis[1] != '-'
+ && pszArgThis[1] != '\0')
+ {
+ pOpt = rtGetOptSearchShort(pszArgThis[1], pState->paOptions, pState->cOptions, pState->fFlags);
+ fShort = pOpt != NULL;
+ }
+ else
+ fShort = false;
+
+ /* Look for dash-dash. */
+ if (!pOpt && !strcmp(pszArgThis, "--"))
+ {
+ rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
+ pState->cNonOptions = INT32_MAX;
+ continue;
+ }
+
+ /* Options first hacks. */
+ if (pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST)
+ {
+ if (pOpt)
+ rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
+ else if (*pszArgThis == '-')
+ {
+ pValueUnion->psz = pszArgThis;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ else
+ {
+ /* not an option, add it to the non-options and try again. */
+ pState->iNext--;
+ pState->cNonOptions++;
+
+ /* Switch to returning non-options if we've reached the end. */
+ if (pState->iNext + pState->cNonOptions >= pState->argc)
+ pState->cNonOptions = INT32_MAX;
+ continue;
+ }
+ }
+
+ /* done */
+ break;
+ }
+ }
+
+ if (pOpt)
+ {
+ pValueUnion->pDef = pOpt; /* in case of no value or error. */
+
+ uint32_t const fOptFlags = pOpt->fFlags;
+ if ((fOptFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
+ {
+ /*
+ * Find the argument value.
+ *
+ * A value is required with the argument. We're trying to be
+ * understanding here and will permit any of the following:
+ * -svalue, -s value, -s:value and -s=value
+ * (Ditto for long options.)
+ */
+ const char *pszValue;
+ if (fShort)
+ {
+ if (pszArgThis[2] == '\0')
+ {
+ if (iThis + 1 >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ pszValue = pState->argv[iThis + pState->cNonOptions + 1];
+ rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
+ pState->iNext++;
+ }
+ else /* same argument. */
+ pszValue = &pszArgThis[2 + (pszArgThis[2] == ':' || pszArgThis[2] == '=')];
+ if (pState->pszNextShort)
+ {
+ pState->pszNextShort = NULL;
+ pState->iNext++;
+ }
+ }
+ else
+ {
+ size_t cchLong = strlen(pOpt->pszLong);
+ if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ {
+ if ( pszArgThis[cchLong] != '\0'
+ || (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszArgThis[cchLong] == '-')
+ cchLong++;
+
+ uint32_t uIndex;
+ char *pszRet = NULL;
+ int rc = RTStrToUInt32Ex(&pszArgThis[cchLong], &pszRet, 10, &uIndex);
+ if ( rc == VERR_NO_DIGITS
+ && (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
+ {
+ uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
+ rc = pszRet[0] == '\0' ? VINF_SUCCESS : VWRN_TRAILING_CHARS;
+ }
+ if (rc == VWRN_TRAILING_CHARS)
+ {
+ if ( pszRet[0] != ':'
+ && pszRet[0] != '=')
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pState->uIndex = uIndex;
+ pszValue = pszRet + 1;
+ }
+ else if (rc == VINF_SUCCESS)
+ {
+ if (iThis + 1 + pState->cNonOptions >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ pState->uIndex = uIndex;
+ pszValue = pState->argv[iThis + pState->cNonOptions + 1];
+ rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
+ pState->iNext++;
+ }
+ else
+ AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
+ }
+ else
+ return VERR_GETOPT_INDEX_MISSING;
+ }
+ else
+ {
+ if (pszArgThis[cchLong] == '\0')
+ {
+ if (iThis + 1 + pState->cNonOptions >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ pszValue = pState->argv[iThis + pState->cNonOptions + 1];
+ rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
+ pState->iNext++;
+ }
+ else /* same argument. */
+ pszValue = &pszArgThis[cchLong + 1];
+ }
+ }
+
+ /*
+ * Set up the ValueUnion.
+ */
+ int rc = rtGetOptProcessValue(fOptFlags, pszValue, pValueUnion);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else if (fShort)
+ {
+ /*
+ * Deal with "compressed" short option lists, correcting the next
+ * state variables for the start and end cases.
+ */
+ if (pszArgThis[2])
+ {
+ if (!pState->pszNextShort)
+ {
+ /* start */
+ pState->pszNextShort = &pszArgThis[2];
+ pState->iNext--;
+ }
+ }
+ else if (pState->pszNextShort)
+ {
+ /* end */
+ pState->pszNextShort = NULL;
+ pState->iNext++;
+ }
+ }
+ else if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ {
+ size_t cchLong = strlen(pOpt->pszLong);
+ uint32_t uIndex;
+ if (pszArgThis[cchLong] != '\0')
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszArgThis[cchLong] == '-')
+ cchLong++;
+ if (RTStrToUInt32Full(&pszArgThis[cchLong], 10, &uIndex) == VINF_SUCCESS)
+ pState->uIndex = uIndex;
+ else
+ AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
+ }
+ else if (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK)
+ uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
+ else
+ return VERR_GETOPT_INDEX_MISSING;
+ }
+
+ pState->pDef = pOpt;
+ return pOpt->iShort;
+ }
+
+ /*
+ * Not a known option argument. If it starts with a switch char (-) we'll
+ * fail with unknown option, and if it doesn't we'll return it as a non-option.
+ */
+ if (*pszArgThis == '-')
+ {
+ pValueUnion->psz = pszArgThis;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+
+ pValueUnion->psz = pszArgThis;
+ return VINF_GETOPT_NOT_OPTION;
+}
+RT_EXPORT_SYMBOL(RTGetOpt);
+
+
+RTDECL(int) RTGetOptFetchValue(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ PCRTGETOPTDEF pOpt = pState->pDef;
+ AssertReturn(!(fFlags & ~RTGETOPT_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn((fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING, VERR_INVALID_PARAMETER);
+
+ /*
+ * Make sure the union is completely cleared out, whatever happens below.
+ */
+ pValueUnion->u64 = 0;
+ pValueUnion->pDef = NULL;
+
+ /*
+ * Pop off the next argument and convert it into a value union.
+ */
+ if (pState->iNext >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ int iThis = pState->iNext++;
+ const char *pszValue = pState->argv[iThis + (pState->cNonOptions != INT32_MAX ? pState->cNonOptions : 0)];
+ pValueUnion->pDef = pOpt; /* in case of no value or error. */
+
+ if (pState->cNonOptions && pState->cNonOptions != INT32_MAX)
+ rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
+
+ return rtGetOptProcessValue(fFlags, pszValue, pValueUnion);
+}
+RT_EXPORT_SYMBOL(RTGetOptFetchValue);
+
+
+RTDECL(char **) RTGetOptNonOptionArrayPtr(PRTGETOPTSTATE pState)
+{
+ AssertReturn(pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST, NULL);
+ return &pState->argv[pState->iNext - 1];
+}
+RT_EXPORT_SYMBOL(RTGetOptNonOptionArrayPtr);
+
+
+RTDECL(RTEXITCODE) RTGetOptPrintError(int ch, PCRTGETOPTUNION pValueUnion)
+{
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ RTMsgError("Invalid parameter: %s", pValueUnion->psz);
+ else if (ch > 0)
+ {
+ if (RT_C_IS_GRAPH(ch))
+ RTMsgError("Unhandled option: -%c", ch);
+ else
+ RTMsgError("Unhandled option: %i (%#x)", ch, ch);
+ }
+ else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
+ RTMsgError("Unknown option: '%s'", pValueUnion->psz);
+ else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
+ /** @todo r=klaus not really ideal, as the value isn't available */
+ RTMsgError("The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
+ else if (pValueUnion->pDef)
+ RTMsgError("%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
+ else
+ RTMsgError("%Rrs\n", ch);
+
+ return RTEXITCODE_SYNTAX;
+}
+RT_EXPORT_SYMBOL(RTGetOptPrintError);
+
+
+RTDECL(ssize_t) RTGetOptFormatError(char *pszBuf, size_t cbBuf, int ch, PCRTGETOPTUNION pValueUnion)
+{
+ ssize_t cchRet;
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Invalid parameter: %s", pValueUnion->psz);
+ else if (ch > 0)
+ {
+ if (RT_C_IS_GRAPH(ch))
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: -%c", ch);
+ else
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: %i (%#x)", ch, ch);
+ }
+ else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unknown option: '%s'", pValueUnion->psz);
+ else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
+ /** @todo r=klaus not really ideal, as the value isn't available */
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
+ else if (pValueUnion->pDef)
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
+ else
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "%Rrs\n", ch);
+
+ return cchRet;
+}
+RT_EXPORT_SYMBOL(RTGetOptFormatError);
+
diff --git a/src/VBox/Runtime/common/misc/getoptargv.cpp b/src/VBox/Runtime/common/misc/getoptargv.cpp
new file mode 100644
index 00000000..9a84507a
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/getoptargv.cpp
@@ -0,0 +1,654 @@
+/* $Id: getoptargv.cpp $ */
+/** @file
+ * IPRT - Command Line Parsing, Argument Vector.
+ */
+
+/*
+ * 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/getopt.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/**
+ * Array indexed by the quoting type and 7-bit ASCII character.
+ *
+ * We include some extra stuff here that the corresponding shell would normally
+ * require quoting of.
+ */
+static uint8_t
+#ifndef IPRT_REGENERATE_QUOTE_CHARS
+const
+#endif
+g_abmQuoteChars[RTGETOPTARGV_CNV_QUOTE_MASK + 1][16] =
+{
+ { 0xfe, 0xff, 0xff, 0xff, 0x65, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 },
+ { 0xfe, 0xff, 0xff, 0xff, 0xd7, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x50 },
+};
+
+
+#ifdef IPRT_REGENERATE_QUOTE_CHARS /* To re-generate the bitmaps. */
+# include <stdio.h>
+int main()
+{
+ RT_ZERO(g_abmQuoteChars);
+
+# define SET_ALL(ch) \
+ do { \
+ for (size_t iType = 0; iType <= RTGETOPTARGV_CNV_QUOTE_MASK; iType++) \
+ ASMBitSet(&g_abmQuoteChars[iType], (ch)); \
+ } while (0)
+# define SET(ConstSuffix, ch) \
+ do { \
+ ASMBitSet(&g_abmQuoteChars[RTGETOPTARGV_CNV_QUOTE_##ConstSuffix], (ch)); \
+ printf(#ConstSuffix ": %#x %d %c\n", (ch), (ch), (ch)); \
+ } while (0)
+
+ /* just flag all the control chars as in need of quoting. */
+ for (char ch = 1; ch < 0x20; ch++)
+ SET_ALL(ch);
+
+ /* ... and space of course */
+ SET_ALL(' ');
+
+ /* MS CRT / CMD.EXE: */
+ SET(MS_CRT, '"');
+ SET(MS_CRT, '&');
+ SET(MS_CRT, '>');
+ SET(MS_CRT, '<');
+ SET(MS_CRT, '|');
+ SET(MS_CRT, '%');
+
+ /* Bourne shell: */
+ SET(BOURNE_SH, '!');
+ SET(BOURNE_SH, '"');
+ SET(BOURNE_SH, '$');
+ SET(BOURNE_SH, '&');
+ SET(BOURNE_SH, '(');
+ SET(BOURNE_SH, ')');
+ SET(BOURNE_SH, '*');
+ SET(BOURNE_SH, ';');
+ SET(BOURNE_SH, '<');
+ SET(BOURNE_SH, '>');
+ SET(BOURNE_SH, '?');
+ SET(BOURNE_SH, '[');
+ SET(BOURNE_SH, '\'');
+ SET(BOURNE_SH, '\\');
+ SET(BOURNE_SH, '`');
+ SET(BOURNE_SH, '|');
+ SET(BOURNE_SH, '~');
+
+ for (size_t iType = 0; iType <= RTGETOPTARGV_CNV_QUOTE_MASK; iType++)
+ {
+ printf(" {");
+ for (size_t iByte = 0; iByte < 16; iByte++)
+ printf(iByte == 0 ? " 0x%02x" : ", 0x%02x", g_abmQuoteChars[iType][iByte]);
+ printf(" },\n");
+ }
+ return 0;
+}
+
+#else /* !IPRT_REGENERATE_QUOTE_CHARS */
+
+/**
+ * Look for an unicode code point in the separator string.
+ *
+ * @returns true if it's a separator, false if it isn't.
+ * @param Cp The code point.
+ * @param pszSeparators The separators.
+ */
+static bool rtGetOptIsUniCpInString(RTUNICP Cp, const char *pszSeparators)
+{
+ /* This could be done in a more optimal fashion. Probably worth a
+ separate RTStr function at some point. */
+ for (;;)
+ {
+ RTUNICP CpSep;
+ int rc = RTStrGetCpEx(&pszSeparators, &CpSep);
+ AssertRCReturn(rc, false);
+ if (CpSep == Cp)
+ return true;
+ if (!CpSep)
+ return false;
+ }
+}
+
+
+/**
+ * Look for an 7-bit ASCII character in the separator string.
+ *
+ * @returns true if it's a separator, false if it isn't.
+ * @param ch The character.
+ * @param pszSeparators The separators.
+ * @param cchSeparators The number of separators chars.
+ */
+DECLINLINE(bool) rtGetOptIsAsciiInSet(char ch, const char *pszSeparators, size_t cchSeparators)
+{
+ switch (cchSeparators)
+ {
+ case 8: if (ch == pszSeparators[7]) return true; RT_FALL_THRU();
+ case 7: if (ch == pszSeparators[6]) return true; RT_FALL_THRU();
+ case 6: if (ch == pszSeparators[5]) return true; RT_FALL_THRU();
+ case 5: if (ch == pszSeparators[4]) return true; RT_FALL_THRU();
+ case 4: if (ch == pszSeparators[3]) return true; RT_FALL_THRU();
+ case 3: if (ch == pszSeparators[2]) return true; RT_FALL_THRU();
+ case 2: if (ch == pszSeparators[1]) return true; RT_FALL_THRU();
+ case 1: if (ch == pszSeparators[0]) return true;
+ return false;
+ default:
+ return memchr(pszSeparators, ch, cchSeparators) != NULL;
+ }
+}
+
+
+/**
+ * Checks if the character is in the set of separators
+ *
+ * @returns true if it is, false if it isn't.
+ *
+ * @param Cp The code point.
+ * @param pszSeparators The separators.
+ * @param cchSeparators The length of @a pszSeparators.
+ */
+DECL_FORCE_INLINE(bool) rtGetOptIsCpInSet(RTUNICP Cp, const char *pszSeparators, size_t cchSeparators)
+{
+ if (RT_LIKELY(Cp <= 127))
+ return rtGetOptIsAsciiInSet((char)Cp, pszSeparators, cchSeparators);
+ return rtGetOptIsUniCpInString(Cp, pszSeparators);
+}
+
+
+/**
+ * Skips any delimiters at the start of the string that is pointed to.
+ *
+ * @returns VINF_SUCCESS or RTStrGetCpEx status code.
+ * @param ppszSrc Where to get and return the string pointer.
+ * @param pszSeparators The separators.
+ * @param cchSeparators The length of @a pszSeparators.
+ */
+static int rtGetOptSkipDelimiters(const char **ppszSrc, const char *pszSeparators, size_t cchSeparators)
+{
+ const char *pszSrc = *ppszSrc;
+ const char *pszRet;
+ for (;;)
+ {
+ pszRet = pszSrc;
+ RTUNICP Cp;
+ int rc = RTStrGetCpEx(&pszSrc, &Cp);
+ if (RT_FAILURE(rc))
+ {
+ *ppszSrc = pszRet;
+ return rc;
+ }
+ if ( !Cp
+ || !rtGetOptIsCpInSet(Cp, pszSeparators, cchSeparators))
+ break;
+ }
+
+ *ppszSrc = pszRet;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTGetOptArgvFromString(char ***ppapszArgv, int *pcArgs, const char *pszCmdLine,
+ uint32_t fFlags, const char *pszSeparators)
+{
+ /*
+ * Some input validation.
+ */
+ AssertPtr(pszCmdLine);
+ AssertPtr(pcArgs);
+ AssertPtr(ppapszArgv);
+ AssertReturn( (fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_BOURNE_SH
+ || (fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_MS_CRT, VERR_INVALID_FLAGS);
+ AssertReturn(~(fFlags & ~RTGETOPTARGV_CNV_VALID_MASK), VERR_INVALID_FLAGS);
+
+ if (!pszSeparators)
+ pszSeparators = " \t\n\r";
+ else
+ AssertPtr(pszSeparators);
+ size_t const cchSeparators = strlen(pszSeparators);
+ AssertReturn(cchSeparators > 0, VERR_INVALID_PARAMETER);
+
+ /*
+ * Parse the command line and chop off it into argv individual argv strings.
+ */
+ const char *pszSrc = pszCmdLine;
+ char *pszDup = NULL;
+ char *pszDst;
+ if (fFlags & RTGETOPTARGV_CNV_MODIFY_INPUT)
+ pszDst = (char *)pszCmdLine;
+ else
+ {
+ pszDst = pszDup = (char *)RTMemAlloc(strlen(pszSrc) + 1);
+ if (!pszDup)
+ return VERR_NO_STR_MEMORY;
+ }
+ int rc = VINF_SUCCESS;
+ char **papszArgs = NULL;
+ unsigned iArg = 0;
+ while (*pszSrc)
+ {
+ /* Skip stuff */
+ rc = rtGetOptSkipDelimiters(&pszSrc, pszSeparators, cchSeparators);
+ if (RT_FAILURE(rc))
+ break;
+ if (!*pszSrc)
+ break;
+
+ /* Start a new entry. */
+ if ((iArg % 32) == 0)
+ {
+ void *pvNew = RTMemRealloc(papszArgs, (iArg + 33) * sizeof(char *));
+ if (!pvNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ papszArgs = (char **)pvNew;
+ }
+ papszArgs[iArg++] = pszDst;
+
+ /*
+ * Parse and copy the string over.
+ */
+ RTUNICP uc;
+ if ((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_BOURNE_SH)
+ {
+ /*
+ * Bourne shell style.
+ */
+ RTUNICP ucQuote = 0;
+ for (;;)
+ {
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ if (!ucQuote)
+ {
+ if (uc == '"' || uc == '\'')
+ ucQuote = uc;
+ else if (rtGetOptIsCpInSet(uc, pszSeparators, cchSeparators))
+ break;
+ else if (uc != '\\')
+ pszDst = RTStrPutCp(pszDst, uc);
+ else
+ {
+ /* escaped char */
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ pszDst = RTStrPutCp(pszDst, uc);
+ }
+ }
+ else if (ucQuote != uc)
+ {
+ if (uc != '\\' || ucQuote == '\'')
+ pszDst = RTStrPutCp(pszDst, uc);
+ else
+ {
+ /* escaped char */
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ if ( uc != '"'
+ && uc != '\\'
+ && uc != '`'
+ && uc != '$'
+ && uc != '\n')
+ pszDst = RTStrPutCp(pszDst, ucQuote);
+ pszDst = RTStrPutCp(pszDst, uc);
+ }
+ }
+ else
+ ucQuote = 0;
+ }
+ }
+ else
+ {
+ /*
+ * Microsoft CRT style.
+ */
+ Assert((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_MS_CRT);
+ bool fInQuote = false;
+ for (;;)
+ {
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ if (uc == '"')
+ {
+ /* Two double quotes insides a quoted string in an escape
+ sequence and we output one double quote char.
+ See http://www.daviddeley.com/autohotkey/parameters/parameters.htm */
+ if (!fInQuote)
+ fInQuote = true;
+ else if (*pszSrc != '"')
+ fInQuote = false;
+ else
+ {
+ pszDst = RTStrPutCp(pszDst, '"');
+ pszSrc++;
+ }
+ }
+ else if (!fInQuote && rtGetOptIsCpInSet(uc, pszSeparators, cchSeparators))
+ break;
+ else if (uc != '\\')
+ pszDst = RTStrPutCp(pszDst, uc);
+ else
+ {
+ /* A backslash sequence is only relevant if followed by
+ a double quote, then it will work like an escape char. */
+ size_t cSlashes = 1;
+ while (*pszSrc == '\\')
+ {
+ cSlashes++;
+ pszSrc++;
+ }
+ if (*pszSrc != '"')
+ /* Not an escape sequence. */
+ while (cSlashes-- > 0)
+ pszDst = RTStrPutCp(pszDst, '\\');
+ else
+ {
+ /* Escape sequence. Output half of the slashes. If odd
+ number, output the escaped double quote . */
+ while (cSlashes >= 2)
+ {
+ pszDst = RTStrPutCp(pszDst, '\\');
+ cSlashes -= 2;
+ }
+ if (cSlashes)
+ {
+ pszDst = RTStrPutCp(pszDst, '"');
+ pszSrc++;
+ }
+ }
+ }
+ }
+ }
+
+ *pszDst++ = '\0';
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pszDup);
+ RTMemFree(papszArgs);
+ return rc;
+ }
+
+ /*
+ * Terminate the array.
+ * Check for empty string to make sure we've got an array.
+ */
+ if (iArg == 0)
+ {
+ RTMemFree(pszDup);
+ papszArgs = (char **)RTMemAlloc(1 * sizeof(char *));
+ if (!papszArgs)
+ return VERR_NO_MEMORY;
+ }
+ papszArgs[iArg] = NULL;
+
+ *pcArgs = iArg;
+ *ppapszArgv = papszArgs;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTGetOptArgvFree(char **papszArgv)
+{
+ RTGetOptArgvFreeEx(papszArgv, 0);
+}
+
+
+RTDECL(void) RTGetOptArgvFreeEx(char **papszArgv, uint32_t fFlags)
+{
+ Assert(~(fFlags & ~RTGETOPTARGV_CNV_VALID_MASK));
+ if (papszArgv)
+ {
+ /*
+ * We've really only _two_ allocations here. Check the code in
+ * RTGetOptArgvFromString for the particulars.
+ */
+ if (!(fFlags & RTGETOPTARGV_CNV_MODIFY_INPUT))
+ RTMemFree(papszArgv[0]);
+ RTMemFree(papszArgv);
+ }
+}
+
+
+/**
+ * Checks if the argument needs quoting or not.
+ *
+ * @returns true if it needs, false if it don't.
+ * @param pszArg The argument.
+ * @param fFlags Quoting style.
+ * @param pcch Where to store the argument length when quoting
+ * is not required. (optimization)
+ */
+DECLINLINE(bool) rtGetOpArgvRequiresQuoting(const char *pszArg, uint32_t fFlags, size_t *pcch)
+{
+ if ((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) != RTGETOPTARGV_CNV_UNQUOTED)
+ {
+ char const *psz = pszArg;
+ unsigned char ch;
+ while ((ch = (unsigned char)*psz))
+ {
+ if ( ch < 128
+ && ASMBitTest(&g_abmQuoteChars[fFlags & RTGETOPTARGV_CNV_QUOTE_MASK], ch))
+ return true;
+ psz++;
+ }
+
+ *pcch = psz - pszArg;
+ }
+ else
+ *pcch = strlen(pszArg);
+ return false;
+}
+
+
+/**
+ * Grows the command line string buffer.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY.
+ * @param ppszCmdLine Pointer to the command line string pointer.
+ * @param pcbCmdLineAlloc Pointer to the allocation length variable.
+ * @param cchMin The minimum size to grow with, kind of.
+ */
+static int rtGetOptArgvToStringGrow(char **ppszCmdLine, size_t *pcbCmdLineAlloc, size_t cchMin)
+{
+ size_t cb = *pcbCmdLineAlloc;
+ while (cb < cchMin)
+ cb *= 2;
+ cb *= 2;
+ *pcbCmdLineAlloc = cb;
+ return RTStrRealloc(ppszCmdLine, cb);
+}
+
+/**
+ * Checks if we have a sequence of DOS slashes followed by a double quote char.
+ *
+ * @returns true / false accordingly.
+ * @param psz The string.
+ */
+DECLINLINE(bool) rtGetOptArgvMsCrtIsSlashQuote(const char *psz)
+{
+ while (*psz == '\\')
+ psz++;
+ return *psz == '"' || *psz == '\0';
+}
+
+
+RTDECL(int) RTGetOptArgvToString(char **ppszCmdLine, const char * const *papszArgv, uint32_t fFlags)
+{
+ AssertReturn((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) <= RTGETOPTARGV_CNV_UNQUOTED, VERR_INVALID_FLAGS);
+ AssertReturn(!(fFlags & (~RTGETOPTARGV_CNV_VALID_MASK | RTGETOPTARGV_CNV_MODIFY_INPUT)), VERR_INVALID_FLAGS);
+
+#define PUT_CH(ch) \
+ if (RT_UNLIKELY(off + 1 >= cbCmdLineAlloc)) { \
+ rc = rtGetOptArgvToStringGrow(&pszCmdLine, &cbCmdLineAlloc, 1); \
+ if (RT_FAILURE(rc)) \
+ break; \
+ } \
+ pszCmdLine[off++] = (ch)
+
+#define PUT_PSZ(psz, cch) \
+ if (RT_UNLIKELY(off + (cch) >= cbCmdLineAlloc)) { \
+ rc = rtGetOptArgvToStringGrow(&pszCmdLine, &cbCmdLineAlloc, (cch)); \
+ if (RT_FAILURE(rc)) \
+ break; \
+ } \
+ memcpy(&pszCmdLine[off], (psz), (cch)); \
+ off += (cch);
+#define PUT_SZ(sz) PUT_PSZ(sz, sizeof(sz) - 1)
+
+ /*
+ * Take the realloc approach, it requires less code and is probably more
+ * efficient than figuring out the size first.
+ */
+ int rc = VINF_SUCCESS;
+ size_t off = 0;
+ size_t cbCmdLineAlloc = 256;
+ char *pszCmdLine = RTStrAlloc(256);
+ if (!pszCmdLine)
+ return VERR_NO_STR_MEMORY;
+
+ for (size_t i = 0; papszArgv[i]; i++)
+ {
+ if (i > 0)
+ {
+ PUT_CH(' ');
+ }
+
+ /* does it need quoting? */
+ const char *pszArg = papszArgv[i];
+ size_t cchArg;
+ if (!rtGetOpArgvRequiresQuoting(pszArg, fFlags, &cchArg))
+ {
+ /* No quoting needed, just append the argument. */
+ PUT_PSZ(pszArg, cchArg);
+ }
+ else if ((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_MS_CRT)
+ {
+ /*
+ * Microsoft CRT quoting. Quote the whole argument in double
+ * quotes to make it easier to read and code.
+ */
+ PUT_CH('"');
+ char ch;
+ while ((ch = *pszArg++))
+ {
+ if ( ch == '\\'
+ && rtGetOptArgvMsCrtIsSlashQuote(pszArg))
+ {
+ PUT_SZ("\\\\");
+ }
+ else if (ch == '"')
+ {
+ PUT_SZ("\\\"");
+ }
+ else
+ {
+ PUT_CH(ch);
+ }
+ }
+ PUT_CH('"');
+ }
+ else
+ {
+ /*
+ * Bourne Shell quoting. Quote the whole thing in single quotes
+ * and use double quotes for any single quote chars.
+ */
+ PUT_CH('\'');
+ char ch;
+ while ((ch = *pszArg++))
+ {
+ if (ch == '\'')
+ {
+ PUT_SZ("'\"'\"'");
+ }
+ else
+ {
+ PUT_CH(ch);
+ }
+ }
+ PUT_CH('\'');
+ }
+ }
+
+ /* Set return value / cleanup. */
+ if (RT_SUCCESS(rc))
+ {
+ pszCmdLine[off] = '\0';
+ *ppszCmdLine = pszCmdLine;
+ }
+ else
+ RTStrFree(pszCmdLine);
+#undef PUT_SZ
+#undef PUT_PSZ
+#undef PUT_CH
+ return rc;
+}
+
+
+RTDECL(int) RTGetOptArgvToUtf16String(PRTUTF16 *ppwszCmdLine, const char * const *papszArgv, uint32_t fFlags)
+{
+ char *pszCmdLine;
+ int rc = RTGetOptArgvToString(&pszCmdLine, papszArgv, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrToUtf16(pszCmdLine, ppwszCmdLine);
+ RTStrFree(pszCmdLine);
+ }
+ return rc;
+}
+
+#endif /* !IPRT_REGENERATE_QUOTE_CHARS */
+
diff --git a/src/VBox/Runtime/common/misc/handle.cpp b/src/VBox/Runtime/common/misc/handle.cpp
new file mode 100644
index 00000000..b73a3c60
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handle.cpp
@@ -0,0 +1,85 @@
+/* $Id: handle.cpp $ */
+/** @file
+ * IPRT - Generic Handle Manipulation.
+ */
+
+/*
+ * 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/handle.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/pipe.h>
+
+
+RTDECL(int) RTHandleClose(PRTHANDLE ph)
+{
+ int rc = VINF_SUCCESS;
+ if (ph)
+ {
+ switch (ph->enmType)
+ {
+ case RTHANDLETYPE_FILE:
+ rc = RTFileClose(ph->u.hFile);
+ ph->u.hFile = NIL_RTFILE;
+ break;
+
+ case RTHANDLETYPE_PIPE:
+ rc = RTPipeClose(ph->u.hPipe);
+ ph->u.hPipe = NIL_RTPIPE;
+ break;
+
+ case RTHANDLETYPE_SOCKET:
+ AssertMsgFailed(("Socket not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+
+ case RTHANDLETYPE_THREAD:
+ AssertMsgFailed(("Thread not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid type %d\n", ph->enmType));
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/misc/handletable.cpp b/src/VBox/Runtime/common/misc/handletable.cpp
new file mode 100644
index 00000000..2da55a7f
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletable.cpp
@@ -0,0 +1,234 @@
+/* $Id: handletable.cpp $ */
+/** @file
+ * IPRT - Handle Tables.
+ */
+
+/*
+ * 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/handletable.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+#include <iprt/spinlock.h>
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include "internal/magics.h"
+#include "handletable.h"
+
+
+
+RTDECL(int) RTHandleTableCreateEx(PRTHANDLETABLE phHandleTable, uint32_t fFlags, uint32_t uBase, uint32_t cMax,
+ PFNRTHANDLETABLERETAIN pfnRetain, void *pvUser)
+{
+ PRTHANDLETABLEINT pThis;
+ uint32_t cLevel1;
+ size_t cb;
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(phHandleTable, VERR_INVALID_POINTER);
+ *phHandleTable = NIL_RTHANDLETABLE;
+ AssertPtrNullReturn(pfnRetain, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTHANDLETABLE_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn(RT_BOOL(fFlags & RTHANDLETABLE_FLAGS_LOCKED) + RT_BOOL(fFlags & RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE) < 2,
+ VERR_INVALID_PARAMETER);
+ AssertReturn(cMax > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(UINT32_MAX - cMax >= uBase, VERR_INVALID_PARAMETER);
+
+ /*
+ * Adjust the cMax value so it is a multiple of the 2nd level tables.
+ */
+ if (cMax >= UINT32_MAX - RTHT_LEVEL2_ENTRIES)
+ cMax = UINT32_MAX - RTHT_LEVEL2_ENTRIES + 1;
+ cMax = ((cMax + RTHT_LEVEL2_ENTRIES - 1) / RTHT_LEVEL2_ENTRIES) * RTHT_LEVEL2_ENTRIES;
+
+ cLevel1 = cMax / RTHT_LEVEL2_ENTRIES;
+ Assert(cLevel1 * RTHT_LEVEL2_ENTRIES == cMax);
+
+ /*
+ * Allocate the structure, include the 1st level lookup table
+ * if it's below the threshold size.
+ */
+ cb = sizeof(RTHANDLETABLEINT);
+ if (cLevel1 < RTHT_LEVEL1_DYN_ALLOC_THRESHOLD)
+ cb = RT_ALIGN(cb, sizeof(void *)) + cLevel1 * sizeof(void *);
+ pThis = (PRTHANDLETABLEINT)RTMemAllocZ(cb);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Initialize it.
+ */
+ pThis->u32Magic = RTHANDLETABLE_MAGIC;
+ pThis->fFlags = fFlags;
+ pThis->uBase = uBase;
+ pThis->cCur = 0;
+ pThis->hSpinlock = NIL_RTSPINLOCK;
+ if (cLevel1 < RTHT_LEVEL1_DYN_ALLOC_THRESHOLD)
+ pThis->papvLevel1 = (void **)((uint8_t *)pThis + RT_ALIGN(sizeof(*pThis), sizeof(void *)));
+ else
+ pThis->papvLevel1 = NULL;
+ pThis->pfnRetain = pfnRetain;
+ pThis->pvRetainUser = pvUser;
+ pThis->cMax = cMax;
+ pThis->cCurAllocated = 0;
+ pThis->cLevel1 = cLevel1 < RTHT_LEVEL1_DYN_ALLOC_THRESHOLD ? cLevel1 : 0;
+ pThis->iFreeHead = NIL_RTHT_INDEX;
+ pThis->iFreeTail = NIL_RTHT_INDEX;
+ if (fFlags & (RTHANDLETABLE_FLAGS_LOCKED | RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE))
+ {
+ int rc;
+ if (fFlags & RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE)
+ rc = RTSpinlockCreate(&pThis->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "RTHandleTableCreateEx");
+ else
+ rc = RTSpinlockCreate(&pThis->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "RTHandleTableCreateEx");
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+ }
+
+ *phHandleTable = pThis;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTHandleTableCreateEx);
+
+
+RTDECL(int) RTHandleTableCreate(PRTHANDLETABLE phHandleTable)
+{
+ return RTHandleTableCreateEx(phHandleTable, RTHANDLETABLE_FLAGS_LOCKED, 1, 65534, NULL, NULL);
+}
+RT_EXPORT_SYMBOL(RTHandleTableCreate);
+
+
+RTDECL(int) RTHandleTableDestroy(RTHANDLETABLE hHandleTable, PFNRTHANDLETABLEDELETE pfnDelete, void *pvUser)
+{
+ PRTHANDLETABLEINT pThis;
+ uint32_t i1;
+ uint32_t i;
+
+ /*
+ * Validate input, quietly ignore the NIL handle.
+ */
+ if (hHandleTable == NIL_RTHANDLETABLE)
+ return VINF_SUCCESS;
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pfnDelete, VERR_INVALID_POINTER);
+
+ /*
+ * Mark the thing as invalid / deleted.
+ * Then kill the lock.
+ */
+ rtHandleTableLock(pThis);
+ ASMAtomicWriteU32(&pThis->u32Magic, ~RTHANDLETABLE_MAGIC);
+ rtHandleTableUnlock(pThis);
+
+ if (pThis->hSpinlock != NIL_RTSPINLOCK)
+ {
+ rtHandleTableLock(pThis);
+ rtHandleTableUnlock(pThis);
+
+ RTSpinlockDestroy(pThis->hSpinlock);
+ pThis->hSpinlock = NIL_RTSPINLOCK;
+ }
+
+ if (pfnDelete)
+ {
+ /*
+ * Walk all the tables looking for used handles.
+ */
+ uint32_t cLeft = pThis->cCurAllocated;
+ if (pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT)
+ {
+ for (i1 = 0; cLeft > 0 && i1 < pThis->cLevel1; i1++)
+ {
+ PRTHTENTRYCTX paTable = (PRTHTENTRYCTX)pThis->papvLevel1[i1];
+ if (paTable)
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES; i++)
+ if (!RTHT_IS_FREE(paTable[i].pvObj))
+ {
+ pfnDelete(hHandleTable, pThis->uBase + i + i1 * RTHT_LEVEL2_ENTRIES,
+ paTable[i].pvObj, paTable[i].pvCtx, pvUser);
+ Assert(cLeft > 0);
+ cLeft--;
+ }
+ }
+ }
+ else
+ {
+ for (i1 = 0; cLeft > 0 && i1 < pThis->cLevel1; i1++)
+ {
+ PRTHTENTRY paTable = (PRTHTENTRY)pThis->papvLevel1[i1];
+ if (paTable)
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES; i++)
+ if (!RTHT_IS_FREE(paTable[i].pvObj))
+ {
+ pfnDelete(hHandleTable, pThis->uBase + i + i1 * RTHT_LEVEL2_ENTRIES,
+ paTable[i].pvObj, NULL, pvUser);
+ Assert(cLeft > 0);
+ cLeft--;
+ }
+ }
+ }
+ Assert(!cLeft);
+ }
+
+ /*
+ * Free the memory.
+ */
+ for (i1 = 0; i1 < pThis->cLevel1; i1++)
+ if (pThis->papvLevel1[i1])
+ {
+ RTMemFree(pThis->papvLevel1[i1]);
+ pThis->papvLevel1[i1] = NULL;
+ }
+
+ if (pThis->cMax / RTHT_LEVEL2_ENTRIES >= RTHT_LEVEL1_DYN_ALLOC_THRESHOLD)
+ RTMemFree(pThis->papvLevel1);
+
+ RTMemFree(pThis);
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTHandleTableDestroy);
+
diff --git a/src/VBox/Runtime/common/misc/handletable.h b/src/VBox/Runtime/common/misc/handletable.h
new file mode 100644
index 00000000..7ae0c38a
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletable.h
@@ -0,0 +1,257 @@
+/* $Id: handletable.h $ */
+/** @file
+ * IPRT - Handle Tables, internal header.
+ */
+
+/*
+ * 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
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_misc_handletable_h
+#define IPRT_INCLUDED_SRC_common_misc_handletable_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** The number of entries in the 2nd level lookup table. */
+#define RTHT_LEVEL2_ENTRIES 2048
+
+/** The number of (max) 1st level entries requiring dynamic allocation of the
+ * 1st level table. If the max number is below this threshold, the 1st level
+ * table will be allocated as part of the handle table structure. */
+#define RTHT_LEVEL1_DYN_ALLOC_THRESHOLD 256
+
+/** Checks whether a object pointer is really a free entry or not. */
+#define RTHT_IS_FREE(pvObj) ( ((uintptr_t)(pvObj) & 3) == 3 )
+
+/** Sets RTHTENTRYFREE::iNext. */
+#define RTHT_SET_FREE_IDX(pFree, idx) \
+ do { \
+ (pFree)->iNext = ((uintptr_t)((uint32_t)(idx)) << 2) | 3U; \
+ } while (0)
+
+/** Gets the index part of RTHTENTRYFREE::iNext. */
+#define RTHT_GET_FREE_IDX(pFree) ( (uint32_t)((pFree)->iNext >> 2) )
+
+/** @def NIL_RTHT_INDEX
+ * The NIL handle index for use in the free list. (The difference between
+ * 32-bit and 64-bit hosts here comes down to the shifting performed for
+ * RTHTENTRYFREE::iNext.) */
+#if ARCH_BITS == 32
+# define NIL_RTHT_INDEX ( UINT32_C(0x3fffffff) )
+#elif ARCH_BITS >= 34
+# define NIL_RTHT_INDEX ( UINT32_C(0xffffffff) )
+#else
+# error "Missing or unsupported ARCH_BITS."
+#endif
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+
+/**
+ * Handle table entry, simple variant.
+ */
+typedef struct RTHTENTRY
+{
+ /** The object. */
+ void *pvObj;
+} RTHTENTRY;
+/** Pointer to a handle table entry, simple variant. */
+typedef RTHTENTRY *PRTHTENTRY;
+
+
+/**
+ * Handle table entry, context variant.
+ */
+typedef struct RTHTENTRYCTX
+{
+ /** The object. */
+ void *pvObj;
+ /** The context. */
+ void *pvCtx;
+} RTHTENTRYCTX;
+/** Pointer to a handle table entry, context variant. */
+typedef RTHTENTRYCTX *PRTHTENTRYCTX;
+
+
+/**
+ * Free handle table entry, shared by all variants.
+ */
+typedef struct RTHTENTRYFREE
+{
+ /** The index of the next handle, special format.
+ * In order to distinguish free and used handle table entries we exploit
+ * the heap alignment and use the lower two bits to do this. Used entries
+ * will have these bits set to 0, while free entries will have tem set
+ * to 3. Use the RTHT_GET_FREE_IDX and RTHT_SET_FREE_IDX macros to access
+ * this field. */
+ uintptr_t iNext;
+} RTHTENTRYFREE;
+/** Pointer to a free handle table entry. */
+typedef RTHTENTRYFREE *PRTHTENTRYFREE;
+
+AssertCompile(sizeof(RTHTENTRYFREE) <= sizeof(RTHTENTRY));
+AssertCompile(sizeof(RTHTENTRYFREE) <= sizeof(RTHTENTRYCTX));
+AssertCompileMemberOffset(RTHTENTRYFREE, iNext, 0);
+AssertCompileMemberOffset(RTHTENTRY, pvObj, 0);
+AssertCompileMemberOffset(RTHTENTRYCTX, pvObj, 0);
+
+
+/**
+ * Internal handle table structure.
+ */
+typedef struct RTHANDLETABLEINT
+{
+ /** Magic value (RTHANDLETABLE_MAGIC). */
+ uint32_t u32Magic;
+ /** The handle table flags specified to RTHandleTableCreateEx. */
+ uint32_t fFlags;
+ /** The base handle value (i.e. the first handle). */
+ uint32_t uBase;
+ /** The current number of handle table entries. */
+ uint32_t cCur;
+ /** The spinlock handle (NIL if RTHANDLETABLE_FLAGS_LOCKED wasn't used). */
+ RTSPINLOCK hSpinlock;
+ /** The level one lookup table. */
+ void **papvLevel1;
+ /** The retainer callback. Can be NULL. */
+ PFNRTHANDLETABLERETAIN pfnRetain;
+ /** The user argument to the retainer. */
+ void *pvRetainUser;
+ /** The max number of handles. */
+ uint32_t cMax;
+ /** The number of handles currently allocated. (for optimizing destruction) */
+ uint32_t cCurAllocated;
+ /** The current number of 1st level entries. */
+ uint32_t cLevel1;
+ /** Head of the list of free handle entires (index). */
+ uint32_t iFreeHead;
+ /** Tail of the list of free handle entires (index). */
+ uint32_t iFreeTail;
+} RTHANDLETABLEINT;
+/** Pointer to an handle table structure. */
+typedef RTHANDLETABLEINT *PRTHANDLETABLEINT;
+
+
+/**
+ * Looks up a simple index.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param i The index to look up.
+ */
+DECLINLINE(PRTHTENTRY) rtHandleTableLookupSimpleIdx(PRTHANDLETABLEINT pThis, uint32_t i)
+{
+ if (i < pThis->cCur)
+ {
+ PRTHTENTRY paTable = (PRTHTENTRY)pThis->papvLevel1[i / RTHT_LEVEL2_ENTRIES];
+ if (paTable)
+ return &paTable[i % RTHT_LEVEL2_ENTRIES];
+ }
+ return NULL;
+}
+
+
+/**
+ * Looks up a simple handle.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param h The handle to look up.
+ */
+DECLINLINE(PRTHTENTRY) rtHandleTableLookupSimple(PRTHANDLETABLEINT pThis, uint32_t h)
+{
+ return rtHandleTableLookupSimpleIdx(pThis, h - pThis->uBase);
+}
+
+
+/**
+ * Looks up a context index.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param i The index to look up.
+ */
+DECLINLINE(PRTHTENTRYCTX) rtHandleTableLookupWithCtxIdx(PRTHANDLETABLEINT pThis, uint32_t i)
+{
+ if (i < pThis->cCur)
+ {
+ PRTHTENTRYCTX paTable = (PRTHTENTRYCTX)pThis->papvLevel1[i / RTHT_LEVEL2_ENTRIES];
+ if (paTable)
+ return &paTable[i % RTHT_LEVEL2_ENTRIES];
+ }
+ return NULL;
+}
+
+
+/**
+ * Looks up a context handle.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param h The handle to look up.
+ */
+DECLINLINE(PRTHTENTRYCTX) rtHandleTableLookupWithCtx(PRTHANDLETABLEINT pThis, uint32_t h)
+{
+ return rtHandleTableLookupWithCtxIdx(pThis, h - pThis->uBase);
+}
+
+
+/**
+ * Locks the handle table.
+ *
+ * @param pThis The handle table structure.
+ */
+DECLINLINE(void) rtHandleTableLock(PRTHANDLETABLEINT pThis)
+{
+ if (pThis->hSpinlock != NIL_RTSPINLOCK)
+ RTSpinlockAcquire(pThis->hSpinlock);
+}
+
+
+/**
+ * Locks the handle table.
+ *
+ * @param pThis The handle table structure.
+ */
+DECLINLINE(void) rtHandleTableUnlock(PRTHANDLETABLEINT pThis)
+{
+ if (pThis->hSpinlock != NIL_RTSPINLOCK)
+ RTSpinlockRelease(pThis->hSpinlock);
+}
+
+#endif /* !IPRT_INCLUDED_SRC_common_misc_handletable_h */
+
diff --git a/src/VBox/Runtime/common/misc/handletablectx.cpp b/src/VBox/Runtime/common/misc/handletablectx.cpp
new file mode 100644
index 00000000..e093fe90
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletablectx.cpp
@@ -0,0 +1,339 @@
+/* $Id: handletablectx.cpp $ */
+/** @file
+ * IPRT - Handle Tables.
+ */
+
+/*
+ * 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/handletable.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+#include <iprt/spinlock.h>
+#include <iprt/err.h>
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include "internal/magics.h"
+#include "handletable.h"
+
+
+RTDECL(int) RTHandleTableAllocWithCtx(RTHANDLETABLE hHandleTable, void *pvObj, void *pvCtx, uint32_t *ph)
+{
+ PRTHANDLETABLEINT pThis;
+ int rc;
+
+ /* validate the input */
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT, VERR_INVALID_FUNCTION);
+ AssertReturn(!RTHT_IS_FREE(pvObj), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ph, VERR_INVALID_POINTER);
+ *ph = pThis->uBase - 1;
+
+ /*
+ * Allocation loop.
+ */
+ rtHandleTableLock(pThis);
+
+ do
+ {
+ /*
+ * Try grab a free entry from the head of the free list.
+ */
+ uint32_t i = pThis->iFreeHead;
+ if (i != NIL_RTHT_INDEX)
+ {
+ PRTHTENTRYCTX pEntry;
+ PRTHTENTRYFREE pFree = (PRTHTENTRYFREE)rtHandleTableLookupWithCtxIdx(pThis, i);
+ Assert(pFree);
+ if (i == pThis->iFreeTail)
+ pThis->iFreeTail = pThis->iFreeHead = NIL_RTHT_INDEX;
+ else
+ pThis->iFreeHead = RTHT_GET_FREE_IDX(pFree);
+ pThis->cCurAllocated++;
+ Assert(pThis->cCurAllocated <= pThis->cCur);
+
+ /*
+ * Setup the entry and return.
+ */
+ pEntry = (PRTHTENTRYCTX)pFree;
+ pEntry->pvObj = pvObj;
+ pEntry->pvCtx = pvCtx;
+ *ph = i + pThis->uBase;
+ rc = VINF_SUCCESS;
+ }
+ /*
+ * Must expand the handle table, unless it's full.
+ */
+ else if (pThis->cCur >= pThis->cMax)
+ {
+ rc = VERR_NO_MORE_HANDLES;
+ Assert(pThis->cCur == pThis->cCurAllocated);
+ }
+ else
+ {
+ void **papvLevel1;
+ uint32_t iLevel1New;
+ PRTHTENTRYCTX paTable;
+
+ /*
+ * Do we have to expand the 1st level table too?
+ */
+ uint32_t const iLevel1 = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ uint32_t cLevel1 = iLevel1 >= pThis->cLevel1
+ ? pThis->cLevel1 + PAGE_SIZE / sizeof(void *)
+ : 0;
+ if (cLevel1 > pThis->cMax / RTHT_LEVEL2_ENTRIES)
+ cLevel1 = pThis->cMax / RTHT_LEVEL2_ENTRIES;
+ Assert(!cLevel1 || pThis->cMax / RTHT_LEVEL2_ENTRIES >= RTHT_LEVEL1_DYN_ALLOC_THRESHOLD);
+
+ /* leave the lock (never do fancy stuff from behind a spinlock). */
+ rtHandleTableUnlock(pThis);
+
+ /*
+ * Do the allocation(s).
+ */
+ rc = VERR_TRY_AGAIN;
+ papvLevel1 = NULL;
+ if (cLevel1)
+ {
+ papvLevel1 = (void **)RTMemAlloc(sizeof(void *) * cLevel1);
+ if (!papvLevel1)
+ return VERR_NO_MEMORY;
+ }
+
+ paTable = (PRTHTENTRYCTX)RTMemAlloc(sizeof(*paTable) * RTHT_LEVEL2_ENTRIES);
+ if (!paTable)
+ {
+ RTMemFree(papvLevel1);
+ return VERR_NO_MEMORY;
+ }
+
+ /* re-enter the lock. */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Insert the new bits, but be a bit careful as someone might have
+ * raced us expanding the table.
+ */
+ /* deal with the 1st level lookup expansion first */
+ if (cLevel1)
+ {
+ Assert(papvLevel1);
+ if (cLevel1 > pThis->cLevel1)
+ {
+ void **papvTmp;
+
+ /* Replace the 1st level table. */
+ memcpy(papvLevel1, pThis->papvLevel1, sizeof(void *) * pThis->cLevel1);
+ memset(&papvLevel1[pThis->cLevel1], 0, sizeof(void *) * (cLevel1 - pThis->cLevel1));
+ pThis->cLevel1 = cLevel1;
+ papvTmp = pThis->papvLevel1;
+ pThis->papvLevel1 = papvLevel1;
+ papvLevel1 = papvTmp;
+ }
+
+ /* free the obsolete one (outside the lock of course) */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(papvLevel1);
+ rtHandleTableLock(pThis);
+ }
+
+ /* insert the table we allocated. */
+ iLevel1New = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ if ( iLevel1New < pThis->cLevel1
+ && pThis->cCur < pThis->cMax)
+ {
+ pThis->papvLevel1[iLevel1New] = paTable;
+
+ /* link all entries into a free list. */
+ Assert(!(pThis->cCur % RTHT_LEVEL2_ENTRIES));
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES - 1; i++)
+ {
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[i], i + 1 + pThis->cCur);
+ paTable[i].pvCtx = (void *)~(uintptr_t)7;
+ }
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[RTHT_LEVEL2_ENTRIES - 1], NIL_RTHT_INDEX);
+ paTable[RTHT_LEVEL2_ENTRIES - 1].pvCtx = (void *)~(uintptr_t)7;
+
+ /* join the free list with the other. */
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->cCur;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupWithCtxIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, pThis->cCur);
+ }
+ pThis->iFreeTail = pThis->cCur + RTHT_LEVEL2_ENTRIES - 1;
+
+ pThis->cCur += RTHT_LEVEL2_ENTRIES;
+ }
+ else
+ {
+ /* free the table (raced someone, and we lost). */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(paTable);
+ rtHandleTableLock(pThis);
+ }
+
+ rc = VERR_TRY_AGAIN;
+ }
+ } while (rc == VERR_TRY_AGAIN);
+
+ rtHandleTableUnlock(pThis);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTHandleTableAllocWithCtx);
+
+
+RTDECL(void *) RTHandleTableLookupWithCtx(RTHANDLETABLE hHandleTable, uint32_t h, void *pvCtx)
+{
+ void *pvObj = NULL;
+ PRTHTENTRYCTX pEntry;
+ PRTHANDLETABLEINT pThis;
+
+ /* validate the input */
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT, NULL);
+
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ pEntry = rtHandleTableLookupWithCtx(pThis, h);
+ if (pEntry && pEntry->pvCtx == pvCtx)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, pvCtx, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableLookupWithCtx);
+
+
+RTDECL(void *) RTHandleTableFreeWithCtx(RTHANDLETABLE hHandleTable, uint32_t h, void *pvCtx)
+{
+ void *pvObj = NULL;
+ PRTHTENTRYCTX pEntry;
+ PRTHANDLETABLEINT pThis;
+
+ /* validate the input */
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT, NULL);
+
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ pEntry = rtHandleTableLookupWithCtx(pThis, h);
+ if (pEntry && pEntry->pvCtx == pvCtx)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, pvCtx, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+
+ /*
+ * Link it into the free list.
+ */
+ if (pvObj)
+ {
+ PRTHTENTRYFREE pFree;
+ uint32_t i;
+
+ pEntry->pvCtx = (void *)~(uintptr_t)7;
+
+ pFree = (PRTHTENTRYFREE)pEntry;
+ RTHT_SET_FREE_IDX(pFree, NIL_RTHT_INDEX);
+
+ i = h - pThis->uBase;
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->iFreeTail = i;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupWithCtxIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, i);
+ pThis->iFreeTail = i;
+ }
+
+ Assert(pThis->cCurAllocated > 0);
+ pThis->cCurAllocated--;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableFreeWithCtx);
+
diff --git a/src/VBox/Runtime/common/misc/handletablesimple.cpp b/src/VBox/Runtime/common/misc/handletablesimple.cpp
new file mode 100644
index 00000000..474d9f35
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletablesimple.cpp
@@ -0,0 +1,314 @@
+/* $Id: handletablesimple.cpp $ */
+/** @file
+ * IPRT - Handle Tables.
+ */
+
+/*
+ * 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/handletable.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+#include <iprt/spinlock.h>
+#include <iprt/err.h>
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include "internal/magics.h"
+#include "handletable.h"
+
+
+RTDECL(int) RTHandleTableAlloc(RTHANDLETABLE hHandleTable, void *pvObj, uint32_t *ph)
+{
+ /* validate the input */
+ PRTHANDLETABLEINT pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT), VERR_INVALID_FUNCTION);
+ AssertReturn(!RTHT_IS_FREE(pvObj), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ph, VERR_INVALID_POINTER);
+ *ph = pThis->uBase - 1;
+
+ /*
+ * Allocation loop.
+ */
+ rtHandleTableLock(pThis);
+
+ int rc;
+ do
+ {
+ /*
+ * Try grab a free entry from the head of the free list.
+ */
+ uint32_t i = pThis->iFreeHead;
+ if (i != NIL_RTHT_INDEX)
+ {
+ PRTHTENTRYFREE pFree = (PRTHTENTRYFREE)rtHandleTableLookupSimpleIdx(pThis, i);
+ Assert(pFree);
+ if (i == pThis->iFreeTail)
+ pThis->iFreeTail = pThis->iFreeHead = NIL_RTHT_INDEX;
+ else
+ pThis->iFreeHead = RTHT_GET_FREE_IDX(pFree);
+ pThis->cCurAllocated++;
+ Assert(pThis->cCurAllocated <= pThis->cCur);
+
+ /*
+ * Setup the entry and return.
+ */
+ PRTHTENTRY pEntry = (PRTHTENTRY)pFree;
+ pEntry->pvObj = pvObj;
+ *ph = i + pThis->uBase;
+ rc = VINF_SUCCESS;
+ }
+ /*
+ * Must expand the handle table, unless it's full.
+ */
+ else if (pThis->cCur >= pThis->cMax)
+ {
+ rc = VERR_NO_MORE_HANDLES;
+ Assert(pThis->cCur == pThis->cCurAllocated);
+ }
+ else
+ {
+ /*
+ * Do we have to expand the 1st level table too?
+ */
+ uint32_t const iLevel1 = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ uint32_t cLevel1 = iLevel1 >= pThis->cLevel1
+ ? pThis->cLevel1 + PAGE_SIZE / sizeof(void *)
+ : 0;
+ if (cLevel1 > pThis->cMax / RTHT_LEVEL2_ENTRIES)
+ cLevel1 = pThis->cMax / RTHT_LEVEL2_ENTRIES;
+ Assert(!cLevel1 || pThis->cMax / RTHT_LEVEL2_ENTRIES >= RTHT_LEVEL1_DYN_ALLOC_THRESHOLD);
+
+ /* leave the lock (never do fancy stuff from behind a spinlock). */
+ rtHandleTableUnlock(pThis);
+
+ /*
+ * Do the allocation(s).
+ */
+ rc = VERR_TRY_AGAIN;
+ void **papvLevel1 = NULL;
+ if (cLevel1)
+ {
+ papvLevel1 = (void **)RTMemAlloc(sizeof(void *) * cLevel1);
+ if (!papvLevel1)
+ return VERR_NO_MEMORY;
+ }
+
+ PRTHTENTRY paTable = (PRTHTENTRY)RTMemAlloc(sizeof(*paTable) * RTHT_LEVEL2_ENTRIES);
+ if (!paTable)
+ {
+ RTMemFree(papvLevel1);
+ return VERR_NO_MEMORY;
+ }
+
+ /* re-enter the lock. */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Insert the new bits, but be a bit careful as someone might have
+ * raced us expanding the table.
+ */
+ /* deal with the 1st level lookup expansion first */
+ if (cLevel1)
+ {
+ Assert(papvLevel1);
+ if (cLevel1 > pThis->cLevel1)
+ {
+ /* Replace the 1st level table. */
+ memcpy(papvLevel1, pThis->papvLevel1, sizeof(void *) * pThis->cLevel1);
+ memset(&papvLevel1[pThis->cLevel1], 0, sizeof(void *) * (cLevel1 - pThis->cLevel1));
+ pThis->cLevel1 = cLevel1;
+ void **papvTmp = pThis->papvLevel1;
+ pThis->papvLevel1 = papvLevel1;
+ papvLevel1 = papvTmp;
+ }
+
+ /* free the obsolete one (outside the lock of course) */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(papvLevel1);
+ rtHandleTableLock(pThis);
+ }
+
+ /* insert the table we allocated. */
+ uint32_t iLevel1New = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ if ( iLevel1New < pThis->cLevel1
+ && pThis->cCur < pThis->cMax)
+ {
+ pThis->papvLevel1[iLevel1New] = paTable;
+
+ /* link all entries into a free list. */
+ Assert(!(pThis->cCur % RTHT_LEVEL2_ENTRIES));
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES - 1; i++)
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[i], i + 1 + pThis->cCur);
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[RTHT_LEVEL2_ENTRIES - 1], NIL_RTHT_INDEX);
+
+ /* join the free list with the other. */
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->cCur;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupSimpleIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, pThis->cCur);
+ }
+ pThis->iFreeTail = pThis->cCur + RTHT_LEVEL2_ENTRIES - 1;
+
+ pThis->cCur += RTHT_LEVEL2_ENTRIES;
+ }
+ else
+ {
+ /* free the table (raced someone, and we lost). */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(paTable);
+ rtHandleTableLock(pThis);
+ }
+
+ rc = VERR_TRY_AGAIN;
+ }
+ } while (rc == VERR_TRY_AGAIN);
+
+ rtHandleTableUnlock(pThis);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTHandleTableAlloc);
+
+
+RTDECL(void *) RTHandleTableLookup(RTHANDLETABLE hHandleTable, uint32_t h)
+{
+ /* validate the input */
+ PRTHANDLETABLEINT pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(!(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT), NULL);
+
+ void *pvObj = NULL;
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ PRTHTENTRY pEntry = rtHandleTableLookupSimple(pThis, h);
+ if (pEntry)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, NULL, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableLookup);
+
+
+RTDECL(void *) RTHandleTableFree(RTHANDLETABLE hHandleTable, uint32_t h)
+{
+ /* validate the input */
+ PRTHANDLETABLEINT pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(!(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT), NULL);
+
+ void *pvObj = NULL;
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ PRTHTENTRY pEntry = rtHandleTableLookupSimple(pThis, h);
+ if (pEntry)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, NULL, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+
+ /*
+ * Link it into the free list.
+ */
+ if (pvObj)
+ {
+ PRTHTENTRYFREE pFree = (PRTHTENTRYFREE)pEntry;
+ RTHT_SET_FREE_IDX(pFree, NIL_RTHT_INDEX);
+
+ uint32_t const i = h - pThis->uBase;
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->iFreeTail = i;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupSimpleIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, i);
+ pThis->iFreeTail = i;
+ }
+
+ Assert(pThis->cCurAllocated > 0);
+ pThis->cCurAllocated--;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableFree);
+
diff --git a/src/VBox/Runtime/common/misc/inifile.cpp b/src/VBox/Runtime/common/misc/inifile.cpp
new file mode 100644
index 00000000..ef173573
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/inifile.cpp
@@ -0,0 +1,733 @@
+/* $Id: inifile.cpp $ */
+/** @file
+ * IPRT - INI-file parser.
+ */
+
+/*
+ * 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/inifile.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/latin1.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def RTINIFILE_MAX_SIZE
+ * The maximum INI-file size we accept loading. */
+#if ARCH_BITS > 32
+# define RTINIFILE_MAX_SIZE (_64M - 2U)
+#elif ARCH_BITS > 16
+# define RTINIFILE_MAX_SIZE (_16M - 2U)
+#else
+# define RTINIFILE_MAX_SIZE (_64K - 2U)
+#endif
+
+/** @def RTINIFILE_MAX_SECTIONS
+ * The maximum number of sections we accept in an INI-file. */
+#if ARCH_BITS > 32
+# define RTINIFILE_MAX_SECTIONS (_1M)
+#elif ARCH_BITS > 16
+# define RTINIFILE_MAX_SECTIONS (_256K)
+#else
+# define RTINIFILE_MAX_SECTIONS (_1K)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * File encoding types.
+ */
+typedef enum RTINIFILEENCODING
+{
+ /** The customary invalid zero value. */
+ RTINIFILEENCODING_INVALID = 0,
+ /** We treat this as latin-1. */
+ RTINIFILEENCODING_ANSI,
+ /** UTF-8. */
+ RTINIFILEENCODING_UTF8,
+ /** Little endian UTF-16. */
+ RTINIFILEENCODING_UTF16LE,
+ /** Big endian UTF-16. */
+ RTINIFILEENCODING_UTF16BE,
+ /** End of valid encoding types. */
+ RTINIFILEENCODING_END
+} RTINIFILEENCODING;
+
+
+/**
+ * Preparsed section info.
+ */
+typedef struct RTINIFILESECTION
+{
+ /** The section name offset (byte). */
+ uint32_t offName;
+ /** The section length in bytes starting with the name. */
+ uint32_t cchSection;
+ /** The UTF-8 length of the section name. */
+ uint32_t cchName;
+ /** Offset into the section where to start looking for values. */
+ uint32_t cchSkipToValues : 24;
+ /** @todo use 4 bits for flags and stuff. like escaped name. */
+} RTINIFILESECTION;
+/** Pointer to preparsed section info. */
+typedef RTINIFILESECTION *PRTINIFILESECTION;
+
+
+/**
+ * INI-file instance data.
+ */
+typedef struct RTINIFILEINT
+{
+ /** Magic value (RTINIFILEINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The file we're working on. */
+ RTVFSFILE hVfsFile;
+ /** Flags, RTINIFILE_F_XXX. */
+ uint32_t fFlags;
+
+ /** The original file encoding. */
+ RTINIFILEENCODING enmEncoding;
+ /** Pointer to the file content (converted to UTF-8). */
+ char *pszFile;
+ /** The file size. */
+ uint32_t cbFile;
+ /** Number of sections. */
+ uint32_t cSections;
+ /** Sections in the loaded file. */
+ PRTINIFILESECTION paSections;
+
+} RTINIFILEINT;
+/** Pointer to an INI-file instance. */
+typedef RTINIFILEINT *PRTINIFILEINT;
+
+
+static int rtIniFileLoad(PRTINIFILEINT pThis)
+{
+ /*
+ * Load the entire file into memory, ensuring two terminating zeros.
+ */
+ uint64_t cbFile;
+ int rc = RTVfsFileQuerySize(pThis->hVfsFile, &cbFile);
+ AssertRCReturn(rc, rc);
+
+ if (cbFile > RTINIFILE_MAX_SIZE)
+ return VERR_TOO_MUCH_DATA;
+ if (cbFile == 0)
+ return VINF_SUCCESS; /* Nothing to do. */
+
+ pThis->cbFile = (uint32_t)cbFile;
+ pThis->pszFile = (char *)RTMemAllocZ(pThis->cbFile + 2);
+ if (!pThis->pszFile)
+ return VERR_NO_MEMORY;
+
+ rc = RTVfsFileReadAt(pThis->hVfsFile, 0, pThis->pszFile, pThis->cbFile, NULL);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Detect encoding and convert to BOM prefixed UTF-8.
+ */
+ if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xef)
+ && (uint8_t)pThis->pszFile[1] == UINT8_C(0xbb)
+ && (uint8_t)pThis->pszFile[2] == UINT8_C(0xbf))
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_UTF8;
+ rc = RTStrValidateEncoding(&pThis->pszFile[3]);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ size_t cchUtf8;
+ if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xfe)
+ && (uint8_t)pThis->pszFile[1] == UINT8_C(0xff))
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_UTF16BE;
+ rc = RTUtf16BigCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8);
+ }
+ else if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xff)
+ && (uint8_t)pThis->pszFile[1] == UINT8_C(0xfe))
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_UTF16LE;
+ rc = RTUtf16LittleCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8);
+ }
+ else
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_ANSI;
+ rc = RTLatin1CalcUtf8LenEx(pThis->pszFile, RTSTR_MAX, &cchUtf8);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char *pszUtf8Bom = (char *)RTMemAllocZ(3 + cchUtf8 + 1);
+ if (!pszUtf8Bom)
+ return VERR_NO_MEMORY;
+ pszUtf8Bom[0] = '\xEF';
+ pszUtf8Bom[1] = '\xBB';
+ pszUtf8Bom[2] = '\xBF';
+
+ char *pszUtf8 = pszUtf8Bom + 3;
+ if (pThis->enmEncoding == RTINIFILEENCODING_UTF16BE)
+ rc = RTUtf16BigToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
+ else if (pThis->enmEncoding == RTINIFILEENCODING_UTF16LE)
+ rc = RTUtf16LittleToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
+ else
+ rc = RTLatin1ToUtf8Ex(pThis->pszFile, RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
+ AssertRCReturnStmt(rc, RTMemFree(pszUtf8Bom), rc);
+
+ RTMemFree(pThis->pszFile);
+ pThis->pszFile = pszUtf8Bom;
+ pThis->cbFile = 3 + (uint32_t)cchUtf8;
+ }
+
+ /*
+ * Do a rough section count.
+ * Section zero is for unsectioned values at the start of the file.
+ */
+ uint32_t cSections = 1;
+ const char *psz = pThis->pszFile + 3;
+ char ch;
+ while ((ch = *psz) != '\0')
+ {
+ while (RT_C_IS_SPACE(ch))
+ ch = *++psz;
+ if (ch == '[')
+ cSections++;
+
+ /* next line. */
+ psz = strchr(psz, '\n');
+ if (psz)
+ psz++;
+ else
+ break;
+ }
+ if (cSections > RTINIFILE_MAX_SECTIONS)
+ return VERR_TOO_MUCH_DATA;
+
+ /*
+ * Allocation section array and do the preparsing.
+ */
+ pThis->paSections = (PRTINIFILESECTION)RTMemAllocZ(sizeof(pThis->paSections[0]) * cSections);
+ if (!pThis->paSections)
+ return VERR_NO_MEMORY;
+
+ uint32_t iSection = 0;
+ pThis->paSections[0].offName = 3;
+ pThis->paSections[0].cchName = 0;
+ pThis->paSections[0].cchSkipToValues = 0;
+ psz = pThis->pszFile + 3;
+ while ((ch = *psz) != '\0')
+ {
+ const char *const pszLine = psz;
+
+ while (RT_C_IS_SPACE(ch))
+ ch = *++psz;
+ if (ch == '[')
+ {
+ /* Complete previous section. */
+ pThis->paSections[iSection].cchSection = (uint32_t)(pszLine - &pThis->pszFile[pThis->paSections[iSection].offName]);
+
+ /* New section. */
+ iSection++;
+ AssertReturn(iSection < cSections, VERR_INTERNAL_ERROR_3);
+ const char * const pszName = ++psz;
+ pThis->paSections[iSection].offName = (uint32_t)(psz - pThis->pszFile);
+
+ /* Figure the name length. We're very very relaxed about terminating bracket. */
+ while ((ch = *psz) != '\0' && ch != ']' && ch != '\r' && ch != '\n')
+ psz++;
+ pThis->paSections[iSection].cchName = (uint32_t)(psz - pszName);
+
+ /* Set skip count to the start of the next line. */
+ while (ch != '\0' && ch != '\n')
+ ch = *++psz;
+ pThis->paSections[iSection].cchSkipToValues = (uint32_t)(psz - pszName + 1);
+
+ if (ch == '\n')
+ psz++;
+ else
+ break;
+ }
+ else
+ {
+ psz = strchr(psz, '\n');
+ if (psz)
+ psz++;
+ else
+ break;
+ }
+ }
+
+ /* Complete the final section. */
+ pThis->paSections[iSection].cchSection = pThis->cbFile - pThis->paSections[iSection].offName;
+ pThis->cSections = iSection + 1;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTIniFileCreateFromVfsFile(PRTINIFILE phIniFile, RTVFSFILE hVfsFile, uint32_t fFlags)
+{
+ /*
+ * Validate input, retaining a reference to the file.
+ */
+ AssertPtrReturn(phIniFile, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTINIFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create an instance.
+ */
+ PRTINIFILEINT pThis = (PRTINIFILEINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->u32Magic = RTINIFILE_MAGIC;
+ pThis->cRefs = 1;
+ pThis->hVfsFile = hVfsFile;
+ pThis->fFlags = fFlags;
+
+ int rc = rtIniFileLoad(pThis);
+ if (RT_SUCCESS(rc))
+ {
+
+ *phIniFile = pThis;
+ return VINF_SUCCESS;
+ }
+ RTIniFileRelease(pThis);
+ return rc;
+ }
+ RTVfsFileRelease(hVfsFile);
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(uint32_t) RTIniFileRetain(RTINIFILE hIniFile)
+{
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < _64K);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTIniFileRelease(RTINIFILE hIniFile)
+{
+ if (hIniFile == NIL_RTINIFILE)
+ return 0;
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < _64K);
+ if (cRefs == 0)
+ {
+ AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTINIFILE_MAGIC_DEAD, RTINIFILE_MAGIC), UINT32_MAX);
+ RTMemFree(pThis->paSections);
+ pThis->paSections = NULL;
+ RTMemFree(pThis->pszFile);
+ pThis->pszFile = NULL;
+ RTVfsFileRelease(pThis->hVfsFile);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+ RTMemFree(pThis);
+ }
+ return cRefs;
+}
+
+
+/**
+ * Worker for RTIniFileQueryValue.
+ */
+static int rtIniFileQueryValueInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, const char *pszKey, size_t cchKey,
+ char *pszValue, size_t cbValue, size_t *pcbActual)
+{
+ /*
+ * Scan the section, looking for the matching key.
+ */
+ Assert(pSection->cchSkipToValues <= pSection->cchSection);
+ const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection];
+ const char * pszNext = pszEnd;
+ for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues];
+ (uintptr_t)psz < (uintptr_t)pszEnd;
+ psz = pszNext)
+ {
+ /* Find start of next line so we can use 'continue' to skip a line. */
+ pszNext = strchr(psz, '\n');
+ if (pszNext)
+ pszNext++;
+ else
+ pszNext = pszEnd;
+
+ /* Skip leading spaces. */
+ char ch;
+ while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch))
+ psz++;
+ if ( ch != ';' /* comment line */
+ && ch != '\n' /* empty line */
+ && ch != '\r' /* empty line */
+ && (uintptr_t)psz < (uintptr_t)pszEnd)
+ {
+ /* Find end of key name, if any. */
+ const char *pszCurKey = psz;
+ size_t cchCurKey;
+ const char *pszEqual;
+ if (ch != '=')
+ {
+ /** @todo deal with escaped equal signs? */
+ pszEqual = strchr(psz, '=');
+ if (pszEqual)
+ {
+ if ((uintptr_t)pszEqual < (uintptr_t)pszNext)
+ cchCurKey = pszEqual - pszCurKey;
+ else
+ continue;
+ }
+ else
+ break;
+
+ /* Strip trailing spaces from the current key name. */
+ while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1]))
+ cchCurKey--;
+ }
+ else
+ {
+ cchCurKey = 0;
+ pszEqual = psz;
+ }
+
+ /* Match the keys. */
+ /** @todo escape sequences? */
+ if ( cchCurKey == cchKey
+ && RTStrNICmp(pszCurKey, pszKey, cchKey) == 0)
+ {
+ /*
+ * Copy out the return value, without quotes.
+ */
+
+ /* Skip leading blanks. */
+ psz = pszEqual + 1;
+ while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n')
+ psz++;
+
+ /* Strip trailing spaces. */
+ size_t cchCurValue = pszNext - psz;
+ while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1]))
+ cchCurValue--;
+
+ /* Strip quotes. */
+ if ( cchCurValue > 2
+ && ( (ch = *psz) == '"'
+ || ch == '\'' )
+ && psz[cchCurValue - 1] == ch)
+ {
+ cchCurValue -= 2;
+ psz++;
+ }
+
+ /* Do the copying. */
+ if (cchCurValue < cbValue)
+ {
+ memcpy(pszValue, psz, cchCurValue);
+ pszValue[cchCurValue] = '\0';
+ if (pcbActual)
+ *pcbActual = cchCurValue;
+ return VINF_SUCCESS;
+ }
+
+ if (cbValue > 0)
+ {
+ memcpy(pszValue, psz, cbValue - 1);
+ pszValue[cbValue - 1] = '\0';
+ }
+ if (pcbActual)
+ *pcbActual = cchCurValue + 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTIniFileQueryValue(RTINIFILE hIniFile, const char *pszSection, const char *pszKey,
+ char *pszValue, size_t cbValue, size_t *pcbActual)
+{
+ /*
+ * Validate input.
+ */
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ size_t const cchKey = strlen(pszKey);
+ if (cbValue)
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pcbActual, VERR_INVALID_POINTER);
+
+ /*
+ * Search relevant sections.
+ */
+ int rc;
+ if (pszSection == NULL)
+ rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[0], pszKey, cchKey, pszValue, cbValue, pcbActual);
+ else
+ {
+ rc = VERR_NOT_FOUND;
+ uint32_t const cchSection = (uint32_t)strlen(pszSection);
+ for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++)
+ if ( pThis->paSections[iSection].cchName == cchSection
+ && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0)
+ {
+ rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[iSection], pszKey, cchKey,
+ pszValue, cbValue, pcbActual);
+ if (rc != VERR_NOT_FOUND)
+ break;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Worker for RTIniFileQueryPair.
+ *
+ * This can also be used to count the number of pairs in a section.
+ */
+static int rtIniFileQueryPairInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, uint32_t *pidxPair,
+ char *pszKey, size_t cbKey, size_t *pcbKeyActual,
+ char *pszValue, size_t cbValue, size_t *pcbValueActual)
+{
+ uint32_t idxPair = *pidxPair;
+
+ /*
+ * Scan the section, looking for the matching key.
+ */
+ Assert(pSection->cchSkipToValues <= pSection->cchSection);
+ const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection];
+ const char * pszNext = pszEnd;
+ for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues];
+ (uintptr_t)psz < (uintptr_t)pszEnd;
+ psz = pszNext)
+ {
+ /* Find start of next line so we can use 'continue' to skip a line. */
+ pszNext = strchr(psz, '\n');
+ if (pszNext)
+ pszNext++;
+ else
+ pszNext = pszEnd;
+
+ /* Skip leading spaces. */
+ char ch;
+ while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch))
+ psz++;
+ if ( ch != ';' /* comment line */
+ && ch != '\n' /* empty line */
+ && ch != '\r' /* empty line */
+ && (uintptr_t)psz < (uintptr_t)pszEnd)
+ {
+ /* Find end of key name, if any. */
+ const char *pszCurKey = psz;
+ size_t cchCurKey;
+ const char *pszEqual;
+ if (ch != '=')
+ {
+ /** @todo deal with escaped equal signs? */
+ pszEqual = strchr(psz, '=');
+ if (pszEqual)
+ {
+ if ((uintptr_t)pszEqual < (uintptr_t)pszNext)
+ cchCurKey = pszEqual - pszCurKey;
+ else
+ continue;
+ }
+ else
+ break;
+ }
+ else
+ {
+ cchCurKey = 0;
+ pszEqual = psz;
+ }
+
+ /* Is this the pair we're looking for? */
+ if (idxPair > 0)
+ idxPair--;
+ else
+ {
+ /*
+ * Yes it's the stuff we're looking for.
+ * Prepare the the return stuff.
+ */
+
+ /* Strip trailing spaces from the key name. */
+ while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1]))
+ cchCurKey--;
+
+ /* Skip leading blanks from the value. */
+ psz = pszEqual + 1;
+ while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n')
+ psz++;
+
+ /* Strip trailing spaces from the value. */
+ size_t cchCurValue = pszNext - psz;
+ while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1]))
+ cchCurValue--;
+
+ /* Strip value quotes. */
+ if ( cchCurValue > 2
+ && ( (ch = *psz) == '"'
+ || ch == '\'' )
+ && psz[cchCurValue - 1] == ch)
+ {
+ cchCurValue -= 2;
+ psz++;
+ }
+
+ /*
+ * Copy the stuff out.
+ */
+ if ( cchCurValue < cbValue
+ && cchCurKey < cbKey)
+ {
+ memcpy(pszKey, pszCurKey, cchCurKey);
+ pszKey[cchCurKey] = '\0';
+ if (pcbKeyActual)
+ *pcbKeyActual = cchCurKey;
+
+ memcpy(pszValue, psz, cchCurValue);
+ pszValue[cchCurValue] = '\0';
+ if (pcbValueActual)
+ *pcbValueActual = cchCurValue;
+
+ *pidxPair = 0;
+ return VINF_SUCCESS;
+ }
+
+ /* Buffer overflow. Copy out what we can. */
+ if (cbKey > 0)
+ {
+ if (cchCurKey < cbKey)
+ cbKey = cchCurKey + 1;
+ memcpy(pszKey, pszCurKey, cbKey - 1);
+ pszKey[cbKey - 1] = '\0';
+ }
+ if (pcbKeyActual)
+ *pcbKeyActual = cchCurKey + 1;
+
+ if (cbValue > 0)
+ {
+ if (cchCurValue < cbValue)
+ cbValue = cchCurValue + 1;
+ memcpy(pszValue, psz, cbValue - 1);
+ pszValue[cbValue - 1] = '\0';
+ }
+ if (pcbValueActual)
+ *pcbValueActual = cchCurValue + 1;
+
+ *pidxPair = 0;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ *pidxPair = idxPair;
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTIniFileQueryPair(RTINIFILE hIniFile, const char *pszSection, uint32_t idxPair,
+ char *pszKey, size_t cbKey, size_t *pcbKeyActual,
+ char *pszValue, size_t cbValue, size_t *pcbValueActual)
+{
+ /*
+ * Validate input.
+ */
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER);
+ if (cbKey)
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pcbKeyActual, VERR_INVALID_POINTER);
+ if (cbValue)
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pcbValueActual, VERR_INVALID_POINTER);
+
+ /*
+ * Search relevant sections.
+ */
+ int rc;
+ if (pszSection == NULL)
+ rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[0], &idxPair,
+ pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual);
+ else
+ {
+ rc = VERR_NOT_FOUND;
+ uint32_t const cchSection = (uint32_t)strlen(pszSection);
+ for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++)
+ if ( pThis->paSections[iSection].cchName == cchSection
+ && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0)
+ {
+ rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[iSection], &idxPair,
+ pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual);
+ if (rc != VERR_NOT_FOUND)
+ break;
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/misc/json.cpp b/src/VBox/Runtime/common/misc/json.cpp
new file mode 100644
index 00000000..a74a8bf2
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/json.cpp
@@ -0,0 +1,1914 @@
+/* $Id: json.cpp $ */
+/** @file
+ * IPRT JSON parser API (JSON).
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#include <iprt/json.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+
+#include <stdlib.h> /* strtod() */
+#include <errno.h> /* errno */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * JSON parser position information.
+ */
+typedef struct RTJSONPOS
+{
+ /** Line in the source. */
+ size_t iLine;
+ /** Current start character .*/
+ size_t iChStart;
+ /** Current end character. */
+ size_t iChEnd;
+} RTJSONPOS;
+/** Pointer to a position. */
+typedef RTJSONPOS *PRTJSONPOS;
+
+/**
+ * JSON token class.
+ */
+typedef enum RTJSONTOKENCLASS
+{
+ /** Invalid. */
+ RTJSONTOKENCLASS_INVALID = 0,
+ /** Array begin. */
+ RTJSONTOKENCLASS_BEGIN_ARRAY,
+ /** Object begin. */
+ RTJSONTOKENCLASS_BEGIN_OBJECT,
+ /** Array end. */
+ RTJSONTOKENCLASS_END_ARRAY,
+ /** Object end. */
+ RTJSONTOKENCLASS_END_OBJECT,
+ /** Separator for name/value pairs. */
+ RTJSONTOKENCLASS_NAME_SEPARATOR,
+ /** Value separator. */
+ RTJSONTOKENCLASS_VALUE_SEPARATOR,
+ /** String */
+ RTJSONTOKENCLASS_STRING,
+ /** Integer number. */
+ RTJSONTOKENCLASS_INTEGER,
+ /** Floating point number. */
+ RTJSONTOKENCLASS_NUMBER,
+ /** null keyword. */
+ RTJSONTOKENCLASS_NULL,
+ /** false keyword. */
+ RTJSONTOKENCLASS_FALSE,
+ /** true keyword. */
+ RTJSONTOKENCLASS_TRUE,
+ /** End of stream */
+ RTJSONTOKENCLASS_EOS,
+ /** 32bit hack. */
+ RTJSONTOKENCLASS_32BIT_HACK = 0x7fffffff
+} RTJSONTOKENCLASS;
+/** Pointer to a token class. */
+typedef RTJSONTOKENCLASS *PRTJSONTOKENCLASS;
+
+/**
+ * JSON token.
+ */
+typedef struct RTJSONTOKEN
+{
+ /** Token class. */
+ RTJSONTOKENCLASS enmClass;
+ /** Token position in the source buffer. */
+ RTJSONPOS Pos;
+ /** Data based on the token class. */
+ union
+ {
+ /** String. */
+ struct
+ {
+ /** Pointer to the start of the string. */
+ char *pszStr;
+ } String;
+ /** Number. */
+ struct
+ {
+ int64_t i64Num;
+ } Integer;
+ /** Floating point number. */
+ double rdNum;
+ } Class;
+} RTJSONTOKEN;
+/** Pointer to a JSON token. */
+typedef RTJSONTOKEN *PRTJSONTOKEN;
+/** Pointer to a const script token. */
+typedef const RTJSONTOKEN *PCRTJSONTOKEN;
+
+/**
+ * Tokenizer read input callback.
+ *
+ * @returns IPRT status code.
+ * @param pvUser Opaque user data for the callee.
+ * @param offInput Start offset from the start of the input stream to read from.
+ * @param pvBuf Where to store the read data.
+ * @param cbBuf How much to read.
+ * @param pcbRead Where to store the amount of data read on success.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTJSONTOKENIZERREAD,(void *pvUser, size_t offInput, void *pvBuf, size_t cbBuf,
+ size_t *pcbRead));
+/** Pointer to a tokenizer read buffer callback. */
+typedef FNRTJSONTOKENIZERREAD *PFNRTJSONTOKENIZERREAD;
+
+/**
+ * Tokenizer state.
+ */
+typedef struct RTJSONTOKENIZER
+{
+ /** Read callback. */
+ PFNRTJSONTOKENIZERREAD pfnRead;
+ /** Opaque user data. */
+ void *pvUser;
+ /** Current offset into the input stream. */
+ size_t offInput;
+ /** Number of valid bytes in the input buffer. */
+ size_t cbBuf;
+ /** Current offset into the input buffer. */
+ size_t offBuf;
+ /** Input cache buffer. */
+ char achBuf[512];
+ /** Current position into the input stream. */
+ RTJSONPOS Pos;
+ /** Token 1. */
+ RTJSONTOKEN Token1;
+ /** Token 2. */
+ RTJSONTOKEN Token2;
+ /** Pointer to the current active token. */
+ PRTJSONTOKEN pTokenCurr;
+ /** The next token in the input stream (used for peeking). */
+ PRTJSONTOKEN pTokenNext;
+ /** The tokenizer error state. */
+ int rcTok;
+ /** Where to return extended error information.*/
+ PRTERRINFO pErrInfo;
+} RTJSONTOKENIZER;
+/** Pointer to a JSON tokenizer. */
+typedef RTJSONTOKENIZER *PRTJSONTOKENIZER;
+
+/** Pointer to the internal JSON value instance. */
+typedef struct RTJSONVALINT *PRTJSONVALINT;
+
+/**
+ * A JSON value.
+ */
+typedef struct RTJSONVALINT
+{
+ /** Type of the JSON value. */
+ RTJSONVALTYPE enmType;
+ /** Reference count for this JSON value. */
+ volatile uint32_t cRefs;
+ /** Type dependent data. */
+ union
+ {
+ /** String type*/
+ struct
+ {
+ /** Pointer to the string. */
+ char *pszStr;
+ } String;
+ /** Number type. */
+ struct
+ {
+ /** Signed 64-bit integer. */
+ int64_t i64Num;
+ } Integer;
+ /** Floating point number . */
+ double rdNum;
+ /** Array type. */
+ struct
+ {
+ /** Number of elements in the array. */
+ unsigned cItems;
+ /** Pointer to the array of items. */
+ PRTJSONVALINT *papItems;
+ } Array;
+ /** Object type. */
+ struct
+ {
+ /** Number of members. */
+ unsigned cMembers;
+ /** Pointer to the array holding the member names. */
+ char **papszNames;
+ /** Pointer to the array holding the values. */
+ PRTJSONVALINT *papValues;
+ } Object;
+ } Type;
+} RTJSONVALINT;
+
+/**
+ * A JSON iterator.
+ */
+typedef struct RTJSONITINT
+{
+ /** Referenced JSON value. */
+ PRTJSONVALINT pJsonVal;
+ /** Current index. */
+ unsigned idxCur;
+} RTJSONITINT;
+/** Pointer to the internal JSON iterator instance. */
+typedef RTJSONITINT *PRTJSONITINT;
+
+/**
+ * Passing arguments for the read callbacks.
+ */
+typedef struct RTJSONREADERARGS
+{
+ /** Buffer/File size */
+ size_t cbData;
+ /** Data specific for one callback. */
+ union
+ {
+ PRTSTREAM hStream;
+ const uint8_t *pbBuf;
+ RTVFSFILE hVfsFile;
+ } u;
+} RTJSONREADERARGS;
+/** Pointer to a readers argument. */
+typedef RTJSONREADERARGS *PRTJSONREADERARGS;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtJsonParseValue(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken, PRTJSONVALINT *ppJsonVal);
+
+
+/**
+ * Fill the input buffer from the input stream.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ */
+static int rtJsonTokenizerRead(PRTJSONTOKENIZER pTokenizer)
+{
+ size_t cbRead = 0;
+ int rc = pTokenizer->pfnRead(pTokenizer->pvUser, pTokenizer->offInput, &pTokenizer->achBuf[0],
+ sizeof(pTokenizer->achBuf), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ pTokenizer->cbBuf = cbRead;
+ pTokenizer->offInput += cbRead;
+ pTokenizer->offBuf = 0;
+ /* Validate UTF-8 encoding. */
+ rc = RTStrValidateEncodingEx(&pTokenizer->achBuf[0], cbRead, 0 /* fFlags */);
+ /* If we read less than requested we reached the end and fill the remainder with terminators. */
+ if (cbRead < sizeof(pTokenizer->achBuf))
+ memset(&pTokenizer->achBuf[cbRead], 0, sizeof(pTokenizer->achBuf) - cbRead);
+ }
+
+ return rc;
+}
+
+/**
+ * Skips the given amount of characters in the input stream.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param cchSkip The amount of characters to skip.
+ */
+static int rtJsonTokenizerSkip(PRTJSONTOKENIZER pTokenizer, size_t cchSkip)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * In case we reached the end of the stream don't even attempt to read new data.
+ * Safety precaution for possible bugs in the parser causing out of bounds reads
+ */
+ if (pTokenizer->achBuf[pTokenizer->offBuf] == '\0')
+ return rc;
+
+ while ( cchSkip > 0
+ && pTokenizer->offBuf < pTokenizer->cbBuf
+ && RT_SUCCESS(rc))
+ {
+ size_t cchThisSkip = RT_MIN(cchSkip, pTokenizer->cbBuf - pTokenizer->offBuf);
+
+ pTokenizer->offBuf += cchThisSkip;
+ /* Read new data if required and we didn't reach the end yet. */
+ if ( pTokenizer->offBuf == pTokenizer->cbBuf
+ && pTokenizer->cbBuf == sizeof(pTokenizer->achBuf))
+ rc = rtJsonTokenizerRead(pTokenizer);
+
+ cchSkip -= cchThisSkip;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Returns whether the tokenizer reached the end of the stream.
+ *
+ * @returns true if the tokenizer reached the end of stream marker
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) rtJsonTokenizerIsEos(PRTJSONTOKENIZER pTokenizer)
+{
+ return pTokenizer->achBuf[pTokenizer->offBuf] == '\0';
+}
+
+/**
+ * Skip one character in the input stream.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) rtJsonTokenizerSkipCh(PRTJSONTOKENIZER pTokenizer)
+{
+ rtJsonTokenizerSkip(pTokenizer, 1);
+ pTokenizer->Pos.iChStart++;
+ pTokenizer->Pos.iChEnd++;
+}
+
+/**
+ * Returns the next char in the input buffer without advancing it.
+ *
+ * @returns Next character in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) rtJsonTokenizerPeekCh(PRTJSONTOKENIZER pTokenizer)
+{
+ return !rtJsonTokenizerIsEos(pTokenizer)
+ ? pTokenizer->achBuf[pTokenizer->offBuf + 1] /** @todo Read out of bounds */
+ : '\0';
+}
+
+/**
+ * Returns the next character in the input buffer advancing the internal
+ * position.
+ *
+ * @returns Next character in the stream.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) rtJsonTokenizerGetCh(PRTJSONTOKENIZER pTokenizer)
+{
+ char ch;
+
+ if (!rtJsonTokenizerIsEos(pTokenizer))
+ ch = pTokenizer->achBuf[pTokenizer->offBuf];
+ else
+ ch = '\0';
+
+ return ch;
+}
+
+/**
+ * Sets a new line for the tokenizer.
+ *
+ * @param pTokenizer The tokenizer state.
+ * @param cSkip Amount of characters to skip making up the new line.
+ */
+DECLINLINE(void) rtJsonTokenizerNewLine(PRTJSONTOKENIZER pTokenizer, unsigned cSkip)
+{
+ rtJsonTokenizerSkip(pTokenizer, cSkip);
+ pTokenizer->Pos.iLine++;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+}
+
+/**
+ * Checks whether the current position in the input stream is a new line
+ * and skips it.
+ *
+ * @returns Flag whether there was a new line at the current position
+ * in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) rtJsonTokenizerIsSkipNewLine(PRTJSONTOKENIZER pTokenizer)
+{
+ bool fNewline = true;
+
+ if ( rtJsonTokenizerGetCh(pTokenizer) == '\r'
+ && rtJsonTokenizerPeekCh(pTokenizer) == '\n')
+ rtJsonTokenizerNewLine(pTokenizer, 2);
+ else if (rtJsonTokenizerGetCh(pTokenizer) == '\n')
+ rtJsonTokenizerNewLine(pTokenizer, 1);
+ else
+ fNewline = false;
+
+ return fNewline;
+}
+
+/**
+ * Skip all whitespace starting from the current input buffer position.
+ * Skips all present comments too.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) rtJsonTokenizerSkipWhitespace(PRTJSONTOKENIZER pTokenizer)
+{
+ while (!rtJsonTokenizerIsEos(pTokenizer))
+ {
+ while ( rtJsonTokenizerGetCh(pTokenizer) == ' '
+ || rtJsonTokenizerGetCh(pTokenizer) == '\t')
+ rtJsonTokenizerSkipCh(pTokenizer);
+
+ if ( !rtJsonTokenizerIsEos(pTokenizer)
+ && !rtJsonTokenizerIsSkipNewLine(pTokenizer))
+ break; /* Skipped everything, next is some real content. */
+ }
+}
+
+/**
+ * Get an literal token from the tokenizer.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetLiteral(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ int rc = VINF_SUCCESS;
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ size_t cchLiteral = 0;
+ char szLiteral[6]; /* false + 0 terminator as the lingest possible literal. */
+ RT_ZERO(szLiteral);
+
+ pToken->Pos = pTokenizer->Pos;
+
+ Assert(RT_C_IS_ALPHA(ch));
+
+ while ( RT_C_IS_ALPHA(ch)
+ && cchLiteral < RT_ELEMENTS(szLiteral) - 1)
+ {
+ szLiteral[cchLiteral] = ch;
+ cchLiteral++;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ if (!RTStrNCmp(&szLiteral[0], "false", RT_ELEMENTS(szLiteral)))
+ pToken->enmClass = RTJSONTOKENCLASS_FALSE;
+ else if (!RTStrNCmp(&szLiteral[0], "true", RT_ELEMENTS(szLiteral)))
+ pToken->enmClass = RTJSONTOKENCLASS_TRUE;
+ else if (!RTStrNCmp(&szLiteral[0], "null", RT_ELEMENTS(szLiteral)))
+ pToken->enmClass = RTJSONTOKENCLASS_NULL;
+ else
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_INVALID;
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "malformed literal '%.6s' (line %zu col %zu)",
+ &szLiteral[0], pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ pToken->Pos.iChEnd += cchLiteral;
+ return rc;
+}
+
+/**
+ * Get a numerical constant from the tokenizer.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetNumber(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ size_t cchNum = 0;
+ char szTmp[128]; /* Everything larger is not possible to display in signed 64bit. */
+
+ pToken->enmClass = RTJSONTOKENCLASS_INTEGER;
+
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == '-')
+ {
+ szTmp[cchNum++] = '-';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ while ( RT_C_IS_DIGIT(ch)
+ && cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum] = ch;
+ cchNum++;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ int rc = VINF_SUCCESS;
+ if (RT_C_IS_DIGIT(ch) && cchNum >= sizeof(szTmp) - 1)
+ rc = VERR_NUMBER_TOO_BIG;
+ else if (ch != '.')
+ {
+ szTmp[cchNum] = '\0';
+ rc = RTStrToInt64Ex(&szTmp[0], NULL, 10, &pToken->Class.Integer.i64Num);
+ Assert(RT_SUCCESS(rc) || rc == VWRN_NUMBER_TOO_BIG);
+ if (rc == VWRN_NUMBER_TOO_BIG)
+ rc = VERR_NUMBER_TOO_BIG;
+ }
+ else
+ {
+ /*
+ * A floating point value.
+ */
+ pToken->enmClass = RTJSONTOKENCLASS_NUMBER;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ szTmp[cchNum++] = '.';
+
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ while ( RT_C_IS_DIGIT(ch)
+ && cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+ if ( (ch == 'e' || ch == 'E')
+ && cchNum < sizeof(szTmp) - 2)
+ {
+ szTmp[cchNum++] = 'e';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == '+' || ch == '-')
+ {
+ szTmp[cchNum++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+ while ( RT_C_IS_DIGIT(ch)
+ && cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+ }
+ if (cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum] = '\0';
+
+ /** @todo Not sure if strtod does the 100% right thing here... */
+ errno = 0;
+ char *pszNext = NULL;
+ pToken->Class.rdNum = strtod(szTmp, &pszNext);
+ if (errno == 0)
+ {
+ rc = VINF_SUCCESS;
+ Assert(!pszNext || *pszNext == '\0');
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Parses a string constant.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetString(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ size_t cchStrMax = 64;
+ char *pszDecoded = (char *)RTStrAlloc(cchStrMax);
+ AssertReturn(pszDecoded, VERR_NO_STR_MEMORY);
+
+ Assert(rtJsonTokenizerGetCh(pTokenizer) == '\"');
+ rtJsonTokenizerSkipCh(pTokenizer); /* Skip " */
+
+ pToken->enmClass = RTJSONTOKENCLASS_STRING;
+ pToken->Pos = pTokenizer->Pos;
+
+ size_t cchStr = 0;
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ while ( ch != '\"'
+ && ch != '\0')
+ {
+ if (ch != '\\')
+ {
+ pszDecoded[cchStr++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else
+ {
+ /* Escape sequence, check the next character */
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chNext = rtJsonTokenizerGetCh(pTokenizer);
+ switch (chNext)
+ {
+ case '\"':
+ pszDecoded[cchStr++] = '\"';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case '\\':
+ pszDecoded[cchStr++] = '\\';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case '/':
+ pszDecoded[cchStr++] = '/';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'b':
+ pszDecoded[cchStr++] = '\b';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'n':
+ pszDecoded[cchStr++] = '\n';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'f':
+ pszDecoded[cchStr++] = '\f';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'r':
+ pszDecoded[cchStr++] = '\r';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 't':
+ pszDecoded[cchStr++] = '\t';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'u':
+ {
+ /* \uXXXX */
+ int rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX1 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX1))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX2 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX2))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX3 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX3))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX4 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX4))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+
+ RTUNICP uc = ((RTUTF16)(chX1 <= '9' ? chX1 - '0' : (chX1 & 7) + 9) << 12)
+ | ((RTUTF16)(chX2 <= '9' ? chX2 - '0' : (chX2 & 7) + 9) << 8)
+ | ((RTUTF16)(chX3 <= '9' ? chX3 - '0' : (chX3 & 7) + 9) << 4)
+ | ((RTUTF16)(chX4 <= '9' ? chX4 - '0' : (chX4 & 7) + 9));
+ if ( !RTUtf16IsHighSurrogate((RTUTF16)uc)
+ && !RTUtf16IsLowSurrogate((RTUTF16)uc))
+ rc = VINF_SUCCESS;
+ else if (RTUtf16IsHighSurrogate((RTUTF16)uc))
+ {
+ /* The must be a low surrogate pair following the high one: */
+ rc = VINF_SUCCESS;
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == '\\')
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else
+ rc = VERR_JSON_MISSING_SURROGATE_PAIR;
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == 'u')
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else
+ rc = VERR_JSON_MISSING_SURROGATE_PAIR;
+ chX1 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX1))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ chX2 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX2))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ chX3 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX3))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ chX4 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX4))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ if (RT_SUCCESS(rc))
+ {
+ RTUTF16 wc2 = ((RTUTF16)(chX1 <= '9' ? chX1 - '0' : (chX1 & 7) + 9) << 12)
+ | ((RTUTF16)(chX2 <= '9' ? chX2 - '0' : (chX2 & 7) + 9) << 8)
+ | ((RTUTF16)(chX3 <= '9' ? chX3 - '0' : (chX3 & 7) + 9) << 4)
+ | ((RTUTF16)(chX4 <= '9' ? chX4 - '0' : (chX4 & 7) + 9));
+ if (RTUtf16IsLowSurrogate(wc2))
+ uc = 0x10000 + (((uc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ else
+ rc = VERR_JSON_BAD_SURROGATE_PAIR_SEQUENCE;
+ }
+ }
+ else
+ rc = VERR_JSON_BAD_SURROGATE_PAIR_SEQUENCE;
+ if (RT_SUCCESS(rc))
+ {
+ if ( uc != 0
+ && uc != 0xfffe
+ && uc != 0xffff)
+ {
+ Assert(cchStr + RTStrCpSize(uc) < cchStrMax);
+ char *pszNext = RTStrPutCp(&pszDecoded[cchStr], uc);
+ Assert((size_t)(pszNext - &pszDecoded[cchStr]) == RTStrCpSize(uc));
+ cchStr += pszNext - &pszDecoded[cchStr];
+ break;
+ }
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_INVALID_CODEPOINT,
+ "Invalid \\u code point: %#x (line %zu col %zu)",
+ uc, pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ }
+ }
+ }
+ }
+ RTStrFree(pszDecoded);
+ if (rc == VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, rc, "Invalid \\u escape sequence (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ else if (rc == VERR_JSON_MISSING_SURROGATE_PAIR)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, rc, "Missing UTF-16 surrogate pair (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ else if (rc == VERR_JSON_BAD_SURROGATE_PAIR_SEQUENCE)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, rc, "Invalid UTF-16 surrogate pair (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ return rc;
+ }
+
+ default:
+ RTStrFree(pszDecoded);
+ return RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "bad escape sequence (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ }
+
+
+ if (cchStr < cchStrMax - 4)
+ { /* likely */ }
+ else
+ {
+ /* Increase string space. */
+ size_t cchStrMaxNew = cchStrMax < _4K ? cchStrMax * 2 : cchStrMax + _4K;
+ int rc = RTStrRealloc(&pszDecoded, cchStrMaxNew);
+ if (RT_SUCCESS(rc))
+ cchStrMax = cchStrMaxNew;
+ else
+ {
+ RTStrFree(pszDecoded);
+ return rc;
+ }
+ }
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ if (ch == '\"')
+ rtJsonTokenizerSkipCh(pTokenizer); /* Skip closing " */
+
+ Assert(cchStr < cchStrMax);
+ pszDecoded[cchStr] = '\0';
+ if (cchStrMax - cchStr >= cchStrMax / 2)
+ RTStrRealloc(&pszDecoded, cchStr + 1);
+ pToken->Class.String.pszStr = pszDecoded;
+
+ pToken->Pos.iChEnd = pTokenizer->Pos.iChEnd;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Get the end of stream token.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetEos(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ Assert(rtJsonTokenizerGetCh(pTokenizer) == '\0');
+
+ pToken->enmClass = RTJSONTOKENCLASS_EOS;
+ pToken->Pos = pTokenizer->Pos;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the next token from the tokenizer stream.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to read from.
+ * @param pToken Uninitialized token to fill the token data into.
+ */
+static int rtJsonTokenizerReadNextToken(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Skip all eventually existing whitespace and newlines first. */
+ rtJsonTokenizerSkipWhitespace(pTokenizer);
+
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_ALPHA(ch))
+ rc = rtJsonTokenizerGetLiteral(pTokenizer, pToken);
+ else if (RT_C_IS_DIGIT(ch) || ch == '-')
+ rc = rtJsonTokenizerGetNumber(pTokenizer, pToken);
+ else if (ch == '\"')
+ rc = rtJsonTokenizerGetString(pTokenizer, pToken);
+ else if (ch == '\0')
+ rc = rtJsonTokenizerGetEos(pTokenizer, pToken);
+ else if (ch == '{')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_BEGIN_OBJECT;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == '}')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_END_OBJECT;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == '[')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_BEGIN_ARRAY;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == ']')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_END_ARRAY;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == ':')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_NAME_SEPARATOR;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == ',')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_VALUE_SEPARATOR;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_INVALID;
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "bad token '%c' (line %zu col %zu)",
+ ch, pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_FAILURE(rc))
+ pTokenizer->rcTok = rc;
+
+ return rc;
+}
+
+/**
+ * Create a new tokenizer.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state to initialize.
+ * @param pfnRead Read callback for the input stream.
+ * @param pvUser Opaque user data to pass to the callback.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtJsonTokenizerInit(PRTJSONTOKENIZER pTokenizer, PFNRTJSONTOKENIZERREAD pfnRead, void *pvUser, PRTERRINFO pErrInfo)
+{
+ pTokenizer->pfnRead = pfnRead;
+ pTokenizer->pvUser = pvUser;
+ pTokenizer->offInput = 0;
+ pTokenizer->cbBuf = 0;
+ pTokenizer->offBuf = 0;
+ pTokenizer->Pos.iLine = 1;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+ pTokenizer->pTokenCurr = &pTokenizer->Token1;
+ pTokenizer->pTokenNext = &pTokenizer->Token2;
+ pTokenizer->rcTok = VINF_SUCCESS;
+ pTokenizer->pErrInfo = pErrInfo;
+
+ RT_ZERO(pTokenizer->achBuf);
+
+ /* Fill the input buffer. */
+ int rc = rtJsonTokenizerRead(pTokenizer);
+
+ /* Fill the tokenizer with two first tokens. */
+ if (RT_SUCCESS(rc))
+ rc = rtJsonTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenCurr);
+ if (RT_SUCCESS(rc))
+ rc = rtJsonTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+
+ return rc;
+}
+
+/**
+ * Cleans up any resources still in control of the given token.
+ *
+ * @param pToken The toke nto clean up.
+ */
+static void rtJsonTokenizerTokenCleanup(PRTJSONTOKEN pToken)
+{
+ if ( pToken->enmClass == RTJSONTOKENCLASS_STRING
+ && pToken->Class.String.pszStr)
+ RTStrFree(pToken->Class.String.pszStr);
+}
+
+/**
+ * Destroys a given tokenizer state.
+ *
+ * @param pTokenizer The tokenizer to destroy.
+ */
+static void rtJsonTokenizerDestroy(PRTJSONTOKENIZER pTokenizer)
+{
+ rtJsonTokenizerTokenCleanup(pTokenizer->pTokenCurr);
+ rtJsonTokenizerTokenCleanup(pTokenizer->pTokenNext);
+}
+
+/**
+ * Get the current token in the input stream.
+ *
+ * @returns Pointer to the next token in the stream.
+ * @param pTokenizer The tokenizer state.
+ * @param ppToken Where to store the pointer to the current token on success.
+ */
+DECLINLINE(int) rtJsonTokenizerGetToken(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN *ppToken)
+{
+ if (RT_SUCCESS(pTokenizer->rcTok))
+ {
+ *ppToken = pTokenizer->pTokenCurr;
+ return VINF_SUCCESS;
+ }
+
+ return pTokenizer->rcTok;
+}
+
+/**
+ * Consume the current token advancing to the next in the stream.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+static void rtJsonTokenizerConsume(PRTJSONTOKENIZER pTokenizer)
+{
+ PRTJSONTOKEN pTokenTmp = pTokenizer->pTokenCurr;
+
+ /* Switch next token to current token and read in the next token. */
+ pTokenizer->pTokenCurr = pTokenizer->pTokenNext;
+ pTokenizer->pTokenNext = pTokenTmp;
+ rtJsonTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+}
+
+/**
+ * Consumes the current token if it matches the given class returning an indicator.
+ *
+ * @returns true if the class matched and the token was consumed.
+ * @retval false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmClass The token class to match against.
+ */
+static bool rtJsonTokenizerConsumeIfMatched(PRTJSONTOKENIZER pTokenizer, RTJSONTOKENCLASS enmClass)
+{
+ PRTJSONTOKEN pToken = NULL;
+ int rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ if (RT_SUCCESS(rc))
+ {
+ if (pToken->enmClass == enmClass)
+ {
+ rtJsonTokenizerConsume(pTokenizer);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Destroys a given JSON value releasing the reference to all child values.
+ *
+ * @param pThis The JSON value to destroy.
+ */
+static void rtJsonValDestroy(PRTJSONVALINT pThis)
+{
+ switch (pThis->enmType)
+ {
+ case RTJSONVALTYPE_OBJECT:
+ for (unsigned i = 0; i < pThis->Type.Object.cMembers; i++)
+ {
+ RTStrFree(pThis->Type.Object.papszNames[i]);
+ RTJsonValueRelease(pThis->Type.Object.papValues[i]);
+ }
+ RTMemFree(pThis->Type.Object.papszNames);
+ RTMemFree(pThis->Type.Object.papValues);
+ break;
+ case RTJSONVALTYPE_ARRAY:
+ for (unsigned i = 0; i < pThis->Type.Array.cItems; i++)
+ RTJsonValueRelease(pThis->Type.Array.papItems[i]);
+ RTMemFree(pThis->Type.Array.papItems);
+ break;
+ case RTJSONVALTYPE_STRING:
+ RTStrFree(pThis->Type.String.pszStr);
+ break;
+ case RTJSONVALTYPE_INTEGER:
+ case RTJSONVALTYPE_NUMBER:
+ case RTJSONVALTYPE_NULL:
+ case RTJSONVALTYPE_TRUE:
+ case RTJSONVALTYPE_FALSE:
+ /* nothing to do. */
+ break;
+ default:
+ AssertMsgFailed(("Invalid JSON value type: %u\n", pThis->enmType));
+ }
+ RTMemFree(pThis);
+}
+
+/**
+ * Creates a new JSON value with the given type.
+ *
+ * @returns Pointer to JSON value on success, NULL if out of memory.
+ * @param enmType The JSON value type.
+ */
+static PRTJSONVALINT rtJsonValueCreate(RTJSONVALTYPE enmType)
+{
+ PRTJSONVALINT pThis = (PRTJSONVALINT)RTMemAllocZ(sizeof(RTJSONVALINT));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->enmType = enmType;
+ pThis->cRefs = 1;
+ }
+
+ return pThis;
+}
+
+/**
+ * Parses an JSON array.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to use.
+ * @param pJsonVal The JSON array value to fill in.
+ */
+static int rtJsonParseArray(PRTJSONTOKENIZER pTokenizer, PRTJSONVALINT pJsonVal)
+{
+ int rc = VINF_SUCCESS;
+ PRTJSONTOKEN pToken = NULL;
+ uint32_t cItems = 0;
+ uint32_t cItemsMax = 0;
+ PRTJSONVALINT *papItems = NULL;
+
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ while ( RT_SUCCESS(rc)
+ && pToken->enmClass != RTJSONTOKENCLASS_END_ARRAY
+ && pToken->enmClass != RTJSONTOKENCLASS_EOS)
+ {
+ PRTJSONVALINT pVal = NULL;
+ rc = rtJsonParseValue(pTokenizer, pToken, &pVal);
+ if (RT_SUCCESS(rc))
+ {
+ if (cItems == cItemsMax)
+ {
+ cItemsMax += 10;
+ PRTJSONVALINT *papItemsNew = (PRTJSONVALINT *)RTMemRealloc(papItems, cItemsMax * sizeof(PRTJSONVALINT));
+ if (RT_UNLIKELY(!papItemsNew))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ papItems = papItemsNew;
+ }
+
+ Assert(cItems < cItemsMax);
+ papItems[cItems] = pVal;
+ cItems++;
+ }
+
+ /* Skip value separator and continue with next token. */
+ bool fSkippedSep = rtJsonTokenizerConsumeIfMatched(pTokenizer, RTJSONTOKENCLASS_VALUE_SEPARATOR);
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+
+ if ( RT_SUCCESS(rc)
+ && !fSkippedSep
+ && pToken->enmClass != RTJSONTOKENCLASS_END_ARRAY)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of array (#1) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pToken->enmClass == RTJSONTOKENCLASS_END_ARRAY)
+ {
+ rtJsonTokenizerConsume(pTokenizer);
+ pJsonVal->Type.Array.cItems = cItems;
+ pJsonVal->Type.Array.papItems = papItems;
+ }
+ else
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of array (#2) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ for (uint32_t i = 0; i < cItems; i++)
+ RTJsonValueRelease(papItems[i]);
+ RTMemFree(papItems);
+ }
+
+ return rc;
+}
+
+/**
+ * Parses an JSON object.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to use.
+ * @param pJsonVal The JSON object value to fill in.
+ */
+static int rtJsonParseObject(PRTJSONTOKENIZER pTokenizer, PRTJSONVALINT pJsonVal)
+{
+ int rc = VINF_SUCCESS;
+ PRTJSONTOKEN pToken = NULL;
+ uint32_t cMembers = 0;
+ uint32_t cMembersMax = 0;
+ PRTJSONVALINT *papValues = NULL;
+ char **papszNames = NULL;
+
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ while ( RT_SUCCESS(rc)
+ && pToken->enmClass == RTJSONTOKENCLASS_STRING)
+ {
+ char *pszName = pToken->Class.String.pszStr; /* We can consume this string as it was allocated. */
+ pToken->Class.String.pszStr = NULL;
+
+ rtJsonTokenizerConsume(pTokenizer);
+ if (rtJsonTokenizerConsumeIfMatched(pTokenizer, RTJSONTOKENCLASS_NAME_SEPARATOR))
+ {
+ PRTJSONVALINT pVal = NULL;
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ if (RT_SUCCESS(rc))
+ rc = rtJsonParseValue(pTokenizer, pToken, &pVal);
+ if (RT_SUCCESS(rc))
+ {
+ if (cMembers == cMembersMax)
+ {
+ cMembersMax += 10;
+ PRTJSONVALINT *papValuesNew = (PRTJSONVALINT *)RTMemRealloc(papValues, cMembersMax * sizeof(PRTJSONVALINT));
+ char **papszNamesNew = (char **)RTMemRealloc(papszNames, cMembersMax * sizeof(char *));
+ if (RT_UNLIKELY(!papValuesNew || !papszNamesNew))
+ {
+ if (papValuesNew)
+ RTMemFree(papValuesNew);
+ if (papszNamesNew)
+ RTMemFree(papszNamesNew);
+ RTStrFree(pszName);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ papValues = papValuesNew;
+ papszNames = papszNamesNew;
+ }
+
+ Assert(cMembers < cMembersMax);
+ papszNames[cMembers] = pszName;
+ papValues[cMembers] = pVal;
+ cMembers++;
+
+ /* Skip value separator and continue with next token. */
+ bool fSkippedSep = rtJsonTokenizerConsumeIfMatched(pTokenizer, RTJSONTOKENCLASS_VALUE_SEPARATOR);
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+
+ if ( RT_SUCCESS(rc)
+ && !fSkippedSep
+ && pToken->enmClass != RTJSONTOKENCLASS_END_OBJECT)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of object (#1) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ else
+ RTStrFree(pszName);
+ }
+ else
+ {
+ RTStrFree(pszName);
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected name separator (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pToken->enmClass == RTJSONTOKENCLASS_END_OBJECT)
+ {
+ rtJsonTokenizerConsume(pTokenizer);
+ pJsonVal->Type.Object.cMembers = cMembers;
+ pJsonVal->Type.Object.papValues = papValues;
+ pJsonVal->Type.Object.papszNames = papszNames;
+ }
+ else
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of object (#2) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ for (uint32_t i = 0; i < cMembers; i++)
+ {
+ RTJsonValueRelease(papValues[i]);
+ RTStrFree(papszNames[i]);
+ }
+ RTMemFree(papValues);
+ RTMemFree(papszNames);
+ }
+
+ return rc;
+}
+
+/**
+ * Parses a single JSON value and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to use.
+ * @param pToken The token to parse.
+ * @param ppJsonVal Where to store the pointer to the JSON value on success.
+ */
+static int rtJsonParseValue(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken, PRTJSONVALINT *ppJsonVal)
+{
+ int rc = VINF_SUCCESS;
+ PRTJSONVALINT pVal = NULL;
+
+ switch (pToken->enmClass)
+ {
+ case RTJSONTOKENCLASS_BEGIN_ARRAY:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_ARRAY);
+ if (RT_LIKELY(pVal))
+ rc = rtJsonParseArray(pTokenizer, pVal);
+ break;
+ case RTJSONTOKENCLASS_BEGIN_OBJECT:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_OBJECT);
+ if (RT_LIKELY(pVal))
+ rc = rtJsonParseObject(pTokenizer, pVal);
+ break;
+ case RTJSONTOKENCLASS_STRING:
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_STRING);
+ if (RT_LIKELY(pVal))
+ pVal->Type.String.pszStr = pToken->Class.String.pszStr;
+ rtJsonTokenizerConsume(pTokenizer);
+ break;
+ case RTJSONTOKENCLASS_INTEGER:
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_INTEGER);
+ if (RT_LIKELY(pVal))
+ pVal->Type.Integer.i64Num = pToken->Class.Integer.i64Num;
+ rtJsonTokenizerConsume(pTokenizer);
+ break;
+ case RTJSONTOKENCLASS_NUMBER:
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_NUMBER);
+ if (RT_LIKELY(pVal))
+ pVal->Type.rdNum = pToken->Class.rdNum;
+ rtJsonTokenizerConsume(pTokenizer);
+ break;
+ case RTJSONTOKENCLASS_NULL:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_NULL);
+ break;
+ case RTJSONTOKENCLASS_FALSE:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_FALSE);
+ break;
+ case RTJSONTOKENCLASS_TRUE:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_TRUE);
+ break;
+
+ case RTJSONTOKENCLASS_INVALID:
+ Assert(!pTokenizer->pErrInfo || RTErrInfoIsSet(pTokenizer->pErrInfo));
+ rc = VERR_JSON_MALFORMED;
+ break;
+ case RTJSONTOKENCLASS_END_ARRAY:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected '}' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_END_OBJECT:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected ']' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_NAME_SEPARATOR:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected ':' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_VALUE_SEPARATOR:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected ',' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_EOS:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of object (#1) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ default:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "Unexpected token class %d (line %zu col %zu)",
+ pToken->enmClass, pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pVal)
+ *ppJsonVal = pVal;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (pVal)
+ rtJsonValDestroy(pVal);
+
+ return rc;
+}
+
+/**
+ * Entry point to parse a JSON document.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param ppJsonVal Where to store the root JSON value on success.
+ */
+static int rtJsonParse(PRTJSONTOKENIZER pTokenizer, PRTJSONVALINT *ppJsonVal)
+{
+ PRTJSONTOKEN pToken = NULL;
+ int rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ if (RT_SUCCESS(rc))
+ rc = rtJsonParseValue(pTokenizer, pToken, ppJsonVal);
+
+ return rc;
+}
+
+/**
+ * Read callback for RTJsonParseFromBuf().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromBuf(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ PRTJSONREADERARGS pArgs = (PRTJSONREADERARGS)pvUser;
+ size_t cbLeft = offInput < pArgs->cbData ? pArgs->cbData - offInput : 0;
+
+ if (cbLeft)
+ memcpy(pvBuf, &pArgs->u.pbBuf[offInput], RT_MIN(cbLeft, cbBuf));
+
+ *pcbRead = RT_MIN(cbLeft, cbBuf);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read callback for RTJsonParseFromString().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromString(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ const char *pszStr = (const char *)pvUser;
+ size_t cchStr = strlen(pszStr) + 1; /* Include zero terminator. */
+ size_t cbLeft = offInput < cchStr ? cchStr - offInput : 0;
+
+ if (cbLeft)
+ memcpy(pvBuf, &pszStr[offInput], RT_MIN(cbLeft, cbBuf));
+
+ *pcbRead = RT_MIN(cbLeft, cbBuf);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read callback for RTJsonParseFromFile().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromFile(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ PRTJSONREADERARGS pArgs = (PRTJSONREADERARGS)pvUser;
+
+ RT_NOREF_PV(offInput);
+
+ size_t cbRead = 0;
+ int rc = RTStrmReadEx(pArgs->u.hStream, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ *pcbRead = cbRead;
+
+ return rc;
+}
+
+/**
+ * Read callback for RTJsonParseFromVfsFile().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromVfsFile(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ PRTJSONREADERARGS pArgs = (PRTJSONREADERARGS)pvUser;
+
+ RT_NOREF_PV(offInput);
+
+ size_t cbRead = 0;
+ int rc = RTVfsFileRead(pArgs->u.hVfsFile, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ *pcbRead = cbRead;
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromBuf(PRTJSONVAL phJsonVal, const uint8_t *pbBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertPtrReturn(pbBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER);
+
+ RTJSONTOKENIZER Tokenizer;
+ RTJSONREADERARGS Args;
+ Args.cbData = cbBuf;
+ Args.u.pbBuf = pbBuf;
+
+ int rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromBuf, &Args, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromString(PRTJSONVAL phJsonVal, const char *pszStr, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszStr, VERR_INVALID_POINTER);
+
+ /** @todo r=bird: The rtJsonTokenizerParseFromString function does
+ * strlen() on the whole pszStr for each read. For larger strings (
+ * longer than sizeof(Tokenizer.achBuf)) it would be good to join
+ * forces with RTJsonParseFromBuf. */
+ RTJSONTOKENIZER Tokenizer;
+ int rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromString, (void *)pszStr, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromFile(PRTJSONVAL phJsonVal, const char *pszFilename, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ RTJSONREADERARGS Args;
+
+ Args.cbData = 0;
+ rc = RTStrmOpen(pszFilename, "r", &Args.u.hStream);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONTOKENIZER Tokenizer;
+
+ rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromFile, &Args, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+ RTStrmClose(Args.u.hStream);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromVfsFile(PRTJSONVAL phJsonVal, RTVFSFILE hVfsFile, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertReturn(hVfsFile != NIL_RTVFSFILE, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ RTJSONREADERARGS Args;
+ RTJSONTOKENIZER Tokenizer;
+
+ Args.cbData = 0;
+ Args.u.hVfsFile = hVfsFile;
+ rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromVfsFile, &Args, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+
+ return rc;
+}
+
+RTDECL(uint32_t) RTJsonValueRetain(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ 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) RTJsonValueRelease(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ if (pThis == NIL_RTJSONVAL)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ rtJsonValDestroy(pThis);
+ return cRefs;
+}
+
+RTDECL(RTJSONVALTYPE) RTJsonValueGetType(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, RTJSONVALTYPE_INVALID);
+
+ if (pThis == NIL_RTJSONVAL)
+ return RTJSONVALTYPE_INVALID;
+
+ return pThis->enmType;
+}
+
+
+RTDECL(const char *) RTJsonValueTypeName(RTJSONVALTYPE enmType)
+{
+ switch (enmType)
+ {
+ case RTJSONVALTYPE_INVALID: return "invalid";
+ case RTJSONVALTYPE_OBJECT: return "object";
+ case RTJSONVALTYPE_ARRAY: return "array";
+ case RTJSONVALTYPE_STRING: return "string";
+ case RTJSONVALTYPE_INTEGER: return "integer";
+ case RTJSONVALTYPE_NUMBER: return "number";
+ case RTJSONVALTYPE_NULL: return "null";
+ case RTJSONVALTYPE_TRUE: return "true";
+ case RTJSONVALTYPE_FALSE: return "false";
+ default: return "???";
+ }
+}
+
+
+#define RTJSON_ASSERT_VALID_HANDLE_AND_TYPE_RETURN(pJson, enmExpectedType, ret) do { \
+ AssertPtrReturn((pJson), (ret)); \
+ AssertReturn((pJson) != NIL_RTJSONVAL, (ret)); \
+ AssertReturn((pJson)->enmType == (enmExpectedType), (ret)); \
+ } while (0)
+
+#define RTJSON_TYPECHECK_RETURN(pJson, enmExpectedType) do {\
+ if ((pJson)->enmType == (enmExpectedType)) { /*likely*/ } \
+ else { /*AssertFailed();*/ return VERR_JSON_VALUE_INVALID_TYPE; } \
+ } while (0)
+
+
+#define RTJSON_TYPECHECK_CONTAINER_RETURN(pJson) do { \
+ if ( (pJson)->enmType == RTJSONVALTYPE_ARRAY \
+ || (pJson)->enmType == RTJSONVALTYPE_OBJECT) \
+ { /* likely */ } \
+ else { /*AssertFailed();*/ return VERR_JSON_VALUE_INVALID_TYPE;} \
+ } while (0)
+
+
+RTDECL(const char *) RTJsonValueGetString(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ RTJSON_ASSERT_VALID_HANDLE_AND_TYPE_RETURN(pThis, RTJSONVALTYPE_STRING, NULL);
+
+ return pThis->Type.String.pszStr;
+}
+
+
+RTDECL(int) RTJsonValueQueryString(RTJSONVAL hJsonVal, const char **ppszStr)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(ppszStr, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_STRING);
+ *ppszStr = pThis->Type.String.pszStr;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryInteger(RTJSONVAL hJsonVal, int64_t *pi64Num)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pi64Num, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_INTEGER);
+ *pi64Num = pThis->Type.Integer.i64Num;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryNumber(RTJSONVAL hJsonVal, double *prdNum)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(prdNum, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_NUMBER);
+ *prdNum = pThis->Type.rdNum;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryByName(RTJSONVAL hJsonVal, const char *pszName, PRTJSONVAL phJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_OBJECT);
+
+ int rc = VERR_NOT_FOUND;
+ for (unsigned i = 0; i < pThis->Type.Object.cMembers; i++)
+ {
+ if (!RTStrCmp(pThis->Type.Object.papszNames[i], pszName))
+ {
+ RTJsonValueRetain(pThis->Type.Object.papValues[i]);
+ *phJsonVal = pThis->Type.Object.papValues[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryIntegerByName(RTJSONVAL hJsonVal, const char *pszName, int64_t *pi64Num)
+{
+ RTJSONVAL hJsonValNum = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValNum);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTJsonValueQueryInteger(hJsonValNum, pi64Num);
+ RTJsonValueRelease(hJsonValNum);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryNumberByName(RTJSONVAL hJsonVal, const char *pszName, double *prdNum)
+{
+ RTJSONVAL hJsonValNum = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValNum);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTJsonValueQueryNumber(hJsonValNum, prdNum);
+ RTJsonValueRelease(hJsonValNum);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryStringByName(RTJSONVAL hJsonVal, const char *pszName, char **ppszStr)
+{
+ RTJSONVAL hJsonValStr = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValStr);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszStr = NULL;
+ rc = RTJsonValueQueryString(hJsonValStr, &pszStr);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszStr = RTStrDup(pszStr);
+ if (!*ppszStr)
+ rc = VERR_NO_STR_MEMORY;
+ }
+ RTJsonValueRelease(hJsonValStr);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryBooleanByName(RTJSONVAL hJsonVal, const char *pszName, bool *pfBoolean)
+{
+ AssertPtrReturn(pfBoolean, VERR_INVALID_POINTER);
+
+ RTJSONVAL hJsonValBool = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValBool);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONVALTYPE enmType = RTJsonValueGetType(hJsonValBool);
+ if (enmType == RTJSONVALTYPE_TRUE)
+ *pfBoolean = true;
+ else if (enmType == RTJSONVALTYPE_FALSE)
+ *pfBoolean = false;
+ else
+ rc = VERR_JSON_VALUE_INVALID_TYPE;
+ RTJsonValueRelease(hJsonValBool);
+ }
+
+ return rc;
+}
+
+RTDECL(unsigned) RTJsonValueGetArraySize(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ RTJSON_ASSERT_VALID_HANDLE_AND_TYPE_RETURN(pThis, RTJSONVALTYPE_ARRAY, 0);
+
+ return pThis->Type.Array.cItems;
+}
+
+RTDECL(int) RTJsonValueQueryArraySize(RTJSONVAL hJsonVal, unsigned *pcItems)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pcItems, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_ARRAY);
+ *pcItems = pThis->Type.Array.cItems;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryByIndex(RTJSONVAL hJsonVal, unsigned idx, PRTJSONVAL phJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_ARRAY);
+ if (RT_UNLIKELY(idx >= pThis->Type.Array.cItems))
+ return VERR_OUT_OF_RANGE;
+
+ RTJsonValueRetain(pThis->Type.Array.papItems[idx]);
+ *phJsonVal = pThis->Type.Array.papItems[idx];
+ return VINF_SUCCESS;
+}
+
+static int rtJsonIteratorBeginWorker(PRTJSONVALINT pThis, PRTJSONIT phJsonIt)
+{
+ PRTJSONITINT pIt = (PRTJSONITINT)RTMemTmpAllocZ(sizeof(RTJSONITINT));
+ if (pIt)
+ {
+ RTJsonValueRetain(pThis);
+ pIt->pJsonVal = pThis;
+ pIt->idxCur = 0;
+
+ *phJsonIt = pIt;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+RTDECL(int) RTJsonIteratorBegin(RTJSONVAL hJsonVal, PRTJSONIT phJsonIt)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonIt, VERR_INVALID_POINTER);
+ RTJSON_TYPECHECK_CONTAINER_RETURN(pThis);
+
+ return rtJsonIteratorBeginWorker(pThis, phJsonIt);
+}
+
+RTDECL(int) RTJsonIteratorBeginArray(RTJSONVAL hJsonVal, PRTJSONIT phJsonIt)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonIt, VERR_INVALID_POINTER);
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_ARRAY);
+
+ if (pThis->Type.Array.cItems > 0)
+ return rtJsonIteratorBeginWorker(pThis, phJsonIt);
+ return VERR_JSON_IS_EMPTY;
+}
+
+RTDECL(int) RTJsonIteratorBeginObject(RTJSONVAL hJsonVal, PRTJSONIT phJsonIt)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonIt, VERR_INVALID_POINTER);
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_OBJECT);
+
+ if (pThis->Type.Object.cMembers > 0)
+ return rtJsonIteratorBeginWorker(pThis, phJsonIt);
+ return VERR_JSON_IS_EMPTY;
+}
+
+RTDECL(int) RTJsonIteratorQueryValue(RTJSONIT hJsonIt, PRTJSONVAL phJsonVal, const char **ppszName)
+{
+ PRTJSONITINT pIt = hJsonIt;
+ AssertPtrReturn(pIt, VERR_INVALID_HANDLE);
+ AssertReturn(pIt != NIL_RTJSONIT, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PRTJSONVALINT pThis = pIt->pJsonVal;
+ if (pThis->enmType == RTJSONVALTYPE_ARRAY)
+ {
+ if (pIt->idxCur < pThis->Type.Array.cItems)
+ {
+ if (ppszName)
+ *ppszName = NULL;
+
+ RTJsonValueRetain(pThis->Type.Array.papItems[pIt->idxCur]);
+ *phJsonVal = pThis->Type.Array.papItems[pIt->idxCur];
+ }
+ else
+ rc = VERR_JSON_ITERATOR_END;
+ }
+ else
+ {
+ Assert(pThis->enmType == RTJSONVALTYPE_OBJECT);
+
+ if (pIt->idxCur < pThis->Type.Object.cMembers)
+ {
+ if (ppszName)
+ *ppszName = pThis->Type.Object.papszNames[pIt->idxCur];
+
+ RTJsonValueRetain(pThis->Type.Object.papValues[pIt->idxCur]);
+ *phJsonVal = pThis->Type.Object.papValues[pIt->idxCur];
+ }
+ else
+ rc = VERR_JSON_ITERATOR_END;
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonIteratorNext(RTJSONIT hJsonIt)
+{
+ PRTJSONITINT pIt = hJsonIt;
+ AssertPtrReturn(pIt, VERR_INVALID_HANDLE);
+ AssertReturn(pIt != NIL_RTJSONIT, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ PRTJSONVALINT pThis = pIt->pJsonVal;
+ if (pThis->enmType == RTJSONVALTYPE_ARRAY)
+ {
+ if (pIt->idxCur < pThis->Type.Array.cItems)
+ pIt->idxCur++;
+
+ if (pIt->idxCur == pThis->Type.Object.cMembers)
+ rc = VERR_JSON_ITERATOR_END;
+ }
+ else
+ {
+ Assert(pThis->enmType == RTJSONVALTYPE_OBJECT);
+
+ if (pIt->idxCur < pThis->Type.Object.cMembers)
+ pIt->idxCur++;
+
+ if (pIt->idxCur == pThis->Type.Object.cMembers)
+ rc = VERR_JSON_ITERATOR_END;
+ }
+
+ return rc;
+}
+
+RTDECL(void) RTJsonIteratorFree(RTJSONIT hJsonIt)
+{
+ PRTJSONITINT pThis = hJsonIt;
+ AssertPtrReturnVoid(pThis);
+
+ if (pThis == NIL_RTJSONIT)
+ return;
+
+ RTJsonValueRelease(pThis->pJsonVal);
+ RTMemTmpFree(pThis);
+}
diff --git a/src/VBox/Runtime/common/misc/lockvalidator.cpp b/src/VBox/Runtime/common/misc/lockvalidator.cpp
new file mode 100644
index 00000000..340f8dcf
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/lockvalidator.cpp
@@ -0,0 +1,4482 @@
+/* $Id: lockvalidator.cpp $ */
+/** @file
+ * IPRT - Lock Validator.
+ */
+
+/*
+ * 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/lockvalidator.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include "internal/lockvalidator.h"
+#include "internal/magics.h"
+#include "internal/strhash.h"
+#include "internal/thread.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Macro that asserts that a pointer is aligned correctly.
+ * Only used when fighting bugs. */
+#if 1
+# define RTLOCKVAL_ASSERT_PTR_ALIGN(p) \
+ AssertMsg(!((uintptr_t)(p) & (sizeof(uintptr_t) - 1)), ("%p\n", (p)));
+#else
+# define RTLOCKVAL_ASSERT_PTR_ALIGN(p) do { } while (0)
+#endif
+
+/** Hashes the class handle (pointer) into an apPriorLocksHash index. */
+#define RTLOCKVALCLASS_HASH(hClass) \
+ ( ((uintptr_t)(hClass) >> 6 ) \
+ % ( RT_SIZEOFMEMB(RTLOCKVALCLASSINT, apPriorLocksHash) \
+ / sizeof(PRTLOCKVALCLASSREF)) )
+
+/** The max value for RTLOCKVALCLASSINT::cRefs. */
+#define RTLOCKVALCLASS_MAX_REFS UINT32_C(0xffff0000)
+/** The max value for RTLOCKVALCLASSREF::cLookups. */
+#define RTLOCKVALCLASSREF_MAX_LOOKUPS UINT32_C(0xfffe0000)
+/** The absolute max value for RTLOCKVALCLASSREF::cLookups at which it will
+ * be set back to RTLOCKVALCLASSREF_MAX_LOOKUPS. */
+#define RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX UINT32_C(0xffff0000)
+
+
+/** @def RTLOCKVAL_WITH_RECURSION_RECORDS
+ * Enable recursion records. */
+#if defined(IN_RING3) || defined(DOXYGEN_RUNNING)
+# define RTLOCKVAL_WITH_RECURSION_RECORDS 1
+#endif
+
+/** @def RTLOCKVAL_WITH_VERBOSE_DUMPS
+ * Enables some extra verbosity in the lock dumping. */
+#if defined(DOXYGEN_RUNNING)
+# define RTLOCKVAL_WITH_VERBOSE_DUMPS
+#endif
+
+/** @def RTLOCKVAL_WITH_CLASS_HASH_STATS
+ * Enables collection prior class hash lookup statistics, dumping them when
+ * complaining about the class. */
+#if defined(DEBUG) || defined(DOXYGEN_RUNNING)
+# define RTLOCKVAL_WITH_CLASS_HASH_STATS
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Deadlock detection stack entry.
+ */
+typedef struct RTLOCKVALDDENTRY
+{
+ /** The current record. */
+ PRTLOCKVALRECUNION pRec;
+ /** The current entry number if pRec is a shared one. */
+ uint32_t iEntry;
+ /** The thread state of the thread we followed to get to pFirstSibling.
+ * This is only used for validating a deadlock stack. */
+ RTTHREADSTATE enmState;
+ /** The thread we followed to get to pFirstSibling.
+ * This is only used for validating a deadlock stack. */
+ PRTTHREADINT pThread;
+ /** What pThread is waiting on, i.e. where we entered the circular list of
+ * siblings. This is used for validating a deadlock stack as well as
+ * terminating the sibling walk. */
+ PRTLOCKVALRECUNION pFirstSibling;
+} RTLOCKVALDDENTRY;
+
+
+/**
+ * Deadlock detection stack.
+ */
+typedef struct RTLOCKVALDDSTACK
+{
+ /** The number stack entries. */
+ uint32_t c;
+ /** The stack entries. */
+ RTLOCKVALDDENTRY a[32];
+} RTLOCKVALDDSTACK;
+/** Pointer to a deadlock detection stack. */
+typedef RTLOCKVALDDSTACK *PRTLOCKVALDDSTACK;
+
+
+/**
+ * Reference to another class.
+ */
+typedef struct RTLOCKVALCLASSREF
+{
+ /** The class. */
+ RTLOCKVALCLASS hClass;
+ /** The number of lookups of this class. */
+ uint32_t volatile cLookups;
+ /** Indicates whether the entry was added automatically during order checking
+ * (true) or manually via the API (false). */
+ bool fAutodidacticism;
+ /** Reserved / explicit alignment padding. */
+ bool afReserved[3];
+} RTLOCKVALCLASSREF;
+/** Pointer to a class reference. */
+typedef RTLOCKVALCLASSREF *PRTLOCKVALCLASSREF;
+
+
+/** Pointer to a chunk of class references. */
+typedef struct RTLOCKVALCLASSREFCHUNK *PRTLOCKVALCLASSREFCHUNK;
+/**
+ * Chunk of class references.
+ */
+typedef struct RTLOCKVALCLASSREFCHUNK
+{
+ /** Array of refs. */
+#if 0 /** @todo for testing allocation of new chunks. */
+ RTLOCKVALCLASSREF aRefs[ARCH_BITS == 32 ? 10 : 8];
+#else
+ RTLOCKVALCLASSREF aRefs[2];
+#endif
+ /** Pointer to the next chunk. */
+ PRTLOCKVALCLASSREFCHUNK volatile pNext;
+} RTLOCKVALCLASSREFCHUNK;
+
+
+/**
+ * Lock class.
+ */
+typedef struct RTLOCKVALCLASSINT
+{
+ /** AVL node core. */
+ AVLLU32NODECORE Core;
+ /** Magic value (RTLOCKVALCLASS_MAGIC). */
+ uint32_t volatile u32Magic;
+ /** Reference counter. See RTLOCKVALCLASS_MAX_REFS. */
+ uint32_t volatile cRefs;
+ /** Whether the class is allowed to teach it self new locking order rules. */
+ bool fAutodidact;
+ /** Whether to allow recursion. */
+ bool fRecursionOk;
+ /** Strict release order. */
+ bool fStrictReleaseOrder;
+ /** Whether this class is in the tree. */
+ bool fInTree;
+ /** Donate a reference to the next retainer. This is a hack to make
+ * RTLockValidatorClassCreateUnique work. */
+ bool volatile fDonateRefToNextRetainer;
+ /** Reserved future use / explicit alignment. */
+ bool afReserved[3];
+ /** The minimum wait interval for which we do deadlock detection
+ * (milliseconds). */
+ RTMSINTERVAL cMsMinDeadlock;
+ /** The minimum wait interval for which we do order checks (milliseconds). */
+ RTMSINTERVAL cMsMinOrder;
+ /** More padding. */
+ uint32_t au32Reserved[ARCH_BITS == 32 ? 5 : 2];
+ /** Classes that may be taken prior to this one.
+ * This is a linked list where each node contains a chunk of locks so that we
+ * reduce the number of allocations as well as localize the data. */
+ RTLOCKVALCLASSREFCHUNK PriorLocks;
+ /** Hash table containing frequently encountered prior locks. */
+ PRTLOCKVALCLASSREF apPriorLocksHash[17];
+ /** Class name. (Allocated after the end of the block as usual.) */
+ char const *pszName;
+ /** Where this class was created.
+ * This is mainly used for finding automatically created lock classes.
+ * @remarks The strings are stored after this structure so we won't crash
+ * if the class lives longer than the module (dll/so/dylib) that
+ * spawned it. */
+ RTLOCKVALSRCPOS CreatePos;
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ /** Hash hits. */
+ uint32_t volatile cHashHits;
+ /** Hash misses. */
+ uint32_t volatile cHashMisses;
+#endif
+} RTLOCKVALCLASSINT;
+AssertCompileSize(AVLLU32NODECORE, ARCH_BITS == 32 ? 20 : 32);
+AssertCompileMemberOffset(RTLOCKVALCLASSINT, PriorLocks, 64);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Serializing object destruction and deadlock detection.
+ *
+ * This makes sure that none of the memory examined by the deadlock detection
+ * code will become invalid (reused for other purposes or made not present)
+ * while the detection is in progress.
+ *
+ * NS: RTLOCKVALREC*, RTTHREADINT and RTLOCKVALDRECSHRD::papOwners destruction.
+ * EW: Deadlock detection and some related activities.
+ */
+static RTSEMXROADS g_hLockValidatorXRoads = NIL_RTSEMXROADS;
+/** Serializing class tree insert and lookups. */
+static RTSEMRW g_hLockValClassTreeRWLock= NIL_RTSEMRW;
+/** Class tree. */
+static PAVLLU32NODECORE g_LockValClassTree = NULL;
+/** Critical section serializing the teaching new rules to the classes. */
+static RTCRITSECT g_LockValClassTeachCS;
+
+/** Whether the lock validator is enabled or disabled.
+ * Only applies to new locks. */
+static bool volatile g_fLockValidatorEnabled = true;
+/** Set if the lock validator is quiet. */
+#ifdef RT_STRICT
+static bool volatile g_fLockValidatorQuiet = false;
+#else
+static bool volatile g_fLockValidatorQuiet = true;
+#endif
+/** Set if the lock validator may panic. */
+#ifdef RT_STRICT
+static bool volatile g_fLockValidatorMayPanic = true;
+#else
+static bool volatile g_fLockValidatorMayPanic = false;
+#endif
+/** Whether to return an error status on wrong locking order. */
+static bool volatile g_fLockValSoftWrongOrder = false;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass);
+static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread);
+
+
+/**
+ * Lazy initialization of the lock validator globals.
+ */
+static void rtLockValidatorLazyInit(void)
+{
+ static uint32_t volatile s_fInitializing = false;
+ if (ASMAtomicCmpXchgU32(&s_fInitializing, true, false))
+ {
+ /*
+ * The locks.
+ */
+ if (!RTCritSectIsInitialized(&g_LockValClassTeachCS))
+ RTCritSectInitEx(&g_LockValClassTeachCS, RTCRITSECT_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS,
+ RTLOCKVAL_SUB_CLASS_ANY, "RTLockVal-Teach");
+
+ if (g_hLockValClassTreeRWLock == NIL_RTSEMRW)
+ {
+ RTSEMRW hSemRW;
+ int rc = RTSemRWCreateEx(&hSemRW, RTSEMRW_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, "RTLockVal-Tree");
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteHandle(&g_hLockValClassTreeRWLock, hSemRW);
+ }
+
+ if (g_hLockValidatorXRoads == NIL_RTSEMXROADS)
+ {
+ RTSEMXROADS hXRoads;
+ int rc = RTSemXRoadsCreate(&hXRoads);
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteHandle(&g_hLockValidatorXRoads, hXRoads);
+ }
+
+#ifdef IN_RING3
+ /*
+ * Check the environment for our config variables.
+ */
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_ENABLED"))
+ ASMAtomicWriteBool(&g_fLockValidatorEnabled, true);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_DISABLED"))
+ ASMAtomicWriteBool(&g_fLockValidatorEnabled, false);
+
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_MAY_PANIC"))
+ ASMAtomicWriteBool(&g_fLockValidatorMayPanic, true);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_MAY_NOT_PANIC"))
+ ASMAtomicWriteBool(&g_fLockValidatorMayPanic, false);
+
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_NOT_QUIET"))
+ ASMAtomicWriteBool(&g_fLockValidatorQuiet, false);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_QUIET"))
+ ASMAtomicWriteBool(&g_fLockValidatorQuiet, true);
+
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_STRICT_ORDER"))
+ ASMAtomicWriteBool(&g_fLockValSoftWrongOrder, false);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_SOFT_ORDER"))
+ ASMAtomicWriteBool(&g_fLockValSoftWrongOrder, true);
+#endif
+
+ /*
+ * Register cleanup
+ */
+ /** @todo register some cleanup callback if we care. */
+
+ ASMAtomicWriteU32(&s_fInitializing, false);
+ }
+}
+
+
+
+/** Wrapper around ASMAtomicReadPtr. */
+DECL_FORCE_INLINE(PRTLOCKVALRECUNION) rtLockValidatorReadRecUnionPtr(PRTLOCKVALRECUNION volatile *ppRec)
+{
+ PRTLOCKVALRECUNION p = ASMAtomicReadPtrT(ppRec, PRTLOCKVALRECUNION);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(p);
+ return p;
+}
+
+
+/** Wrapper around ASMAtomicWritePtr. */
+DECL_FORCE_INLINE(void) rtLockValidatorWriteRecUnionPtr(PRTLOCKVALRECUNION volatile *ppRec, PRTLOCKVALRECUNION pRecNew)
+{
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRecNew);
+ ASMAtomicWritePtr(ppRec, pRecNew);
+}
+
+
+/** Wrapper around ASMAtomicReadPtr. */
+DECL_FORCE_INLINE(PRTTHREADINT) rtLockValidatorReadThreadHandle(RTTHREAD volatile *phThread)
+{
+ PRTTHREADINT p = ASMAtomicReadPtrT(phThread, PRTTHREADINT);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(p);
+ return p;
+}
+
+
+/** Wrapper around ASMAtomicUoReadPtr. */
+DECL_FORCE_INLINE(PRTLOCKVALRECSHRDOWN) rtLockValidatorUoReadSharedOwner(PRTLOCKVALRECSHRDOWN volatile *ppOwner)
+{
+ PRTLOCKVALRECSHRDOWN p = ASMAtomicUoReadPtrT(ppOwner, PRTLOCKVALRECSHRDOWN);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(p);
+ return p;
+}
+
+
+/**
+ * Reads a volatile thread handle field and returns the thread name.
+ *
+ * @returns Thread name (read only).
+ * @param phThread The thread handle field.
+ */
+static const char *rtLockValidatorNameThreadHandle(RTTHREAD volatile *phThread)
+{
+ PRTTHREADINT pThread = rtLockValidatorReadThreadHandle(phThread);
+ if (!pThread)
+ return "<NIL>";
+ if (!RT_VALID_PTR(pThread))
+ return "<INVALID>";
+ if (pThread->u32Magic != RTTHREADINT_MAGIC)
+ return "<BAD-THREAD-MAGIC>";
+ return pThread->szName;
+}
+
+
+/**
+ * Launch a simple assertion like complaint w/ panic.
+ *
+ * @param SRC_POS The source position where call is being made from.
+ * @param pszWhat What we're complaining about.
+ * @param ... Format arguments.
+ */
+static void rtLockValComplain(RT_SRC_POS_DECL, const char *pszWhat, ...)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ RTAssertMsg1Weak("RTLockValidator", iLine, pszFile, pszFunction);
+ va_list va;
+ va_start(va, pszWhat);
+ RTAssertMsg2WeakV(pszWhat, va);
+ va_end(va);
+ }
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ RTAssertPanic();
+}
+
+
+/**
+ * Describes the class.
+ *
+ * @param pszPrefix Message prefix.
+ * @param pClass The class to complain about.
+ * @param uSubClass My sub-class.
+ * @param fVerbose Verbose description including relations to other
+ * classes.
+ */
+static void rtLockValComplainAboutClass(const char *pszPrefix, RTLOCKVALCLASSINT *pClass, uint32_t uSubClass, bool fVerbose)
+{
+ if (ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ return;
+
+ /* Stringify the sub-class. */
+ const char *pszSubClass;
+ char szSubClass[32];
+ if (uSubClass < RTLOCKVAL_SUB_CLASS_USER)
+ switch (uSubClass)
+ {
+ case RTLOCKVAL_SUB_CLASS_NONE: pszSubClass = "none"; break;
+ case RTLOCKVAL_SUB_CLASS_ANY: pszSubClass = "any"; break;
+ default:
+ RTStrPrintf(szSubClass, sizeof(szSubClass), "invl-%u", uSubClass);
+ pszSubClass = szSubClass;
+ break;
+ }
+ else
+ {
+ RTStrPrintf(szSubClass, sizeof(szSubClass), "%u", uSubClass);
+ pszSubClass = szSubClass;
+ }
+
+ /* Validate the class pointer. */
+ if (!RT_VALID_PTR(pClass))
+ {
+ RTAssertMsg2AddWeak("%sbad class=%p sub-class=%s\n", pszPrefix, pClass, pszSubClass);
+ return;
+ }
+ if (pClass->u32Magic != RTLOCKVALCLASS_MAGIC)
+ {
+ RTAssertMsg2AddWeak("%sbad class=%p magic=%#x sub-class=%s\n", pszPrefix, pClass, pClass->u32Magic, pszSubClass);
+ return;
+ }
+
+ /* OK, dump the class info. */
+ RTAssertMsg2AddWeak("%sclass=%p %s created={%Rbn(%u) %Rfn %p} sub-class=%s\n", pszPrefix,
+ pClass,
+ pClass->pszName,
+ pClass->CreatePos.pszFile,
+ pClass->CreatePos.uLine,
+ pClass->CreatePos.pszFunction,
+ pClass->CreatePos.uId,
+ pszSubClass);
+ if (fVerbose)
+ {
+ uint32_t i = 0;
+ uint32_t cPrinted = 0;
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext)
+ for (unsigned j = 0; j < RT_ELEMENTS(pChunk->aRefs); j++, i++)
+ {
+ RTLOCKVALCLASSINT *pCurClass = pChunk->aRefs[j].hClass;
+ if (pCurClass != NIL_RTLOCKVALCLASS)
+ {
+ RTAssertMsg2AddWeak("%s%s #%02u: %s, %s, %u lookup%s\n", pszPrefix,
+ cPrinted == 0
+ ? "Prior:"
+ : " ",
+ i,
+ pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism
+ ? "autodidactic"
+ : "manually ",
+ pChunk->aRefs[j].cLookups,
+ pChunk->aRefs[j].cLookups != 1 ? "s" : "");
+ cPrinted++;
+ }
+ }
+ if (!cPrinted)
+ RTAssertMsg2AddWeak("%sPrior: none\n", pszPrefix);
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ RTAssertMsg2AddWeak("%sHash Stats: %u hits, %u misses\n", pszPrefix, pClass->cHashHits, pClass->cHashMisses);
+#endif
+ }
+ else
+ {
+ uint32_t cPrinted = 0;
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext)
+ for (unsigned j = 0; j < RT_ELEMENTS(pChunk->aRefs); j++)
+ {
+ RTLOCKVALCLASSINT *pCurClass = pChunk->aRefs[j].hClass;
+ if (pCurClass != NIL_RTLOCKVALCLASS)
+ {
+ if ((cPrinted % 10) == 0)
+ RTAssertMsg2AddWeak("%sPrior classes: %s%s", pszPrefix, pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism ? "*" : "");
+ else if ((cPrinted % 10) != 9)
+ RTAssertMsg2AddWeak(", %s%s", pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism ? "*" : "");
+ else
+ RTAssertMsg2AddWeak(", %s%s\n", pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism ? "*" : "");
+ cPrinted++;
+ }
+ }
+ if (!cPrinted)
+ RTAssertMsg2AddWeak("%sPrior classes: none\n", pszPrefix);
+ else if ((cPrinted % 10) != 0)
+ RTAssertMsg2AddWeak("\n");
+ }
+}
+
+
+/**
+ * Helper for getting the class name.
+ * @returns Class name string.
+ * @param pClass The class.
+ */
+static const char *rtLockValComplainGetClassName(RTLOCKVALCLASSINT *pClass)
+{
+ if (!pClass)
+ return "<nil-class>";
+ if (!RT_VALID_PTR(pClass))
+ return "<bad-class-ptr>";
+ if (pClass->u32Magic != RTLOCKVALCLASS_MAGIC)
+ return "<bad-class-magic>";
+ if (!pClass->pszName)
+ return "<no-class-name>";
+ return pClass->pszName;
+}
+
+/**
+ * Formats the sub-class.
+ *
+ * @returns Stringified sub-class.
+ * @param uSubClass The name.
+ * @param pszBuf Buffer that is big enough.
+ */
+static const char *rtLockValComplainGetSubClassName(uint32_t uSubClass, char *pszBuf)
+{
+ if (uSubClass < RTLOCKVAL_SUB_CLASS_USER)
+ switch (uSubClass)
+ {
+ case RTLOCKVAL_SUB_CLASS_NONE: return "none";
+ case RTLOCKVAL_SUB_CLASS_ANY: return "any";
+ default:
+ RTStrPrintf(pszBuf, 32, "invl-%u", uSubClass);
+ break;
+ }
+ else
+ RTStrPrintf(pszBuf, 32, "%x", uSubClass);
+ return pszBuf;
+}
+
+
+/**
+ * Helper for rtLockValComplainAboutLock.
+ */
+DECL_FORCE_INLINE(void) rtLockValComplainAboutLockHlp(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix,
+ uint32_t u32Magic, PCRTLOCKVALSRCPOS pSrcPos, uint32_t cRecursion,
+ const char *pszFrameType)
+{
+ char szBuf[32];
+ switch (u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+#ifdef RTLOCKVAL_WITH_VERBOSE_DUMPS
+ RTAssertMsg2AddWeak("%s%p %s xrec=%p own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix,
+ pRec->Excl.hLock, pRec->Excl.szName, pRec,
+ rtLockValidatorNameThreadHandle(&pRec->Excl.hThread), cRecursion,
+ rtLockValComplainGetClassName(pRec->Excl.hClass),
+ rtLockValComplainGetSubClassName(pRec->Excl.uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+#else
+ RTAssertMsg2AddWeak("%s%p %s own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix,
+ pRec->Excl.hLock, pRec->Excl.szName,
+ rtLockValidatorNameThreadHandle(&pRec->Excl.hThread), cRecursion,
+ rtLockValComplainGetClassName(pRec->Excl.hClass),
+ rtLockValComplainGetSubClassName(pRec->Excl.uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+#endif
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ RTAssertMsg2AddWeak("%ss %p %s srec=%p cls=%s/%s [s%s]%s", pszPrefix,
+ pRec->Shared.hLock, pRec->Shared.szName, pRec,
+ rtLockValComplainGetClassName(pRec->Shared.hClass),
+ rtLockValComplainGetSubClassName(pRec->Shared.uSubClass, szBuf),
+ pszFrameType, pszSuffix);
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pShared = pRec->ShrdOwner.pSharedRec;
+ if ( RT_VALID_PTR(pShared)
+ && pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)
+#ifdef RTLOCKVAL_WITH_VERBOSE_DUMPS
+ RTAssertMsg2AddWeak("%s%p %s srec=%p trec=%p own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [o%s]%s", pszPrefix,
+ pShared->hLock, pShared->szName, pShared,
+ pRec, rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion,
+ rtLockValComplainGetClassName(pShared->hClass),
+ rtLockValComplainGetSubClassName(pShared->uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszSuffix, pszSuffix);
+#else
+ RTAssertMsg2AddWeak("%s%p %s own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [o%s]%s", pszPrefix,
+ pShared->hLock, pShared->szName,
+ rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion,
+ rtLockValComplainGetClassName(pShared->hClass),
+ rtLockValComplainGetSubClassName(pShared->uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+#endif
+ else
+ RTAssertMsg2AddWeak("%sbad srec=%p trec=%p own=%s r=%u pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix,
+ pShared,
+ pRec, rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion,
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", u32Magic));
+ }
+}
+
+
+/**
+ * Describes the lock.
+ *
+ * @param pszPrefix Message prefix.
+ * @param pRec The lock record we're working on.
+ * @param pszSuffix Message suffix.
+ */
+static void rtLockValComplainAboutLock(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix)
+{
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+# define FIX_REC(r) 1
+#else
+# define FIX_REC(r) (r)
+#endif
+ if ( RT_VALID_PTR(pRec)
+ && !ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECEXCL_MAGIC,
+ &pRec->Excl.SrcPos, FIX_REC(pRec->Excl.cRecursion), "");
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECSHRD_MAGIC, NULL, 0, "");
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECSHRDOWN_MAGIC,
+ &pRec->ShrdOwner.SrcPos, FIX_REC(pRec->ShrdOwner.cRecursion), "");
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ uint32_t u32Magic;
+ if ( RT_VALID_PTR(pRealRec)
+ && ( (u32Magic = pRealRec->Core.u32Magic) == RTLOCKVALRECEXCL_MAGIC
+ || u32Magic == RTLOCKVALRECSHRD_MAGIC
+ || u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ )
+ rtLockValComplainAboutLockHlp(pszPrefix, pRealRec, pszSuffix, u32Magic,
+ &pRec->Nest.SrcPos, pRec->Nest.cRecursion, "/r");
+ else
+ RTAssertMsg2AddWeak("%sbad rrec=%p nrec=%p r=%u pos={%Rbn(%u) %Rfn %p}%s", pszPrefix,
+ pRealRec, pRec, pRec->Nest.cRecursion,
+ pRec->Nest.SrcPos.pszFile, pRec->Nest.SrcPos.uLine, pRec->Nest.SrcPos.pszFunction, pRec->Nest.SrcPos.uId,
+ pszSuffix);
+ break;
+ }
+
+ default:
+ RTAssertMsg2AddWeak("%spRec=%p u32Magic=%#x (bad)%s", pszPrefix, pRec, pRec->Core.u32Magic, pszSuffix);
+ break;
+ }
+ }
+#undef FIX_REC
+}
+
+
+/**
+ * Dump the lock stack.
+ *
+ * @param pThread The thread which lock stack we're gonna dump.
+ * @param cchIndent The indentation in chars.
+ * @param cMinFrames The minimum number of frames to consider
+ * dumping.
+ * @param pHighightRec Record that should be marked specially in the
+ * dump.
+ */
+static void rtLockValComplainAboutLockStack(PRTTHREADINT pThread, unsigned cchIndent, uint32_t cMinFrames,
+ PRTLOCKVALRECUNION pHighightRec)
+{
+ if ( RT_VALID_PTR(pThread)
+ && !ASMAtomicUoReadBool(&g_fLockValidatorQuiet)
+ && pThread->u32Magic == RTTHREADINT_MAGIC
+ )
+ {
+ uint32_t cEntries = rtLockValidatorStackDepth(pThread);
+ if (cEntries >= cMinFrames)
+ {
+ RTAssertMsg2AddWeak("%*s---- start of lock stack for %p %s - %u entr%s ----\n", cchIndent, "",
+ pThread, pThread->szName, cEntries, cEntries == 1 ? "y" : "ies");
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ for (uint32_t i = 0; RT_VALID_PTR(pCur); i++)
+ {
+ char szPrefix[80];
+ RTStrPrintf(szPrefix, sizeof(szPrefix), "%*s#%02u: ", cchIndent, "", i);
+ rtLockValComplainAboutLock(szPrefix, pCur, pHighightRec != pCur ? "\n" : " (*)\n");
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown); break;
+ case RTLOCKVALRECSHRDOWN_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown); break;
+ case RTLOCKVALRECNEST_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown); break;
+ default:
+ RTAssertMsg2AddWeak("%*s<bad stack frame>\n", cchIndent, "");
+ pCur = NULL;
+ break;
+ }
+ }
+ RTAssertMsg2AddWeak("%*s---- end of lock stack ----\n", cchIndent, "");
+ }
+ }
+}
+
+
+/**
+ * Launch the initial complaint.
+ *
+ * @param pszWhat What we're complaining about.
+ * @param pSrcPos Where we are complaining from, as it were.
+ * @param pThreadSelf The calling thread.
+ * @param pRec The main lock involved. Can be NULL.
+ * @param fDumpStack Whether to dump the lock stack (true) or not
+ * (false).
+ */
+static void rtLockValComplainFirst(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf,
+ PRTLOCKVALRECUNION pRec, bool fDumpStack)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ ASMCompilerBarrier(); /* paranoia */
+ RTAssertMsg1Weak("RTLockValidator", pSrcPos ? pSrcPos->uLine : 0, pSrcPos ? pSrcPos->pszFile : NULL, pSrcPos ? pSrcPos->pszFunction : NULL);
+ if (pSrcPos && pSrcPos->uId)
+ RTAssertMsg2Weak("%s [uId=%p thrd=%s]\n", pszWhat, pSrcPos->uId, RT_VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>");
+ else
+ RTAssertMsg2Weak("%s [thrd=%s]\n", pszWhat, RT_VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>");
+ rtLockValComplainAboutLock("Lock: ", pRec, "\n");
+ if (fDumpStack)
+ rtLockValComplainAboutLockStack(pThreadSelf, 0, 1, pRec);
+ }
+}
+
+
+/**
+ * Continue bitching.
+ *
+ * @param pszFormat Format string.
+ * @param ... Format arguments.
+ */
+static void rtLockValComplainMore(const char *pszFormat, ...)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2AddWeakV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * Raise a panic if enabled.
+ */
+static void rtLockValComplainPanic(void)
+{
+ if (ASMAtomicUoReadBool(&g_fLockValidatorMayPanic))
+ RTAssertPanic();
+}
+
+
+/**
+ * Copy a source position record.
+ *
+ * @param pDst The destination.
+ * @param pSrc The source. Can be NULL.
+ */
+DECL_FORCE_INLINE(void) rtLockValidatorSrcPosCopy(PRTLOCKVALSRCPOS pDst, PCRTLOCKVALSRCPOS pSrc)
+{
+ if (pSrc)
+ {
+ ASMAtomicUoWriteU32(&pDst->uLine, pSrc->uLine);
+ ASMAtomicUoWritePtr(&pDst->pszFile, pSrc->pszFile);
+ ASMAtomicUoWritePtr(&pDst->pszFunction, pSrc->pszFunction);
+ ASMAtomicUoWritePtr((void * volatile *)&pDst->uId, (void *)pSrc->uId);
+ }
+ else
+ {
+ ASMAtomicUoWriteU32(&pDst->uLine, 0);
+ ASMAtomicUoWriteNullPtr(&pDst->pszFile);
+ ASMAtomicUoWriteNullPtr(&pDst->pszFunction);
+ ASMAtomicUoWritePtr(&pDst->uId, (RTHCUINTPTR)0);
+ }
+}
+
+
+/**
+ * Init a source position record.
+ *
+ * @param pSrcPos The source position record.
+ */
+DECL_FORCE_INLINE(void) rtLockValidatorSrcPosInit(PRTLOCKVALSRCPOS pSrcPos)
+{
+ pSrcPos->pszFile = NULL;
+ pSrcPos->pszFunction = NULL;
+ pSrcPos->uId = 0;
+ pSrcPos->uLine = 0;
+#if HC_ARCH_BITS == 64
+ pSrcPos->u32Padding = 0;
+#endif
+}
+
+
+/**
+ * Hashes the specified source position.
+ *
+ * @returns Hash.
+ * @param pSrcPos The source position record.
+ */
+static uint32_t rtLockValidatorSrcPosHash(PCRTLOCKVALSRCPOS pSrcPos)
+{
+ uint32_t uHash;
+ if ( ( pSrcPos->pszFile
+ || pSrcPos->pszFunction)
+ && pSrcPos->uLine != 0)
+ {
+ uHash = 0;
+ if (pSrcPos->pszFile)
+ uHash = sdbmInc(pSrcPos->pszFile, uHash);
+ if (pSrcPos->pszFunction)
+ uHash = sdbmInc(pSrcPos->pszFunction, uHash);
+ uHash += pSrcPos->uLine;
+ }
+ else
+ {
+ Assert(pSrcPos->uId);
+ uHash = (uint32_t)pSrcPos->uId;
+ }
+
+ return uHash;
+}
+
+
+/**
+ * Compares two source positions.
+ *
+ * @returns 0 if equal, < 0 if pSrcPos1 is smaller than pSrcPos2, > 0 if
+ * otherwise.
+ * @param pSrcPos1 The first source position.
+ * @param pSrcPos2 The second source position.
+ */
+static int rtLockValidatorSrcPosCompare(PCRTLOCKVALSRCPOS pSrcPos1, PCRTLOCKVALSRCPOS pSrcPos2)
+{
+ if (pSrcPos1->uLine != pSrcPos2->uLine)
+ return pSrcPos1->uLine < pSrcPos2->uLine ? -1 : 1;
+
+ int iDiff = RTStrCmp(pSrcPos1->pszFile, pSrcPos2->pszFile);
+ if (iDiff != 0)
+ return iDiff;
+
+ iDiff = RTStrCmp(pSrcPos1->pszFunction, pSrcPos2->pszFunction);
+ if (iDiff != 0)
+ return iDiff;
+
+ if (pSrcPos1->uId != pSrcPos2->uId)
+ return pSrcPos1->uId < pSrcPos2->uId ? -1 : 1;
+ return 0;
+}
+
+
+
+/**
+ * Serializes destruction of RTLOCKVALREC* and RTTHREADINT structures.
+ */
+DECLHIDDEN(void) rtLockValidatorSerializeDestructEnter(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsNSEnter(hXRoads);
+}
+
+
+/**
+ * Call after rtLockValidatorSerializeDestructEnter.
+ */
+DECLHIDDEN(void) rtLockValidatorSerializeDestructLeave(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsNSLeave(hXRoads);
+}
+
+
+/**
+ * Serializes deadlock detection against destruction of the objects being
+ * inspected.
+ */
+DECLINLINE(void) rtLockValidatorSerializeDetectionEnter(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsEWEnter(hXRoads);
+}
+
+
+/**
+ * Call after rtLockValidatorSerializeDetectionEnter.
+ */
+DECLHIDDEN(void) rtLockValidatorSerializeDetectionLeave(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsEWLeave(hXRoads);
+}
+
+
+/**
+ * Initializes the per thread lock validator data.
+ *
+ * @param pPerThread The data.
+ */
+DECLHIDDEN(void) rtLockValidatorInitPerThread(RTLOCKVALPERTHREAD *pPerThread)
+{
+ pPerThread->bmFreeShrdOwners = UINT32_MAX;
+
+ /* ASSUMES the rest has already been zeroed. */
+ Assert(pPerThread->pRec == NULL);
+ Assert(pPerThread->cWriteLocks == 0);
+ Assert(pPerThread->cReadLocks == 0);
+ Assert(pPerThread->fInValidator == false);
+ Assert(pPerThread->pStackTop == NULL);
+}
+
+
+/**
+ * Delete the per thread lock validator data.
+ *
+ * @param pPerThread The data.
+ */
+DECLHIDDEN(void) rtLockValidatorDeletePerThread(RTLOCKVALPERTHREAD *pPerThread)
+{
+ /*
+ * Check that the thread doesn't own any locks at this time.
+ */
+ if (pPerThread->pStackTop)
+ {
+ rtLockValComplainFirst("Thread terminating owning locks!", NULL,
+ RT_FROM_MEMBER(pPerThread, RTTHREADINT, LockValidator),
+ pPerThread->pStackTop, true);
+ rtLockValComplainPanic();
+ }
+
+ /*
+ * Free the recursion records.
+ */
+ PRTLOCKVALRECNEST pCur = pPerThread->pFreeNestRecs;
+ pPerThread->pFreeNestRecs = NULL;
+ while (pCur)
+ {
+ PRTLOCKVALRECNEST pNext = pCur->pNextFree;
+ RTMemFree(pCur);
+ pCur = pNext;
+ }
+}
+
+RTDECL(int) RTLockValidatorClassCreateEx(PRTLOCKVALCLASS phClass, PCRTLOCKVALSRCPOS pSrcPos,
+ bool fAutodidact, bool fRecursionOk, bool fStrictReleaseOrder,
+ RTMSINTERVAL cMsMinDeadlock, RTMSINTERVAL cMsMinOrder,
+ const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(phClass, pSrcPos, fAutodidact, fRecursionOk, fStrictReleaseOrder,
+ cMsMinDeadlock, cMsMinOrder, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTLockValidatorClassCreateExV(PRTLOCKVALCLASS phClass, PCRTLOCKVALSRCPOS pSrcPos,
+ bool fAutodidact, bool fRecursionOk, bool fStrictReleaseOrder,
+ RTMSINTERVAL cMsMinDeadlock, RTMSINTERVAL cMsMinOrder,
+ const char *pszNameFmt, va_list va)
+{
+ Assert(cMsMinDeadlock >= 1);
+ Assert(cMsMinOrder >= 1);
+ AssertPtr(pSrcPos);
+
+ /*
+ * Format the name and calc its length.
+ */
+ size_t cbName;
+ char szName[32];
+ if (pszNameFmt && *pszNameFmt)
+ cbName = RTStrPrintfV(szName, sizeof(szName), pszNameFmt, va) + 1;
+ else
+ {
+ static uint32_t volatile s_cAnonymous = 0;
+ uint32_t i = ASMAtomicIncU32(&s_cAnonymous);
+ cbName = RTStrPrintf(szName, sizeof(szName), "anon-%u", i - 1) + 1;
+ }
+
+ /*
+ * Figure out the file and function name lengths and allocate memory for
+ * it all.
+ */
+ size_t const cbFile = pSrcPos->pszFile ? strlen(pSrcPos->pszFile) + 1 : 0;
+ size_t const cbFunction = pSrcPos->pszFunction ? strlen(pSrcPos->pszFunction) + 1 : 0;
+ RTLOCKVALCLASSINT *pThis = (RTLOCKVALCLASSINT *)RTMemAllocVarTag(sizeof(*pThis) + cbFile + cbFunction + cbName,
+ "may-leak:RTLockValidatorClassCreateExV");
+ if (!pThis)
+ return VERR_NO_MEMORY;
+ RTMEM_MAY_LEAK(pThis);
+
+ /*
+ * Initialize the class data.
+ */
+ pThis->Core.Key = rtLockValidatorSrcPosHash(pSrcPos);
+ pThis->Core.uchHeight = 0;
+ pThis->Core.pLeft = NULL;
+ pThis->Core.pRight = NULL;
+ pThis->Core.pList = NULL;
+ pThis->u32Magic = RTLOCKVALCLASS_MAGIC;
+ pThis->cRefs = 1;
+ pThis->fAutodidact = fAutodidact;
+ pThis->fRecursionOk = fRecursionOk;
+ pThis->fStrictReleaseOrder = fStrictReleaseOrder;
+ pThis->fInTree = false;
+ pThis->fDonateRefToNextRetainer = false;
+ pThis->afReserved[0] = false;
+ pThis->afReserved[1] = false;
+ pThis->afReserved[2] = false;
+ pThis->cMsMinDeadlock = cMsMinDeadlock;
+ pThis->cMsMinOrder = cMsMinOrder;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->au32Reserved); i++)
+ pThis->au32Reserved[i] = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->PriorLocks.aRefs); i++)
+ {
+ pThis->PriorLocks.aRefs[i].hClass = NIL_RTLOCKVALCLASS;
+ pThis->PriorLocks.aRefs[i].cLookups = 0;
+ pThis->PriorLocks.aRefs[i].fAutodidacticism = false;
+ pThis->PriorLocks.aRefs[i].afReserved[0] = false;
+ pThis->PriorLocks.aRefs[i].afReserved[1] = false;
+ pThis->PriorLocks.aRefs[i].afReserved[2] = false;
+ }
+ pThis->PriorLocks.pNext = NULL;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->apPriorLocksHash); i++)
+ pThis->apPriorLocksHash[i] = NULL;
+ char *pszDst = (char *)(pThis + 1);
+ pThis->pszName = (char *)memcpy(pszDst, szName, cbName);
+ pszDst += cbName;
+ rtLockValidatorSrcPosCopy(&pThis->CreatePos, pSrcPos);
+ pThis->CreatePos.pszFile = pSrcPos->pszFile ? (char *)memcpy(pszDst, pSrcPos->pszFile, cbFile) : NULL;
+ pszDst += cbFile;
+ pThis->CreatePos.pszFunction= pSrcPos->pszFunction ? (char *)memcpy(pszDst, pSrcPos->pszFunction, cbFunction) : NULL;
+ Assert(rtLockValidatorSrcPosHash(&pThis->CreatePos) == pThis->Core.Key);
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ pThis->cHashHits = 0;
+ pThis->cHashMisses = 0;
+#endif
+
+ *phClass = pThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorClassCreate(PRTLOCKVALCLASS phClass, bool fAutodidact, RT_SRC_POS_DECL, const char *pszNameFmt, ...)
+{
+ RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID();
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(phClass, &SrcPos,
+ fAutodidact, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/,
+ 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/,
+ pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Creates a new lock validator class with a reference that is consumed by the
+ * first call to RTLockValidatorClassRetain.
+ *
+ * This is tailored for use in the parameter list of a semaphore constructor.
+ *
+ * @returns Class handle with a reference that is automatically consumed by the
+ * first retainer. NIL_RTLOCKVALCLASS if we run into trouble.
+ *
+ * @param SRC_POS The source position where call is being made from.
+ * Use RT_SRC_POS when possible. Optional.
+ * @param pszNameFmt Class name format string, optional (NULL). Max
+ * length is 32 bytes.
+ * @param ... Format string arguments.
+ */
+RTDECL(RTLOCKVALCLASS) RTLockValidatorClassCreateUnique(RT_SRC_POS_DECL, const char *pszNameFmt, ...)
+{
+ RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID();
+ RTLOCKVALCLASSINT *pClass;
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(&pClass, &SrcPos,
+ true /*fAutodidact*/, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/,
+ 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/,
+ pszNameFmt, va);
+ va_end(va);
+ if (RT_FAILURE(rc))
+ return NIL_RTLOCKVALCLASS;
+ ASMAtomicWriteBool(&pClass->fDonateRefToNextRetainer, true); /* see rtLockValidatorClassRetain */
+ return pClass;
+}
+
+
+/**
+ * Internal class retainer.
+ * @returns The new reference count.
+ * @param pClass The class.
+ */
+DECL_FORCE_INLINE(uint32_t) rtLockValidatorClassRetain(RTLOCKVALCLASSINT *pClass)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pClass->cRefs);
+ if (cRefs > RTLOCKVALCLASS_MAX_REFS)
+ ASMAtomicWriteU32(&pClass->cRefs, RTLOCKVALCLASS_MAX_REFS);
+ else if ( cRefs == 2
+ && ASMAtomicXchgBool(&pClass->fDonateRefToNextRetainer, false))
+ cRefs = ASMAtomicDecU32(&pClass->cRefs);
+ return cRefs;
+}
+
+
+/**
+ * Validates and retains a lock validator class.
+ *
+ * @returns @a hClass on success, NIL_RTLOCKVALCLASS on failure.
+ * @param hClass The class handle. NIL_RTLOCKVALCLASS is ok.
+ */
+DECL_FORCE_INLINE(RTLOCKVALCLASS) rtLockValidatorClassValidateAndRetain(RTLOCKVALCLASS hClass)
+{
+ if (hClass == NIL_RTLOCKVALCLASS)
+ return hClass;
+ AssertPtrReturn(hClass, NIL_RTLOCKVALCLASS);
+ AssertReturn(hClass->u32Magic == RTLOCKVALCLASS_MAGIC, NIL_RTLOCKVALCLASS);
+ rtLockValidatorClassRetain(hClass);
+ return hClass;
+}
+
+
+/**
+ * Internal class releaser.
+ * @returns The new reference count.
+ * @param pClass The class.
+ */
+DECLINLINE(uint32_t) rtLockValidatorClassRelease(RTLOCKVALCLASSINT *pClass)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pClass->cRefs);
+ if (cRefs + 1 == RTLOCKVALCLASS_MAX_REFS)
+ ASMAtomicWriteU32(&pClass->cRefs, RTLOCKVALCLASS_MAX_REFS);
+ else if (!cRefs)
+ rtLockValidatorClassDestroy(pClass);
+ return cRefs;
+}
+
+
+/**
+ * Destroys a class once there are not more references to it.
+ *
+ * @param pClass The class.
+ */
+static void rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass)
+{
+ AssertReturnVoid(!pClass->fInTree);
+ ASMAtomicWriteU32(&pClass->u32Magic, RTLOCKVALCLASS_MAGIC_DEAD);
+
+ PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks;
+ while (pChunk)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++)
+ {
+ RTLOCKVALCLASSINT *pClass2 = pChunk->aRefs[i].hClass;
+ if (pClass2 != NIL_RTLOCKVALCLASS)
+ {
+ pChunk->aRefs[i].hClass = NIL_RTLOCKVALCLASS;
+ rtLockValidatorClassRelease(pClass2);
+ }
+ }
+
+ PRTLOCKVALCLASSREFCHUNK pNext = pChunk->pNext;
+ pChunk->pNext = NULL;
+ if (pChunk != &pClass->PriorLocks)
+ RTMemFree(pChunk);
+ pChunk = pNext;
+ }
+
+ RTMemFree(pClass);
+}
+
+
+RTDECL(RTLOCKVALCLASS) RTLockValidatorClassFindForSrcPos(PRTLOCKVALSRCPOS pSrcPos)
+{
+ if (g_hLockValClassTreeRWLock == NIL_RTSEMRW)
+ rtLockValidatorLazyInit();
+ int rcLock = RTSemRWRequestRead(g_hLockValClassTreeRWLock, RT_INDEFINITE_WAIT);
+
+ uint32_t uSrcPosHash = rtLockValidatorSrcPosHash(pSrcPos);
+ RTLOCKVALCLASSINT *pClass = (RTLOCKVALCLASSINT *)RTAvllU32Get(&g_LockValClassTree, uSrcPosHash);
+ while (pClass)
+ {
+ if (rtLockValidatorSrcPosCompare(&pClass->CreatePos, pSrcPos) == 0)
+ break;
+ pClass = (RTLOCKVALCLASSINT *)pClass->Core.pList;
+ }
+
+ if (RT_SUCCESS(rcLock))
+ RTSemRWReleaseRead(g_hLockValClassTreeRWLock);
+ return pClass;
+}
+
+
+RTDECL(RTLOCKVALCLASS) RTLockValidatorClassForSrcPos(RT_SRC_POS_DECL, const char *pszNameFmt, ...)
+{
+ RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID();
+ RTLOCKVALCLASS hClass = RTLockValidatorClassFindForSrcPos(&SrcPos);
+ if (hClass == NIL_RTLOCKVALCLASS)
+ {
+ /*
+ * Create a new class and insert it into the tree.
+ */
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(&hClass, &SrcPos,
+ true /*fAutodidact*/, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/,
+ 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/,
+ pszNameFmt, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ {
+ if (g_hLockValClassTreeRWLock == NIL_RTSEMRW)
+ rtLockValidatorLazyInit();
+ int rcLock = RTSemRWRequestWrite(g_hLockValClassTreeRWLock, RT_INDEFINITE_WAIT);
+
+ Assert(!hClass->fInTree);
+ hClass->fInTree = RTAvllU32Insert(&g_LockValClassTree, &hClass->Core);
+ Assert(hClass->fInTree);
+
+ if (RT_SUCCESS(rcLock))
+ RTSemRWReleaseWrite(g_hLockValClassTreeRWLock);
+ return hClass;
+ }
+ }
+ return hClass;
+}
+
+
+RTDECL(uint32_t) RTLockValidatorClassRetain(RTLOCKVALCLASS hClass)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ AssertPtrReturn(pClass, UINT32_MAX);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, UINT32_MAX);
+ return rtLockValidatorClassRetain(pClass);
+}
+
+
+RTDECL(uint32_t) RTLockValidatorClassRelease(RTLOCKVALCLASS hClass)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ if (pClass == NIL_RTLOCKVALCLASS)
+ return 0;
+ AssertPtrReturn(pClass, UINT32_MAX);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, UINT32_MAX);
+ return rtLockValidatorClassRelease(pClass);
+}
+
+
+/**
+ * Worker for rtLockValidatorClassIsPriorClass that does a linear search thru
+ * all the chunks for @a pPriorClass.
+ *
+ * @returns true / false.
+ * @param pClass The class to search.
+ * @param pPriorClass The class to search for.
+ */
+static bool rtLockValidatorClassIsPriorClassByLinearSearch(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass)
+{
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext)
+ for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++)
+ {
+ if (pChunk->aRefs[i].hClass == pPriorClass)
+ {
+ uint32_t cLookups = ASMAtomicIncU32(&pChunk->aRefs[i].cLookups);
+ if (RT_UNLIKELY(cLookups >= RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX))
+ {
+ ASMAtomicWriteU32(&pChunk->aRefs[i].cLookups, RTLOCKVALCLASSREF_MAX_LOOKUPS);
+ cLookups = RTLOCKVALCLASSREF_MAX_LOOKUPS;
+ }
+
+ /* update the hash table entry. */
+ PRTLOCKVALCLASSREF *ppHashEntry = &pClass->apPriorLocksHash[RTLOCKVALCLASS_HASH(pPriorClass)];
+ if ( !(*ppHashEntry)
+ || (*ppHashEntry)->cLookups + 128 < cLookups)
+ ASMAtomicWritePtr(ppHashEntry, &pChunk->aRefs[i]);
+
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ ASMAtomicIncU32(&pClass->cHashMisses);
+#endif
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Checks if @a pPriorClass is a known prior class.
+ *
+ * @returns true / false.
+ * @param pClass The class to search.
+ * @param pPriorClass The class to search for.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorClassIsPriorClass(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass)
+{
+ /*
+ * Hash lookup here.
+ */
+ PRTLOCKVALCLASSREF pRef = pClass->apPriorLocksHash[RTLOCKVALCLASS_HASH(pPriorClass)];
+ if ( pRef
+ && pRef->hClass == pPriorClass)
+ {
+ uint32_t cLookups = ASMAtomicIncU32(&pRef->cLookups);
+ if (RT_UNLIKELY(cLookups >= RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX))
+ ASMAtomicWriteU32(&pRef->cLookups, RTLOCKVALCLASSREF_MAX_LOOKUPS);
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ ASMAtomicIncU32(&pClass->cHashHits);
+#endif
+ return true;
+ }
+
+ return rtLockValidatorClassIsPriorClassByLinearSearch(pClass, pPriorClass);
+}
+
+
+/**
+ * Adds a class to the prior list.
+ *
+ * @returns VINF_SUCCESS, VERR_NO_MEMORY or VERR_SEM_LV_WRONG_ORDER.
+ * @param pClass The class to work on.
+ * @param pPriorClass The class to add.
+ * @param fAutodidacticism Whether we're teaching ourselves (true) or
+ * somebody is teaching us via the API (false).
+ * @param pSrcPos Where this rule was added (optional).
+ */
+static int rtLockValidatorClassAddPriorClass(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass,
+ bool fAutodidacticism, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ NOREF(pSrcPos);
+ if (!RTCritSectIsInitialized(&g_LockValClassTeachCS))
+ rtLockValidatorLazyInit();
+ int rcLock = RTCritSectEnter(&g_LockValClassTeachCS);
+
+ /*
+ * Check that there are no conflict (no assert since we might race each other).
+ */
+ int rc = VERR_SEM_LV_INTERNAL_ERROR;
+ if (!rtLockValidatorClassIsPriorClass(pPriorClass, pClass))
+ {
+ if (!rtLockValidatorClassIsPriorClass(pClass, pPriorClass))
+ {
+ /*
+ * Scan the table for a free entry, allocating a new chunk if necessary.
+ */
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; ; pChunk = pChunk->pNext)
+ {
+ bool fDone = false;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++)
+ {
+ ASMAtomicCmpXchgHandle(&pChunk->aRefs[i].hClass, pPriorClass, NIL_RTLOCKVALCLASS, fDone);
+ if (fDone)
+ {
+ pChunk->aRefs[i].fAutodidacticism = fAutodidacticism;
+ rtLockValidatorClassRetain(pPriorClass);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ if (fDone)
+ break;
+
+ /* If no more chunks, allocate a new one and insert the class before linking it. */
+ if (!pChunk->pNext)
+ {
+ PRTLOCKVALCLASSREFCHUNK pNew = (PRTLOCKVALCLASSREFCHUNK)RTMemAlloc(sizeof(*pNew));
+ if (!pNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ RTMEM_MAY_LEAK(pNew);
+ pNew->pNext = NULL;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pNew->aRefs); i++)
+ {
+ pNew->aRefs[i].hClass = NIL_RTLOCKVALCLASS;
+ pNew->aRefs[i].cLookups = 0;
+ pNew->aRefs[i].fAutodidacticism = false;
+ pNew->aRefs[i].afReserved[0] = false;
+ pNew->aRefs[i].afReserved[1] = false;
+ pNew->aRefs[i].afReserved[2] = false;
+ }
+
+ pNew->aRefs[0].hClass = pPriorClass;
+ pNew->aRefs[0].fAutodidacticism = fAutodidacticism;
+
+ ASMAtomicWritePtr(&pChunk->pNext, pNew);
+ rtLockValidatorClassRetain(pPriorClass);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ } /* chunk loop */
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_ORDER : VINF_SUCCESS;
+
+ if (RT_SUCCESS(rcLock))
+ RTCritSectLeave(&g_LockValClassTeachCS);
+ return rc;
+}
+
+
+RTDECL(int) RTLockValidatorClassAddPriorClass(RTLOCKVALCLASS hClass, RTLOCKVALCLASS hPriorClass)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ AssertPtrReturn(pClass, VERR_INVALID_HANDLE);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE);
+
+ RTLOCKVALCLASSINT *pPriorClass = hPriorClass;
+ AssertPtrReturn(pPriorClass, VERR_INVALID_HANDLE);
+ AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE);
+
+ return rtLockValidatorClassAddPriorClass(pClass, pPriorClass, false /*fAutodidacticism*/, NULL);
+}
+
+
+RTDECL(int) RTLockValidatorClassEnforceStrictReleaseOrder(RTLOCKVALCLASS hClass, bool fEnabled)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ AssertPtrReturn(pClass, VERR_INVALID_HANDLE);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE);
+
+ ASMAtomicWriteBool(&pClass->fStrictReleaseOrder, fEnabled);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unlinks all siblings.
+ *
+ * This is used during record deletion and assumes no races.
+ *
+ * @param pCore One of the siblings.
+ */
+static void rtLockValidatorUnlinkAllSiblings(PRTLOCKVALRECCORE pCore)
+{
+ /* ASSUMES sibling destruction doesn't involve any races and that all
+ related records are to be disposed off now. */
+ PRTLOCKVALRECUNION pSibling = (PRTLOCKVALRECUNION)pCore;
+ while (pSibling)
+ {
+ PRTLOCKVALRECUNION volatile *ppCoreNext;
+ switch (pSibling->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ case RTLOCKVALRECEXCL_MAGIC_DEAD:
+ ppCoreNext = &pSibling->Excl.pSibling;
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ case RTLOCKVALRECSHRD_MAGIC_DEAD:
+ ppCoreNext = &pSibling->Shared.pSibling;
+ break;
+
+ default:
+ AssertFailed();
+ ppCoreNext = NULL;
+ break;
+ }
+ if (RT_UNLIKELY(ppCoreNext))
+ break;
+ pSibling = ASMAtomicXchgPtrT(ppCoreNext, NULL, PRTLOCKVALRECUNION);
+ }
+}
+
+
+RTDECL(int) RTLockValidatorRecMakeSiblings(PRTLOCKVALRECCORE pRec1, PRTLOCKVALRECCORE pRec2)
+{
+ /*
+ * Validate input.
+ */
+ PRTLOCKVALRECUNION p1 = (PRTLOCKVALRECUNION)pRec1;
+ PRTLOCKVALRECUNION p2 = (PRTLOCKVALRECUNION)pRec2;
+
+ AssertPtrReturn(p1, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn( p1->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ || p1->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+
+ AssertPtrReturn(p2, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn( p2->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ || p2->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Link them (circular list).
+ */
+ if ( p1->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ && p2->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)
+ {
+ p1->Excl.pSibling = p2;
+ p2->Shared.pSibling = p1;
+ }
+ else if ( p1->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ && p2->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ {
+ p1->Shared.pSibling = p2;
+ p2->Excl.pSibling = p1;
+ }
+ else
+ AssertFailedReturn(VERR_SEM_LV_INVALID_PARAMETER); /* unsupported mix */
+
+ return VINF_SUCCESS;
+}
+
+
+#if 0 /* unused */
+/**
+ * Gets the lock name for the given record.
+ *
+ * @returns Read-only lock name.
+ * @param pRec The lock record.
+ */
+DECL_FORCE_INLINE(const char *) rtLockValidatorRecName(PRTLOCKVALRECUNION pRec)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.szName;
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.szName;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ return pRec->ShrdOwner.pSharedRec ? pRec->ShrdOwner.pSharedRec->szName : "orphaned";
+ case RTLOCKVALRECNEST_MAGIC:
+ pRec = rtLockValidatorReadRecUnionPtr(&pRec->Nest.pRec);
+ if (RT_VALID_PTR(pRec))
+ {
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.szName;
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.szName;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ return pRec->ShrdOwner.pSharedRec ? pRec->ShrdOwner.pSharedRec->szName : "orphaned";
+ default:
+ return "unknown-nested";
+ }
+ }
+ return "orphaned-nested";
+ default:
+ return "unknown";
+ }
+}
+#endif /* unused */
+
+
+#if 0 /* unused */
+/**
+ * Gets the class for this locking record.
+ *
+ * @returns Pointer to the class or NIL_RTLOCKVALCLASS.
+ * @param pRec The lock validator record.
+ */
+DECLINLINE(RTLOCKVALCLASSINT *) rtLockValidatorRecGetClass(PRTLOCKVALRECUNION pRec)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->hClass;
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ if (RT_VALID_PTR(pRealRec))
+ {
+ switch (pRealRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRealRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->hClass;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic));
+ break;
+ }
+ }
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ return NIL_RTLOCKVALCLASS;
+ }
+}
+#endif /* unused */
+
+/**
+ * Gets the class for this locking record and the pointer to the one below it in
+ * the stack.
+ *
+ * @returns Pointer to the class or NIL_RTLOCKVALCLASS.
+ * @param pRec The lock validator record.
+ * @param puSubClass Where to return the sub-class.
+ * @param ppDown Where to return the pointer to the record below.
+ */
+DECL_FORCE_INLINE(RTLOCKVALCLASSINT *)
+rtLockValidatorRecGetClassesAndDown(PRTLOCKVALRECUNION pRec, uint32_t *puSubClass, PRTLOCKVALRECUNION *ppDown)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ *ppDown = pRec->Excl.pDown;
+ *puSubClass = pRec->Excl.uSubClass;
+ return pRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ *ppDown = NULL;
+ *puSubClass = pRec->Shared.uSubClass;
+ return pRec->Shared.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ *ppDown = pRec->ShrdOwner.pDown;
+
+ PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ {
+ *puSubClass = pSharedRec->uSubClass;
+ return pSharedRec->hClass;
+ }
+ *puSubClass = RTLOCKVAL_SUB_CLASS_NONE;
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ *ppDown = pRec->Nest.pDown;
+
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ if (RT_VALID_PTR(pRealRec))
+ {
+ switch (pRealRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ *puSubClass = pRealRec->Excl.uSubClass;
+ return pRealRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ {
+ *puSubClass = pSharedRec->uSubClass;
+ return pSharedRec->hClass;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic));
+ break;
+ }
+ }
+ *puSubClass = RTLOCKVAL_SUB_CLASS_NONE;
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ *ppDown = NULL;
+ *puSubClass = RTLOCKVAL_SUB_CLASS_NONE;
+ return NIL_RTLOCKVALCLASS;
+ }
+}
+
+
+/**
+ * Gets the sub-class for a lock record.
+ *
+ * @returns the sub-class.
+ * @param pRec The lock validator record.
+ */
+DECLINLINE(uint32_t) rtLockValidatorRecGetSubClass(PRTLOCKVALRECUNION pRec)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.uSubClass;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.uSubClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->uSubClass;
+ return RTLOCKVAL_SUB_CLASS_NONE;
+ }
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ if (RT_VALID_PTR(pRealRec))
+ {
+ switch (pRealRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.uSubClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->uSubClass;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic));
+ break;
+ }
+ }
+ return RTLOCKVAL_SUB_CLASS_NONE;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ return RTLOCKVAL_SUB_CLASS_NONE;
+ }
+}
+
+
+
+
+/**
+ * Calculates the depth of a lock stack.
+ *
+ * @returns Number of stack frames.
+ * @param pThread The thread.
+ */
+static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread)
+{
+ uint32_t cEntries = 0;
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ while (RT_VALID_PTR(pCur))
+ {
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown);
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown);
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), cEntries);
+ }
+ cEntries++;
+ }
+ return cEntries;
+}
+
+
+#ifdef RT_STRICT
+/**
+ * Checks if the stack contains @a pRec.
+ *
+ * @returns true / false.
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ */
+static bool rtLockValidatorStackContainsRec(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop;
+ while (pCur)
+ {
+ AssertPtrReturn(pCur, false);
+ if (pCur == pRec)
+ return true;
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pCur->Excl.cRecursion >= 1);
+ pCur = pCur->Excl.pDown;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pCur->ShrdOwner.cRecursion >= 1);
+ pCur = pCur->ShrdOwner.pDown;
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ Assert(pCur->Nest.cRecursion > 1);
+ pCur = pCur->Nest.pDown;
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), false);
+ }
+ }
+ return false;
+}
+#endif /* RT_STRICT */
+
+
+/**
+ * Pushes a lock record onto the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ */
+static void rtLockValidatorStackPush(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+ Assert(!rtLockValidatorStackContainsRec(pThreadSelf, pRec));
+
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pRec->Excl.cRecursion == 1);
+ Assert(pRec->Excl.pDown == NULL);
+ rtLockValidatorWriteRecUnionPtr(&pRec->Excl.pDown, pThreadSelf->LockValidator.pStackTop);
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pRec->ShrdOwner.cRecursion == 1);
+ Assert(pRec->ShrdOwner.pDown == NULL);
+ rtLockValidatorWriteRecUnionPtr(&pRec->ShrdOwner.pDown, pThreadSelf->LockValidator.pStackTop);
+ break;
+
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic));
+ }
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pRec);
+}
+
+
+/**
+ * Pops a lock record off the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock.
+ */
+static void rtLockValidatorStackPop(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+
+ PRTLOCKVALRECUNION pDown;
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pRec->Excl.cRecursion == 0);
+ pDown = pRec->Excl.pDown;
+ rtLockValidatorWriteRecUnionPtr(&pRec->Excl.pDown, NULL); /* lazy bird */
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pRec->ShrdOwner.cRecursion == 0);
+ pDown = pRec->ShrdOwner.pDown;
+ rtLockValidatorWriteRecUnionPtr(&pRec->ShrdOwner.pDown, NULL);
+ break;
+
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic));
+ }
+ if (pThreadSelf->LockValidator.pStackTop == pRec)
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pDown);
+ else
+ {
+ /* Find the pointer to our record and unlink ourselves. */
+ PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop;
+ while (pCur)
+ {
+ PRTLOCKVALRECUNION volatile *ppDown;
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pCur->Excl.cRecursion >= 1);
+ ppDown = &pCur->Excl.pDown;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pCur->ShrdOwner.cRecursion >= 1);
+ ppDown = &pCur->ShrdOwner.pDown;
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ Assert(pCur->Nest.cRecursion >= 1);
+ ppDown = &pCur->Nest.pDown;
+ break;
+
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pCur->Core.u32Magic));
+ }
+ pCur = *ppDown;
+ if (pCur == pRec)
+ {
+ rtLockValidatorWriteRecUnionPtr(ppDown, pDown);
+ return;
+ }
+ }
+ AssertMsgFailed(("%p %p\n", pRec, pThreadSelf));
+ }
+}
+
+
+/**
+ * Creates and pushes lock recursion record onto the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ * @param pSrcPos Where the recursion occurred.
+ */
+static void rtLockValidatorStackPushRecursion(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+ Assert(rtLockValidatorStackContainsRec(pThreadSelf, pRec));
+
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+ /*
+ * Allocate a new recursion record
+ */
+ PRTLOCKVALRECNEST pRecursionRec = pThreadSelf->LockValidator.pFreeNestRecs;
+ if (pRecursionRec)
+ pThreadSelf->LockValidator.pFreeNestRecs = pRecursionRec->pNextFree;
+ else
+ {
+ pRecursionRec = (PRTLOCKVALRECNEST)RTMemAlloc(sizeof(*pRecursionRec));
+ if (!pRecursionRec)
+ return;
+ }
+
+ /*
+ * Initialize it.
+ */
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ pRecursionRec->cRecursion = pRec->Excl.cRecursion;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ pRecursionRec->cRecursion = pRec->ShrdOwner.cRecursion;
+ break;
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ rtLockValidatorSerializeDestructEnter();
+ rtLockValidatorSerializeDestructLeave();
+ RTMemFree(pRecursionRec);
+ return;
+ }
+ Assert(pRecursionRec->cRecursion > 1);
+ pRecursionRec->pRec = pRec;
+ pRecursionRec->pDown = NULL;
+ pRecursionRec->pNextFree = NULL;
+ rtLockValidatorSrcPosCopy(&pRecursionRec->SrcPos, pSrcPos);
+ pRecursionRec->Core.u32Magic = RTLOCKVALRECNEST_MAGIC;
+
+ /*
+ * Link it.
+ */
+ pRecursionRec->pDown = pThreadSelf->LockValidator.pStackTop;
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, (PRTLOCKVALRECUNION)pRecursionRec);
+#endif /* RTLOCKVAL_WITH_RECURSION_RECORDS */
+}
+
+
+/**
+ * Pops a lock recursion record off the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ */
+static void rtLockValidatorStackPopRecursion(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+ Assert(rtLockValidatorStackContainsRec(pThreadSelf, pRec));
+
+ uint32_t cRecursion;
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC: cRecursion = pRec->Excl.cRecursion; break;
+ case RTLOCKVALRECSHRDOWN_MAGIC: cRecursion = pRec->ShrdOwner.cRecursion; break;
+ default: AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic));
+ }
+ Assert(cRecursion >= 1);
+
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+ /*
+ * Pop the recursion record.
+ */
+ PRTLOCKVALRECUNION pNest = pThreadSelf->LockValidator.pStackTop;
+ if ( pNest != NULL
+ && pNest->Core.u32Magic == RTLOCKVALRECNEST_MAGIC
+ && pNest->Nest.pRec == pRec
+ )
+ {
+ Assert(pNest->Nest.cRecursion == cRecursion + 1);
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pNest->Nest.pDown);
+ }
+ else
+ {
+ /* Find the record above ours. */
+ PRTLOCKVALRECUNION volatile *ppDown = NULL;
+ for (;;)
+ {
+ AssertMsgReturnVoid(pNest, ("%p %p\n", pRec, pThreadSelf));
+ switch (pNest->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ ppDown = &pNest->Excl.pDown;
+ pNest = *ppDown;
+ continue;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ ppDown = &pNest->ShrdOwner.pDown;
+ pNest = *ppDown;
+ continue;
+ case RTLOCKVALRECNEST_MAGIC:
+ if (pNest->Nest.pRec == pRec)
+ break;
+ ppDown = &pNest->Nest.pDown;
+ pNest = *ppDown;
+ continue;
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pNest->Core.u32Magic));
+ }
+ break; /* ugly */
+ }
+ Assert(pNest->Nest.cRecursion == cRecursion + 1);
+ rtLockValidatorWriteRecUnionPtr(ppDown, pNest->Nest.pDown);
+ }
+
+ /*
+ * Invalidate and free the record.
+ */
+ ASMAtomicWriteU32(&pNest->Core.u32Magic, RTLOCKVALRECNEST_MAGIC);
+ rtLockValidatorWriteRecUnionPtr(&pNest->Nest.pDown, NULL);
+ rtLockValidatorWriteRecUnionPtr(&pNest->Nest.pRec, NULL);
+ pNest->Nest.cRecursion = 0;
+ pNest->Nest.pNextFree = pThreadSelf->LockValidator.pFreeNestRecs;
+ pThreadSelf->LockValidator.pFreeNestRecs = &pNest->Nest;
+#endif /* RTLOCKVAL_WITH_RECURSION_RECORDS */
+}
+
+
+/**
+ * Helper for rtLockValidatorStackCheckLockingOrder that does the bitching and
+ * returns VERR_SEM_LV_WRONG_ORDER.
+ */
+static int rtLockValidatorStackWrongOrder(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf,
+ PRTLOCKVALRECUNION pRec1, PRTLOCKVALRECUNION pRec2,
+ RTLOCKVALCLASSINT *pClass1, RTLOCKVALCLASSINT *pClass2)
+
+
+{
+ rtLockValComplainFirst(pszWhat, pSrcPos, pThreadSelf, pRec1, false);
+ rtLockValComplainAboutLock("Other lock: ", pRec2, "\n");
+ rtLockValComplainAboutClass("My class: ", pClass1, rtLockValidatorRecGetSubClass(pRec1), true /*fVerbose*/);
+ rtLockValComplainAboutClass("Other class: ", pClass2, rtLockValidatorRecGetSubClass(pRec2), true /*fVerbose*/);
+ rtLockValComplainAboutLockStack(pThreadSelf, 0, 0, pRec2);
+ rtLockValComplainPanic();
+ return !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_ORDER : VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if the sub-class order is ok or not.
+ *
+ * Used to deal with two locks from the same class.
+ *
+ * @returns true if ok, false if not.
+ * @param uSubClass1 The sub-class of the lock that is being
+ * considered.
+ * @param uSubClass2 The sub-class of the lock that is already being
+ * held.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorIsSubClassOrderOk(uint32_t uSubClass1, uint32_t uSubClass2)
+{
+ if (uSubClass1 > uSubClass2)
+ {
+ /* NONE kills ANY. */
+ if (uSubClass2 == RTLOCKVAL_SUB_CLASS_NONE)
+ return false;
+ return true;
+ }
+
+ /* ANY counters all USER values. (uSubClass1 == NONE only if they are equal) */
+ AssertCompile(RTLOCKVAL_SUB_CLASS_ANY > RTLOCKVAL_SUB_CLASS_NONE);
+ if (uSubClass1 == RTLOCKVAL_SUB_CLASS_ANY)
+ return true;
+ return false;
+}
+
+
+/**
+ * Checks if the class and sub-class lock order is ok.
+ *
+ * @returns true if ok, false if not.
+ * @param pClass1 The class of the lock that is being considered.
+ * @param uSubClass1 The sub-class that goes with @a pClass1.
+ * @param pClass2 The class of the lock that is already being
+ * held.
+ * @param uSubClass2 The sub-class that goes with @a pClass2.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorIsClassOrderOk(RTLOCKVALCLASSINT *pClass1, uint32_t uSubClass1,
+ RTLOCKVALCLASSINT *pClass2, uint32_t uSubClass2)
+{
+ if (pClass1 == pClass2)
+ return rtLockValidatorIsSubClassOrderOk(uSubClass1, uSubClass2);
+ return rtLockValidatorClassIsPriorClass(pClass1, pClass2);
+}
+
+
+/**
+ * Checks the locking order, part two.
+ *
+ * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_ORDER or VERR_SEM_LV_INTERNAL_ERROR.
+ * @param pClass The lock class.
+ * @param uSubClass The lock sub-class.
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ * @param pSrcPos The source position of the locking operation.
+ * @param pFirstBadClass The first bad class.
+ * @param pFirstBadRec The first bad lock record.
+ * @param pFirstBadDown The next record on the lock stack.
+ */
+static int rtLockValidatorStackCheckLockingOrder2(RTLOCKVALCLASSINT * const pClass, uint32_t const uSubClass,
+ PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION const pRec,
+ PCRTLOCKVALSRCPOS const pSrcPos,
+ RTLOCKVALCLASSINT * const pFirstBadClass,
+ PRTLOCKVALRECUNION const pFirstBadRec,
+ PRTLOCKVALRECUNION const pFirstBadDown)
+{
+ /*
+ * Something went wrong, pCur is pointing to where.
+ */
+ if ( pClass == pFirstBadClass
+ || rtLockValidatorClassIsPriorClass(pFirstBadClass, pClass))
+ return rtLockValidatorStackWrongOrder("Wrong locking order!", pSrcPos, pThreadSelf,
+ pRec, pFirstBadRec, pClass, pFirstBadClass);
+ if (!pClass->fAutodidact)
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (unknown)", pSrcPos, pThreadSelf,
+ pRec, pFirstBadRec, pClass, pFirstBadClass);
+
+ /*
+ * This class is an autodidact, so we have to check out the rest of the stack
+ * for direct violations.
+ */
+ uint32_t cNewRules = 1;
+ PRTLOCKVALRECUNION pCur = pFirstBadDown;
+ while (pCur)
+ {
+ AssertPtrReturn(pCur, VERR_SEM_LV_INTERNAL_ERROR);
+
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ PRTLOCKVALRECUNION pDown;
+ uint32_t uPriorSubClass;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ AssertPtrReturn(pPriorClass, VERR_SEM_LV_INTERNAL_ERROR);
+ AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_SEM_LV_INTERNAL_ERROR);
+ if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass))
+ {
+ if ( pClass == pPriorClass
+ || rtLockValidatorClassIsPriorClass(pPriorClass, pClass))
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (more than one)", pSrcPos, pThreadSelf,
+ pRec, pCur, pClass, pPriorClass);
+ cNewRules++;
+ }
+ }
+ pCur = pDown;
+ }
+ }
+
+ if (cNewRules == 1)
+ {
+ /*
+ * Special case the simple operation, hoping that it will be a
+ * frequent case.
+ */
+ int rc = rtLockValidatorClassAddPriorClass(pClass, pFirstBadClass, true /*fAutodidacticism*/, pSrcPos);
+ if (rc == VERR_SEM_LV_WRONG_ORDER)
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (race)", pSrcPos, pThreadSelf,
+ pRec, pFirstBadRec, pClass, pFirstBadClass);
+ Assert(RT_SUCCESS(rc) || rc == VERR_NO_MEMORY);
+ }
+ else
+ {
+ /*
+ * We may be adding more than one rule, so we have to take the lock
+ * before starting to add the rules. This means we have to check
+ * the state after taking it since we might be racing someone adding
+ * a conflicting rule.
+ */
+ if (!RTCritSectIsInitialized(&g_LockValClassTeachCS))
+ rtLockValidatorLazyInit();
+ int rcLock = RTCritSectEnter(&g_LockValClassTeachCS);
+
+ /* Check */
+ pCur = pFirstBadRec;
+ while (pCur)
+ {
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ uint32_t uPriorSubClass;
+ PRTLOCKVALRECUNION pDown;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass))
+ {
+ if ( pClass == pPriorClass
+ || rtLockValidatorClassIsPriorClass(pPriorClass, pClass))
+ {
+ if (RT_SUCCESS(rcLock))
+ RTCritSectLeave(&g_LockValClassTeachCS);
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (2nd)", pSrcPos, pThreadSelf,
+ pRec, pCur, pClass, pPriorClass);
+ }
+ }
+ }
+ pCur = pDown;
+ }
+ }
+
+ /* Iterate the stack yet again, adding new rules this time. */
+ pCur = pFirstBadRec;
+ while (pCur)
+ {
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ uint32_t uPriorSubClass;
+ PRTLOCKVALRECUNION pDown;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass))
+ {
+ Assert( pClass != pPriorClass
+ && !rtLockValidatorClassIsPriorClass(pPriorClass, pClass));
+ int rc = rtLockValidatorClassAddPriorClass(pClass, pPriorClass, true /*fAutodidacticism*/, pSrcPos);
+ if (RT_FAILURE(rc))
+ {
+ Assert(rc == VERR_NO_MEMORY);
+ break;
+ }
+ Assert(rtLockValidatorClassIsPriorClass(pClass, pPriorClass));
+ }
+ }
+ pCur = pDown;
+ }
+ }
+
+ if (RT_SUCCESS(rcLock))
+ RTCritSectLeave(&g_LockValClassTeachCS);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Checks the locking order.
+ *
+ * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_ORDER or VERR_SEM_LV_INTERNAL_ERROR.
+ * @param pClass The lock class.
+ * @param uSubClass The lock sub-class.
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ * @param pSrcPos The source position of the locking operation.
+ */
+static int rtLockValidatorStackCheckLockingOrder(RTLOCKVALCLASSINT * const pClass, uint32_t const uSubClass,
+ PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION const pRec,
+ PCRTLOCKVALSRCPOS pSrcPos)
+{
+ /*
+ * Some internal paranoia first.
+ */
+ AssertPtr(pClass);
+ Assert(pClass->u32Magic == RTLOCKVALCLASS_MAGIC);
+ AssertPtr(pThreadSelf);
+ Assert(pThreadSelf->u32Magic == RTTHREADINT_MAGIC);
+ AssertPtr(pRec);
+ AssertPtrNull(pSrcPos);
+
+ /*
+ * Walk the stack, delegate problems to a worker routine.
+ */
+ PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop;
+ if (!pCur)
+ return VINF_SUCCESS;
+
+ for (;;)
+ {
+ AssertPtrReturn(pCur, VERR_SEM_LV_INTERNAL_ERROR);
+
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ uint32_t uPriorSubClass;
+ PRTLOCKVALRECUNION pDown;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ AssertPtrReturn(pPriorClass, VERR_SEM_LV_INTERNAL_ERROR);
+ AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_SEM_LV_INTERNAL_ERROR);
+ if (RT_UNLIKELY(!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass)))
+ return rtLockValidatorStackCheckLockingOrder2(pClass, uSubClass, pThreadSelf, pRec, pSrcPos,
+ pPriorClass, pCur, pDown);
+ }
+ pCur = pDown;
+ }
+ if (!pCur)
+ return VINF_SUCCESS;
+ }
+}
+
+
+/**
+ * Check that the lock record is the topmost one on the stack, complain and fail
+ * if it isn't.
+ *
+ * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_RELEASE_ORDER or
+ * VERR_SEM_LV_INVALID_PARAMETER.
+ * @param pThreadSelf The current thread.
+ * @param pRec The record.
+ */
+static int rtLockValidatorStackCheckReleaseOrder(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ AssertReturn(pThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pThreadSelf == RTThreadSelf());
+
+ PRTLOCKVALRECUNION pTop = pThreadSelf->LockValidator.pStackTop;
+ if (RT_LIKELY( pTop == pRec
+ || ( pTop
+ && pTop->Core.u32Magic == RTLOCKVALRECNEST_MAGIC
+ && pTop->Nest.pRec == pRec) ))
+ return VINF_SUCCESS;
+
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+ /* Look for a recursion record so the right frame is dumped and marked. */
+ while (pTop)
+ {
+ if (pTop->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ {
+ if (pTop->Nest.pRec == pRec)
+ {
+ pRec = pTop;
+ break;
+ }
+ pTop = pTop->Nest.pDown;
+ }
+ else if (pTop->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ pTop = pTop->Excl.pDown;
+ else if (pTop->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ pTop = pTop->ShrdOwner.pDown;
+ else
+ break;
+ }
+#endif
+
+ rtLockValComplainFirst("Wrong release order!", NULL, pThreadSelf, pRec, true);
+ rtLockValComplainPanic();
+ return !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_RELEASE_ORDER : VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if all owners are blocked - shared record operated in signaller mode.
+ *
+ * @returns true / false accordingly.
+ * @param pRec The record.
+ * @param pThreadSelf The current thread.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorDdAreAllThreadsBlocked(PRTLOCKVALRECSHRD pRec, PRTTHREADINT pThreadSelf)
+{
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->papOwners;
+ uint32_t cAllocated = pRec->cAllocated;
+ uint32_t cEntries = ASMAtomicUoReadU32(&pRec->cEntries);
+ if (cEntries == 0)
+ return false;
+
+ for (uint32_t i = 0; i < cAllocated; i++)
+ {
+ PRTLOCKVALRECSHRDOWN pEntry = rtLockValidatorUoReadSharedOwner(&papOwners[i]);
+ if ( pEntry
+ && pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ {
+ PRTTHREADINT pCurThread = rtLockValidatorReadThreadHandle(&pEntry->hThread);
+ if (!pCurThread)
+ return false;
+ if (pCurThread->u32Magic != RTTHREADINT_MAGIC)
+ return false;
+ if ( !RTTHREAD_IS_SLEEPING(rtThreadGetState(pCurThread))
+ && pCurThread != pThreadSelf)
+ return false;
+ if (--cEntries == 0)
+ break;
+ }
+ else
+ Assert(!pEntry || pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC_DEAD);
+ }
+
+ return true;
+}
+
+
+/**
+ * Verifies the deadlock stack before calling it a deadlock.
+ *
+ * @retval VERR_SEM_LV_DEADLOCK if it's a deadlock.
+ * @retval VERR_SEM_LV_ILLEGAL_UPGRADE if it's a deadlock on the same lock.
+ * @retval VERR_TRY_AGAIN if something changed.
+ *
+ * @param pStack The deadlock detection stack.
+ * @param pThreadSelf The current thread.
+ */
+static int rtLockValidatorDdVerifyDeadlock(PRTLOCKVALDDSTACK pStack, PRTTHREADINT pThreadSelf)
+{
+ uint32_t const c = pStack->c;
+ for (uint32_t iPass = 0; iPass < 3; iPass++)
+ {
+ for (uint32_t i = 1; i < c; i++)
+ {
+ PRTTHREADINT pThread = pStack->a[i].pThread;
+ if (pThread->u32Magic != RTTHREADINT_MAGIC)
+ return VERR_TRY_AGAIN;
+ if (rtThreadGetState(pThread) != pStack->a[i].enmState)
+ return VERR_TRY_AGAIN;
+ if (rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pRec) != pStack->a[i].pFirstSibling)
+ return VERR_TRY_AGAIN;
+ /* ASSUMES the signaller records won't have siblings! */
+ PRTLOCKVALRECUNION pRec = pStack->a[i].pRec;
+ if ( pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ && pRec->Shared.fSignaller
+ && !rtLockValidatorDdAreAllThreadsBlocked(&pRec->Shared, pThreadSelf))
+ return VERR_TRY_AGAIN;
+ }
+ RTThreadYield();
+ }
+
+ if (c == 1)
+ return VERR_SEM_LV_ILLEGAL_UPGRADE;
+ return VERR_SEM_LV_DEADLOCK;
+}
+
+
+/**
+ * Checks for stack cycles caused by another deadlock before returning.
+ *
+ * @retval VINF_SUCCESS if the stack is simply too small.
+ * @retval VERR_SEM_LV_EXISTING_DEADLOCK if a cycle was detected.
+ *
+ * @param pStack The deadlock detection stack.
+ */
+static int rtLockValidatorDdHandleStackOverflow(PRTLOCKVALDDSTACK pStack)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(pStack->a) - 1; i++)
+ {
+ PRTTHREADINT pThread = pStack->a[i].pThread;
+ for (size_t j = i + 1; j < RT_ELEMENTS(pStack->a); j++)
+ if (pStack->a[j].pThread == pThread)
+ return VERR_SEM_LV_EXISTING_DEADLOCK;
+ }
+ static bool volatile s_fComplained = false;
+ if (!s_fComplained)
+ {
+ s_fComplained = true;
+ rtLockValComplain(RT_SRC_POS, "lock validator stack is too small! (%zu entries)\n", RT_ELEMENTS(pStack->a));
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtLockValidatorDeadlockDetection that does the actual deadlock
+ * detection.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_SEM_LV_DEADLOCK
+ * @retval VERR_SEM_LV_EXISTING_DEADLOCK
+ * @retval VERR_SEM_LV_ILLEGAL_UPGRADE
+ * @retval VERR_TRY_AGAIN
+ *
+ * @param pStack The stack to use.
+ * @param pOriginalRec The original record.
+ * @param pThreadSelf The calling thread.
+ */
+static int rtLockValidatorDdDoDetection(PRTLOCKVALDDSTACK pStack, PRTLOCKVALRECUNION const pOriginalRec,
+ PRTTHREADINT const pThreadSelf)
+{
+ pStack->c = 0;
+
+ /* We could use a single RTLOCKVALDDENTRY variable here, but the
+ compiler may make a better job of it when using individual variables. */
+ PRTLOCKVALRECUNION pRec = pOriginalRec;
+ PRTLOCKVALRECUNION pFirstSibling = pOriginalRec;
+ uint32_t iEntry = UINT32_MAX;
+ PRTTHREADINT pThread = NIL_RTTHREAD;
+ RTTHREADSTATE enmState = RTTHREADSTATE_RUNNING;
+ for (uint32_t iLoop = 0; ; iLoop++)
+ {
+ /*
+ * Process the current record.
+ */
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRec);
+
+ /* Find the next relevant owner thread and record. */
+ PRTLOCKVALRECUNION pNextRec = NULL;
+ RTTHREADSTATE enmNextState = RTTHREADSTATE_RUNNING;
+ PRTTHREADINT pNextThread = NIL_RTTHREAD;
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(iEntry == UINT32_MAX);
+ for (;;)
+ {
+ pNextThread = rtLockValidatorReadThreadHandle(&pRec->Excl.hThread);
+ if ( !pNextThread
+ || pNextThread->u32Magic != RTTHREADINT_MAGIC)
+ break;
+ enmNextState = rtThreadGetState(pNextThread);
+ if ( !RTTHREAD_IS_SLEEPING(enmNextState)
+ && pNextThread != pThreadSelf)
+ break;
+ pNextRec = rtLockValidatorReadRecUnionPtr(&pNextThread->LockValidator.pRec);
+ if (RT_LIKELY( !pNextRec
+ || enmNextState == rtThreadGetState(pNextThread)))
+ break;
+ pNextRec = NULL;
+ }
+ if (!pNextRec)
+ {
+ pRec = pRec->Excl.pSibling;
+ if ( pRec
+ && pRec != pFirstSibling)
+ continue;
+ pNextThread = NIL_RTTHREAD;
+ }
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ if (!pRec->Shared.fSignaller)
+ {
+ /* Skip to the next sibling if same side. ASSUMES reader priority. */
+ /** @todo The read side of a read-write lock is problematic if
+ * the implementation prioritizes writers over readers because
+ * that means we should could deadlock against current readers
+ * if a writer showed up. If the RW sem implementation is
+ * wrapping some native API, it's not so easy to detect when we
+ * should do this and when we shouldn't. Checking when we
+ * shouldn't is subject to wakeup scheduling and cannot easily
+ * be made reliable.
+ *
+ * At the moment we circumvent all this mess by declaring that
+ * readers has priority. This is TRUE on linux, but probably
+ * isn't on Solaris and FreeBSD. */
+ if ( pRec == pFirstSibling
+ && pRec->Shared.pSibling != NULL
+ && pRec->Shared.pSibling != pFirstSibling)
+ {
+ pRec = pRec->Shared.pSibling;
+ Assert(iEntry == UINT32_MAX);
+ continue;
+ }
+ }
+
+ /* Scan the owner table for blocked owners. */
+ if ( ASMAtomicUoReadU32(&pRec->Shared.cEntries) > 0
+ && ( !pRec->Shared.fSignaller
+ || iEntry != UINT32_MAX
+ || rtLockValidatorDdAreAllThreadsBlocked(&pRec->Shared, pThreadSelf)
+ )
+ )
+ {
+ uint32_t cAllocated = pRec->Shared.cAllocated;
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->Shared.papOwners;
+ while (++iEntry < cAllocated)
+ {
+ PRTLOCKVALRECSHRDOWN pEntry = rtLockValidatorUoReadSharedOwner(&papOwners[iEntry]);
+ if (pEntry)
+ {
+ for (;;)
+ {
+ if (pEntry->Core.u32Magic != RTLOCKVALRECSHRDOWN_MAGIC)
+ break;
+ pNextThread = rtLockValidatorReadThreadHandle(&pEntry->hThread);
+ if ( !pNextThread
+ || pNextThread->u32Magic != RTTHREADINT_MAGIC)
+ break;
+ enmNextState = rtThreadGetState(pNextThread);
+ if ( !RTTHREAD_IS_SLEEPING(enmNextState)
+ && pNextThread != pThreadSelf)
+ break;
+ pNextRec = rtLockValidatorReadRecUnionPtr(&pNextThread->LockValidator.pRec);
+ if (RT_LIKELY( !pNextRec
+ || enmNextState == rtThreadGetState(pNextThread)))
+ break;
+ pNextRec = NULL;
+ }
+ if (pNextRec)
+ break;
+ }
+ else
+ Assert(!pEntry || pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC_DEAD);
+ }
+ if (pNextRec)
+ break;
+ pNextThread = NIL_RTTHREAD;
+ }
+
+ /* Advance to the next sibling, if any. */
+ pRec = pRec->Shared.pSibling;
+ if ( pRec != NULL
+ && pRec != pFirstSibling)
+ {
+ iEntry = UINT32_MAX;
+ continue;
+ }
+ break;
+
+ case RTLOCKVALRECEXCL_MAGIC_DEAD:
+ case RTLOCKVALRECSHRD_MAGIC_DEAD:
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ case RTLOCKVALRECSHRDOWN_MAGIC_DEAD:
+ default:
+ AssertMsgFailed(("%p: %#x\n", pRec, pRec->Core.u32Magic));
+ break;
+ }
+
+ if (pNextRec)
+ {
+ /*
+ * Recurse and check for deadlock.
+ */
+ uint32_t i = pStack->c;
+ if (RT_UNLIKELY(i >= RT_ELEMENTS(pStack->a)))
+ return rtLockValidatorDdHandleStackOverflow(pStack);
+
+ pStack->c++;
+ pStack->a[i].pRec = pRec;
+ pStack->a[i].iEntry = iEntry;
+ pStack->a[i].enmState = enmState;
+ pStack->a[i].pThread = pThread;
+ pStack->a[i].pFirstSibling = pFirstSibling;
+
+ if (RT_UNLIKELY( pNextThread == pThreadSelf
+ && ( i != 0
+ || pRec->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC
+ || !pRec->Shared.fSignaller) /* ASSUMES signaller records have no siblings. */
+ )
+ )
+ return rtLockValidatorDdVerifyDeadlock(pStack, pThreadSelf);
+
+ pRec = pNextRec;
+ pFirstSibling = pNextRec;
+ iEntry = UINT32_MAX;
+ enmState = enmNextState;
+ pThread = pNextThread;
+ }
+ else
+ {
+ /*
+ * No deadlock here, unwind the stack and deal with any unfinished
+ * business there.
+ */
+ uint32_t i = pStack->c;
+ for (;;)
+ {
+ /* pop */
+ if (i == 0)
+ return VINF_SUCCESS;
+ i--;
+ pRec = pStack->a[i].pRec;
+ iEntry = pStack->a[i].iEntry;
+
+ /* Examine it. */
+ uint32_t u32Magic = pRec->Core.u32Magic;
+ if (u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ pRec = pRec->Excl.pSibling;
+ else if (u32Magic == RTLOCKVALRECSHRD_MAGIC)
+ {
+ if (iEntry + 1 < pRec->Shared.cAllocated)
+ break; /* continue processing this record. */
+ pRec = pRec->Shared.pSibling;
+ }
+ else
+ {
+ Assert( u32Magic == RTLOCKVALRECEXCL_MAGIC_DEAD
+ || u32Magic == RTLOCKVALRECSHRD_MAGIC_DEAD);
+ continue;
+ }
+
+ /* Any next record to advance to? */
+ if ( !pRec
+ || pRec == pStack->a[i].pFirstSibling)
+ continue;
+ iEntry = UINT32_MAX;
+ break;
+ }
+
+ /* Restore the rest of the state and update the stack. */
+ pFirstSibling = pStack->a[i].pFirstSibling;
+ enmState = pStack->a[i].enmState;
+ pThread = pStack->a[i].pThread;
+ pStack->c = i;
+ }
+
+ Assert(iLoop != 1000000);
+ }
+}
+
+
+/**
+ * Check for the simple no-deadlock case.
+ *
+ * @returns true if no deadlock, false if further investigation is required.
+ *
+ * @param pOriginalRec The original record.
+ */
+DECLINLINE(int) rtLockValidatorIsSimpleNoDeadlockCase(PRTLOCKVALRECUNION pOriginalRec)
+{
+ if ( pOriginalRec->Excl.Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ && !pOriginalRec->Excl.pSibling)
+ {
+ PRTTHREADINT pThread = rtLockValidatorReadThreadHandle(&pOriginalRec->Excl.hThread);
+ if ( !pThread
+ || pThread->u32Magic != RTTHREADINT_MAGIC)
+ return true;
+ RTTHREADSTATE enmState = rtThreadGetState(pThread);
+ if (!RTTHREAD_IS_SLEEPING(enmState))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Worker for rtLockValidatorDeadlockDetection that bitches about a deadlock.
+ *
+ * @param pStack The chain of locks causing the deadlock.
+ * @param pRec The record relating to the current thread's lock
+ * operation.
+ * @param pThreadSelf This thread.
+ * @param pSrcPos Where we are going to deadlock.
+ * @param rc The return code.
+ */
+static void rcLockValidatorDoDeadlockComplaining(PRTLOCKVALDDSTACK pStack, PRTLOCKVALRECUNION pRec,
+ PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos, int rc)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ const char *pszWhat;
+ switch (rc)
+ {
+ case VERR_SEM_LV_DEADLOCK: pszWhat = "Detected deadlock!"; break;
+ case VERR_SEM_LV_EXISTING_DEADLOCK: pszWhat = "Found existing deadlock!"; break;
+ case VERR_SEM_LV_ILLEGAL_UPGRADE: pszWhat = "Illegal lock upgrade!"; break;
+ default: AssertFailed(); pszWhat = "!unexpected rc!"; break;
+ }
+ rtLockValComplainFirst(pszWhat, pSrcPos, pThreadSelf, pStack->a[0].pRec != pRec ? pRec : NULL, true);
+ rtLockValComplainMore("---- start of deadlock chain - %u entries ----\n", pStack->c);
+ for (uint32_t i = 0; i < pStack->c; i++)
+ {
+ char szPrefix[24];
+ RTStrPrintf(szPrefix, sizeof(szPrefix), "#%02u: ", i);
+ PRTLOCKVALRECUNION pShrdOwner = NULL;
+ if (pStack->a[i].pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)
+ pShrdOwner = (PRTLOCKVALRECUNION)pStack->a[i].pRec->Shared.papOwners[pStack->a[i].iEntry];
+ if (RT_VALID_PTR(pShrdOwner) && pShrdOwner->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ {
+ rtLockValComplainAboutLock(szPrefix, pShrdOwner, "\n");
+ rtLockValComplainAboutLockStack(pShrdOwner->ShrdOwner.hThread, 5, 2, pShrdOwner);
+ }
+ else
+ {
+ rtLockValComplainAboutLock(szPrefix, pStack->a[i].pRec, "\n");
+ if (pStack->a[i].pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ rtLockValComplainAboutLockStack(pStack->a[i].pRec->Excl.hThread, 5, 2, pStack->a[i].pRec);
+ }
+ }
+ rtLockValComplainMore("---- end of deadlock chain ----\n");
+ }
+
+ rtLockValComplainPanic();
+}
+
+
+/**
+ * Perform deadlock detection.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_SEM_LV_DEADLOCK
+ * @retval VERR_SEM_LV_EXISTING_DEADLOCK
+ * @retval VERR_SEM_LV_ILLEGAL_UPGRADE
+ *
+ * @param pRec The record relating to the current thread's lock
+ * operation.
+ * @param pThreadSelf The current thread.
+ * @param pSrcPos The position of the current lock operation.
+ */
+static int rtLockValidatorDeadlockDetection(PRTLOCKVALRECUNION pRec, PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ RTLOCKVALDDSTACK Stack;
+ rtLockValidatorSerializeDetectionEnter();
+ int rc = rtLockValidatorDdDoDetection(&Stack, pRec, pThreadSelf);
+ rtLockValidatorSerializeDetectionLeave();
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ if (rc == VERR_TRY_AGAIN)
+ {
+ for (uint32_t iLoop = 0; ; iLoop++)
+ {
+ rtLockValidatorSerializeDetectionEnter();
+ rc = rtLockValidatorDdDoDetection(&Stack, pRec, pThreadSelf);
+ rtLockValidatorSerializeDetectionLeave();
+ if (RT_SUCCESS_NP(rc))
+ return VINF_SUCCESS;
+ if (rc != VERR_TRY_AGAIN)
+ break;
+ RTThreadYield();
+ if (iLoop >= 3)
+ return VINF_SUCCESS;
+ }
+ }
+
+ rcLockValidatorDoDeadlockComplaining(&Stack, pRec, pThreadSelf, pSrcPos, rc);
+ return rc;
+}
+
+
+RTDECL(void) RTLockValidatorRecExclInitV(PRTLOCKVALRECEXCL pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fEnabled, const char *pszNameFmt, va_list va)
+{
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRec);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(hLock);
+ Assert( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY);
+
+ pRec->Core.u32Magic = RTLOCKVALRECEXCL_MAGIC;
+ pRec->fEnabled = fEnabled && RTLockValidatorIsEnabled();
+ pRec->afReserved[0] = 0;
+ pRec->afReserved[1] = 0;
+ pRec->afReserved[2] = 0;
+ rtLockValidatorSrcPosInit(&pRec->SrcPos);
+ pRec->hThread = NIL_RTTHREAD;
+ pRec->pDown = NULL;
+ pRec->hClass = rtLockValidatorClassValidateAndRetain(hClass);
+ pRec->uSubClass = uSubClass;
+ pRec->cRecursion = 0;
+ pRec->hLock = hLock;
+ pRec->pSibling = NULL;
+ if (pszNameFmt)
+ RTStrPrintfV(pRec->szName, sizeof(pRec->szName), pszNameFmt, va);
+ else
+ {
+ static uint32_t volatile s_cAnonymous = 0;
+ uint32_t i = ASMAtomicIncU32(&s_cAnonymous) - 1;
+ RTStrPrintf(pRec->szName, sizeof(pRec->szName), "anon-excl-%u", i);
+ }
+
+ /* Lazy initialization. */
+ if (RT_UNLIKELY(g_hLockValidatorXRoads == NIL_RTSEMXROADS))
+ rtLockValidatorLazyInit();
+}
+
+
+RTDECL(void) RTLockValidatorRecExclInit(PRTLOCKVALRECEXCL pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fEnabled, const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ RTLockValidatorRecExclInitV(pRec, hClass, uSubClass, hLock, fEnabled, pszNameFmt, va);
+ va_end(va);
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCreateV(PRTLOCKVALRECEXCL *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fEnabled,
+ const char *pszNameFmt, va_list va)
+{
+ PRTLOCKVALRECEXCL pRec;
+ *ppRec = pRec = (PRTLOCKVALRECEXCL)RTMemAlloc(sizeof(*pRec));
+ if (!pRec)
+ return VERR_NO_MEMORY;
+ RTLockValidatorRecExclInitV(pRec, hClass, uSubClass, pvLock, fEnabled, pszNameFmt, va);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCreate(PRTLOCKVALRECEXCL *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fEnabled,
+ const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorRecExclCreateV(ppRec, hClass, uSubClass, pvLock, fEnabled, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(void) RTLockValidatorRecExclDelete(PRTLOCKVALRECEXCL pRec)
+{
+ Assert(pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC);
+
+ rtLockValidatorSerializeDestructEnter();
+
+ /** @todo Check that it's not on our stack first. Need to make it
+ * configurable whether deleting a owned lock is acceptable? */
+
+ ASMAtomicWriteU32(&pRec->Core.u32Magic, RTLOCKVALRECEXCL_MAGIC_DEAD);
+ ASMAtomicWriteHandle(&pRec->hThread, NIL_RTTHREAD);
+ RTLOCKVALCLASS hClass;
+ ASMAtomicXchgHandle(&pRec->hClass, NIL_RTLOCKVALCLASS, &hClass);
+ if (pRec->pSibling)
+ rtLockValidatorUnlinkAllSiblings(&pRec->Core);
+ rtLockValidatorSerializeDestructLeave();
+ if (hClass != NIL_RTLOCKVALCLASS)
+ RTLockValidatorClassRelease(hClass);
+}
+
+
+RTDECL(void) RTLockValidatorRecExclDestroy(PRTLOCKVALRECEXCL *ppRec)
+{
+ PRTLOCKVALRECEXCL pRec = *ppRec;
+ *ppRec = NULL;
+ if (pRec)
+ {
+ RTLockValidatorRecExclDelete(pRec);
+ RTMemFree(pRec);
+ }
+}
+
+
+RTDECL(uint32_t) RTLockValidatorRecExclSetSubClass(PRTLOCKVALRECEXCL pRec, uint32_t uSubClass)
+{
+ AssertPtrReturn(pRec, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY,
+ RTLOCKVAL_SUB_CLASS_INVALID);
+ return ASMAtomicXchgU32(&pRec->uSubClass, uSubClass);
+}
+
+
+RTDECL(void) RTLockValidatorRecExclSetOwner(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fFirstRecursion)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return;
+ AssertReturnVoid(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC);
+ if (!pRecU->Excl.fEnabled)
+ return;
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturnVoid(hThreadSelf != NIL_RTTHREAD);
+ }
+ AssertReturnVoid(hThreadSelf->u32Magic == RTTHREADINT_MAGIC);
+ Assert(hThreadSelf == RTThreadSelf());
+
+ ASMAtomicIncS32(&hThreadSelf->LockValidator.cWriteLocks);
+
+ if (pRecU->Excl.hThread == hThreadSelf)
+ {
+ Assert(!fFirstRecursion); RT_NOREF_PV(fFirstRecursion);
+ pRecU->Excl.cRecursion++;
+ rtLockValidatorStackPushRecursion(hThreadSelf, pRecU, pSrcPos);
+ }
+ else
+ {
+ Assert(pRecU->Excl.hThread == NIL_RTTHREAD);
+
+ rtLockValidatorSrcPosCopy(&pRecU->Excl.SrcPos, pSrcPos);
+ ASMAtomicUoWriteU32(&pRecU->Excl.cRecursion, 1);
+ ASMAtomicWriteHandle(&pRecU->Excl.hThread, hThreadSelf);
+
+ rtLockValidatorStackPush(hThreadSelf, pRecU);
+ }
+}
+
+
+/**
+ * Internal worker for RTLockValidatorRecExclReleaseOwner and
+ * RTLockValidatorRecExclReleaseOwnerUnchecked.
+ */
+static void rtLockValidatorRecExclReleaseOwnerUnchecked(PRTLOCKVALRECUNION pRec, bool fFinalRecursion)
+{
+ RTTHREADINT *pThread = pRec->Excl.hThread;
+ AssertReturnVoid(pThread != NIL_RTTHREAD);
+ Assert(pThread == RTThreadSelf());
+
+ ASMAtomicDecS32(&pThread->LockValidator.cWriteLocks);
+ uint32_t c = ASMAtomicDecU32(&pRec->Excl.cRecursion);
+ if (c == 0)
+ {
+ rtLockValidatorStackPop(pThread, pRec);
+ ASMAtomicWriteHandle(&pRec->Excl.hThread, NIL_RTTHREAD);
+ }
+ else
+ {
+ Assert(c < UINT32_C(0xffff0000));
+ Assert(!fFinalRecursion); RT_NOREF_PV(fFinalRecursion);
+ rtLockValidatorStackPopRecursion(pThread, pRec);
+ }
+}
+
+RTDECL(int) RTLockValidatorRecExclReleaseOwner(PRTLOCKVALRECEXCL pRec, bool fFinalRecursion)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+
+ /*
+ * Check the release order.
+ */
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && pRecU->Excl.hClass->fStrictReleaseOrder
+ && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Join paths with RTLockValidatorRecExclReleaseOwnerUnchecked.
+ */
+ rtLockValidatorRecExclReleaseOwnerUnchecked(pRecU, fFinalRecursion);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTLockValidatorRecExclReleaseOwnerUnchecked(PRTLOCKVALRECEXCL pRec)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturnVoid(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC);
+ if (pRecU->Excl.fEnabled)
+ rtLockValidatorRecExclReleaseOwnerUnchecked(pRecU, false);
+}
+
+
+RTDECL(int) RTLockValidatorRecExclRecursion(PRTLOCKVALRECEXCL pRec, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Excl.cRecursion > 0, VERR_SEM_LV_INVALID_PARAMETER);
+
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && !pRecU->Excl.hClass->fRecursionOk)
+ {
+ rtLockValComplainFirst("Recursion not allowed by the class!",
+ pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NESTED;
+ }
+
+ Assert(pRecU->Excl.cRecursion < _1M);
+ pRecU->Excl.cRecursion++;
+ rtLockValidatorStackPushRecursion(pRecU->Excl.hThread, pRecU, pSrcPos);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclUnwind(PRTLOCKVALRECEXCL pRec)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pRecU->Excl.hThread == RTThreadSelf());
+ AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Check the release order.
+ */
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && pRecU->Excl.hClass->fStrictReleaseOrder
+ && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Perform the unwind.
+ */
+ pRecU->Excl.cRecursion--;
+ rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclRecursionMixed(PRTLOCKVALRECEXCL pRec, PRTLOCKVALRECCORE pRecMixed, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ PRTLOCKVALRECUNION pRecMixedU = (PRTLOCKVALRECUNION)pRecMixed;
+ AssertReturn( pRecMixedU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ || pRecMixedU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ Assert(pRecU->Excl.hThread == RTThreadSelf());
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Excl.cRecursion > 0, VERR_SEM_LV_INVALID_PARAMETER);
+
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && !pRecU->Excl.hClass->fRecursionOk)
+ {
+ rtLockValComplainFirst("Mixed recursion not allowed by the class!",
+ pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NESTED;
+ }
+
+ Assert(pRecU->Excl.cRecursion < _1M);
+ pRecU->Excl.cRecursion++;
+ rtLockValidatorStackPushRecursion(pRecU->Excl.hThread, pRecU, pSrcPos);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclUnwindMixed(PRTLOCKVALRECEXCL pRec, PRTLOCKVALRECCORE pRecMixed)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ PRTLOCKVALRECUNION pRecMixedU = (PRTLOCKVALRECUNION)pRecMixed;
+ AssertReturn( pRecMixedU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ || pRecMixedU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ Assert(pRecU->Excl.hThread == RTThreadSelf());
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Check the release order.
+ */
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && pRecU->Excl.hClass->fStrictReleaseOrder
+ && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Perform the unwind.
+ */
+ pRecU->Excl.cRecursion--;
+ rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCheckOrder(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate and adjust input. Quit early if order validation is disabled.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if ( !pRecU->Excl.fEnabled
+ || pRecU->Excl.hClass == NIL_RTLOCKVALCLASS
+ || pRecU->Excl.hClass->cMsMinOrder == RT_INDEFINITE_WAIT
+ || pRecU->Excl.hClass->cMsMinOrder > cMillies)
+ return VINF_SUCCESS;
+
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(hThreadSelf == RTThreadSelf());
+
+ /*
+ * Detect recursion as it isn't subject to order restrictions.
+ */
+ if (pRec->hThread == hThreadSelf)
+ return VINF_SUCCESS;
+
+ return rtLockValidatorStackCheckLockingOrder(pRecU->Excl.hClass, pRecU->Excl.uSubClass, hThreadSelf, pRecU, pSrcPos);
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCheckBlocking(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ /*
+ * Fend off wild life.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertPtrReturn(pRecU, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRec->fEnabled)
+ return VINF_SUCCESS;
+
+ PRTTHREADINT pThreadSelf = hThreadSelf;
+ AssertPtrReturn(pThreadSelf, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pThreadSelf == RTThreadSelf());
+
+ AssertReturn(RTTHREAD_IS_SLEEPING(enmSleepState), VERR_SEM_LV_INVALID_PARAMETER);
+
+ RTTHREADSTATE enmThreadState = rtThreadGetState(pThreadSelf);
+ if (RT_UNLIKELY(enmThreadState != RTTHREADSTATE_RUNNING))
+ {
+ AssertReturn( enmThreadState == RTTHREADSTATE_TERMINATED /* rtThreadRemove uses locks too */
+ || enmThreadState == RTTHREADSTATE_INITIALIZING /* rtThreadInsert uses locks too */
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ enmSleepState = enmThreadState;
+ }
+
+ /*
+ * Record the location.
+ */
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, pRecU);
+ rtLockValidatorSrcPosCopy(&pThreadSelf->LockValidator.SrcPos, pSrcPos);
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, true);
+ pThreadSelf->LockValidator.enmRecState = enmSleepState;
+ rtThreadSetState(pThreadSelf, enmSleepState);
+
+ /*
+ * Don't do deadlock detection if we're recursing.
+ *
+ * On some hosts we don't do recursion accounting our selves and there
+ * isn't any other place to check for this.
+ */
+ int rc = VINF_SUCCESS;
+ if (rtLockValidatorReadThreadHandle(&pRecU->Excl.hThread) == pThreadSelf)
+ {
+ if ( !fRecursiveOk
+ || ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && !pRecU->Excl.hClass->fRecursionOk))
+ {
+ rtLockValComplainFirst("Recursion not allowed!", pSrcPos, pThreadSelf, pRecU, true);
+ rtLockValComplainPanic();
+ rc = VERR_SEM_LV_NESTED;
+ }
+ }
+ /*
+ * Perform deadlock detection.
+ */
+ else if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && ( pRecU->Excl.hClass->cMsMinDeadlock > cMillies
+ || pRecU->Excl.hClass->cMsMinDeadlock > RT_INDEFINITE_WAIT))
+ rc = VINF_SUCCESS;
+ else if (!rtLockValidatorIsSimpleNoDeadlockCase(pRecU))
+ rc = rtLockValidatorDeadlockDetection(pRecU, pThreadSelf, pSrcPos);
+
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteBool(&pThreadSelf->fReallySleeping, fReallySleeping);
+ else
+ {
+ rtThreadSetState(pThreadSelf, enmThreadState);
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, NULL);
+ }
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, false);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecExclCheckBlocking);
+
+
+RTDECL(int) RTLockValidatorRecExclCheckOrderAndBlocking(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ int rc = RTLockValidatorRecExclCheckOrder(pRec, hThreadSelf, pSrcPos, cMillies);
+ if (RT_SUCCESS(rc))
+ rc = RTLockValidatorRecExclCheckBlocking(pRec, hThreadSelf, pSrcPos, fRecursiveOk, cMillies,
+ enmSleepState, fReallySleeping);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecExclCheckOrderAndBlocking);
+
+
+RTDECL(void) RTLockValidatorRecSharedInitV(PRTLOCKVALRECSHRD pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fSignaller, bool fEnabled, const char *pszNameFmt, va_list va)
+{
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRec);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(hLock);
+ Assert( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY);
+
+ pRec->Core.u32Magic = RTLOCKVALRECSHRD_MAGIC;
+ pRec->uSubClass = uSubClass;
+ pRec->hClass = rtLockValidatorClassValidateAndRetain(hClass);
+ pRec->hLock = hLock;
+ pRec->fEnabled = fEnabled && RTLockValidatorIsEnabled();
+ pRec->fSignaller = fSignaller;
+ pRec->pSibling = NULL;
+
+ /* the table */
+ pRec->cEntries = 0;
+ pRec->iLastEntry = 0;
+ pRec->cAllocated = 0;
+ pRec->fReallocating = false;
+ pRec->fPadding = false;
+ pRec->papOwners = NULL;
+
+ /* the name */
+ if (pszNameFmt)
+ RTStrPrintfV(pRec->szName, sizeof(pRec->szName), pszNameFmt, va);
+ else
+ {
+ static uint32_t volatile s_cAnonymous = 0;
+ uint32_t i = ASMAtomicIncU32(&s_cAnonymous) - 1;
+ RTStrPrintf(pRec->szName, sizeof(pRec->szName), "anon-shrd-%u", i);
+ }
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedInit(PRTLOCKVALRECSHRD pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fSignaller, bool fEnabled, const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ RTLockValidatorRecSharedInitV(pRec, hClass, uSubClass, hLock, fSignaller, fEnabled, pszNameFmt, va);
+ va_end(va);
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCreateV(PRTLOCKVALRECSHRD *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fSignaller, bool fEnabled,
+ const char *pszNameFmt, va_list va)
+{
+ PRTLOCKVALRECSHRD pRec;
+ *ppRec = pRec = (PRTLOCKVALRECSHRD)RTMemAlloc(sizeof(*pRec));
+ if (!pRec)
+ return VERR_NO_MEMORY;
+ RTLockValidatorRecSharedInitV(pRec, hClass, uSubClass, pvLock, fSignaller, fEnabled, pszNameFmt, va);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCreate(PRTLOCKVALRECSHRD *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fSignaller, bool fEnabled,
+ const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorRecSharedCreateV(ppRec, hClass, uSubClass, pvLock, fSignaller, fEnabled, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedDelete(PRTLOCKVALRECSHRD pRec)
+{
+ Assert(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+
+ /** @todo Check that it's not on our stack first. Need to make it
+ * configurable whether deleting a owned lock is acceptable? */
+
+ /*
+ * Flip it into table realloc mode and take the destruction lock.
+ */
+ rtLockValidatorSerializeDestructEnter();
+ while (!ASMAtomicCmpXchgBool(&pRec->fReallocating, true, false))
+ {
+ rtLockValidatorSerializeDestructLeave();
+
+ rtLockValidatorSerializeDetectionEnter();
+ rtLockValidatorSerializeDetectionLeave();
+
+ rtLockValidatorSerializeDestructEnter();
+ }
+
+ ASMAtomicWriteU32(&pRec->Core.u32Magic, RTLOCKVALRECSHRD_MAGIC_DEAD);
+ RTLOCKVALCLASS hClass;
+ ASMAtomicXchgHandle(&pRec->hClass, NIL_RTLOCKVALCLASS, &hClass);
+ if (pRec->papOwners)
+ {
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->papOwners;
+ ASMAtomicUoWriteNullPtr(&pRec->papOwners);
+ ASMAtomicUoWriteU32(&pRec->cAllocated, 0);
+
+ RTMemFree((void *)papOwners);
+ }
+ if (pRec->pSibling)
+ rtLockValidatorUnlinkAllSiblings(&pRec->Core);
+ ASMAtomicWriteBool(&pRec->fReallocating, false);
+
+ rtLockValidatorSerializeDestructLeave();
+
+ if (hClass != NIL_RTLOCKVALCLASS)
+ RTLockValidatorClassRelease(hClass);
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedDestroy(PRTLOCKVALRECSHRD *ppRec)
+{
+ PRTLOCKVALRECSHRD pRec = *ppRec;
+ *ppRec = NULL;
+ if (pRec)
+ {
+ RTLockValidatorRecSharedDelete(pRec);
+ RTMemFree(pRec);
+ }
+}
+
+
+RTDECL(uint32_t) RTLockValidatorRecSharedSetSubClass(PRTLOCKVALRECSHRD pRec, uint32_t uSubClass)
+{
+ AssertPtrReturn(pRec, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY,
+ RTLOCKVAL_SUB_CLASS_INVALID);
+ return ASMAtomicXchgU32(&pRec->uSubClass, uSubClass);
+}
+
+
+/**
+ * Locates an owner (thread) in a shared lock record.
+ *
+ * @returns Pointer to the owner entry on success, NULL on failure..
+ * @param pShared The shared lock record.
+ * @param hThread The thread (owner) to find.
+ * @param piEntry Where to optionally return the table in index.
+ * Optional.
+ */
+DECLINLINE(PRTLOCKVALRECUNION)
+rtLockValidatorRecSharedFindOwner(PRTLOCKVALRECSHRD pShared, RTTHREAD hThread, uint32_t *piEntry)
+{
+ rtLockValidatorSerializeDetectionEnter();
+
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners;
+ if (papOwners)
+ {
+ uint32_t const cMax = pShared->cAllocated;
+ for (uint32_t iEntry = 0; iEntry < cMax; iEntry++)
+ {
+ PRTLOCKVALRECUNION pEntry = (PRTLOCKVALRECUNION)rtLockValidatorUoReadSharedOwner(&papOwners[iEntry]);
+ if (pEntry && pEntry->ShrdOwner.hThread == hThread)
+ {
+ rtLockValidatorSerializeDetectionLeave();
+ if (piEntry)
+ *piEntry = iEntry;
+ return pEntry;
+ }
+ }
+ }
+
+ rtLockValidatorSerializeDetectionLeave();
+ return NULL;
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckOrder(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate and adjust input. Quit early if order validation is disabled.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if ( !pRecU->Shared.fEnabled
+ || pRecU->Shared.hClass == NIL_RTLOCKVALCLASS
+ || pRecU->Shared.hClass->cMsMinOrder == RT_INDEFINITE_WAIT
+ || pRecU->Shared.hClass->cMsMinOrder > cMillies
+ )
+ return VINF_SUCCESS;
+
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(hThreadSelf == RTThreadSelf());
+
+ /*
+ * Detect recursion as it isn't subject to order restrictions.
+ */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(&pRecU->Shared, hThreadSelf, NULL);
+ if (pEntry)
+ return VINF_SUCCESS;
+
+ return rtLockValidatorStackCheckLockingOrder(pRecU->Shared.hClass, pRecU->Shared.uSubClass, hThreadSelf, pRecU, pSrcPos);
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckBlocking(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ /*
+ * Fend off wild life.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertPtrReturn(pRecU, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Shared.fEnabled)
+ return VINF_SUCCESS;
+
+ PRTTHREADINT pThreadSelf = hThreadSelf;
+ AssertPtrReturn(pThreadSelf, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pThreadSelf == RTThreadSelf());
+
+ AssertReturn(RTTHREAD_IS_SLEEPING(enmSleepState), VERR_SEM_LV_INVALID_PARAMETER);
+
+ RTTHREADSTATE enmThreadState = rtThreadGetState(pThreadSelf);
+ if (RT_UNLIKELY(enmThreadState != RTTHREADSTATE_RUNNING))
+ {
+ AssertReturn( enmThreadState == RTTHREADSTATE_TERMINATED /* rtThreadRemove uses locks too */
+ || enmThreadState == RTTHREADSTATE_INITIALIZING /* rtThreadInsert uses locks too */
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ enmSleepState = enmThreadState;
+ }
+
+ /*
+ * Record the location.
+ */
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, pRecU);
+ rtLockValidatorSrcPosCopy(&pThreadSelf->LockValidator.SrcPos, pSrcPos);
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, true);
+ pThreadSelf->LockValidator.enmRecState = enmSleepState;
+ rtThreadSetState(pThreadSelf, enmSleepState);
+
+ /*
+ * Don't do deadlock detection if we're recursing.
+ */
+ int rc = VINF_SUCCESS;
+ PRTLOCKVALRECUNION pEntry = !pRecU->Shared.fSignaller
+ ? rtLockValidatorRecSharedFindOwner(&pRecU->Shared, pThreadSelf, NULL)
+ : NULL;
+ if (pEntry)
+ {
+ if ( !fRecursiveOk
+ || ( pRec->hClass
+ && !pRec->hClass->fRecursionOk)
+ )
+ {
+ rtLockValComplainFirst("Recursion not allowed!", pSrcPos, pThreadSelf, pRecU, true);
+ rtLockValComplainPanic();
+ rc = VERR_SEM_LV_NESTED;
+ }
+ }
+ /*
+ * Perform deadlock detection.
+ */
+ else if ( pRec->hClass
+ && ( pRec->hClass->cMsMinDeadlock == RT_INDEFINITE_WAIT
+ || pRec->hClass->cMsMinDeadlock > cMillies))
+ rc = VINF_SUCCESS;
+ else if (!rtLockValidatorIsSimpleNoDeadlockCase(pRecU))
+ rc = rtLockValidatorDeadlockDetection(pRecU, pThreadSelf, pSrcPos);
+
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteBool(&pThreadSelf->fReallySleeping, fReallySleeping);
+ else
+ {
+ rtThreadSetState(pThreadSelf, enmThreadState);
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, NULL);
+ }
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, false);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedCheckBlocking);
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckOrderAndBlocking(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ int rc = RTLockValidatorRecSharedCheckOrder(pRec, hThreadSelf, pSrcPos, cMillies);
+ if (RT_SUCCESS(rc))
+ rc = RTLockValidatorRecSharedCheckBlocking(pRec, hThreadSelf, pSrcPos, fRecursiveOk, cMillies,
+ enmSleepState, fReallySleeping);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedCheckOrderAndBlocking);
+
+
+/**
+ * Allocates and initializes an owner entry for the shared lock record.
+ *
+ * @returns The new owner entry.
+ * @param pRec The shared lock record.
+ * @param pThreadSelf The calling thread and owner. Used for record
+ * initialization and allocation.
+ * @param pSrcPos The source position.
+ */
+DECLINLINE(PRTLOCKVALRECUNION)
+rtLockValidatorRecSharedAllocOwner(PRTLOCKVALRECSHRD pRec, PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ PRTLOCKVALRECUNION pEntry;
+
+ /*
+ * Check if the thread has any statically allocated records we can easily
+ * make use of.
+ */
+ unsigned iEntry = ASMBitFirstSetU32(ASMAtomicUoReadU32(&pThreadSelf->LockValidator.bmFreeShrdOwners));
+ if ( iEntry > 0
+ && ASMAtomicBitTestAndClear(&pThreadSelf->LockValidator.bmFreeShrdOwners, iEntry - 1))
+ {
+ pEntry = (PRTLOCKVALRECUNION)&pThreadSelf->LockValidator.aShrdOwners[iEntry - 1];
+ Assert(!pEntry->ShrdOwner.fReserved);
+ pEntry->ShrdOwner.fStaticAlloc = true;
+ rtThreadGet(pThreadSelf);
+ }
+ else
+ {
+ pEntry = (PRTLOCKVALRECUNION)RTMemAlloc(sizeof(RTLOCKVALRECSHRDOWN));
+ if (RT_UNLIKELY(!pEntry))
+ return NULL;
+ pEntry->ShrdOwner.fStaticAlloc = false;
+ }
+
+ pEntry->Core.u32Magic = RTLOCKVALRECSHRDOWN_MAGIC;
+ pEntry->ShrdOwner.cRecursion = 1;
+ pEntry->ShrdOwner.fReserved = true;
+ pEntry->ShrdOwner.hThread = pThreadSelf;
+ pEntry->ShrdOwner.pDown = NULL;
+ pEntry->ShrdOwner.pSharedRec = pRec;
+#if HC_ARCH_BITS == 32
+ pEntry->ShrdOwner.pvReserved = NULL;
+#endif
+ if (pSrcPos)
+ pEntry->ShrdOwner.SrcPos = *pSrcPos;
+ else
+ rtLockValidatorSrcPosInit(&pEntry->ShrdOwner.SrcPos);
+ return pEntry;
+}
+
+
+/**
+ * Frees an owner entry allocated by rtLockValidatorRecSharedAllocOwner.
+ *
+ * @param pEntry The owner entry.
+ */
+DECLINLINE(void) rtLockValidatorRecSharedFreeOwner(PRTLOCKVALRECSHRDOWN pEntry)
+{
+ if (pEntry)
+ {
+ Assert(pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC);
+ ASMAtomicWriteU32(&pEntry->Core.u32Magic, RTLOCKVALRECSHRDOWN_MAGIC_DEAD);
+
+ PRTTHREADINT pThread;
+ ASMAtomicXchgHandle(&pEntry->hThread, NIL_RTTHREAD, &pThread);
+
+ Assert(pEntry->fReserved);
+ pEntry->fReserved = false;
+
+ if (pEntry->fStaticAlloc)
+ {
+ AssertPtrReturnVoid(pThread);
+ AssertReturnVoid(pThread->u32Magic == RTTHREADINT_MAGIC);
+
+ uintptr_t iEntry = pEntry - &pThread->LockValidator.aShrdOwners[0];
+ AssertReleaseReturnVoid(iEntry < RT_ELEMENTS(pThread->LockValidator.aShrdOwners));
+
+ Assert(!ASMBitTest(&pThread->LockValidator.bmFreeShrdOwners, (int32_t)iEntry));
+ ASMAtomicBitSet(&pThread->LockValidator.bmFreeShrdOwners, (int32_t)iEntry);
+
+ rtThreadRelease(pThread);
+ }
+ else
+ {
+ rtLockValidatorSerializeDestructEnter();
+ rtLockValidatorSerializeDestructLeave();
+
+ RTMemFree(pEntry);
+ }
+ }
+}
+
+
+/**
+ * Make more room in the table.
+ *
+ * @retval true on success
+ * @retval false if we're out of memory or running into a bad race condition
+ * (probably a bug somewhere). No longer holding the lock.
+ *
+ * @param pShared The shared lock record.
+ */
+static bool rtLockValidatorRecSharedMakeRoom(PRTLOCKVALRECSHRD pShared)
+{
+ for (unsigned i = 0; i < 1000; i++)
+ {
+ /*
+ * Switch to the other data access direction.
+ */
+ rtLockValidatorSerializeDetectionLeave();
+ if (i >= 10)
+ {
+ Assert(i != 10 && i != 100);
+ RTThreadSleep(i >= 100);
+ }
+ rtLockValidatorSerializeDestructEnter();
+
+ /*
+ * Try grab the privilege to reallocating the table.
+ */
+ if ( pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ && ASMAtomicCmpXchgBool(&pShared->fReallocating, true, false))
+ {
+ uint32_t cAllocated = pShared->cAllocated;
+ if (cAllocated < pShared->cEntries)
+ {
+ /*
+ * Ok, still not enough space. Reallocate the table.
+ */
+ uint32_t cInc = RT_ALIGN_32(pShared->cEntries - cAllocated, 16);
+ PRTLOCKVALRECSHRDOWN *papOwners;
+ papOwners = (PRTLOCKVALRECSHRDOWN *)RTMemRealloc((void *)pShared->papOwners,
+ (cAllocated + cInc) * sizeof(void *));
+ if (!papOwners)
+ {
+ ASMAtomicWriteBool(&pShared->fReallocating, false);
+ rtLockValidatorSerializeDestructLeave();
+ /* RTMemRealloc will assert */
+ return false;
+ }
+
+ while (cInc-- > 0)
+ {
+ papOwners[cAllocated] = NULL;
+ cAllocated++;
+ }
+
+ ASMAtomicWritePtr(&pShared->papOwners, papOwners);
+ ASMAtomicWriteU32(&pShared->cAllocated, cAllocated);
+ }
+ ASMAtomicWriteBool(&pShared->fReallocating, false);
+ }
+ rtLockValidatorSerializeDestructLeave();
+
+ rtLockValidatorSerializeDetectionEnter();
+ if (RT_UNLIKELY(pShared->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC))
+ break;
+
+ if (pShared->cAllocated >= pShared->cEntries)
+ return true;
+ }
+
+ rtLockValidatorSerializeDetectionLeave();
+ AssertFailed(); /* too many iterations or destroyed while racing. */
+ return false;
+}
+
+
+/**
+ * Adds an owner entry to a shared lock record.
+ *
+ * @returns true on success, false on serious race or we're if out of memory.
+ * @param pShared The shared lock record.
+ * @param pEntry The owner entry.
+ */
+DECLINLINE(bool) rtLockValidatorRecSharedAddOwner(PRTLOCKVALRECSHRD pShared, PRTLOCKVALRECSHRDOWN pEntry)
+{
+ rtLockValidatorSerializeDetectionEnter();
+ if (RT_LIKELY(pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) /* paranoia */
+ {
+ if ( ASMAtomicIncU32(&pShared->cEntries) > pShared->cAllocated /** @todo add fudge */
+ && !rtLockValidatorRecSharedMakeRoom(pShared))
+ return false; /* the worker leave the lock */
+
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners;
+ uint32_t const cMax = pShared->cAllocated;
+ for (unsigned i = 0; i < 100; i++)
+ {
+ for (uint32_t iEntry = 0; iEntry < cMax; iEntry++)
+ {
+ if (ASMAtomicCmpXchgPtr(&papOwners[iEntry], pEntry, NULL))
+ {
+ rtLockValidatorSerializeDetectionLeave();
+ return true;
+ }
+ }
+ Assert(i != 25);
+ }
+ AssertFailed();
+ }
+ rtLockValidatorSerializeDetectionLeave();
+ return false;
+}
+
+
+/**
+ * Remove an owner entry from a shared lock record and free it.
+ *
+ * @param pShared The shared lock record.
+ * @param pEntry The owner entry to remove.
+ * @param iEntry The last known index.
+ */
+DECLINLINE(void) rtLockValidatorRecSharedRemoveAndFreeOwner(PRTLOCKVALRECSHRD pShared, PRTLOCKVALRECSHRDOWN pEntry,
+ uint32_t iEntry)
+{
+ /*
+ * Remove it from the table.
+ */
+ rtLockValidatorSerializeDetectionEnter();
+ AssertReturnVoidStmt(pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, rtLockValidatorSerializeDetectionLeave());
+ if (RT_UNLIKELY( iEntry >= pShared->cAllocated
+ || !ASMAtomicCmpXchgPtr(&pShared->papOwners[iEntry], NULL, pEntry)))
+ {
+ /* this shouldn't happen yet... */
+ AssertFailed();
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners;
+ uint32_t const cMax = pShared->cAllocated;
+ for (iEntry = 0; iEntry < cMax; iEntry++)
+ if (ASMAtomicCmpXchgPtr(&papOwners[iEntry], NULL, pEntry))
+ break;
+ AssertReturnVoidStmt(iEntry < cMax, rtLockValidatorSerializeDetectionLeave());
+ }
+ uint32_t cNow = ASMAtomicDecU32(&pShared->cEntries);
+ Assert(!(cNow & RT_BIT_32(31))); NOREF(cNow);
+ rtLockValidatorSerializeDetectionLeave();
+
+ /*
+ * Successfully removed, now free it.
+ */
+ rtLockValidatorRecSharedFreeOwner(pEntry);
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedResetOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+ if (!pRec->fEnabled)
+ return;
+ AssertReturnVoid(hThread == NIL_RTTHREAD || hThread->u32Magic == RTTHREADINT_MAGIC);
+ AssertReturnVoid(pRec->fSignaller);
+
+ /*
+ * Free all current owners.
+ */
+ rtLockValidatorSerializeDetectionEnter();
+ while (ASMAtomicUoReadU32(&pRec->cEntries) > 0)
+ {
+ AssertReturnVoidStmt(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, rtLockValidatorSerializeDetectionLeave());
+ uint32_t iEntry = 0;
+ uint32_t cEntries = pRec->cAllocated;
+ PRTLOCKVALRECSHRDOWN volatile *papEntries = pRec->papOwners;
+ while (iEntry < cEntries)
+ {
+ PRTLOCKVALRECSHRDOWN pEntry = ASMAtomicXchgPtrT(&papEntries[iEntry], NULL, PRTLOCKVALRECSHRDOWN);
+ if (pEntry)
+ {
+ ASMAtomicDecU32(&pRec->cEntries);
+ rtLockValidatorSerializeDetectionLeave();
+
+ rtLockValidatorRecSharedFreeOwner(pEntry);
+
+ rtLockValidatorSerializeDetectionEnter();
+ if (ASMAtomicUoReadU32(&pRec->cEntries) == 0)
+ break;
+ cEntries = pRec->cAllocated;
+ papEntries = pRec->papOwners;
+ }
+ iEntry++;
+ }
+ }
+ rtLockValidatorSerializeDetectionLeave();
+
+ if (hThread != NIL_RTTHREAD)
+ {
+ /*
+ * Allocate a new owner entry and insert it into the table.
+ */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedAllocOwner(pRec, hThread, pSrcPos);
+ if ( pEntry
+ && !rtLockValidatorRecSharedAddOwner(pRec, &pEntry->ShrdOwner))
+ rtLockValidatorRecSharedFreeOwner(&pEntry->ShrdOwner);
+ }
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedResetOwner);
+
+
+RTDECL(void) RTLockValidatorRecSharedAddOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+ if (!pRec->fEnabled)
+ return;
+ if (hThread == NIL_RTTHREAD)
+ {
+ hThread = RTThreadSelfAutoAdopt();
+ AssertReturnVoid(hThread != NIL_RTTHREAD);
+ }
+ AssertReturnVoid(hThread->u32Magic == RTTHREADINT_MAGIC);
+
+ /*
+ * Recursive?
+ *
+ * Note! This code can be optimized to try avoid scanning the table on
+ * insert. However, that's annoying work that makes the code big,
+ * so it can wait til later sometime.
+ */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, NULL);
+ if (pEntry)
+ {
+ Assert(!pRec->fSignaller);
+ pEntry->ShrdOwner.cRecursion++;
+ rtLockValidatorStackPushRecursion(hThread, pEntry, pSrcPos);
+ return;
+ }
+
+ /*
+ * Allocate a new owner entry and insert it into the table.
+ */
+ pEntry = rtLockValidatorRecSharedAllocOwner(pRec, hThread, pSrcPos);
+ if (pEntry)
+ {
+ if (rtLockValidatorRecSharedAddOwner(pRec, &pEntry->ShrdOwner))
+ {
+ if (!pRec->fSignaller)
+ rtLockValidatorStackPush(hThread, pEntry);
+ }
+ else
+ rtLockValidatorRecSharedFreeOwner(&pEntry->ShrdOwner);
+ }
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedAddOwner);
+
+
+RTDECL(void) RTLockValidatorRecSharedRemoveOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread)
+{
+ AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+ if (!pRec->fEnabled)
+ return;
+ if (hThread == NIL_RTTHREAD)
+ {
+ hThread = RTThreadSelfAutoAdopt();
+ AssertReturnVoid(hThread != NIL_RTTHREAD);
+ }
+ AssertReturnVoid(hThread->u32Magic == RTTHREADINT_MAGIC);
+
+ /*
+ * Find the entry hope it's a recursive one.
+ */
+ uint32_t iEntry = UINT32_MAX; /* shuts up gcc */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, &iEntry);
+ AssertReturnVoid(pEntry);
+ AssertReturnVoid(pEntry->ShrdOwner.cRecursion > 0);
+
+ uint32_t c = --pEntry->ShrdOwner.cRecursion;
+ if (c == 0)
+ {
+ if (!pRec->fSignaller)
+ rtLockValidatorStackPop(hThread, (PRTLOCKVALRECUNION)pEntry);
+ rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry);
+ }
+ else
+ {
+ Assert(!pRec->fSignaller);
+ rtLockValidatorStackPopRecursion(hThread, pEntry);
+ }
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedRemoveOwner);
+
+
+RTDECL(bool) RTLockValidatorRecSharedIsOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread)
+{
+ /* Validate and resolve input. */
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, false);
+ if (!pRec->fEnabled)
+ return false;
+ if (hThread == NIL_RTTHREAD)
+ {
+ hThread = RTThreadSelfAutoAdopt();
+ AssertReturn(hThread != NIL_RTTHREAD, false);
+ }
+ AssertReturn(hThread->u32Magic == RTTHREADINT_MAGIC, false);
+
+ /* Do the job. */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, NULL);
+ return pEntry != NULL;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedIsOwner);
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckAndRelease(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf)
+{
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRec->fEnabled)
+ return VINF_SUCCESS;
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ Assert(hThreadSelf == RTThreadSelf());
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Locate the entry for this thread in the table.
+ */
+ uint32_t iEntry = 0;
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThreadSelf, &iEntry);
+ if (RT_UNLIKELY(!pEntry))
+ {
+ rtLockValComplainFirst("Not owner (shared)!", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NOT_OWNER;
+ }
+
+ /*
+ * Check the release order.
+ */
+ if ( pRec->hClass != NIL_RTLOCKVALCLASS
+ && pRec->hClass->fStrictReleaseOrder
+ && pRec->hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(hThreadSelf, (PRTLOCKVALRECUNION)pEntry);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Release the ownership or unwind a level of recursion.
+ */
+ Assert(pEntry->ShrdOwner.cRecursion > 0);
+ uint32_t c = --pEntry->ShrdOwner.cRecursion;
+ if (c == 0)
+ {
+ rtLockValidatorStackPop(hThreadSelf, pEntry);
+ rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry);
+ }
+ else
+ rtLockValidatorStackPopRecursion(hThreadSelf, pEntry);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckSignaller(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf)
+{
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRec->fEnabled)
+ return VINF_SUCCESS;
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ Assert(hThreadSelf == RTThreadSelf());
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Locate the entry for this thread in the table.
+ */
+ uint32_t iEntry = 0;
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThreadSelf, &iEntry);
+ if (RT_UNLIKELY(!pEntry))
+ {
+ rtLockValComplainFirst("Invalid signaller!", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NOT_SIGNALLER;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int32_t) RTLockValidatorWriteLockGetCount(RTTHREAD Thread)
+{
+ if (Thread == NIL_RTTHREAD)
+ return 0;
+
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (!pThread)
+ return VERR_INVALID_HANDLE;
+ int32_t cWriteLocks = ASMAtomicReadS32(&pThread->LockValidator.cWriteLocks);
+ rtThreadRelease(pThread);
+ return cWriteLocks;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorWriteLockGetCount);
+
+
+RTDECL(void) RTLockValidatorWriteLockInc(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ AssertReturnVoid(pThread);
+ ASMAtomicIncS32(&pThread->LockValidator.cWriteLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorWriteLockInc);
+
+
+RTDECL(void) RTLockValidatorWriteLockDec(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ AssertReturnVoid(pThread);
+ ASMAtomicDecS32(&pThread->LockValidator.cWriteLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorWriteLockDec);
+
+
+RTDECL(int32_t) RTLockValidatorReadLockGetCount(RTTHREAD Thread)
+{
+ if (Thread == NIL_RTTHREAD)
+ return 0;
+
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (!pThread)
+ return VERR_INVALID_HANDLE;
+ int32_t cReadLocks = ASMAtomicReadS32(&pThread->LockValidator.cReadLocks);
+ rtThreadRelease(pThread);
+ return cReadLocks;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorReadLockGetCount);
+
+
+RTDECL(void) RTLockValidatorReadLockInc(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ Assert(pThread);
+ ASMAtomicIncS32(&pThread->LockValidator.cReadLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorReadLockInc);
+
+
+RTDECL(void) RTLockValidatorReadLockDec(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ Assert(pThread);
+ ASMAtomicDecS32(&pThread->LockValidator.cReadLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorReadLockDec);
+
+
+RTDECL(void *) RTLockValidatorQueryBlocking(RTTHREAD hThread)
+{
+ void *pvLock = NULL;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ RTTHREADSTATE enmState = rtThreadGetState(pThread);
+ if (RTTHREAD_IS_SLEEPING(enmState))
+ {
+ rtLockValidatorSerializeDetectionEnter();
+
+ enmState = rtThreadGetState(pThread);
+ if (RTTHREAD_IS_SLEEPING(enmState))
+ {
+ PRTLOCKVALRECUNION pRec = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pRec);
+ if (pRec)
+ {
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ pvLock = pRec->Excl.hLock;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ pRec = (PRTLOCKVALRECUNION)pRec->ShrdOwner.pSharedRec;
+ if (!pRec || pRec->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC)
+ break;
+ RT_FALL_THRU();
+ case RTLOCKVALRECSHRD_MAGIC:
+ pvLock = pRec->Shared.hLock;
+ break;
+ }
+ if (RTThreadGetState(pThread) != enmState)
+ pvLock = NULL;
+ }
+ }
+
+ rtLockValidatorSerializeDetectionLeave();
+ }
+ rtThreadRelease(pThread);
+ }
+ return pvLock;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorQueryBlocking);
+
+
+RTDECL(bool) RTLockValidatorIsBlockedThreadInValidator(RTTHREAD hThread)
+{
+ bool fRet = false;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ fRet = ASMAtomicReadBool(&pThread->LockValidator.fInValidator);
+ rtThreadRelease(pThread);
+ }
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorIsBlockedThreadInValidator);
+
+
+RTDECL(bool) RTLockValidatorHoldsLocksInClass(RTTHREAD hCurrentThread, RTLOCKVALCLASS hClass)
+{
+ bool fRet = false;
+ if (hCurrentThread == NIL_RTTHREAD)
+ hCurrentThread = RTThreadSelf();
+ else
+ Assert(hCurrentThread == RTThreadSelf());
+ PRTTHREADINT pThread = rtThreadGet(hCurrentThread);
+ if (pThread)
+ {
+ if (hClass != NIL_RTLOCKVALCLASS)
+ {
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ while (RT_VALID_PTR(pCur) && !fRet)
+ {
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Excl.hClass == hClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->ShrdOwner.pSharedRec->hClass == hClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown);
+ break;
+ case RTLOCKVALRECNEST_MAGIC:
+ switch (pCur->Nest.pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Nest.pRec->Excl.hClass == hClass;
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->Nest.pRec->ShrdOwner.pSharedRec->hClass == hClass;
+ break;
+ }
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown);
+ break;
+ default:
+ pCur = NULL;
+ break;
+ }
+ }
+ }
+
+ rtThreadRelease(pThread);
+ }
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorHoldsLocksInClass);
+
+
+RTDECL(bool) RTLockValidatorHoldsLocksInSubClass(RTTHREAD hCurrentThread, RTLOCKVALCLASS hClass, uint32_t uSubClass)
+{
+ bool fRet = false;
+ if (hCurrentThread == NIL_RTTHREAD)
+ hCurrentThread = RTThreadSelf();
+ else
+ Assert(hCurrentThread == RTThreadSelf());
+ PRTTHREADINT pThread = rtThreadGet(hCurrentThread);
+ if (pThread)
+ {
+ if (hClass != NIL_RTLOCKVALCLASS)
+ {
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ while (RT_VALID_PTR(pCur) && !fRet)
+ {
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Excl.hClass == hClass
+ && pCur->Excl.uSubClass == uSubClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->ShrdOwner.pSharedRec->hClass == hClass
+ && pCur->ShrdOwner.pSharedRec->uSubClass == uSubClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown);
+ break;
+ case RTLOCKVALRECNEST_MAGIC:
+ switch (pCur->Nest.pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Nest.pRec->Excl.hClass == hClass
+ && pCur->Nest.pRec->Excl.uSubClass == uSubClass;
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->Nest.pRec->ShrdOwner.pSharedRec->hClass == hClass
+ && pCur->Nest.pRec->ShrdOwner.pSharedRec->uSubClass == uSubClass;
+ break;
+ }
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown);
+ break;
+ default:
+ pCur = NULL;
+ break;
+ }
+ }
+ }
+
+ rtThreadRelease(pThread);
+ }
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorHoldsLocksInClass);
+
+
+RTDECL(bool) RTLockValidatorSetEnabled(bool fEnabled)
+{
+ return ASMAtomicXchgBool(&g_fLockValidatorEnabled, fEnabled);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorSetEnabled);
+
+
+RTDECL(bool) RTLockValidatorIsEnabled(void)
+{
+ return ASMAtomicUoReadBool(&g_fLockValidatorEnabled);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorIsEnabled);
+
+
+RTDECL(bool) RTLockValidatorSetQuiet(bool fQuiet)
+{
+ return ASMAtomicXchgBool(&g_fLockValidatorQuiet, fQuiet);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorSetQuiet);
+
+
+RTDECL(bool) RTLockValidatorIsQuiet(void)
+{
+ return ASMAtomicUoReadBool(&g_fLockValidatorQuiet);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorIsQuiet);
+
+
+RTDECL(bool) RTLockValidatorSetMayPanic(bool fMayPanic)
+{
+ return ASMAtomicXchgBool(&g_fLockValidatorMayPanic, fMayPanic);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorSetMayPanic);
+
+
+RTDECL(bool) RTLockValidatorMayPanic(void)
+{
+ return ASMAtomicUoReadBool(&g_fLockValidatorMayPanic);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorMayPanic);
+
diff --git a/src/VBox/Runtime/common/misc/message.cpp b/src/VBox/Runtime/common/misc/message.cpp
new file mode 100644
index 00000000..87cffd1b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/message.cpp
@@ -0,0 +1,266 @@
+/* $Id: message.cpp $ */
+/** @file
+ * IPRT - Error reporting to standard error.
+ */
+
+/*
+ * 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 "internal/iprt.h"
+#include <iprt/message.h>
+
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include "internal/process.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The program name we're using. */
+static const char * volatile g_pszProgName = NULL;
+/** Custom program name set via RTMsgSetProgName. */
+static char g_szProgName[128];
+
+
+RTDECL(int) RTMsgSetProgName(const char *pszFormat, ...)
+{
+ g_pszProgName = &g_szrtProcExePath[g_offrtProcName];
+
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrPrintfV(g_szProgName, sizeof(g_szProgName) - 1, pszFormat, va);
+ va_end(va);
+
+ g_pszProgName = g_szProgName;
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTMsgSetProgName);
+
+
+static int rtMsgWorker(PRTSTREAM pDst, const char *pszPrefix, const char *pszFormat, va_list va)
+{
+ if ( !*pszFormat
+ || !strcmp(pszFormat, "\n"))
+ RTStrmPrintf(pDst, "\n");
+ else
+ {
+ const char *pszProgName = g_pszProgName;
+ if (!pszProgName)
+ g_pszProgName = pszProgName = &g_szrtProcExePath[g_offrtProcName];
+
+ char *pszMsg;
+ ssize_t cch = RTStrAPrintfV(&pszMsg, pszFormat, va);
+ if (cch >= 0)
+ {
+ /* print it line by line. */
+ char *psz = pszMsg;
+ do
+ {
+ char *pszEnd = strchr(psz, '\n');
+ if (!pszEnd)
+ {
+ RTStrmPrintf(pDst, "%s: %s%s\n", pszProgName, pszPrefix, psz);
+ break;
+ }
+ if (pszEnd == psz)
+ RTStrmPrintf(pDst, "\n");
+ else
+ {
+ *pszEnd = '\0';
+ RTStrmPrintf(pDst, "%s: %s%s\n", pszProgName, pszPrefix, psz);
+ }
+ psz = pszEnd + 1;
+ } while (*psz);
+ RTStrFree(pszMsg);
+ }
+ else
+ {
+ /* Simple fallback for handling out-of-memory conditions. */
+ RTStrmPrintf(pDst, "%s: %s", pszProgName, pszPrefix);
+ RTStrmPrintfV(pDst, pszFormat, va);
+ if (!strchr(pszFormat, '\n'))
+ RTStrmPrintf(pDst, "\n");
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTMsgError(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTMsgError);
+
+
+RTDECL(int) RTMsgErrorV(const char *pszFormat, va_list va)
+{
+ return rtMsgWorker(g_pStdErr, "error: ", pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTMsgErrorV);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExit(RTEXITCODE enmExitCode, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return enmExitCode;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExit);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExitV(RTEXITCODE enmExitCode, const char *pszFormat, va_list va)
+{
+ RTMsgErrorV(pszFormat, va);
+ return enmExitCode;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExitV);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExitFailure(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExitFailure);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExitFailureV(const char *pszFormat, va_list va)
+{
+ RTMsgErrorV(pszFormat, va);
+ return RTEXITCODE_FAILURE;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExitFailureV);
+
+
+RTDECL(int) RTMsgErrorRc(int rcRet, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return rcRet;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorRcV);
+
+
+RTDECL(int) RTMsgErrorRcV(int rcRet, const char *pszFormat, va_list va)
+{
+ RTMsgErrorV(pszFormat, va);
+ return rcRet;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorRcV);
+
+
+RTDECL(RTEXITCODE) RTMsgInitFailure(int rcRTR3Init)
+{
+ if ( g_offrtProcName
+ && g_offrtProcName < sizeof(g_szrtProcExePath)
+ && g_szrtProcExePath[0]
+ && g_szrtProcExePath[g_offrtProcName])
+ RTStrmPrintf(g_pStdErr, "%s: fatal error: RTR3Init: %Rrc\n", &g_szrtProcExePath[g_offrtProcName], rcRTR3Init);
+ else
+ RTStrmPrintf(g_pStdErr, "fatal error: RTR3Init: %Rrc\n", rcRTR3Init);
+ return RTEXITCODE_INIT;
+}
+RT_EXPORT_SYMBOL(RTMsgInitFailure);
+
+
+RTDECL(RTEXITCODE) RTMsgSyntax(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgSyntaxV(pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_SYNTAX;
+}
+RT_EXPORT_SYMBOL(RTMsgSyntax);
+
+
+RTDECL(RTEXITCODE) RTMsgSyntaxV(const char *pszFormat, va_list va)
+{
+ rtMsgWorker(g_pStdOut, "syntax error: ", pszFormat, va);
+ return RTEXITCODE_SYNTAX;
+}
+RT_EXPORT_SYMBOL(RTMsgSyntaxV);
+
+
+RTDECL(int) RTMsgWarning(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTMsgWarningV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTMsgInfo);
+
+
+RTDECL(int) RTMsgWarningV(const char *pszFormat, va_list va)
+{
+ return rtMsgWorker(g_pStdErr, "warning: ", pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTMsgWarningV);
+
+
+RTDECL(int) RTMsgInfo(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTMsgInfoV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTMsgInfo);
+
+
+RTDECL(int) RTMsgInfoV(const char *pszFormat, va_list va)
+{
+ return rtMsgWorker(g_pStdOut, "info: ", pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTMsgInfoV);
+
diff --git a/src/VBox/Runtime/common/misc/messagerefentry.cpp b/src/VBox/Runtime/common/misc/messagerefentry.cpp
new file mode 100644
index 00000000..2ad0f2fd
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/messagerefentry.cpp
@@ -0,0 +1,332 @@
+/* $Id: messagerefentry.cpp $ */
+/** @file
+ * IPRT - Program usage and help formatting.
+ */
+
+/*
+ * 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 "internal/iprt.h"
+#include <iprt/message.h>
+
+#include <iprt/env.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include "internal/process.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Spaces for intending. */
+static const char g_szSpaces[] = " ";
+
+
+/**
+ * Retruns the width for the given handle.
+ *
+ * @returns Screen width.
+ * @param pStrm The stream, g_pStdErr or g_pStdOut.
+ */
+static uint32_t getScreenWidth(PRTSTREAM pStrm)
+{
+ static uint32_t s_acch[2] = { 0, 0 };
+ uint32_t iWhich = pStrm == g_pStdErr ? 1 : 0;
+ uint32_t cch = s_acch[iWhich];
+ if (cch)
+ return cch;
+
+ const char *psz = RTEnvGet("IPRT_SCREEN_WIDTH");
+ if ( !psz
+ || RTStrToUInt32Full(psz, 0, &cch) != VINF_SUCCESS
+ || cch == 0)
+ {
+ int rc = RTStrmQueryTerminalWidth(pStrm, &cch);
+ if (rc == VERR_INVALID_FUNCTION)
+ {
+ /* It's not a console, but in case we're being piped to less/more/list
+ we look for a console handle on the other standard output handle
+ and standard input. (Latter doesn't work on windows.) */
+ rc = RTStrmQueryTerminalWidth(pStrm == g_pStdErr ? g_pStdOut : g_pStdErr, &cch);
+ if (rc == VERR_INVALID_FUNCTION || rc == VERR_INVALID_HANDLE)
+ rc = RTStrmQueryTerminalWidth(g_pStdIn, &cch);
+ if (RT_FAILURE(rc))
+ cch = 80;
+ }
+ }
+
+ s_acch[iWhich] = cch;
+ return cch;
+}
+
+
+/**
+ * Prints a string table string (paragraph), performing non-breaking-space
+ * replacement and wrapping.
+ *
+ * @returns IRPT status code.
+ * @param pStrm The output stream.
+ * @param psz The string table string to print.
+ * @param cchMaxWidth The maximum output width.
+ * @param fFlags String flags that may affect formatting.
+ * @param pcLinesWritten Pointer to variable to update with written lines.
+ */
+static int printString(PRTSTREAM pStrm, const char *psz, uint32_t cchMaxWidth, uint64_t fFlags, uint32_t *pcLinesWritten)
+{
+ uint32_t cLinesWritten;
+ size_t cch = strlen(psz);
+ const char *pszNbsp = strchr(psz, RTMSGREFENTRY_NBSP);
+ int rc;
+
+ /*
+ * No-wrap case is simpler, so handle that separately.
+ */
+ if (cch <= cchMaxWidth)
+ {
+ if (!pszNbsp)
+ rc = RTStrmWrite(pStrm, psz, cch);
+ else
+ {
+ do
+ {
+ rc = RTStrmWrite(pStrm, psz, pszNbsp - psz);
+ if (RT_SUCCESS(rc))
+ rc = RTStrmPutCh(pStrm, ' ');
+ psz = pszNbsp + 1;
+ pszNbsp = strchr(psz, RTMSGREFENTRY_NBSP);
+ } while (pszNbsp && RT_SUCCESS(rc));
+ if (RT_SUCCESS(rc))
+ rc = RTStrmWrite(pStrm, psz, strlen(psz));
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTStrmPutCh(pStrm, '\n');
+ cLinesWritten = 1;
+ }
+ /*
+ * We need to wrap stuff, too bad.
+ */
+ else
+ {
+ /* Figure the paragraph indent level first. */
+ uint32_t cchIndent = 0;
+ while (*psz == ' ')
+ cchIndent++, psz++;
+ Assert(cchIndent + 4 + 1 <= RT_ELEMENTS(g_szSpaces));
+
+ if (cchIndent + 8 >= cchMaxWidth)
+ cchMaxWidth += cchIndent + 8;
+
+ /* Work our way thru the string, line by line. */
+ uint32_t cchHangingIndent = 0;
+ cLinesWritten = 0;
+ do
+ {
+ rc = RTStrmWrite(pStrm, g_szSpaces, cchIndent + cchHangingIndent);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t offLine = cchIndent + cchHangingIndent;
+ bool fPendingSpace = false;
+ do
+ {
+ const char *pszSpace = strchr(psz, ' ');
+ size_t cchWord = pszSpace ? pszSpace - psz : strlen(psz);
+ if ( offLine + cchWord + fPendingSpace > cchMaxWidth
+ && offLine != cchIndent
+ && fPendingSpace /* don't stop before first word */)
+ break;
+
+ pszNbsp = (const char *)memchr(psz, RTMSGREFENTRY_NBSP, cchWord);
+ while (pszNbsp)
+ {
+ size_t cchSubWord = pszNbsp - psz;
+ if (fPendingSpace)
+ {
+ rc = RTStrmPutCh(pStrm, ' ');
+ if (RT_FAILURE(rc))
+ break;
+ }
+ rc = RTStrmWrite(pStrm, psz, cchSubWord);
+ if (RT_FAILURE(rc))
+ break;
+ offLine += cchSubWord + fPendingSpace;
+ psz += cchSubWord + 1;
+ cchWord -= cchSubWord + 1;
+ pszNbsp = (const char *)memchr(psz, RTMSGREFENTRY_NBSP, cchWord);
+ fPendingSpace = true;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ if (fPendingSpace)
+ {
+ rc = RTStrmPutCh(pStrm, ' ');
+ if (RT_FAILURE(rc))
+ break;
+ }
+ rc = RTStrmWrite(pStrm, psz, cchWord);
+ if (RT_FAILURE(rc))
+ break;
+
+ offLine += cchWord + fPendingSpace;
+ psz = pszSpace ? pszSpace + 1 : strchr(psz, '\0');
+ fPendingSpace = true;
+ } while (offLine < cchMaxWidth && *psz != '\0' && RT_SUCCESS(rc));
+
+ if (RT_SUCCESS(rc))
+ rc = RTStrmPutCh(pStrm, '\n');
+ if (RT_FAILURE(rc))
+ break;
+ cLinesWritten++;
+
+ /* Set up hanging indent if relevant. */
+ if (fFlags & RTMSGREFENTRYSTR_FLAGS_SYNOPSIS)
+ cchHangingIndent = 4;
+ } while (*psz != '\0');
+ }
+ *pcLinesWritten += cLinesWritten;
+ return rc;
+}
+
+
+/**
+ * Checks if the given string is empty (only spaces).
+ * @returns true if empty, false if not.
+ * @param psz The string to examine.
+ */
+DECLINLINE(bool) isEmptyString(const char *psz)
+{
+ char ch;
+ while ((ch = *psz) == ' ')
+ psz++;
+ return ch == '\0';
+}
+
+
+/**
+ * Prints a string table.
+ *
+ * @returns Current number of pending blank lines.
+ * @param pStrm The output stream.
+ * @param pStrTab The string table.
+ * @param fScope The selection scope.
+ * @param pcPendingBlankLines In: Pending blank lines from previous string
+ * table. Out: Pending blank lines.
+ * @param pcLinesWritten Pointer to variable that should be incremented
+ * by the number of lines written. Optional.
+ */
+RTDECL(int) RTMsgRefEntryPrintStringTable(PRTSTREAM pStrm, PCRTMSGREFENTRYSTRTAB pStrTab, uint64_t fScope,
+ uint32_t *pcPendingBlankLines, uint32_t *pcLinesWritten)
+{
+ uint32_t cPendingBlankLines = pcPendingBlankLines ? *pcPendingBlankLines : 0;
+ uint32_t cLinesWritten = 0;
+ uint32_t cchWidth = getScreenWidth(pStrm) - 1; /* (Seems a -1 here is prudent, at least on windows.) */
+ uint64_t fPrevScope = fScope;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < pStrTab->cStrings; i++)
+ {
+ uint64_t fCurScope = pStrTab->paStrings[i].fScope;
+ if ((fCurScope & RTMSGREFENTRYSTR_SCOPE_MASK) == RTMSGREFENTRYSTR_SCOPE_SAME)
+ {
+ fCurScope &= ~RTMSGREFENTRYSTR_SCOPE_MASK;
+ fCurScope |= (fPrevScope & RTMSGREFENTRYSTR_SCOPE_MASK);
+ }
+ if (fCurScope & RTMSGREFENTRYSTR_SCOPE_MASK & fScope)
+ {
+ const char *psz = pStrTab->paStrings[i].psz;
+ if (psz && !isEmptyString(psz))
+ {
+ while (cPendingBlankLines > 0 && RT_SUCCESS(rc))
+ {
+ cPendingBlankLines--;
+ rc = RTStrmPutCh(pStrm, '\n');
+ cLinesWritten++;
+ }
+ if (RT_SUCCESS(rc))
+ rc = printString(pStrm, psz, cchWidth, fCurScope & RTMSGREFENTRYSTR_FLAGS_MASK, &cLinesWritten);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ cPendingBlankLines++;
+ }
+ fPrevScope = fCurScope;
+ }
+
+ if (pcLinesWritten)
+ *pcLinesWritten += cLinesWritten;
+ if (pcPendingBlankLines)
+ *pcPendingBlankLines = cPendingBlankLines;
+ return rc;
+}
+
+
+RTDECL(int) RTMsgRefEntrySynopsisEx(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry, uint64_t fScope, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~RTMSGREFENTRY_SYNOPSIS_F_USAGE), VERR_INVALID_FLAGS);
+
+ if (!pStrm)
+ pStrm = g_pStdOut;
+ int rc = VINF_SUCCESS;
+ if (fFlags & RTMSGREFENTRY_SYNOPSIS_F_USAGE)
+ RTStrmPutStr(pStrm, "Usage: ");
+ if (RT_SUCCESS(rc))
+ rc = RTMsgRefEntryPrintStringTable(pStrm, &pEntry->Synopsis, fScope, NULL, NULL);
+ return rc;
+}
+
+
+RTDECL(int) RTMsgRefEntrySynopsis(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry)
+{
+ return RTMsgRefEntrySynopsisEx(pStrm, pEntry, UINT64_MAX, true /*fPrintUsage*/);
+}
+
+
+RTDECL(int) RTMsgRefEntryHelpEx(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry, uint64_t fScope, uint32_t fFlags)
+{
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+ if (!pStrm)
+ pStrm = g_pStdOut;
+ return RTMsgRefEntryPrintStringTable(pStrm, &pEntry->Help, fScope, NULL, NULL);
+}
+
+
+RTDECL(int) RTMsgRefEntryHelp(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry)
+{
+ return RTMsgRefEntryHelpEx(pStrm, pEntry, UINT64_MAX, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/misc/once.cpp b/src/VBox/Runtime/common/misc/once.cpp
new file mode 100644
index 00000000..521d6971
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/once.cpp
@@ -0,0 +1,450 @@
+/* $Id: once.cpp $ */
+/** @file
+ * IPRT - Execute Once.
+ */
+
+/*
+ * 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/once.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#ifdef IN_RING3
+# include <iprt/critsect.h>
+# define RTONCE_USE_CRITSECT_FOR_TERM
+#elif defined(IN_RING0)
+# include <iprt/spinlock.h>
+# define RTONCE_USE_SPINLOCK_FOR_TERM
+#else
+# define RTONCE_NO_TERM
+#endif
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifndef RTONCE_NO_TERM
+/** For initializing the clean-up list code. */
+static RTONCE g_OnceCleanUp = RTONCE_INITIALIZER;
+/** Lock protecting the clean-up list. */
+#ifdef RTONCE_USE_CRITSECT_FOR_TERM
+static RTCRITSECT g_CleanUpCritSect;
+#else
+static RTSEMFASTMUTEX g_hCleanUpLock;
+#endif
+/** The clean-up list. */
+static RTLISTANCHOR g_CleanUpList;
+
+/** Locks the clean-up list. */
+#ifdef RTONCE_USE_CRITSECT_FOR_TERM
+# define RTONCE_CLEANUP_LOCK() RTCritSectEnter(&g_CleanUpCritSect)
+#else
+# define RTONCE_CLEANUP_LOCK() RTSemFastMutexRequest(g_hCleanUpLock);
+#endif
+
+/** Unlocks the clean-up list. */
+#ifdef RTONCE_USE_CRITSECT_FOR_TERM
+# define RTONCE_CLEANUP_UNLOCK() RTCritSectLeave(&g_CleanUpCritSect);
+#else
+# define RTONCE_CLEANUP_UNLOCK() RTSemFastMutexRelease(g_hCleanUpLock);
+#endif
+
+
+
+/** @callback_method_impl{FNRTTERMCALLBACK} */
+static DECLCALLBACK(void) rtOnceTermCallback(RTTERMREASON enmReason, int32_t iStatus, void *pvUser)
+{
+ bool const fLazyCleanUpOk = RTTERMREASON_IS_LAZY_CLEANUP_OK(enmReason);
+ RTONCE_CLEANUP_LOCK(); /* Potentially dangerous. */
+
+ PRTONCE pCur, pPrev;
+ RTListForEachReverseSafe(&g_CleanUpList, pCur, pPrev, RTONCE, CleanUpNode)
+ {
+ /*
+ * Mostly reset it before doing the callback.
+ *
+ * Should probably introduce some new states here, but I'm not sure
+ * it's really worth it at this point.
+ */
+ PFNRTONCECLEANUP pfnCleanUp = pCur->pfnCleanUp;
+ void *pvUserCleanUp = pCur->pvUser;
+ pCur->pvUser = NULL;
+ pCur->pfnCleanUp = NULL;
+ ASMAtomicWriteS32(&pCur->rc, VERR_WRONG_ORDER);
+
+ pfnCleanUp(pvUserCleanUp, fLazyCleanUpOk);
+
+ /*
+ * Reset the reset of the state if we're being unloaded or smth.
+ */
+ if (!fLazyCleanUpOk)
+ {
+ ASMAtomicWriteS32(&pCur->rc, VERR_INTERNAL_ERROR);
+ ASMAtomicWriteS32(&pCur->iState, RTONCESTATE_UNINITIALIZED);
+ }
+ }
+
+ RTONCE_CLEANUP_UNLOCK();
+
+ /*
+ * Reset our own structure and the critsect / mutex.
+ */
+ if (!fLazyCleanUpOk)
+ {
+# ifdef RTONCE_USE_CRITSECT_FOR_TERM
+ RTCritSectDelete(&g_CleanUpCritSect);
+# else
+ RTSemFastMutexDestroy(g_hCleanUpLock);
+ g_hCleanUpLock = NIL_RTSEMFASTMUTEX;
+# endif
+
+ ASMAtomicWriteS32(&g_OnceCleanUp.rc, VERR_INTERNAL_ERROR);
+ ASMAtomicWriteS32(&g_OnceCleanUp.iState, RTONCESTATE_UNINITIALIZED);
+ }
+
+ NOREF(pvUser); NOREF(iStatus);
+}
+
+
+
+/**
+ * Initializes the globals (using RTOnce).
+ *
+ * @returns IPRT status code
+ * @param pvUser Unused.
+ */
+static DECLCALLBACK(int32_t) rtOnceInitCleanUp(void *pvUser)
+{
+ NOREF(pvUser);
+ RTListInit(&g_CleanUpList);
+# ifdef RTONCE_USE_CRITSECT_FOR_TERM
+ int rc = RTCritSectInit(&g_CleanUpCritSect);
+# else
+ int rc = RTSemFastMutexCreate(&g_hCleanUpLock);
+# endif
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTermRegisterCallback(rtOnceTermCallback, NULL);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+# ifdef RTONCE_USE_CRITSECT_FOR_TERM
+ RTCritSectDelete(&g_CleanUpCritSect);
+# else
+ RTSemFastMutexDestroy(g_hCleanUpLock);
+ g_hCleanUpLock = NIL_RTSEMFASTMUTEX;
+# endif
+ }
+ return rc;
+}
+
+#endif /* !RTONCE_NO_TERM */
+
+/**
+ * The state loop of the other threads.
+ *
+ * @returns VINF_SUCCESS when everything went smoothly. IPRT status code if we
+ * encountered trouble.
+ * @param pOnce The execute once structure.
+ * @param phEvtM Where to store the semaphore handle so the caller
+ * can do the cleaning up for us.
+ */
+static int rtOnceOtherThread(PRTONCE pOnce, PRTSEMEVENTMULTI phEvtM)
+{
+ uint32_t cYields = 0;
+ for (;;)
+ {
+ int32_t iState = ASMAtomicReadS32(&pOnce->iState);
+ switch (iState)
+ {
+ /*
+ * No semaphore, try create one.
+ */
+ case RTONCESTATE_BUSY_NO_SEM:
+ if (ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_CREATING_SEM, RTONCESTATE_BUSY_NO_SEM))
+ {
+ int rc = RTSemEventMultiCreate(phEvtM);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicWriteHandle(&pOnce->hEventMulti, *phEvtM);
+ int32_t cRefs = ASMAtomicIncS32(&pOnce->cEventRefs); Assert(cRefs == 1); NOREF(cRefs);
+
+ if (!ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_HAVE_SEM, RTONCESTATE_BUSY_CREATING_SEM))
+ {
+ /* Too slow. */
+ AssertReturn(ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_CREATING_SEM)
+ , VERR_INTERNAL_ERROR_5);
+
+ ASMAtomicWriteHandle(&pOnce->hEventMulti, NIL_RTSEMEVENTMULTI);
+ cRefs = ASMAtomicDecS32(&pOnce->cEventRefs); Assert(cRefs == 0);
+
+ RTSemEventMultiDestroy(*phEvtM);
+ *phEvtM = NIL_RTSEMEVENTMULTI;
+ }
+ }
+ else
+ {
+ AssertReturn( ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_SPIN, RTONCESTATE_BUSY_CREATING_SEM)
+ || ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_CREATING_SEM)
+ , VERR_INTERNAL_ERROR_4);
+ *phEvtM = NIL_RTSEMEVENTMULTI;
+ }
+ }
+ break;
+
+ /*
+ * This isn't nice, but it's the easy way out.
+ */
+ case RTONCESTATE_BUSY_CREATING_SEM:
+ case RTONCESTATE_BUSY_SPIN:
+ cYields++;
+ if (!(++cYields % 8))
+ RTThreadSleep(1);
+ else
+ RTThreadYield();
+ break;
+
+ /*
+ * There is a semaphore, try wait on it.
+ *
+ * We continue waiting after reaching DONE_HAVE_SEM if we
+ * already got the semaphore to avoid racing the first thread.
+ */
+ case RTONCESTATE_DONE_HAVE_SEM:
+ if (*phEvtM == NIL_RTSEMEVENTMULTI)
+ return VINF_SUCCESS;
+ RT_FALL_THRU();
+ case RTONCESTATE_BUSY_HAVE_SEM:
+ {
+ /*
+ * Grab the semaphore if we haven't got it yet.
+ * We must take care not to increment the counter if it
+ * is 0. This may happen if we're racing a state change.
+ */
+ if (*phEvtM == NIL_RTSEMEVENTMULTI)
+ {
+ int32_t cEventRefs = ASMAtomicUoReadS32(&pOnce->cEventRefs);
+ while ( cEventRefs > 0
+ && ASMAtomicUoReadS32(&pOnce->iState) == RTONCESTATE_BUSY_HAVE_SEM)
+ {
+ if (ASMAtomicCmpXchgExS32(&pOnce->cEventRefs, cEventRefs + 1, cEventRefs, &cEventRefs))
+ break;
+ ASMNopPause();
+ }
+ if (cEventRefs <= 0)
+ break;
+
+ ASMAtomicReadHandle(&pOnce->hEventMulti, phEvtM);
+ AssertReturn(*phEvtM != NIL_RTSEMEVENTMULTI, VERR_INTERNAL_ERROR_2);
+ }
+
+ /*
+ * We've got a sempahore, do the actual waiting.
+ */
+ do
+ RTSemEventMultiWaitNoResume(*phEvtM, RT_INDEFINITE_WAIT);
+ while (ASMAtomicReadS32(&pOnce->iState) == RTONCESTATE_BUSY_HAVE_SEM);
+ break;
+ }
+
+ case RTONCESTATE_DONE_CREATING_SEM:
+ case RTONCESTATE_DONE:
+ return VINF_SUCCESS;
+
+ default:
+ AssertMsgFailedReturn(("%d\n", iState), VERR_INTERNAL_ERROR_3);
+ }
+ }
+}
+
+
+RTDECL(int) RTOnceSlow(PRTONCE pOnce, PFNRTONCE pfnOnce, PFNRTONCECLEANUP pfnCleanUp, void *pvUser)
+{
+ /*
+ * Validate input (strict builds only).
+ */
+ AssertPtr(pOnce);
+ AssertPtr(pfnOnce);
+
+ /*
+ * Deal with the 'initialized' case first
+ */
+ int32_t iState = ASMAtomicUoReadS32(&pOnce->iState);
+ if (RT_LIKELY( iState == RTONCESTATE_DONE
+ || iState == RTONCESTATE_DONE_CREATING_SEM
+ || iState == RTONCESTATE_DONE_HAVE_SEM
+ ))
+ return ASMAtomicUoReadS32(&pOnce->rc);
+
+ AssertReturn( iState == RTONCESTATE_UNINITIALIZED
+ || iState == RTONCESTATE_BUSY_NO_SEM
+ || iState == RTONCESTATE_BUSY_SPIN
+ || iState == RTONCESTATE_BUSY_CREATING_SEM
+ || iState == RTONCESTATE_BUSY_HAVE_SEM
+ , VERR_INTERNAL_ERROR);
+
+#ifdef RTONCE_NO_TERM
+ AssertReturn(!pfnCleanUp, VERR_NOT_SUPPORTED);
+#else /* !RTONCE_NO_TERM */
+
+ /*
+ * Make sure our clean-up bits are working if needed later.
+ */
+ if (pfnCleanUp)
+ {
+ int rc = RTOnce(&g_OnceCleanUp, rtOnceInitCleanUp, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+#endif /* !RTONCE_NO_TERM */
+
+ /*
+ * Do we initialize it?
+ */
+ int32_t rcOnce;
+ if ( iState == RTONCESTATE_UNINITIALIZED
+ && ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_NO_SEM, RTONCESTATE_UNINITIALIZED))
+ {
+ /*
+ * Yes, so do the execute once stuff.
+ */
+ rcOnce = pfnOnce(pvUser);
+ ASMAtomicWriteS32(&pOnce->rc, rcOnce);
+
+#ifndef RTONCE_NO_TERM
+ /*
+ * Register clean-up if requested and we were successful.
+ */
+ if (pfnCleanUp && RT_SUCCESS(rcOnce))
+ {
+ RTONCE_CLEANUP_LOCK();
+
+ pOnce->pfnCleanUp = pfnCleanUp;
+ pOnce->pvUser = pvUser;
+ RTListAppend(&g_CleanUpList, &pOnce->CleanUpNode);
+
+ RTONCE_CLEANUP_UNLOCK();
+ }
+#endif /* !RTONCE_NO_TERM */
+
+ /*
+ * If there is a sempahore to signal, we're in for some extra work here.
+ */
+ if ( !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_BUSY_NO_SEM)
+ && !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_BUSY_SPIN)
+ && !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE_CREATING_SEM, RTONCESTATE_BUSY_CREATING_SEM)
+ )
+ {
+ /* Grab the sempahore by switching to 'DONE_HAVE_SEM' before reaching 'DONE'. */
+ AssertReturn(ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE_HAVE_SEM, RTONCESTATE_BUSY_HAVE_SEM),
+ VERR_INTERNAL_ERROR_2);
+
+ int32_t cRefs = ASMAtomicIncS32(&pOnce->cEventRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+
+ RTSEMEVENTMULTI hEvtM;
+ ASMAtomicReadHandle(&pOnce->hEventMulti, &hEvtM);
+ Assert(hEvtM != NIL_RTSEMEVENTMULTI);
+
+ ASMAtomicWriteS32(&pOnce->iState, RTONCESTATE_DONE);
+
+ /* Signal it and return. */
+ RTSemEventMultiSignal(hEvtM);
+ }
+ }
+ else
+ {
+ /*
+ * Wait for the first thread to complete. Delegate this to a helper
+ * function to simplify cleanup and keep things a bit shorter.
+ */
+ RTSEMEVENTMULTI hEvtM = NIL_RTSEMEVENTMULTI;
+ rcOnce = rtOnceOtherThread(pOnce, &hEvtM);
+ if (hEvtM != NIL_RTSEMEVENTMULTI)
+ {
+ if (ASMAtomicDecS32(&pOnce->cEventRefs) == 0)
+ {
+ bool fRc;
+ ASMAtomicCmpXchgHandle(&pOnce->hEventMulti, NIL_RTSEMEVENTMULTI, hEvtM, fRc); Assert(fRc);
+ fRc = ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_HAVE_SEM); Assert(fRc);
+ RTSemEventMultiDestroy(hEvtM);
+ }
+ }
+ if (RT_SUCCESS(rcOnce))
+ rcOnce = ASMAtomicUoReadS32(&pOnce->rc);
+ }
+
+ return rcOnce;
+}
+RT_EXPORT_SYMBOL(RTOnceSlow);
+
+
+RTDECL(void) RTOnceReset(PRTONCE pOnce)
+{
+ /* Cannot be done while busy! */
+ AssertPtr(pOnce);
+ Assert(pOnce->hEventMulti == NIL_RTSEMEVENTMULTI);
+ int32_t iState = ASMAtomicUoReadS32(&pOnce->iState);
+ AssertMsg( iState == RTONCESTATE_DONE
+ || iState == RTONCESTATE_UNINITIALIZED,
+ ("%d\n", iState));
+ NOREF(iState);
+
+#ifndef RTONCE_NO_TERM
+ /* Unregister clean-up. */
+ if (pOnce->pfnCleanUp)
+ {
+ RTONCE_CLEANUP_LOCK();
+
+ RTListNodeRemove(&pOnce->CleanUpNode);
+ pOnce->pfnCleanUp = NULL;
+ pOnce->pvUser = NULL;
+
+ RTONCE_CLEANUP_UNLOCK();
+ }
+#endif /* !RTONCE_NO_TERM */
+
+ /* Do the same as RTONCE_INITIALIZER does. */
+ ASMAtomicWriteS32(&pOnce->rc, VERR_INTERNAL_ERROR);
+ ASMAtomicWriteS32(&pOnce->iState, RTONCESTATE_UNINITIALIZED);
+}
+RT_EXPORT_SYMBOL(RTOnceReset);
+
diff --git a/src/VBox/Runtime/common/misc/req.cpp b/src/VBox/Runtime/common/misc/req.cpp
new file mode 100644
index 00000000..943ae84f
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/req.cpp
@@ -0,0 +1,537 @@
+/* $Id: req.cpp $ */
+/** @file
+ * IPRT - Request packets
+ */
+
+/*
+ * 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/req.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+
+#include "internal/req.h"
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Allocate a new request from the heap.
+ *
+ * @returns IPRT status code.
+ * @param enmType The reques type.
+ * @param fPoolOrQueue The owner type.
+ * @param pvOwner The owner.
+ * @param phReq Where to return the request handle.
+ */
+DECLHIDDEN(int) rtReqAlloc(RTREQTYPE enmType, bool fPoolOrQueue, void *pvOwner, PRTREQ *phReq)
+{
+ PRTREQ pReq = (PRTREQ)RTMemAllocZ(sizeof(*pReq));
+ if (RT_UNLIKELY(!pReq))
+ return VERR_NO_MEMORY;
+
+ /*
+ * Create the semaphore used for waiting.
+ */
+ int rc = RTSemEventCreate(&pReq->EventSem);
+ AssertRCReturnStmt(rc, RTMemFree(pReq), rc);
+
+ /*
+ * Initialize the packet and return it.
+ */
+ pReq->u32Magic = RTREQ_MAGIC;
+ pReq->fEventSemClear = true;
+ pReq->fSignalPushBack = true;
+ pReq->fPoolOrQueue = fPoolOrQueue;
+ pReq->iStatusX = VERR_RT_REQUEST_STATUS_STILL_PENDING;
+ pReq->enmState = RTREQSTATE_ALLOCATED;
+ pReq->pNext = NULL;
+ pReq->uOwner.pv = pvOwner;
+ pReq->fFlags = RTREQFLAGS_IPRT_STATUS;
+ pReq->enmType = enmType;
+ pReq->cRefs = 1;
+
+ *phReq = pReq;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Re-initializes a request when it's being recycled.
+ *
+ * @returns IRPT status code, the request is freed on failure.
+ * @param pReq The request.
+ * @param enmType The request type.
+ */
+DECLHIDDEN(int) rtReqReInit(PRTREQINT pReq, RTREQTYPE enmType)
+{
+ Assert(pReq->u32Magic == RTREQ_MAGIC);
+ Assert(pReq->enmType == RTREQTYPE_INVALID);
+ Assert(pReq->enmState == RTREQSTATE_FREE);
+ Assert(pReq->cRefs == 0);
+
+ /*
+ * Make sure the event sem is not signaled.
+ */
+ if (!pReq->fEventSemClear)
+ {
+ int rc = RTSemEventWait(pReq->EventSem, 0);
+ if (rc != VINF_SUCCESS && rc != VERR_TIMEOUT)
+ {
+ /*
+ * This shall not happen, but if it does we'll just destroy
+ * the semaphore and create a new one.
+ */
+ AssertMsgFailed(("rc=%Rrc from RTSemEventWait(%#x).\n", rc, pReq->EventSem));
+ RTSemEventDestroy(pReq->EventSem);
+ rc = RTSemEventCreate(&pReq->EventSem);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ pReq->EventSem = NIL_RTSEMEVENT;
+ rtReqFreeIt(pReq);
+ return rc;
+ }
+ }
+ pReq->fEventSemClear = true;
+ }
+ else
+ Assert(RTSemEventWait(pReq->EventSem, 0) == VERR_TIMEOUT);
+
+ /*
+ * Initialize the packet and return it.
+ */
+ ASMAtomicWriteNullPtr(&pReq->pNext);
+ pReq->iStatusX = VERR_RT_REQUEST_STATUS_STILL_PENDING;
+ pReq->enmState = RTREQSTATE_ALLOCATED;
+ pReq->fFlags = RTREQFLAGS_IPRT_STATUS;
+ pReq->enmType = enmType;
+ pReq->cRefs = 1;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(uint32_t) RTReqRetain(PRTREQ hReq)
+{
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, UINT32_MAX);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);
+
+ return ASMAtomicIncU32(&pReq->cRefs);
+}
+RT_EXPORT_SYMBOL(RTReqRetain);
+
+
+/**
+ * Frees a request.
+ *
+ * @param pReq The request.
+ */
+DECLHIDDEN(void) rtReqFreeIt(PRTREQINT pReq)
+{
+ Assert(pReq->u32Magic == RTREQ_MAGIC);
+ Assert(pReq->cRefs == 0);
+
+ pReq->u32Magic = RTREQ_MAGIC_DEAD;
+ RTSemEventDestroy(pReq->EventSem);
+ pReq->EventSem = NIL_RTSEMEVENT;
+ RTSemEventMultiDestroy(pReq->hPushBackEvt);
+ pReq->hPushBackEvt = NIL_RTSEMEVENTMULTI;
+ RTMemFree(pReq);
+}
+
+
+RTDECL(uint32_t) RTReqRelease(PRTREQ hReq)
+{
+ /*
+ * Ignore NULL and validate the request.
+ */
+ if (!hReq)
+ return 0;
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, UINT32_MAX);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);
+
+ /*
+ * Drop a reference, recycle the request when we reach 0.
+ */
+ uint32_t cRefs = ASMAtomicDecU32(&pReq->cRefs);
+ if (cRefs == 0)
+ {
+ /*
+ * Check packet state.
+ */
+ RTREQSTATE const enmState = pReq->enmState;
+ switch (enmState)
+ {
+ case RTREQSTATE_ALLOCATED:
+ case RTREQSTATE_COMPLETED:
+ break;
+ default:
+ AssertMsgFailedReturn(("Invalid state %d!\n", enmState), 0);
+ }
+
+ /*
+ * Make it a free packet and put it into one of the free packet lists.
+ */
+ pReq->enmState = RTREQSTATE_FREE;
+ pReq->iStatusX = VERR_RT_REQUEST_STATUS_FREED;
+ pReq->enmType = RTREQTYPE_INVALID;
+
+ bool fRecycled;
+ if (pReq->fPoolOrQueue)
+ fRecycled = rtReqPoolRecycle(pReq->uOwner.hPool, pReq);
+ else
+ fRecycled = rtReqQueueRecycle(pReq->uOwner.hQueue, pReq);
+ if (!fRecycled)
+ rtReqFreeIt(pReq);
+ }
+
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTReqRelease);
+
+
+RTDECL(int) RTReqSubmit(PRTREQ hReq, RTMSINTERVAL cMillies)
+{
+ LogFlow(("RTReqSubmit: hReq=%p cMillies=%d\n", hReq, cMillies));
+
+ /*
+ * Verify the supplied package.
+ */
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn(pReq->enmState == RTREQSTATE_ALLOCATED, ("%d\n", pReq->enmState), VERR_RT_REQUEST_STATE);
+ AssertMsgReturn(pReq->uOwner.hQueue && !pReq->pNext && pReq->EventSem != NIL_RTSEMEVENT,
+ ("Invalid request package! Anyone cooking their own packages???\n"),
+ VERR_RT_REQUEST_INVALID_PACKAGE);
+ AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
+ ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
+ pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
+ VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Insert it. Always grab a reference for the queue (we used to
+ * donate the caller's reference in the NO_WAIT case once upon a time).
+ */
+ pReq->uSubmitNanoTs = RTTimeNanoTS();
+ pReq->enmState = RTREQSTATE_QUEUED;
+ unsigned fFlags = ((RTREQ volatile *)pReq)->fFlags; /* volatile paranoia */
+ RTReqRetain(pReq);
+
+ if (!pReq->fPoolOrQueue)
+ rtReqQueueSubmit(pReq->uOwner.hQueue, pReq);
+ else
+ rtReqPoolSubmit(pReq->uOwner.hPool, pReq);
+
+ /*
+ * Wait and return.
+ */
+ int rc = VINF_SUCCESS;
+ if (!(fFlags & RTREQFLAGS_NO_WAIT))
+ rc = RTReqWait(pReq, cMillies);
+
+ LogFlow(("RTReqSubmit: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqSubmit);
+
+
+RTDECL(int) RTReqWait(PRTREQ hReq, RTMSINTERVAL cMillies)
+{
+ LogFlow(("RTReqWait: hReq=%p cMillies=%d\n", hReq, cMillies));
+
+ /*
+ * Verify the supplied package.
+ */
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
+ RTREQSTATE enmState = pReq->enmState;
+ AssertMsgReturn( enmState == RTREQSTATE_QUEUED
+ || enmState == RTREQSTATE_PROCESSING
+ || enmState == RTREQSTATE_COMPLETED
+ || enmState == RTREQSTATE_CANCELLED,
+ ("Invalid state %d\n", enmState),
+ VERR_RT_REQUEST_STATE);
+ AssertMsgReturn(pReq->uOwner.hQueue && pReq->EventSem != NIL_RTSEMEVENT,
+ ("Invalid request package! Anyone cooking their own packages???\n"),
+ VERR_RT_REQUEST_INVALID_PACKAGE);
+ AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
+ ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
+ pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
+ VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Wait on the package.
+ */
+ int rc;
+ if (cMillies != RT_INDEFINITE_WAIT)
+ rc = RTSemEventWait(pReq->EventSem, cMillies);
+ else
+ {
+ do
+ {
+ rc = RTSemEventWait(pReq->EventSem, RT_INDEFINITE_WAIT);
+ Assert(rc != VERR_TIMEOUT);
+ } while (pReq->enmState != RTREQSTATE_COMPLETED);
+ }
+ if (rc == VINF_SUCCESS)
+ ASMAtomicWriteBool(&pReq->fEventSemClear, true);
+ if (pReq->enmState == RTREQSTATE_COMPLETED)
+ rc = VINF_SUCCESS;
+ LogFlow(("RTReqWait: returns %Rrc\n", rc));
+ Assert(rc != VERR_INTERRUPTED);
+ Assert(pReq->cRefs >= 1);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqWait);
+
+
+RTDECL(int) RTReqCancel(PRTREQ hReq)
+{
+ LogFlow(("RTReqCancel: hReq=%p\n", hReq));
+
+ /*
+ * Verify the supplied package.
+ */
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn(pReq->uOwner.hQueue && pReq->EventSem != NIL_RTSEMEVENT,
+ ("Invalid request package! Anyone cooking their own packages???\n"),
+ VERR_RT_REQUEST_INVALID_PACKAGE);
+ AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
+ ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
+ pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
+ VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Try cancel the request itself by changing its state.
+ */
+ int rc;
+ if (ASMAtomicCmpXchgU32((uint32_t volatile *)&pReq->enmState, RTREQSTATE_CANCELLED, RTREQSTATE_QUEUED))
+ {
+ if (pReq->fPoolOrQueue)
+ rtReqPoolCancel(pReq->uOwner.hPool, pReq);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ Assert(pReq->enmState == RTREQSTATE_PROCESSING || pReq->enmState == RTREQSTATE_COMPLETED);
+ rc = VERR_RT_REQUEST_STATE;
+ }
+
+ LogFlow(("RTReqCancel: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqCancel);
+
+
+RTDECL(int) RTReqGetStatus(PRTREQ hReq)
+{
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_POINTER);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_POINTER);
+ return pReq->iStatusX;
+}
+RT_EXPORT_SYMBOL(RTReqGetStatus);
+
+
+
+/**
+ * Process one request.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pReq Request packet to process.
+ */
+DECLHIDDEN(int) rtReqProcessOne(PRTREQINT pReq)
+{
+ LogFlow(("rtReqProcessOne: pReq=%p type=%d fFlags=%#x\n", pReq, pReq->enmType, pReq->fFlags));
+
+ /*
+ * Try switch the request status to processing.
+ */
+ int rcRet = VINF_SUCCESS; /* the return code of this function. */
+ int rcReq = VERR_NOT_IMPLEMENTED; /* the request status. */
+ if (ASMAtomicCmpXchgU32((uint32_t volatile *)&pReq->enmState, RTREQSTATE_PROCESSING, RTREQSTATE_QUEUED))
+ {
+ /*
+ * Process the request.
+ */
+ pReq->enmState = RTREQSTATE_PROCESSING;
+ switch (pReq->enmType)
+ {
+ /*
+ * A packed down call frame.
+ */
+ case RTREQTYPE_INTERNAL:
+ {
+ uintptr_t *pauArgs = &pReq->u.Internal.aArgs[0];
+ union
+ {
+ PFNRT pfn;
+ DECLCALLBACKMEMBER(int, pfn00,(void));
+ DECLCALLBACKMEMBER(int, pfn01,(uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn02,(uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn03,(uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn04,(uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn05,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn06,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn07,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn08,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn09,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn10,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn11,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn12,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ } u;
+ u.pfn = pReq->u.Internal.pfn;
+#ifndef RT_ARCH_X86
+ switch (pReq->u.Internal.cArgs)
+ {
+ case 0: rcRet = u.pfn00(); break;
+ case 1: rcRet = u.pfn01(pauArgs[0]); break;
+ case 2: rcRet = u.pfn02(pauArgs[0], pauArgs[1]); break;
+ case 3: rcRet = u.pfn03(pauArgs[0], pauArgs[1], pauArgs[2]); break;
+ case 4: rcRet = u.pfn04(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3]); break;
+ case 5: rcRet = u.pfn05(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4]); break;
+ case 6: rcRet = u.pfn06(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5]); break;
+ case 7: rcRet = u.pfn07(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6]); break;
+ case 8: rcRet = u.pfn08(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7]); break;
+ case 9: rcRet = u.pfn09(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8]); break;
+ case 10: rcRet = u.pfn10(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9]); break;
+ case 11: rcRet = u.pfn11(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10]); break;
+ case 12: rcRet = u.pfn12(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10], pauArgs[11]); break;
+ default:
+ AssertReleaseMsgFailed(("cArgs=%d\n", pReq->u.Internal.cArgs));
+ rcRet = rcReq = VERR_INTERNAL_ERROR;
+ break;
+ }
+#else /* RT_ARCH_X86 */
+ size_t cbArgs = pReq->u.Internal.cArgs * sizeof(uintptr_t);
+# ifdef __GNUC__
+ __asm__ __volatile__("movl %%esp, %%edx\n\t"
+ "subl %2, %%esp\n\t"
+ "andl $0xfffffff0, %%esp\n\t"
+ "shrl $2, %2\n\t"
+ "movl %%esp, %%edi\n\t"
+ "rep movsl\n\t"
+ "movl %%edx, %%edi\n\t"
+ "call *%%eax\n\t"
+ "mov %%edi, %%esp\n\t"
+ : "=a" (rcRet),
+ "=S" (pauArgs),
+ "=c" (cbArgs)
+ : "0" (u.pfn),
+ "1" (pauArgs),
+ "2" (cbArgs)
+ : "edi", "edx");
+# else
+ __asm
+ {
+ xor edx, edx /* just mess it up. */
+ mov eax, u.pfn
+ mov ecx, cbArgs
+ shr ecx, 2
+ mov esi, pauArgs
+ mov ebx, esp
+ sub esp, cbArgs
+ and esp, 0xfffffff0
+ mov edi, esp
+ rep movsd
+ call eax
+ mov esp, ebx
+ mov rcRet, eax
+ }
+# endif
+#endif /* RT_ARCH_X86 */
+ if ((pReq->fFlags & (RTREQFLAGS_RETURN_MASK)) == RTREQFLAGS_VOID)
+ rcRet = VINF_SUCCESS;
+ rcReq = rcRet;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("pReq->enmType=%d\n", pReq->enmType));
+ rcReq = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+ else
+ {
+ Assert(pReq->enmState == RTREQSTATE_CANCELLED);
+ rcReq = VERR_CANCELLED;
+ }
+
+ /*
+ * Complete the request and then release our request handle reference.
+ */
+ pReq->iStatusX = rcReq;
+ pReq->enmState = RTREQSTATE_COMPLETED;
+ if (pReq->fFlags & RTREQFLAGS_NO_WAIT)
+ LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc (no wait)\n",
+ pReq, rcReq, rcRet));
+ else
+ {
+ /* Notify the waiting thread. */
+ LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc - notifying waiting thread\n",
+ pReq, rcReq, rcRet));
+ ASMAtomicWriteBool(&pReq->fEventSemClear, false);
+ int rc2 = RTSemEventSignal(pReq->EventSem);
+ if (rc2 != VINF_SUCCESS)
+ {
+ AssertRC(rc2);
+ rcRet = rc2;
+ }
+ }
+ RTReqRelease(pReq);
+ return rcRet;
+}
+
diff --git a/src/VBox/Runtime/common/misc/reqpool.cpp b/src/VBox/Runtime/common/misc/reqpool.cpp
new file mode 100644
index 00000000..1489c5b7
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/reqpool.cpp
@@ -0,0 +1,1299 @@
+/* $Id: reqpool.cpp $ */
+/** @file
+ * IPRT - Request Pool.
+ */
+
+/*
+ * 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/req.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+#include "internal/req.h"
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max number of worker threads. */
+#define RTREQPOOL_MAX_THREADS UINT32_C(16384)
+/** The max number of milliseconds to push back. */
+#define RTREQPOOL_PUSH_BACK_MAX_MS RT_MS_1MIN
+/** The max number of free requests to keep around. */
+#define RTREQPOOL_MAX_FREE_REQUESTS (RTREQPOOL_MAX_THREADS * 2U)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTREQPOOLTHREAD
+{
+ /** Node in the RTREQPOOLINT::IdleThreads list. */
+ RTLISTNODE IdleNode;
+ /** Node in the RTREQPOOLINT::WorkerThreads list. */
+ RTLISTNODE ListNode;
+
+ /** The submit timestamp of the pending request. */
+ uint64_t uPendingNanoTs;
+ /** The submit timestamp of the request processing. */
+ uint64_t uProcessingNanoTs;
+ /** When this CPU went idle the last time. */
+ uint64_t uIdleNanoTs;
+ /** The number of requests processed by this thread. */
+ uint64_t cReqProcessed;
+ /** Total time the requests processed by this thread took to process. */
+ uint64_t cNsTotalReqProcessing;
+ /** Total time the requests processed by this thread had to wait in
+ * the queue before being scheduled. */
+ uint64_t cNsTotalReqQueued;
+ /** The CPU this was scheduled last time we checked. */
+ RTCPUID idLastCpu;
+
+ /** The submitter will put an incoming request here when scheduling an idle
+ * thread. */
+ PRTREQINT volatile pTodoReq;
+ /** The request the thread is currently processing. */
+ PRTREQINT volatile pPendingReq;
+
+ /** The thread handle. */
+ RTTHREAD hThread;
+ /** Nano seconds timestamp representing the birth time of the thread. */
+ uint64_t uBirthNanoTs;
+ /** Pointer to the request thread pool instance the thread is associated
+ * with. */
+ struct RTREQPOOLINT *pPool;
+} RTREQPOOLTHREAD;
+/** Pointer to a worker thread. */
+typedef RTREQPOOLTHREAD *PRTREQPOOLTHREAD;
+
+/**
+ * Request thread pool instance data.
+ */
+typedef struct RTREQPOOLINT
+{
+ /** Magic value (RTREQPOOL_MAGIC). */
+ uint32_t u32Magic;
+ /** The request pool name. */
+ char szName[12];
+
+ /** @name Config
+ * @{ */
+ /** The worker thread type. */
+ RTTHREADTYPE enmThreadType;
+ /** The work thread flags (RTTHREADFLAGS). */
+ uint32_t fThreadFlags;
+ /** The maximum number of worker threads. */
+ uint32_t cMaxThreads;
+ /** The minimum number of worker threads. */
+ uint32_t cMinThreads;
+ /** The number of milliseconds a thread needs to be idle before it is
+ * considered for retirement. */
+ uint32_t cMsMinIdle;
+ /** cMsMinIdle in nano seconds. */
+ uint64_t cNsMinIdle;
+ /** The idle thread sleep interval in milliseconds. */
+ RTMSINTERVAL cMsIdleSleep;
+ /** The number of threads which should be spawned before throttling kicks
+ * in. */
+ uint32_t cThreadsPushBackThreshold;
+ /** The max number of milliseconds to push back a submitter before creating
+ * a new worker thread once the threshold has been reached. */
+ uint32_t cMsMaxPushBack;
+ /** The minimum number of milliseconds to push back a submitter before
+ * creating a new worker thread once the threshold has been reached. */
+ uint32_t cMsMinPushBack;
+ /** The max number of free requests in the recycle LIFO. */
+ uint32_t cMaxFreeRequests;
+ /** @} */
+
+ /** Signaled by terminating worker threads. */
+ RTSEMEVENTMULTI hThreadTermEvt;
+
+ /** Destruction indicator. The worker threads checks in their loop. */
+ bool volatile fDestructing;
+
+ /** The current submitter push back in milliseconds.
+ * This is recalculated when worker threads come and go. */
+ uint32_t cMsCurPushBack;
+ /** The current number of worker threads. */
+ uint32_t cCurThreads;
+ /** Statistics: The total number of threads created. */
+ uint32_t cThreadsCreated;
+ /** Statistics: The timestamp when the last thread was created. */
+ uint64_t uLastThreadCreateNanoTs;
+ /** Linked list of worker threads. */
+ RTLISTANCHOR WorkerThreads;
+
+ /** The number of requests processed and counted in the time totals. */
+ uint64_t cReqProcessed;
+ /** Total time the requests processed by this thread took to process. */
+ uint64_t cNsTotalReqProcessing;
+ /** Total time the requests processed by this thread had to wait in
+ * the queue before being scheduled. */
+ uint64_t cNsTotalReqQueued;
+
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The number of idle thread or threads in the process of becoming
+ * idle. This is increased before the to-be-idle thread tries to enter
+ * the critical section and add itself to the list. */
+ uint32_t volatile cIdleThreads;
+ /** Linked list of idle threads. */
+ RTLISTANCHOR IdleThreads;
+
+ /** Head of the request FIFO. */
+ PRTREQINT pPendingRequests;
+ /** Where to insert the next request. */
+ PRTREQINT *ppPendingRequests;
+ /** The number of requests currently pending. */
+ uint32_t cCurPendingRequests;
+ /** The number of requests currently being executed. */
+ uint32_t volatile cCurActiveRequests;
+ /** The number of requests submitted. */
+ uint64_t cReqSubmitted;
+ /** The number of cancelled. */
+ uint64_t cReqCancelled;
+
+ /** Head of the request recycling LIFO. */
+ PRTREQINT pFreeRequests;
+ /** The number of requests in the recycling LIFO. This is read without
+ * entering the critical section, thus volatile. */
+ uint32_t volatile cCurFreeRequests;
+
+ /** Critical section serializing access to members of this structure. */
+ RTCRITSECT CritSect;
+
+} RTREQPOOLINT;
+
+
+/**
+ * Used by exiting thread and the pool destruction code to cancel unexpected
+ * requests.
+ *
+ * @param pReq The request.
+ */
+static void rtReqPoolCancelReq(PRTREQINT pReq)
+{
+ pReq->uOwner.hPool = NIL_RTREQPOOL; /* force free */
+ pReq->enmState = RTREQSTATE_COMPLETED;
+ ASMAtomicWriteS32(&pReq->iStatusX, VERR_CANCELLED);
+ if (pReq->hPushBackEvt != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(pReq->hPushBackEvt);
+ RTSemEventSignal(pReq->EventSem);
+
+ RTReqRelease(pReq);
+}
+
+
+/**
+ * Recalculate the max pushback interval when adding or removing worker threads.
+ *
+ * @param pPool The pool. cMsCurPushBack will be changed.
+ */
+static void rtReqPoolRecalcPushBack(PRTREQPOOLINT pPool)
+{
+ uint32_t const cMsRange = pPool->cMsMaxPushBack - pPool->cMsMinPushBack;
+ uint32_t const cSteps = pPool->cMaxThreads - pPool->cThreadsPushBackThreshold;
+ uint32_t const iStep = pPool->cCurThreads - pPool->cThreadsPushBackThreshold;
+
+ uint32_t cMsCurPushBack;
+ if (cSteps == 0 /* disabled */)
+ cMsCurPushBack = 0;
+ else if ((cMsRange >> 2) >= cSteps)
+ cMsCurPushBack = cMsRange / cSteps * iStep;
+ else
+ cMsCurPushBack = (uint32_t)( (uint64_t)cMsRange * RT_NS_1MS / cSteps * iStep / RT_NS_1MS );
+ cMsCurPushBack += pPool->cMsMinPushBack;
+
+ pPool->cMsCurPushBack = cMsCurPushBack;
+}
+
+
+
+/**
+ * Performs thread exit.
+ *
+ * @returns Thread termination status code (VINF_SUCCESS).
+ * @param pPool The pool.
+ * @param pThread The thread.
+ * @param fLocked Whether we are inside the critical section
+ * already.
+ */
+static int rtReqPoolThreadExit(PRTREQPOOLINT pPool, PRTREQPOOLTHREAD pThread, bool fLocked)
+{
+ if (!fLocked)
+ RTCritSectEnter(&pPool->CritSect);
+
+ /* Get out of the idle list. */
+ if (!RTListIsEmpty(&pThread->IdleNode))
+ {
+ RTListNodeRemove(&pThread->IdleNode);
+ Assert(pPool->cIdleThreads > 0);
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ }
+
+ /* Get out of the thread list. */
+ RTListNodeRemove(&pThread->ListNode);
+ Assert(pPool->cCurThreads > 0);
+ pPool->cCurThreads--;
+ rtReqPoolRecalcPushBack(pPool);
+
+ /* This shouldn't happen... */
+ PRTREQINT pReq = pThread->pTodoReq;
+ if (pReq)
+ {
+ AssertFailed();
+ pThread->pTodoReq = NULL;
+ rtReqPoolCancelReq(pReq);
+ }
+
+ /* If we're the last thread terminating, ping the destruction thread before
+ we leave the critical section. */
+ if ( RTListIsEmpty(&pPool->WorkerThreads)
+ && pPool->hThreadTermEvt != NIL_RTSEMEVENT)
+ RTSemEventMultiSignal(pPool->hThreadTermEvt);
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ RTMemFree(pThread);
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Process one request.
+ *
+ * @param pPool The pool.
+ * @param pThread The worker thread.
+ * @param pReq The request to process.
+ */
+static void rtReqPoolThreadProcessRequest(PRTREQPOOLINT pPool, PRTREQPOOLTHREAD pThread, PRTREQINT pReq)
+{
+ /*
+ * Update thread state.
+ */
+ pThread->uProcessingNanoTs = RTTimeNanoTS();
+ pThread->uPendingNanoTs = pReq->uSubmitNanoTs;
+ pThread->pPendingReq = pReq;
+ ASMAtomicIncU32(&pPool->cCurActiveRequests);
+ Assert(pReq->u32Magic == RTREQ_MAGIC);
+
+ /*
+ * Do the actual processing.
+ */
+ rtReqProcessOne(pReq);
+
+ /*
+ * Update thread statistics and state.
+ */
+ ASMAtomicDecU32(&pPool->cCurActiveRequests);
+ pThread->pPendingReq = NULL;
+ uint64_t const uNsTsEnd = RTTimeNanoTS();
+ pThread->cNsTotalReqProcessing += uNsTsEnd - pThread->uProcessingNanoTs;
+ pThread->cNsTotalReqQueued += pThread->uProcessingNanoTs - pThread->uPendingNanoTs;
+ pThread->cReqProcessed++;
+}
+
+
+
+/**
+ * The Worker Thread Procedure.
+ *
+ * @returns VINF_SUCCESS.
+ * @param hThreadSelf The thread handle (unused).
+ * @param pvArg Pointer to the thread data.
+ */
+static DECLCALLBACK(int) rtReqPoolThreadProc(RTTHREAD hThreadSelf, void *pvArg)
+{
+ PRTREQPOOLTHREAD pThread = (PRTREQPOOLTHREAD)pvArg;
+ PRTREQPOOLINT pPool = pThread->pPool;
+
+ /*
+ * The work loop.
+ */
+ uint64_t cReqPrevProcessedIdle = UINT64_MAX;
+ uint64_t cReqPrevProcessedStat = 0;
+ uint64_t cNsPrevTotalReqProcessing = 0;
+ uint64_t cNsPrevTotalReqQueued = 0;
+ while (!pPool->fDestructing)
+ {
+ /*
+ * Process pending work.
+ */
+
+ /* Check if anything is scheduled directly to us. */
+ PRTREQINT pReq = ASMAtomicXchgPtrT(&pThread->pTodoReq, NULL, PRTREQINT);
+ if (pReq)
+ {
+ Assert(RTListIsEmpty(&pThread->IdleNode)); /* Must not be in the idle list. */
+ rtReqPoolThreadProcessRequest(pPool, pThread, pReq);
+ continue;
+ }
+
+ ASMAtomicIncU32(&pPool->cIdleThreads);
+ RTCritSectEnter(&pPool->CritSect);
+
+ /* Update the global statistics. */
+ if (cReqPrevProcessedStat != pThread->cReqProcessed)
+ {
+ pPool->cReqProcessed += pThread->cReqProcessed - cReqPrevProcessedStat;
+ cReqPrevProcessedStat = pThread->cReqProcessed;
+ pPool->cNsTotalReqProcessing += pThread->cNsTotalReqProcessing - cNsPrevTotalReqProcessing;
+ cNsPrevTotalReqProcessing = pThread->cNsTotalReqProcessing;
+ pPool->cNsTotalReqQueued += pThread->cNsTotalReqQueued - cNsPrevTotalReqQueued;
+ cNsPrevTotalReqQueued = pThread->cNsTotalReqQueued;
+ }
+
+ /* Recheck the todo request pointer after entering the critsect. */
+ pReq = ASMAtomicXchgPtrT(&pThread->pTodoReq, NULL, PRTREQINT);
+ if (pReq)
+ {
+ Assert(RTListIsEmpty(&pThread->IdleNode)); /* Must not be in the idle list. */
+ RTCritSectLeave(&pPool->CritSect);
+
+ rtReqPoolThreadProcessRequest(pPool, pThread, pReq);
+ continue;
+ }
+
+ /* Any pending requests in the queue? */
+ pReq = pPool->pPendingRequests;
+ if (pReq)
+ {
+ pPool->pPendingRequests = pReq->pNext;
+ if (pReq->pNext == NULL)
+ pPool->ppPendingRequests = &pPool->pPendingRequests;
+ Assert(pPool->cCurPendingRequests > 0);
+ pPool->cCurPendingRequests--;
+
+ /* Un-idle ourselves and process the request. */
+ if (!RTListIsEmpty(&pThread->IdleNode))
+ {
+ RTListNodeRemove(&pThread->IdleNode);
+ RTListInit(&pThread->IdleNode);
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ }
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ RTCritSectLeave(&pPool->CritSect);
+
+ rtReqPoolThreadProcessRequest(pPool, pThread, pReq);
+ continue;
+ }
+
+ /*
+ * Nothing to do, go idle.
+ */
+ if (cReqPrevProcessedIdle != pThread->cReqProcessed)
+ {
+ cReqPrevProcessedIdle = pThread->cReqProcessed;
+ pThread->uIdleNanoTs = RTTimeNanoTS();
+ }
+ else if (pPool->cCurThreads > pPool->cMinThreads)
+ {
+ uint64_t cNsIdle = RTTimeNanoTS() - pThread->uIdleNanoTs;
+ if (cNsIdle >= pPool->cNsMinIdle)
+ return rtReqPoolThreadExit(pPool, pThread, true /*fLocked*/);
+ }
+
+ if (RTListIsEmpty(&pThread->IdleNode))
+ RTListPrepend(&pPool->IdleThreads, &pThread->IdleNode);
+ else
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ RTThreadUserReset(hThreadSelf);
+ uint32_t const cMsSleep = pPool->cMsIdleSleep;
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ RTThreadUserWait(hThreadSelf, cMsSleep);
+ }
+
+ return rtReqPoolThreadExit(pPool, pThread, false /*fLocked*/);
+}
+
+
+/**
+ * Create a new worker thread.
+ *
+ * @param pPool The pool needing new worker thread.
+ * @remarks Caller owns the critical section
+ */
+static void rtReqPoolCreateNewWorker(RTREQPOOL pPool)
+{
+ PRTREQPOOLTHREAD pThread = (PRTREQPOOLTHREAD)RTMemAllocZ(sizeof(RTREQPOOLTHREAD));
+ if (!pThread)
+ return;
+
+ pThread->uBirthNanoTs = RTTimeNanoTS();
+ pThread->pPool = pPool;
+ pThread->idLastCpu = NIL_RTCPUID;
+ pThread->hThread = NIL_RTTHREAD;
+ RTListInit(&pThread->IdleNode);
+ RTListAppend(&pPool->WorkerThreads, &pThread->ListNode);
+ pPool->cCurThreads++;
+ pPool->cThreadsCreated++;
+
+ int rc = RTThreadCreateF(&pThread->hThread, rtReqPoolThreadProc, pThread, 0 /*default stack size*/,
+ pPool->enmThreadType, pPool->fThreadFlags, "%s%02u", pPool->szName, pPool->cThreadsCreated);
+ if (RT_SUCCESS(rc))
+ pPool->uLastThreadCreateNanoTs = pThread->uBirthNanoTs;
+ else
+ {
+ pPool->cCurThreads--;
+ RTListNodeRemove(&pThread->ListNode);
+ RTMemFree(pThread);
+ }
+}
+
+
+/**
+ * Repel the submitter, giving the worker threads a chance to process the
+ * incoming request.
+ *
+ * @returns Success if a worker picked up the request, failure if not. The
+ * critical section has been left on success, while we'll be inside it
+ * on failure.
+ * @param pPool The pool.
+ * @param pReq The incoming request.
+ */
+static int rtReqPoolPushBack(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ /*
+ * Lazily create the push back semaphore that we'll be blociing on.
+ */
+ int rc;
+ RTSEMEVENTMULTI hEvt = pReq->hPushBackEvt;
+ if (hEvt == NIL_RTSEMEVENTMULTI)
+ {
+ rc = RTSemEventMultiCreate(&hEvt);
+ if (RT_FAILURE(rc))
+ return rc;
+ pReq->hPushBackEvt = hEvt;
+ }
+
+ /*
+ * Prepare the request and semaphore.
+ */
+ uint32_t const cMsTimeout = pPool->cMsCurPushBack;
+ pReq->fSignalPushBack = true;
+ RTReqRetain(pReq);
+ RTSemEventMultiReset(hEvt);
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ /*
+ * Block.
+ */
+ rc = RTSemEventMultiWait(hEvt, cMsTimeout);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsg(rc == VERR_TIMEOUT, ("%Rrc\n", rc));
+ RTCritSectEnter(&pPool->CritSect);
+ }
+ RTReqRelease(pReq);
+ return rc;
+}
+
+
+
+DECLHIDDEN(void) rtReqPoolSubmit(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ RTCritSectEnter(&pPool->CritSect);
+
+ pPool->cReqSubmitted++;
+
+ /*
+ * Try schedule the request to a thread that's currently idle.
+ */
+ PRTREQPOOLTHREAD pThread = RTListGetFirst(&pPool->IdleThreads, RTREQPOOLTHREAD, IdleNode);
+ if (pThread)
+ {
+ /** @todo CPU affinity??? */
+ ASMAtomicWritePtr(&pThread->pTodoReq, pReq);
+
+ RTListNodeRemove(&pThread->IdleNode);
+ RTListInit(&pThread->IdleNode);
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+
+ RTThreadUserSignal(pThread->hThread);
+
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+ }
+ Assert(RTListIsEmpty(&pPool->IdleThreads));
+
+ /*
+ * Put the request in the pending queue.
+ */
+ pReq->pNext = NULL;
+ *pPool->ppPendingRequests = pReq;
+ pPool->ppPendingRequests = (PRTREQINT *)&pReq->pNext;
+ pPool->cCurPendingRequests++;
+
+ /*
+ * If there is an incoming worker thread already or we've reached the
+ * maximum number of worker threads, we're done.
+ */
+ if ( pPool->cIdleThreads > 0
+ || pPool->cCurThreads >= pPool->cMaxThreads)
+ {
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+ }
+
+ /*
+ * Push back before creating a new worker thread.
+ */
+ if ( pPool->cCurThreads > pPool->cThreadsPushBackThreshold
+ && (RTTimeNanoTS() - pReq->uSubmitNanoTs) / RT_NS_1MS >= pPool->cMsCurPushBack )
+ {
+ int rc = rtReqPoolPushBack(pPool, pReq);
+ if (RT_SUCCESS(rc))
+ return;
+ }
+
+ /*
+ * Create a new thread for processing the request.
+ * For simplicity, we don't bother leaving the critical section while doing so.
+ */
+ rtReqPoolCreateNewWorker(pPool);
+
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+}
+
+
+/**
+ * Worker for RTReqCancel that looks for the request in the pending list and
+ * completes it if found there.
+ *
+ * @param pPool The request thread pool.
+ * @param pReq The request.
+ */
+DECLHIDDEN(void) rtReqPoolCancel(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ RTCritSectEnter(&pPool->CritSect);
+
+ pPool->cReqCancelled++;
+
+ /*
+ * Check if the request is in the pending list.
+ */
+ PRTREQINT pPrev = NULL;
+ PRTREQINT pCur = pPool->pPendingRequests;
+ while (pCur)
+ if (pCur != pReq)
+ {
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+ else
+ {
+ /*
+ * Unlink it and process it.
+ */
+ if (!pPrev)
+ {
+ pPool->pPendingRequests = pReq->pNext;
+ if (!pReq->pNext)
+ pPool->ppPendingRequests = &pPool->pPendingRequests;
+ }
+ else
+ {
+ pPrev->pNext = pReq->pNext;
+ if (!pReq->pNext)
+ pPool->ppPendingRequests = (PRTREQINT *)&pPrev->pNext;
+ }
+ Assert(pPool->cCurPendingRequests > 0);
+ pPool->cCurPendingRequests--;
+
+ rtReqProcessOne(pReq);
+ break;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+}
+
+
+/**
+ * Frees a requst.
+ *
+ * @returns true if recycled, false if not.
+ * @param pPool The request thread pool.
+ * @param pReq The request.
+ */
+DECLHIDDEN(bool) rtReqPoolRecycle(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ if ( pPool
+ && ASMAtomicReadU32(&pPool->cCurFreeRequests) < pPool->cMaxFreeRequests)
+ {
+ RTCritSectEnter(&pPool->CritSect);
+ if (pPool->cCurFreeRequests < pPool->cMaxFreeRequests)
+ {
+ pReq->pNext = pPool->pFreeRequests;
+ pPool->pFreeRequests = pReq;
+ ASMAtomicIncU32(&pPool->cCurFreeRequests);
+
+ RTCritSectLeave(&pPool->CritSect);
+ return true;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+ }
+ return false;
+}
+
+
+RTDECL(int) RTReqPoolCreate(uint32_t cMaxThreads, RTMSINTERVAL cMsMinIdle,
+ uint32_t cThreadsPushBackThreshold, uint32_t cMsMaxPushBack,
+ const char *pszName, PRTREQPOOL phPool)
+{
+ /*
+ * Validate and massage the config.
+ */
+ if (cMaxThreads == UINT32_MAX)
+ cMaxThreads = RTREQPOOL_MAX_THREADS;
+ AssertMsgReturn(cMaxThreads > 0 && cMaxThreads <= RTREQPOOL_MAX_THREADS, ("%u\n", cMaxThreads), VERR_OUT_OF_RANGE);
+ uint32_t const cMinThreads = cMaxThreads > 2 ? 2 : cMaxThreads - 1;
+
+ if (cThreadsPushBackThreshold == 0)
+ cThreadsPushBackThreshold = cMinThreads;
+ else if (cThreadsPushBackThreshold == UINT32_MAX)
+ cThreadsPushBackThreshold = cMaxThreads;
+ AssertMsgReturn(cThreadsPushBackThreshold <= cMaxThreads, ("%u/%u\n", cThreadsPushBackThreshold, cMaxThreads), VERR_OUT_OF_RANGE);
+
+ if (cMsMaxPushBack == UINT32_MAX)
+ cMsMaxPushBack = RTREQPOOL_PUSH_BACK_MAX_MS;
+ AssertMsgReturn(cMsMaxPushBack <= RTREQPOOL_PUSH_BACK_MAX_MS, ("%llu\n", cMsMaxPushBack), VERR_OUT_OF_RANGE);
+ uint32_t const cMsMinPushBack = cMsMaxPushBack >= 200 ? 100 : cMsMaxPushBack / 2;
+
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ size_t cchName = strlen(pszName);
+ AssertReturn(cchName > 0, VERR_INVALID_PARAMETER);
+ Assert(cchName <= 10);
+
+ AssertPtrReturn(phPool, VERR_INVALID_POINTER);
+
+ /*
+ * Create and initialize the pool.
+ */
+ PRTREQPOOLINT pPool = (PRTREQPOOLINT)RTMemAlloc(sizeof(*pPool));
+ if (!pPool)
+ return VERR_NO_MEMORY;
+
+ pPool->u32Magic = RTREQPOOL_MAGIC;
+ RTStrCopy(pPool->szName, sizeof(pPool->szName), pszName);
+
+ pPool->enmThreadType = RTTHREADTYPE_DEFAULT;
+ pPool->fThreadFlags = 0;
+ pPool->cMaxThreads = cMaxThreads;
+ pPool->cMinThreads = cMinThreads;
+ pPool->cMsMinIdle = cMsMinIdle == RT_INDEFINITE_WAIT || cMsMinIdle >= UINT32_MAX ? UINT32_MAX : cMsMinIdle;
+ pPool->cNsMinIdle = pPool->cMsMinIdle == UINT32_MAX ? UINT64_MAX : cMsMinIdle * RT_NS_1MS_64;
+ pPool->cMsIdleSleep = pPool->cMsMinIdle == UINT32_MAX ? RT_INDEFINITE_WAIT : RT_MAX(RT_MS_1SEC, pPool->cMsMinIdle);
+ pPool->cThreadsPushBackThreshold = cThreadsPushBackThreshold;
+ pPool->cMsMaxPushBack = cMsMaxPushBack;
+ pPool->cMsMinPushBack = cMsMinPushBack;
+ pPool->cMaxFreeRequests = cMaxThreads * 2;
+ pPool->hThreadTermEvt = NIL_RTSEMEVENTMULTI;
+ pPool->fDestructing = false;
+ pPool->cMsCurPushBack = 0;
+ pPool->cCurThreads = 0;
+ pPool->cThreadsCreated = 0;
+ pPool->uLastThreadCreateNanoTs = 0;
+ RTListInit(&pPool->WorkerThreads);
+ pPool->cReqProcessed = 0;
+ pPool->cNsTotalReqProcessing= 0;
+ pPool->cNsTotalReqQueued = 0;
+ pPool->cRefs = 1;
+ pPool->cIdleThreads = 0;
+ RTListInit(&pPool->IdleThreads);
+ pPool->pPendingRequests = NULL;
+ pPool->ppPendingRequests = &pPool->pPendingRequests;
+ pPool->cCurPendingRequests = 0;
+ pPool->cCurActiveRequests = 0;
+ pPool->cReqSubmitted = 0;
+ pPool->cReqCancelled = 0;
+ pPool->pFreeRequests = NULL;
+ pPool->cCurFreeRequests = 0;
+
+ int rc = RTSemEventMultiCreate(&pPool->hThreadTermEvt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pPool->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ *phPool = pPool;
+ return VINF_SUCCESS;
+ }
+
+ RTSemEventMultiDestroy(pPool->hThreadTermEvt);
+ }
+ pPool->u32Magic = RTREQPOOL_MAGIC_DEAD;
+ RTMemFree(pPool);
+ return rc;
+}
+
+
+
+RTDECL(int) RTReqPoolSetCfgVar(RTREQPOOL hPool, RTREQPOOLCFGVAR enmVar, uint64_t uValue)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, VERR_INVALID_HANDLE);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(enmVar > RTREQPOOLCFGVAR_INVALID && enmVar < RTREQPOOLCFGVAR_END, VERR_INVALID_PARAMETER);
+
+ RTCritSectEnter(&pPool->CritSect);
+
+ bool fWakeUpIdleThreads = false;
+ int rc = VINF_SUCCESS;
+ switch (enmVar)
+ {
+ case RTREQPOOLCFGVAR_THREAD_TYPE:
+ AssertMsgBreakStmt(uValue > (uint64_t)RTTHREADTYPE_INVALID && uValue < (uint64_t)RTTHREADTYPE_END,
+ ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+
+ pPool->enmThreadType = (RTTHREADTYPE)uValue;
+ break;
+
+ case RTREQPOOLCFGVAR_THREAD_FLAGS:
+ AssertMsgBreakStmt(!(uValue & ~(uint64_t)RTTHREADFLAGS_MASK) && !(uValue & RTTHREADFLAGS_WAITABLE),
+ ("%#llx\n", uValue), rc = VERR_INVALID_FLAGS);
+
+ pPool->fThreadFlags = (uint32_t)uValue;
+ break;
+
+ case RTREQPOOLCFGVAR_MIN_THREADS:
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_MAX_THREADS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ fWakeUpIdleThreads = pPool->cMinThreads > (uint32_t)uValue;
+ pPool->cMinThreads = (uint32_t)uValue;
+ if (pPool->cMinThreads > pPool->cMaxThreads)
+ pPool->cMaxThreads = pPool->cMinThreads;
+ if ( pPool->cThreadsPushBackThreshold < pPool->cMinThreads
+ || pPool->cThreadsPushBackThreshold > pPool->cMaxThreads)
+ pPool->cThreadsPushBackThreshold = pPool->cMinThreads + (pPool->cMaxThreads - pPool->cMinThreads) / 2;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_THREADS:
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_MAX_THREADS && uValue >= 1, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMaxThreads = (uint32_t)uValue;
+ if (pPool->cMaxThreads < pPool->cMinThreads)
+ {
+ pPool->cMinThreads = pPool->cMaxThreads;
+ fWakeUpIdleThreads = true;
+ }
+ if (pPool->cMaxThreads < pPool->cThreadsPushBackThreshold)
+ pPool->cThreadsPushBackThreshold = pPool->cMinThreads + (pPool->cMaxThreads - pPool->cMinThreads) / 2;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_MS_MIN_IDLE:
+ AssertMsgBreakStmt(uValue < UINT32_MAX || uValue == RT_INDEFINITE_WAIT, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ if (uValue < UINT32_MAX && uValue != RT_INDEFINITE_WAIT)
+ {
+ fWakeUpIdleThreads = pPool->cMsMinIdle != (uint32_t)uValue;
+ pPool->cMsMinIdle = (uint32_t)uValue;
+ pPool->cNsMinIdle = pPool->cMsMinIdle * RT_NS_1MS_64;
+ if (pPool->cMsIdleSleep > pPool->cMsMinIdle)
+ pPool->cMsIdleSleep = RT_MAX(RT_MS_1SEC, pPool->cMsMinIdle);
+ }
+ else
+ {
+ pPool->cMsMinIdle = UINT32_MAX;
+ pPool->cNsMinIdle = UINT64_MAX;
+ pPool->cMsIdleSleep = RT_INDEFINITE_WAIT;
+ }
+ break;
+
+ case RTREQPOOLCFGVAR_MS_IDLE_SLEEP:
+ AssertMsgBreakStmt(uValue <= RT_INDEFINITE_WAIT, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ fWakeUpIdleThreads = pPool->cMsMinIdle > (RTMSINTERVAL)uValue;
+ pPool->cMsIdleSleep = (RTMSINTERVAL)uValue;
+ if (pPool->cMsIdleSleep == RT_INDEFINITE_WAIT)
+ {
+ pPool->cMsMinIdle = UINT32_MAX;
+ pPool->cNsMinIdle = UINT64_MAX;
+ }
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_THRESHOLD:
+ if (uValue == UINT64_MAX)
+ pPool->cThreadsPushBackThreshold = pPool->cMaxThreads;
+ else if (uValue == 0)
+ pPool->cThreadsPushBackThreshold = pPool->cMinThreads;
+ else
+ {
+ AssertMsgBreakStmt(uValue <= pPool->cMaxThreads, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ AssertMsgBreakStmt(uValue >= pPool->cMinThreads, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cThreadsPushBackThreshold = (uint32_t)uValue;
+ }
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MIN_MS:
+ if (uValue == UINT32_MAX || uValue == UINT64_MAX)
+ uValue = RTREQPOOL_PUSH_BACK_MAX_MS;
+ else
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_PUSH_BACK_MAX_MS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMsMinPushBack = (uint32_t)uValue;
+ if (pPool->cMsMaxPushBack < pPool->cMsMinPushBack)
+ pPool->cMsMaxPushBack = pPool->cMsMinPushBack;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MAX_MS:
+ if (uValue == UINT32_MAX || uValue == UINT64_MAX)
+ uValue = RTREQPOOL_PUSH_BACK_MAX_MS;
+ else
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_PUSH_BACK_MAX_MS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMsMaxPushBack = (uint32_t)uValue;
+ if (pPool->cMsMinPushBack < pPool->cMsMaxPushBack)
+ pPool->cMsMinPushBack = pPool->cMsMaxPushBack;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_FREE_REQUESTS:
+ if (uValue == UINT64_MAX)
+ {
+ pPool->cMaxFreeRequests = pPool->cMaxThreads * 2;
+ if (pPool->cMaxFreeRequests < 16)
+ pPool->cMaxFreeRequests = 16;
+ }
+ else
+ {
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_MAX_FREE_REQUESTS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMaxFreeRequests = (uint32_t)uValue;
+ }
+
+ while (pPool->cCurFreeRequests > pPool->cMaxFreeRequests)
+ {
+ PRTREQINT pReq = pPool->pFreeRequests;
+ pPool->pFreeRequests = pReq->pNext;
+ ASMAtomicDecU32(&pPool->cCurFreeRequests);
+ rtReqFreeIt(pReq);
+ }
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_IPE_NOT_REACHED_DEFAULT_CASE;
+ }
+
+ /* Wake up all idle threads if required. */
+ if (fWakeUpIdleThreads)
+ {
+ Assert(rc == VINF_SUCCESS);
+ PRTREQPOOLTHREAD pThread;
+ RTListForEach(&pPool->WorkerThreads, pThread, RTREQPOOLTHREAD, ListNode)
+ {
+ RTThreadUserSignal(pThread->hThread);
+ }
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolSetCfgVar);
+
+
+RTDECL(uint64_t) RTReqPoolGetCfgVar(RTREQPOOL hPool, RTREQPOOLCFGVAR enmVar)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT64_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT64_MAX);
+ AssertReturn(enmVar > RTREQPOOLCFGVAR_INVALID && enmVar < RTREQPOOLCFGVAR_END, UINT64_MAX);
+
+ RTCritSectEnter(&pPool->CritSect);
+
+ uint64_t u64;
+ switch (enmVar)
+ {
+ case RTREQPOOLCFGVAR_THREAD_TYPE:
+ u64 = pPool->enmThreadType;
+ break;
+
+ case RTREQPOOLCFGVAR_THREAD_FLAGS:
+ u64 = pPool->fThreadFlags;
+ break;
+
+ case RTREQPOOLCFGVAR_MIN_THREADS:
+ u64 = pPool->cMinThreads;
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_THREADS:
+ u64 = pPool->cMaxThreads;
+ break;
+
+ case RTREQPOOLCFGVAR_MS_MIN_IDLE:
+ u64 = pPool->cMsMinIdle;
+ break;
+
+ case RTREQPOOLCFGVAR_MS_IDLE_SLEEP:
+ u64 = pPool->cMsIdleSleep;
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_THRESHOLD:
+ u64 = pPool->cThreadsPushBackThreshold;
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MIN_MS:
+ u64 = pPool->cMsMinPushBack;
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MAX_MS:
+ u64 = pPool->cMsMaxPushBack;
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_FREE_REQUESTS:
+ u64 = pPool->cMaxFreeRequests;
+ break;
+
+ default:
+ AssertFailed();
+ u64 = UINT64_MAX;
+ break;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ return u64;
+}
+RT_EXPORT_SYMBOL(RTReqGetQueryCfgVar);
+
+
+RTDECL(uint64_t) RTReqPoolGetStat(RTREQPOOL hPool, RTREQPOOLSTAT enmStat)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT64_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT64_MAX);
+ AssertReturn(enmStat > RTREQPOOLSTAT_INVALID && enmStat < RTREQPOOLSTAT_END, UINT64_MAX);
+
+ RTCritSectEnter(&pPool->CritSect);
+
+ uint64_t u64;
+ switch (enmStat)
+ {
+ case RTREQPOOLSTAT_THREADS: u64 = pPool->cCurThreads; break;
+ case RTREQPOOLSTAT_THREADS_CREATED: u64 = pPool->cThreadsCreated; break;
+ case RTREQPOOLSTAT_REQUESTS_PROCESSED: u64 = pPool->cReqProcessed; break;
+ case RTREQPOOLSTAT_REQUESTS_SUBMITTED: u64 = pPool->cReqSubmitted; break;
+ case RTREQPOOLSTAT_REQUESTS_CANCELLED: u64 = pPool->cReqCancelled; break;
+ case RTREQPOOLSTAT_REQUESTS_PENDING: u64 = pPool->cCurPendingRequests; break;
+ case RTREQPOOLSTAT_REQUESTS_ACTIVE: u64 = pPool->cCurActiveRequests; break;
+ case RTREQPOOLSTAT_REQUESTS_FREE: u64 = pPool->cCurFreeRequests; break;
+ case RTREQPOOLSTAT_NS_TOTAL_REQ_PROCESSING: u64 = pPool->cNsTotalReqProcessing; break;
+ case RTREQPOOLSTAT_NS_TOTAL_REQ_QUEUED: u64 = pPool->cNsTotalReqQueued; break;
+ case RTREQPOOLSTAT_NS_AVERAGE_REQ_PROCESSING: u64 = pPool->cNsTotalReqProcessing / RT_MAX(pPool->cReqProcessed, 1); break;
+ case RTREQPOOLSTAT_NS_AVERAGE_REQ_QUEUED: u64 = pPool->cNsTotalReqQueued / RT_MAX(pPool->cReqProcessed, 1); break;
+ default:
+ AssertFailed();
+ u64 = UINT64_MAX;
+ break;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ return u64;
+}
+RT_EXPORT_SYMBOL(RTReqPoolGetStat);
+
+
+RTDECL(uint32_t) RTReqPoolRetain(RTREQPOOL hPool)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT32_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT32_MAX);
+
+ return ASMAtomicIncU32(&pPool->cRefs);
+}
+RT_EXPORT_SYMBOL(RTReqPoolRetain);
+
+
+RTDECL(uint32_t) RTReqPoolRelease(RTREQPOOL hPool)
+{
+ /*
+ * Ignore NULL and validate the request.
+ */
+ if (!hPool)
+ return 0;
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT32_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT32_MAX);
+
+ /*
+ * Drop a reference, free it when it reaches zero.
+ */
+ uint32_t cRefs = ASMAtomicDecU32(&pPool->cRefs);
+ if (cRefs == 0)
+ {
+ AssertReturn(ASMAtomicCmpXchgU32(&pPool->u32Magic, RTREQPOOL_MAGIC_DEAD, RTREQPOOL_MAGIC), UINT32_MAX);
+
+ RTCritSectEnter(&pPool->CritSect);
+#ifdef RT_STRICT
+ RTTHREAD const hSelf = RTThreadSelf();
+#endif
+
+ /* Indicate to the worker threads that we're shutting down. */
+ ASMAtomicWriteBool(&pPool->fDestructing, true);
+ PRTREQPOOLTHREAD pThread;
+ RTListForEach(&pPool->WorkerThreads, pThread, RTREQPOOLTHREAD, ListNode)
+ {
+ Assert(pThread->hThread != hSelf);
+ RTThreadUserSignal(pThread->hThread);
+ }
+
+ /* Cancel pending requests. */
+ Assert(!pPool->pPendingRequests);
+ while (pPool->pPendingRequests)
+ {
+ PRTREQINT pReq = pPool->pPendingRequests;
+ pPool->pPendingRequests = pReq->pNext;
+ rtReqPoolCancelReq(pReq);
+ }
+ pPool->ppPendingRequests = NULL;
+ pPool->cCurPendingRequests = 0;
+
+ /* Wait for the workers to shut down. */
+ while (!RTListIsEmpty(&pPool->WorkerThreads))
+ {
+ RTCritSectLeave(&pPool->CritSect);
+ RTSemEventMultiWait(pPool->hThreadTermEvt, RT_MS_1MIN);
+ RTCritSectEnter(&pPool->CritSect);
+ /** @todo should we wait forever here? */
+ }
+
+ /* Free recycled requests. */
+ for (;;)
+ {
+ PRTREQINT pReq = pPool->pFreeRequests;
+ if (!pReq)
+ break;
+ pPool->pFreeRequests = pReq->pNext;
+ pPool->cCurFreeRequests--;
+ rtReqFreeIt(pReq);
+ }
+
+ /* Finally, free the critical section and pool instance. */
+ RTSemEventMultiDestroy(pPool->hThreadTermEvt);
+ RTCritSectLeave(&pPool->CritSect);
+ RTCritSectDelete(&pPool->CritSect);
+ RTMemFree(pPool);
+ }
+
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTReqPoolRelease);
+
+
+RTDECL(int) RTReqPoolAlloc(RTREQPOOL hPool, RTREQTYPE enmType, PRTREQ *phReq)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, VERR_INVALID_HANDLE);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Try recycle old requests.
+ */
+ if (ASMAtomicReadU32(&pPool->cCurFreeRequests) > 0)
+ {
+ RTCritSectEnter(&pPool->CritSect);
+ PRTREQINT pReq = pPool->pFreeRequests;
+ if (pReq)
+ {
+ ASMAtomicDecU32(&pPool->cCurFreeRequests);
+ pPool->pFreeRequests = pReq->pNext;
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ Assert(pReq->fPoolOrQueue);
+ Assert(pReq->uOwner.hPool == pPool);
+
+ int rc = rtReqReInit(pReq, enmType);
+ if (RT_SUCCESS(rc))
+ {
+ *phReq = pReq;
+ LogFlow(("RTReqPoolAlloc: returns VINF_SUCCESS *phReq=%p recycled\n", pReq));
+ return rc;
+ }
+ }
+ else
+ RTCritSectLeave(&pPool->CritSect);
+ }
+
+ /*
+ * Allocate a new request.
+ */
+ int rc = rtReqAlloc(enmType, true /*fPoolOrQueue*/, pPool, phReq);
+ LogFlow(("RTReqPoolAlloc: returns %Rrc *phReq=%p\n", rc, *phReq));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolAlloc);
+
+
+RTDECL(int) RTReqPoolCallEx( RTREQPOOL hPool, RTMSINTERVAL cMillies, PRTREQ *phReq, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, cMillies, phReq, fFlags, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallEx);
+
+
+RTDECL(int) RTReqPoolCallExV(RTREQPOOL hPool, RTMSINTERVAL cMillies, PRTREQ *phReq, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list va)
+{
+ /*
+ * Check input.
+ */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+ AssertMsgReturn(!((uint32_t)fFlags & ~(uint32_t)(RTREQFLAGS_NO_WAIT | RTREQFLAGS_RETURN_MASK)), ("%#x\n", (uint32_t)fFlags), VERR_INVALID_PARAMETER);
+ if (!(fFlags & RTREQFLAGS_NO_WAIT) || phReq)
+ {
+ AssertPtrReturn(phReq, VERR_INVALID_POINTER);
+ *phReq = NIL_RTREQ;
+ }
+
+ PRTREQINT pReq = NULL;
+ AssertMsgReturn(cArgs * sizeof(uintptr_t) <= sizeof(pReq->u.Internal.aArgs), ("cArgs=%u\n", cArgs), VERR_TOO_MUCH_DATA);
+
+ /*
+ * Allocate and initialize the request.
+ */
+ int rc = RTReqPoolAlloc(hPool, RTREQTYPE_INTERNAL, &pReq);
+ if (RT_FAILURE(rc))
+ return rc;
+ pReq->fFlags = fFlags;
+ pReq->u.Internal.pfn = pfnFunction;
+ pReq->u.Internal.cArgs = cArgs;
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ pReq->u.Internal.aArgs[iArg] = va_arg(va, uintptr_t);
+
+ /*
+ * Submit the request.
+ */
+ rc = RTReqSubmit(pReq, cMillies);
+ if ( rc != VINF_SUCCESS
+ && rc != VERR_TIMEOUT)
+ {
+ Assert(rc != VERR_INTERRUPTED);
+ RTReqRelease(pReq);
+ pReq = NULL;
+ }
+
+ if (phReq)
+ {
+ *phReq = pReq;
+ LogFlow(("RTReqPoolCallExV: returns %Rrc *phReq=%p\n", rc, pReq));
+ }
+ else
+ {
+ RTReqRelease(pReq);
+ LogFlow(("RTReqPoolCallExV: returns %Rrc\n", rc));
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallExV);
+
+
+RTDECL(int) RTReqPoolCallWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ PRTREQINT pReq;
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, RT_INDEFINITE_WAIT, &pReq, RTREQFLAGS_IPRT_STATUS,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ rc = pReq->iStatusX;
+ RTReqRelease(pReq);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallWait);
+
+
+RTDECL(int) RTReqPoolCallNoWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, 0, NULL, RTREQFLAGS_IPRT_STATUS | RTREQFLAGS_NO_WAIT,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallNoWait);
+
+
+RTDECL(int) RTReqPoolCallVoidWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ PRTREQINT pReq;
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, RT_INDEFINITE_WAIT, &pReq, RTREQFLAGS_VOID,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ rc = pReq->iStatusX;
+ RTReqRelease(pReq);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallVoidWait);
+
+
+RTDECL(int) RTReqPoolCallVoidNoWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, 0, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallVoidNoWait);
+
diff --git a/src/VBox/Runtime/common/misc/reqqueue.cpp b/src/VBox/Runtime/common/misc/reqqueue.cpp
new file mode 100644
index 00000000..c0c89e1a
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/reqqueue.cpp
@@ -0,0 +1,465 @@
+/* $Id: reqqueue.cpp $ */
+/** @file
+ * IPRT - Request Queue.
+ */
+
+/*
+ * 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/req.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+
+#include "internal/req.h"
+#include "internal/magics.h"
+
+
+
+RTDECL(int) RTReqQueueCreate(RTREQQUEUE *phQueue)
+{
+ PRTREQQUEUEINT pQueue = (PRTREQQUEUEINT)RTMemAllocZ(sizeof(RTREQQUEUEINT));
+ if (!pQueue)
+ return VERR_NO_MEMORY;
+ int rc = RTSemEventCreate(&pQueue->EventSem);
+ if (RT_SUCCESS(rc))
+ {
+ pQueue->u32Magic = RTREQQUEUE_MAGIC;
+
+ *phQueue = pQueue;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pQueue);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCreate);
+
+
+RTDECL(int) RTReqQueueDestroy(RTREQQUEUE hQueue)
+{
+ /*
+ * Check input.
+ */
+ if (hQueue == NIL_RTREQQUEUE)
+ return VINF_SUCCESS;
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(ASMAtomicCmpXchgU32(&pQueue->u32Magic, RTREQQUEUE_MAGIC_DEAD, RTREQQUEUE_MAGIC), VERR_INVALID_HANDLE);
+
+ RTSemEventDestroy(pQueue->EventSem);
+ pQueue->EventSem = NIL_RTSEMEVENT;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pQueue->apReqFree); i++)
+ {
+ PRTREQ pReq = (PRTREQ)ASMAtomicXchgPtr((void **)&pQueue->apReqFree[i], NULL);
+ while (pReq)
+ {
+ PRTREQ pNext = pReq->pNext;
+ rtReqFreeIt(pReq);
+ pReq = pNext;
+ }
+ }
+
+ RTMemFree(pQueue);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTReqQueueDestroy);
+
+
+RTDECL(int) RTReqQueueProcess(RTREQQUEUE hQueue, RTMSINTERVAL cMillies)
+{
+ LogFlow(("RTReqQueueProcess %x\n", hQueue));
+
+ /*
+ * Check input.
+ */
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(pQueue->u32Magic == RTREQQUEUE_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Process loop. Stop (break) after the first non-VINF_SUCCESS status code.
+ */
+ int rc = VINF_SUCCESS;
+ for (;;)
+ {
+ /*
+ * Get pending requests.
+ */
+ PRTREQ pReqs = ASMAtomicXchgPtrT(&pQueue->pAlreadyPendingReqs, NULL, PRTREQ);
+ if (RT_LIKELY(!pReqs))
+ {
+ pReqs = ASMAtomicXchgPtrT(&pQueue->pReqs, NULL, PRTREQ);
+ if (!pReqs)
+ {
+ /* We do not adjust cMillies (documented behavior). */
+ ASMAtomicWriteBool(&pQueue->fBusy, false); /* this aint 100% perfect, but it's good enough for now... */
+ rc = RTSemEventWait(pQueue->EventSem, cMillies);
+ if (rc != VINF_SUCCESS)
+ break;
+ continue;
+ }
+
+ ASMAtomicWriteBool(&pQueue->fBusy, true);
+
+ /*
+ * Reverse the list to process it in FIFO order.
+ */
+ PRTREQ pReq = pReqs;
+ if (pReq->pNext)
+ Log2(("RTReqQueueProcess: 2+ requests: %p %p %p\n", pReq, pReq->pNext, pReq->pNext->pNext));
+ pReqs = NULL;
+ while (pReq)
+ {
+ Assert(pReq->enmState == RTREQSTATE_QUEUED);
+ Assert(pReq->uOwner.hQueue == pQueue);
+ PRTREQ pCur = pReq;
+ pReq = pReq->pNext;
+ pCur->pNext = pReqs;
+ pReqs = pCur;
+ }
+
+ }
+ else
+ ASMAtomicWriteBool(&pQueue->fBusy, true);
+
+ /*
+ * Process the requests.
+ */
+ while (pReqs)
+ {
+ /* Unchain the first request and advance the list. */
+ PRTREQ pReq = pReqs;
+ pReqs = pReqs->pNext;
+ pReq->pNext = NULL;
+
+ /* Process the request. */
+ rc = rtReqProcessOne(pReq);
+ if (rc != VINF_SUCCESS)
+ {
+ /* Propagate the return code to caller. If more requests pending, queue them for later. */
+ if (pReqs)
+ {
+ pReqs = ASMAtomicXchgPtrT(&pQueue->pAlreadyPendingReqs, pReqs, PRTREQ);
+ Assert(!pReqs);
+ }
+ break;
+ }
+ }
+ if (rc != VINF_SUCCESS)
+ break;
+ }
+
+ LogFlow(("RTReqQueueProcess: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueProcess);
+
+
+RTDECL(int) RTReqQueueCall(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqQueueCallV(hQueue, ppReq, cMillies, RTREQFLAGS_IPRT_STATUS, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCall);
+
+
+RTDECL(int) RTReqQueueCallVoid(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqQueueCallV(hQueue, ppReq, cMillies, RTREQFLAGS_VOID, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCallVoid);
+
+
+RTDECL(int) RTReqQueueCallEx(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, unsigned fFlags, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqQueueCallV(hQueue, ppReq, cMillies, fFlags, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCallEx);
+
+
+RTDECL(int) RTReqQueueCallV(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, unsigned fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args)
+{
+ LogFlow(("RTReqQueueCallV: cMillies=%d fFlags=%#x pfnFunction=%p cArgs=%d\n", cMillies, fFlags, pfnFunction, cArgs));
+
+ /*
+ * Check input.
+ */
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(pQueue->u32Magic == RTREQQUEUE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~(RTREQFLAGS_RETURN_MASK | RTREQFLAGS_NO_WAIT)), VERR_INVALID_PARAMETER);
+
+ if (!(fFlags & RTREQFLAGS_NO_WAIT) || ppReq)
+ {
+ AssertPtrReturn(ppReq, VERR_INVALID_POINTER);
+ *ppReq = NIL_RTREQ;
+ }
+
+ PRTREQ pReq = NULL;
+ AssertMsgReturn(cArgs * sizeof(uintptr_t) <= sizeof(pReq->u.Internal.aArgs), ("cArgs=%u\n", cArgs), VERR_TOO_MUCH_DATA);
+
+ /*
+ * Allocate request
+ */
+ int rc = RTReqQueueAlloc(pQueue, RTREQTYPE_INTERNAL, &pReq);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ /*
+ * Initialize the request data.
+ */
+ pReq->fFlags = fFlags;
+ pReq->u.Internal.pfn = pfnFunction;
+ pReq->u.Internal.cArgs = cArgs;
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ pReq->u.Internal.aArgs[iArg] = va_arg(Args, uintptr_t);
+
+ /*
+ * Queue the request and return.
+ */
+ rc = RTReqSubmit(pReq, cMillies);
+ if ( rc != VINF_SUCCESS
+ && rc != VERR_TIMEOUT)
+ {
+ RTReqRelease(pReq);
+ pReq = NULL;
+ }
+ if (ppReq)
+ {
+ *ppReq = pReq;
+ LogFlow(("RTReqQueueCallV: returns %Rrc *ppReq=%p\n", rc, pReq));
+ }
+ else
+ {
+ RTReqRelease(pReq);
+ LogFlow(("RTReqQueueCallV: returns %Rrc\n", rc));
+ }
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCallV);
+
+
+RTDECL(bool) RTReqQueueIsBusy(RTREQQUEUE hQueue)
+{
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, false);
+
+ if (ASMAtomicReadBool(&pQueue->fBusy))
+ return true;
+ if (ASMAtomicReadPtrT(&pQueue->pReqs, PRTREQ) != NULL)
+ return true;
+ if (ASMAtomicReadBool(&pQueue->fBusy))
+ return true;
+ return false;
+}
+RT_EXPORT_SYMBOL(RTReqQueueIsBusy);
+
+
+/**
+ * Joins the list pList with whatever is linked up at *pHead.
+ */
+static void vmr3ReqJoinFreeSub(volatile PRTREQ *ppHead, PRTREQ pList)
+{
+ for (unsigned cIterations = 0;; cIterations++)
+ {
+ PRTREQ pHead = ASMAtomicXchgPtrT(ppHead, pList, PRTREQ);
+ if (!pHead)
+ return;
+ PRTREQ pTail = pHead;
+ while (pTail->pNext)
+ pTail = pTail->pNext;
+ pTail->pNext = pList;
+ if (ASMAtomicCmpXchgPtr(ppHead, pHead, pList))
+ return;
+ pTail->pNext = NULL;
+ if (ASMAtomicCmpXchgPtr(ppHead, pHead, NULL))
+ return;
+ pList = pHead;
+ Assert(cIterations != 32);
+ Assert(cIterations != 64);
+ }
+}
+
+
+/**
+ * Joins the list pList with whatever is linked up at *pHead.
+ */
+static void vmr3ReqJoinFree(PRTREQQUEUEINT pQueue, PRTREQ pList)
+{
+ /*
+ * Split the list if it's too long.
+ */
+ unsigned cReqs = 1;
+ PRTREQ pTail = pList;
+ while (pTail->pNext)
+ {
+ if (cReqs++ > 25)
+ {
+ const uint32_t i = pQueue->iReqFree;
+ vmr3ReqJoinFreeSub(&pQueue->apReqFree[(i + 2) % RT_ELEMENTS(pQueue->apReqFree)], pTail->pNext);
+
+ pTail->pNext = NULL;
+ vmr3ReqJoinFreeSub(&pQueue->apReqFree[(i + 2 + (i == pQueue->iReqFree)) % RT_ELEMENTS(pQueue->apReqFree)], pTail->pNext);
+ return;
+ }
+ pTail = pTail->pNext;
+ }
+ vmr3ReqJoinFreeSub(&pQueue->apReqFree[(pQueue->iReqFree + 2) % RT_ELEMENTS(pQueue->apReqFree)], pList);
+}
+
+
+RTDECL(int) RTReqQueueAlloc(RTREQQUEUE hQueue, RTREQTYPE enmType, PRTREQ *phReq)
+{
+ /*
+ * Validate input.
+ */
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(pQueue->u32Magic == RTREQQUEUE_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn(enmType > RTREQTYPE_INVALID && enmType < RTREQTYPE_MAX, ("%d\n", enmType), VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Try get a recycled packet.
+ *
+ * While this could all be solved with a single list with a lock, it's a sport
+ * of mine to avoid locks.
+ */
+ int cTries = RT_ELEMENTS(pQueue->apReqFree) * 2;
+ while (--cTries >= 0)
+ {
+ PRTREQ volatile *ppHead = &pQueue->apReqFree[ASMAtomicIncU32(&pQueue->iReqFree) % RT_ELEMENTS(pQueue->apReqFree)];
+ PRTREQ pReq = ASMAtomicXchgPtrT(ppHead, NULL, PRTREQ);
+ if (pReq)
+ {
+ PRTREQ pNext = pReq->pNext;
+ if ( pNext
+ && !ASMAtomicCmpXchgPtr(ppHead, pNext, NULL))
+ vmr3ReqJoinFree(pQueue, pReq->pNext);
+ ASMAtomicDecU32(&pQueue->cReqFree);
+
+ Assert(pReq->uOwner.hQueue == pQueue);
+ Assert(!pReq->fPoolOrQueue);
+
+ int rc = rtReqReInit(pReq, enmType);
+ if (RT_SUCCESS(rc))
+ {
+ *phReq = pReq;
+ LogFlow(("RTReqQueueAlloc: returns VINF_SUCCESS *phReq=%p recycled\n", pReq));
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ /*
+ * Ok, allocate a new one.
+ */
+ int rc = rtReqAlloc(enmType, false /*fPoolOrQueue*/, pQueue, phReq);
+ LogFlow(("RTReqQueueAlloc: returns %Rrc *phReq=%p\n", rc, *phReq));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueAlloc);
+
+
+/**
+ * Recycles a requst.
+ *
+ * @returns true if recycled, false if it should be freed.
+ * @param pQueue The queue.
+ * @param pReq The request.
+ */
+DECLHIDDEN(bool) rtReqQueueRecycle(PRTREQQUEUEINT pQueue, PRTREQINT pReq)
+{
+ if ( !pQueue
+ || pQueue->cReqFree >= 128)
+ return false;
+
+ ASMAtomicIncU32(&pQueue->cReqFree);
+ PRTREQ volatile *ppHead = &pQueue->apReqFree[ASMAtomicIncU32(&pQueue->iReqFree) % RT_ELEMENTS(pQueue->apReqFree)];
+ PRTREQ pNext;
+ do
+ {
+ pNext = *ppHead;
+ ASMAtomicWritePtr(&pReq->pNext, pNext);
+ } while (!ASMAtomicCmpXchgPtr(ppHead, pReq, pNext));
+
+ return true;
+}
+
+
+/**
+ * Submits a request to the queue.
+ *
+ * @param pQueue The queue.
+ * @param pReq The request.
+ */
+DECLHIDDEN(void) rtReqQueueSubmit(PRTREQQUEUEINT pQueue, PRTREQINT pReq)
+{
+ PRTREQ pNext;
+ do
+ {
+ pNext = pQueue->pReqs;
+ pReq->pNext = pNext;
+ ASMAtomicWriteBool(&pQueue->fBusy, true);
+ } while (!ASMAtomicCmpXchgPtr(&pQueue->pReqs, pReq, pNext));
+
+ /*
+ * Notify queue thread.
+ */
+ RTSemEventSignal(pQueue->EventSem);
+}
+
diff --git a/src/VBox/Runtime/common/misc/sanity-c.c b/src/VBox/Runtime/common/misc/sanity-c.c
new file mode 100644
index 00000000..4df2a59e
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sanity-c.c
@@ -0,0 +1,37 @@
+/* $Id: sanity-c.c $ */
+/** @file
+ * IPRT - Setup Sanity Checks, C.
+ */
+
+/*
+ * 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
+ */
+
+#include "sanity.h"
diff --git a/src/VBox/Runtime/common/misc/sanity-cpp.cpp b/src/VBox/Runtime/common/misc/sanity-cpp.cpp
new file mode 100644
index 00000000..57ec37f1
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sanity-cpp.cpp
@@ -0,0 +1,38 @@
+/* $Id: sanity-cpp.cpp $ */
+/** @file
+ * IPRT - Setup Sanity Checks, C++.
+ */
+
+/*
+ * 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
+ */
+
+#include "sanity.h"
+
diff --git a/src/VBox/Runtime/common/misc/sanity.h b/src/VBox/Runtime/common/misc/sanity.h
new file mode 100644
index 00000000..7117adfd
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sanity.h
@@ -0,0 +1,225 @@
+/* $Id: sanity.h $ */
+/** @file
+ * IPRT - Setup Sanity Checks, C and C++.
+ */
+
+/*
+ * 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
+ */
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/assert.h>
+
+/*
+ * Check that the IN_[RING3|RING0|GC] and [|R3_|R0_|GC_]ARCH_BITS
+ * match up correctly.
+ *
+ * IPRT assumes r0 and r3 to has the same bit count.
+ */
+
+#if defined(IN_RING3) && ARCH_BITS != R3_ARCH_BITS
+# error "defined(IN_RING3) && ARCH_BITS != R3_ARCH_BITS"
+#endif
+#if defined(IN_RING0) && ARCH_BITS != R0_ARCH_BITS
+# error "defined(IN_RING0) && ARCH_BITS != R0_ARCH_BITS"
+#endif
+#if defined(IN_RC) && ARCH_BITS != 32
+# error "defined(IN_RC) && ARCH_BITS != 32"
+#endif
+#if (defined(IN_RING0) || defined(IN_RING3)) && HC_ARCH_BITS != ARCH_BITS
+# error "(defined(IN_RING0) || defined(IN_RING3)) && HC_ARCH_BITS != ARCH_BITS"
+#endif
+#if defined(IN_RC) && GC_ARCH_BITS != 64 && GC_ARCH_BITS != ARCH_BITS
+# error "defined(IN_RC) && GC_ARCH_BITS != ARCH_BITS"
+#endif
+
+
+/*
+ * Check basic host (hc/r0/r3) types.
+ */
+#if HC_ARCH_BITS == 64
+
+AssertCompileSize(RTHCPTR, 8);
+AssertCompileSize(RTHCINT, 4);
+AssertCompileSize(RTHCUINT, 4);
+AssertCompileSize(RTHCINTPTR, 8);
+AssertCompileSize(RTHCUINTPTR, 8);
+/*AssertCompileSize(RTHCINTREG, 8);*/
+AssertCompileSize(RTHCUINTREG, 8);
+AssertCompileSize(RTR0PTR, 8);
+/*AssertCompileSize(RTR0INT, 4);*/
+/*AssertCompileSize(RTR0UINT, 4);*/
+AssertCompileSize(RTR0INTPTR, 8);
+AssertCompileSize(RTR0UINTPTR, 8);
+/*AssertCompileSize(RTR3PTR, 8);*/
+/*AssertCompileSize(RTR3INT, 4);*/
+/*AssertCompileSize(RTR3UINT, 4);*/
+AssertCompileSize(RTR3INTPTR, 8);
+AssertCompileSize(RTR3UINTPTR, 8);
+AssertCompileSize(RTUINTPTR, 8);
+
+# if defined(IN_RING3) || defined(IN_RING0)
+/*AssertCompileSize(RTCCINTREG, 8);*/
+AssertCompileSize(RTCCUINTREG, 8);
+# endif
+
+#else
+
+AssertCompileSize(RTHCPTR, 4);
+AssertCompileSize(RTHCINT, 4);
+AssertCompileSize(RTHCUINT, 4);
+/*AssertCompileSize(RTHCINTPTR, 4);*/
+AssertCompileSize(RTHCUINTPTR, 4);
+AssertCompileSize(RTR0PTR, 4);
+/*AssertCompileSize(RTR0INT, 4);*/
+/*AssertCompileSize(RTR0UINT, 4);*/
+AssertCompileSize(RTR0INTPTR, 4);
+AssertCompileSize(RTR0UINTPTR, 4);
+/*AssertCompileSize(RTR3PTR, 4);*/
+/*AssertCompileSize(RTR3INT, 4);*/
+/*AssertCompileSize(RTR3UINT, 4);*/
+AssertCompileSize(RTR3INTPTR, 4);
+AssertCompileSize(RTR3UINTPTR, 4);
+# if GC_ARCH_BITS == 64
+AssertCompileSize(RTUINTPTR, 8);
+# else
+AssertCompileSize(RTUINTPTR, 4);
+# endif
+
+# if defined(IN_RING3) || defined(IN_RING0)
+/*AssertCompileSize(RTCCINTREG, 4);*/
+AssertCompileSize(RTCCUINTREG, 4);
+# endif
+
+#endif
+
+AssertCompileSize(RTHCPHYS, 8);
+
+
+/*
+ * Check basic guest context types.
+ */
+#if GC_ARCH_BITS == 64
+
+AssertCompileSize(RTGCINT, 8);
+AssertCompileSize(RTGCUINT, 8);
+AssertCompileSize(RTGCINTPTR, 8);
+AssertCompileSize(RTGCUINTPTR, 8);
+/*AssertCompileSize(RTGCINTREG, 8);*/
+AssertCompileSize(RTGCUINTREG, 8);
+
+# ifdef IN_RC
+/*AssertCompileSize(RTCCINTREG, 8);*/
+/* Hack alert: there is no such thing as a GC context when GC_ARCH_BITS == 64; it's still 32 bits */
+AssertCompileSize(RTCCUINTREG, 4);
+# endif
+
+#else
+
+AssertCompileSize(RTGCINT, 4);
+AssertCompileSize(RTGCUINT, 4);
+AssertCompileSize(RTGCINTPTR, 4);
+AssertCompileSize(RTGCUINTPTR, 4);
+/*AssertCompileSize(RTGCINTREG, 4);*/
+AssertCompileSize(RTGCUINTREG, 4);
+
+# ifdef IN_RC
+/*AssertCompileSize(RTCCINTREG, 4);*/
+AssertCompileSize(RTCCUINTREG, 4);
+# endif
+
+#endif
+
+AssertCompileSize(RTGCPHYS64, 8);
+AssertCompileSize(RTGCPHYS32, 4);
+AssertCompileSize(RTGCPHYS, 8);
+
+
+/*
+ * Check basic current context types.
+ */
+#if ARCH_BITS == 64
+
+AssertCompileSize(void *, 8);
+AssertCompileSize(intptr_t, 8);
+AssertCompileSize(uintptr_t, 8);
+AssertCompileSize(size_t, 8);
+AssertCompileSize(ssize_t, 8);
+
+#else
+
+AssertCompileSize(void *, 4);
+AssertCompileSize(intptr_t, 4);
+AssertCompileSize(uintptr_t, 4);
+AssertCompileSize(size_t, 4);
+AssertCompileSize(ssize_t, 4);
+
+#endif
+
+
+/*
+ * Standard sized types.
+ */
+AssertCompileSize(uint8_t, 1);
+AssertCompileSize(uint16_t, 2);
+AssertCompileSize(uint32_t, 4);
+AssertCompileSize(uint64_t, 8);
+
+#define TEST_CONST_MACRO(c,t) \
+ AssertCompile(sizeof(c) == sizeof(t) || (sizeof(c) == sizeof(int) && sizeof(t) < sizeof(int)) )
+
+TEST_CONST_MACRO(UINT8_C(1), uint8_t);
+TEST_CONST_MACRO(UINT16_C(1), uint16_t);
+TEST_CONST_MACRO(UINT32_C(1), uint32_t);
+TEST_CONST_MACRO(UINT64_C(1), uint64_t);
+
+TEST_CONST_MACRO(INT8_C(1), int8_t);
+TEST_CONST_MACRO(INT8_C(-1), int8_t);
+TEST_CONST_MACRO(INT16_C(1), int16_t);
+TEST_CONST_MACRO(INT16_C(-1), int16_t);
+TEST_CONST_MACRO(INT32_C(1), int32_t);
+TEST_CONST_MACRO(INT32_C(-1), int32_t);
+TEST_CONST_MACRO(INT64_C(1), int64_t);
+TEST_CONST_MACRO(INT64_C(-1), int64_t);
+
+
+/*
+ * Our union types.
+ */
+AssertCompileSize(RTUINT16U, 2);
+AssertCompileSize(RTUINT32U, 4);
+AssertCompileSize(RTUINT64U, 8);
+AssertCompileSize(RTUINT128U, 16);
+/*AssertCompileSize(RTFLOAT32U, 8);*/
+AssertCompileSize(RTFLOAT64U, 8);
+AssertCompileSize(RTFLOAT80U, 10);
+/*AssertCompileSize(RTFLOAT128U, 16);*/
+
diff --git a/src/VBox/Runtime/common/misc/semspingpong.cpp b/src/VBox/Runtime/common/misc/semspingpong.cpp
new file mode 100644
index 00000000..59a27aa8
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/semspingpong.cpp
@@ -0,0 +1,218 @@
+/* $Id: semspingpong.cpp $ */
+/** @file
+ * IPRT - Thread Ping-Pong Construct.
+ */
+
+/*
+ * 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/thread.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/**
+ * Validation macro returns if invalid parameter.
+ *
+ * Expects a enmSpeaker variable to be handy and will set it to the current
+ * enmSpeaker value.
+ */
+#define RTSEMPP_VALIDATE_RETURN(pPP) \
+ do { \
+ AssertPtrReturn(pPP, VERR_INVALID_PARAMETER); \
+ AssertCompileSize(pPP->enmSpeaker, 4); \
+ enmSpeaker = (RTPINGPONGSPEAKER)ASMAtomicUoReadU32((volatile uint32_t *)&pPP->enmSpeaker); \
+ AssertMsgReturn( enmSpeaker == RTPINGPONGSPEAKER_PING \
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG \
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG_SIGNALED \
+ || enmSpeaker == RTPINGPONGSPEAKER_PING_SIGNALED, \
+ ("enmSpeaker=%d\n", enmSpeaker), \
+ VERR_INVALID_PARAMETER); \
+ } while (0)
+
+
+RTDECL(int) RTSemPingPongInit(PRTPINGPONG pPP)
+{
+ /*
+ * Init the structure.
+ */
+ pPP->enmSpeaker = RTPINGPONGSPEAKER_PING;
+
+ int rc = RTSemEventCreate(&pPP->Ping);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventCreate(&pPP->Pong);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ RTSemEventDestroy(pPP->Ping);
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPingPongInit);
+
+
+RTDECL(int) RTSemPingPongDelete(PRTPINGPONG pPP)
+{
+ /*
+ * Validate input
+ */
+ if (!pPP)
+ return VINF_SUCCESS;
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+
+ /*
+ * Invalidate the ping pong handle and destroy the event semaphores.
+ */
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_UNINITIALIZE);
+ int rc = RTSemEventDestroy(pPP->Ping);
+ int rc2 = RTSemEventDestroy(pPP->Pong);
+ AssertRC(rc);
+ AssertRC(rc2);
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTSemPingPongDelete);
+
+
+RTDECL(int) RTSemPing(PRTPINGPONG pPP)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn(enmSpeaker == RTPINGPONGSPEAKER_PING,("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Signal the other thread.
+ */
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PONG_SIGNALED);
+ int rc = RTSemEventSignal(pPP->Pong);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ /* restore the state. */
+ AssertMsgFailed(("Failed to signal pong sem %x. rc=%Rrc\n", pPP->Pong, rc));
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PING);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPing);
+
+
+RTDECL(int) RTSemPong(PRTPINGPONG pPP)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn(enmSpeaker == RTPINGPONGSPEAKER_PONG,("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Signal the other thread.
+ */
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PING_SIGNALED);
+ int rc = RTSemEventSignal(pPP->Ping);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ /* restore the state. */
+ AssertMsgFailed(("Failed to signal ping sem %x. rc=%Rrc\n", pPP->Ping, rc));
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PONG);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPong);
+
+
+RTDECL(int) RTSemPingWait(PRTPINGPONG pPP, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn( enmSpeaker == RTPINGPONGSPEAKER_PONG
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG_SIGNALED
+ || enmSpeaker == RTPINGPONGSPEAKER_PING_SIGNALED,
+ ("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Wait.
+ */
+ int rc = RTSemEventWait(pPP->Ping, cMillies);
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PING);
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPingWait);
+
+
+RTDECL(int) RTSemPongWait(PRTPINGPONG pPP, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn( enmSpeaker == RTPINGPONGSPEAKER_PING
+ || enmSpeaker == RTPINGPONGSPEAKER_PING_SIGNALED
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG_SIGNALED,
+ ("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Wait.
+ */
+ int rc = RTSemEventWait(pPP->Pong, cMillies);
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PONG);
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPongWait);
+
diff --git a/src/VBox/Runtime/common/misc/setjmp.asm b/src/VBox/Runtime/common/misc/setjmp.asm
new file mode 100644
index 00000000..f56136a0
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/setjmp.asm
@@ -0,0 +1,148 @@
+; $Id: setjmp.asm $
+;; @file
+; IPRT - No-CRT setjmp & longjmp - AMD64 & X86.
+;
+
+;
+; 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"
+
+
+BEGINCODE
+
+
+;;
+; @param x86:[esp+4] msc:rcx gcc:rdi The jump buffer pointer.
+RT_NOCRT_BEGINPROC setjmp
+%ifdef RT_ARCH_AMD64
+ %ifndef ASM_CALL64_MSC
+ mov rcx, rdi
+ %endif
+ mov rax, [rsp]
+ mov [rcx + 0h*8], rax ; 0 - rip
+ lea rdx, [rsp + 8]
+ mov [rcx + 1h*8], rdx ; 1 - rsp
+ mov [rcx + 2h*8], rbp
+ mov [rcx + 3h*8], r15
+ mov [rcx + 4h*8], r14
+ mov [rcx + 5h*8], r13
+ mov [rcx + 6h*8], r12
+ mov [rcx + 7h*8], rbx
+ %ifdef ASM_CALL64_MSC
+ mov [rcx + 8h*8], rsi
+ mov [rcx + 9h*8], rdi
+ movdqa [rcx + 0ah*8], xmm6
+ movdqa [rcx + 0ch*8], xmm7
+ movdqa [rcx + 0eh*8], xmm8
+ movdqa [rcx + 10h*8], xmm9
+ movdqa [rcx + 12h*8], xmm10
+ movdqa [rcx + 14h*8], xmm11
+ movdqa [rcx + 16h*8], xmm12
+ movdqa [rcx + 18h*8], xmm13
+ movdqa [rcx + 1ah*8], xmm14
+ movdqa [rcx + 1ch*8], xmm15
+ %ifndef RT_OS_WINDOWS
+ %error "Fix setjmp.h"
+ %endif
+ %endif
+%else
+ mov edx, [esp + 4h]
+ mov eax, [esp]
+ mov [edx + 0h*4], eax ; eip
+ lea ecx, [esp + 4h]
+ mov [edx + 1h*4], ecx ; esp
+ mov [edx + 2h*4], ebp
+ mov [edx + 3h*4], ebx
+ mov [edx + 4h*4], edi
+ mov [edx + 5h*4], esi
+%endif
+ xor eax, eax
+ ret
+ENDPROC RT_NOCRT(setjmp)
+
+
+;;
+; @param x86:[esp+4] msc:rcx gcc:rdi The jump buffer pointer.
+; @param x86:[esp+8] msc:rdx gcc:rsi Return value.
+RT_NOCRT_BEGINPROC longjmp
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov eax, edx ; ret
+ %else
+ mov rcx, rdi ; jmp_buf
+ mov eax, esi ; ret
+ %endif
+ mov rbp, [rcx + 2h*8]
+ mov r15, [rcx + 3h*8]
+ mov r14, [rcx + 4h*8]
+ mov r13, [rcx + 5h*8]
+ mov r12, [rcx + 6h*8]
+ mov rbx, [rcx + 7h*8]
+ %ifdef ASM_CALL64_MSC
+ mov rsi, [rcx + 8h*8]
+ mov rdi, [rcx + 9h*8]
+ movdqa xmm6, [rcx + 0ah*8]
+ movdqa xmm7, [rcx + 0ch*8]
+ movdqa xmm8, [rcx + 0eh*8]
+ movdqa xmm9, [rcx + 10h*8]
+ movdqa xmm10, [rcx + 12h*8]
+ movdqa xmm11, [rcx + 14h*8]
+ movdqa xmm12, [rcx + 16h*8]
+ movdqa xmm13, [rcx + 18h*8]
+ movdqa xmm14, [rcx + 1ah*8]
+ movdqa xmm15, [rcx + 1ch*8]
+ %ifndef RT_OS_WINDOWS
+ %error "Fix setjmp.h"
+ %endif
+ %endif
+ test eax, eax
+ jnz .fine
+ inc al
+.fine:
+ mov rsp, [rcx + 1h*8]
+ jmp qword [rcx + 0h*8]
+%else
+ mov edx, [esp + 4h] ; jmp_buf
+ mov eax, [esp + 8h] ; ret
+ mov esi, [edx + 5h*4]
+ mov edi, [edx + 4h*4]
+ mov ebx, [edx + 3h*4]
+ mov ebp, [edx + 2h*4]
+ test eax, eax
+ jnz .fine
+ inc al
+.fine:
+ mov esp, [edx + 1h*4]
+ jmp dword [edx+ 0h*4]
+%endif
+ENDPROC RT_NOCRT(longjmp)
+
diff --git a/src/VBox/Runtime/common/misc/sg.cpp b/src/VBox/Runtime/common/misc/sg.cpp
new file mode 100644
index 00000000..64829321
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sg.cpp
@@ -0,0 +1,507 @@
+/* $Id: sg.cpp $ */
+/** @file
+ * IPRT - S/G buffer handling.
+ */
+
+/*
+ * 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/sg.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+
+
+static void *rtSgBufGet(PRTSGBUF pSgBuf, size_t *pcbData)
+{
+ size_t cbData;
+ void *pvBuf;
+
+ /* Check that the S/G buffer has memory left. */
+ if (RT_UNLIKELY( pSgBuf->idxSeg == pSgBuf->cSegs
+ && !pSgBuf->cbSegLeft))
+ {
+ *pcbData = 0;
+ return NULL;
+ }
+
+#ifndef RDESKTOP
+ AssertMsg( pSgBuf->cbSegLeft <= 128 * _1M
+ && (uintptr_t)pSgBuf->pvSegCur >= (uintptr_t)pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg
+ && (uintptr_t)pSgBuf->pvSegCur + pSgBuf->cbSegLeft <= (uintptr_t)pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg + pSgBuf->paSegs[pSgBuf->idxSeg].cbSeg,
+ ("pSgBuf->idxSeg=%d pSgBuf->cSegs=%d pSgBuf->pvSegCur=%p pSgBuf->cbSegLeft=%zd pSgBuf->paSegs[%d].pvSeg=%p pSgBuf->paSegs[%d].cbSeg=%zd\n",
+ pSgBuf->idxSeg, pSgBuf->cSegs, pSgBuf->pvSegCur, pSgBuf->cbSegLeft, pSgBuf->idxSeg, pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg, pSgBuf->idxSeg,
+ pSgBuf->paSegs[pSgBuf->idxSeg].cbSeg));
+#endif
+
+ cbData = RT_MIN(*pcbData, pSgBuf->cbSegLeft);
+ pvBuf = pSgBuf->pvSegCur;
+ pSgBuf->cbSegLeft -= cbData;
+
+ /* Advance to the next segment if required. */
+ if (!pSgBuf->cbSegLeft)
+ {
+ pSgBuf->idxSeg++;
+
+ if (pSgBuf->idxSeg < pSgBuf->cSegs)
+ {
+ pSgBuf->pvSegCur = pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg;
+ pSgBuf->cbSegLeft = pSgBuf->paSegs[pSgBuf->idxSeg].cbSeg;
+ }
+
+ *pcbData = cbData;
+ }
+ else
+ pSgBuf->pvSegCur = (uint8_t *)pSgBuf->pvSegCur + cbData;
+
+ return pvBuf;
+}
+
+
+RTDECL(void) RTSgBufInit(PRTSGBUF pSgBuf, PCRTSGSEG paSegs, size_t cSegs)
+{
+ AssertPtr(pSgBuf);
+ Assert( (cSegs > 0 && RT_VALID_PTR(paSegs))
+ || (!cSegs && !paSegs));
+ Assert(cSegs < (~(unsigned)0 >> 1));
+
+ pSgBuf->paSegs = paSegs;
+ pSgBuf->cSegs = (unsigned)cSegs;
+ pSgBuf->idxSeg = 0;
+ if (cSegs && paSegs)
+ {
+ pSgBuf->pvSegCur = paSegs[0].pvSeg;
+ pSgBuf->cbSegLeft = paSegs[0].cbSeg;
+ }
+ else
+ {
+ pSgBuf->pvSegCur = NULL;
+ pSgBuf->cbSegLeft = 0;
+ }
+}
+
+
+RTDECL(void) RTSgBufReset(PRTSGBUF pSgBuf)
+{
+ AssertPtrReturnVoid(pSgBuf);
+
+ pSgBuf->idxSeg = 0;
+ if (pSgBuf->cSegs)
+ {
+ pSgBuf->pvSegCur = pSgBuf->paSegs[0].pvSeg;
+ pSgBuf->cbSegLeft = pSgBuf->paSegs[0].cbSeg;
+ }
+ else
+ {
+ pSgBuf->pvSegCur = NULL;
+ pSgBuf->cbSegLeft = 0;
+ }
+}
+
+
+RTDECL(void) RTSgBufClone(PRTSGBUF pSgBufTo, PCRTSGBUF pSgBufFrom)
+{
+ AssertPtr(pSgBufTo);
+ AssertPtr(pSgBufFrom);
+
+ pSgBufTo->paSegs = pSgBufFrom->paSegs;
+ pSgBufTo->cSegs = pSgBufFrom->cSegs;
+ pSgBufTo->idxSeg = pSgBufFrom->idxSeg;
+ pSgBufTo->pvSegCur = pSgBufFrom->pvSegCur;
+ pSgBufTo->cbSegLeft = pSgBufFrom->cbSegLeft;
+}
+
+
+RTDECL(void *) RTSgBufGetNextSegment(PRTSGBUF pSgBuf, size_t *pcbSeg)
+{
+ AssertPtrReturn(pSgBuf, NULL);
+ AssertPtrReturn(pcbSeg, NULL);
+
+ if (!*pcbSeg)
+ *pcbSeg = pSgBuf->cbSegLeft;
+
+ return rtSgBufGet(pSgBuf, pcbSeg);
+}
+
+
+RTDECL(size_t) RTSgBufCopy(PRTSGBUF pSgBufDst, PRTSGBUF pSgBufSrc, size_t cbCopy)
+{
+ AssertPtrReturn(pSgBufDst, 0);
+ AssertPtrReturn(pSgBufSrc, 0);
+
+ size_t cbLeft = cbCopy;
+ while (cbLeft)
+ {
+ size_t cbThisCopy = RT_MIN(RT_MIN(pSgBufDst->cbSegLeft, cbLeft), pSgBufSrc->cbSegLeft);
+ if (!cbThisCopy)
+ break;
+
+ size_t cbTmp = cbThisCopy;
+ void *pvBufDst = rtSgBufGet(pSgBufDst, &cbTmp);
+ Assert(cbTmp == cbThisCopy);
+ void *pvBufSrc = rtSgBufGet(pSgBufSrc, &cbTmp);
+ Assert(cbTmp == cbThisCopy);
+
+ memcpy(pvBufDst, pvBufSrc, cbThisCopy);
+
+ cbLeft -= cbThisCopy;
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(int) RTSgBufCmp(PCRTSGBUF pSgBuf1, PCRTSGBUF pSgBuf2, size_t cbCmp)
+{
+ AssertPtrReturn(pSgBuf1, 0);
+ AssertPtrReturn(pSgBuf2, 0);
+
+ /* Set up the temporary buffers */
+ RTSGBUF SgBuf1;
+ RTSgBufClone(&SgBuf1, pSgBuf1);
+ RTSGBUF SgBuf2;
+ RTSgBufClone(&SgBuf2, pSgBuf2);
+
+ size_t cbLeft = cbCmp;
+ while (cbLeft)
+ {
+ size_t cbThisCmp = RT_MIN(RT_MIN(SgBuf1.cbSegLeft, cbLeft), SgBuf2.cbSegLeft);
+ if (!cbThisCmp)
+ break;
+
+ size_t cbTmp = cbThisCmp;
+ void *pvBuf1 = rtSgBufGet(&SgBuf1, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+ void *pvBuf2 = rtSgBufGet(&SgBuf2, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+
+ int rc = memcmp(pvBuf1, pvBuf2, cbThisCmp);
+ if (rc)
+ return rc;
+
+ cbLeft -= cbThisCmp;
+ }
+
+ return 0;
+}
+
+
+RTDECL(int) RTSgBufCmpEx(PRTSGBUF pSgBuf1, PRTSGBUF pSgBuf2, size_t cbCmp, size_t *poffDiff, bool fAdvance)
+{
+ AssertPtrReturn(pSgBuf1, 0);
+ AssertPtrReturn(pSgBuf2, 0);
+
+ RTSGBUF SgBuf1Tmp;
+ RTSGBUF SgBuf2Tmp;
+ PRTSGBUF pSgBuf1Tmp;
+ PRTSGBUF pSgBuf2Tmp;
+
+ if (!fAdvance)
+ {
+ /* Set up the temporary buffers */
+ RTSgBufClone(&SgBuf1Tmp, pSgBuf1);
+ RTSgBufClone(&SgBuf2Tmp, pSgBuf2);
+ pSgBuf1Tmp = &SgBuf1Tmp;
+ pSgBuf2Tmp = &SgBuf2Tmp;
+ }
+ else
+ {
+ pSgBuf1Tmp = pSgBuf1;
+ pSgBuf2Tmp = pSgBuf2;
+ }
+
+ size_t cbLeft = cbCmp;
+ size_t off = 0;
+ while (cbLeft)
+ {
+ size_t cbThisCmp = RT_MIN(RT_MIN(pSgBuf1Tmp->cbSegLeft, cbLeft), pSgBuf2Tmp->cbSegLeft);
+ if (!cbThisCmp)
+ break;
+
+ size_t cbTmp = cbThisCmp;
+ uint8_t *pbBuf1 = (uint8_t *)rtSgBufGet(pSgBuf1Tmp, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+ uint8_t *pbBuf2 = (uint8_t *)rtSgBufGet(pSgBuf2Tmp, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+
+ int iDiff = memcmp(pbBuf1, pbBuf2, cbThisCmp);
+ if (iDiff)
+ {
+ /* Locate the first byte that differs if the caller requested this. */
+ if (poffDiff)
+ {
+ while ( cbThisCmp-- > 0
+ && *pbBuf1 == *pbBuf2)
+ {
+ pbBuf1++;
+ pbBuf2++;
+ off++;
+ }
+
+ *poffDiff = off;
+ }
+ return iDiff;
+ }
+
+ cbLeft -= cbThisCmp;
+ off += cbThisCmp;
+ }
+
+ return 0;
+}
+
+
+RTDECL(size_t) RTSgBufSet(PRTSGBUF pSgBuf, uint8_t ubFill, size_t cbSet)
+{
+ AssertPtrReturn(pSgBuf, 0);
+
+ size_t cbLeft = cbSet;
+
+ while (cbLeft)
+ {
+ size_t cbThisSet = cbLeft;
+ void *pvBuf = rtSgBufGet(pSgBuf, &cbThisSet);
+
+ if (!cbThisSet)
+ break;
+
+ memset(pvBuf, ubFill, cbThisSet);
+
+ cbLeft -= cbThisSet;
+ }
+
+ return cbSet - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyToBuf(PRTSGBUF pSgBuf, void *pvBuf, size_t cbCopy)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pvBuf, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvSrc = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ memcpy(pvBuf, pvSrc, cbThisCopy);
+
+ cbLeft -= cbThisCopy;
+ pvBuf = (void *)((uintptr_t)pvBuf + cbThisCopy);
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyFromBuf(PRTSGBUF pSgBuf, const void *pvBuf, size_t cbCopy)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pvBuf, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvDst = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ memcpy(pvDst, pvBuf, cbThisCopy);
+
+ cbLeft -= cbThisCopy;
+ pvBuf = (const void *)((uintptr_t)pvBuf + cbThisCopy);
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyToFn(PRTSGBUF pSgBuf, size_t cbCopy, PFNRTSGBUFCOPYTO pfnCopyTo, void *pvUser)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pfnCopyTo, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvSrc = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ size_t cbThisCopied = pfnCopyTo(pSgBuf, pvSrc, cbThisCopy, pvUser);
+ cbLeft -= cbThisCopied;
+ if (cbThisCopied < cbThisCopy)
+ break;
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyFromFn(PRTSGBUF pSgBuf, size_t cbCopy, PFNRTSGBUFCOPYFROM pfnCopyFrom, void *pvUser)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pfnCopyFrom, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvDst = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ size_t cbThisCopied = pfnCopyFrom(pSgBuf, pvDst, cbThisCopy, pvUser);
+ cbLeft -= cbThisCopied;
+ if (cbThisCopied < cbThisCopy)
+ break;
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufAdvance(PRTSGBUF pSgBuf, size_t cbAdvance)
+{
+ AssertPtrReturn(pSgBuf, 0);
+
+ size_t cbLeft = cbAdvance;
+ while (cbLeft)
+ {
+ size_t cbThisAdvance = cbLeft;
+ rtSgBufGet(pSgBuf, &cbThisAdvance);
+ if (!cbThisAdvance)
+ break;
+
+ cbLeft -= cbThisAdvance;
+ }
+
+ return cbAdvance - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufSegArrayCreate(PRTSGBUF pSgBuf, PRTSGSEG paSeg, unsigned *pcSeg, size_t cbData)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pcSeg, 0);
+
+ unsigned cSeg = 0;
+ size_t cb = 0;
+
+ if (!paSeg)
+ {
+ if (pSgBuf->cbSegLeft > 0)
+ {
+ size_t idx = pSgBuf->idxSeg;
+ cSeg = 1;
+
+ cb += RT_MIN(pSgBuf->cbSegLeft, cbData);
+ cbData -= RT_MIN(pSgBuf->cbSegLeft, cbData);
+
+ while ( cbData
+ && idx < pSgBuf->cSegs - 1)
+ {
+ idx++;
+ cSeg++;
+ cb += RT_MIN(pSgBuf->paSegs[idx].cbSeg, cbData);
+ cbData -= RT_MIN(pSgBuf->paSegs[idx].cbSeg, cbData);
+ }
+ }
+ }
+ else
+ {
+ while ( cbData
+ && cSeg < *pcSeg)
+ {
+ size_t cbThisSeg = cbData;
+ void *pvSeg = rtSgBufGet(pSgBuf, &cbThisSeg);
+
+ if (!cbThisSeg)
+ {
+ Assert(!pvSeg);
+ break;
+ }
+
+ AssertMsg(cbThisSeg <= cbData, ("Impossible!\n"));
+
+ paSeg[cSeg].cbSeg = cbThisSeg;
+ paSeg[cSeg].pvSeg = pvSeg;
+ cSeg++;
+ cbData -= cbThisSeg;
+ cb += cbThisSeg;
+ }
+ }
+
+ *pcSeg = cSeg;
+
+ return cb;
+}
+
+
+RTDECL(bool) RTSgBufIsZero(PRTSGBUF pSgBuf, size_t cbCheck)
+{
+ RTSGBUF SgBufTmp;
+ RTSgBufClone(&SgBufTmp, pSgBuf);
+
+ bool fIsZero = true;
+ size_t cbLeft = cbCheck;
+ while (cbLeft)
+ {
+ size_t cbThisCheck = cbLeft;
+ void *pvBuf = rtSgBufGet(&SgBufTmp, &cbThisCheck);
+ if (!cbThisCheck)
+ break;
+ fIsZero = ASMMemIsZero(pvBuf, cbThisCheck);
+ if (!fIsZero)
+ break;
+ cbLeft -= cbThisCheck;
+ }
+
+ return fIsZero;
+}
+
diff --git a/src/VBox/Runtime/common/misc/term.cpp b/src/VBox/Runtime/common/misc/term.cpp
new file mode 100644
index 00000000..e67b36a7
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/term.cpp
@@ -0,0 +1,252 @@
+/* $Id: term.cpp $ */
+/** @file
+ * IPRT - Common Termination Code.
+ */
+
+/*
+ * 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/initterm.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a termination callback record. */
+typedef struct RTTERMCALLBACKREC *PRTTERMCALLBACKREC;
+/**
+ * Termination callback record.
+ */
+typedef struct RTTERMCALLBACKREC
+{
+ /** Pointer to the next record. */
+ PRTTERMCALLBACKREC pNext;
+ /** Pointer to the callback. */
+ PFNRTTERMCALLBACK pfnCallback;
+ /** The user argument. */
+ void *pvUser;
+} RTTERMCALLBACKREC;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Execute once construct protecting lazy callback initialization. */
+static RTONCE g_InitTermCallbacksOnce = RTONCE_INITIALIZER;
+/** Mutex protecting the callback globals. */
+static RTSEMFASTMUTEX g_hFastMutex = NIL_RTSEMFASTMUTEX;
+/** Number of registered callbacks. */
+static uint32_t g_cCallbacks = 0;
+/** The callback head. */
+static PRTTERMCALLBACKREC g_pCallbackHead = NULL;
+
+
+
+/**
+ * Initializes the globals.
+ *
+ * @returns IPRT status code
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int32_t) rtTermInitOnce(void *pvUser)
+{
+ RTSEMFASTMUTEX hFastMutex;
+ int rc;
+
+ Assert(!g_cCallbacks);
+ Assert(!g_pCallbackHead);
+ Assert(g_hFastMutex == NIL_RTSEMFASTMUTEX);
+
+ rc = RTSemFastMutexCreate(&hFastMutex);
+ if (RT_SUCCESS(rc))
+ g_hFastMutex = hFastMutex;
+
+ NOREF(pvUser);
+
+ return rc;
+}
+
+
+
+RTDECL(int) RTTermRegisterCallback(PFNRTTERMCALLBACK pfnCallback, void *pvUser)
+{
+ int rc;
+ PRTTERMCALLBACKREC pNew;
+
+ /*
+ * Validation and lazy init.
+ */
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+
+ rc = RTOnce(&g_InitTermCallbacksOnce, rtTermInitOnce, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Allocate and initialize a new callback record.
+ */
+ pNew = (PRTTERMCALLBACKREC)RTMemAlloc(sizeof(*pNew));
+ if (!pNew)
+ return VERR_NO_MEMORY;
+ pNew->pfnCallback = pfnCallback;
+ pNew->pvUser = pvUser;
+
+ /*
+ * Insert into the list.
+ */
+ rc = RTSemFastMutexRequest(g_hFastMutex);
+ if (RT_SUCCESS(rc))
+ {
+ g_cCallbacks++;
+ pNew->pNext = g_pCallbackHead;
+ g_pCallbackHead = pNew;
+
+ RTSemFastMutexRelease(g_hFastMutex);
+ }
+ else
+ RTMemFree(pNew);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTTermRegisterCallback);
+
+
+RTDECL(int) RTTermDeregisterCallback(PFNRTTERMCALLBACK pfnCallback, void *pvUser)
+{
+ /*
+ * g_hFastMutex will be NIL if we're not initialized.
+ */
+ int rc;
+ RTSEMFASTMUTEX hFastMutex = g_hFastMutex;
+ if (hFastMutex == NIL_RTSEMFASTMUTEX)
+ return VERR_NOT_FOUND;
+
+ rc = RTSemFastMutexRequest(hFastMutex);
+ if (RT_SUCCESS(rc))
+ {
+
+ /*
+ * Search for the specified pfnCallback/pvUser pair.
+ */
+ PRTTERMCALLBACKREC pPrev = NULL;
+ PRTTERMCALLBACKREC pCur = g_pCallbackHead;
+ while (pCur)
+ {
+ if ( pCur->pfnCallback == pfnCallback
+ && pCur->pvUser == pvUser)
+ {
+ if (pPrev)
+ pPrev->pNext = pCur->pNext;
+ else
+ g_pCallbackHead = pCur->pNext;
+ g_cCallbacks--;
+ RTSemFastMutexRelease(hFastMutex);
+
+ pCur->pfnCallback = NULL;
+ RTMemFree(pCur);
+ return VINF_SUCCESS;
+ }
+
+ /* next */
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+
+ RTSemFastMutexRelease(hFastMutex);
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTTermDeregisterCallback);
+
+
+RTDECL(void) RTTermRunCallbacks(RTTERMREASON enmReason, int32_t iStatus)
+{
+ RTSEMFASTMUTEX hFastMutex;
+ Assert( enmReason == RTTERMREASON_EXIT
+ || enmReason == RTTERMREASON_ABEND
+ || enmReason == RTTERMREASON_SIGNAL
+ || enmReason == RTTERMREASON_UNLOAD);
+
+ /*
+ * Run the callback list. This is a bit paranoid in order to guard against
+ * recursive calls to RTTermRunCallbacks.
+ */
+ while (g_hFastMutex != NIL_RTSEMFASTMUTEX)
+ {
+ PRTTERMCALLBACKREC pCur;
+ RTTERMCALLBACKREC CurCopy;
+ int rc;
+
+ /* Unlink the head of the chain. */
+ rc = RTSemFastMutexRequest(g_hFastMutex);
+ AssertRCReturnVoid(rc);
+ pCur = g_pCallbackHead;
+ if (pCur)
+ {
+ g_pCallbackHead = pCur->pNext;
+ g_cCallbacks--;
+ }
+ RTSemFastMutexRelease(g_hFastMutex);
+ if (!pCur)
+ break;
+
+ /* Copy and free it. */
+ CurCopy = *pCur;
+ RTMemFree(pCur);
+
+ /* Make the call. */
+ CurCopy.pfnCallback(enmReason, iStatus, CurCopy.pvUser);
+ }
+
+ /*
+ * Free the lock.
+ */
+ ASMAtomicXchgHandle(&g_hFastMutex, NIL_RTSEMFASTMUTEX, &hFastMutex);
+ RTSemFastMutexDestroy(hFastMutex);
+ RTOnceReset(&g_InitTermCallbacksOnce); /* for the testcase */
+}
+RT_EXPORT_SYMBOL(RTTermRunCallbacks);
+
diff --git a/src/VBox/Runtime/common/misc/thread.cpp b/src/VBox/Runtime/common/misc/thread.cpp
new file mode 100644
index 00000000..c5d5f519
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/thread.cpp
@@ -0,0 +1,1446 @@
+/* $Id: thread.cpp $ */
+/** @file
+ * IPRT - Threads, common routines.
+ */
+
+/*
+ * 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/thread.h>
+#include "internal/iprt.h"
+
+#include <iprt/log.h>
+#include <iprt/avl.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/lockvalidator.h>
+#include <iprt/semaphore.h>
+#ifdef IN_RING0
+# include <iprt/spinlock.h>
+#endif
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include "internal/magics.h"
+#include "internal/thread.h"
+#include "internal/sched.h"
+#include "internal/process.h"
+#ifdef RT_WITH_ICONV_CACHE
+# include "internal/string.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef IN_RING0
+# define RT_THREAD_LOCK_RW() RTSpinlockAcquire(g_ThreadSpinlock)
+# define RT_THREAD_UNLOCK_RW() RTSpinlockRelease(g_ThreadSpinlock)
+# define RT_THREAD_LOCK_RD() RTSpinlockAcquire(g_ThreadSpinlock)
+# define RT_THREAD_UNLOCK_RD() RTSpinlockRelease(g_ThreadSpinlock)
+#else
+# define RT_THREAD_LOCK_RW() rtThreadLockRW()
+# define RT_THREAD_UNLOCK_RW() rtThreadUnLockRW()
+# define RT_THREAD_LOCK_RD() rtThreadLockRD()
+# define RT_THREAD_UNLOCK_RD() rtThreadUnLockRD()
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Indicates whether we've been initialized or not. */
+static bool g_frtThreadInitialized;
+#ifdef IN_RING3
+/** The RW lock protecting the tree. */
+static RTSEMRW g_ThreadRWSem = NIL_RTSEMRW;
+#else
+/** The spinlocks protecting the tree. */
+static RTSPINLOCK g_ThreadSpinlock = NIL_RTSPINLOCK;
+#endif
+/** The AVL thread containing the threads. */
+static PAVLPVNODECORE g_ThreadTree;
+/** The number of threads in the tree (for ring-0 termination kludge). */
+static uint32_t volatile g_cThreadInTree;
+/** Counters for each thread type. */
+DECL_HIDDEN_DATA(uint32_t volatile) g_acRTThreadTypeStats[RTTHREADTYPE_END];
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtThreadDestroy(PRTTHREADINT pThread);
+#ifdef IN_RING3
+static int rtThreadAdopt(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName);
+#endif
+static void rtThreadRemoveLocked(PRTTHREADINT pThread);
+static PRTTHREADINT rtThreadAlloc(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName);
+
+
+/** @page pg_rt_thread IPRT Thread Internals
+ *
+ * IPRT provides interface to whatever native threading that the host provides,
+ * preferably using a CRT level interface to better integrate with other libraries.
+ *
+ * Internally IPRT keeps track of threads by means of the RTTHREADINT structure.
+ * All the RTTHREADINT structures are kept in a AVL tree which is protected by a
+ * read/write lock for efficient access. A thread is inserted into the tree in
+ * three places in the code. The main thread is 'adopted' by IPRT on rtR3Init()
+ * by rtThreadAdopt(). When creating a new thread there the child and the parent
+ * race inserting the thread, this is rtThreadMain() and RTThreadCreate.
+ *
+ * RTTHREADINT objects are using reference counting as a mean of sticking around
+ * till no-one needs them any longer. Waitable threads is created with one extra
+ * reference so they won't go away until they are waited on. This introduces a
+ * major problem if we use the host thread identifier as key in the AVL tree - the
+ * host may reuse the thread identifier before the thread was waited on. So, on
+ * most platforms we are using the RTTHREADINT pointer as key and not the
+ * thread id. RTThreadSelf() then have to be implemented using a pointer stored
+ * in thread local storage (TLS).
+ *
+ * In Ring-0 we only try keep track of kernel threads created by RTThreadCreate
+ * at the moment. There we really only need the 'join' feature, but doing things
+ * the same way allow us to name threads and similar stuff.
+ */
+
+
+/**
+ * Initializes the thread database.
+ *
+ * @returns iprt status code.
+ */
+DECLHIDDEN(int) rtThreadInit(void)
+{
+#ifdef IN_RING3
+ int rc = VINF_ALREADY_INITIALIZED;
+ if (g_ThreadRWSem == NIL_RTSEMRW)
+ {
+ /*
+ * We assume the caller is the 1st thread, which we'll call 'main'.
+ * But first, we'll create the semaphore.
+ */
+ rc = RTSemRWCreateEx(&g_ThreadRWSem, RTSEMRW_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtThreadNativeInit();
+ if (RT_SUCCESS(rc))
+ rc = rtThreadAdopt(RTTHREADTYPE_DEFAULT, 0, RTTHREADINT_FLAGS_MAIN, "main");
+ if (RT_SUCCESS(rc))
+ rc = rtSchedNativeCalcDefaultPriority(RTTHREADTYPE_DEFAULT);
+ if (RT_SUCCESS(rc))
+ {
+ g_frtThreadInitialized = true;
+ return VINF_SUCCESS;
+ }
+
+ /* failed, clear out */
+ RTSemRWDestroy(g_ThreadRWSem);
+ g_ThreadRWSem = NIL_RTSEMRW;
+ }
+ }
+
+#elif defined(IN_RING0)
+ int rc;
+ /*
+ * Create the spinlock and to native init.
+ */
+ Assert(g_ThreadSpinlock == NIL_RTSPINLOCK);
+ rc = RTSpinlockCreate(&g_ThreadSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "RTThread");
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtThreadNativeInit();
+ if (RT_SUCCESS(rc))
+ {
+ g_frtThreadInitialized = true;
+ return VINF_SUCCESS;
+ }
+
+ /* failed, clear out */
+ RTSpinlockDestroy(g_ThreadSpinlock);
+ g_ThreadSpinlock = NIL_RTSPINLOCK;
+ }
+#else
+# error "!IN_RING0 && !IN_RING3"
+#endif
+ return rc;
+}
+
+
+#ifdef IN_RING3
+/**
+ * Called when IPRT was first initialized in unobtrusive mode and later changed
+ * to obtrustive.
+ *
+ * This is only applicable in ring-3.
+ */
+DECLHIDDEN(void) rtThreadReInitObtrusive(void)
+{
+ rtThreadNativeReInitObtrusive();
+}
+#endif
+
+
+/**
+ * Terminates the thread database.
+ */
+DECLHIDDEN(void) rtThreadTerm(void)
+{
+#ifdef IN_RING3
+ /* we don't cleanup here yet */
+
+#elif defined(IN_RING0)
+ /* just destroy the spinlock and assume the thread is fine... */
+ RTSpinlockDestroy(g_ThreadSpinlock);
+ g_ThreadSpinlock = NIL_RTSPINLOCK;
+ if (g_ThreadTree != NULL)
+ RTAssertMsg2Weak("WARNING: g_ThreadTree=%p\n", g_ThreadTree);
+#endif
+}
+
+
+#ifdef IN_RING3
+
+DECLINLINE(void) rtThreadLockRW(void)
+{
+ if (g_ThreadRWSem == NIL_RTSEMRW)
+ rtThreadInit();
+ int rc = RTSemRWRequestWrite(g_ThreadRWSem, RT_INDEFINITE_WAIT);
+ AssertReleaseRC(rc);
+}
+
+
+DECLINLINE(void) rtThreadLockRD(void)
+{
+ if (g_ThreadRWSem == NIL_RTSEMRW)
+ rtThreadInit();
+ int rc = RTSemRWRequestRead(g_ThreadRWSem, RT_INDEFINITE_WAIT);
+ AssertReleaseRC(rc);
+}
+
+
+DECLINLINE(void) rtThreadUnLockRW(void)
+{
+ int rc = RTSemRWReleaseWrite(g_ThreadRWSem);
+ AssertReleaseRC(rc);
+}
+
+
+DECLINLINE(void) rtThreadUnLockRD(void)
+{
+ int rc = RTSemRWReleaseRead(g_ThreadRWSem);
+ AssertReleaseRC(rc);
+}
+
+
+/**
+ * Adopts the calling thread.
+ * No locks are taken or released by this function.
+ */
+static int rtThreadAdopt(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName)
+{
+ int rc;
+ PRTTHREADINT pThread;
+ Assert(!(fFlags & RTTHREADFLAGS_WAITABLE));
+ fFlags &= ~RTTHREADFLAGS_WAITABLE;
+
+ /*
+ * Allocate and insert the thread.
+ * (It is vital that rtThreadNativeAdopt updates the TLS before
+ * we try inserting the thread because of locking.)
+ */
+ rc = VERR_NO_MEMORY;
+ pThread = rtThreadAlloc(enmType, fFlags, RTTHREADINT_FLAGS_ALIEN | fIntFlags, pszName);
+ if (pThread)
+ {
+ RTNATIVETHREAD NativeThread = RTThreadNativeSelf();
+ rc = rtThreadNativeAdopt(pThread);
+ if (RT_SUCCESS(rc))
+ {
+ rtThreadInsert(pThread, NativeThread);
+ rtThreadSetState(pThread, RTTHREADSTATE_RUNNING);
+ rtThreadRelease(pThread);
+ }
+ else
+ rtThreadDestroy(pThread);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTThreadAdopt(RTTHREADTYPE enmType, unsigned fFlags, const char *pszName, PRTTHREAD pThread)
+{
+ int rc;
+ RTTHREAD Thread;
+
+ AssertReturn(!(fFlags & RTTHREADFLAGS_WAITABLE), VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pThread, VERR_INVALID_POINTER);
+
+ rc = VINF_SUCCESS;
+ Thread = RTThreadSelf();
+ if (Thread == NIL_RTTHREAD)
+ {
+ /* generate a name if none was given. */
+ char szName[RTTHREAD_NAME_LEN];
+ if (!pszName || !*pszName)
+ {
+ static uint32_t s_i32AlienId = 0;
+ uint32_t i32Id = ASMAtomicIncU32(&s_i32AlienId);
+ RTStrPrintf(szName, sizeof(szName), "ALIEN-%RX32", i32Id);
+ pszName = szName;
+ }
+
+ /* try adopt it */
+ rc = rtThreadAdopt(enmType, fFlags, 0, pszName);
+ Thread = RTThreadSelf();
+
+ /* Don't too early during init, as rtLogLock may end up here and cause endless recursion. */
+ if (rc != VERR_FAILED_TO_SET_SELF_TLS)
+ Log(("RTThreadAdopt: %RTthrd %RTnthrd '%s' enmType=%d fFlags=%#x rc=%Rrc\n",
+ Thread, RTThreadNativeSelf(), pszName, enmType, fFlags, rc));
+ }
+ else
+ Log(("RTThreadAdopt: %RTthrd %RTnthrd '%s' enmType=%d fFlags=%#x - already adopted!\n",
+ Thread, RTThreadNativeSelf(), pszName, enmType, fFlags));
+
+ if (pThread)
+ *pThread = Thread;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadAdopt);
+
+
+RTDECL(RTTHREAD) RTThreadSelfAutoAdopt(void)
+{
+ RTTHREAD hSelf = RTThreadSelf();
+ if (RT_UNLIKELY(hSelf == NIL_RTTHREAD))
+ RTThreadAdopt(RTTHREADTYPE_DEFAULT, 0, NULL, &hSelf);
+ return hSelf;
+}
+RT_EXPORT_SYMBOL(RTThreadSelfAutoAdopt);
+
+#endif /* IN_RING3 */
+
+/**
+ * Allocates a per thread data structure and initializes the basic fields.
+ *
+ * @returns Pointer to per thread data structure.
+ * This is reference once.
+ * @returns NULL on failure.
+ * @param enmType The thread type.
+ * @param fFlags The thread flags.
+ * @param fIntFlags The internal thread flags.
+ * @param pszName Pointer to the thread name.
+ */
+PRTTHREADINT rtThreadAlloc(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)RTMemAllocZ(sizeof(RTTHREADINT));
+ if (pThread)
+ {
+ size_t cchName;
+ int rc;
+
+ pThread->Core.Key = (void*)NIL_RTTHREAD;
+ pThread->u32Magic = RTTHREADINT_MAGIC;
+ cchName = strlen(pszName);
+ if (cchName >= RTTHREAD_NAME_LEN)
+ cchName = RTTHREAD_NAME_LEN - 1;
+ memcpy(pThread->szName, pszName, cchName);
+ pThread->szName[cchName] = '\0';
+ pThread->cRefs = 2 + !!(fFlags & RTTHREADFLAGS_WAITABLE); /* And extra reference if waitable. */
+ pThread->rc = VERR_PROCESS_RUNNING; /** @todo get a better error code! */
+ pThread->enmType = enmType;
+ pThread->fFlags = fFlags;
+ pThread->fIntFlags = fIntFlags;
+ pThread->enmState = RTTHREADSTATE_INITIALIZING;
+ pThread->fReallySleeping = false;
+#ifdef IN_RING3
+ rtLockValidatorInitPerThread(&pThread->LockValidator);
+#endif
+#ifdef RT_WITH_ICONV_CACHE
+ rtStrIconvCacheInit(pThread);
+#endif
+#if defined(IPRT_NO_CRT) && defined(IN_RING3)
+ pThread->NoCrt.enmAllocType = RTNOCRTTHREADDATA::kAllocType_Embedded;
+ RTListInit(&pThread->NoCrt.ListEntry);
+#endif
+ rc = RTSemEventMultiCreate(&pThread->EventUser);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventMultiCreate(&pThread->EventTerminated);
+ if (RT_SUCCESS(rc))
+ return pThread;
+ RTSemEventMultiDestroy(pThread->EventUser);
+ }
+ RTMemFree(pThread);
+ }
+ return NULL;
+}
+
+
+/**
+ * Insert the per thread data structure into the tree.
+ *
+ * This can be called from both the thread it self and the parent,
+ * thus it must handle insertion failures in a nice manner.
+ *
+ * @param pThread Pointer to thread structure allocated by rtThreadAlloc().
+ * @param NativeThread The native thread id.
+ */
+DECLHIDDEN(void) rtThreadInsert(PRTTHREADINT pThread, RTNATIVETHREAD NativeThread)
+{
+ Assert(pThread);
+ Assert(pThread->u32Magic == RTTHREADINT_MAGIC);
+
+ {
+ RT_THREAD_LOCK_RW();
+
+ /*
+ * Do not insert a terminated thread.
+ *
+ * This may happen if the thread finishes before the RTThreadCreate call
+ * gets this far. Since the OS may quickly reuse the native thread ID
+ * it should not be reinserted at this point.
+ */
+ if (rtThreadGetState(pThread) != RTTHREADSTATE_TERMINATED)
+ {
+ /*
+ * Before inserting we must check if there is a thread with this id
+ * in the tree already. We're racing parent and child on insert here
+ * so that the handle is valid in both ends when they return / start.
+ *
+ * If it's not ourself we find, it's a dead alien thread and we will
+ * unlink it from the tree. Alien threads will be released at this point.
+ */
+ PRTTHREADINT pThreadOther = (PRTTHREADINT)RTAvlPVGet(&g_ThreadTree, (void *)NativeThread);
+ if (pThreadOther != pThread)
+ {
+ bool fRc;
+ /* remove dead alien if any */
+ if (pThreadOther)
+ {
+ AssertMsg(pThreadOther->fIntFlags & RTTHREADINT_FLAGS_ALIEN, ("%p:%s; %p:%s\n", pThread, pThread->szName, pThreadOther, pThreadOther->szName));
+ ASMAtomicBitClear(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE_BIT);
+ rtThreadRemoveLocked(pThreadOther);
+ if (pThreadOther->fIntFlags & RTTHREADINT_FLAGS_ALIEN)
+ rtThreadRelease(pThreadOther);
+ }
+
+ /* insert the thread */
+ ASMAtomicWritePtr(&pThread->Core.Key, (void *)NativeThread);
+ fRc = RTAvlPVInsert(&g_ThreadTree, &pThread->Core);
+ ASMAtomicOrU32(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE);
+ if (fRc)
+ {
+ ASMAtomicIncU32(&g_cThreadInTree);
+ ASMAtomicIncU32(&g_acRTThreadTypeStats[pThread->enmType]);
+
+#if defined(IPRT_NO_CRT) && defined(IN_RING3)
+ RTTLS const iTlsPerThread = g_iTlsRtNoCrtPerThread;
+ if ( iTlsPerThread != NIL_RTTLS
+ && RTTlsGet(iTlsPerThread) == NULL)
+ RTTlsSet(iTlsPerThread, &pThread->NoCrt);
+#endif
+ }
+
+ AssertReleaseMsg(fRc, ("Lock problem? %p (%RTnthrd) %s\n", pThread, NativeThread, pThread->szName));
+ NOREF(fRc);
+ }
+ }
+
+ RT_THREAD_UNLOCK_RW();
+ }
+}
+
+
+/**
+ * Removes the thread from the AVL tree, call owns the tree lock
+ * and has cleared the RTTHREADINT_FLAG_IN_TREE bit.
+ *
+ * @param pThread The thread to remove.
+ */
+static void rtThreadRemoveLocked(PRTTHREADINT pThread)
+{
+ PRTTHREADINT pThread2 = (PRTTHREADINT)RTAvlPVRemove(&g_ThreadTree, pThread->Core.Key);
+ AssertMsg(pThread2 == pThread, ("%p(%s) != %p (%p/%s)\n", pThread2, pThread2 ? pThread2->szName : "<null>",
+ pThread, pThread->Core.Key, pThread->szName));
+ if (pThread2)
+ {
+ ASMAtomicDecU32(&g_cThreadInTree);
+ ASMAtomicDecU32(&g_acRTThreadTypeStats[pThread->enmType]);
+ }
+}
+
+
+/**
+ * Removes the thread from the AVL tree.
+ *
+ * @param pThread The thread to remove.
+ */
+static void rtThreadRemove(PRTTHREADINT pThread)
+{
+ RT_THREAD_LOCK_RW();
+ if (ASMAtomicBitTestAndClear(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE_BIT))
+ rtThreadRemoveLocked(pThread);
+ RT_THREAD_UNLOCK_RW();
+}
+
+
+/**
+ * Checks if a thread is alive or not.
+ *
+ * @returns true if the thread is alive (or we don't really know).
+ * @returns false if the thread has surely terminate.
+ */
+DECLINLINE(bool) rtThreadIsAlive(PRTTHREADINT pThread)
+{
+ return !(pThread->fIntFlags & RTTHREADINT_FLAGS_TERMINATED);
+}
+
+
+/**
+ * Gets a thread by it's native ID.
+ *
+ * @returns pointer to the thread structure.
+ * @returns NULL if not a thread IPRT knows.
+ * @param NativeThread The native thread id.
+ */
+DECLHIDDEN(PRTTHREADINT) rtThreadGetByNative(RTNATIVETHREAD NativeThread)
+{
+ PRTTHREADINT pThread;
+ /*
+ * Simple tree lookup.
+ */
+ RT_THREAD_LOCK_RD();
+ pThread = (PRTTHREADINT)RTAvlPVGet(&g_ThreadTree, (void *)NativeThread);
+ RT_THREAD_UNLOCK_RD();
+ return pThread;
+}
+
+
+/**
+ * Gets the per thread data structure for a thread handle.
+ *
+ * @returns Pointer to the per thread data structure for Thread.
+ * The caller must release the thread using rtThreadRelease().
+ * @returns NULL if Thread was not found.
+ * @param Thread Thread id which structure is to be returned.
+ */
+DECLHIDDEN(PRTTHREADINT) rtThreadGet(RTTHREAD Thread)
+{
+ if ( Thread != NIL_RTTHREAD
+ && RT_VALID_PTR(Thread))
+ {
+ PRTTHREADINT pThread = (PRTTHREADINT)Thread;
+ if ( pThread->u32Magic == RTTHREADINT_MAGIC
+ && pThread->cRefs > 0)
+ {
+ ASMAtomicIncU32(&pThread->cRefs);
+ return pThread;
+ }
+ }
+
+ AssertMsgFailed(("Thread=%RTthrd\n", Thread));
+ return NULL;
+}
+
+/**
+ * Release a per thread data structure.
+ *
+ * @returns New reference count.
+ * @param pThread The thread structure to release.
+ */
+DECLHIDDEN(uint32_t) rtThreadRelease(PRTTHREADINT pThread)
+{
+ uint32_t cRefs;
+
+ Assert(pThread);
+ if (pThread->cRefs >= 1)
+ {
+ cRefs = ASMAtomicDecU32(&pThread->cRefs);
+ if (!cRefs)
+ rtThreadDestroy(pThread);
+ }
+ else
+ {
+ cRefs = 0;
+ AssertFailed();
+ }
+ return cRefs;
+}
+
+
+/**
+ * Destroys the per thread data.
+ *
+ * @param pThread The thread to destroy.
+ */
+static void rtThreadDestroy(PRTTHREADINT pThread)
+{
+ RTSEMEVENTMULTI hEvt1, hEvt2;
+ /*
+ * Remove it from the tree and mark it as dead.
+ *
+ * Threads that has seen rtThreadTerminate and should already have been
+ * removed from the tree. There is probably no thread that should
+ * require removing here. However, be careful making sure that cRefs
+ * isn't 0 if we do or we'll blow up because the strict locking code
+ * will be calling us back.
+ */
+ if (ASMBitTest(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE_BIT))
+ {
+ ASMAtomicIncU32(&pThread->cRefs);
+ rtThreadRemove(pThread);
+ ASMAtomicDecU32(&pThread->cRefs);
+ }
+
+ /*
+ * Invalidate the thread structure.
+ */
+#ifdef IN_RING3
+ rtLockValidatorSerializeDestructEnter();
+
+ rtLockValidatorDeletePerThread(&pThread->LockValidator);
+#endif
+#ifdef RT_WITH_ICONV_CACHE
+ rtStrIconvCacheDestroy(pThread);
+#endif
+ ASMAtomicXchgU32(&pThread->u32Magic, RTTHREADINT_MAGIC_DEAD);
+ ASMAtomicWritePtr(&pThread->Core.Key, (void *)NIL_RTTHREAD);
+ pThread->enmType = RTTHREADTYPE_INVALID;
+ hEvt1 = pThread->EventUser;
+ pThread->EventUser = NIL_RTSEMEVENTMULTI;
+ hEvt2 = pThread->EventTerminated;
+ pThread->EventTerminated = NIL_RTSEMEVENTMULTI;
+
+#ifdef IN_RING3
+ rtLockValidatorSerializeDestructLeave();
+#endif
+
+ /*
+ * Destroy semaphore resources and free the bugger.
+ */
+ RTSemEventMultiDestroy(hEvt1);
+ if (hEvt2 != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiDestroy(hEvt2);
+
+ rtThreadNativeDestroy(pThread);
+ RTMemFree(pThread);
+}
+
+
+/**
+ * Terminates the thread.
+ * Called by the thread wrapper function when the thread terminates.
+ *
+ * @param pThread The thread structure.
+ * @param rc The thread result code.
+ */
+DECLHIDDEN(void) rtThreadTerminate(PRTTHREADINT pThread, int rc)
+{
+ Assert(pThread->cRefs >= 1);
+
+ /*
+ * Destroy TLS entries.
+ */
+#ifdef IPRT_WITH_GENERIC_TLS
+ rtThreadTlsDestruction(pThread);
+#elif defined(RT_OS_WINDOWS) && defined(IN_RING3)
+ rtThreadWinTlsDestruction();
+#endif
+
+ /*
+ * Set the rc, mark it terminated and signal anyone waiting.
+ */
+ pThread->rc = rc;
+ rtThreadSetState(pThread, RTTHREADSTATE_TERMINATED);
+ ASMAtomicOrU32(&pThread->fIntFlags, RTTHREADINT_FLAGS_TERMINATED);
+ if (pThread->EventTerminated != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(pThread->EventTerminated);
+
+ /*
+ * Remove the thread from the tree so that there will be no
+ * key clashes in the AVL tree and release our reference to ourself.
+ */
+ rtThreadRemove(pThread);
+
+#if defined(IPRT_NO_CRT) && defined(IN_RING3)
+ RTTLS const iTlsPerThread = g_iTlsRtNoCrtPerThread;
+ if ( iTlsPerThread != NIL_RTTLS
+ && RTTlsGet(iTlsPerThread) == &pThread->NoCrt)
+ RTTlsSet(iTlsPerThread, &g_RtNoCrtPerThreadDummy);
+#endif
+
+ rtThreadRelease(pThread);
+}
+
+
+/**
+ * The common thread main function.
+ * This is called by rtThreadNativeMain().
+ *
+ * @returns The status code of the thread.
+ * pThread is dereference by the thread before returning!
+ * @param pThread The thread structure.
+ * @param NativeThread The native thread id.
+ * @param pszThreadName The name of the thread (purely a dummy for backtrace).
+ */
+DECL_HIDDEN_CALLBACK(int) rtThreadMain(PRTTHREADINT pThread, RTNATIVETHREAD NativeThread, const char *pszThreadName)
+{
+ int rc;
+ NOREF(pszThreadName);
+ rtThreadInsert(pThread, NativeThread);
+ Log(("rtThreadMain: Starting: pThread=%p NativeThread=%RTnthrd Name=%s pfnThread=%p pvUser=%p\n",
+ pThread, NativeThread, pThread->szName, pThread->pfnThread, pThread->pvUser));
+
+ /*
+ * Change the priority.
+ */
+ rc = rtThreadNativeSetPriority(pThread, pThread->enmType);
+#ifdef IN_RING3
+ AssertMsgRC(rc, ("Failed to set priority of thread %p (%RTnthrd / %s) to enmType=%d enmPriority=%d rc=%Rrc\n",
+ pThread, NativeThread, pThread->szName, pThread->enmType, g_enmProcessPriority, rc));
+#else
+ AssertMsgRC(rc, ("Failed to set priority of thread %p (%RTnthrd / %s) to enmType=%d rc=%Rrc\n",
+ pThread, NativeThread, pThread->szName, pThread->enmType, rc));
+#endif
+
+ /*
+ * Call thread function and terminate when it returns.
+ */
+ rtThreadSetState(pThread, RTTHREADSTATE_RUNNING);
+ rc = pThread->pfnThread(pThread, pThread->pvUser);
+
+ /*
+ * Paranoia checks for leftover resources.
+ */
+#ifdef RTSEMRW_STRICT
+ int32_t cWrite = ASMAtomicReadS32(&pThread->cWriteLocks);
+ Assert(!cWrite);
+ int32_t cRead = ASMAtomicReadS32(&pThread->cReadLocks);
+ Assert(!cRead);
+#endif
+
+ Log(("rtThreadMain: Terminating: rc=%d pThread=%p NativeThread=%RTnthrd Name=%s pfnThread=%p pvUser=%p\n",
+ rc, pThread, NativeThread, pThread->szName, pThread->pfnThread, pThread->pvUser));
+ rtThreadTerminate(pThread, rc);
+ return rc;
+}
+
+
+RTDECL(int) RTThreadCreate(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack,
+ RTTHREADTYPE enmType, unsigned fFlags, const char *pszName)
+{
+ int rc;
+ PRTTHREADINT pThreadInt;
+
+ LogFlow(("RTThreadCreate: pThread=%p pfnThread=%p pvUser=%p cbStack=%#x enmType=%d fFlags=%#x pszName=%p:{%s}\n",
+ pThread, pfnThread, pvUser, cbStack, enmType, fFlags, pszName, pszName));
+
+ /*
+ * Validate input.
+ */
+ AssertPtrNullReturn(pThread, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnThread, VERR_INVALID_POINTER);
+ AssertMsgReturn(pszName && *pszName != '\0' && strlen(pszName) < RTTHREAD_NAME_LEN,
+ ("pszName=%s (max len is %d because of logging)\n", pszName, RTTHREAD_NAME_LEN - 1),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(!(fFlags & ~RTTHREADFLAGS_MASK), ("fFlags=%#x\n", fFlags), VERR_INVALID_FLAGS);
+
+ /*
+ * Allocate thread argument.
+ */
+ pThreadInt = rtThreadAlloc(enmType, fFlags, 0, pszName);
+ if (pThreadInt)
+ {
+ RTNATIVETHREAD NativeThread;
+
+ pThreadInt->pfnThread = pfnThread;
+ pThreadInt->pvUser = pvUser;
+ pThreadInt->cbStack = cbStack;
+
+ rc = rtThreadNativeCreate(pThreadInt, &NativeThread);
+ if (RT_SUCCESS(rc))
+ {
+ rtThreadInsert(pThreadInt, NativeThread);
+ rtThreadRelease(pThreadInt);
+ Log(("RTThreadCreate: Created thread %p (%p) %s\n", pThreadInt, NativeThread, pszName));
+ if (pThread)
+ *pThread = pThreadInt;
+ return VINF_SUCCESS;
+ }
+
+ pThreadInt->cRefs = 1;
+ rtThreadRelease(pThreadInt);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ LogFlow(("RTThreadCreate: Failed to create thread, rc=%Rrc\n", rc));
+ AssertReleaseRC(rc);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadCreate);
+
+
+RTDECL(int) RTThreadCreateV(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack,
+ RTTHREADTYPE enmType, uint32_t fFlags, const char *pszNameFmt, va_list va)
+{
+ char szName[RTTHREAD_NAME_LEN * 2];
+ RTStrPrintfV(szName, sizeof(szName), pszNameFmt, va);
+ return RTThreadCreate(pThread, pfnThread, pvUser, cbStack, enmType, fFlags, szName);
+}
+RT_EXPORT_SYMBOL(RTThreadCreateV);
+
+
+RTDECL(int) RTThreadCreateF(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack,
+ RTTHREADTYPE enmType, uint32_t fFlags, const char *pszNameFmt, ...)
+{
+ va_list va;
+ int rc;
+ va_start(va, pszNameFmt);
+ rc = RTThreadCreateV(pThread, pfnThread, pvUser, cbStack, enmType, fFlags, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadCreateF);
+
+
+RTDECL(RTNATIVETHREAD) RTThreadGetNative(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ RTNATIVETHREAD NativeThread = (RTNATIVETHREAD)pThread->Core.Key;
+ rtThreadRelease(pThread);
+ return NativeThread;
+ }
+ return NIL_RTNATIVETHREAD;
+}
+RT_EXPORT_SYMBOL(RTThreadGetNative);
+
+
+RTDECL(RTTHREAD) RTThreadFromNative(RTNATIVETHREAD NativeThread)
+{
+ PRTTHREADINT pThread = rtThreadGetByNative(NativeThread);
+ if (pThread)
+ return pThread;
+ return NIL_RTTHREAD;
+}
+RT_EXPORT_SYMBOL(RTThreadFromNative);
+
+
+RTDECL(const char *) RTThreadSelfName(void)
+{
+ RTTHREAD Thread = RTThreadSelf();
+ if (Thread != NIL_RTTHREAD)
+ {
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ const char *pszName = pThread->szName;
+ rtThreadRelease(pThread);
+ return pszName;
+ }
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTThreadSelfName);
+
+
+RTDECL(const char *) RTThreadGetName(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread;
+ if (Thread == NIL_RTTHREAD)
+ return NULL;
+ pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ const char *szName = pThread->szName;
+ rtThreadRelease(pThread);
+ return szName;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTThreadGetName);
+
+
+RTDECL(int) RTThreadSetName(RTTHREAD Thread, const char *pszName)
+{
+ /*
+ * Validate input.
+ */
+ PRTTHREADINT pThread;
+ size_t cchName = strlen(pszName);
+ if (cchName >= RTTHREAD_NAME_LEN)
+ {
+ AssertMsgFailed(("pszName=%s is too long, max is %d\n", pszName, RTTHREAD_NAME_LEN - 1));
+ return VERR_INVALID_PARAMETER;
+ }
+ pThread = rtThreadGet(Thread);
+ if (!pThread)
+ return VERR_INVALID_HANDLE;
+
+ /*
+ * Update the name.
+ */
+ pThread->szName[cchName] = '\0'; /* paranoia */
+ memcpy(pThread->szName, pszName, cchName);
+ rtThreadRelease(pThread);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTThreadSetName);
+
+
+RTDECL(bool) RTThreadIsMain(RTTHREAD hThread)
+{
+ if (hThread != NIL_RTTHREAD)
+ {
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ bool fRc = !!(pThread->fIntFlags & RTTHREADINT_FLAGS_MAIN);
+ rtThreadRelease(pThread);
+ return fRc;
+ }
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTThreadIsMain);
+
+
+RTDECL(bool) RTThreadIsSelfAlive(void)
+{
+ if (g_frtThreadInitialized)
+ {
+ RTTHREAD hSelf = RTThreadSelf();
+ if (hSelf != NIL_RTTHREAD)
+ {
+ /*
+ * Inspect the thread state. ASSUMES thread state order.
+ */
+ RTTHREADSTATE enmState = rtThreadGetState(hSelf);
+ if ( enmState >= RTTHREADSTATE_RUNNING
+ && enmState <= RTTHREADSTATE_END)
+ return true;
+ }
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTThreadIsSelfAlive);
+
+
+RTDECL(bool) RTThreadIsSelfKnown(void)
+{
+ if (g_frtThreadInitialized)
+ {
+ RTTHREAD hSelf = RTThreadSelf();
+ if (hSelf != NIL_RTTHREAD)
+ return true;
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTThreadIsSelfKnown);
+
+
+RTDECL(bool) RTThreadIsInitialized(void)
+{
+ return g_frtThreadInitialized;
+}
+RT_EXPORT_SYMBOL(RTThreadIsInitialized);
+
+
+RTDECL(int) RTThreadUserSignal(RTTHREAD Thread)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiSignal(pThread->EventUser);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserSignal);
+
+
+RTDECL(int) RTThreadUserWait(RTTHREAD Thread, RTMSINTERVAL cMillies)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiWait(pThread->EventUser, cMillies);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserWait);
+
+
+RTDECL(int) RTThreadUserWaitNoResume(RTTHREAD Thread, RTMSINTERVAL cMillies)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiWaitNoResume(pThread->EventUser, cMillies);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserWaitNoResume);
+
+
+RTDECL(int) RTThreadUserReset(RTTHREAD Thread)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiReset(pThread->EventUser);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserReset);
+
+
+/**
+ * Wait for the thread to terminate.
+ *
+ * @returns iprt status code.
+ * @param Thread The thread to wait for.
+ * @param cMillies The number of milliseconds to wait. Use RT_INDEFINITE_WAIT for
+ * an indefinite wait.
+ * @param prc Where to store the return code of the thread. Optional.
+ * @param fAutoResume Whether or not to resume the wait on VERR_INTERRUPTED.
+ */
+static int rtThreadWait(RTTHREAD Thread, RTMSINTERVAL cMillies, int *prc, bool fAutoResume)
+{
+ int rc = VERR_INVALID_HANDLE;
+ if (Thread != NIL_RTTHREAD)
+ {
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ if (pThread->fFlags & RTTHREADFLAGS_WAITABLE)
+ {
+#if defined(IN_RING3) && defined(RT_OS_WINDOWS)
+ if (RT_LIKELY(rtThreadNativeIsAliveKludge(pThread)))
+#endif
+ {
+ if (fAutoResume)
+ rc = RTSemEventMultiWait(pThread->EventTerminated, cMillies);
+ else
+ rc = RTSemEventMultiWaitNoResume(pThread->EventTerminated, cMillies);
+ }
+#if defined(IN_RING3) && defined(RT_OS_WINDOWS)
+ else
+ {
+ rc = VINF_SUCCESS;
+ if (pThread->rc == VERR_PROCESS_RUNNING)
+ pThread->rc = VERR_THREAD_IS_DEAD;
+ }
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ if (prc)
+ *prc = pThread->rc;
+
+ /*
+ * If the thread is marked as waitable, we'll do one additional
+ * release in order to free up the thread structure (see how we
+ * init cRef in rtThreadAlloc()).
+ */
+ if (ASMAtomicBitTestAndClear(&pThread->fFlags, RTTHREADFLAGS_WAITABLE_BIT))
+ {
+ rtThreadRelease(pThread);
+#ifdef IN_RING0
+ /*
+ * IPRT termination kludge. Call native code to make sure
+ * the last thread is really out of IPRT to prevent it from
+ * crashing after we destroyed the spinlock in rtThreadTerm.
+ */
+ if ( ASMAtomicReadU32(&g_cThreadInTree) == 1
+ && ASMAtomicReadU32(&pThread->cRefs) > 1)
+ rtThreadNativeWaitKludge(pThread);
+#endif
+ }
+ }
+ }
+ else
+ {
+ rc = VERR_THREAD_NOT_WAITABLE;
+ AssertRC(rc);
+ }
+ rtThreadRelease(pThread);
+ }
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTThreadWait(RTTHREAD Thread, RTMSINTERVAL cMillies, int *prc)
+{
+ int rc = rtThreadWait(Thread, cMillies, prc, true);
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadWait);
+
+
+RTDECL(int) RTThreadWaitNoResume(RTTHREAD Thread, RTMSINTERVAL cMillies, int *prc)
+{
+ return rtThreadWait(Thread, cMillies, prc, false);
+}
+RT_EXPORT_SYMBOL(RTThreadWaitNoResume);
+
+
+RTDECL(int) RTThreadSetType(RTTHREAD Thread, RTTHREADTYPE enmType)
+{
+ /*
+ * Validate input.
+ */
+ int rc;
+ if ( enmType > RTTHREADTYPE_INVALID
+ && enmType < RTTHREADTYPE_END)
+ {
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ if (rtThreadIsAlive(pThread))
+ {
+ /*
+ * Do the job.
+ */
+ RT_THREAD_LOCK_RW();
+ rc = rtThreadNativeSetPriority(pThread, enmType);
+ if (RT_SUCCESS(rc))
+ ASMAtomicXchgSize(&pThread->enmType, enmType);
+ RT_THREAD_UNLOCK_RW();
+ if (RT_FAILURE(rc))
+ Log(("RTThreadSetType: failed on thread %p (%s), rc=%Rrc!!!\n", Thread, pThread->szName, rc));
+ }
+ else
+ rc = VERR_THREAD_IS_DEAD;
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ {
+ AssertMsgFailed(("enmType=%d\n", enmType));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadSetType);
+
+
+RTDECL(RTTHREADTYPE) RTThreadGetType(RTTHREAD Thread)
+{
+ RTTHREADTYPE enmType = RTTHREADTYPE_INVALID;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ enmType = pThread->enmType;
+ rtThreadRelease(pThread);
+ }
+ return enmType;
+}
+RT_EXPORT_SYMBOL(RTThreadGetType);
+
+#ifdef IN_RING3
+
+/**
+ * Recalculates scheduling attributes for the default process
+ * priority using the specified priority type for the calling thread.
+ *
+ * The scheduling attributes are targeted at threads and they are protected
+ * by the thread read-write semaphore, that's why RTProc is forwarding the
+ * operation to RTThread.
+ *
+ * @returns iprt status code.
+ * @remarks Will only work for strict builds.
+ */
+int rtThreadDoCalcDefaultPriority(RTTHREADTYPE enmType)
+{
+ RT_THREAD_LOCK_RW();
+ int rc = rtSchedNativeCalcDefaultPriority(enmType);
+ RT_THREAD_UNLOCK_RW();
+ return rc;
+}
+
+
+/**
+ * Thread enumerator - sets the priority of one thread.
+ *
+ * @returns 0 to continue.
+ * @returns !0 to stop. In our case a VERR_ code.
+ * @param pNode The thread node.
+ * @param pvUser The new priority.
+ */
+static DECLCALLBACK(int) rtThreadSetPriorityOne(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)pNode;
+ if (!rtThreadIsAlive(pThread))
+ return VINF_SUCCESS;
+ int rc = rtThreadNativeSetPriority(pThread, pThread->enmType);
+ if (RT_SUCCESS(rc)) /* hide any warnings */
+ return VINF_SUCCESS;
+ NOREF(pvUser);
+ return rc;
+}
+
+
+/**
+ * Attempts to alter the priority of the current process.
+ *
+ * The scheduling attributes are targeted at threads and they are protected
+ * by the thread read-write semaphore, that's why RTProc is forwarding the
+ * operation to RTThread. This operation also involves updating all thread
+ * which is much faster done from RTThread.
+ *
+ * @returns iprt status code.
+ * @param enmPriority The new priority.
+ */
+DECLHIDDEN(int) rtThreadDoSetProcPriority(RTPROCPRIORITY enmPriority)
+{
+ LogFlow(("rtThreadDoSetProcPriority: enmPriority=%d\n", enmPriority));
+
+ /*
+ * First validate that we're allowed by the OS to use all the
+ * scheduling attributes defined by the specified process priority.
+ */
+ RT_THREAD_LOCK_RW();
+ int rc = rtProcNativeSetPriority(enmPriority);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update the priority of existing thread.
+ */
+ rc = RTAvlPVDoWithAll(&g_ThreadTree, true, rtThreadSetPriorityOne, NULL);
+ if (RT_SUCCESS(rc))
+ ASMAtomicXchgSize(&g_enmProcessPriority, enmPriority);
+ else
+ {
+ /*
+ * Failed, restore the priority.
+ */
+ rtProcNativeSetPriority(g_enmProcessPriority);
+ RTAvlPVDoWithAll(&g_ThreadTree, true, rtThreadSetPriorityOne, NULL);
+ }
+ }
+ RT_THREAD_UNLOCK_RW();
+ LogFlow(("rtThreadDoSetProcPriority: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+RTDECL(void) RTThreadBlocking(RTTHREAD hThread, RTTHREADSTATE enmState, bool fReallySleeping)
+{
+ Assert(RTTHREAD_IS_SLEEPING(enmState));
+ PRTTHREADINT pThread = hThread;
+ if (pThread != NIL_RTTHREAD)
+ {
+ Assert(pThread == RTThreadSelf());
+ if (rtThreadGetState(pThread) == RTTHREADSTATE_RUNNING)
+ rtThreadSetState(pThread, enmState);
+ ASMAtomicWriteBool(&pThread->fReallySleeping, fReallySleeping);
+ }
+}
+RT_EXPORT_SYMBOL(RTThreadBlocking);
+
+
+RTDECL(void) RTThreadUnblocked(RTTHREAD hThread, RTTHREADSTATE enmCurState)
+{
+ PRTTHREADINT pThread = hThread;
+ if (pThread != NIL_RTTHREAD)
+ {
+ Assert(pThread == RTThreadSelf());
+ ASMAtomicWriteBool(&pThread->fReallySleeping, false);
+
+ RTTHREADSTATE enmActualState = rtThreadGetState(pThread);
+ if (enmActualState == enmCurState)
+ {
+ rtThreadSetState(pThread, RTTHREADSTATE_RUNNING);
+ if ( pThread->LockValidator.pRec
+ && pThread->LockValidator.enmRecState == enmCurState)
+ ASMAtomicWriteNullPtr(&pThread->LockValidator.pRec);
+ }
+ /* This is a bit ugly... :-/ */
+ else if ( ( enmActualState == RTTHREADSTATE_TERMINATED
+ || enmActualState == RTTHREADSTATE_INITIALIZING)
+ && pThread->LockValidator.pRec)
+ ASMAtomicWriteNullPtr(&pThread->LockValidator.pRec);
+ Assert( pThread->LockValidator.pRec == NULL
+ || RTTHREAD_IS_SLEEPING(enmActualState));
+ }
+}
+RT_EXPORT_SYMBOL(RTThreadUnblocked);
+
+
+RTDECL(RTTHREADSTATE) RTThreadGetState(RTTHREAD hThread)
+{
+ RTTHREADSTATE enmState = RTTHREADSTATE_INVALID;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ enmState = rtThreadGetState(pThread);
+ rtThreadRelease(pThread);
+ }
+ return enmState;
+}
+RT_EXPORT_SYMBOL(RTThreadGetState);
+
+
+RTDECL(RTTHREADSTATE) RTThreadGetReallySleeping(RTTHREAD hThread)
+{
+ RTTHREADSTATE enmState = RTTHREADSTATE_INVALID;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ enmState = rtThreadGetState(pThread);
+ if (!ASMAtomicUoReadBool(&pThread->fReallySleeping))
+ enmState = RTTHREADSTATE_RUNNING;
+ rtThreadRelease(pThread);
+ }
+ return enmState;
+}
+RT_EXPORT_SYMBOL(RTThreadGetReallySleeping);
+
+
+/**
+ * Translate a thread state into a string.
+ *
+ * @returns Pointer to a read-only string containing the state name.
+ * @param enmState The state.
+ */
+RTDECL(const char *) RTThreadStateName(RTTHREADSTATE enmState)
+{
+ switch (enmState)
+ {
+ case RTTHREADSTATE_INVALID: return "INVALID";
+ case RTTHREADSTATE_INITIALIZING: return "INITIALIZING";
+ case RTTHREADSTATE_TERMINATED: return "TERMINATED";
+ case RTTHREADSTATE_RUNNING: return "RUNNING";
+ case RTTHREADSTATE_CRITSECT: return "CRITSECT";
+ case RTTHREADSTATE_EVENT: return "EVENT";
+ case RTTHREADSTATE_EVENT_MULTI: return "EVENT_MULTI";
+ case RTTHREADSTATE_FAST_MUTEX: return "FAST_MUTEX";
+ case RTTHREADSTATE_MUTEX: return "MUTEX";
+ case RTTHREADSTATE_RW_READ: return "RW_READ";
+ case RTTHREADSTATE_RW_WRITE: return "RW_WRITE";
+ case RTTHREADSTATE_SLEEP: return "SLEEP";
+ case RTTHREADSTATE_SPIN_MUTEX: return "SPIN_MUTEX";
+ default: return "UnknownThreadState";
+ }
+}
+RT_EXPORT_SYMBOL(RTThreadStateName);
+
+#endif /* IN_RING3 */
+#ifdef IPRT_WITH_GENERIC_TLS
+
+/**
+ * Thread enumerator - clears a TLS entry.
+ *
+ * @returns 0.
+ * @param pNode The thread node.
+ * @param pvUser The TLS index.
+ */
+static DECLCALLBACK(int) rtThreadClearTlsEntryCallback(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)pNode;
+ RTTLS iTls = (RTTLS)(uintptr_t)pvUser;
+ ASMAtomicWriteNullPtr(&pThread->apvTlsEntries[iTls]);
+ return 0;
+}
+
+
+/**
+ * Helper for the generic TLS implementation that clears a given TLS
+ * entry on all threads.
+ *
+ * @param iTls The TLS entry. (valid)
+ */
+DECLHIDDEN(void) rtThreadClearTlsEntry(RTTLS iTls)
+{
+ RT_THREAD_LOCK_RD();
+ RTAvlPVDoWithAll(&g_ThreadTree, true /* fFromLeft*/, rtThreadClearTlsEntryCallback, (void *)(uintptr_t)iTls);
+ RT_THREAD_UNLOCK_RD();
+}
+
+#endif /* IPRT_WITH_GENERIC_TLS */
+
+
+#if defined(RT_OS_WINDOWS) && defined(IN_RING3)
+
+/**
+ * Thread enumeration callback for RTThreadNameThreads
+ */
+static DECLCALLBACK(int) rtThreadNameThreadCallback(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)pNode;
+ rtThreadNativeInformDebugger(pThread);
+ RT_NOREF_PV(pvUser);
+ return 0;
+}
+
+/**
+ * A function that can be called from the windows debugger to get the names of
+ * all threads when attaching to a process.
+ *
+ * Usage: .call VBoxRT!RTThreadNameThreads()
+ *
+ * @returns 0
+ * @remarks Do not call from source code as it skips locks.
+ */
+extern "C" RTDECL(int) RTThreadNameThreads(void);
+RTDECL(int) RTThreadNameThreads(void)
+{
+ return RTAvlPVDoWithAll(&g_ThreadTree, true /* fFromLeft*/, rtThreadNameThreadCallback, NULL);
+}
+
+#endif
diff --git a/src/VBox/Runtime/common/misc/uri.cpp b/src/VBox/Runtime/common/misc/uri.cpp
new file mode 100644
index 00000000..9545969f
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/uri.cpp
@@ -0,0 +1,1181 @@
+/* $Id: uri.cpp $ */
+/** @file
+ * IPRT - Uniform Resource Identifier handling.
+ */
+
+/*
+ * 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/uri.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Internal magic value we use to check if a RTURIPARSED structure has made it thru RTUriParse. */
+#define RTURIPARSED_MAGIC UINT32_C(0x439e0745)
+
+
+/* General URI format:
+
+ foo://example.com:8042/over/there?name=ferret#nose
+ \_/ \______________/\_________/ \_________/ \__/
+ | | | | |
+ scheme authority path query fragment
+ | _____________________|__
+ / \ / \
+ urn:example:animal:ferret:nose
+*/
+
+
+/**
+ * The following defines characters which have to be % escaped:
+ * control = 00-1F
+ * space = ' '
+ * delims = '<' , '>' , '#' , '%' , '"'
+ * unwise = '{' , '}' , '|' , '\' , '^' , '[' , ']' , '`'
+ */
+#define URI_EXCLUDED(a) \
+ ( ((a) >= 0x0 && (a) <= 0x20) \
+ || ((a) >= 0x5B && (a) <= 0x5E) \
+ || ((a) >= 0x7B && (a) <= 0x7D) \
+ || (a) == '<' || (a) == '>' || (a) == '#' \
+ || (a) == '%' || (a) == '"' || (a) == '`' )
+
+static char *rtUriPercentEncodeN(const char *pszString, size_t cchMax)
+{
+ if (!pszString)
+ return NULL;
+
+ int rc = VINF_SUCCESS;
+
+ size_t cbLen = RT_MIN(strlen(pszString), cchMax);
+ /* The new string can be max 3 times in size of the original string. */
+ char *pszNew = RTStrAlloc(cbLen * 3 + 1);
+ if (!pszNew)
+ return NULL;
+
+ char *pszRes = NULL;
+ size_t iIn = 0;
+ size_t iOut = 0;
+ while (iIn < cbLen)
+ {
+ if (URI_EXCLUDED(pszString[iIn]))
+ {
+ char szNum[3] = { 0, 0, 0 };
+ RTStrFormatU8(&szNum[0], 3, pszString[iIn++], 16, 2, 2, RTSTR_F_CAPITAL | RTSTR_F_ZEROPAD);
+ pszNew[iOut++] = '%';
+ pszNew[iOut++] = szNum[0];
+ pszNew[iOut++] = szNum[1];
+ }
+ else
+ pszNew[iOut++] = pszString[iIn++];
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszNew[iOut] = '\0';
+ if (iOut != iIn)
+ {
+ /* If the source and target strings have different size, recreate
+ * the target string with the correct size. */
+ pszRes = RTStrDupN(pszNew, iOut);
+ RTStrFree(pszNew);
+ }
+ else
+ pszRes = pszNew;
+ }
+ else
+ RTStrFree(pszNew);
+
+ return pszRes;
+}
+
+
+/**
+ * Calculates the encoded string length.
+ *
+ * @returns Number of chars (excluding the terminator).
+ * @param pszString The string to encode.
+ * @param cchMax The maximum string length (e.g. RTSTR_MAX).
+ * @param fEncodeDosSlash Whether to encode DOS slashes or not.
+ */
+static size_t rtUriCalcEncodedLength(const char *pszString, size_t cchMax, bool fEncodeDosSlash)
+{
+ size_t cchEncoded = 0;
+ if (pszString)
+ {
+ size_t cchSrcLeft = RTStrNLen(pszString, cchMax);
+ while (cchSrcLeft-- > 0)
+ {
+ char const ch = *pszString++;
+ if (!URI_EXCLUDED(ch) || (ch == '\\' && !fEncodeDosSlash))
+ cchEncoded += 1;
+ else
+ cchEncoded += 3;
+ }
+ }
+ return cchEncoded;
+}
+
+
+/**
+ * Encodes an URI into a caller allocated buffer.
+ *
+ * @returns IPRT status code.
+ * @param pszString The string to encode.
+ * @param cchMax The maximum string length (e.g. RTSTR_MAX).
+ * @param fEncodeDosSlash Whether to encode DOS slashes or not.
+ * @param pszDst The destination buffer.
+ * @param cbDst The size of the destination buffer.
+ */
+static int rtUriEncodeIntoBuffer(const char *pszString, size_t cchMax, bool fEncodeDosSlash, char *pszDst, size_t cbDst)
+{
+ AssertReturn(pszString, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDst, VERR_INVALID_POINTER);
+
+ /*
+ * We do buffer size checking up front and every time we encode a special
+ * character. That's faster than checking for each char.
+ */
+ size_t cchSrcLeft = RTStrNLen(pszString, cchMax);
+ AssertMsgReturn(cbDst > cchSrcLeft, ("cbDst=%zu cchSrcLeft=%zu\n", cbDst, cchSrcLeft), VERR_BUFFER_OVERFLOW);
+ cbDst -= cchSrcLeft;
+
+ while (cchSrcLeft-- > 0)
+ {
+ char const ch = *pszString++;
+ if (!URI_EXCLUDED(ch) || (ch == '\\' && !fEncodeDosSlash))
+ *pszDst++ = ch;
+ else
+ {
+ AssertReturn(cbDst >= 3, VERR_BUFFER_OVERFLOW); /* 2 extra bytes + zero terminator. */
+ cbDst -= 2;
+
+ *pszDst++ = '%';
+ ssize_t cchTmp = RTStrFormatU8(pszDst, 3, (unsigned char)ch, 16, 2, 2, RTSTR_F_CAPITAL | RTSTR_F_ZEROPAD);
+ Assert(cchTmp == 2); NOREF(cchTmp);
+ pszDst += 2;
+ }
+ }
+
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+}
+
+
+static char *rtUriPercentDecodeN(const char *pszString, size_t cchString)
+{
+ AssertPtrReturn(pszString, NULL);
+ AssertReturn(memchr(pszString, '\0', cchString) == NULL, NULL);
+
+ /*
+ * The new string can only get smaller, so use the input length as a
+ * staring buffer size.
+ */
+ char *pszDecoded = RTStrAlloc(cchString + 1);
+ if (pszDecoded)
+ {
+ /*
+ * Knowing that the pszString itself is valid UTF-8, we only have to
+ * validate the escape sequences.
+ */
+ size_t cchLeft = cchString;
+ char const *pchSrc = pszString;
+ char *pchDst = pszDecoded;
+ while (cchLeft > 0)
+ {
+ const char *pchPct = (const char *)memchr(pchSrc, '%', cchLeft);
+ if (pchPct)
+ {
+ size_t cchBefore = pchPct - pchSrc;
+ if (cchBefore)
+ {
+ memcpy(pchDst, pchSrc, cchBefore);
+ pchDst += cchBefore;
+ pchSrc += cchBefore;
+ cchLeft -= cchBefore;
+ }
+
+ char chHigh, chLow;
+ if ( cchLeft >= 3
+ && RT_C_IS_XDIGIT(chHigh = pchSrc[1])
+ && RT_C_IS_XDIGIT(chLow = pchSrc[2]))
+ {
+ uint8_t b = RT_C_IS_DIGIT(chHigh) ? chHigh - '0' : (chHigh & ~0x20) - 'A' + 10;
+ b <<= 4;
+ b |= RT_C_IS_DIGIT(chLow) ? chLow - '0' : (chLow & ~0x20) - 'A' + 10;
+ *pchDst++ = (char)b;
+ pchSrc += 3;
+ cchLeft -= 3;
+ }
+ else
+ {
+ AssertFailed();
+ *pchDst++ = *pchSrc++;
+ cchLeft--;
+ }
+ }
+ else
+ {
+ memcpy(pchDst, pchSrc, cchLeft);
+ pchDst += cchLeft;
+ pchSrc += cchLeft;
+ cchLeft = 0;
+ break;
+ }
+ }
+
+ *pchDst = '\0';
+
+ /*
+ * If we've got lof space room in the result string, reallocate it.
+ */
+ size_t cchDecoded = pchDst - pszDecoded;
+ Assert(cchDecoded <= cchString);
+ if (cchString - cchDecoded > 64)
+ RTStrRealloc(&pszDecoded, cchDecoded + 1);
+ }
+ return pszDecoded;
+}
+
+
+/**
+ * Calculates the decoded string length.
+ *
+ * @returns Number of chars (excluding the terminator).
+ * @param pszString The string to decode.
+ * @param cchMax The maximum string length (e.g. RTSTR_MAX).
+ */
+static size_t rtUriCalcDecodedLength(const char *pszString, size_t cchMax)
+{
+ size_t cchDecoded;
+ if (pszString)
+ {
+ size_t cchSrcLeft = cchDecoded = RTStrNLen(pszString, cchMax);
+ while (cchSrcLeft-- > 0)
+ {
+ char const ch = *pszString++;
+ if (ch != '%')
+ { /* typical */}
+ else if ( cchSrcLeft >= 2
+ && RT_C_IS_XDIGIT(pszString[0])
+ && RT_C_IS_XDIGIT(pszString[1]))
+ {
+ cchDecoded -= 2;
+ pszString += 2;
+ cchSrcLeft -= 2;
+ }
+ }
+ }
+ else
+ cchDecoded = 0;
+ return cchDecoded;
+}
+
+
+/**
+ * Decodes a string into a buffer.
+ *
+ * @returns IPRT status code.
+ * @param pchSrc The source string.
+ * @param cchSrc The max number of bytes to decode in the source string.
+ * @param pszDst The destination buffer.
+ * @param cbDst The size of the buffer (including terminator).
+ */
+static int rtUriDecodeIntoBuffer(const char *pchSrc, size_t cchSrc, char *pszDst, size_t cbDst)
+{
+ AssertPtrReturn(pchSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDst, VERR_INVALID_POINTER);
+
+ /*
+ * Knowing that the pszString itself is valid UTF-8, we only have to
+ * validate the escape sequences.
+ */
+ cchSrc = RTStrNLen(pchSrc, cchSrc);
+ while (cchSrc > 0)
+ {
+ const char *pchPct = (const char *)memchr(pchSrc, '%', cchSrc);
+ if (pchPct)
+ {
+ size_t cchBefore = pchPct - pchSrc;
+ AssertReturn(cchBefore + 1 < cbDst, VERR_BUFFER_OVERFLOW);
+ if (cchBefore)
+ {
+ memcpy(pszDst, pchSrc, cchBefore);
+ pszDst += cchBefore;
+ cbDst -= cchBefore;
+ pchSrc += cchBefore;
+ cchSrc -= cchBefore;
+ }
+
+ char chHigh, chLow;
+ if ( cchSrc >= 3
+ && RT_C_IS_XDIGIT(chHigh = pchSrc[1])
+ && RT_C_IS_XDIGIT(chLow = pchSrc[2]))
+ {
+ uint8_t b = RT_C_IS_DIGIT(chHigh) ? chHigh - '0' : (chHigh & ~0x20) - 'A' + 10;
+ b <<= 4;
+ b |= RT_C_IS_DIGIT(chLow) ? chLow - '0' : (chLow & ~0x20) - 'A' + 10;
+ *pszDst++ = (char)b;
+ pchSrc += 3;
+ cchSrc -= 3;
+ }
+ else
+ {
+ AssertFailed();
+ *pszDst++ = *pchSrc++;
+ cchSrc--;
+ }
+ cbDst -= 1;
+ }
+ else
+ {
+ AssertReturn(cchSrc < cbDst, VERR_BUFFER_OVERFLOW);
+ memcpy(pszDst, pchSrc, cchSrc);
+ pszDst += cchSrc;
+ cbDst -= cchSrc;
+ pchSrc += cchSrc;
+ cchSrc = 0;
+ break;
+ }
+ }
+
+ AssertReturn(cbDst > 0, VERR_BUFFER_OVERFLOW);
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+}
+
+
+
+static int rtUriParse(const char *pszUri, PRTURIPARSED pParsed)
+{
+ /*
+ * Validate the input and clear the output.
+ */
+ AssertPtrReturn(pParsed, VERR_INVALID_POINTER);
+ RT_ZERO(*pParsed);
+ pParsed->uAuthorityPort = UINT32_MAX;
+
+ AssertPtrReturn(pszUri, VERR_INVALID_POINTER);
+
+ size_t const cchUri = strlen(pszUri);
+ if (RT_LIKELY(cchUri >= 3)) { /* likely */ }
+ else return cchUri ? VERR_URI_TOO_SHORT : VERR_URI_EMPTY;
+
+ /*
+ * Validating escaped text sequences is much simpler if we know that
+ * that the base URI string is valid. Also, we don't necessarily trust
+ * the developer calling us to remember to do this.
+ */
+ int rc = RTStrValidateEncoding(pszUri);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * RFC-3986, section 3.1:
+ * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ *
+ * The scheme ends with a ':', which we also skip here.
+ */
+ size_t off = 0;
+ char ch = pszUri[off++];
+ if (RT_LIKELY(RT_C_IS_ALPHA(ch))) { /* likely */ }
+ else return VERR_URI_INVALID_SCHEME;
+ for (;;)
+ {
+ ch = pszUri[off];
+ if (ch == ':')
+ break;
+ if (RT_LIKELY(RT_C_IS_ALNUM(ch) || ch == '.' || ch == '-' || ch == '+')) { /* likely */ }
+ else return VERR_URI_INVALID_SCHEME;
+ off++;
+ }
+ pParsed->cchScheme = off;
+
+ /* Require the scheme length to be at least two chars so we won't confuse
+ it with a path starting with a DOS drive letter specification. */
+ if (RT_LIKELY(off >= 2)) { /* likely */ }
+ else return VERR_URI_INVALID_SCHEME;
+
+ off++; /* (skip colon) */
+
+ /*
+ * Find the end of the path, we'll need this several times.
+ * Also, while we're potentially scanning the whole thing, check for '%'.
+ */
+ size_t const offHash = RTStrOffCharOrTerm(&pszUri[off], '#') + off;
+ size_t const offQuestionMark = RTStrOffCharOrTerm(&pszUri[off], '?') + off;
+
+ if (memchr(pszUri, '%', cchUri) != NULL)
+ pParsed->fFlags |= RTURIPARSED_F_CONTAINS_ESCAPED_CHARS;
+
+ /*
+ * RFC-3986, section 3.2:
+ * The authority component is preceeded by a double slash ("//")...
+ */
+ if ( pszUri[off] == '/'
+ && pszUri[off + 1] == '/')
+ {
+ off += 2;
+ pParsed->offAuthority = pParsed->offAuthorityUsername = pParsed->offAuthorityPassword = pParsed->offAuthorityHost = off;
+ pParsed->fFlags |= RTURIPARSED_F_HAS_AUTHORITY;
+
+ /*
+ * RFC-3986, section 3.2:
+ * ...and is terminated by the next slash ("/"), question mark ("?"),
+ * or number sign ("#") character, or by the end of the URI.
+ */
+ const char *pszAuthority = &pszUri[off];
+ size_t cchAuthority = RTStrOffCharOrTerm(pszAuthority, '/');
+ cchAuthority = RT_MIN(cchAuthority, offHash - off);
+ cchAuthority = RT_MIN(cchAuthority, offQuestionMark - off);
+ pParsed->cchAuthority = cchAuthority;
+
+ /* The Authority can be empty, like for: file:///usr/bin/grep */
+ if (cchAuthority > 0)
+ {
+ pParsed->cchAuthorityHost = cchAuthority;
+
+ /*
+ * If there is a userinfo part, it is ended by a '@'.
+ */
+ const char *pszAt = (const char *)memchr(pszAuthority, '@', cchAuthority);
+ if (pszAt)
+ {
+ size_t cchTmp = pszAt - pszAuthority;
+ pParsed->offAuthorityHost += cchTmp + 1;
+ pParsed->cchAuthorityHost -= cchTmp + 1;
+
+ /* If there is a password part, it's separated from the username with a colon. */
+ const char *pszColon = (const char *)memchr(pszAuthority, ':', cchTmp);
+ if (pszColon)
+ {
+ pParsed->cchAuthorityUsername = pszColon - pszAuthority;
+ pParsed->offAuthorityPassword = &pszColon[1] - pszUri;
+ pParsed->cchAuthorityPassword = pszAt - &pszColon[1];
+ }
+ else
+ {
+ pParsed->cchAuthorityUsername = cchTmp;
+ pParsed->offAuthorityPassword = off + cchTmp;
+ }
+ }
+
+ /*
+ * If there is a port part, its after the last colon in the host part.
+ */
+ const char *pszColon = (const char *)memrchr(&pszUri[pParsed->offAuthorityHost], ':', pParsed->cchAuthorityHost);
+ if (pszColon)
+ {
+ size_t cchTmp = &pszUri[pParsed->offAuthorityHost + pParsed->cchAuthorityHost] - &pszColon[1];
+ pParsed->cchAuthorityHost -= cchTmp + 1;
+ pParsed->fFlags |= RTURIPARSED_F_HAS_PORT;
+ if (cchTmp > 0)
+ {
+ pParsed->uAuthorityPort = 0;
+ while (cchTmp-- > 0)
+ {
+ ch = *++pszColon;
+ if ( RT_C_IS_DIGIT(ch)
+ && pParsed->uAuthorityPort < UINT32_MAX / UINT32_C(10))
+ {
+ pParsed->uAuthorityPort *= 10;
+ pParsed->uAuthorityPort += ch - '0';
+ }
+ else
+ return VERR_URI_INVALID_PORT_NUMBER;
+ }
+ }
+ }
+ }
+
+ /* Skip past the authority. */
+ off += cchAuthority;
+ }
+ else
+ pParsed->offAuthority = pParsed->offAuthorityUsername = pParsed->offAuthorityPassword = pParsed->offAuthorityHost = off;
+
+ /*
+ * RFC-3986, section 3.3: Path
+ * The path is terminated by the first question mark ("?")
+ * or number sign ("#") character, or by the end of the URI.
+ */
+ pParsed->offPath = off;
+ pParsed->cchPath = RT_MIN(offHash, offQuestionMark) - off;
+ off += pParsed->cchPath;
+
+ /*
+ * RFC-3986, section 3.4: Query
+ * The query component is indicated by the first question mark ("?")
+ * character and terminated by a number sign ("#") character or by the
+ * end of the URI.
+ */
+ if ( off == offQuestionMark
+ && off < cchUri)
+ {
+ Assert(pszUri[offQuestionMark] == '?');
+ pParsed->offQuery = ++off;
+ pParsed->cchQuery = offHash - off;
+ off = offHash;
+ }
+ else
+ {
+ Assert(!pszUri[offQuestionMark]);
+ pParsed->offQuery = off;
+ }
+
+ /*
+ * RFC-3986, section 3.5: Fragment
+ * A fragment identifier component is indicated by the presence of a
+ * number sign ("#") character and terminated by the end of the URI.
+ */
+ if ( off == offHash
+ && off < cchUri)
+ {
+ pParsed->offFragment = ++off;
+ pParsed->cchFragment = cchUri - off;
+ }
+ else
+ {
+ Assert(!pszUri[offHash]);
+ pParsed->offFragment = off;
+ }
+
+ /*
+ * If there are any escape sequences, validate them.
+ *
+ * This is reasonably simple as we already know that the string is valid UTF-8
+ * before they get decoded. Thus we only have to validate the escaped sequences.
+ */
+ if (pParsed->fFlags & RTURIPARSED_F_CONTAINS_ESCAPED_CHARS)
+ {
+ const char *pchSrc = (const char *)memchr(pszUri, '%', cchUri);
+ AssertReturn(pchSrc, VERR_INTERNAL_ERROR);
+ do
+ {
+ char szUtf8Seq[8];
+ unsigned cchUtf8Seq = 0;
+ unsigned cchNeeded = 0;
+ size_t cchLeft = &pszUri[cchUri] - pchSrc;
+ do
+ {
+ if (cchLeft >= 3)
+ {
+ char chHigh = pchSrc[1];
+ char chLow = pchSrc[2];
+ if ( RT_C_IS_XDIGIT(chHigh)
+ && RT_C_IS_XDIGIT(chLow))
+ {
+ uint8_t b = RT_C_IS_DIGIT(chHigh) ? chHigh - '0' : (chHigh & ~0x20) - 'A' + 10;
+ b <<= 4;
+ b |= RT_C_IS_DIGIT(chLow) ? chLow - '0' : (chLow & ~0x20) - 'A' + 10;
+
+ if (!(b & 0x80))
+ {
+ /* We don't want the string to be terminated prematurely. */
+ if (RT_LIKELY(b != 0)) { /* likely */ }
+ else return VERR_URI_ESCAPED_ZERO;
+
+ /* Check that we're not expecting more UTF-8 bytes. */
+ if (RT_LIKELY(cchNeeded == 0)) { /* likely */ }
+ else return VERR_URI_MISSING_UTF8_CONTINUATION_BYTE;
+ }
+ /* Are we waiting UTF-8 bytes? */
+ else if (cchNeeded > 0)
+ {
+ if (RT_LIKELY(!(b & 0x40))) { /* likely */ }
+ else return VERR_URI_INVALID_ESCAPED_UTF8_CONTINUATION_BYTE;
+
+ szUtf8Seq[cchUtf8Seq++] = (char)b;
+ if (--cchNeeded == 0)
+ {
+ szUtf8Seq[cchUtf8Seq] = '\0';
+ rc = RTStrValidateEncoding(szUtf8Seq);
+ if (RT_FAILURE(rc))
+ return VERR_URI_ESCAPED_CHARS_NOT_VALID_UTF8;
+ cchUtf8Seq = 0;
+ }
+ }
+ /* Start a new UTF-8 sequence. */
+ else
+ {
+ if ((b & 0xf8) == 0xf0)
+ cchNeeded = 3;
+ else if ((b & 0xf0) == 0xe0)
+ cchNeeded = 2;
+ else if ((b & 0xe0) == 0xc0)
+ cchNeeded = 1;
+ else
+ return VERR_URI_INVALID_ESCAPED_UTF8_LEAD_BYTE;
+ szUtf8Seq[0] = (char)b;
+ cchUtf8Seq = 1;
+ }
+ pchSrc += 3;
+ cchLeft -= 3;
+ }
+ else
+ return VERR_URI_INVALID_ESCAPE_SEQ;
+ }
+ else
+ return VERR_URI_INVALID_ESCAPE_SEQ;
+ } while (cchLeft > 0 && pchSrc[0] == '%');
+
+ /* Check that we're not expecting more UTF-8 bytes. */
+ if (RT_LIKELY(cchNeeded == 0)) { /* likely */ }
+ else return VERR_URI_MISSING_UTF8_CONTINUATION_BYTE;
+
+ /* next */
+ pchSrc = (const char *)memchr(pchSrc, '%', cchLeft);
+ } while (pchSrc);
+ }
+
+ pParsed->u32Magic = RTURIPARSED_MAGIC;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTUriParse(const char *pszUri, PRTURIPARSED pParsed)
+{
+ return rtUriParse(pszUri, pParsed);
+}
+
+
+RTDECL(char *) RTUriParsedScheme(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ return RTStrDupN(pszUri, pParsed->cchScheme);
+}
+
+
+RTDECL(char *) RTUriParsedAuthority(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthority || (pParsed->fFlags & RTURIPARSED_F_HAS_AUTHORITY))
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthority], pParsed->cchAuthority);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedAuthorityUsername(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthorityUsername)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthorityUsername], pParsed->cchAuthorityUsername);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedAuthorityPassword(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthorityPassword)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthorityPassword], pParsed->cchAuthorityPassword);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedAuthorityHost(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthorityHost)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthorityHost], pParsed->cchAuthorityHost);
+ return NULL;
+}
+
+
+RTDECL(uint32_t) RTUriParsedAuthorityPort(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, UINT32_MAX);
+ AssertPtrReturn(pParsed, UINT32_MAX);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, UINT32_MAX);
+ return pParsed->uAuthorityPort;
+}
+
+
+RTDECL(char *) RTUriParsedPath(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchPath)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offPath], pParsed->cchPath);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedQuery(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchQuery)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offQuery], pParsed->cchQuery);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedFragment(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchFragment)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offFragment], pParsed->cchFragment);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriCreate(const char *pszScheme, const char *pszAuthority, const char *pszPath, const char *pszQuery,
+ const char *pszFragment)
+{
+ if (!pszScheme) /* Scheme is minimum requirement */
+ return NULL;
+
+ char *pszResult = 0;
+ char *pszAuthority1 = 0;
+ char *pszPath1 = 0;
+ char *pszQuery1 = 0;
+ char *pszFragment1 = 0;
+
+ do
+ {
+ /* Create the percent encoded strings and calculate the necessary uri
+ * length. */
+ size_t cbSize = strlen(pszScheme) + 1 + 1; /* plus zero byte */
+ if (pszAuthority)
+ {
+ pszAuthority1 = rtUriPercentEncodeN(pszAuthority, RTSTR_MAX);
+ if (!pszAuthority1)
+ break;
+ cbSize += strlen(pszAuthority1) + 2;
+ }
+ if (pszPath)
+ {
+ pszPath1 = rtUriPercentEncodeN(pszPath, RTSTR_MAX);
+ if (!pszPath1)
+ break;
+ cbSize += strlen(pszPath1);
+ }
+ if (pszQuery)
+ {
+ pszQuery1 = rtUriPercentEncodeN(pszQuery, RTSTR_MAX);
+ if (!pszQuery1)
+ break;
+ cbSize += strlen(pszQuery1) + 1;
+ }
+ if (pszFragment)
+ {
+ pszFragment1 = rtUriPercentEncodeN(pszFragment, RTSTR_MAX);
+ if (!pszFragment1)
+ break;
+ cbSize += strlen(pszFragment1) + 1;
+ }
+
+ char *pszTmp = pszResult = (char *)RTStrAlloc(cbSize);
+ if (!pszResult)
+ break;
+ RT_BZERO(pszTmp, cbSize);
+
+ /* Compose the target uri string. */
+ RTStrCatP(&pszTmp, &cbSize, pszScheme);
+ RTStrCatP(&pszTmp, &cbSize, ":");
+ if (pszAuthority1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, "//");
+ RTStrCatP(&pszTmp, &cbSize, pszAuthority1);
+ }
+ if (pszPath1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, pszPath1);
+ }
+ if (pszQuery1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, "?");
+ RTStrCatP(&pszTmp, &cbSize, pszQuery1);
+ }
+ if (pszFragment1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, "#");
+ RTStrCatP(&pszTmp, &cbSize, pszFragment1);
+ }
+ } while (0);
+
+ /* Cleanup */
+ if (pszAuthority1)
+ RTStrFree(pszAuthority1);
+ if (pszPath1)
+ RTStrFree(pszPath1);
+ if (pszQuery1)
+ RTStrFree(pszQuery1);
+ if (pszFragment1)
+ RTStrFree(pszFragment1);
+
+ return pszResult;
+}
+
+
+RTDECL(bool) RTUriIsSchemeMatch(const char *pszUri, const char *pszScheme)
+{
+ AssertPtrReturn(pszUri, false);
+ size_t const cchScheme = strlen(pszScheme);
+ return RTStrNICmp(pszUri, pszScheme, cchScheme) == 0
+ && pszUri[cchScheme] == ':';
+}
+
+
+RTDECL(int) RTUriFileCreateEx(const char *pszPath, uint32_t fPathStyle, char **ppszUri, size_t cbUri, size_t *pcchUri)
+{
+ /*
+ * Validate and adjust input. (RTPathParse check pszPath out for us)
+ */
+ if (pcchUri)
+ {
+ AssertPtrReturn(pcchUri, VERR_INVALID_POINTER);
+ *pcchUri = ~(size_t)0;
+ }
+ AssertPtrReturn(ppszUri, VERR_INVALID_POINTER);
+ AssertReturn(!(fPathStyle & ~RTPATH_STR_F_STYLE_MASK) && fPathStyle != RTPATH_STR_F_STYLE_RESERVED, VERR_INVALID_FLAGS);
+ if (fPathStyle == RTPATH_STR_F_STYLE_HOST)
+ fPathStyle = RTPATH_STYLE;
+
+ /*
+ * Let the RTPath code parse the stuff (no reason to duplicate path parsing
+ * and get it slightly wrong here).
+ */
+ union
+ {
+ RTPATHPARSED ParsedPath;
+ uint8_t abPadding[sizeof(RTPATHPARSED)];
+ } u;
+ int rc = RTPathParse(pszPath, &u.ParsedPath, sizeof(u.ParsedPath), fPathStyle);
+ if (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* Skip leading slashes. */
+ if (u.ParsedPath.fProps & RTPATH_PROP_ROOT_SLASH)
+ {
+ if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+ while (pszPath[0] == '/' || pszPath[0] == '\\')
+ pszPath++;
+ else
+ while (pszPath[0] == '/')
+ pszPath++;
+ }
+ const size_t cchPath = strlen(pszPath);
+
+ /*
+ * Calculate the encoded length and figure destination buffering.
+ */
+ static const char s_szPrefix[] = "file:///";
+ size_t const cchPrefix = sizeof(s_szPrefix) - (u.ParsedPath.fProps & RTPATH_PROP_UNC ? 2 : 1);
+ size_t cchEncoded = rtUriCalcEncodedLength(pszPath, cchPath, fPathStyle != RTPATH_STR_F_STYLE_DOS);
+
+ if (pcchUri)
+ *pcchUri = cchEncoded;
+
+ char *pszDst;
+ char *pszFreeMe = NULL;
+ if (!cbUri || *ppszUri == NULL)
+ {
+ cbUri = RT_MAX(cbUri, cchPrefix + cchEncoded + 1);
+ *ppszUri = pszFreeMe = pszDst = RTStrAlloc(cbUri);
+ AssertReturn(pszDst, VERR_NO_STR_MEMORY);
+ }
+ else if (cchEncoded < cbUri)
+ pszDst = *ppszUri;
+ else
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Construct the URI.
+ */
+ memcpy(pszDst, s_szPrefix, cchPrefix);
+ pszDst[cchPrefix] = '\0';
+ rc = rtUriEncodeIntoBuffer(pszPath, cchPath, fPathStyle != RTPATH_STR_F_STYLE_DOS, &pszDst[cchPrefix], cbUri - cchPrefix);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(strlen(pszDst) == cbUri - 1);
+ if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+ RTPathChangeToUnixSlashes(pszDst, true /*fForce*/);
+ return VINF_SUCCESS;
+ }
+
+ AssertRC(rc); /* Impossible! rtUriCalcEncodedLength or something above is busted! */
+ if (pszFreeMe)
+ RTStrFree(pszFreeMe);
+ }
+ return rc;
+}
+
+
+RTDECL(char *) RTUriFileCreate(const char *pszPath)
+{
+ char *pszUri = NULL;
+ int rc = RTUriFileCreateEx(pszPath, RTPATH_STR_F_STYLE_HOST, &pszUri, 0 /*cbUri*/, NULL /*pcchUri*/);
+ if (RT_SUCCESS(rc))
+ return pszUri;
+ return NULL;
+}
+
+
+RTDECL(int) RTUriFilePathEx(const char *pszUri, uint32_t fPathStyle, char **ppszPath, size_t cbPath, size_t *pcchPath)
+{
+ /*
+ * Validate and adjust input.
+ */
+ if (pcchPath)
+ {
+ AssertPtrReturn(pcchPath, VERR_INVALID_POINTER);
+ *pcchPath = ~(size_t)0;
+ }
+ AssertPtrReturn(ppszPath, VERR_INVALID_POINTER);
+ AssertReturn(!(fPathStyle & ~RTPATH_STR_F_STYLE_MASK) && fPathStyle != RTPATH_STR_F_STYLE_RESERVED, VERR_INVALID_FLAGS);
+ if (fPathStyle == RTPATH_STR_F_STYLE_HOST)
+ fPathStyle = RTPATH_STYLE;
+ AssertPtrReturn(pszUri, VERR_INVALID_POINTER);
+
+ /*
+ * Check that this is a file URI.
+ */
+ if (RTStrNICmp(pszUri, RT_STR_TUPLE("file:")) == 0)
+ { /* likely */ }
+ else
+ return VERR_URI_NOT_FILE_SCHEME;
+
+ /*
+ * We may have a number of variations here, mostly thanks to
+ * various windows software. First the canonical variations:
+ * - file:///C:/Windows/System32/kernel32.dll
+ * - file:///C|/Windows/System32/kernel32.dll
+ * - file:///C:%5CWindows%5CSystem32%5Ckernel32.dll
+ * - file://localhost/C:%5CWindows%5CSystem32%5Ckernel32.dll
+ * - file://cifsserver.dev/systemshare%5CWindows%5CSystem32%5Ckernel32.dll
+ * - file://cifsserver.dev:139/systemshare%5CWindows%5CSystem32%5Ckernel32.dll (not quite sure here, but whatever)
+ *
+ * Legacy variant without any slashes after the schema:
+ * - file:C:/Windows/System32/kernel32.dll
+ * - file:C|/Windows/System32%5Ckernel32.dll
+ * - file:~/.bashrc
+ * \--path-/
+ *
+ * Legacy variant with exactly one slashes after the schema:
+ * - file:/C:/Windows/System32%5Ckernel32.dll
+ * - file:/C|/Windows/System32/kernel32.dll
+ * - file:/usr/bin/env
+ * \---path---/
+ *
+ * Legacy variant with two slashes after the schema and an unescaped DOS path:
+ * - file://C:/Windows/System32\kernel32.dll (**)
+ * - file://C|/Windows/System32\kernel32.dll
+ * \---path---------------------/
+ * -- authority, with ':' as non-working port separator
+ *
+ * Legacy variant with exactly four slashes after the schema and an unescaped DOS path.
+ * - file:////C:/Windows\System32\user32.dll
+ *
+ * Legacy variant with four or more slashes after the schema and an unescaped UNC path:
+ * - file:////cifsserver.dev/systemshare/System32%\kernel32.dll
+ * - file://///cifsserver.dev/systemshare/System32\kernel32.dll
+ * \---path--------------------------------------------/
+ *
+ * The two unescaped variants shouldn't be handed to rtUriParse, which
+ * is good as we cannot actually handle the one marked by (**). So, handle
+ * those two special when parsing.
+ */
+ RTURIPARSED Parsed;
+ int rc;
+ size_t cSlashes = 0;
+ while (pszUri[5 + cSlashes] == '/')
+ cSlashes++;
+ if ( (cSlashes == 2 || cSlashes == 4)
+ && RT_C_IS_ALPHA(pszUri[5 + cSlashes])
+ && (pszUri[5 + cSlashes + 1] == ':' || pszUri[5 + cSlashes + 1] == '|'))
+ {
+ RT_ZERO(Parsed); /* RTURIPARSED_F_CONTAINS_ESCAPED_CHARS is now clear. */
+ Parsed.offPath = 5 + cSlashes;
+ Parsed.cchPath = strlen(&pszUri[Parsed.offPath]);
+ rc = RTStrValidateEncoding(&pszUri[Parsed.offPath]);
+ }
+ else if (cSlashes >= 4)
+ {
+ RT_ZERO(Parsed);
+ Parsed.fFlags = cSlashes > 4 ? RTURIPARSED_F_CONTAINS_ESCAPED_CHARS : 0;
+ Parsed.offPath = 5 + cSlashes - 2;
+ Parsed.cchPath = strlen(&pszUri[Parsed.offPath]);
+ rc = RTStrValidateEncoding(&pszUri[Parsed.offPath]);
+ }
+ else
+ rc = rtUriParse(pszUri, &Parsed);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Ignore localhost as hostname (it's implicit).
+ */
+ static char const s_szLocalhost[] = "localhost";
+ if ( Parsed.cchAuthorityHost == sizeof(s_szLocalhost) - 1U
+ && RTStrNICmp(&pszUri[Parsed.offAuthorityHost], RT_STR_TUPLE(s_szLocalhost)) == 0)
+ {
+ Parsed.cchAuthorityHost = 0;
+ Parsed.cchAuthority = 0;
+ }
+
+ /*
+ * Ignore leading path slash/separator if we detect a DOS drive letter
+ * and we don't have a host name.
+ */
+ if ( Parsed.cchPath >= 3
+ && Parsed.cchAuthorityHost == 0
+ && pszUri[Parsed.offPath] == '/' /* Leading path slash/separator. */
+ && ( pszUri[Parsed.offPath + 2] == ':' /* Colon after drive letter. */
+ || pszUri[Parsed.offPath + 2] == '|') /* Colon alternative. */
+ && RT_C_IS_ALPHA(pszUri[Parsed.offPath + 1]) ) /* Drive letter. */
+ {
+ Parsed.offPath++;
+ Parsed.cchPath--;
+ }
+
+ /*
+ * Calculate the size of the encoded result.
+ *
+ * Since we're happily returning "C:/Windows/System32/kernel.dll"
+ * style paths when the caller requested UNIX style paths, we will
+ * return straight UNC paths too ("//cifsserver/share/dir/file").
+ */
+ size_t cchDecodedHost = 0;
+ size_t cbResult;
+ if (Parsed.fFlags & RTURIPARSED_F_CONTAINS_ESCAPED_CHARS)
+ {
+ cchDecodedHost = rtUriCalcDecodedLength(&pszUri[Parsed.offAuthorityHost], Parsed.cchAuthorityHost);
+ cbResult = cchDecodedHost + rtUriCalcDecodedLength(&pszUri[Parsed.offPath], Parsed.cchPath) + 1;
+ }
+ else
+ {
+ cchDecodedHost = 0;
+ cbResult = Parsed.cchAuthorityHost + Parsed.cchPath + 1;
+ }
+ if (pcchPath)
+ *pcchPath = cbResult - 1;
+ if (cbResult > 1)
+ {
+ /*
+ * Prepare the necessary buffer space for the result.
+ */
+ char *pszDst;
+ char *pszFreeMe = NULL;
+ if (!cbPath || *ppszPath == NULL)
+ {
+ cbPath = RT_MAX(cbPath, cbResult);
+ *ppszPath = pszFreeMe = pszDst = RTStrAlloc(cbPath);
+ AssertReturn(pszDst, VERR_NO_STR_MEMORY);
+ }
+ else if (cbResult <= cbPath)
+ pszDst = *ppszPath;
+ else
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Compose the result.
+ */
+ if (Parsed.fFlags & RTURIPARSED_F_CONTAINS_ESCAPED_CHARS)
+ {
+ rc = rtUriDecodeIntoBuffer(&pszUri[Parsed.offAuthorityHost],Parsed.cchAuthorityHost,
+ pszDst, cchDecodedHost + 1);
+ Assert(RT_SUCCESS(rc) && strlen(pszDst) == cchDecodedHost);
+ if (RT_SUCCESS(rc))
+ rc = rtUriDecodeIntoBuffer(&pszUri[Parsed.offPath], Parsed.cchPath,
+ &pszDst[cchDecodedHost], cbResult - cchDecodedHost);
+ Assert(RT_SUCCESS(rc) && strlen(pszDst) == cbResult - 1);
+ }
+ else
+ {
+ memcpy(pszDst, &pszUri[Parsed.offAuthorityHost], Parsed.cchAuthorityHost);
+ memcpy(&pszDst[Parsed.cchAuthorityHost], &pszUri[Parsed.offPath], Parsed.cchPath);
+ pszDst[cbResult - 1] = '\0';
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert colon DOS driver letter colon alternative.
+ * We do this regardless of the desired path style.
+ */
+ if ( RT_C_IS_ALPHA(pszDst[0])
+ && pszDst[1] == '|')
+ pszDst[1] = ':';
+
+ /*
+ * Fix slashes.
+ */
+ if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+ RTPathChangeToDosSlashes(pszDst, true);
+ else if (fPathStyle == RTPATH_STR_F_STYLE_UNIX)
+ RTPathChangeToUnixSlashes(pszDst, true); /** @todo not quite sure how this actually makes sense... */
+ else
+ AssertFailed();
+ return rc;
+ }
+
+ /* bail out */
+ RTStrFree(pszFreeMe);
+ }
+ else
+ rc = VERR_PATH_ZERO_LENGTH;
+ }
+ return rc;
+}
+
+
+RTDECL(char *) RTUriFilePath(const char *pszUri)
+{
+ char *pszPath = NULL;
+ int rc = RTUriFilePathEx(pszUri, RTPATH_STR_F_STYLE_HOST, &pszPath, 0 /*cbPath*/, NULL /*pcchPath*/);
+ if (RT_SUCCESS(rc))
+ return pszPath;
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/misc/zero-alt.S b/src/VBox/Runtime/common/misc/zero-alt.S
new file mode 100644
index 00000000..f1f52869
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/zero-alt.S
@@ -0,0 +1,118 @@
+/* $Id: zero-alt.S $ */
+/** @file
+ * IPRT - Zero Memory, mach-o version (for arm/sparc).
+ */
+
+/*
+ * 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
+ */
+
+
+#ifdef ASM_FORMAT_MACHO
+; Putting it in the code segment/section for now.
+ .section __TEXT,__text,regular,pure_instructions
+ .section __TEXT,__const
+# define NAME(a) _##a
+#elif defined(ASM_FORMAT_ELF) && (defined(RT_ARCH_ARM64) || defined(RT_ARCH_SPARC) || defined(RT_ARCH_SPARC64))
+/* Putting it in the rodata segment/section for now. */
+ .file "zero-alt.S"
+ .section ".rodata"
+# define NAME(a) a
+# define NEED_HIDDEN
+#else
+# error "PORT ME!"
+#endif
+
+/* 64KB of zero memory with various sized labels. */
+ .globl NAME(g_abRTZeroPage)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZeroPage)
+# endif
+ .type NAME(g_abRTZeroPage),#object
+# if defined(RT_ARCH_SPARC) || defined(RT_ARCH_SPARC64)
+ .size NAME(g_abRTZeroPage),8192
+# else
+ .size NAME(g_abRTZeroPage),4096
+# endif
+#endif
+NAME(g_abRTZeroPage):
+ .globl NAME(g_abRTZero4K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero4K)
+# endif
+ .type NAME(g_abRTZero4K),#object
+ .size NAME(g_abRTZero4K),4096
+#endif
+NAME(g_abRTZero4K):
+ .globl NAME(g_abRTZero8K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero8K)
+# endif
+ .type NAME(g_abRTZero8K),#object
+ .size NAME(g_abRTZero8K),8192
+#endif
+NAME(g_abRTZero8K):
+ .globl NAME(g_abRTZero16K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero16K)
+# endif
+ .type NAME(g_abRTZero16K),#object
+ .size NAME(g_abRTZero16K),16384
+#endif
+NAME(g_abRTZero16K):
+ .globl NAME(g_abRTZero32K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero32K)
+# endif
+ .type NAME(g_abRTZero32K),#object
+ .size NAME(g_abRTZero32K),32768
+#endif
+NAME(g_abRTZero32K):
+ .globl NAME(g_abRTZero64K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero64K)
+# endif
+ .type NAME(g_abRTZero64K),#object
+ .size NAME(g_abRTZero64K),65536
+#endif
+NAME(g_abRTZero64K):
+
+#ifdef ASM_FORMAT_MACHO
+ .space 65536
+#elif defined(ASM_FORMAT_ELF)
+ .skip 65536
+#endif
+
diff --git a/src/VBox/Runtime/common/misc/zero.asm b/src/VBox/Runtime/common/misc/zero.asm
new file mode 100644
index 00000000..d6111c63
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/zero.asm
@@ -0,0 +1,83 @@
+; $Id: zero.asm $
+;; @file
+; IPRT - Zero Memory.
+;
+
+;
+; 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
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+;
+; Section to put this in.
+;
+; - PE/COFF: Unless we put this in an BSS like section, the linker will
+; write out 64KB of zeros. (Tried using .rdata$zz and put it at the end
+; of that section, but the linker did not reduce the RawDataSize.) The
+; assmebler does not let us control the write flag directly, so we emit
+; a linker directive that switches of the write flag for the section.
+;
+; - Fallback: Code section.
+;
+%ifdef ASM_FORMAT_PE
+section .drectve info
+ db '-section:.zero,!W '
+section .zero bss align=4096
+%else
+BEGINCODE
+%endif
+
+;;
+; 64KB of zero memory with various sized labels.
+;
+EXPORTEDNAME_EX g_abRTZeroPage, object
+EXPORTEDNAME_EX g_abRTZero4K, object
+EXPORTEDNAME_EX g_abRTZero8K, object
+EXPORTEDNAME_EX g_abRTZero16K, object
+EXPORTEDNAME_EX g_abRTZero32K, object
+EXPORTEDNAME_EX g_abRTZero64K, object
+%ifdef ASM_FORMAT_PE
+ resb 0x10000
+%else
+ times 0x10000/(16*4) dd 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
+%endif
+%ifdef ASM_FORMAT_ELF
+size g_abRTZeroPage _4K
+size g_abRTZero4K _4K
+size g_abRTZero8K _8K
+size g_abRTZero16K _16K
+size g_abRTZero32K _32K
+size g_abRTZero64K _64K
+%endif
+
diff --git a/src/VBox/Runtime/common/misc/zero.cpp b/src/VBox/Runtime/common/misc/zero.cpp
new file mode 100644
index 00000000..c1a32e1e
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/zero.cpp
@@ -0,0 +1,52 @@
+/* $Id: zero.cpp $ */
+/** @file
+ * IPRT - Zero Memory.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under 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/param.h>
+#include <iprt/zero.h>
+
+uint8_t const g_abRTZeroPage[PAGE_SIZE] = { 0 };
+uint8_t const g_abRTZero4K[_4K] = { 0 };
+uint8_t const g_abRTZero8K[_8K] = { 0 };
+uint8_t const g_abRTZero16K[_16K] = { 0 };
+uint8_t const g_abRTZero32K[_32K] = { 0 };
+uint8_t const g_abRTZero64K[_64K] = { 0 };
+