From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Main/xml/Makefile.kup | 0 src/VBox/Main/xml/SchemaDefs.xsl | 246 + src/VBox/Main/xml/Settings.cpp | 9735 ++++++++++++++++++++ src/VBox/Main/xml/VirtualBox-settings.xsd | 1514 +++ src/VBox/Main/xml/ovfreader.cpp | 1090 +++ src/VBox/Main/xml/samples/VirtualBox-global.xml | 62 + .../Main/xml/samples/VirtualBox-machine-linux.xml | 65 + .../xml/samples/VirtualBox-machine-windows.xml | 203 + 8 files changed, 12915 insertions(+) create mode 100644 src/VBox/Main/xml/Makefile.kup create mode 100644 src/VBox/Main/xml/SchemaDefs.xsl create mode 100644 src/VBox/Main/xml/Settings.cpp create mode 100644 src/VBox/Main/xml/VirtualBox-settings.xsd create mode 100644 src/VBox/Main/xml/ovfreader.cpp create mode 100644 src/VBox/Main/xml/samples/VirtualBox-global.xml create mode 100644 src/VBox/Main/xml/samples/VirtualBox-machine-linux.xml create mode 100644 src/VBox/Main/xml/samples/VirtualBox-machine-windows.xml (limited to 'src/VBox/Main/xml') diff --git a/src/VBox/Main/xml/Makefile.kup b/src/VBox/Main/xml/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/Main/xml/SchemaDefs.xsl b/src/VBox/Main/xml/SchemaDefs.xsl new file mode 100644 index 00000000..262ea41a --- /dev/null +++ b/src/VBox/Main/xml/SchemaDefs.xsl @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Value '' of parameter 'mode' is invalid! + + + + + + + + + +/* + * DO NOT EDIT. + * + * This header is automatically generated from the VirtualBox XML Settings + * Schema and contains selected schema constraints declared in C++. + */ + +#ifndef ____H_SCHEMADEFS +#define ____H_SCHEMADEFS + +namespace SchemaDefs +{ + enum + { + + + + + DummyTerminator + }; + + + + +} + +#endif // !____H_SCHEMADEFS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/* + * DO NOT EDIT. + * + * This source is automatically generated from the VirtualBox XML Settings + * Schema and contains selected schema constraints defined in C++. + */ + +#include "SchemaDefs.h" + +namespace SchemaDefs +{ + + + + +} + + + + + + diff --git a/src/VBox/Main/xml/Settings.cpp b/src/VBox/Main/xml/Settings.cpp new file mode 100644 index 00000000..fa1c1d82 --- /dev/null +++ b/src/VBox/Main/xml/Settings.cpp @@ -0,0 +1,9735 @@ +/* $Id: Settings.cpp $ */ +/** @file + * Settings File Manipulation API. + * + * Two classes, MainConfigFile and MachineConfigFile, represent the VirtualBox.xml and + * machine XML files. They share a common ancestor class, ConfigFileBase, which shares + * functionality such as talking to the XML back-end classes and settings version management. + * + * The code can read all VirtualBox settings files version 1.3 and higher. That version was + * written by VirtualBox 2.0. It can write settings version 1.7 (used by VirtualBox 2.2 and + * 3.0) and 1.9 (used by VirtualBox 3.1) and newer ones obviously. + * + * The settings versions enum is defined in src/VBox/Main/idl/VirtualBox.xidl. To introduce + * a new settings version (should be necessary at most once per VirtualBox major release, + * if at all), add a new SettingsVersion value to that enum and grep for the previously + * highest value to see which code in here needs adjusting. + * + * Certainly ConfigFileBase::ConfigFileBase() will. Change VBOX_XML_VERSION below as well. + * VBOX_XML_VERSION does not have to be changed if the settings for a default VM do not + * touch newly introduced attributes or tags. It has the benefit that older VirtualBox + * versions do not trigger their "newer" code path. + * + * Once a new settings version has been added, these are the rules for introducing a new + * setting: If an XML element or attribute or value is introduced that was not present in + * previous versions, then settings version checks need to be introduced. See the + * SettingsVersion enumeration in src/VBox/Main/idl/VirtualBox.xidl for details about which + * version was used when. + * + * The settings versions checks are necessary because since version 3.1, VirtualBox no longer + * automatically converts XML settings files but only if necessary, that is, if settings are + * present that the old format does not support. If we write an element or attribute to a + * settings file of an older version, then an old VirtualBox (before 3.1) will attempt to + * validate it with XML schema, and that will certainly fail. + * + * So, to introduce a new setting: + * + * 1) Make sure the constructor of corresponding settings structure has a proper default. + * + * 2) In the settings reader method, try to read the setting; if it's there, great, if not, + * the default value will have been set by the constructor. The rule is to be tolerant + * here. + * + * 3) In MachineConfigFile::bumpSettingsVersionIfNeeded(), check if the new setting has + * a non-default value (i.e. that differs from the constructor). If so, bump the + * settings version to the current version so the settings writer (4) can write out + * the non-default value properly. + * + * So far a corresponding method for MainConfigFile has not been necessary since there + * have been no incompatible changes yet. + * + * 4) In the settings writer method, write the setting _only_ if the current settings + * version (stored in m->sv) is high enough. That is, for VirtualBox 4.0, write it + * only if (m->sv >= SettingsVersion_v1_11). + * + * 5) You _must_ update xml/VirtualBox-settings.xsd to contain the new tags and attributes. + * Check that settings file from before and after your change are validating properly. + * Use "kmk testvalidsettings", it should not find any files which don't validate. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_MAIN +#include "VBox/com/string.h" +#include "VBox/settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef RT_OS_WINDOWS +# include /* For RTSystemGetNtVersion() / RTSYSTEM_MAKE_NT_VERSION. */ +#endif +#include + +// Guest Properties validation. +#include "VBox/HostServices/GuestPropertySvc.h" + +// generated header +#include "SchemaDefs.h" + +#include "HashedPw.h" +#include "LoggingNew.h" + +using namespace com; +using namespace settings; + +//////////////////////////////////////////////////////////////////////////////// +// +// Defines +// +//////////////////////////////////////////////////////////////////////////////// + +/** VirtualBox XML settings namespace */ +#define VBOX_XML_NAMESPACE "http://www.virtualbox.org/" + +/** VirtualBox XML schema location (relative URI) */ +#define VBOX_XML_SCHEMA "VirtualBox-settings.xsd" + +/** VirtualBox XML settings version number substring ("x.y") */ +#define VBOX_XML_VERSION "1.12" + +/** VirtualBox OVF settings import default version number substring ("x.y"). + * + * Think twice before changing this, as all VirtualBox versions before 5.1 + * wrote the settings version when exporting, but totally ignored it on + * importing (while it should have been a mandatory attribute), so 3rd party + * software out there creates OVF files with the VirtualBox specific settings + * but lacking the version attribute. This shouldn't happen any more, but + * breaking existing OVF files isn't nice. */ +#define VBOX_XML_IMPORT_VERSION "1.15" + +/** VirtualBox XML settings version platform substring */ +#if defined (RT_OS_DARWIN) +# define VBOX_XML_PLATFORM "macosx" +#elif defined (RT_OS_FREEBSD) +# define VBOX_XML_PLATFORM "freebsd" +#elif defined (RT_OS_LINUX) +# define VBOX_XML_PLATFORM "linux" +#elif defined (RT_OS_NETBSD) +# define VBOX_XML_PLATFORM "netbsd" +#elif defined (RT_OS_OPENBSD) +# define VBOX_XML_PLATFORM "openbsd" +#elif defined (RT_OS_OS2) +# define VBOX_XML_PLATFORM "os2" +#elif defined (RT_OS_SOLARIS) +# define VBOX_XML_PLATFORM "solaris" +#elif defined (RT_OS_WINDOWS) +# define VBOX_XML_PLATFORM "windows" +#else +# error Unsupported platform! +#endif + +/** VirtualBox XML settings full version string ("x.y-platform") */ +#define VBOX_XML_VERSION_FULL VBOX_XML_VERSION "-" VBOX_XML_PLATFORM + +/** VirtualBox OVF import default settings full version string ("x.y-platform") */ +#define VBOX_XML_IMPORT_VERSION_FULL VBOX_XML_IMPORT_VERSION "-" VBOX_XML_PLATFORM + +//////////////////////////////////////////////////////////////////////////////// +// +// Internal data +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Opaque data structore for ConfigFileBase (only declared + * in header, defined only here). + */ + +struct ConfigFileBase::Data +{ + Data() + : pDoc(NULL), + pelmRoot(NULL), + sv(SettingsVersion_Null), + svRead(SettingsVersion_Null) + {} + + ~Data() + { + cleanup(); + } + + RTCString strFilename; + bool fFileExists; + + xml::Document *pDoc; + xml::ElementNode *pelmRoot; + + com::Utf8Str strSettingsVersionFull; // e.g. "1.7-linux" + SettingsVersion_T sv; // e.g. SettingsVersion_v1_7 + + SettingsVersion_T svRead; // settings version that the original file had when it was read, + // or SettingsVersion_Null if none + + void copyFrom(const Data &d) + { + strFilename = d.strFilename; + fFileExists = d.fFileExists; + strSettingsVersionFull = d.strSettingsVersionFull; + sv = d.sv; + svRead = d.svRead; + } + + void cleanup() + { + if (pDoc) + { + delete pDoc; + pDoc = NULL; + pelmRoot = NULL; + } + } +}; + +/** + * Private exception class (not in the header file) that makes + * throwing xml::LogicError instances easier. That class is public + * and should be caught by client code. + */ +class settings::ConfigFileError : public xml::LogicError +{ +public: + ConfigFileError(const ConfigFileBase *file, + const xml::Node *pNode, + const char *pcszFormat, ...) + : xml::LogicError() + { + va_list args; + va_start(args, pcszFormat); + Utf8Str strWhat(pcszFormat, args); + va_end(args); + + Utf8Str strLine; + if (pNode) + strLine = Utf8StrFmt(" (line %RU32)", pNode->getLineNumber()); + + const char *pcsz = strLine.c_str(); + Utf8StrFmt str(N_("Error in %s%s -- %s"), + file->m->strFilename.c_str(), + (pcsz) ? pcsz : "", + strWhat.c_str()); + + setWhat(str.c_str()); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// ConfigFileBase +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Constructor. Allocates the XML internals, parses the XML file if + * pstrFilename is != NULL and reads the settings version from it. + * @param pstrFilename + */ +ConfigFileBase::ConfigFileBase(const com::Utf8Str *pstrFilename) + : m(new Data) +{ + m->fFileExists = false; + + if (pstrFilename) + { + try + { + // reading existing settings file: + m->strFilename = *pstrFilename; + + xml::XmlFileParser parser; + m->pDoc = new xml::Document; + parser.read(*pstrFilename, + *m->pDoc); + + m->fFileExists = true; + + m->pelmRoot = m->pDoc->getRootElement(); + if (!m->pelmRoot || !m->pelmRoot->nameEquals("VirtualBox")) + throw ConfigFileError(this, m->pelmRoot, N_("Root element in VirtualBox settings files must be \"VirtualBox\"")); + + if (!(m->pelmRoot->getAttributeValue("version", m->strSettingsVersionFull))) + throw ConfigFileError(this, m->pelmRoot, N_("Required VirtualBox/@version attribute is missing")); + + LogRel(("Loading settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str())); + + m->sv = parseVersion(m->strSettingsVersionFull, m->pelmRoot); + + // remember the settings version we read in case it gets upgraded later, + // so we know when to make backups + m->svRead = m->sv; + } + catch(...) + { + /* + * The destructor is not called when an exception is thrown in the constructor, + * so we have to do the cleanup here. + */ + delete m; + m = NULL; + throw; + } + } + else + { + // creating new settings file: + m->strSettingsVersionFull = VBOX_XML_VERSION_FULL; + m->sv = SettingsVersion_v1_12; + } +} + +ConfigFileBase::ConfigFileBase(const ConfigFileBase &other) + : m(new Data) +{ + copyBaseFrom(other); + m->strFilename = ""; + m->fFileExists = false; +} + +/** + * Clean up. + */ +ConfigFileBase::~ConfigFileBase() +{ + if (m) + { + delete m; + m = NULL; + } +} + +/** + * Helper function to convert a MediaType enum value into string from. + * @param t + */ +/*static*/ +const char *ConfigFileBase::stringifyMediaType(MediaType t) +{ + switch (t) + { + case HardDisk: + return "hard disk"; + case DVDImage: + return "DVD"; + case FloppyImage: + return "floppy"; + default: + AssertMsgFailed(("media type %d\n", t)); + return "UNKNOWN"; + } +} + +/** + * Helper function that parses a full version number. + * + * Allow future versions but fail if file is older than 1.6. Throws on errors. + * @returns settings version + * @param strVersion + * @param pElm + */ +SettingsVersion_T ConfigFileBase::parseVersion(const Utf8Str &strVersion, const xml::ElementNode *pElm) +{ + SettingsVersion_T sv = SettingsVersion_Null; + if (strVersion.length() > 3) + { + const char *pcsz = strVersion.c_str(); + + uint32_t uMajor = 0; + char ch; + while ( (ch = *pcsz) + && RT_C_IS_DIGIT(ch) ) + { + uMajor *= 10; + uMajor += (uint32_t)(ch - '0'); + ++pcsz; + } + + uint32_t uMinor = 0; + if (ch == '.') + { + pcsz++; + while ( (ch = *pcsz) + && RT_C_IS_DIGIT(ch)) + { + uMinor *= 10; + uMinor += (ULONG)(ch - '0'); + ++pcsz; + } + } + + if (uMajor == 1) + { + if (uMinor == 3) + sv = SettingsVersion_v1_3; + else if (uMinor == 4) + sv = SettingsVersion_v1_4; + else if (uMinor == 5) + sv = SettingsVersion_v1_5; + else if (uMinor == 6) + sv = SettingsVersion_v1_6; + else if (uMinor == 7) + sv = SettingsVersion_v1_7; + else if (uMinor == 8) + sv = SettingsVersion_v1_8; + else if (uMinor == 9) + sv = SettingsVersion_v1_9; + else if (uMinor == 10) + sv = SettingsVersion_v1_10; + else if (uMinor == 11) + sv = SettingsVersion_v1_11; + else if (uMinor == 12) + sv = SettingsVersion_v1_12; + else if (uMinor == 13) + sv = SettingsVersion_v1_13; + else if (uMinor == 14) + sv = SettingsVersion_v1_14; + else if (uMinor == 15) + sv = SettingsVersion_v1_15; + else if (uMinor == 16) + sv = SettingsVersion_v1_16; + else if (uMinor == 17) + sv = SettingsVersion_v1_17; + else if (uMinor == 18) + sv = SettingsVersion_v1_18; + else if (uMinor == 19) + sv = SettingsVersion_v1_19; + else if (uMinor > 19) + sv = SettingsVersion_Future; + } + else if (uMajor > 1) + sv = SettingsVersion_Future; + + Log(("Parsed settings version %d.%d to enum value %d\n", uMajor, uMinor, sv)); + } + + if (sv == SettingsVersion_Null) + throw ConfigFileError(this, pElm, N_("Cannot handle settings version '%s'"), strVersion.c_str()); + + return sv; +} + +/** + * Helper function that parses a UUID in string form into + * a com::Guid item. Accepts UUIDs both with and without + * "{}" brackets. Throws on errors. + * @param guid + * @param strUUID + * @param pElm + */ +void ConfigFileBase::parseUUID(Guid &guid, + const Utf8Str &strUUID, + const xml::ElementNode *pElm) const +{ + guid = strUUID.c_str(); + if (guid.isZero()) + throw ConfigFileError(this, pElm, N_("UUID \"%s\" has zero format"), strUUID.c_str()); + else if (!guid.isValid()) + throw ConfigFileError(this, pElm, N_("UUID \"%s\" has invalid format"), strUUID.c_str()); +} + +/** + * Parses the given string in str and attempts to treat it as an ISO + * date/time stamp to put into timestamp. Throws on errors. + * @param timestamp + * @param str + * @param pElm + */ +void ConfigFileBase::parseTimestamp(RTTIMESPEC ×tamp, + const com::Utf8Str &str, + const xml::ElementNode *pElm) const +{ + const char *pcsz = str.c_str(); + // yyyy-mm-ddThh:mm:ss + // "2009-07-10T11:54:03Z" + // 01234567890123456789 + // 1 + if (str.length() > 19) + { + // timezone must either be unspecified or 'Z' for UTC + if ( (pcsz[19]) + && (pcsz[19] != 'Z') + ) + throw ConfigFileError(this, pElm, N_("Cannot handle ISO timestamp '%s': is not UTC date"), str.c_str()); + + int32_t yyyy; + uint32_t mm, dd, hh, min, secs; + if ( (pcsz[4] == '-') + && (pcsz[7] == '-') + && (pcsz[10] == 'T') + && (pcsz[13] == ':') + && (pcsz[16] == ':') + ) + { + int vrc; + if ( (RT_SUCCESS(vrc = RTStrToInt32Ex(pcsz, NULL, 0, &yyyy))) + // could theoretically be negative but let's assume that nobody + // created virtual machines before the Christian era + && (RT_SUCCESS(vrc = RTStrToUInt32Ex(pcsz + 5, NULL, 0, &mm))) + && (RT_SUCCESS(vrc = RTStrToUInt32Ex(pcsz + 8, NULL, 0, &dd))) + && (RT_SUCCESS(vrc = RTStrToUInt32Ex(pcsz + 11, NULL, 0, &hh))) + && (RT_SUCCESS(vrc = RTStrToUInt32Ex(pcsz + 14, NULL, 0, &min))) + && (RT_SUCCESS(vrc = RTStrToUInt32Ex(pcsz + 17, NULL, 0, &secs))) + ) + { + RTTIME time = + { + yyyy, + (uint8_t)mm, + 0, + 0, + (uint8_t)dd, + (uint8_t)hh, + (uint8_t)min, + (uint8_t)secs, + 0, + RTTIME_FLAGS_TYPE_UTC, + 0 + }; + if (RTTimeNormalize(&time)) + if (RTTimeImplode(×tamp, &time)) + return; + } + + throw ConfigFileError(this, pElm, N_("Cannot parse ISO timestamp '%s': runtime error, %Rra"), str.c_str(), vrc); + } + + throw ConfigFileError(this, pElm, N_("Cannot parse ISO timestamp '%s': invalid format"), str.c_str()); + } +} + +/** + * Helper function that parses a Base64 formatted string into a binary blob. + * @param binary + * @param str + * @param pElm + */ +void ConfigFileBase::parseBase64(IconBlob &binary, + const Utf8Str &str, + const xml::ElementNode *pElm) const +{ +#define DECODE_STR_MAX _1M + const char* psz = str.c_str(); + ssize_t cbOut = RTBase64DecodedSize(psz, NULL); + if (cbOut > DECODE_STR_MAX) + throw ConfigFileError(this, pElm, N_("Base64 encoded data too long (%d > %d)"), cbOut, DECODE_STR_MAX); + else if (cbOut < 0) + throw ConfigFileError(this, pElm, N_("Base64 encoded data '%s' invalid"), psz); + binary.resize((size_t)cbOut); + int vrc = VINF_SUCCESS; + if (cbOut) + vrc = RTBase64Decode(psz, &binary.front(), (size_t)cbOut, NULL, NULL); + if (RT_FAILURE(vrc)) + { + binary.resize(0); + throw ConfigFileError(this, pElm, N_("Base64 encoded data could not be decoded (%Rrc)"), vrc); + } +} + +/** + * Helper to create a string for a RTTIMESPEC for writing out ISO timestamps. + * @param stamp + * @return + */ +com::Utf8Str ConfigFileBase::stringifyTimestamp(const RTTIMESPEC &stamp) const +{ + RTTIME time; + if (!RTTimeExplode(&time, &stamp)) + throw ConfigFileError(this, NULL, N_("Timespec %lld ms is invalid"), RTTimeSpecGetMilli(&stamp)); + + return Utf8StrFmt("%04u-%02u-%02uT%02u:%02u:%02uZ", + time.i32Year, time.u8Month, time.u8MonthDay, + time.u8Hour, time.u8Minute, time.u8Second); +} + +/** + * Helper to create a base64 encoded string out of a binary blob. + * @param str + * @param binary + * @throws std::bad_alloc and ConfigFileError + */ +void ConfigFileBase::toBase64(com::Utf8Str &str, const IconBlob &binary) const +{ + size_t cb = binary.size(); + if (cb > 0) + { + size_t cchOut = RTBase64EncodedLength(cb); + str.reserve(cchOut + 1); + int vrc = RTBase64Encode(&binary.front(), cb, str.mutableRaw(), str.capacity(), NULL); + if (RT_FAILURE(vrc)) + throw ConfigFileError(this, NULL, N_("Failed to convert binary data to base64 format (%Rrc)"), vrc); + str.jolt(); + } +} + +/** + * Helper method to read in an ExtraData subtree and stores its contents + * in the given map of extradata items. Used for both main and machine + * extradata (MainConfigFile and MachineConfigFile). + * @param elmExtraData + * @param map + */ +void ConfigFileBase::readExtraData(const xml::ElementNode &elmExtraData, + StringsMap &map) +{ + xml::NodesLoop nlLevel4(elmExtraData); + const xml::ElementNode *pelmExtraDataItem; + while ((pelmExtraDataItem = nlLevel4.forAllNodes())) + { + if (pelmExtraDataItem->nameEquals("ExtraDataItem")) + { + // + Utf8Str strName, strValue; + if ( pelmExtraDataItem->getAttributeValue("name", strName) + && pelmExtraDataItem->getAttributeValue("value", strValue) ) + map[strName] = strValue; + else + throw ConfigFileError(this, pelmExtraDataItem, N_("Required ExtraDataItem/@name or @value attribute is missing")); + } + } +} + +/** + * Reads \ entries from under the given elmDeviceFilters node and + * stores them in the given linklist. This is in ConfigFileBase because it's used + * from both MainConfigFile (for host filters) and MachineConfigFile (for machine + * filters). + * @param elmDeviceFilters + * @param ll + */ +void ConfigFileBase::readUSBDeviceFilters(const xml::ElementNode &elmDeviceFilters, + USBDeviceFiltersList &ll) +{ + xml::NodesLoop nl1(elmDeviceFilters, "DeviceFilter"); + const xml::ElementNode *pelmLevel4Child; + while ((pelmLevel4Child = nl1.forAllNodes())) + { + USBDeviceFilter flt; + flt.action = USBDeviceFilterAction_Ignore; + Utf8Str strAction; + if ( pelmLevel4Child->getAttributeValue("name", flt.strName) + && pelmLevel4Child->getAttributeValue("active", flt.fActive)) + { + if (!pelmLevel4Child->getAttributeValue("vendorId", flt.strVendorId)) + pelmLevel4Child->getAttributeValue("vendorid", flt.strVendorId); // used before 1.3 + if (!pelmLevel4Child->getAttributeValue("productId", flt.strProductId)) + pelmLevel4Child->getAttributeValue("productid", flt.strProductId); // used before 1.3 + pelmLevel4Child->getAttributeValue("revision", flt.strRevision); + pelmLevel4Child->getAttributeValue("manufacturer", flt.strManufacturer); + pelmLevel4Child->getAttributeValue("product", flt.strProduct); + if (!pelmLevel4Child->getAttributeValue("serialNumber", flt.strSerialNumber)) + pelmLevel4Child->getAttributeValue("serialnumber", flt.strSerialNumber); // used before 1.3 + pelmLevel4Child->getAttributeValue("port", flt.strPort); + + // the next 2 are irrelevant for host USB objects + pelmLevel4Child->getAttributeValue("remote", flt.strRemote); + pelmLevel4Child->getAttributeValue("maskedInterfaces", flt.ulMaskedInterfaces); + + // action is only used with host USB objects + if (pelmLevel4Child->getAttributeValue("action", strAction)) + { + if (strAction == "Ignore") + flt.action = USBDeviceFilterAction_Ignore; + else if (strAction == "Hold") + flt.action = USBDeviceFilterAction_Hold; + else + throw ConfigFileError(this, pelmLevel4Child, N_("Invalid value '%s' in DeviceFilter/@action attribute"), strAction.c_str()); + } + + ll.push_back(flt); + } + } +} + +/** + * Reads a media registry entry from the main VirtualBox.xml file. + * + * Whereas the current media registry code is fairly straightforward, it was quite a mess + * with settings format before 1.4 (VirtualBox 2.0 used settings format 1.3). The elements + * in the media registry were much more inconsistent, and different elements were used + * depending on the type of device and image. + * + * @param t + * @param elmMedium + * @param med + */ +void ConfigFileBase::readMediumOne(MediaType t, + const xml::ElementNode &elmMedium, + Medium &med) +{ + // + + Utf8Str strUUID; + if (!elmMedium.getAttributeValue("uuid", strUUID)) + throw ConfigFileError(this, &elmMedium, N_("Required %s/@uuid attribute is missing"), elmMedium.getName()); + + parseUUID(med.uuid, strUUID, &elmMedium); + + bool fNeedsLocation = true; + + if (t == HardDisk) + { + if (m->sv < SettingsVersion_v1_4) + { + // here the system is: + // + // + // + + fNeedsLocation = false; + bool fNeedsFilePath = true; + const xml::ElementNode *pelmImage; + if ((pelmImage = elmMedium.findChildElement("VirtualDiskImage"))) + med.strFormat = "VDI"; + else if ((pelmImage = elmMedium.findChildElement("VMDKImage"))) + med.strFormat = "VMDK"; + else if ((pelmImage = elmMedium.findChildElement("VHDImage"))) + med.strFormat = "VHD"; + else if ((pelmImage = elmMedium.findChildElement("ISCSIHardDisk"))) + { + med.strFormat = "iSCSI"; + + fNeedsFilePath = false; + // location is special here: current settings specify an "iscsi://user@server:port/target/lun" + // string for the location and also have several disk properties for these, whereas this used + // to be hidden in several sub-elements before 1.4, so compose a location string and set up + // the properties: + med.strLocation = "iscsi://"; + Utf8Str strUser, strServer, strPort, strTarget, strLun; + if (pelmImage->getAttributeValue("userName", strUser)) + { + med.strLocation.append(strUser); + med.strLocation.append("@"); + } + Utf8Str strServerAndPort; + if (pelmImage->getAttributeValue("server", strServer)) + { + strServerAndPort = strServer; + } + if (pelmImage->getAttributeValue("port", strPort)) + { + if (strServerAndPort.length()) + strServerAndPort.append(":"); + strServerAndPort.append(strPort); + } + med.strLocation.append(strServerAndPort); + if (pelmImage->getAttributeValue("target", strTarget)) + { + med.strLocation.append("/"); + med.strLocation.append(strTarget); + } + if (pelmImage->getAttributeValue("lun", strLun)) + { + med.strLocation.append("/"); + med.strLocation.append(strLun); + } + + if (strServer.length() && strPort.length()) + med.properties["TargetAddress"] = strServerAndPort; + if (strTarget.length()) + med.properties["TargetName"] = strTarget; + if (strUser.length()) + med.properties["InitiatorUsername"] = strUser; + Utf8Str strPassword; + if (pelmImage->getAttributeValue("password", strPassword)) + med.properties["InitiatorSecret"] = strPassword; + if (strLun.length()) + med.properties["LUN"] = strLun; + } + else if ((pelmImage = elmMedium.findChildElement("CustomHardDisk"))) + { + fNeedsFilePath = false; + fNeedsLocation = true; + // also requires @format attribute, which will be queried below + } + else + throw ConfigFileError(this, &elmMedium, N_("Required %s/VirtualDiskImage element is missing"), elmMedium.getName()); + + if (fNeedsFilePath) + { + if (!(pelmImage->getAttributeValuePath("filePath", med.strLocation))) + throw ConfigFileError(this, &elmMedium, N_("Required %s/@filePath attribute is missing"), elmMedium.getName()); + } + } + + if (med.strFormat.isEmpty()) // not set with 1.4 format above, or 1.4 Custom format? + if (!elmMedium.getAttributeValue("format", med.strFormat)) + throw ConfigFileError(this, &elmMedium, N_("Required %s/@format attribute is missing"), elmMedium.getName()); + + if (!elmMedium.getAttributeValue("autoReset", med.fAutoReset)) + med.fAutoReset = false; + + Utf8Str strType; + if (elmMedium.getAttributeValue("type", strType)) + { + // pre-1.4 used lower case, so make this case-insensitive + strType.toUpper(); + if (strType == "NORMAL") + med.hdType = MediumType_Normal; + else if (strType == "IMMUTABLE") + med.hdType = MediumType_Immutable; + else if (strType == "WRITETHROUGH") + med.hdType = MediumType_Writethrough; + else if (strType == "SHAREABLE") + med.hdType = MediumType_Shareable; + else if (strType == "READONLY") + med.hdType = MediumType_Readonly; + else if (strType == "MULTIATTACH") + med.hdType = MediumType_MultiAttach; + else + throw ConfigFileError(this, &elmMedium, N_("HardDisk/@type attribute must be one of Normal, Immutable, Writethrough, Shareable, Readonly or MultiAttach")); + } + } + else + { + if (m->sv < SettingsVersion_v1_4) + { + // DVD and floppy images before 1.4 had "src" attribute instead of "location" + if (!elmMedium.getAttributeValue("src", med.strLocation)) + throw ConfigFileError(this, &elmMedium, N_("Required %s/@src attribute is missing"), elmMedium.getName()); + + fNeedsLocation = false; + } + + if (!elmMedium.getAttributeValue("format", med.strFormat)) + { + // DVD and floppy images before 1.11 had no format attribute. assign the default. + med.strFormat = "RAW"; + } + + if (t == DVDImage) + med.hdType = MediumType_Readonly; + else if (t == FloppyImage) + med.hdType = MediumType_Writethrough; + } + + if (fNeedsLocation) + // current files and 1.4 CustomHardDisk elements must have a location attribute + if (!elmMedium.getAttributeValue("location", med.strLocation)) + throw ConfigFileError(this, &elmMedium, N_("Required %s/@location attribute is missing"), elmMedium.getName()); + + // 3.2 builds added Description as an attribute, read it silently + // and write it back as an element starting with 5.1.26 + elmMedium.getAttributeValue("Description", med.strDescription); + + xml::NodesLoop nlMediumChildren(elmMedium); + const xml::ElementNode *pelmMediumChild; + while ((pelmMediumChild = nlMediumChildren.forAllNodes())) + { + if (pelmMediumChild->nameEquals("Description")) + med.strDescription = pelmMediumChild->getValue(); + else if (pelmMediumChild->nameEquals("Property")) + { + // handle medium properties + Utf8Str strPropName, strPropValue; + if ( pelmMediumChild->getAttributeValue("name", strPropName) + && pelmMediumChild->getAttributeValue("value", strPropValue) ) + med.properties[strPropName] = strPropValue; + else + throw ConfigFileError(this, pelmMediumChild, N_("Required HardDisk/Property/@name or @value attribute is missing")); + } + } +} + +/** + * Reads a media registry entry from the main VirtualBox.xml file and + * likewise for all children where applicable. + * + * @param t + * @param elmMedium + * @param med + */ +void ConfigFileBase::readMedium(MediaType t, + const xml::ElementNode &elmMedium, + Medium &med) +{ + std::list llElementsTodo; + llElementsTodo.push_back(&elmMedium); + std::list llSettingsTodo; + llSettingsTodo.push_back(&med); + std::list llDepthsTodo; + llDepthsTodo.push_back(1); + + while (llElementsTodo.size() > 0) + { + const xml::ElementNode *pElement = llElementsTodo.front(); + llElementsTodo.pop_front(); + Medium *pMed = llSettingsTodo.front(); + llSettingsTodo.pop_front(); + uint32_t depth = llDepthsTodo.front(); + llDepthsTodo.pop_front(); + + if (depth > SETTINGS_MEDIUM_DEPTH_MAX) + throw ConfigFileError(this, pElement, N_("Maximum medium tree depth of %u exceeded"), SETTINGS_MEDIUM_DEPTH_MAX); + + readMediumOne(t, *pElement, *pMed); + + if (t != HardDisk) + return; + + // load all children + xml::NodesLoop nl2(*pElement, m->sv >= SettingsVersion_v1_4 ? "HardDisk" : "DiffHardDisk"); + const xml::ElementNode *pelmHDChild; + while ((pelmHDChild = nl2.forAllNodes())) + { + llElementsTodo.push_back(pelmHDChild); + pMed->llChildren.push_back(Medium::Empty); + llSettingsTodo.push_back(&pMed->llChildren.back()); + llDepthsTodo.push_back(depth + 1); + } + } +} + +/** + * Reads in the entire \ chunk and stores its media in the lists + * of the given MediaRegistry structure. + * + * This is used in both MainConfigFile and MachineConfigFile since starting with + * VirtualBox 4.0, we can have media registries in both. + * + * For pre-1.4 files, this gets called with the \ chunk instead. + * + * @param elmMediaRegistry + * @param mr + */ +void ConfigFileBase::readMediaRegistry(const xml::ElementNode &elmMediaRegistry, + MediaRegistry &mr) +{ + xml::NodesLoop nl1(elmMediaRegistry); + const xml::ElementNode *pelmChild1; + while ((pelmChild1 = nl1.forAllNodes())) + { + MediaType t = Error; + if (pelmChild1->nameEquals("HardDisks")) + t = HardDisk; + else if (pelmChild1->nameEquals("DVDImages")) + t = DVDImage; + else if (pelmChild1->nameEquals("FloppyImages")) + t = FloppyImage; + else + continue; + + xml::NodesLoop nl2(*pelmChild1); + const xml::ElementNode *pelmMedium; + while ((pelmMedium = nl2.forAllNodes())) + { + if ( t == HardDisk + && (pelmMedium->nameEquals("HardDisk"))) + { + mr.llHardDisks.push_back(Medium::Empty); + readMedium(t, *pelmMedium, mr.llHardDisks.back()); + } + else if ( t == DVDImage + && (pelmMedium->nameEquals("Image"))) + { + mr.llDvdImages.push_back(Medium::Empty); + readMedium(t, *pelmMedium, mr.llDvdImages.back()); + } + else if ( t == FloppyImage + && (pelmMedium->nameEquals("Image"))) + { + mr.llFloppyImages.push_back(Medium::Empty); + readMedium(t, *pelmMedium, mr.llFloppyImages.back()); + } + } + } +} + +/** + * This is common version for reading NAT port forward rule in per-_machine's_adapter_ and + * per-network approaches. + * Note: this function doesn't in fill given list from xml::ElementNodesList, because there is conflicting + * declaration in ovmfreader.h. + */ +void ConfigFileBase::readNATForwardRulesMap(const xml::ElementNode &elmParent, NATRulesMap &mapRules) +{ + xml::ElementNodesList plstRules; + elmParent.getChildElements(plstRules, "Forwarding"); + for (xml::ElementNodesList::iterator pf = plstRules.begin(); pf != plstRules.end(); ++pf) + { + NATRule rule; + uint32_t port = 0; + (*pf)->getAttributeValue("name", rule.strName); + (*pf)->getAttributeValue("proto", (uint32_t&)rule.proto); + (*pf)->getAttributeValue("hostip", rule.strHostIP); + (*pf)->getAttributeValue("hostport", port); + rule.u16HostPort = (uint16_t)port; + (*pf)->getAttributeValue("guestip", rule.strGuestIP); + (*pf)->getAttributeValue("guestport", port); + rule.u16GuestPort = (uint16_t)port; + mapRules.insert(std::make_pair(rule.strName, rule)); + } +} + +void ConfigFileBase::readNATLoopbacks(const xml::ElementNode &elmParent, NATLoopbackOffsetList &llLoopbacks) +{ + xml::ElementNodesList plstLoopbacks; + elmParent.getChildElements(plstLoopbacks, "Loopback4"); + for (xml::ElementNodesList::iterator lo = plstLoopbacks.begin(); + lo != plstLoopbacks.end(); ++lo) + { + NATHostLoopbackOffset loopback; + (*lo)->getAttributeValue("address", loopback.strLoopbackHostAddress); + (*lo)->getAttributeValue("offset", (uint32_t&)loopback.u32Offset); + llLoopbacks.push_back(loopback); + } +} + + +/** + * Adds a "version" attribute to the given XML element with the + * VirtualBox settings version (e.g. "1.10-linux"). Used by + * the XML format for the root element and by the OVF export + * for the vbox:Machine element. + * @param elm + */ +void ConfigFileBase::setVersionAttribute(xml::ElementNode &elm) +{ + const char *pcszVersion = NULL; + switch (m->sv) + { + case SettingsVersion_v1_8: + pcszVersion = "1.8"; + break; + + case SettingsVersion_v1_9: + pcszVersion = "1.9"; + break; + + case SettingsVersion_v1_10: + pcszVersion = "1.10"; + break; + + case SettingsVersion_v1_11: + pcszVersion = "1.11"; + break; + + case SettingsVersion_v1_12: + pcszVersion = "1.12"; + break; + + case SettingsVersion_v1_13: + pcszVersion = "1.13"; + break; + + case SettingsVersion_v1_14: + pcszVersion = "1.14"; + break; + + case SettingsVersion_v1_15: + pcszVersion = "1.15"; + break; + + case SettingsVersion_v1_16: + pcszVersion = "1.16"; + break; + + case SettingsVersion_v1_17: + pcszVersion = "1.17"; + break; + + case SettingsVersion_v1_18: + pcszVersion = "1.18"; + break; + + case SettingsVersion_v1_19: + pcszVersion = "1.19"; + break; + + default: + // catch human error: the assertion below will trigger in debug + // or dbgopt builds, so hopefully this will get noticed sooner in + // the future, because it's easy to forget top update something. + AssertMsg(m->sv <= SettingsVersion_v1_7, ("Settings.cpp: unexpected settings version %d, unhandled future version?\n", m->sv)); + // silently upgrade if this is less than 1.7 because that's the oldest we can write + if (m->sv <= SettingsVersion_v1_7) + { + pcszVersion = "1.7"; + m->sv = SettingsVersion_v1_7; + } + else + { + // This is reached for SettingsVersion_Future and forgotten + // settings version after SettingsVersion_v1_7, which should + // not happen (see assertion above). Set the version to the + // latest known version, to minimize loss of information, but + // as we can't predict the future we have to use some format + // we know, and latest should be the best choice. Note that + // for "forgotten settings" this may not be the best choice, + // but as it's an omission of someone who changed this file + // it's the only generic possibility. + pcszVersion = "1.19"; + m->sv = SettingsVersion_v1_19; + } + break; + } + + m->strSettingsVersionFull = Utf8StrFmt("%s-%s", + pcszVersion, + VBOX_XML_PLATFORM); // e.g. "linux" + elm.setAttribute("version", m->strSettingsVersionFull); +} + + +/** + * Creates a special backup file in case there is a version + * bump, so that it is possible to go back to the previous + * state. This is done only once (not for every settings + * version bump), when the settings version is newer than + * the version read from the config file. Must be called + * before ConfigFileBase::createStubDocument, because that + * method may alter information which this method needs. + */ +void ConfigFileBase::specialBackupIfFirstBump() +{ + // Since this gets called before the XML document is actually written out, + // this is where we must check whether we're upgrading the settings version + // and need to make a backup, so the user can go back to an earlier + // VirtualBox version and recover his old settings files. + if ( (m->svRead != SettingsVersion_Null) // old file exists? + && (m->svRead < m->sv) // we're upgrading? + ) + { + // compose new filename: strip off trailing ".xml"/".vbox" + Utf8Str strFilenameNew; + Utf8Str strExt = ".xml"; + if (m->strFilename.endsWith(".xml")) + strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 4); + else if (m->strFilename.endsWith(".vbox")) + { + strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 5); + strExt = ".vbox"; + } + + // and append something like "-1.3-linux.xml" + strFilenameNew.append("-"); + strFilenameNew.append(m->strSettingsVersionFull); // e.g. "1.3-linux" + strFilenameNew.append(strExt); // .xml for main config, .vbox for machine config + + // Copying the file cannot be avoided, as doing tricks with renaming + // causes trouble on OS X with aliases (which follow the rename), and + // on all platforms there is a risk of "losing" the VM config when + // running out of space, as a rename here couldn't be rolled back. + // Ignoring all errors besides running out of space is intentional, as + // we don't want to do anything if the file already exists. + int vrc = RTFileCopy(m->strFilename.c_str(), strFilenameNew.c_str()); + if (RT_UNLIKELY(vrc == VERR_DISK_FULL)) + throw ConfigFileError(this, NULL, N_("Cannot create settings backup file when upgrading to a newer settings format")); + + // do this only once + m->svRead = SettingsVersion_Null; + } +} + +/** + * Creates a new stub xml::Document in the m->pDoc member with the + * root "VirtualBox" element set up. This is used by both + * MainConfigFile and MachineConfigFile at the beginning of writing + * out their XML. + * + * Before calling this, it is the responsibility of the caller to + * set the "sv" member to the required settings version that is to + * be written. For newly created files, the settings version will be + * recent (1.12 or later if necessary); for files read in from disk + * earlier, it will be the settings version indicated in the file. + * However, this method will silently make sure that the settings + * version is always at least 1.7 and change it if necessary, since + * there is no write support for earlier settings versions. + */ +void ConfigFileBase::createStubDocument() +{ + Assert(m->pDoc == NULL); + m->pDoc = new xml::Document; + + m->pelmRoot = m->pDoc->createRootElement("VirtualBox", + "\n" + "** DO NOT EDIT THIS FILE.\n" + "** If you make changes to this file while any VirtualBox related application\n" + "** is running, your changes will be overwritten later, without taking effect.\n" + "** Use VBoxManage or the VirtualBox Manager GUI to make changes.\n" +); + m->pelmRoot->setAttribute("xmlns", VBOX_XML_NAMESPACE); + // Have the code for producing a proper schema reference. Not used by most + // tools, so don't bother doing it. The schema is not on the server anyway. +#ifdef VBOX_WITH_SETTINGS_SCHEMA + m->pelmRoot->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + m->pelmRoot->setAttribute("xsi:schemaLocation", VBOX_XML_NAMESPACE " " VBOX_XML_SCHEMA); +#endif + + // add settings version attribute to root element, update m->strSettingsVersionFull + setVersionAttribute(*m->pelmRoot); + + LogRel(("Saving settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str())); +} + +/** + * Creates an \ node under the given parent element with + * \ childern according to the contents of the given + * map. + * + * This is in ConfigFileBase because it's used in both MainConfigFile + * and MachineConfigFile, which both can have extradata. + * + * @param elmParent + * @param me + */ +void ConfigFileBase::buildExtraData(xml::ElementNode &elmParent, + const StringsMap &me) +{ + if (me.size()) + { + xml::ElementNode *pelmExtraData = elmParent.createChild("ExtraData"); + for (StringsMap::const_iterator it = me.begin(); + it != me.end(); + ++it) + { + const Utf8Str &strName = it->first; + const Utf8Str &strValue = it->second; + xml::ElementNode *pelmThis = pelmExtraData->createChild("ExtraDataItem"); + pelmThis->setAttribute("name", strName); + pelmThis->setAttribute("value", strValue); + } + } +} + +/** + * Creates \ nodes under the given parent element according to + * the contents of the given USBDeviceFiltersList. This is in ConfigFileBase + * because it's used in both MainConfigFile (for host filters) and + * MachineConfigFile (for machine filters). + * + * If fHostMode is true, this means that we're supposed to write filters + * for the IHost interface (respect "action", omit "strRemote" and + * "ulMaskedInterfaces" in struct USBDeviceFilter). + * + * @param elmParent + * @param ll + * @param fHostMode + */ +void ConfigFileBase::buildUSBDeviceFilters(xml::ElementNode &elmParent, + const USBDeviceFiltersList &ll, + bool fHostMode) +{ + for (USBDeviceFiltersList::const_iterator it = ll.begin(); + it != ll.end(); + ++it) + { + const USBDeviceFilter &flt = *it; + xml::ElementNode *pelmFilter = elmParent.createChild("DeviceFilter"); + pelmFilter->setAttribute("name", flt.strName); + pelmFilter->setAttribute("active", flt.fActive); + if (flt.strVendorId.length()) + pelmFilter->setAttribute("vendorId", flt.strVendorId); + if (flt.strProductId.length()) + pelmFilter->setAttribute("productId", flt.strProductId); + if (flt.strRevision.length()) + pelmFilter->setAttribute("revision", flt.strRevision); + if (flt.strManufacturer.length()) + pelmFilter->setAttribute("manufacturer", flt.strManufacturer); + if (flt.strProduct.length()) + pelmFilter->setAttribute("product", flt.strProduct); + if (flt.strSerialNumber.length()) + pelmFilter->setAttribute("serialNumber", flt.strSerialNumber); + if (flt.strPort.length()) + pelmFilter->setAttribute("port", flt.strPort); + + if (fHostMode) + { + const char *pcsz = + (flt.action == USBDeviceFilterAction_Ignore) ? "Ignore" + : /*(flt.action == USBDeviceFilterAction_Hold) ?*/ "Hold"; + pelmFilter->setAttribute("action", pcsz); + } + else + { + if (flt.strRemote.length()) + pelmFilter->setAttribute("remote", flt.strRemote); + if (flt.ulMaskedInterfaces) + pelmFilter->setAttribute("maskedInterfaces", flt.ulMaskedInterfaces); + } + } +} + +/** + * Creates a single \ element for the given Medium structure + * and all child hard disks underneath. Called from MainConfigFile::write(). + * + * @param t + * @param elmMedium + * @param med + */ +void ConfigFileBase::buildMedium(MediaType t, + xml::ElementNode &elmMedium, + const Medium &med) +{ + std::list llSettingsTodo; + llSettingsTodo.push_back(&med); + std::list llElementsTodo; + llElementsTodo.push_back(&elmMedium); + std::list llDepthsTodo; + llDepthsTodo.push_back(1); + + while (llSettingsTodo.size() > 0) + { + const Medium *pMed = llSettingsTodo.front(); + llSettingsTodo.pop_front(); + xml::ElementNode *pElement = llElementsTodo.front(); + llElementsTodo.pop_front(); + uint32_t depth = llDepthsTodo.front(); + llDepthsTodo.pop_front(); + + if (depth > SETTINGS_MEDIUM_DEPTH_MAX) + throw ConfigFileError(this, pElement, N_("Maximum medium tree depth of %u exceeded"), SETTINGS_MEDIUM_DEPTH_MAX); + + xml::ElementNode *pelmMedium; + + if (t == HardDisk) + pelmMedium = pElement->createChild("HardDisk"); + else + pelmMedium = pElement->createChild("Image"); + + pelmMedium->setAttribute("uuid", pMed->uuid.toStringCurly()); + + pelmMedium->setAttributePath("location", pMed->strLocation); + + if (t == HardDisk || RTStrICmp(pMed->strFormat.c_str(), "RAW")) + pelmMedium->setAttribute("format", pMed->strFormat); + if ( t == HardDisk + && pMed->fAutoReset) + pelmMedium->setAttribute("autoReset", pMed->fAutoReset); + if (pMed->strDescription.length()) + pelmMedium->createChild("Description")->addContent(pMed->strDescription); + + for (StringsMap::const_iterator it = pMed->properties.begin(); + it != pMed->properties.end(); + ++it) + { + xml::ElementNode *pelmProp = pelmMedium->createChild("Property"); + pelmProp->setAttribute("name", it->first); + pelmProp->setAttribute("value", it->second); + } + + // only for base hard disks, save the type + if (depth == 1) + { + // no need to save the usual DVD/floppy medium types + if ( ( t != DVDImage + || ( pMed->hdType != MediumType_Writethrough // shouldn't happen + && pMed->hdType != MediumType_Readonly)) + && ( t != FloppyImage + || pMed->hdType != MediumType_Writethrough)) + { + const char *pcszType = + pMed->hdType == MediumType_Normal ? "Normal" : + pMed->hdType == MediumType_Immutable ? "Immutable" : + pMed->hdType == MediumType_Writethrough ? "Writethrough" : + pMed->hdType == MediumType_Shareable ? "Shareable" : + pMed->hdType == MediumType_Readonly ? "Readonly" : + pMed->hdType == MediumType_MultiAttach ? "MultiAttach" : + "INVALID"; + pelmMedium->setAttribute("type", pcszType); + } + } + + /* save all children */ + MediaList::const_iterator itBegin = pMed->llChildren.begin(); + MediaList::const_iterator itEnd = pMed->llChildren.end(); + for (MediaList::const_iterator it = itBegin; it != itEnd; ++it) + { + llSettingsTodo.push_back(&*it); + llElementsTodo.push_back(pelmMedium); + llDepthsTodo.push_back(depth + 1); + } + } +} + +/** + * Creates a \ node under the given parent and writes out all + * hard disks and DVD and floppy images from the lists in the given MediaRegistry + * structure under it. + * + * This is used in both MainConfigFile and MachineConfigFile since starting with + * VirtualBox 4.0, we can have media registries in both. + * + * @param elmParent + * @param mr + */ +void ConfigFileBase::buildMediaRegistry(xml::ElementNode &elmParent, + const MediaRegistry &mr) +{ + if (mr.llHardDisks.size() == 0 && mr.llDvdImages.size() == 0 && mr.llFloppyImages.size() == 0) + return; + + xml::ElementNode *pelmMediaRegistry = elmParent.createChild("MediaRegistry"); + + if (mr.llHardDisks.size()) + { + xml::ElementNode *pelmHardDisks = pelmMediaRegistry->createChild("HardDisks"); + for (MediaList::const_iterator it = mr.llHardDisks.begin(); + it != mr.llHardDisks.end(); + ++it) + { + buildMedium(HardDisk, *pelmHardDisks, *it); + } + } + + if (mr.llDvdImages.size()) + { + xml::ElementNode *pelmDVDImages = pelmMediaRegistry->createChild("DVDImages"); + for (MediaList::const_iterator it = mr.llDvdImages.begin(); + it != mr.llDvdImages.end(); + ++it) + { + buildMedium(DVDImage, *pelmDVDImages, *it); + } + } + + if (mr.llFloppyImages.size()) + { + xml::ElementNode *pelmFloppyImages = pelmMediaRegistry->createChild("FloppyImages"); + for (MediaList::const_iterator it = mr.llFloppyImages.begin(); + it != mr.llFloppyImages.end(); + ++it) + { + buildMedium(FloppyImage, *pelmFloppyImages, *it); + } + } +} + +/** + * Serialize NAT port-forwarding rules in parent container. + * Note: it's responsibility of caller to create parent of the list tag. + * because this method used for serializing per-_mahine's_adapter_ and per-network approaches. + */ +void ConfigFileBase::buildNATForwardRulesMap(xml::ElementNode &elmParent, const NATRulesMap &mapRules) +{ + for (NATRulesMap::const_iterator r = mapRules.begin(); + r != mapRules.end(); ++r) + { + xml::ElementNode *pelmPF; + pelmPF = elmParent.createChild("Forwarding"); + const NATRule &nr = r->second; + if (nr.strName.length()) + pelmPF->setAttribute("name", nr.strName); + pelmPF->setAttribute("proto", nr.proto); + if (nr.strHostIP.length()) + pelmPF->setAttribute("hostip", nr.strHostIP); + if (nr.u16HostPort) + pelmPF->setAttribute("hostport", nr.u16HostPort); + if (nr.strGuestIP.length()) + pelmPF->setAttribute("guestip", nr.strGuestIP); + if (nr.u16GuestPort) + pelmPF->setAttribute("guestport", nr.u16GuestPort); + } +} + + +void ConfigFileBase::buildNATLoopbacks(xml::ElementNode &elmParent, const NATLoopbackOffsetList &natLoopbackOffsetList) +{ + for (NATLoopbackOffsetList::const_iterator lo = natLoopbackOffsetList.begin(); + lo != natLoopbackOffsetList.end(); ++lo) + { + xml::ElementNode *pelmLo; + pelmLo = elmParent.createChild("Loopback4"); + pelmLo->setAttribute("address", (*lo).strLoopbackHostAddress); + pelmLo->setAttribute("offset", (*lo).u32Offset); + } +} + +/** + * Cleans up memory allocated by the internal XML parser. To be called by + * descendant classes when they're done analyzing the DOM tree to discard it. + */ +void ConfigFileBase::clearDocument() +{ + m->cleanup(); +} + +/** + * Returns true only if the underlying config file exists on disk; + * either because the file has been loaded from disk, or it's been written + * to disk, or both. + * @return + */ +bool ConfigFileBase::fileExists() +{ + return m->fFileExists; +} + +/** + * Returns the settings file version + * + * @returns Settings file version enum. + */ +SettingsVersion_T ConfigFileBase::getSettingsVersion() +{ + return m->sv; +} + + +/** + * Copies the base variables from another instance. Used by Machine::saveSettings + * so that the settings version does not get lost when a copy of the Machine settings + * file is made to see if settings have actually changed. + * @param b + */ +void ConfigFileBase::copyBaseFrom(const ConfigFileBase &b) +{ + m->copyFrom(*b.m); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Structures shared between Machine XML and VirtualBox.xml +// +//////////////////////////////////////////////////////////////////////////////// + + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +USBDeviceFilter::USBDeviceFilter() : + fActive(false), + action(USBDeviceFilterAction_Null), + ulMaskedInterfaces(0) +{ +} + +/** + * Comparison operator. This gets called from MachineConfigFile::operator==, + * which in turn gets called from Machine::saveSettings to figure out whether + * machine settings have really changed and thus need to be written out to disk. + */ +bool USBDeviceFilter::operator==(const USBDeviceFilter &u) const +{ + return (this == &u) + || ( strName == u.strName + && fActive == u.fActive + && strVendorId == u.strVendorId + && strProductId == u.strProductId + && strRevision == u.strRevision + && strManufacturer == u.strManufacturer + && strProduct == u.strProduct + && strSerialNumber == u.strSerialNumber + && strPort == u.strPort + && action == u.action + && strRemote == u.strRemote + && ulMaskedInterfaces == u.ulMaskedInterfaces); +} + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +settings::Medium::Medium() : + fAutoReset(false), + hdType(MediumType_Normal) +{ +} + +/** + * Comparison operator. This gets called from MachineConfigFile::operator==, + * which in turn gets called from Machine::saveSettings to figure out whether + * machine settings have really changed and thus need to be written out to disk. + */ +bool settings::Medium::operator==(const settings::Medium &m) const +{ + return (this == &m) + || ( uuid == m.uuid + && strLocation == m.strLocation + && strDescription == m.strDescription + && strFormat == m.strFormat + && fAutoReset == m.fAutoReset + && properties == m.properties + && hdType == m.hdType + && llChildren == m.llChildren); // this is deep and recurses +} + +const struct settings::Medium settings::Medium::Empty; /* default ctor is OK */ + +/** + * Comparison operator. This gets called from MachineConfigFile::operator==, + * which in turn gets called from Machine::saveSettings to figure out whether + * machine settings have really changed and thus need to be written out to disk. + */ +bool MediaRegistry::operator==(const MediaRegistry &m) const +{ + return (this == &m) + || ( llHardDisks == m.llHardDisks + && llDvdImages == m.llDvdImages + && llFloppyImages == m.llFloppyImages); +} + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +NATRule::NATRule() : + proto(NATProtocol_TCP), + u16HostPort(0), + u16GuestPort(0) +{ +} + +/** + * Comparison operator. This gets called from MachineConfigFile::operator==, + * which in turn gets called from Machine::saveSettings to figure out whether + * machine settings have really changed and thus need to be written out to disk. + */ +bool NATRule::operator==(const NATRule &r) const +{ + return (this == &r) + || ( strName == r.strName + && proto == r.proto + && u16HostPort == r.u16HostPort + && strHostIP == r.strHostIP + && u16GuestPort == r.u16GuestPort + && strGuestIP == r.strGuestIP); +} + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +NATHostLoopbackOffset::NATHostLoopbackOffset() : + u32Offset(0) +{ +} + +/** + * Comparison operator. This gets called from MachineConfigFile::operator==, + * which in turn gets called from Machine::saveSettings to figure out whether + * machine settings have really changed and thus need to be written out to disk. + */ +bool NATHostLoopbackOffset::operator==(const NATHostLoopbackOffset &o) const +{ + return (this == &o) + || ( strLoopbackHostAddress == o.strLoopbackHostAddress + && u32Offset == o.u32Offset); +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// VirtualBox.xml structures +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +SystemProperties::SystemProperties() + : uProxyMode(ProxyMode_System) + , uLogHistoryCount(3) + , fExclusiveHwVirt(true) +{ +#if defined(RT_OS_DARWIN) || defined(RT_OS_WINDOWS) || defined(RT_OS_SOLARIS) + fExclusiveHwVirt = false; +#endif +} + +#ifdef VBOX_WITH_UPDATE_AGENT +UpdateAgent::UpdateAgent() + : fEnabled(false) + , enmChannel(UpdateChannel_Stable) + , uCheckFreqSeconds(RT_SEC_1DAY) + , uCheckCount(0) +{ +} +#endif /* VBOX_WITH_UPDATE_AGENT */ + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +DhcpOptValue::DhcpOptValue() + : strValue() + , enmEncoding(DHCPOptionEncoding_Normal) +{ +} + +/** + * Non-standard constructor. + */ +DhcpOptValue::DhcpOptValue(const com::Utf8Str &aText, DHCPOptionEncoding_T aEncoding) + : strValue(aText) + , enmEncoding(aEncoding) +{ +} + +/** + * Default constructor. + */ +DHCPGroupCondition::DHCPGroupCondition() + : fInclusive(true) + , enmType(DHCPGroupConditionType_MAC) + , strValue() +{ +} + +/** + * Default constructor. + */ +DHCPConfig::DHCPConfig() + : mapOptions() + , secMinLeaseTime(0) + , secDefaultLeaseTime(0) + , secMaxLeaseTime(0) +{ +} + +/** + * Default constructor. + */ +DHCPGroupConfig::DHCPGroupConfig() + : DHCPConfig() + , strName() + , vecConditions() +{ +} + +/** + * Default constructor. + */ +DHCPIndividualConfig::DHCPIndividualConfig() + : DHCPConfig() + , strMACAddress() + , strVMName() + , uSlot(0) +{ +} + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +DHCPServer::DHCPServer() + : fEnabled(false) +{ +} + +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +NATNetwork::NATNetwork() : + fEnabled(true), + fIPv6Enabled(false), + fAdvertiseDefaultIPv6Route(false), + fNeedDhcpServer(true), + u32HostLoopback6Offset(0) +{ +} + +#ifdef VBOX_WITH_VMNET +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +HostOnlyNetwork::HostOnlyNetwork() : + strNetworkMask("255.255.255.0"), + strIPLower("192.168.56.1"), + strIPUpper("192.168.56.199"), + fEnabled(true) +{ + uuid.create(); +} +#endif /* VBOX_WITH_VMNET */ + +#ifdef VBOX_WITH_CLOUD_NET +/** + * Constructor. Needs to set sane defaults which stand the test of time. + */ +CloudNetwork::CloudNetwork() : + strProviderShortName("OCI"), + strProfileName("Default"), + fEnabled(true) +{ +} +#endif /* VBOX_WITH_CLOUD_NET */ + + + +//////////////////////////////////////////////////////////////////////////////// +// +// MainConfigFile +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Reads one \ from the main VirtualBox.xml file. + * @param elmMachineRegistry + */ +void MainConfigFile::readMachineRegistry(const xml::ElementNode &elmMachineRegistry) +{ + // + xml::NodesLoop nl1(elmMachineRegistry); + const xml::ElementNode *pelmChild1; + while ((pelmChild1 = nl1.forAllNodes())) + { + if (pelmChild1->nameEquals("MachineEntry")) + { + MachineRegistryEntry mre; + Utf8Str strUUID; + if ( pelmChild1->getAttributeValue("uuid", strUUID) + && pelmChild1->getAttributeValue("src", mre.strSettingsFile) ) + { + parseUUID(mre.uuid, strUUID, pelmChild1); + llMachines.push_back(mre); + } + else + throw ConfigFileError(this, pelmChild1, N_("Required MachineEntry/@uuid or @src attribute is missing")); + } + } +} + +/** + * Builds the XML tree for the DHCP servers. + */ +void MainConfigFile::buildDHCPServers(xml::ElementNode &elmDHCPServers, DHCPServersList const &ll) +{ + for (DHCPServersList::const_iterator it = ll.begin(); it != ll.end(); ++it) + { + const DHCPServer &srv = *it; + xml::ElementNode *pElmThis = elmDHCPServers.createChild("DHCPServer"); + + pElmThis->setAttribute("networkName", srv.strNetworkName); + pElmThis->setAttribute("IPAddress", srv.strIPAddress); + DhcpOptConstIterator itOpt = srv.globalConfig.mapOptions.find(DHCPOption_SubnetMask); + if (itOpt != srv.globalConfig.mapOptions.end()) + pElmThis->setAttribute("networkMask", itOpt->second.strValue); + pElmThis->setAttribute("lowerIP", srv.strIPLower); + pElmThis->setAttribute("upperIP", srv.strIPUpper); + pElmThis->setAttribute("enabled", (srv.fEnabled) ? 1 : 0); // too bad we chose 1 vs. 0 here + + /* We don't want duplicate validation check of networkMask here*/ + if (srv.globalConfig.mapOptions.size() > (itOpt != srv.globalConfig.mapOptions.end() ? 1U : 0U)) + { + xml::ElementNode *pElmOptions = pElmThis->createChild("Options"); + buildDHCPOptions(*pElmOptions, srv.globalConfig, true); + } + + for (DHCPGroupConfigVec::const_iterator itGroup = srv.vecGroupConfigs.begin(); + itGroup != srv.vecGroupConfigs.end(); ++itGroup) + { + DHCPGroupConfig const &rGroupConfig = *itGroup; + + xml::ElementNode *pElmGroup = pElmThis->createChild("Group"); + pElmGroup->setAttribute("name", rGroupConfig.strName); + buildDHCPOptions(*pElmGroup, rGroupConfig, false); + + for (DHCPGroupConditionVec::const_iterator itCond = rGroupConfig.vecConditions.begin(); + itCond != rGroupConfig.vecConditions.end(); ++itCond) + { + xml::ElementNode *pElmCondition = pElmGroup->createChild("Condition"); + pElmCondition->setAttribute("inclusive", itCond->fInclusive); + pElmCondition->setAttribute("type", (int32_t)itCond->enmType); + pElmCondition->setAttribute("value", itCond->strValue); + } + } + + for (DHCPIndividualConfigMap::const_iterator itHost = srv.mapIndividualConfigs.begin(); + itHost != srv.mapIndividualConfigs.end(); ++itHost) + { + DHCPIndividualConfig const &rIndividualConfig = itHost->second; + + xml::ElementNode *pElmConfig = pElmThis->createChild("Config"); + if (rIndividualConfig.strMACAddress.isNotEmpty()) + pElmConfig->setAttribute("MACAddress", rIndividualConfig.strMACAddress); + if (rIndividualConfig.strVMName.isNotEmpty()) + pElmConfig->setAttribute("vm-name", rIndividualConfig.strVMName); + if (rIndividualConfig.uSlot != 0 || rIndividualConfig.strVMName.isNotEmpty()) + pElmConfig->setAttribute("slot", rIndividualConfig.uSlot); + if (rIndividualConfig.strFixedAddress.isNotEmpty()) + pElmConfig->setAttribute("fixedAddress", rIndividualConfig.strFixedAddress); + buildDHCPOptions(*pElmConfig, rIndividualConfig, false); + } + } +} + +/** + * Worker for buildDHCPServers() that builds Options or Config element trees. + */ +void MainConfigFile::buildDHCPOptions(xml::ElementNode &elmOptions, DHCPConfig const &rConfig, bool fSkipSubnetMask) +{ + /* Generic (and optional) attributes on the Options or Config element: */ + if (rConfig.secMinLeaseTime > 0) + elmOptions.setAttribute("secMinLeaseTime", rConfig.secMinLeaseTime); + if (rConfig.secDefaultLeaseTime > 0) + elmOptions.setAttribute("secDefaultLeaseTime", rConfig.secDefaultLeaseTime); + if (rConfig.secMaxLeaseTime > 0) + elmOptions.setAttribute("secMaxLeaseTime", rConfig.secMaxLeaseTime); + if (rConfig.strForcedOptions.isNotEmpty()) + elmOptions.setAttribute("forcedOptions", rConfig.strForcedOptions); + if (rConfig.strSuppressedOptions.isNotEmpty()) + elmOptions.setAttribute("suppressedOptions", rConfig.strSuppressedOptions); + + /* The DHCP options are