/* $Id: Settings.cpp 94727 2022-04-27 17:45:11Z vboxsync $ */ /** @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-2022 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ #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 // 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 rc; if ( (RT_SUCCESS(rc = RTStrToInt32Ex(pcsz, NULL, 0, &yyyy))) // could theoretically be negative but let's assume that nobody // created virtual machines before the Christian era && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 5, NULL, 0, &mm))) && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 8, NULL, 0, &dd))) && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 11, NULL, 0, &hh))) && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 14, NULL, 0, &min))) && (RT_SUCCESS(rc = 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(), rc); } 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) , enmProxyMode(ProxyMode_NoProxy) , 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