summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/UnattendedScript.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-server/UnattendedScript.cpp')
-rw-r--r--src/VBox/Main/src-server/UnattendedScript.cpp957
1 files changed, 957 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/UnattendedScript.cpp b/src/VBox/Main/src-server/UnattendedScript.cpp
new file mode 100644
index 00000000..ce0492df
--- /dev/null
+++ b/src/VBox/Main/src-server/UnattendedScript.cpp
@@ -0,0 +1,957 @@
+/* $Id: UnattendedScript.cpp $ */
+/** @file
+ * Classes for reading/parsing/saving scripts for unattended installation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
+#include "LoggingNew.h"
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include <VBox/com/ErrorInfo.h>
+
+#include "UnattendedScript.h"
+#include "UnattendedImpl.h"
+
+#include <iprt/err.h>
+
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+#include <iprt/getopt.h>
+#include <iprt/path.h>
+
+using namespace std;
+
+#ifdef VBOX_WITH_UNATTENDED
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+static const char g_szPrefix[] = "@@VBOX_";
+static const char g_szPrefixInsert[] = "@@VBOX_INSERT";
+static const char g_szPrefixInsertXxx[] = "@@VBOX_INSERT_";
+static const char g_szPrefixInsertExpr[] = "@@VBOX_INSERT[";
+static const char g_szPrefixCond[] = "@@VBOX_COND";
+static const char g_szPrefixCondXxx[] = "@@VBOX_COND_";
+static const char g_szPrefixCondExpr[] = "@@VBOX_COND[";
+static const char g_szPrefixCondElse[] = "@@VBOX_COND_ELSE@@";
+static const char g_szPrefixCondEnd[] = "@@VBOX_COND_END@@";
+static const char g_szPrefixSplitter[] = "@@VBOX_SPLITTER";
+
+
+/*********************************************************************************************************************************
+* UnattendedScriptTemplate Implementation *
+*********************************************************************************************************************************/
+
+UnattendedScriptTemplate::UnattendedScriptTemplate(Unattended *pUnattended, const char *pszDefaultTemplateFilename,
+ const char *pszDefaultFilename)
+ : BaseTextScript(pUnattended, pszDefaultTemplateFilename, pszDefaultFilename), mpUnattended(pUnattended)
+{
+}
+
+HRESULT UnattendedScriptTemplate::saveToString(Utf8Str &rStrDst)
+{
+ RTEXPREVAL hEvaluator = NIL_RTEXPREVAL;
+ int vrc = RTExprEvalCreate(&hEvaluator, 0, "unattended", this, UnattendedScriptTemplate::queryVariableForExpr);
+ AssertRCReturn(vrc, mpSetError->setErrorVrc(vrc));
+
+ struct
+ {
+ bool fSavedOutputting;
+ } aConds[8];
+ unsigned cConds = 0;
+ bool fOutputting = true;
+ HRESULT hrc = E_FAIL;
+ size_t offTemplate = 0;
+ size_t cchTemplate = mStrScriptFullContent.length();
+ rStrDst.setNull();
+ for (;;)
+ {
+ /*
+ * Find the next placeholder and add any text before it to the output.
+ */
+ size_t offPlaceholder = mStrScriptFullContent.find(g_szPrefix, offTemplate);
+ size_t cchToCopy = offPlaceholder != RTCString::npos ? offPlaceholder - offTemplate : cchTemplate - offTemplate;
+ if (cchToCopy > 0)
+ {
+ if (fOutputting)
+ {
+ try
+ {
+ rStrDst.append(mStrScriptFullContent, offTemplate , cchToCopy);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ offTemplate += cchToCopy;
+ }
+
+ /*
+ * Process placeholder.
+ */
+ if (offPlaceholder != RTCString::npos)
+ {
+ /*
+ * First we must find the end of the placeholder string.
+ */
+ size_t const cchMaxPlaceholder = RT_MIN(cchTemplate - offPlaceholder, _1K);
+ const char *pszPlaceholder = mStrScriptFullContent.c_str() + offPlaceholder;
+ size_t cchPlaceholder = sizeof(g_szPrefix) - 1;
+ char ch;
+ while ( cchPlaceholder < cchMaxPlaceholder
+ && (ch = pszPlaceholder[cchPlaceholder]) != '\0'
+ && (RT_C_IS_PRINT(ch) || RT_C_IS_SPACE(ch))
+ && ch != '@')
+ cchPlaceholder++;
+
+ if ( offPlaceholder + cchPlaceholder < cchTemplate
+ && pszPlaceholder[cchPlaceholder] == '@')
+ {
+ cchPlaceholder++;
+ if ( offPlaceholder + cchPlaceholder < cchTemplate
+ && pszPlaceholder[cchPlaceholder] == '@')
+ cchPlaceholder++;
+ }
+
+ if ( pszPlaceholder[cchPlaceholder - 1] != '@'
+ || pszPlaceholder[cchPlaceholder - 2] != '@'
+ || ( strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsert)) != 0
+ && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCond)) != 0
+ && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) != 0 ) )
+ {
+ hrc = mpSetError->setError(E_FAIL, tr("Malformed or too long template placeholder '%.*s'"),
+ cchPlaceholder, pszPlaceholder);
+ break;
+ }
+
+ offTemplate += cchPlaceholder;
+
+ /*
+ * @@VBOX_INSERT_XXX@@:
+ */
+ if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertXxx)) == 0)
+ {
+ /*
+ * Get the placeholder value and add it to the output.
+ */
+ RTCString strValue;
+ hrc = getReplacement(pszPlaceholder, cchPlaceholder, fOutputting, strValue);
+ if (SUCCEEDED(hrc))
+ {
+ if (fOutputting)
+ {
+ try
+ {
+ rStrDst.append(strValue);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ }
+ else
+ break;
+ }
+ /*
+ * @@VBOX_INSERT[expr]@@:
+ * @@VBOX_INSERT[expr]SH@@:
+ * @@VBOX_INSERT[expr]ELEMENT@@:
+ * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertExpr)) == 0)
+ {
+ /*
+ * Get the placeholder value and add it to the output.
+ */
+ char *pszValue = NULL;
+ hrc = getReplacementForExpr(hEvaluator, pszPlaceholder, cchPlaceholder, fOutputting, &pszValue);
+ if (SUCCEEDED(hrc))
+ {
+ if (fOutputting && pszValue)
+ {
+ try
+ {
+ rStrDst.append(pszValue);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ RTStrFree(pszValue);
+ }
+ else
+ break;
+ }
+ /*
+ * @@VBOX_COND_END@@: Pop one item of the conditional stack.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondEnd)) == 0)
+ {
+ if (cConds > 0)
+ {
+ cConds--;
+ fOutputting = aConds[cConds].fSavedOutputting;
+ }
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
+ g_szPrefixCondEnd, offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_COND_ELSE@@: Flip the output setting of the current condition.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondElse)) == 0)
+ {
+ if (cConds > 0)
+ fOutputting = !fOutputting;
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
+ g_szPrefixCondElse, offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_COND_XXX@@: Push the previous outputting state and combine it with the
+ * one from the condition.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondXxx)) == 0)
+ {
+ if (cConds + 1 < RT_ELEMENTS(aConds))
+ {
+ aConds[cConds].fSavedOutputting = fOutputting;
+ bool fNewOutputting = fOutputting;
+ hrc = getConditional(pszPlaceholder, cchPlaceholder, &fNewOutputting);
+ if (SUCCEEDED(hrc))
+ fOutputting = fOutputting && fNewOutputting;
+ else
+ break;
+ cConds++;
+ }
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Too deep conditional nesting at offset %zu (%#zx)"),
+ offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_COND[expr]@@: Push the previous outputting state and combine it with the
+ * one from the condition.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondExpr)) == 0)
+ {
+ if (cConds + 1 < RT_ELEMENTS(aConds))
+ {
+ aConds[cConds].fSavedOutputting = fOutputting;
+ bool fNewOutputting = fOutputting;
+ hrc = resolveConditionalExpr(hEvaluator, pszPlaceholder, cchPlaceholder, &fNewOutputting);
+ if (SUCCEEDED(hrc))
+ fOutputting = fOutputting && fNewOutputting;
+ else
+ break;
+ cConds++;
+ }
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Too deep conditional nesting at offset %zu (%#zx)"),
+ offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_SPLITTER_START/END[filename]@@: Ignored in this pass.
+ */
+ else
+ {
+ Assert(strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) == 0);
+ if (fOutputting)
+ {
+ try
+ {
+ rStrDst.append(pszPlaceholder, cchPlaceholder);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * Done?
+ */
+ if (offTemplate >= cchTemplate)
+ {
+ if (cConds == 0)
+ {
+ RTExprEvalRelease(hEvaluator);
+ return S_OK;
+ }
+ if (cConds == 1)
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing @@VBOX_COND_END@@"));
+ else
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing %u @@VBOX_COND_END@@"), cConds);
+ break;
+ }
+ }
+
+ /* failed */
+ rStrDst.setNull();
+ RTExprEvalRelease(hEvaluator);
+ return hrc;
+}
+
+HRESULT UnattendedScriptTemplate::getReplacement(const char *pachPlaceholder, size_t cchPlaceholder,
+ bool fOutputting, RTCString &rValue)
+{
+ /*
+ * Check for an escaping suffix. Drop the '@@'.
+ */
+ kEvalEscaping_T enmEscaping;
+#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
+ ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
+ && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
+ if (PLACEHOLDER_ENDS_WITH("_SH@@"))
+ {
+ cchPlaceholder -= 3 + 2;
+ enmEscaping = kValueEscaping_Bourne;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("_ELEMENT@@"))
+ {
+ cchPlaceholder -= 8 + 2;
+ enmEscaping = kValueEscaping_XML_Element;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("_ATTRIB_DQ@@"))
+ {
+ cchPlaceholder -= 10 + 2;
+ enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
+ }
+ else
+ {
+ Assert(PLACEHOLDER_ENDS_WITH("@@"));
+ cchPlaceholder -= 2;
+ enmEscaping = kValueEscaping_None;
+ }
+#undef PLACEHOLDER_ENDS_WITH
+
+ /*
+ * Resolve and escape the value.
+ */
+ HRESULT hrc;
+ try
+ {
+ Utf8Str strTmp;
+ const char *pszReadOnlyValue = NULL;
+ int vrc = queryVariable(pachPlaceholder + sizeof(g_szPrefixInsertXxx) - 1,
+ cchPlaceholder - sizeof(g_szPrefixInsertXxx) + 1,
+ strTmp, fOutputting ? &pszReadOnlyValue : NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ if (fOutputting)
+ {
+ Assert(pszReadOnlyValue != NULL);
+ switch (enmEscaping)
+ {
+ case kValueEscaping_None:
+ rValue = pszReadOnlyValue;
+ return S_OK;
+
+ case kValueEscaping_Bourne:
+ case kValueEscaping_XML_Element:
+ case kValueEscaping_XML_Attribute_Double_Quotes:
+ {
+ switch (enmEscaping)
+ {
+ case kValueEscaping_Bourne:
+ {
+ const char * const papszArgs[2] = { pszReadOnlyValue, NULL };
+ char *pszEscaped = NULL;
+ vrc = RTGetOptArgvToString(&pszEscaped, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ rValue = pszEscaped;
+ RTStrFree(pszEscaped);
+ return S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ RTStrFree(pszEscaped);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc);
+ break;
+ }
+
+ case kValueEscaping_XML_Element:
+ rValue.printf("%RMes", pszReadOnlyValue);
+ return S_OK;
+
+ case kValueEscaping_XML_Attribute_Double_Quotes:
+ {
+ RTCString strTmp2;
+ strTmp2.printf("%RMas", pszReadOnlyValue);
+ rValue = RTCString(strTmp2, 1, strTmp2.length() - 2);
+ return S_OK;
+ }
+
+ default:
+ hrc = E_FAIL;
+ break;
+ }
+ break;
+ }
+
+ default:
+ AssertFailedStmt(hrc = E_FAIL);
+ break;
+ }
+ }
+ else
+ hrc = S_OK;
+ }
+ else
+ hrc = E_FAIL;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ rValue.setNull();
+ return hrc;
+}
+
+HRESULT UnattendedScriptTemplate::getReplacementForExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder, size_t cchPlaceholder,
+ bool fOutputting, char **ppszValue) RT_NOEXCEPT
+{
+ /*
+ * Process the tail of the placeholder to figure out the escaping rules.
+ *
+ * @@VBOX_INSERT[expr]@@:
+ * @@VBOX_INSERT[expr]SH@@:
+ * @@VBOX_INSERT[expr]ELEMENT@@:
+ * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
+ */
+ kEvalEscaping_T enmEscaping;
+#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
+ ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
+ && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
+ if (PLACEHOLDER_ENDS_WITH("]SH@@"))
+ {
+ cchPlaceholder -= sizeof("]SH@@") - 1;
+ enmEscaping = kValueEscaping_Bourne;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("]ELEMENT@@"))
+ {
+ cchPlaceholder -= sizeof("]ELEMENT@@") - 1;
+ enmEscaping = kValueEscaping_XML_Element;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("]ATTRIB_DQ@@"))
+ {
+ cchPlaceholder -= sizeof("]ATTRIB_DQ@@") - 1;
+ enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("]@@"))
+ {
+ cchPlaceholder -= sizeof("]@@") - 1;
+ enmEscaping = kValueEscaping_None;
+ }
+ else
+ return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_INSERT[expr]@@: Missing ']' (%.*s)"),
+ cchPlaceholder, pachPlaceholder);
+#undef PLACEHOLDER_ENDS_WITH
+
+ /* The placeholder prefix length. The expression is from cchPrefix to cchPlaceholder. */
+ size_t const cchPrefix = sizeof(g_szPrefixInsertExpr) - 1;
+ Assert(pachPlaceholder[cchPrefix - 1] == '[');
+
+ /*
+ * Evaluate the expression. We do this regardless of fOutput for now.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ char *pszValue = NULL;
+ int vrc = RTExprEvalToString(hEvaluator, &pachPlaceholder[cchPrefix], cchPlaceholder - cchPrefix, &pszValue,
+ RTErrInfoInitStatic(&ErrInfo));
+ LogFlowFunc(("RTExprEvalToString(%.*s) -> %Rrc pszValue=%s\n",
+ cchPlaceholder - cchPrefix, &pachPlaceholder[cchPrefix], vrc, pszValue));
+ if (RT_SUCCESS(vrc))
+ {
+ if (fOutputting)
+ {
+ switch (enmEscaping)
+ {
+ case kValueEscaping_None:
+ *ppszValue = pszValue;
+ pszValue = NULL;
+ break;
+
+ case kValueEscaping_Bourne:
+ {
+ const char * const papszArgs[2] = { pszValue, NULL };
+ vrc = RTGetOptArgvToString(ppszValue, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ break;
+ }
+
+ case kValueEscaping_XML_Element:
+ vrc = RTStrAPrintf(ppszValue, "%RMes", pszValue);
+ break;
+
+ case kValueEscaping_XML_Attribute_Double_Quotes:
+ vrc = RTStrAPrintf(ppszValue, "%RMas", pszValue);
+ if (RT_SUCCESS(vrc))
+ {
+ /* drop the quotes */
+ char *pszRet = *ppszValue;
+ size_t const cchRet = strlen(pszRet) - 2;
+ memmove(pszRet, &pszRet[1], cchRet);
+ pszRet[cchRet] = '\0';
+ }
+ break;
+
+ default:
+ AssertFailedStmt(vrc = VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ break;
+ }
+ RTStrFree(pszValue);
+ if (RT_FAILURE(vrc))
+ return mpSetError->setErrorVrc(vrc);
+ }
+ else
+ {
+ *ppszValue = NULL;
+ RTStrFree(pszValue);
+ }
+ }
+ else
+ return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
+ cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
+ return S_OK;
+}
+
+HRESULT UnattendedScriptTemplate::resolveConditionalExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder,
+ size_t cchPlaceholder, bool *pfOutputting) RT_NOEXCEPT
+{
+ /*
+ * Check the placeholder tail: @@VBOX_COND[expr]@@
+ */
+ static const char s_szTail[] = "]@@";
+ if (memcmp(&pachPlaceholder[cchPlaceholder - sizeof(s_szTail) + 1], RT_STR_TUPLE(s_szTail)) != 0)
+ return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND[expr]@@: Missing ']' (%.*s)"),
+ cchPlaceholder, pachPlaceholder);
+ Assert(pachPlaceholder[sizeof(g_szPrefixCondExpr) - 2 ] == '[');
+
+ /*
+ * Evaluate the expression.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ const char * const pchExpr = &pachPlaceholder[sizeof(g_szPrefixCondExpr) - 1];
+ size_t const cchExpr = cchPlaceholder - sizeof(g_szPrefixCondExpr) + 1 - sizeof(s_szTail) + 1;
+ int vrc = RTExprEvalToBool(hEvaluator, pchExpr, cchExpr, pfOutputting, RTErrInfoInitStatic(&ErrInfo));
+ LogFlowFunc(("RTExprEvalToBool(%.*s) -> %Rrc *pfOutputting=%s\n", cchExpr, pchExpr, vrc, *pfOutputting));
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
+ cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
+}
+
+/*static */ DECLCALLBACK(int)
+UnattendedScriptTemplate::queryVariableForExpr(const char *pchName, size_t cchName, void *pvUser, char **ppszValue) RT_NOEXCEPT
+{
+ UnattendedScriptTemplate *pThis = (UnattendedScriptTemplate *)pvUser;
+ int vrc;
+ try
+ {
+ const char *pszReadOnlyValue = NULL;
+ Utf8Str strTmp;
+ vrc = pThis->queryVariable(pchName, cchName, strTmp, ppszValue ? &pszReadOnlyValue : NULL);
+ if (ppszValue)
+ {
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrDupEx(ppszValue, pszReadOnlyValue);
+ else
+ *ppszValue = NULL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ *ppszValue = NULL;
+ }
+ return vrc;
+}
+
+int UnattendedScriptTemplate::queryVariable(const char *pchName, size_t cchName, Utf8Str &rstrTmp, const char **ppszValue)
+{
+#define IS_MATCH(a_szMatch) \
+ (cchName == sizeof(a_szMatch) - 1U && memcmp(pchName, a_szMatch, sizeof(a_szMatch) - 1U) == 0)
+
+ const char *pszValue;
+
+ /*
+ * Variables
+ */
+ if (IS_MATCH("USER_LOGIN"))
+ pszValue = mpUnattended->i_getUser().c_str();
+ else if (IS_MATCH("USER_PASSWORD"))
+ pszValue = mpUnattended->i_getPassword().c_str();
+ else if (IS_MATCH("ROOT_PASSWORD"))
+ pszValue = mpUnattended->i_getPassword().c_str();
+ else if (IS_MATCH("USER_FULL_NAME"))
+ pszValue = mpUnattended->i_getFullUserName().c_str();
+ else if (IS_MATCH("PRODUCT_KEY"))
+ pszValue = mpUnattended->i_getProductKey().c_str();
+ else if (IS_MATCH("POST_INSTALL_COMMAND"))
+ pszValue = mpUnattended->i_getPostInstallCommand().c_str();
+ else if (IS_MATCH("AUXILIARY_INSTALL_DIR"))
+ pszValue = mpUnattended->i_getAuxiliaryInstallDir().c_str();
+ else if (IS_MATCH("IMAGE_INDEX"))
+ pszValue = rstrTmp.printf("%u", mpUnattended->i_getImageIndex()).c_str();
+ else if (IS_MATCH("OS_ARCH"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "amd64" : "x86";
+ else if (IS_MATCH("OS_ARCH2"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "x86";
+ else if (IS_MATCH("OS_ARCH3"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i386";
+ else if (IS_MATCH("OS_ARCH4"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i486";
+ else if (IS_MATCH("OS_ARCH6"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i686";
+ else if (IS_MATCH("GUEST_OS_VERSION"))
+ pszValue = mpUnattended->i_getDetectedOSVersion().c_str();
+ else if (IS_MATCH("GUEST_OS_MAJOR_VERSION"))
+ {
+ Utf8Str const &rstrOsVer = mpUnattended->i_getDetectedOSVersion();
+ size_t offDot = rstrOsVer.find('.');
+ if (offDot > 0 && offDot != Utf8Str::npos)
+ pszValue = rstrTmp.assign(rstrOsVer, 0, offDot).c_str(); /* caller catches std::bad_alloc */
+ else if (!ppszValue)
+ return VERR_NOT_FOUND;
+ else
+ {
+ mpSetError->setErrorBoth(E_FAIL, VERR_NO_DATA, tr("Unknown guest OS major version '%s'"), rstrOsVer.c_str());
+ return VERR_NO_DATA;
+ }
+ }
+ else if (IS_MATCH("TIME_ZONE_UX"))
+ pszValue = mpUnattended->i_getTimeZoneInfo()
+ ? mpUnattended->i_getTimeZoneInfo()->pszUnixName : mpUnattended->i_getTimeZone().c_str();
+ else if (IS_MATCH("TIME_ZONE_WIN_NAME"))
+ {
+ PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
+ if (pInfo)
+ pszValue = pInfo->pszWindowsName ? pInfo->pszWindowsName : "GMT";
+ else
+ pszValue = mpUnattended->i_getTimeZone().c_str();
+ }
+ else if (IS_MATCH("TIME_ZONE_WIN_INDEX"))
+ {
+ PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
+ if (pInfo)
+ pszValue = rstrTmp.printf("%u", pInfo->idxWindows ? pInfo->idxWindows : 85 /*GMT*/).c_str();
+ else
+ pszValue = mpUnattended->i_getTimeZone().c_str();
+ }
+ else if (IS_MATCH("LOCALE"))
+ pszValue = mpUnattended->i_getLocale().c_str();
+ else if (IS_MATCH("DASH_LOCALE"))
+ {
+ Assert(mpUnattended->i_getLocale()[2] == '_');
+ pszValue = rstrTmp.assign(mpUnattended->i_getLocale()).replace(2, 1, "-").c_str();
+ }
+ else if (IS_MATCH("LANGUAGE"))
+ pszValue = mpUnattended->i_getLanguage().c_str();
+ else if (IS_MATCH("COUNTRY"))
+ pszValue = mpUnattended->i_getCountry().c_str();
+ else if (IS_MATCH("HOSTNAME_FQDN"))
+ pszValue = mpUnattended->i_getHostname().c_str();
+ else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN"))
+ pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, mpUnattended->i_getHostname().find(".")).c_str();
+ else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN_MAX_15"))
+ pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, RT_MIN(mpUnattended->i_getHostname().find("."), 15)).c_str();
+ else if (IS_MATCH("HOSTNAME_DOMAIN"))
+ pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), mpUnattended->i_getHostname().find(".") + 1).c_str();
+ else if (IS_MATCH("PROXY"))
+ pszValue = mpUnattended->i_getProxy().c_str();
+ /*
+ * Indicator variables.
+ */
+ else if (IS_MATCH("IS_INSTALLING_ADDITIONS"))
+ pszValue = mpUnattended->i_getInstallGuestAdditions() ? "1" : "0";
+ else if (IS_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
+ pszValue = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0 ? "1" : "0";
+ else if (IS_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
+ pszValue = mpUnattended->i_getInstallTestExecService() ? "1" : "0";
+ else if (IS_MATCH("HAS_POST_INSTALL_COMMAND"))
+ pszValue = mpUnattended->i_getPostInstallCommand().isNotEmpty() ? "1" : "0";
+ else if (IS_MATCH("HAS_PRODUCT_KEY"))
+ pszValue = mpUnattended->i_getProductKey().isNotEmpty() ? "1" : "0";
+ else if (IS_MATCH("IS_MINIMAL_INSTALLATION"))
+ pszValue = mpUnattended->i_isMinimalInstallation() ? "1" : "0";
+ else if (IS_MATCH("IS_FIRMWARE_UEFI"))
+ pszValue = mpUnattended->i_isFirmwareEFI() ? "1" : "0";
+ else if (IS_MATCH("IS_RTC_USING_UTC"))
+ pszValue = mpUnattended->i_isRtcUsingUtc() ? "1" : "0";
+ else if (IS_MATCH("HAS_PROXY"))
+ pszValue = mpUnattended->i_getProxy().isNotEmpty() ? "1" : "0";
+ /*
+ * Unknown variable.
+ */
+ else if (!ppszValue)
+ return VERR_NOT_FOUND;
+ else
+ {
+ mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown variable '%.*s'"), cchName, pchName);
+ return VERR_NO_DATA;
+ }
+ if (ppszValue)
+ *ppszValue = pszValue;
+ return VINF_SUCCESS;
+}
+
+HRESULT UnattendedScriptTemplate::getConditional(const char *pachPlaceholder, size_t cchPlaceholder, bool *pfOutputting)
+{
+#define IS_PLACEHOLDER_MATCH(a_szMatch) \
+ ( cchPlaceholder == sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U \
+ && memcmp(pachPlaceholder, "@@VBOX_COND_" a_szMatch "@@", sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U) == 0)
+
+ /* Install Guest Additions: */
+ if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_ADDITIONS"))
+ *pfOutputting = mpUnattended->i_getInstallGuestAdditions();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_ADDITIONS"))
+ *pfOutputting = !mpUnattended->i_getInstallGuestAdditions();
+ /* User == Administrator: */
+ else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
+ *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0;
+ else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_NOT_ADMINISTRATOR"))
+ *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) != 0;
+ /* Install TXS: */
+ else if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
+ *pfOutputting = mpUnattended->i_getInstallTestExecService();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_TEST_EXEC_SERVICE"))
+ *pfOutputting = !mpUnattended->i_getInstallTestExecService();
+ /* Post install command: */
+ else if (IS_PLACEHOLDER_MATCH("HAS_POST_INSTALL_COMMAND"))
+ *pfOutputting = mpUnattended->i_getPostInstallCommand().isNotEmpty();
+ else if (IS_PLACEHOLDER_MATCH("HAS_NO_POST_INSTALL_COMMAND"))
+ *pfOutputting = mpUnattended->i_getPostInstallCommand().isEmpty();
+ /* Product key: */
+ else if (IS_PLACEHOLDER_MATCH("HAS_PRODUCT_KEY"))
+ *pfOutputting = mpUnattended->i_getProductKey().isNotEmpty();
+ else if (IS_PLACEHOLDER_MATCH("HAS_NO_PRODUCT_KEY"))
+ *pfOutputting = mpUnattended->i_getProductKey().isEmpty();
+ /* Minimal installation: */
+ else if (IS_PLACEHOLDER_MATCH("IS_MINIMAL_INSTALLATION"))
+ *pfOutputting = mpUnattended->i_isMinimalInstallation();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_MINIMAL_INSTALLATION"))
+ *pfOutputting = !mpUnattended->i_isMinimalInstallation();
+ /* Is firmware UEFI: */
+ else if (IS_PLACEHOLDER_MATCH("IS_FIRMWARE_UEFI"))
+ *pfOutputting = mpUnattended->i_isFirmwareEFI();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_FIRMWARE_UEFI"))
+ *pfOutputting = !mpUnattended->i_isFirmwareEFI();
+ /* Is RTC using UTC (i.e. set to UTC time on startup): */
+ else if (IS_PLACEHOLDER_MATCH("IS_RTC_USING_UTC"))
+ *pfOutputting = mpUnattended->i_isRtcUsingUtc();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_RTC_USING_UTC"))
+ *pfOutputting = !mpUnattended->i_isRtcUsingUtc();
+ else if (IS_PLACEHOLDER_MATCH("HAS_PROXY"))
+ *pfOutputting = mpUnattended->i_getProxy().isNotEmpty();
+ else if (IS_PLACEHOLDER_MATCH("AVOID_UPDATES_OVER_NETWORK"))
+ *pfOutputting = mpUnattended->i_getAvoidUpdatesOverNetwork();
+ else
+ return mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown conditional placeholder '%.*s'"),
+ cchPlaceholder, pachPlaceholder);
+ return S_OK;
+#undef IS_PLACEHOLDER_MATCH
+}
+
+#endif /* VBOX_WITH_UNATTENDED */
+#if 0 /* Keeping this a reference */
+
+
+/*********************************************************************************************************************************
+* UnattendedSUSEXMLScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT UnattendedSUSEXMLScript::parse()
+{
+ HRESULT hrc = UnattendedXMLScript::parse();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Check that we've got the right root element type.
+ */
+ const xml::ElementNode *pelmRoot = mDoc.getRootElement();
+ if ( pelmRoot
+ && strcmp(pelmRoot->getName(), "profile") == 0)
+ {
+ /*
+ * Work thought the sections.
+ */
+ try
+ {
+ LoopThruSections(pelmRoot);
+ hrc = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ else if (pelmRoot)
+ hrc = mpSetError->setError(E_FAIL, tr("XML document root element is '%s' instead of 'profile'"),
+ pelmRoot->getName());
+ else
+ hrc = mpSetError->setError(E_FAIL, tr("Missing XML root element"));
+ }
+ return hrc;
+}
+
+HRESULT UnattendedSUSEXMLScript::setFieldInElement(xml::ElementNode *pElement, const DataId enmDataId, const Utf8Str &rStrValue)
+{
+ /*
+ * Don't set empty values.
+ */
+ if (rStrValue.isEmpty())
+ {
+ Utf8Str strProbableValue;
+ try
+ {
+ strProbableValue = createProbableValue(enmDataId, pElement);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, strProbableValue);
+ }
+ return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, rStrValue);
+}
+
+HRESULT UnattendedSUSEXMLScript::LoopThruSections(const xml::ElementNode *pelmRoot)
+{
+ xml::NodesLoop loopChildren(*pelmRoot);
+ const xml::ElementNode *pelmOuterLoop;
+ while ((pelmOuterLoop = loopChildren.forAllNodes()) != NULL)
+ {
+ const char *pcszElemName = pelmOuterLoop->getName();
+ if (!strcmp(pcszElemName, "users"))
+ {
+ xml::NodesLoop loopUsers(*pelmOuterLoop);
+ const xml::ElementNode *pelmUser;
+ while ((pelmUser = loopUsers.forAllNodes()) != NULL)
+ {
+ HRESULT hrc = HandleUserAccountsSection(pelmUser);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ }
+ return S_OK;
+}
+
+HRESULT UnattendedSUSEXMLScript::HandleUserAccountsSection(const xml::ElementNode *pelmSection)
+{
+ xml::NodesLoop loopUser(*pelmSection);
+
+ const xml::ElementNode *pelmCur;
+ while ((pelmCur = loopUser.forAllNodes()) != NULL)
+ {
+ const char *pszValue = pelmCur->getValue();
+#ifdef LOG_ENABLED
+ if (!RTStrCmp(pelmCur->getName(), "uid"))
+ LogRelFunc(("UnattendedSUSEXMLScript::HandleUserAccountsSection profile/users/%s/%s = %s\n",
+ pelmSection->getName(), pelmCur->getName(), pszValue));
+#endif
+
+ if (!RTStrCmp(pszValue, "$homedir"))
+ mNodesForCorrectionMap.insert(make_pair(USERHOMEDIR_ID, pelmCur));
+
+ if (!RTStrCmp(pszValue, "$user"))
+ mNodesForCorrectionMap.insert(make_pair(USERNAME_ID, pelmCur));
+
+ if (!RTStrCmp(pszValue, "$password"))
+ mNodesForCorrectionMap.insert(make_pair(USERPASSWORD_ID, pelmCur));
+ }
+ return S_OK;
+}
+
+Utf8Str UnattendedSUSEXMLScript::createProbableValue(const DataId enmDataId, const xml::ElementNode *pCurElem)
+{
+ const xml::ElementNode *pElem = pCurElem;
+
+ switch (enmDataId)
+ {
+ case USERHOMEDIR_ID:
+// if ((pElem = pElem->findChildElement("home")))
+// {
+ return createProbableUserHomeDir(pElem);
+// }
+ break;
+ default:
+ break;
+ }
+
+ return Utf8Str::Empty;
+}
+
+Utf8Str UnattendedSUSEXMLScript::createProbableUserHomeDir(const xml::ElementNode *pCurElem)
+{
+ Utf8Str strCalcValue;
+ const xml::ElementNode *pElem = pCurElem->findNextSibilingElement("username");
+ if (pElem)
+ {
+ const char *pszValue = pElem->getValue();
+ strCalcValue = "/home/";
+ strCalcValue.append(pszValue);
+ }
+
+ return strCalcValue;
+}
+#endif /* just for reference */