summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp')
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp
new file mode 100644
index 00000000..6df00ba3
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp
@@ -0,0 +1,439 @@
+/* $Id: VBoxServicePropCache.cpp $ */
+/** @file
+ * VBoxServicePropCache - Guest property cache.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServicePropCache.h"
+
+
+
+/** @todo Docs */
+static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheFindInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName,
+ uint32_t fFlags)
+{
+ RT_NOREF1(fFlags);
+ AssertPtrReturn(pCache, NULL);
+ AssertPtrReturn(pszName, NULL);
+
+ /** @todo This is a O(n) lookup, maybe improve this later to O(1) using a
+ * map.
+ * r=bird: Use a string space (RTstrSpace*). That is O(log n) in its current
+ * implementation (AVL tree). However, this is not important at the
+ * moment. */
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = NULL;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ if (strcmp(pNodeIt->pszName, pszName) == 0)
+ {
+ pNode = pNodeIt;
+ break;
+ }
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return pNode;
+}
+
+
+/** @todo Docs */
+static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheInsertEntryInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName)
+{
+ AssertPtrReturn(pCache, NULL);
+ AssertPtrReturn(pszName, NULL);
+
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = (PVBOXSERVICEVEPROPCACHEENTRY)RTMemAlloc(sizeof(VBOXSERVICEVEPROPCACHEENTRY));
+ if (pNode)
+ {
+ pNode->pszName = RTStrDup(pszName);
+ if (!pNode->pszName)
+ {
+ RTMemFree(pNode);
+ return NULL;
+ }
+ pNode->pszValue = NULL;
+ pNode->fFlags = 0;
+ pNode->pszValueReset = NULL;
+
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&pCache->NodeHead, &pNode->NodeSucc);
+ rc = RTCritSectLeave(&pCache->CritSect);
+ }
+ }
+ return pNode;
+}
+
+
+/** @todo Docs */
+static int vgsvcPropCacheWritePropF(uint32_t u32ClientId, const char *pszName, uint32_t fFlags, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ int rc;
+ if (pszValueFormat != NULL)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+
+ char *pszValue;
+ if (RTStrAPrintfV(&pszValue, pszValueFormat, va) >= 0)
+ {
+ if (fFlags & VGSVCPROPCACHE_FLAGS_TRANSIENT)
+ {
+ /*
+ * Because a value can be temporary we have to make sure it also
+ * gets deleted when the property cache did not have the chance to
+ * gracefully clean it up (due to a hard VM reset etc), so set this
+ * guest property using the TRANSRESET flag..
+ */
+ rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSRESET");
+ if (rc == VERR_PARSE_ERROR)
+ {
+ /* Host does not support the "TRANSRESET" flag, so only
+ * use the "TRANSIENT" flag -- better than nothing :-). */
+ rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSIENT");
+ /** @todo r=bird: Remember that the host doesn't support
+ * this. */
+ }
+ }
+ else
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue /* No transient flags set */);
+ RTStrFree(pszValue);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ va_end(va);
+ }
+ else
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL);
+ return rc;
+}
+
+
+/**
+ * Creates a property cache.
+ *
+ * @returns IPRT status code.
+ * @param pCache Pointer to the cache.
+ * @param uClientId The HGCM handle of to the guest property service.
+ */
+int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ /** @todo Prevent init the cache twice!
+ * r=bird: Use a magic. */
+ RTListInit(&pCache->NodeHead);
+ pCache->uClientID = uClientId;
+ return RTCritSectInit(&pCache->CritSect);
+}
+
+
+/**
+ * Updates a cache entry without submitting any changes to the host.
+ *
+ * This is handy for defining default values/flags.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszName The property name.
+ * @param fFlags The property flags to set.
+ * @param pszValueReset The property reset value.
+ */
+int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0);
+ if (pNode == NULL)
+ pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName);
+
+ int rc;
+ if (pNode != NULL)
+ {
+ rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pNode->fFlags = fFlags;
+ if (pszValueReset)
+ {
+ if (pNode->pszValueReset)
+ RTStrFree(pNode->pszValueReset);
+ pNode->pszValueReset = RTStrDup(pszValueReset);
+ AssertPtr(pNode->pszValueReset);
+ }
+ rc = RTCritSectLeave(&pCache->CritSect);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Updates the local guest property cache and writes it to HGCM if outdated.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszName The property name.
+ * @param pszValueFormat The property format string. If this is NULL then
+ * the property will be deleted (if possible).
+ * @param ... Format arguments.
+ */
+int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ Assert(pCache->uClientID);
+
+ /*
+ * Format the value first.
+ */
+ char *pszValue = NULL;
+ if (pszValueFormat)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+ RTStrAPrintfV(&pszValue, pszValueFormat, va);
+ va_end(va);
+ if (!pszValue)
+ return VERR_NO_STR_MEMORY;
+ }
+
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0);
+
+ /* Lock the cache. */
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (pNode == NULL)
+ pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName);
+
+ AssertPtr(pNode);
+ if (pszValue) /* Do we have a value to check for? */
+ {
+ bool fUpdate = false;
+ /* Always update this property, no matter what? */
+ if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE)
+ fUpdate = true;
+ /* Did the value change so we have to update? */
+ else if (pNode->pszValue && strcmp(pNode->pszValue, pszValue) != 0)
+ fUpdate = true;
+ /* No value stored at the moment but we have a value now? */
+ else if (pNode->pszValue == NULL)
+ fUpdate = true;
+
+ if (fUpdate)
+ {
+ /* Write the update. */
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pszValue);
+ VGSvcVerbose(4, "[PropCache %p]: Written '%s'='%s' (flags: %x), rc=%Rrc\n",
+ pCache, pNode->pszName, pszValue, pNode->fFlags, rc);
+ if (RT_SUCCESS(rc)) /* Only update the node's value on successful write. */
+ {
+ RTStrFree(pNode->pszValue);
+ pNode->pszValue = RTStrDup(pszValue);
+ if (!pNode->pszValue)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VINF_NO_CHANGE; /* No update needed. */
+ }
+ else
+ {
+ /* No value specified. Deletion (or no action required). */
+ if (pNode->pszValue) /* Did we have a value before? Then the value needs to be deleted. */
+ {
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName,
+ 0, /* Flags */ NULL /* Value */);
+ VGSvcVerbose(4, "[PropCache %p]: Deleted '%s'='%s' (flags: %x), rc=%Rrc\n",
+ pCache, pNode->pszName, pNode->pszValue, pNode->fFlags, rc);
+ if (RT_SUCCESS(rc)) /* Only delete property value on successful Vbgl deletion. */
+ {
+ /* Delete property (but do not remove from cache) if not deleted yet. */
+ RTStrFree(pNode->pszValue);
+ pNode->pszValue = NULL;
+ }
+ }
+ else
+ rc = VINF_NO_CHANGE; /* No update needed. */
+ }
+
+ /* Release cache. */
+ RTCritSectLeave(&pCache->CritSect);
+ }
+
+ VGSvcVerbose(4, "[PropCache %p]: Updating '%s' resulted in rc=%Rrc\n", pCache, pszName, rc);
+
+ /* Delete temp stuff. */
+ RTStrFree(pszValue);
+ return rc;
+}
+
+
+/**
+ * Updates all cache values which are matching the specified path.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszValue The value to set. A NULL will delete the value.
+ * @param fFlags Flags to set.
+ * @param pszPathFormat The path format string. May not be null and has
+ * to be an absolute path.
+ * @param ... Format arguments.
+ */
+int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags,
+ const char *pszPathFormat, ...)
+{
+ RT_NOREF1(fFlags);
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPathFormat, VERR_INVALID_POINTER);
+
+ int rc = VERR_NOT_FOUND;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ /*
+ * Format the value first.
+ */
+ char *pszPath = NULL;
+ va_list va;
+ va_start(va, pszPathFormat);
+ RTStrAPrintfV(&pszPath, pszPathFormat, va);
+ va_end(va);
+ if (!pszPath)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ {
+ /* Iterate through all nodes and compare their paths. */
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ if (RTStrStr(pNodeIt->pszName, pszPath) == pNodeIt->pszName)
+ {
+ /** @todo Use some internal function to update the node directly, this is slow atm. */
+ rc = VGSvcPropCacheUpdate(pCache, pNodeIt->pszName, pszValue);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTStrFree(pszPath);
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return rc;
+}
+
+
+/**
+ * Flushes the cache by writing every item regardless of its state.
+ *
+ * @param pCache The property cache.
+ */
+int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNodeIt->pszName, pNodeIt->fFlags, pNodeIt->pszValue);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return rc;
+}
+
+
+/**
+ * Reset all temporary properties and destroy the cache.
+ *
+ * @param pCache The property cache.
+ */
+void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache)
+{
+ AssertPtrReturnVoid(pCache);
+ Assert(pCache->uClientID);
+
+ /* Lock the cache. */
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = RTListGetFirst(&pCache->NodeHead, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc);
+ while (pNode)
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNext = RTListNodeIsLast(&pCache->NodeHead, &pNode->NodeSucc)
+ ? NULL :
+ RTListNodeGetNext(&pNode->NodeSucc,
+ VBOXSERVICEVEPROPCACHEENTRY, NodeSucc);
+ RTListNodeRemove(&pNode->NodeSucc);
+
+ if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_TEMPORARY)
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pNode->pszValueReset);
+
+ AssertPtr(pNode->pszName);
+ RTStrFree(pNode->pszName);
+ RTStrFree(pNode->pszValue);
+ RTStrFree(pNode->pszValueReset);
+ pNode->fFlags = 0;
+
+ RTMemFree(pNode);
+
+ pNode = pNext;
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+
+ /* Destroy critical section. */
+ RTCritSectDelete(&pCache->CritSect);
+}
+