Settings.cpp revision fe06619ae576367ff3568e6abd99fb8ad28cc73a
/** @file
* Settings File Manipulation API.
*/
/*
* Copyright (C) 2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#include "VBox/settings.h"
#include "VirtualBoxXMLUtil.h"
using namespace com;
using namespace settings;
/**
* Opaque data structore for ConfigFileBase (only declared
* in header, defined only here).
*/
struct ConfigFileBase::Data
{
Data()
{}
~Data()
{
cleanup();
}
bool fFileExists;
void cleanup()
{
if (pDoc)
{
delete pDoc;
}
if (pParser)
{
delete pParser;
}
}
};
/**
* 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.
*/
{
public:
: xml::LogicError()
{
}
};
/**
* Constructor. Allocates the XML internals.
* @param strFilename
*/
: m(new Data)
{
m->fFileExists = false;
if (pstrFilename)
{
m->strFilename = *pstrFilename;
*m->pDoc);
m->fFileExists = true;
throw ConfigFileError(this, N_("Root element in VirtualBox settings files must be \"VirtualBox\"."));
m->sv = SettingsVersion_Null;
{
if ( (pcsz[0] == '1')
)
{
m->sv = SettingsVersion_v1_7;
m->sv = SettingsVersion_v1_8;
}
}
if (m->sv == SettingsVersion_Null)
throw ConfigFileError(this, N_("Cannot handle settings version '%s'"), m->strSettingsVersionFull.c_str());
}
else
{
m->sv = SettingsVersion_v1_8;
}
}
/**
* Clean up.
*/
{
if (m)
{
delete m;
m = NULL;
}
}
/**
* Helper function that parses a UUID in string form into
* a com::Guid item. Since that uses an IPRT function which
* does not accept "{}" characters around the UUID string,
* we handle that here. Throws on errors.
* @param guid
* @param strUUID
*/
{
// {5f102a55-a51b-48e3-b45a-b28d33469488}
// 01234567890123456789012345678901234567
// 1 2 3
if ( (strUUID[0] == '{')
)
else
}
/**
* Parses the given string in str and attempts to treat it as an ISO
* @param timestamp
* @param str
*/
{
// yyyy-mm-ddThh:mm:ss
// "2009-07-10T11:54:03Z"
// 01234567890123456789
// 1
{
// timezone must either be unspecified or 'Z' for UTC
if ( (pcsz[19])
)
)
{
int rc;
// could theoretically be negative but let's assume that nobody
// created virtual machines before the Christian era
)
{
0,
0,
0,
if (RTTimeNormalize(&time))
return;
}
throw ConfigFileError(this, N_("Cannot parse ISO timestamp '%s': runtime error, %Rra"), str.c_str(), rc);
}
}
}
/**
* Helper to create a string for a RTTIMESPEC for writing out ISO timestamps.
* @param stamp
* @return
*/
{
return Utf8StrFmt("%04ld-%02hd-%02hdT%02hd:%02hd:%02hdZ",
}
{
return str;
}
/**
* 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
*/
{
{
{
// <ExtraDataItem name="GUI/LastWindowPostion" value="97,88,981,858"/>
)
else
}
else
throw ConfigFileError(this, N_("Invalid element %s in ExtraData section"), pelmExtraDataItem->getName());
}
}
/**
* Reads <USBDeviceFilter> 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
*/
{
{
)
{
// the next 2 are irrelevant for host USB objects
// action is only used with host USB objects
{
if (strAction == "Ignore")
else if (strAction == "Hold")
else
throw ConfigFileError(this, N_("Invalid value %s in DeviceFilter/action attribute"), strAction.c_str());
}
}
}
}
/**
* 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 when writing out their XML.
*/
void ConfigFileBase::createStubDocument()
{
const char *pcszVersion;
switch (m->sv)
{
case SettingsVersion_v1_7:
pcszVersion = "1.7";
break;
case SettingsVersion_v1_8:
pcszVersion = "1.8";
break;
}
VBOX_XML_PLATFORM)); // e.g. "linux"
}
/**
*
* @param elmParent
* @param me
*/
const ExtraDataItemsMap &me)
{
{
++it)
{
}
}
}
/**
* Creates <DeviceFilter> nodes under the given parent element according to
* the contents of the given USBDeviceFiltersList. 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
*/
const USBDeviceFiltersList &ll,
bool fHostMode)
{
++it)
{
if (fHostMode)
{
const char *pcsz =
: /*(flt.action == USBDeviceFilterAction_Hold) ?*/ "Hold";
}
else
{
if (flt.ulMaskedInterfaces)
}
}
}
/**
* 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;
}
/**
* Reads one <MachineEntry> from the main VirtualBox.xml file.
* @param elmMachineRegistry
*/
{
// <MachineEntry uuid="{ xxx }" src=" xxx "/>
{
{
)
{
}
else
}
else
throw ConfigFileError(this, N_("Invalid element %s in MachineRegistry section"), pelmChild1->getName());
}
}
/**
* Reads a media registry entry from the main VirtualBox.xml file.
* @param t
* @param elmMedium
* @param llMedia
*/
const xml::ElementNode &elmMedium, // MediaRegistry/HardDisks or a single HardDisk node if recursing
{
// <HardDisk uuid="{5471ecdb-1ddb-4012-a801-6d98e226868b}" location="/mnt/innotek-unix/vdis/Windows XP.vdi" format="VDI" type="Normal">
)
{
if (t == HardDisk)
{
med.fAutoReset = false;
{
if (strType == "Normal")
else if (strType == "Immutable")
else if (strType == "Writethrough")
else
throw ConfigFileError(this, N_("HardDisk/type attribute must be one of Normal, Immutable or Writethrough"));
}
}
// recurse to handle children
{
if ( t == HardDisk
)
// recurse with this element and push the child onto our current children list
readMedium(t,
{
)
else
}
}
}
else
throw ConfigFileError(this, N_("%s element must have uuid and location attributes"), elmMedium.getName());
}
/**
* Reads in the entire <MediaRegistry> chunk.
* @param elmMediaRegistry
*/
{
// <MachineEntry uuid="{ xxx }" src=" xxx "/>
{
t = HardDisk;
t = DVDImage;
t = FloppyImage;
else
throw ConfigFileError(this, N_("Invalid element %s in MediaRegistry section"), pelmChild1->getName());
{
if ( t == HardDisk
)
readMedium(t,
llHardDisks); // list to append hard disk data to: the root list
else if ( t == DVDImage
)
readMedium(t,
llDvdImages); // list to append dvd images to: the root list
else if ( t == FloppyImage
)
readMedium(t,
llFloppyImages); // list to append floppy images to: the root list
}
}
}
/**
* Reads in the <DHCPServers> chunk.
* @param elmDHCPServers
*/
{
{
{
)
else
throw ConfigFileError(this, N_("DHCPServer element must have networkName, IPAddress, networkMask, lowerIP, upperIP and enabled attributes"));
}
else
throw ConfigFileError(this, N_("Invalid element %s in DHCPServers section"), pelmServer->getName());
}
}
/**
* Constructor.
*
* If pstrFilename is != NULL, this reads the given settings file into the member
* variables and various substructures and lists. Otherwise, the member variables
* are initialized with default values.
*
* Throws variants of xml::Error for I/O, XML and logical content errors, which
* the caller should catch; if this constructor does not throw, then the file has
* been successfully read.
*
* @param strFilename
*/
{
if (pstrFilename)
{
// the ConfigFileBase constructor has loaded the XML file, so now
// we need only analyze what is in there
{
{
{
{
pelmGlobalChild->getAttributeValue("defaultMachineFolder", systemProperties.strDefaultMachineFolder);
pelmGlobalChild->getAttributeValue("defaultHardDiskFolder", systemProperties.strDefaultHardDiskFolder);
pelmGlobalChild->getAttributeValue("defaultHardDiskFormat", systemProperties.strDefaultHardDiskFormat);
pelmGlobalChild->getAttributeValue("remoteDisplayAuthLibrary", systemProperties.strRemoteDisplayAuthLibrary);
pelmGlobalChild->getAttributeValue("webServiceAuthLibrary", systemProperties.strWebServiceAuthLibrary);
}
{
{
else
throw ConfigFileError(this, N_("Invalid element %s in NetserviceRegistry section"), pelmLevel4Child->getName());
}
}
else
throw ConfigFileError(this, N_("Invalid element %s in Global section"), pelmGlobalChild->getName());
}
} // end if (pelmRootChild->nameEquals("Global"))
}
}
}
/**
* Writes out a single <HardDisk> element for the given Medium structure
* and recurses to write the child hard disks underneath. Called from
* MainConfigFile::write().
* @param elmMedium
* @param m
* @param level
*/
const Medium &m,
{
if (m.fAutoReset)
if (m.strDescription.length())
++it)
{
}
// only for base hard disks, save the type
if (level == 0)
{
const char *pcszType =
/*m.hdType == HardDiskType_Writethrough ?*/ "Writethrough";
}
++it)
{
// recurse for children
*it, // settings::Medium
++level); // recursion level
}
}
/**
* Called from the IMachine interface to write out a machine config file. This
* builds an XML DOM tree and writes it out to disk.
*/
void MainConfigFile::write()
{
++it)
{
// <MachineEntry uuid="{5f102a55-a51b-48e3-b45a-b28d33469488}" src="/mnt/innotek-unix/vbox-machines/Windows 5.1 XP 1 (Office 2003)/Windows 5.1 XP 1 (Office 2003).xml"/>
}
++it)
{
}
++it)
{
if (m.strDescription.length())
}
++it)
{
if (m.strDescription.length())
}
++it)
{
const DHCPServer &d = *it;
}
pelmSysProps->setAttribute("remoteDisplayAuthLibrary", systemProperties.strRemoteDisplayAuthLibrary);
true); // fHostMode
// now go write the XML
m->fFileExists = true;
}
/**
* @param elmNetwork
* @param ll
*/
{
{
{
if (strTemp == "Am79C970A")
else if (strTemp == "Am79C973")
else if (strTemp == "82540EM")
else if (strTemp == "82543GC")
else if (strTemp == "82545EM")
else
}
{
}
)
{
}
{
throw ConfigFileError(this, N_("Required 'name' element is missing under 'InternalNetwork' element"));
}
{
throw ConfigFileError(this, N_("Required 'name' element is missing under 'HostOnlyInterface' element"));
}
// else: default is NetworkAttachmentType_Null
}
}
/**
* Called from MachineConfigFile::readHardware() to read serial port information.
* @param elmUART
* @param ll
*/
{
{
// slot must be unique
++it)
throw ConfigFileError(this, N_("UART/Port/slot attribute value %d is used twice, must be unique"), port.ulSlot);
if (strPortMode == "RawFile")
else if (strPortMode == "HostPipe")
else if (strPortMode == "HostDevice")
else if (strPortMode == "Disconnected")
else
throw ConfigFileError(this, N_("Invalid value %s in UART/Port/hostMode attribute"), strPortMode.c_str());
}
}
/**
* Called from MachineConfigFile::readHardware() to read parallel port information.
* @param elmLPT
* @param ll
*/
{
{
// slot must be unique
++it)
throw ConfigFileError(this, N_("LPT/Port/slot attribute value %d is used twice, must be unique"), port.ulSlot);
}
}
/**
* Called from MachineConfigFile::readHardware() to read guest property information.
* @param elmGuestProperties
* @param hw
*/
{
{
}
}
/**
* Reads in a <Hardware> block and stores it in the given structure. Used
* both directly from readMachine and from readSnapshot, since snapshots
* have their own hardware sections.
* @param elmHardware
* @param hw
*/
{
// defaults to 2 and is only written if != 2
{
{
}
{
{
throw ConfigFileError(this, N_("Value %d of 'position' attribute in 'Order' element is not unique"), ulPos);
if (strDevice == "None")
else if (strDevice == "Floppy")
else if (strDevice == "DVD")
else if (strDevice == "HardDisk")
else if (strDevice == "Network")
else
throw ConfigFileError(this, N_("Invalid value %s in Boot/Order/device attribute"), strDevice.c_str());
}
}
{
}
{
{
if (strAuthType == "Null")
else if (strAuthType == "Guest")
else if (strAuthType == "External")
else
throw ConfigFileError(this, N_("Invalid value %s in RemoteDisplay/authType attribute"), strAuthType.c_str());
}
}
{
{
}
{
{
if (strBootMenuMode == "Disabled")
else if (strBootMenuMode == "MenuOnly")
else if (strBootMenuMode == "MessageAndMenu")
else
throw ConfigFileError(this, N_("Invalid value %s in BootMenu/mode attribute"), strBootMenuMode.c_str());
}
}
}
{
)
}
{
)
}
{
}
{
{
if (strTemp == "SB16")
else if (strTemp == "AC97")
else
throw ConfigFileError(this, N_("Invalid value %s in AudioAdapter/controller attribute"), strTemp.c_str());
}
{
if (strTemp == "Null")
else if (strTemp == "WinMM")
else if (strTemp == "DirectSound")
else if (strTemp == "SolAudio")
else if (strTemp == "ALSA")
else if (strTemp == "Pulse")
else if (strTemp == "OSS")
else if (strTemp == "CoreAudio")
else if (strTemp == "MMPM")
else
throw ConfigFileError(this, N_("Invalid value %s in AudioAdapter/driver attribute"), strTemp.c_str());
}
}
{
{
}
}
{
{
if (strTemp == "Disabled")
else if (strTemp == "HostToGuest")
else if (strTemp == "GuestToHost")
else if (strTemp == "Bidirectional")
else
}
}
{
}
else
}
}
/**
* Reads in a <StorageControllers> block and stores it in the given Storage structure.
* Used both directly from readMachine and from readSnapshot, since snapshots
* have their own storage controllers sections.
*
* @param elmStorageControllers
*/
{
{
if (strType == "AHCI")
{
}
else if (strType == "LsiLogic")
{
}
else if (strType == "BusLogic")
{
}
else if (strType == "PIIX3")
{
}
else if (strType == "PIIX4")
{
}
else if (strType == "ICH6")
{
}
else
throw ConfigFileError(this, N_("Invalid value %s for StorageController/type attribute"), strType.c_str());
{
if (strTemp != "HardDisk")
}
}
}
/**
* Called initially for the <Snapshot> element under <Machine>, if present,
* to store the snapshot's data into the given Snapshot structure (which is
* then the one in the Machine struct). This might then recurse if
* a <Snapshots> (plural) element is found in the snapshot, which should
* contain a list of child snapshots; such lists are maintained in the
* Snapshot structure.
*
* @param elmSnapshot
* @param snap
*/
{
{
{
{
{
}
else
throw ConfigFileError(this, N_("Invalid element %s under Snapshots element"), pelmChildSnapshot->getName());
}
}
else
throw ConfigFileError(this, N_("Invalid element %s under Snapshot element"), pelmSnapshotChild->getName());
}
}
/**
* Called from the constructor to actually read in the Machine element
* of a machine config file.
* @param elmMachine
*/
{
)
{
fNameSync = true;
fCurrentStateModified = true;
// constructor has called RTTimeNow(&timeLastStateChange) before
{
{
// this will recurse into child snapshots, if necessary
}
else
throw ConfigFileError(this, N_("Invalid element %s under Machine element"), pelmMachineChild->getName());
}
}
else
}
/**
* Constructor.
*
* If pstrFilename is != NULL, this reads the given settings file into the member
* variables and various substructures and lists. Otherwise, the member variables
* are initialized with default values.
*
* Throws variants of xml::Error for I/O, XML and logical content errors, which
* the caller should catch; if this constructor does not throw, then the file has
* been successfully read and parsed.
*
* @param strFilename
*/
fNameSync(true),
fCurrentStateModified(true),
fAborted(false)
{
if (pstrFilename)
{
// the ConfigFileBase constructor has loaded the XML file, so now
// we need only analyze what is in there
{
else
}
}
}
/**
* Creates a <Hardware> node under elmParent and then writes out the XML
* keys under that. Called for both the <Machine> node and for snapshots.
* @param elmParent
* @param st
*/
{
if (hw.fNestedPaging)
++it)
{
const char *pcszDevice;
switch (type)
{
}
}
if (m->sv >= SettingsVersion_v1_8)
const char *pcszAuthType;
{
}
const char *pcszBootMenu;
{
}
false); // fHostMode
++it)
{
if (nic.fTraceEnabled)
{
}
const char *pcszType;
{
}
{
break;
break;
break;
break;
default: /*case NetworkAttachmentType_Null:*/
break;
}
}
++it)
{
const char *pcszHostMode;
{
}
}
++it)
{
}
pelmAudio->setAttribute("controller", (hw.audioAdapter.controllerType == AudioControllerType_SB16) ? "SB16" : "AC97");
const char *pcszDriver;
{
}
++it)
{
}
const char *pcszClip;
switch (hw.clipboardMode)
{
}
++it)
{
}
}
/**
* Creates a <StorageControllers> node under elmParent and then writes out the XML
* keys under that. Called for both the <Machine> node and for snapshots.
* @param elmParent
* @param st
*/
{
++it)
{
const char *pcszType;
switch (sc.controllerType)
{
}
{
}
++it2)
{
}
}
}
/**
* Writes a single snapshot into the DOM tree. Initially this gets called from MachineConfigFile::write()
* for the root snapshot of a machine, if present; elmParent then points to the <Snapshots> node under the
* <Machine> node to which <Snapshot> must be added. This may then recurse for child snapshots.
* @param elmParent
* @param snap
*/
{
{
++it)
{
}
}
}
/**
* Called from Main code to write a machine config file to disk. This builds a DOM tree from
* the member variables and then writes the XML file; it throws xml::Error instances on errors,
* in particular if the file cannot be written.
*/
{
m->strFilename = strFilename;
if (!fNameSync)
if (strDescription.length())
if (strStateFile.length())
if (!uuidCurrentSnapshot.isEmpty())
if (strSnapshotFolder.length())
if (!fCurrentStateModified)
if (fAborted)
if (llFirstSnapshot.size())
// now go write the XML
m->fFileExists = true;
}