diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
commit | 4035b1bfb1e5843a539a8b624d21952b756974d1 (patch) | |
tree | f1e9cd5bf548cbc57ff2fddfb2b4aa9ae95587e2 /src/VBox/Runtime/common/misc | |
parent | Initial commit. (diff) | |
download | virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.tar.xz virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.zip |
Adding upstream version 6.1.22-dfsg.upstream/6.1.22-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/common/misc')
46 files changed, 19960 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..bf802adb --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp @@ -0,0 +1,42 @@ +/* $Id: RTAssertMsg1Weak.cpp $ */ +/** @file + * IPRT - RTAssertMsg1Weak. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..3e5ae99d --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp @@ -0,0 +1,45 @@ +/* $Id: RTAssertMsg2.cpp $ */ +/** @file + * IPRT - RTAssertMsg2. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..a94c7d47 --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp @@ -0,0 +1,45 @@ +/* $Id: RTAssertMsg2Add.cpp $ */ +/** @file + * IPRT - RTAssertMsg2Add. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..d95585ac --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp @@ -0,0 +1,45 @@ +/* $Id: RTAssertMsg2AddWeak.cpp $ */ +/** @file + * IPRT - RTAssertMsg2AddWeak. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..4c6c8531 --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp @@ -0,0 +1,40 @@ +/* $Id: RTAssertMsg2AddWeakV.cpp $ */ +/** @file + * IPRT - RTAssertMsg2AddWeakV. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..fb19312e --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp @@ -0,0 +1,45 @@ +/* $Id: RTAssertMsg2Weak.cpp $ */ +/** @file + * IPRT - RTAssertMsg2Weak. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..f70dc2e0 --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp @@ -0,0 +1,40 @@ +/* $Id: RTAssertMsg2WeakV.cpp $ */ +/** @file + * IPRT - RTAssertMsg2WeakV. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..5452aed3 --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp @@ -0,0 +1,354 @@ +/* $Id: RTFileModeToFlags.cpp $ */ +/** @file + * IPRT - RTFileModeToFlags. + */ + +/* + * Copyright (C) 2013-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..925932e3 --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTFileOpenF.cpp @@ -0,0 +1,44 @@ +/* $Id: RTFileOpenF.cpp $ */ +/** @file + * IPRT - RTFileOpenF. + */ + +/* + * Copyright (C) 2010-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..46318a77 --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTFileOpenV.cpp @@ -0,0 +1,48 @@ +/* $Id: RTFileOpenV.cpp $ */ +/** @file + * IPRT - RTFileOpenV. + */ + +/* + * Copyright (C) 2010-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..7d045e1e --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp @@ -0,0 +1,55 @@ +/* $Id: RTMemWipeThoroughly.cpp $ */ +/** @file + * IPRT - RTMemWipeThoroughly. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..7df89039 --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp @@ -0,0 +1,50 @@ +/* $Id: RTSystemFirmwareTypeName.cpp $ */ +/** @file + * IPRT - RTSystemFirmwareTypeName. + */ + +/* + * Copyright (C) 2019-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..ed672abd --- /dev/null +++ b/src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp @@ -0,0 +1,48 @@ +/* $Id: RTSystemIsInsideVM-amd64-x86.cpp $ */ +/** @file + * IPRT - + */ + +/* + * Copyright (C) 2013-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..779893cb --- /dev/null +++ b/src/VBox/Runtime/common/misc/assert.cpp @@ -0,0 +1,353 @@ +/* $Id: assert.cpp $ */ +/** @file + * IPRT - Assertions, common code. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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> +# include <stdio.h> +# ifdef RT_OS_WINDOWS +# include <iprt/win/windows.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 = !IsDebuggerPresent() && !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(LOG_NO_COM) +# if 0 /* Enable this iff you have a COM port and really want this debug info. */ + RTLogComPrintf("\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + pszExpr, pszFile, uLine, pszFunction); +# endif +# endif + + PRTLOGGER pLog = RTLogRelGetDefaultInstance(); + if (pLog) + { + RTLogRelPrintf("\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + pszExpr, pszFile, uLine, pszFunction); +# ifdef IPRT_WITH_ASSERT_STACK + RTLogRelPrintf("Stack :\n%s\n", szStack); +# endif +# ifndef IN_RC /* flushing is done automatically in RC */ + RTLogFlush(pLog); +# endif + } + +# ifndef LOG_ENABLED + if (!pLog) +# endif + { + pLog = RTLogDefaultInstance(); + if (pLog) + { + RTLogPrintf("\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + pszExpr, pszFile, uLine, pszFunction); +# ifdef IPRT_WITH_ASSERT_STACK + RTLogPrintf("Stack :\n%s\n", szStack); +# endif +# ifndef IN_RC /* flushing is done automatically in RC */ + RTLogFlush(pLog); +# endif + } + } + +# ifdef IN_RING3 + /* print to stderr, helps user and gdb debugging. */ + fprintf(stderr, + "\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + VALID_PTR(pszExpr) ? pszExpr : "<none>", + VALID_PTR(pszFile) ? pszFile : "<none>", + uLine, + VALID_PTR(pszFunction) ? pszFunction : ""); +# ifdef IPRT_WITH_ASSERT_STACK + fprintf(stderr, "Stack :\n%s\n", szStack); +# endif + fflush(stderr); +# 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(LOG_NO_COM) +# if 0 /* Enable this iff you have a COM port and really want this debug info. */ + va_copy(vaCopy, va); + RTLogComPrintfV(pszFormat, vaCopy); + va_end(vaCopy); +# endif +# endif + + PRTLOGGER pLog = RTLogRelGetDefaultInstance(); + if (pLog) + { + va_copy(vaCopy, va); + RTLogRelPrintfV(pszFormat, vaCopy); + va_end(vaCopy); +# ifndef IN_RC /* flushing is done automatically in RC */ + RTLogFlush(pLog); +# endif + } + + pLog = RTLogDefaultInstance(); + if (pLog) + { + va_copy(vaCopy, va); + RTLogPrintfV(pszFormat, vaCopy); + va_end(vaCopy); +# ifndef IN_RC /* flushing is done automatically in RC */ + RTLogFlush(pLog); +#endif + } + +# ifdef IN_RING3 + /* print to stderr, helps user and gdb debugging. */ + char szMsg[sizeof(g_szRTAssertMsg2)]; + va_copy(vaCopy, va); + RTStrPrintfV(szMsg, sizeof(szMsg), pszFormat, vaCopy); + va_end(vaCopy); + fprintf(stderr, "%s", szMsg); + fflush(stderr); +# 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..68bf161c --- /dev/null +++ b/src/VBox/Runtime/common/misc/buildconfig.cpp @@ -0,0 +1,142 @@ +/* $Id: buildconfig.cpp $ */ +/** @file + * IPRT - Build Configuration Information. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..8f42b367 --- /dev/null +++ b/src/VBox/Runtime/common/misc/cidr.cpp @@ -0,0 +1,119 @@ +/* $Id: cidr.cpp $ */ +/** @file + * IPRT - IPv4 address parsing. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..cd05884a --- /dev/null +++ b/src/VBox/Runtime/common/misc/circbuf.cpp @@ -0,0 +1,252 @@ +/* $Id: circbuf.cpp $ */ +/** @file + * IPRT - Lock Free Circular Buffer + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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/getopt.cpp b/src/VBox/Runtime/common/misc/getopt.cpp new file mode 100644 index 00000000..4e0d3a8c --- /dev/null +++ b/src/VBox/Runtime/common/misc/getopt.cpp @@ -0,0 +1,856 @@ +/* $Id: getopt.cpp $ */ +/** @file + * IPRT - Command Line Parsing + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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> + + +/********************************************************************************************************************************* +* 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; + + /* validate the options. */ + for (size_t i = 0; i < cOptions; i++) + { + Assert(!(paOptions[i].fFlags & ~RTGETOPT_VALID_MASK)); + Assert(paOptions[i].iShort > 0); + Assert(paOptions[i].iShort != VINF_GETOPT_NOT_OPTION); + Assert(paOptions[i].iShort != '-'); + } + + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTGetOptInit); + + +/** + * 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; +} + + +/** + * 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) + { + if ((pOpt->fFlags & 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) + || ( pOpt->fFlags & RTGETOPT_FLAG_ICASE + && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong))) + { + if (pOpt->fFlags & RTGETOPT_FLAG_INDEX) + while (RT_C_IS_DIGIT(pszOption[cchLong])) + cchLong++; + if ( pszOption[cchLong] == '\0' + || pszOption[cchLong] == ':' + || pszOption[cchLong] == '=') + return pOpt; + } + } + else if (pOpt->fFlags & 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) + || ( pOpt->fFlags & RTGETOPT_FLAG_ICASE + && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong))) + { + while (RT_C_IS_DIGIT(pszOption[cchLong])) + cchLong++; + if (pszOption[cchLong] == '\0') + return pOpt; + } + } + else if ( !strcmp(pszOption, pOpt->pszLong) + || ( pOpt->fFlags & 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 + + 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; + } + + 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 \ + { RTAssertMsg2("z rc=%Rrc: '%s' '%s' uBase=%d\n", rc, pszValue, pszNext, uBase); return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; } \ + } \ + else if (fSwitchValue != (a_fReqValueOptional)) \ + { RTAssertMsg2("x\n"); return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; } \ + } \ + else if (fSwitchValue != (a_fReqValueOptional)) \ + { RTAssertMsg2("y\n"); return 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. */ + + if ((pOpt->fFlags & 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 (pOpt->fFlags & RTGETOPT_FLAG_INDEX) + { + + if (pszArgThis[cchLong] == '\0') + return VERR_GETOPT_INDEX_MISSING; + + uint32_t uIndex; + char *pszRet = NULL; + int rc = RTStrToUInt32Ex(&pszArgThis[cchLong], &pszRet, 10, &uIndex); + 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 + { + 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(pOpt->fFlags, 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 (pOpt->fFlags & RTGETOPT_FLAG_INDEX) + { + size_t cchLong = strlen(pOpt->pszLong); + if (pszArgThis[cchLong] == '\0') + return VERR_GETOPT_INDEX_MISSING; + + uint32_t uIndex; + if (RTStrToUInt32Full(&pszArgThis[cchLong], 10, &uIndex) == VINF_SUCCESS) + pState->uIndex = uIndex; + else + AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */ + } + + 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..f536f59b --- /dev/null +++ b/src/VBox/Runtime/common/misc/getoptargv.cpp @@ -0,0 +1,644 @@ +/* $Id: getoptargv.cpp $ */ +/** @file + * IPRT - Command Line Parsing, Argument Vector. + */ + +/* + * Copyright (C) 2010-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..36ef54cc --- /dev/null +++ b/src/VBox/Runtime/common/misc/handle.cpp @@ -0,0 +1,75 @@ +/* $Id: handle.cpp $ */ +/** @file + * IPRT - Generic Handle Manipulation. + */ + +/* + * Copyright (C) 2010-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..68d65c9b --- /dev/null +++ b/src/VBox/Runtime/common/misc/handletable.cpp @@ -0,0 +1,224 @@ +/* $Id: handletable.cpp $ */ +/** @file + * IPRT - Handle Tables. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..5ef47580 --- /dev/null +++ b/src/VBox/Runtime/common/misc/handletable.h @@ -0,0 +1,247 @@ +/* $Id: handletable.h $ */ +/** @file + * IPRT - Handle Tables, internal header. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#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..755a49c4 --- /dev/null +++ b/src/VBox/Runtime/common/misc/handletablectx.cpp @@ -0,0 +1,329 @@ +/* $Id: handletablectx.cpp $ */ +/** @file + * IPRT - Handle Tables. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..3153194b --- /dev/null +++ b/src/VBox/Runtime/common/misc/handletablesimple.cpp @@ -0,0 +1,304 @@ +/* $Id: handletablesimple.cpp $ */ +/** @file + * IPRT - Handle Tables. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..90e82586 --- /dev/null +++ b/src/VBox/Runtime/common/misc/inifile.cpp @@ -0,0 +1,791 @@ +/* $Id: inifile.cpp $ */ +/** @file + * IPRT - INI-file parser. + */ + +/* + * Copyright (C) 2017-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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; +} + + +/** + * Creates a INI-file instance from a VFS file handle. + * + * @returns IPRT status code + * @param phIniFile Where to return the INI-file handle. + * @param hVfsFile The VFS file handle (not consumed, additional + * reference is retained). + * @param fFlags Flags, RTINIFILE_F_XXX. + */ +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; +} + + +/** + * Retains a reference to an INI-file instance. + * + * @returns New reference count, UINT32_MAX on failure. + * @param hIniFile The INI-file handle. + */ +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; +} + + +/** + * Releases a reference to an INI-file instance, destroying it if the count + * reaches zero. + * + * @returns New reference count, UINT32_MAX on failure. + * @param hIniFile The INI-file handle. NIL is ignored. + */ +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; +} + + +/** + * Queries a value in a section. + * + * The first matching value is returned. The matching is by default case + * insensitive. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if section or key not found. + * + * @param hIniFile The INI-file handle. + * @param pszSection The section name. Pass NULL to refer to the + * unsectioned key space at the top of the file. + * @param pszKey The key name. + * @param pszValue Where to return the value. + * @param cbValue Size of the buffer @a pszValue points to. + * @param pcbActual Where to return the actual value size excluding + * terminator on success. On VERR_BUFFER_OVERFLOW this + * will be set to the buffer size needed to hold the + * value, terminator included. Optional. + */ +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; +} + + +/** + * Queries a key-value pair in a section by ordinal. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if the section wasn't found or if it contains no pair + * with the given ordinal value. + * + * @param hIniFile The INI-file handle. + * @param pszSection The section name. Pass NULL to refer to the + * unsectioned key space at the top of the file. + * @param idxPair The pair to fetch (counting from 0). + * + * @param pszKey Where to return the key name. + * @param cbKey Size of the buffer @a pszKey points to. + * @param pcbKeyActual Where to return the actual key size excluding + * terminator on success. On VERR_BUFFER_OVERFLOW this + * will be set to the buffer size needed to hold the + * value, terminator included. Optional. + * + * @param pszValue Where to return the value. + * @param cbValue Size of the buffer @a pszValue points to. + * @param pcbValueActual Where to return the actual value size excluding + * terminator on success. On VERR_BUFFER_OVERFLOW this + * will be set to the buffer size needed to hold the + * value, terminator included. Optional. + */ +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..b5453aa5 --- /dev/null +++ b/src/VBox/Runtime/common/misc/json.cpp @@ -0,0 +1,1869 @@ +/* $Id: json.cpp $ */ +/** @file + * IPRT JSON parser API (JSON). + */ + +/* + * Copyright (C) 2016-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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 <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 succcess. + */ +typedef DECLCALLBACK(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; + } 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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; +} + +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(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..5ed05ea1 --- /dev/null +++ b/src/VBox/Runtime/common/misc/lockvalidator.cpp @@ -0,0 +1,4472 @@ +/* $Id: lockvalidator.cpp $ */ +/** @file + * IPRT - Lock Validator. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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 (!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 (!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 (!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 ( 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 ( 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 ( 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 ( 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; 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, VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>"); + else + RTAssertMsg2Weak("%s [thrd=%s]\n", pszWhat, 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->pszFile ? 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 (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( VALID_PTR(pSharedRec) + && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) + return pSharedRec->hClass; + return NIL_RTLOCKVALCLASS; + } + + case RTLOCKVALRECNEST_MAGIC: + { + PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec; + if (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( 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( 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 (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( 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( 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 (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( 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 (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)); + 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 (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 (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 = 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 = 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 (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 = 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 = 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..ca6daf27 --- /dev/null +++ b/src/VBox/Runtime/common/misc/message.cpp @@ -0,0 +1,237 @@ +/* $Id: message.cpp $ */ +/** @file + * IPRT - Error reporting to standard error. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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(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..5e6cfc40 --- /dev/null +++ b/src/VBox/Runtime/common/misc/messagerefentry.cpp @@ -0,0 +1,322 @@ +/* $Id: messagerefentry.cpp $ */ +/** @file + * IPRT - Program usage and help formatting. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..64d932f2 --- /dev/null +++ b/src/VBox/Runtime/common/misc/once.cpp @@ -0,0 +1,440 @@ +/* $Id: once.cpp $ */ +/** @file + * IPRT - Execute Once. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..2d965399 --- /dev/null +++ b/src/VBox/Runtime/common/misc/req.cpp @@ -0,0 +1,476 @@ +/* $Id: req.cpp $ */ +/** @file + * IPRT - Request packets + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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. + */ + switch (pReq->enmState) + { + case RTREQSTATE_ALLOCATED: + case RTREQSTATE_COMPLETED: + break; + default: + AssertMsgFailed(("Invalid state %d!\n", pReq->enmState)); + return 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. Donate the caller's reference if RTREQFLAGS_NO_WAIT is set, + * otherwise retain another reference for the queue. + */ + pReq->uSubmitNanoTs = RTTimeNanoTS(); + pReq->enmState = RTREQSTATE_QUEUED; + unsigned fFlags = ((RTREQ volatile *)pReq)->fFlags; /* volatile paranoia */ + if (!(fFlags & RTREQFLAGS_NO_WAIT)) + 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); + AssertMsgReturn( pReq->enmState == RTREQSTATE_QUEUED + || pReq->enmState == RTREQSTATE_PROCESSING + || pReq->enmState == RTREQSTATE_COMPLETED, + ("Invalid state %d\n", pReq->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) + ASMAtomicXchgSize(&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) 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)); + + /* + * Process the request. + */ + Assert(pReq->enmState == RTREQSTATE_QUEUED); + pReq->enmState = RTREQSTATE_PROCESSING; + int rcRet = VINF_SUCCESS; /* the return code of this function. */ + int rcReq = VERR_NOT_IMPLEMENTED; /* the request status. */ + 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; + } + + /* + * 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)); + ASMAtomicXchgSize(&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..a11eb29d --- /dev/null +++ b/src/VBox/Runtime/common/misc/reqpool.cpp @@ -0,0 +1,1213 @@ +/* $Id: reqpool.cpp $ */ +/** @file + * IPRT - Request Pool. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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 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; + + /** 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 ((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, 0 /*fFlags*/, "%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; +} + + +/** + * 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->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->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_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_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_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)) + { + 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 (!(fFlags & RTREQFLAGS_NO_WAIT)) + { + *phReq = pReq; + LogFlow(("RTReqPoolCallExV: returns %Rrc *phReq=%p\n", rc, pReq)); + } + else + 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..ae91e033 --- /dev/null +++ b/src/VBox/Runtime/common/misc/reqqueue.cpp @@ -0,0 +1,452 @@ +/* $Id: reqqueue.cpp $ */ +/** @file + * IPRT - Request Queue. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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 = NULL; + } + + 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 (!(fFlags & RTREQFLAGS_NO_WAIT)) + { + *ppReq = pReq; + LogFlow(("RTReqQueueCallV: returns %Rrc *ppReq=%p\n", rc, pReq)); + } + else + 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/s3.cpp b/src/VBox/Runtime/common/misc/s3.cpp new file mode 100644 index 00000000..52b36dcd --- /dev/null +++ b/src/VBox/Runtime/common/misc/s3.cpp @@ -0,0 +1,1019 @@ +/* $Id: s3.cpp $ */ +/** @file + * IPRT - S3 communication API. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/s3.h> +#include "internal/iprt.h" + +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/base64.h> +#include <iprt/file.h> +#include <iprt/stream.h> + +#ifdef RT_OS_WINDOWS /* OpenSSL drags in Windows.h, which isn't compatible with -Wall. */ +# include <iprt/win/windows.h> +#endif +#include <curl/curl.h> +#include <openssl/hmac.h> +#include <libxml/parser.h> + +#include "internal/magics.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTS3INTERNAL +{ + uint32_t u32Magic; + CURL *pCurl; + char *pszAccessKey; + char *pszSecretKey; + char *pszBaseUrl; + char *pszUserAgent; + + PFNRTS3PROGRESS pfnProgressCallback; + void *pvUser; + + long lLastResp; +} RTS3INTERNAL; +typedef RTS3INTERNAL* PRTS3INTERNAL; + +typedef struct RTS3TMPMEMCHUNK +{ + char *pszMem; + size_t cSize; +} RTS3TMPMEMCHUNK; +typedef RTS3TMPMEMCHUNK *PRTS3TMPMEMCHUNK; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTS3_VALID_RETURN_RC(hS3, rc) \ + do { \ + AssertPtrReturn((hS3), (rc)); \ + AssertReturn((hS3)->u32Magic == RTS3_MAGIC, (rc)); \ + } while (0) + +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTS3_VALID_RETURN(hS3) RTS3_VALID_RETURN_RC((hS3), VERR_INVALID_HANDLE) + +/** Validates a handle and returns (void) if not valid. */ +#define RTS3_VALID_RETURN_VOID(hS3) \ + do { \ + AssertPtrReturnVoid(hS3); \ + AssertReturnVoid((hS3)->u32Magic == RTS3_MAGIC); \ + } while (0) + + +/********************************************************************************************************************************* +* Private RTS3 helper * +*********************************************************************************************************************************/ + +static char* rtS3Host(const char* pszBucket, const char* pszKey, const char* pszBaseUrl) +{ + char* pszUrl; + /* Host header entry */ + if (pszBucket[0] == 0) + RTStrAPrintf(&pszUrl, "%s", pszBaseUrl); + else if (pszKey[0] == 0) + RTStrAPrintf(&pszUrl, "%s.%s", pszBucket, pszBaseUrl); + else + RTStrAPrintf(&pszUrl, "%s.%s/%s", pszBucket, pszBaseUrl, pszKey); + return pszUrl; +} + +static char* rtS3HostHeader(const char* pszBucket, const char* pszBaseUrl) +{ + char* pszUrl; + /* Host header entry */ + if (pszBucket[0] != 0) + RTStrAPrintf(&pszUrl, "Host: %s.%s", pszBucket, pszBaseUrl); + else + RTStrAPrintf(&pszUrl, "Host: %s", pszBaseUrl); + return pszUrl; +} + +static char* rtS3DateHeader() +{ + /* Date header entry */ + RTTIMESPEC TimeSpec; + RTTIME Time; + RTTimeExplode(&Time, RTTimeNow(&TimeSpec)); + + static const char s_apszDayNms[7][4] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; + static const char s_apszMonthNms[1+12][4] = + { "???", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + char *pszDate; + RTStrAPrintf(&pszDate, "Date: %s, %02u %s %04d %02u:%02u:%02u UTC", + s_apszDayNms[Time.u8WeekDay], + Time.u8MonthDay, + s_apszMonthNms[Time.u8Month], + Time.i32Year, + Time.u8Hour, + Time.u8Minute, + Time.u8Second); + + return pszDate; +} + +static char* rtS3ParseHeaders(char** ppHeaders, size_t cHeadEnts) +{ + char pszEmpty[] = ""; + char *pszRes = NULL; + char *pszDate = pszEmpty; + char *pszType = pszEmpty; + for(size_t i=0; i < cHeadEnts; ++i) + { + if(ppHeaders[i] != NULL) + { + if (RTStrStr(ppHeaders[i], "Date: ") == ppHeaders[i]) + { + pszDate = &(ppHeaders[i][6]); + } + else if(RTStrStr(ppHeaders[i], "Content-Type: ") == ppHeaders[i]) + { + pszType = &(ppHeaders[i][14]); +// char *pszTmp = RTStrDup (&(ppHeaders[i][14])); +// if (pszRes) +// { +// char *pszTmp1 = pszRes; +// RTStrAPrintf(&pszRes, "%s\n%s", pszRes, pszTmp); +// RTStrFree(pszTmp); +// RTStrFree(pszTmp1); +// } +// else +// pszRes = pszTmp; + } + } + } + RTStrAPrintf(&pszRes, "\n%s\n%s", pszType, pszDate); + return pszRes; +} + +static char* rtS3Canonicalize(const char* pszAction, const char* pszBucket, const char* pszKey, char** papszHeadEnts, size_t cHeadEnts) +{ + char* pszRes; + /* Grep the necessary info out of the headers & put them in a string */ + char* pszHead = rtS3ParseHeaders(papszHeadEnts, cHeadEnts); + /* Create the string which will be used as signature */ + RTStrAPrintf(&pszRes, "%s\n%s\n/", + pszAction, + pszHead); + RTStrFree(pszHead); + /* Add the bucket if the bucket isn't empty */ + if (pszBucket[0] != 0) + { + char* pszTmp = pszRes; + RTStrAPrintf(&pszRes, "%s%s/", pszRes, pszBucket); + RTStrFree(pszTmp); + } + /* Add the key if the key isn't empty. */ + if (pszKey[0] != 0) + { + char* pszTmp = pszRes; + RTStrAPrintf(&pszRes, "%s%s", pszRes, pszKey); + RTStrFree(pszTmp); + } + + return pszRes; +} + +static char* rtS3CreateSignature(PRTS3INTERNAL pS3Int, const char* pszAction, const char* pszBucket, const char* pszKey, + char** papszHeadEnts, size_t cHeadEnts) +{ + /* Create a string we can sign */ + char* pszSig = rtS3Canonicalize(pszAction, pszBucket, pszKey, papszHeadEnts, cHeadEnts); +// printf ("Sig %s\n", pszSig); + /* Sign the string by creating a SHA1 finger print */ + char pszSigEnc[1024]; + unsigned int cSigEnc = sizeof(pszSigEnc); + HMAC(EVP_sha1(), pS3Int->pszSecretKey, (int)strlen(pS3Int->pszSecretKey), + (const unsigned char*)pszSig, strlen(pszSig), + (unsigned char*)pszSigEnc, &cSigEnc); + RTStrFree(pszSig); + /* Convert the signature to Base64 */ + size_t cSigBase64Enc = RTBase64EncodedLength(cSigEnc) + 1; /* +1 for the 0 */ + char *pszSigBase64Enc = (char*)RTMemAlloc(cSigBase64Enc); + size_t cRes; + RTBase64Encode(pszSigEnc, cSigEnc, pszSigBase64Enc, cSigBase64Enc, &cRes); + + return pszSigBase64Enc; +} + +static char* rtS3CreateAuthHeader(PRTS3INTERNAL pS3Int, const char* pszAction, const char* pszBucket, const char* pszKey, + char** papszHeadEnts, size_t cHeadEnts) +{ + char *pszAuth; + /* Create a signature out of the header & the bucket/key info */ + char *pszSigBase64Enc = rtS3CreateSignature(pS3Int, pszAction, pszBucket, pszKey, papszHeadEnts, cHeadEnts); + /* Create the authorization header entry */ + RTStrAPrintf(&pszAuth, "Authorization: AWS %s:%s", + pS3Int->pszAccessKey, + pszSigBase64Enc); + RTStrFree(pszSigBase64Enc); + return pszAuth; +} + +static int rtS3Perform(PRTS3INTERNAL pS3Int) +{ + int rc = VERR_INTERNAL_ERROR; + CURLcode code = curl_easy_perform(pS3Int->pCurl); + if (code == CURLE_OK) + { + curl_easy_getinfo(pS3Int->pCurl, CURLINFO_RESPONSE_CODE, &pS3Int->lLastResp); + switch (pS3Int->lLastResp) + { + case 200: + case 204: rc = VINF_SUCCESS; break; /* No content */ + case 403: rc = VERR_S3_ACCESS_DENIED; break; /* Access denied */ + case 404: rc = VERR_S3_NOT_FOUND; break; /* Site not found */ + } + } + else + { + switch(code) + { + case CURLE_URL_MALFORMAT: + case CURLE_COULDNT_RESOLVE_HOST: +#if defined(CURLE_REMOTE_FILE_NOT_FOUND) + case CURLE_REMOTE_FILE_NOT_FOUND: rc = VERR_S3_NOT_FOUND; break; +#elif defined(CURLE_FILE_COULDNT_READ_FILE) + case CURLE_FILE_COULDNT_READ_FILE: rc = VERR_S3_NOT_FOUND; break; +#endif +#if defined(CURLE_REMOTE_ACCESS_DENIED) + case CURLE_REMOTE_ACCESS_DENIED: rc = VERR_S3_ACCESS_DENIED; break; +#elif defined(CURLE_FTP_ACCESS_DENIED) + case CURLE_FTP_ACCESS_DENIED: rc = VERR_S3_ACCESS_DENIED; break; +#endif + case CURLE_ABORTED_BY_CALLBACK: rc = VERR_S3_CANCELED; break; + default: break; + } + } + return rc; +} + +static size_t rtS3WriteNothingCallback(void *pvBuf, size_t cbItem, size_t cItems, void *pvUser) +{ + NOREF(pvBuf); NOREF(pvUser); + return cbItem * cItems; +} + +static size_t rtS3WriteMemoryCallback(void *pvBuf, size_t cSize, size_t cBSize, void *pvUser) +{ + PRTS3TMPMEMCHUNK pTmpMem = (PRTS3TMPMEMCHUNK)pvUser; + size_t cRSize = cSize * cBSize; + + pTmpMem->pszMem = (char*)RTMemRealloc(pTmpMem->pszMem, pTmpMem->cSize + cRSize + 1); + if (pTmpMem->pszMem) + { + memcpy(&(pTmpMem->pszMem[pTmpMem->cSize]), pvBuf, cRSize); + pTmpMem->cSize += cRSize; + pTmpMem->pszMem[pTmpMem->cSize] = 0; + } + return cRSize; +} + +static size_t rtS3WriteFileCallback(void *pvBuf, size_t cSize, size_t cBSize, void *pvUser) +{ + size_t cWritten; + RTFileWrite(*(RTFILE*)pvUser, pvBuf, cSize * cBSize, &cWritten); + return cWritten; +} + +static size_t rtS3ReadFileCallback(void *pvBuf, size_t cSize, size_t cBSize, void *pvUser) +{ + size_t cRead; + RTFileRead(*(RTFILE*)pvUser, pvBuf, cSize * cBSize, &cRead); + + return cRead; +} + +static int rtS3ProgressCallback(void *pvUser, double dDlTotal, double dDlNow, double dUlTotal, double dUlNow) +{ + if (pvUser) + { + PRTS3INTERNAL pS3Int = (PRTS3INTERNAL)pvUser; + if (pS3Int->pfnProgressCallback) + { + int rc = VINF_SUCCESS; + if (dDlTotal > 0) + rc = pS3Int->pfnProgressCallback((unsigned)(100.0/dDlTotal*dDlNow), pS3Int->pvUser); + else if (dUlTotal > 0) + rc = pS3Int->pfnProgressCallback((unsigned)(100.0/dUlTotal*dUlNow), pS3Int->pvUser); + if (rc != VINF_SUCCESS) + return -1; + } + } + return CURLE_OK; +} + +static void rtS3ReinitCurl(PRTS3INTERNAL pS3Int) +{ + if (pS3Int && + pS3Int->pCurl) + { + /* Reset the CURL object to an defined state */ + curl_easy_reset(pS3Int->pCurl); + /* Make sure HTTP 1.1 is used */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + /* We are cool we are a user agent now */ + if (pS3Int->pszUserAgent) + curl_easy_setopt(pS3Int->pCurl, CURLOPT_USERAGENT, pS3Int->pszUserAgent); + /* Check if the user has a progress callback requested */ + if (pS3Int->pfnProgressCallback) + { + /* Yes, we are willing to receive progress info */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_NOPROGRESS, 0); + /* Callback for the progress info */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_PROGRESSFUNCTION, rtS3ProgressCallback); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_PROGRESSDATA, pS3Int); + } + /* Disable the internal cURL write function by providing one which does + * nothing */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_WRITEFUNCTION, rtS3WriteNothingCallback); + /* Set this do get some verbose info what CURL is doing */ +// curl_easy_setopt(pS3Int->pCurl, CURLOPT_VERBOSE, 1); + } +} + + +/********************************************************************************************************************************* +* Private XML helper * +*********************************************************************************************************************************/ + +static xmlNodePtr rtS3FindNode(xmlNodePtr pNode, const char *pszName) +{ + pNode = pNode->xmlChildrenNode; + while (pNode != NULL) + { + /* Check this level. */ + if (!xmlStrcmp(pNode->name, (const xmlChar *)pszName)) + return pNode; + + /* Recursively check the children of this node. */ + xmlNodePtr pChildNode = rtS3FindNode(pNode, pszName); + if (pChildNode != NULL) + return pChildNode; + + /* Next node. */ + pNode = pNode->next; + } + return pNode; +} + +static int rtS3ReadXmlFromMemory(PRTS3TMPMEMCHUNK pChunk, const char* pszRootElement, xmlDocPtr *ppDoc, xmlNodePtr *ppCur) +{ + *ppDoc = xmlReadMemory(pChunk->pszMem, (int)pChunk->cSize, "", "ISO-8859-1", XML_PARSE_NOBLANKS | XML_PARSE_NONET); + if (*ppDoc == NULL) + return VERR_PARSE_ERROR; + + *ppCur = xmlDocGetRootElement(*ppDoc); + if (*ppCur == NULL) + { + xmlFreeDoc(*ppDoc); + return VERR_PARSE_ERROR; + } + if (xmlStrcmp((*ppCur)->name, (const xmlChar *) pszRootElement)) + { + xmlFreeDoc(*ppDoc); + return VERR_PARSE_ERROR; + } + return VINF_SUCCESS; +} + +static void rtS3ExtractAllBuckets(xmlDocPtr pDoc, xmlNodePtr pNode, PCRTS3BUCKETENTRY *ppBuckets) +{ + pNode = rtS3FindNode(pNode, "Buckets"); + if (pNode != NULL) + { + PRTS3BUCKETENTRY pPrevBucket = NULL; + xmlNodePtr pCurBucket = pNode->xmlChildrenNode; + while (pCurBucket != NULL) + { + if ((!xmlStrcmp(pCurBucket->name, (const xmlChar *)"Bucket"))) + { + PRTS3BUCKETENTRY pBucket = (PRTS3BUCKETENTRY)RTMemAllocZ(sizeof(RTS3BUCKETENTRY)); + pBucket->pPrev = pPrevBucket; + if (pPrevBucket) + pPrevBucket->pNext = pBucket; + else + (*ppBuckets) = pBucket; + pPrevBucket = pBucket; + xmlNodePtr pCurCont = pCurBucket->xmlChildrenNode; + while (pCurCont != NULL) + { + if ((!xmlStrcmp(pCurCont->name, (const xmlChar *)"Name"))) + { + xmlChar *pszKey = xmlNodeListGetString(pDoc, pCurCont->xmlChildrenNode, 1); + pBucket->pszName = RTStrDup((const char*)pszKey); + xmlFree(pszKey); + } + if ((!xmlStrcmp(pCurCont->name, (const xmlChar*)"CreationDate"))) + { + xmlChar *pszKey = xmlNodeListGetString(pDoc, pCurCont->xmlChildrenNode, 1); + pBucket->pszCreationDate = RTStrDup((const char*)pszKey); + xmlFree(pszKey); + } + pCurCont = pCurCont->next; + } + } + pCurBucket = pCurBucket->next; + } + } +} + +static void rtS3ExtractAllKeys(xmlDocPtr pDoc, xmlNodePtr pNode, PCRTS3KEYENTRY *ppKeys) +{ + if (pNode != NULL) + { + PRTS3KEYENTRY pPrevKey = NULL; + xmlNodePtr pCurKey = pNode->xmlChildrenNode; + while (pCurKey != NULL) + { + if ((!xmlStrcmp(pCurKey->name, (const xmlChar *)"Contents"))) + { + PRTS3KEYENTRY pKey = (PRTS3KEYENTRY)RTMemAllocZ(sizeof(RTS3KEYENTRY)); + pKey->pPrev = pPrevKey; + if (pPrevKey) + pPrevKey->pNext = pKey; + else + (*ppKeys) = pKey; + pPrevKey = pKey; + xmlNodePtr pCurCont = pCurKey->xmlChildrenNode; + while (pCurCont != NULL) + { + if ((!xmlStrcmp(pCurCont->name, (const xmlChar *)"Key"))) + { + xmlChar *pszKey = xmlNodeListGetString(pDoc, pCurCont->xmlChildrenNode, 1); + pKey->pszName = RTStrDup((const char*)pszKey); + xmlFree(pszKey); + } + if ((!xmlStrcmp(pCurCont->name, (const xmlChar*)"LastModified"))) + { + xmlChar *pszKey = xmlNodeListGetString(pDoc, pCurCont->xmlChildrenNode, 1); + pKey->pszLastModified = RTStrDup((const char*)pszKey); + xmlFree(pszKey); + } + if ((!xmlStrcmp(pCurCont->name, (const xmlChar*)"Size"))) + { + xmlChar *pszKey = xmlNodeListGetString(pDoc, pCurCont->xmlChildrenNode, 1); + pKey->cbFile = RTStrToUInt64((const char*)pszKey); + xmlFree(pszKey); + } + pCurCont = pCurCont->next; + } + } + pCurKey = pCurKey->next; + } + } +} + + +/********************************************************************************************************************************* +* Public RTS3 interface * +*********************************************************************************************************************************/ + +RTR3DECL(int) RTS3Create(PRTS3 ppS3, const char* pszAccessKey, const char* pszSecretKey, const char* pszBaseUrl, const char* pszUserAgent /* = NULL */) +{ + AssertPtrReturn(ppS3, VERR_INVALID_POINTER); + + /* We need at least an URL to connect with */ + if (pszBaseUrl == NULL || + pszBaseUrl[0] == 0) + return VERR_INVALID_PARAMETER; + + /* In windows, this will init the winsock stuff */ + if (curl_global_init(CURL_GLOBAL_ALL) != 0) + return VERR_INTERNAL_ERROR; + + CURL* pCurl = curl_easy_init(); + if (!pCurl) + return VERR_INTERNAL_ERROR; + + PRTS3INTERNAL pS3Int = (PRTS3INTERNAL)RTMemAllocZ(sizeof(RTS3INTERNAL)); + if (pS3Int == NULL) + return VERR_NO_MEMORY; + + pS3Int->u32Magic = RTS3_MAGIC; + pS3Int->pCurl = pCurl; + pS3Int->pszAccessKey = RTStrDup(pszAccessKey); + pS3Int->pszSecretKey = RTStrDup(pszSecretKey); + pS3Int->pszBaseUrl = RTStrDup(pszBaseUrl); + if (pszUserAgent) + pS3Int->pszUserAgent = RTStrDup(pszUserAgent); + + *ppS3 = (RTS3)pS3Int; + + return VINF_SUCCESS; +} + +RTR3DECL(void) RTS3Destroy(RTS3 hS3) +{ + if (hS3 == NIL_RTS3) + return; + + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN_VOID(pS3Int); + + curl_easy_cleanup(pS3Int->pCurl); + + pS3Int->u32Magic = RTS3_MAGIC_DEAD; + + if (pS3Int->pszUserAgent) + RTStrFree(pS3Int->pszUserAgent); + RTStrFree(pS3Int->pszBaseUrl); + RTStrFree(pS3Int->pszSecretKey); + RTStrFree(pS3Int->pszAccessKey); + + RTMemFree(pS3Int); + + curl_global_cleanup(); +} + +RTR3DECL(void) RTS3SetProgressCallback(RTS3 hS3, PFNRTS3PROGRESS pfnProgressCallback, void *pvUser /* = NULL */) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN_VOID(pS3Int); + + pS3Int->pfnProgressCallback = pfnProgressCallback; + pS3Int->pvUser = pvUser; +} + +RTR3DECL(int) RTS3GetBuckets(RTS3 hS3, PCRTS3BUCKETENTRY *ppBuckets) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN(pS3Int); + + /* Properly initialize this */ + *ppBuckets = NULL; + + /* Reset the CURL object to an defined state */ + rtS3ReinitCurl(pS3Int); + /* Create the CURL object to operate on */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_URL, pS3Int->pszBaseUrl); + + /* Create the three basic header entries */ + char *apszHead[3] = + { + rtS3HostHeader("", pS3Int->pszBaseUrl), /* Host entry */ + rtS3DateHeader(), /* Date entry */ + NULL /* Authorization entry */ + }; + /* Create the authorization header entry */ + apszHead[RT_ELEMENTS(apszHead)-1] = rtS3CreateAuthHeader(pS3Int, "GET", "", "", apszHead, RT_ELEMENTS(apszHead)); + + /* Add all headers to curl */ + struct curl_slist* pHeaders = NULL; /* Init to NULL is important */ + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + pHeaders = curl_slist_append(pHeaders, apszHead[i]); + + /* Pass our list of custom made headers */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTPHEADER, pHeaders); + + RTS3TMPMEMCHUNK chunk = { NULL, 0 }; + /* Set the callback which receive the content */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_WRITEFUNCTION, rtS3WriteMemoryCallback); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_WRITEDATA, (void *)&chunk); + /* Start the request */ + int rc = rtS3Perform(pS3Int); + + /* Regardless of the result, free all used resources first*/ + curl_slist_free_all(pHeaders); + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + RTStrFree(apszHead[i]); + + /* On success parse the result */ + if (RT_SUCCESS(rc)) + { + xmlDocPtr pDoc; + xmlNodePtr pCur; + /* Parse the xml memory for "ListAllMyBucketsResult" */ + rc = rtS3ReadXmlFromMemory(&chunk, "ListAllMyBucketsResult", &pDoc, &pCur); + if (RT_SUCCESS(rc)) + { + /* Now extract all buckets */ + rtS3ExtractAllBuckets(pDoc, pCur, ppBuckets); + /* Free the xml stuff */ + xmlFreeDoc(pDoc); + } + } + /* Free the temporary memory */ + RTMemFree(chunk.pszMem); + + return rc; +} + +RTR3DECL(int) RTS3BucketsDestroy(PCRTS3BUCKETENTRY pBuckets) +{ + if (!pBuckets) + return VINF_SUCCESS; + + while (pBuckets) + { + PCRTS3BUCKETENTRY pTemp = pBuckets; + RTStrFree((char*)pBuckets->pszName); + RTStrFree((char*)pBuckets->pszCreationDate); + pBuckets = pBuckets->pNext; + RTMemFree((PRTS3BUCKETENTRY )pTemp); + } + return VINF_SUCCESS; +} + +RTR3DECL(int) RTS3CreateBucket(RTS3 hS3, const char* pszBucketName) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN(pS3Int); + + /* Reset the CURL object to an defined state */ + rtS3ReinitCurl(pS3Int); + + char* pszUrl = rtS3Host(pszBucketName, "", pS3Int->pszBaseUrl); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_URL, pszUrl); + RTStrFree(pszUrl); + + /* Create the basic header entries */ + char *apszHead[4] = + { + RTStrDup("Content-Length: 0"), /* Content length entry */ + rtS3HostHeader(pszBucketName, pS3Int->pszBaseUrl), /* Host entry */ + rtS3DateHeader(), /* Date entry */ + NULL /* Authorization entry */ + }; + /* Create the authorization header entry */ + apszHead[RT_ELEMENTS(apszHead)-1] = rtS3CreateAuthHeader(pS3Int, "PUT", pszBucketName, "", apszHead, RT_ELEMENTS(apszHead)); + + /* Add all headers to curl */ + struct curl_slist* pHeaders = NULL; /* Init to NULL is important */ + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + pHeaders = curl_slist_append(pHeaders, apszHead[i]); + + /* Pass our list of custom made headers */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTPHEADER, pHeaders); + + /* Set CURL in upload mode */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_PUT, 1); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_UPLOAD, 1); + + /* Set the size of the file we like to transfer */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_INFILESIZE_LARGE, 0); + + /* Start the request */ + int rc = rtS3Perform(pS3Int); + if (RT_FAILURE(rc)) + { + /* Handle special failures */ + if (pS3Int->lLastResp == 409) + rc = VERR_S3_BUCKET_ALREADY_EXISTS; + } + + /* Regardless of the result, free all used resources first*/ + curl_slist_free_all(pHeaders); + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + RTStrFree(apszHead[i]); + + return rc; +} + +RTR3DECL(int) RTS3DeleteBucket(RTS3 hS3, const char* pszBucketName) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN(pS3Int); + + /* Reset the CURL object to an defined state */ + rtS3ReinitCurl(pS3Int); + + char* pszUrl = rtS3Host(pszBucketName, "", pS3Int->pszBaseUrl); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_URL, pszUrl); + RTStrFree(pszUrl); + + /* Create the three basic header entries */ + char *apszHead[3] = + { + rtS3HostHeader(pszBucketName, pS3Int->pszBaseUrl), /* Host entry */ + rtS3DateHeader(), /* Date entry */ + NULL /* Authorization entry */ + }; + /* Create the authorization header entry */ + apszHead[RT_ELEMENTS(apszHead)-1] = rtS3CreateAuthHeader(pS3Int, "DELETE", pszBucketName, "", apszHead, RT_ELEMENTS(apszHead)); + + /* Add all headers to curl */ + struct curl_slist* pHeaders = NULL; /* Init to NULL is important */ + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + pHeaders = curl_slist_append(pHeaders, apszHead[i]); + + /* Pass our list of custom made headers */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTPHEADER, pHeaders); + + /* Set CURL in delete mode */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE"); + + /* Start the request */ + int rc = rtS3Perform(pS3Int); + if (RT_FAILURE(rc)) + { + /* Handle special failures */ + if (pS3Int->lLastResp == 409) + rc = VERR_S3_BUCKET_NOT_EMPTY; + } + + /* Regardless of the result, free all used resources first*/ + curl_slist_free_all(pHeaders); + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + RTStrFree(apszHead[i]); + + return rc; +} + +RTR3DECL(int) RTS3GetBucketKeys(RTS3 hS3, const char* pszBucketName, PCRTS3KEYENTRY *ppKeys) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN(pS3Int); + + *ppKeys = NULL; + + /* Reset the CURL object to an defined state */ + rtS3ReinitCurl(pS3Int); + + char* pszUrl = rtS3Host(pszBucketName, "", pS3Int->pszBaseUrl); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_URL, pszUrl); + RTStrFree(pszUrl); + + /* Create the three basic header entries */ + char *apszHead[3] = + { + rtS3HostHeader(pszBucketName, pS3Int->pszBaseUrl), /* Host entry */ + rtS3DateHeader(), /* Date entry */ + NULL /* Authorization entry */ + }; + /* Create the authorization header entry */ + apszHead[RT_ELEMENTS(apszHead)-1] = rtS3CreateAuthHeader(pS3Int, "GET", pszBucketName, "", apszHead, RT_ELEMENTS(apszHead)); + + /* Add all headers to curl */ + struct curl_slist* pHeaders = NULL; /* Init to NULL is important */ + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + pHeaders = curl_slist_append(pHeaders, apszHead[i]); + + /* Pass our list of custom made headers */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTPHEADER, pHeaders); + + RTS3TMPMEMCHUNK chunk = { NULL, 0 }; + /* Set the callback which receive the content */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_WRITEFUNCTION, rtS3WriteMemoryCallback); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_WRITEDATA, (void *)&chunk); + + /* Start the request */ + int rc = rtS3Perform(pS3Int); + + /* Regardless of the result, free all used resources first*/ + curl_slist_free_all(pHeaders); + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + RTStrFree(apszHead[i]); + + /* On success parse the result */ + if (RT_SUCCESS(rc)) + { + xmlDocPtr pDoc; + xmlNodePtr pCur; + /* Parse the xml memory for "ListBucketResult" */ + rc = rtS3ReadXmlFromMemory(&chunk, "ListBucketResult", &pDoc, &pCur); + if (RT_SUCCESS(rc)) + { + /* Now extract all buckets */ + rtS3ExtractAllKeys(pDoc, pCur, ppKeys); + /* Free the xml stuff */ + xmlFreeDoc(pDoc); + } + } + /* Free the temporary memory */ + RTMemFree(chunk.pszMem); + + return rc; +} + +RTR3DECL(int) RTS3KeysDestroy(PCRTS3KEYENTRY pKeys) +{ + if (!pKeys) + return VINF_SUCCESS; + + while (pKeys) + { + PCRTS3KEYENTRY pTemp = pKeys; + RTStrFree((char*)pKeys->pszName); + RTStrFree((char*)pKeys->pszLastModified); + pKeys = pKeys->pNext; + RTMemFree((PRTS3KEYENTRY)pTemp); + } + return VINF_SUCCESS; +} + +RTR3DECL(int) RTS3DeleteKey(RTS3 hS3, const char* pszBucketName, const char* pszKeyName) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN(pS3Int); + + /* Reset the CURL object to an defined state */ + rtS3ReinitCurl(pS3Int); + + char* pszUrl = rtS3Host(pszBucketName, pszKeyName, pS3Int->pszBaseUrl); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_URL, pszUrl); + RTStrFree(pszUrl); + + /* Create the three basic header entries */ + char *apszHead[3] = + { + rtS3HostHeader(pszBucketName, pS3Int->pszBaseUrl), /* Host entry */ + rtS3DateHeader(), /* Date entry */ + NULL /* Authorization entry */ + }; + /* Create the authorization header entry */ + apszHead[RT_ELEMENTS(apszHead)-1] = rtS3CreateAuthHeader(pS3Int, "DELETE", pszBucketName, pszKeyName, apszHead, RT_ELEMENTS(apszHead)); + + /* Add all headers to curl */ + struct curl_slist* pHeaders = NULL; /* Init to NULL is important */ + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + pHeaders = curl_slist_append(pHeaders, apszHead[i]); + + /* Pass our list of custom made headers */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTPHEADER, pHeaders); + + /* Set CURL in delete mode */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE"); + + /* Start the request */ + int rc = rtS3Perform(pS3Int); + + /* Regardless of the result, free all used resources first*/ + curl_slist_free_all(pHeaders); + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + RTStrFree(apszHead[i]); + + return rc; +} + +RTR3DECL(int) RTS3GetKey(RTS3 hS3, const char *pszBucketName, const char *pszKeyName, const char *pszFilename) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN(pS3Int); + + /* Reset the CURL object to an defined state */ + rtS3ReinitCurl(pS3Int); + + /* Open the file */ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return rc; + + char* pszUrl = rtS3Host(pszBucketName, pszKeyName, pS3Int->pszBaseUrl); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_URL, pszUrl); + RTStrFree(pszUrl); + + /* Create the three basic header entries */ + char *apszHead[3] = + { + rtS3HostHeader(pszBucketName, pS3Int->pszBaseUrl), /* Host entry */ + rtS3DateHeader(), /* Date entry */ + NULL /* Authorization entry */ + }; + /* Create the authorization header entry */ + apszHead[RT_ELEMENTS(apszHead)-1] = rtS3CreateAuthHeader(pS3Int, "GET", pszBucketName, pszKeyName, apszHead, RT_ELEMENTS(apszHead)); + + /* Add all headers to curl */ + struct curl_slist* pHeaders = NULL; /* Init to NULL is important */ + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + pHeaders = curl_slist_append(pHeaders, apszHead[i]); + + /* Pass our list of custom made headers */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTPHEADER, pHeaders); + + /* Set the callback which receive the content */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_WRITEFUNCTION, rtS3WriteFileCallback); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_WRITEDATA, &hFile); + + /* Start the request */ + rc = rtS3Perform(pS3Int); + + /* Regardless of the result, free all used resources first*/ + curl_slist_free_all(pHeaders); + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + RTStrFree(apszHead[i]); + + /* Close the open file */ + RTFileClose(hFile); + + /* If there was an error delete the newly created file */ + if (RT_FAILURE(rc)) + RTFileDelete(pszFilename); + + return rc; +} + +RTR3DECL(int) RTS3PutKey(RTS3 hS3, const char *pszBucketName, const char *pszKeyName, const char *pszFilename) +{ + PRTS3INTERNAL pS3Int = hS3; + RTS3_VALID_RETURN(pS3Int); + + /* Reset the CURL object to an defined state */ + rtS3ReinitCurl(pS3Int); + + /* Open the file */ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return rc; + + uint64_t cbFileSize; + rc = RTFileQuerySize(hFile, &cbFileSize); + if (RT_FAILURE(rc)) + { + RTFileClose(hFile); + return rc; + } + + char* pszUrl = rtS3Host(pszBucketName, pszKeyName, pS3Int->pszBaseUrl); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_URL, pszUrl); + RTStrFree(pszUrl); + + char* pszContentLength; + RTStrAPrintf(&pszContentLength, "Content-Length: %lu", cbFileSize); + /* Create the three basic header entries */ + char *apszHead[5] = + { + /** @todo For now we use octet-stream for all types. Later we should try + * to set the right one (libmagic from the file packet could be a + * candidate for finding the right type). */ + RTStrDup("Content-Type: octet-stream"), /* Content type entry */ + pszContentLength, /* Content length entry */ + rtS3HostHeader(pszBucketName, pS3Int->pszBaseUrl), /* Host entry */ + rtS3DateHeader(), /* Date entry */ + NULL /* Authorization entry */ + }; + /* Create the authorization header entry */ + apszHead[RT_ELEMENTS(apszHead)-1] = rtS3CreateAuthHeader(pS3Int, "PUT", pszBucketName, pszKeyName, apszHead, RT_ELEMENTS(apszHead)); + + /* Add all headers to curl */ + struct curl_slist* pHeaders = NULL; /* Init to NULL is important */ + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + pHeaders = curl_slist_append(pHeaders, apszHead[i]); + + /* Pass our list of custom made headers */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_HTTPHEADER, pHeaders); + + /* Set CURL in upload mode */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_PUT, 1); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_UPLOAD, 1); + + /* Set the size of the file we like to transfer */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_INFILESIZE_LARGE, cbFileSize); + + /* Set the callback which send the content */ + curl_easy_setopt(pS3Int->pCurl, CURLOPT_READFUNCTION, rtS3ReadFileCallback); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_READDATA, &hFile); + curl_easy_setopt(pS3Int->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1); + + /* Start the request */ + rc = rtS3Perform(pS3Int); + + /* Regardless of the result, free all used resources first*/ + curl_slist_free_all(pHeaders); + for(size_t i=0; i < RT_ELEMENTS(apszHead); ++i) + RTStrFree(apszHead[i]); + + /* Close the open file */ + RTFileClose(hFile); + + return rc; +} + 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..281b65f5 --- /dev/null +++ b/src/VBox/Runtime/common/misc/sanity-c.c @@ -0,0 +1,27 @@ +/* $Id: sanity-c.c $ */ +/** @file + * IPRT - Setup Sanity Checks, C. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#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..e267c687 --- /dev/null +++ b/src/VBox/Runtime/common/misc/sanity-cpp.cpp @@ -0,0 +1,28 @@ +/* $Id: sanity-cpp.cpp $ */ +/** @file + * IPRT - Setup Sanity Checks, C++. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#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..18f13a8a --- /dev/null +++ b/src/VBox/Runtime/common/misc/sanity.h @@ -0,0 +1,215 @@ +/* $Id: sanity.h $ */ +/** @file + * IPRT - Setup Sanity Checks, C and C++. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#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..5e5104fd --- /dev/null +++ b/src/VBox/Runtime/common/misc/semspingpong.cpp @@ -0,0 +1,251 @@ +/* $Id: semspingpong.cpp $ */ +/** @file + * IPRT - Thread Ping-Pong Construct. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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) + + +/** + * Init a Ping-Pong construct. + * + * @returns iprt status code. + * @param pPP Pointer to the ping-pong structure which needs initialization. + */ +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); + + +/** + * Destroys a Ping-Pong construct. + * + * @returns iprt status code. + * @param pPP Pointer to the ping-pong structure which is to be destroyed. + * (I.e. put into uninitialized state.) + */ +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); + + +/** + * Signals the pong thread in a ping-pong construct. (I.e. sends ping.) + * This is called by the ping thread. + * + * @returns iprt status code. + * @param pPP Pointer to the ping-pong structure to ping. + */ +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); + + +/** + * Signals the ping thread in a ping-pong construct. (I.e. sends pong.) + * This is called by the pong thread. + * + * @returns iprt status code. + * @param pPP Pointer to the ping-pong structure to pong. + */ +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); + + +/** + * Wait function for the ping thread. + * + * @returns iprt status code. + * Will not return VERR_INTERRUPTED. + * @param pPP Pointer to the ping-pong structure to wait on. + * @param cMillies Number of milliseconds to wait. + */ +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); + + +/** + * Wait function for the pong thread. + * + * @returns iprt status code. + * Will not return VERR_INTERRUPTED. + * @param pPP Pointer to the ping-pong structure to wait on. + * @param cMillies Number of milliseconds to wait. + */ +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..fa952740 --- /dev/null +++ b/src/VBox/Runtime/common/misc/setjmp.asm @@ -0,0 +1,138 @@ +; $Id: setjmp.asm $ +;; @file +; IPRT - No-CRT setjmp & longjmp - AMD64 & X86. +; + +; +; Copyright (C) 2006-2020 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + +%include "iprt/asmdefs.mac" + + +BEGINCODE + + +;; +; @param x86:[esp+4] msc:rcx gcc:rdi The jump buffer pointer. +BEGINPROC RT_NOCRT(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. +BEGINPROC RT_NOCRT(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..34153f0a --- /dev/null +++ b/src/VBox/Runtime/common/misc/sg.cpp @@ -0,0 +1,447 @@ +/* $Id: sg.cpp $ */ +/** @file + * IPRT - S/G buffer handling. + */ + +/* + * Copyright (C) 2010-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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 && 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) 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..513f232e --- /dev/null +++ b/src/VBox/Runtime/common/misc/term.cpp @@ -0,0 +1,242 @@ +/* $Id: term.cpp $ */ +/** @file + * IPRT - Common Termination Code. + */ + +/* + * Copyright (C) 2009-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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..604dd04e --- /dev/null +++ b/src/VBox/Runtime/common/misc/thread.cpp @@ -0,0 +1,1610 @@ +/* $Id: thread.cpp $ */ +/** @file + * IPRT - Threads, common routines. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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. */ +DECLHIDDEN(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; +} + +/** + * Adopts a non-IPRT thread. + * + * @returns IPRT status code. + * @param enmType The thread type. + * @param fFlags The thread flags. RTTHREADFLAGS_WAITABLE is not currently allowed. + * @param pszName The thread name. Optional. + * @param pThread Where to store the thread handle. Optional. + */ +RTDECL(int) RTThreadAdopt(RTTHREADTYPE enmType, unsigned fFlags, const char *pszName, PRTTHREAD pThread) +{ + int rc; + RTTHREAD Thread; + + AssertReturn(!(fFlags & RTTHREADFLAGS_WAITABLE), VERR_INVALID_PARAMETER); + AssertReturn(!pszName || VALID_PTR(pszName), VERR_INVALID_POINTER); + AssertReturn(!pThread || VALID_PTR(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(); + 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); + + +/** + * Get the thread handle of the current thread, automatically adopting alien + * threads. + * + * @returns Thread handle. + */ +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 + 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]); + } + + 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 + && 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); + 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). + */ +DECLCALLBACK(DECLHIDDEN(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; +} + + +/** + * Create a new thread. + * + * @returns iprt status code. + * @param pThread Where to store the thread handle to the new thread. (optional) + * @param pfnThread The thread function. + * @param pvUser User argument. + * @param cbStack The size of the stack for the new thread. + * Use 0 for the default stack size. + * @param enmType The thread type. Used for deciding scheduling attributes + * of the thread. + * @param fFlags Flags of the RTTHREADFLAGS type (ORed together). + * @param pszName Thread name. + */ +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. + */ + if (!VALID_PTR(pThread) && pThread) + { + Assert(VALID_PTR(pThread)); + return VERR_INVALID_PARAMETER; + } + if (!VALID_PTR(pfnThread)) + { + Assert(VALID_PTR(pfnThread)); + return VERR_INVALID_PARAMETER; + } + if (!pszName || !*pszName || strlen(pszName) >= RTTHREAD_NAME_LEN) + { + AssertMsgFailed(("pszName=%s (max len is %d because of logging)\n", pszName, RTTHREAD_NAME_LEN - 1)); + return VERR_INVALID_PARAMETER; + } + if (fFlags & ~RTTHREADFLAGS_MASK) + { + AssertMsgFailed(("fFlags=%#x\n", fFlags)); + return VERR_INVALID_PARAMETER; + } + + /* + * 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); + + +/** + * Create a new thread. + * + * Same as RTThreadCreate except the name is given in the RTStrPrintfV form. + * + * @returns iprt status code. + * @param pThread See RTThreadCreate. + * @param pfnThread See RTThreadCreate. + * @param pvUser See RTThreadCreate. + * @param cbStack See RTThreadCreate. + * @param enmType See RTThreadCreate. + * @param fFlags See RTThreadCreate. + * @param pszNameFmt Thread name format. + * @param va Format arguments. + */ +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); + + +/** + * Create a new thread. + * + * Same as RTThreadCreate except the name is given in the RTStrPrintf form. + * + * @returns iprt status code. + * @param pThread See RTThreadCreate. + * @param pfnThread See RTThreadCreate. + * @param pvUser See RTThreadCreate. + * @param cbStack See RTThreadCreate. + * @param enmType See RTThreadCreate. + * @param fFlags See RTThreadCreate. + * @param pszNameFmt Thread name format. + * @param ... Format arguments. + */ +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); + + +/** + * Gets the native thread id of a IPRT thread. + * + * @returns The native thread id. + * @param Thread The IPRT thread. + */ +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); + + +/** + * Gets the IPRT thread of a native thread. + * + * @returns The IPRT thread handle + * @returns NIL_RTTHREAD if not a thread known to IPRT. + * @param NativeThread The native thread handle/id. + */ +RTDECL(RTTHREAD) RTThreadFromNative(RTNATIVETHREAD NativeThread) +{ + PRTTHREADINT pThread = rtThreadGetByNative(NativeThread); + if (pThread) + return pThread; + return NIL_RTTHREAD; +} +RT_EXPORT_SYMBOL(RTThreadFromNative); + + +/** + * Gets the name of the current thread thread. + * + * @returns Pointer to readonly name string. + * @returns NULL on failure. + */ +RTDECL(const char *) RTThreadSelfName(void) +{ + RTTHREAD Thread = RTThreadSelf(); + if (Thread != NIL_RTTHREAD) + { + PRTTHREADINT pThread = rtThreadGet(Thread); + if (pThread) + { + const char *szName = pThread->szName; + rtThreadRelease(pThread); + return szName; + } + } + return NULL; +} +RT_EXPORT_SYMBOL(RTThreadSelfName); + + +/** + * Gets the name of a thread. + * + * @returns Pointer to readonly name string. + * @returns NULL on failure. + * @param Thread Thread handle of the thread to query the name of. + */ +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); + + +/** + * Sets the name of a thread. + * + * @returns iprt status code. + * @param Thread Thread handle of the thread to query the name of. + * @param pszName The thread name. + */ +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); + + +/** + * Checks if the specified thread is the main thread. + * + * @returns true if it is, false if it isn't. + * + * @param hThread The thread handle. + * + * @remarks This function may not return the correct value when rtR3Init was + * called on a thread of the than the main one. This could for + * instance happen when the DLL/DYLIB/SO containing IPRT is dynamically + * loaded at run time by a different thread. + */ +RTDECL(bool) RTThreadIsMain(RTTHREAD hThread) +{ + 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); + + +/** + * Signal the user event. + * + * @returns iprt status code. + */ +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); + + +/** + * Wait for the user event, resume on interruption. + * + * @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. + */ +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); + + +/** + * Wait for the user event, return on interruption. + * + * @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. + */ +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); + + +/** + * Reset the user event. + * + * @returns iprt status code. + * @param Thread The thread to reset. + */ +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; +} + + +/** + * Wait for the thread to terminate, resume on interruption. + * + * @returns iprt status code. + * Will not return VERR_INTERRUPTED. + * @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. + */ +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); + + +/** + * Wait for the thread to terminate, return on interruption. + * + * @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. + */ +RTDECL(int) RTThreadWaitNoResume(RTTHREAD Thread, RTMSINTERVAL cMillies, int *prc) +{ + return rtThreadWait(Thread, cMillies, prc, false); +} +RT_EXPORT_SYMBOL(RTThreadWaitNoResume); + + +/** + * Changes the type of the specified thread. + * + * @returns iprt status code. + * @param Thread The thread which type should be changed. + * @param enmType The new thread type. + */ +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); + + +/** + * Gets the type of the specified thread. + * + * @returns The thread type. + * @returns RTTHREADTYPE_INVALID if the thread handle is invalid. + * @param Thread The thread in question. + */ +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; +} + + +/** + * Change the thread state to blocking. + * + * @param hThread The current thread. + * @param enmState The sleep state. + * @param fReallySleeping Really going to sleep now. + */ +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); + + +/** + * Unblocks a thread. + * + * This function is paired with rtThreadBlocking. + * + * @param hThread The current thread. + * @param enmCurState The current state, used to check for nested blocking. + * The new state will be running. + */ +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); + + +/** + * Get the current thread state. + * + * @returns The thread state. + * @param hThread The thread. + */ +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..60ace6d7 --- /dev/null +++ b/src/VBox/Runtime/common/misc/uri.cpp @@ -0,0 +1,1171 @@ +/* $Id: uri.cpp $ */ +/** @file + * IPRT - Uniform Resource Identifier handling. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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.asm b/src/VBox/Runtime/common/misc/zero.asm new file mode 100644 index 00000000..f9cf4bd8 --- /dev/null +++ b/src/VBox/Runtime/common/misc/zero.asm @@ -0,0 +1,52 @@ +; $Id: zero.asm $ +;; @file +; IPRT - Zero Memory. +; + +; +; Copyright (C) 2013-2020 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" + +; Putting it in the code segment/section for now. +BEGINCODE + +;; +; 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 + times 0x10000/(16*4) dd 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 +%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..a8825f6d --- /dev/null +++ b/src/VBox/Runtime/common/misc/zero.cpp @@ -0,0 +1,42 @@ +/* $Id: zero.cpp $ */ +/** @file + * IPRT - Zero Memory. + */ + +/* + * Copyright (C) 2015-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* 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 }; + |