VirtualBox

Changeset 64541 in vbox


Ignore:
Timestamp:
Nov 3, 2016 7:13:53 PM (8 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
111725
Message:

Main/xml/Settings.cpp: change backup file handling on version bump (better error behavior), use the same logic for version parsing on import than for loading settings, update the internal settings version string consistently, have consistent logging of settings loading, saving and export, and report XML attribute errors more precisely if possible.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/include/VBox/settings.h

    r62602 r64541  
    233233
    234234    static const char *stringifyMediaType(MediaType t);
    235     SettingsVersion_T parseVersion(const com::Utf8Str &strVersion);
     235    SettingsVersion_T parseVersion(const com::Utf8Str &strVersion,
     236                                   const xml::ElementNode *pElm);
    236237    void parseUUID(com::Guid &guid,
    237                    const com::Utf8Str &strUUID) const;
     238                   const com::Utf8Str &strUUID,
     239                   const xml::ElementNode *pElm) const;
    238240    void parseTimestamp(RTTIMESPEC &timestamp,
    239                         const com::Utf8Str &str) const;
     241                        const com::Utf8Str &str,
     242                        const xml::ElementNode *pElm) const;
    240243    void parseBase64(IconBlob &binary,
    241                      const com::Utf8Str &str) const;
     244                     const com::Utf8Str &str,
     245                     const xml::ElementNode *pElm) const;
    242246    com::Utf8Str stringifyTimestamp(const RTTIMESPEC &tm) const;
    243247    void toBase64(com::Utf8Str &str,
     
    255259
    256260    void setVersionAttribute(xml::ElementNode &elm);
     261    void specialBackupIfFirstBump();
    257262    void createStubDocument();
    258263
  • trunk/src/VBox/Main/xml/Settings.cpp

    r64517 r64541  
    105105#define VBOX_XML_VERSION        "1.12"
    106106
     107/** VirtualBox OVF settings import default version number substring ("x.y").
     108 *
     109 * Think twice before changing this, as all VirtualBox versions before 5.1
     110 * wrote the settings version when exporting, but totally ignored it on
     111 * importing (while it should have been a mandatory attribute), so 3rd party
     112 * software out there creates OVF files with the VirtualBox specific settings
     113 * but lacking the version attribute. This shouldn't happen any more, but
     114 * breaking existing OVF files isn't nice. */
     115#define VBOX_XML_IMPORT_VERSION "1.15"
     116
    107117/** VirtualBox XML settings version platform substring */
    108118#if defined (RT_OS_DARWIN)
     
    129139#define VBOX_XML_VERSION_FULL   VBOX_XML_VERSION "-" VBOX_XML_PLATFORM
    130140
     141/** VirtualBox OVF import default settings full version string ("x.y-platform") */
     142#define VBOX_XML_IMPORT_VERSION_FULL   VBOX_XML_IMPORT_VERSION "-" VBOX_XML_PLATFORM
     143
    131144////////////////////////////////////////////////////////////////////////////////
    132145//
     
    248261        m->pelmRoot = m->pDoc->getRootElement();
    249262        if (!m->pelmRoot || !m->pelmRoot->nameEquals("VirtualBox"))
    250             throw ConfigFileError(this, NULL, N_("Root element in VirtualBox settings files must be \"VirtualBox\"."));
     263            throw ConfigFileError(this, m->pelmRoot, N_("Root element in VirtualBox settings files must be \"VirtualBox\""));
    251264
    252265        if (!(m->pelmRoot->getAttributeValue("version", m->strSettingsVersionFull)))
     
    255268        LogRel(("Loading settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str()));
    256269
    257         m->sv = parseVersion(m->strSettingsVersionFull);
     270        m->sv = parseVersion(m->strSettingsVersionFull, m->pelmRoot);
    258271
    259272        // remember the settings version we read in case it gets upgraded later,
     
    316329 * @returns settings version
    317330 * @param strVersion
    318  */
    319 SettingsVersion_T ConfigFileBase::parseVersion(const Utf8Str &strVersion)
     331 * @param pElm
     332 */
     333SettingsVersion_T ConfigFileBase::parseVersion(const Utf8Str &strVersion, const xml::ElementNode *pElm)
    320334{
    321335    SettingsVersion_T sv = SettingsVersion_Null;
     
    389403
    390404    if (sv == SettingsVersion_Null)
    391         throw ConfigFileError(this, NULL, N_("Cannot handle settings version '%s'"), strVersion.c_str());
     405        throw ConfigFileError(this, pElm, N_("Cannot handle settings version '%s'"), strVersion.c_str());
    392406
    393407    return sv;
     
    400414 * @param guid
    401415 * @param strUUID
     416 * @param pElm
    402417 */
    403418void ConfigFileBase::parseUUID(Guid &guid,
    404                                const Utf8Str &strUUID) const
     419                               const Utf8Str &strUUID,
     420                               const xml::ElementNode *pElm) const
    405421{
    406422    guid = strUUID.c_str();
    407423    if (guid.isZero())
    408         throw ConfigFileError(this, NULL, N_("UUID \"%s\" has zero format"), strUUID.c_str());
     424        throw ConfigFileError(this, pElm, N_("UUID \"%s\" has zero format"), strUUID.c_str());
    409425    else if (!guid.isValid())
    410         throw ConfigFileError(this, NULL, N_("UUID \"%s\" has invalid format"), strUUID.c_str());
     426        throw ConfigFileError(this, pElm, N_("UUID \"%s\" has invalid format"), strUUID.c_str());
    411427}
    412428
     
    416432 * @param timestamp
    417433 * @param str
     434 * @param pElm
    418435 */
    419436void ConfigFileBase::parseTimestamp(RTTIMESPEC &timestamp,
    420                                     const com::Utf8Str &str) const
     437                                    const com::Utf8Str &str,
     438                                    const xml::ElementNode *pElm) const
    421439{
    422440    const char *pcsz = str.c_str();
     
    431449             && (pcsz[19] != 'Z')
    432450           )
    433             throw ConfigFileError(this, NULL, N_("Cannot handle ISO timestamp '%s': is not UTC date"), str.c_str());
     451            throw ConfigFileError(this, pElm, N_("Cannot handle ISO timestamp '%s': is not UTC date"), str.c_str());
    434452
    435453        int32_t yyyy;
     
    472490            }
    473491
    474             throw ConfigFileError(this, NULL, N_("Cannot parse ISO timestamp '%s': runtime error, %Rra"), str.c_str(), rc);
    475         }
    476 
    477         throw ConfigFileError(this, NULL, N_("Cannot parse ISO timestamp '%s': invalid format"), str.c_str());
     492            throw ConfigFileError(this, pElm, N_("Cannot parse ISO timestamp '%s': runtime error, %Rra"), str.c_str(), rc);
     493        }
     494
     495        throw ConfigFileError(this, pElm, N_("Cannot parse ISO timestamp '%s': invalid format"), str.c_str());
    478496    }
    479497}
     
    481499/**
    482500 * Helper function that parses a Base64 formatted string into a binary blob.
    483  * @param guid
    484  * @param strUUID
     501 * @param binary
     502 * @param str
     503 * @param pElm
    485504 */
    486505void ConfigFileBase::parseBase64(IconBlob &binary,
    487                                  const Utf8Str &str) const
     506                                 const Utf8Str &str,
     507                                 const xml::ElementNode *pElm) const
    488508{
    489509#define DECODE_STR_MAX _1M
     
    491511    ssize_t cbOut = RTBase64DecodedSize(psz, NULL);
    492512    if (cbOut > DECODE_STR_MAX)
    493         throw ConfigFileError(this, NULL, N_("Base64 encoded data too long (%d > %d)"), cbOut, DECODE_STR_MAX);
     513        throw ConfigFileError(this, pElm, N_("Base64 encoded data too long (%d > %d)"), cbOut, DECODE_STR_MAX);
    494514    else if (cbOut < 0)
    495         throw ConfigFileError(this, NULL, N_("Base64 encoded data '%s' invalid"), psz);
     515        throw ConfigFileError(this, pElm, N_("Base64 encoded data '%s' invalid"), psz);
    496516    binary.resize(cbOut);
    497517    int vrc = VINF_SUCCESS;
     
    501521    {
    502522        binary.resize(0);
    503         throw ConfigFileError(this, NULL, N_("Base64 encoded data could not be decoded (%Rrc)"), vrc);
     523        throw ConfigFileError(this, pElm, N_("Base64 encoded data could not be decoded (%Rrc)"), vrc);
    504524    }
    505525}
     
    643663        throw ConfigFileError(this, &elmMedium, N_("Required %s/@uuid attribute is missing"), elmMedium.getName());
    644664
    645     parseUUID(med.uuid, strUUID);
     665    parseUUID(med.uuid, strUUID, &elmMedium);
    646666
    647667    bool fNeedsLocation = true;
     
    10181038    }
    10191039
    1020     elm.setAttribute("version", Utf8StrFmt("%s-%s",
     1040    m->strSettingsVersionFull = Utf8StrFmt("%s-%s",
    10211041                                           pcszVersion,
    1022                                            VBOX_XML_PLATFORM));       // e.g. "linux"
     1042                                           VBOX_XML_PLATFORM);  // e.g. "linux"
     1043    elm.setAttribute("version", m->strSettingsVersionFull);
     1044}
     1045
     1046
     1047/**
     1048 * Creates a special backup file in case there is a version
     1049 * bump, so that it is possible to go back to the previous
     1050 * state. This is done only once (not for every settings
     1051 * version bump), when the settings version is newer than
     1052 * the version read from the config file. Must be called
     1053 * before ConfigFileBase::createStubDocument, because that
     1054 * method may alter information which this method needs.
     1055 */
     1056void ConfigFileBase::specialBackupIfFirstBump()
     1057{
     1058    // Since this gets called before the XML document is actually written out,
     1059    // this is where we must check whether we're upgrading the settings version
     1060    // and need to make a backup, so the user can go back to an earlier
     1061    // VirtualBox version and recover his old settings files.
     1062    if (    (m->svRead != SettingsVersion_Null)     // old file exists?
     1063         && (m->svRead < m->sv)                     // we're upgrading?
     1064       )
     1065    {
     1066        // compose new filename: strip off trailing ".xml"/".vbox"
     1067        Utf8Str strFilenameNew;
     1068        Utf8Str strExt = ".xml";
     1069        if (m->strFilename.endsWith(".xml"))
     1070            strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 4);
     1071        else if (m->strFilename.endsWith(".vbox"))
     1072        {
     1073            strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 5);
     1074            strExt = ".vbox";
     1075        }
     1076
     1077        // and append something like "-1.3-linux.xml"
     1078        strFilenameNew.append("-");
     1079        strFilenameNew.append(m->strSettingsVersionFull);       // e.g. "1.3-linux"
     1080        strFilenameNew.append(strExt);                          // .xml for main config, .vbox for machine config
     1081
     1082        // Copying the file cannot be avoided, as doing tricks with renaming
     1083        // causes trouble on OS X with aliases (which follow the rename), and
     1084        // on all platforms there is a risk of "losing" the VM config when
     1085        // running out of space, as a rename here couldn't be rolled back.
     1086        // Ignoring all errors besides running out of space is intentional, as
     1087        // we don't want to do anything if the file already exists.
     1088        int vrc = RTFileCopy(m->strFilename.c_str(), strFilenameNew.c_str());
     1089        if (RT_UNLIKELY(vrc == VERR_DISK_FULL))
     1090            throw ConfigFileError(this, NULL, N_("Cannot create settings backup file when upgrading to a newer settings format"));
     1091
     1092        // do this only once
     1093        m->svRead = SettingsVersion_Null;
     1094    }
    10231095}
    10241096
     
    10321104 * set the "sv" member to the required settings version that is to
    10331105 * be written. For newly created files, the settings version will be
    1034  * the latest (1.12); for files read in from disk earlier, it will be
    1035  * the settings version indicated in the file. However, this method
    1036  * will silently make sure that the settings version is always
    1037  * at least 1.7 and change it if necessary, since there is no write
    1038  * support for earlier settings versions.
     1106 * recent (1.12 or later if necessary); for files read in from disk
     1107 * earlier, it will be the settings version indicated in the file.
     1108 * However, this method will silently make sure that the settings
     1109 * version is always at least 1.7 and change it if necessary, since
     1110 * there is no write support for earlier settings versions.
    10391111 */
    10401112void ConfigFileBase::createStubDocument()
     
    10581130#endif
    10591131
    1060     // add settings version attribute to root element
     1132    // add settings version attribute to root element, update m->strSettingsVersionFull
    10611133    setVersionAttribute(*m->pelmRoot);
    10621134
    1063     // since this gets called before the XML document is actually written out,
    1064     // this is where we must check whether we're upgrading the settings version
    1065     // and need to make a backup, so the user can go back to an earlier
    1066     // VirtualBox version and recover his old settings files.
    1067     if (    (m->svRead != SettingsVersion_Null)     // old file exists?
    1068          && (m->svRead < m->sv)                     // we're upgrading?
    1069        )
    1070     {
    1071         // compose new filename: strip off trailing ".xml"/".vbox"
    1072         Utf8Str strFilenameNew;
    1073         Utf8Str strExt = ".xml";
    1074         if (m->strFilename.endsWith(".xml"))
    1075             strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 4);
    1076         else if (m->strFilename.endsWith(".vbox"))
    1077         {
    1078             strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 5);
    1079             strExt = ".vbox";
    1080         }
    1081 
    1082         // and append something like "-1.3-linux.xml"
    1083         strFilenameNew.append("-");
    1084         strFilenameNew.append(m->strSettingsVersionFull);       // e.g. "1.3-linux"
    1085         strFilenameNew.append(strExt);                          // .xml for main config, .vbox for machine config
    1086 
    1087         RTFileMove(m->strFilename.c_str(),
    1088                    strFilenameNew.c_str(),
    1089                    0);      // no RTFILEMOVE_FLAGS_REPLACE
    1090 
    1091         // do this only once
    1092         m->svRead = SettingsVersion_Null;
    1093     }
     1135    LogRel(("Saving settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str()));
    10941136}
    10951137
     
    16161658                && pelmChild1->getAttributeValue("src", mre.strSettingsFile) )
    16171659            {
    1618                 parseUUID(mre.uuid, strUUID);
     1660                parseUUID(mre.uuid, strUUID, pelmChild1);
    16191661                llMachines.push_back(mre);
    16201662            }
     
    19401982
    19411983    m->strFilename = strFilename;
     1984    specialBackupIfFirstBump();
    19421985    createStubDocument();
    19431986
     
    32053248    // version when they switch to the newer settings style/defaults of 5.1.
    32063249    if (!(elmMachine.getAttributeValue("version", m->strSettingsVersionFull)))
    3207         m->strSettingsVersionFull = "1.15";
     3250        m->strSettingsVersionFull = VBOX_XML_IMPORT_VERSION_FULL;
    32083251
    32093252    LogRel(("Import settings with version \"%s\"\n", m->strSettingsVersionFull.c_str()));
    32103253
    3211     m->sv = parseVersion(m->strSettingsVersionFull);
     3254    m->sv = parseVersion(m->strSettingsVersionFull, &elmMachine);
    32123255
    32133256    // remember the settings version we read in case it gets upgraded later,
     
    37783821    Utf8Str strUUID;
    37793822    if (elmHardware.getAttributeValue("uuid", strUUID))
    3780         parseUUID(hw.uuid, strUUID);
     3823        parseUUID(hw.uuid, strUUID, &elmHardware);
    37813824
    37823825    xml::NodesLoop nl1(elmHardware);
     
    44914534        if (!pelmAttachment->getAttributeValue("hardDisk", strUUID))
    44924535            throw ConfigFileError(this, pelmAttachment, N_("Required HardDiskAttachment/@hardDisk attribute is missing"));
    4493         parseUUID(att.uuid, strUUID);
     4536        parseUUID(att.uuid, strUUID, pelmAttachment);
    44944537
    44954538        if (!pelmAttachment->getAttributeValue("bus", strBus))
     
    46754718                    if (!pelmImage->getAttributeValue("uuid", strTemp))
    46764719                        throw ConfigFileError(this, pelmImage, N_("Required AttachedDevice/Image/@uuid attribute is missing"));
    4677                     parseUUID(att.uuid, strTemp);
     4720                    parseUUID(att.uuid, strTemp, pelmImage);
    46784721                }
    46794722
     
    47324775            if (   (pDriveChild = pelmHwChild->findChildElement("Image")) != NULL
    47334776                && pDriveChild->getAttributeValue("uuid", strTmp))
    4734                 parseUUID(att.uuid, strTmp);
     4777                parseUUID(att.uuid, strTmp, pDriveChild);
    47354778            else if ((pDriveChild = pelmHwChild->findChildElement("HostDrive")))
    47364779                pDriveChild->getAttributeValue("src", att.strHostDriveSrc);
     
    47784821                if (   (pDriveChild = pelmHwChild->findChildElement("Image"))
    47794822                    && pDriveChild->getAttributeValue("uuid", strTmp) )
    4780                     parseUUID(att.uuid, strTmp);
     4823                    parseUUID(att.uuid, strTmp, pDriveChild);
    47814824                else if ((pDriveChild = pelmHwChild->findChildElement("HostDrive")))
    47824825                    pDriveChild->getAttributeValue("src", att.strHostDriveSrc);
     
    49014944    if (!elmSnapshot.getAttributeValue("uuid", strTemp))
    49024945        throw ConfigFileError(this, &elmSnapshot, N_("Required Snapshot/@uuid attribute is missing"));
    4903     parseUUID(snap.uuid, strTemp);
     4946    parseUUID(snap.uuid, strTemp, &elmSnapshot);
    49044947    bool foundCurrentSnapshot = (snap.uuid == curSnapshotUuid);
    49054948
     
    49124955    if (!elmSnapshot.getAttributeValue("timeStamp", strTemp))
    49134956        throw ConfigFileError(this, &elmSnapshot, N_("Required Snapshot/@timeStamp attribute is missing"));
    4914     parseTimestamp(snap.timestamp, strTemp);
     4957    parseTimestamp(snap.timestamp, strTemp, &elmSnapshot);
    49154958
    49164959    elmSnapshot.getAttributeValuePath("stateFile", snap.strStateFile);      // online snapshots only
     
    50325075        && elmMachine.getAttributeValue("name", machineUserData.strName))
    50335076    {
    5034         parseUUID(uuid, strUUID);
     5077        parseUUID(uuid, strUUID, &elmMachine);
    50355078
    50365079        elmMachine.getAttributeValue("directoryIncludesUUID", machineUserData.fDirectoryIncludesUUID);
     
    50465089
    50475090        if (elmMachine.getAttributeValue("currentSnapshot", str))
    5048             parseUUID(uuidCurrentSnapshot, str);
     5091            parseUUID(uuidCurrentSnapshot, str, &elmMachine);
    50495092
    50505093        elmMachine.getAttributeValuePath("snapshotFolder", machineUserData.strSnapshotFolder);
     
    50535096            fCurrentStateModified = true;
    50545097        if (elmMachine.getAttributeValue("lastStateChange", str))
    5055             parseTimestamp(timeLastStateChange, str);
     5098            parseTimestamp(timeLastStateChange, str, &elmMachine);
    50565099            // constructor has called RTTimeNow(&timeLastStateChange) before
    50575100        if (elmMachine.getAttributeValue("aborted", fAborted))
     
    50625105        str.setNull();
    50635106        elmMachine.getAttributeValue("icon", str);
    5064         parseBase64(machineUserData.ovIcon, str);
     5107        parseBase64(machineUserData.ovIcon, str, &elmMachine);
    50655108
    50665109        // parse Hardware before the other elements because other things depend on it
     
    66236666{
    66246667    if (fl & BuildMachineXML_WriteVBoxVersionAttribute)
     6668    {
    66256669        // add settings version attribute to machine element
    66266670        setVersionAttribute(elmMachine);
     6671        LogRel(("Exporting settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str()));
     6672    }
    66276673
    66286674    elmMachine.setAttribute("uuid", uuid.toStringCurly());
     
    73567402
    73577403        m->strFilename = strFilename;
     7404        specialBackupIfFirstBump();
    73587405        createStubDocument();
    73597406
Note: See TracChangeset for help on using the changeset viewer.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette