diff options
Diffstat (limited to 'src/VBox/Main/src-server/UnattendedImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-server/UnattendedImpl.cpp | 4292 |
1 files changed, 4292 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/UnattendedImpl.cpp b/src/VBox/Main/src-server/UnattendedImpl.cpp new file mode 100644 index 00000000..f40e7e68 --- /dev/null +++ b/src/VBox/Main/src-server/UnattendedImpl.cpp @@ -0,0 +1,4292 @@ +/* $Id: UnattendedImpl.cpp $ */ +/** @file + * Unattended class implementation + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED +#include "LoggingNew.h" +#include "VirtualBoxBase.h" +#include "UnattendedImpl.h" +#include "UnattendedInstaller.h" +#include "UnattendedScript.h" +#include "VirtualBoxImpl.h" +#include "SystemPropertiesImpl.h" +#include "MachineImpl.h" +#include "Global.h" +#include "StringifyEnums.h" + +#include <VBox/err.h> +#include <iprt/cpp/xml.h> +#include <iprt/ctype.h> +#include <iprt/file.h> +#ifndef RT_OS_WINDOWS +# include <iprt/formats/mz.h> +# include <iprt/formats/pecoff.h> +#endif +#include <iprt/formats/wim.h> +#include <iprt/fsvfs.h> +#include <iprt/inifile.h> +#include <iprt/locale.h> +#include <iprt/path.h> +#include <iprt/vfs.h> + +using namespace std; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Controller slot for a DVD drive. + * + * The slot can be free and needing a drive to be attached along with the ISO + * image, or it may already be there and only need mounting the ISO. The + * ControllerSlot::fFree member indicates which it is. + */ +struct ControllerSlot +{ + StorageBus_T enmBus; + Utf8Str strControllerName; + LONG iPort; + LONG iDevice; + bool fFree; + + ControllerSlot(StorageBus_T a_enmBus, const Utf8Str &a_rName, LONG a_iPort, LONG a_iDevice, bool a_fFree) + : enmBus(a_enmBus), strControllerName(a_rName), iPort(a_iPort), iDevice(a_iDevice), fFree(a_fFree) + {} + + bool operator<(const ControllerSlot &rThat) const + { + if (enmBus == rThat.enmBus) + { + if (strControllerName == rThat.strControllerName) + { + if (iPort == rThat.iPort) + return iDevice < rThat.iDevice; + return iPort < rThat.iPort; + } + return strControllerName < rThat.strControllerName; + } + + /* + * Bus comparsion in boot priority order. + */ + /* IDE first. */ + if (enmBus == StorageBus_IDE) + return true; + if (rThat.enmBus == StorageBus_IDE) + return false; + /* SATA next */ + if (enmBus == StorageBus_SATA) + return true; + if (rThat.enmBus == StorageBus_SATA) + return false; + /* SCSI next */ + if (enmBus == StorageBus_SCSI) + return true; + if (rThat.enmBus == StorageBus_SCSI) + return false; + /* numerical */ + return (int)enmBus < (int)rThat.enmBus; + } + + bool operator==(const ControllerSlot &rThat) const + { + return enmBus == rThat.enmBus + && strControllerName == rThat.strControllerName + && iPort == rThat.iPort + && iDevice == rThat.iDevice; + } +}; + +/** + * Installation disk. + * + * Used when reconfiguring the VM. + */ +typedef struct UnattendedInstallationDisk +{ + StorageBus_T enmBusType; /**< @todo nobody is using this... */ + Utf8Str strControllerName; + DeviceType_T enmDeviceType; + AccessMode_T enmAccessType; + LONG iPort; + LONG iDevice; + bool fMountOnly; + Utf8Str strImagePath; + bool fAuxiliary; + + UnattendedInstallationDisk(StorageBus_T a_enmBusType, Utf8Str const &a_rBusName, DeviceType_T a_enmDeviceType, + AccessMode_T a_enmAccessType, LONG a_iPort, LONG a_iDevice, bool a_fMountOnly, + Utf8Str const &a_rImagePath, bool a_fAuxiliary) + : enmBusType(a_enmBusType), strControllerName(a_rBusName), enmDeviceType(a_enmDeviceType), enmAccessType(a_enmAccessType) + , iPort(a_iPort), iDevice(a_iDevice), fMountOnly(a_fMountOnly), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary) + { + Assert(strControllerName.length() > 0); + } + + UnattendedInstallationDisk(std::list<ControllerSlot>::const_iterator const &itDvdSlot, Utf8Str const &a_rImagePath, + bool a_fAuxiliary) + : enmBusType(itDvdSlot->enmBus), strControllerName(itDvdSlot->strControllerName), enmDeviceType(DeviceType_DVD) + , enmAccessType(AccessMode_ReadOnly), iPort(itDvdSlot->iPort), iDevice(itDvdSlot->iDevice) + , fMountOnly(!itDvdSlot->fFree), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary) + { + Assert(strControllerName.length() > 0); + } +} UnattendedInstallationDisk; + + +/** + * OS/2 syslevel file header. + */ +#pragma pack(1) +typedef struct OS2SYSLEVELHDR +{ + uint16_t uMinusOne; /**< 0x00: UINT16_MAX */ + char achSignature[8]; /**< 0x02: "SYSLEVEL" */ + uint8_t abReserved1[5]; /**< 0x0a: Usually zero. Ignore. */ + uint16_t uSyslevelFileVer; /**< 0x0f: The syslevel file version: 1. */ + uint8_t abReserved2[16]; /**< 0x11: Zero. Ignore. */ + uint32_t offTable; /**< 0x21: Offset of the syslevel table. */ +} OS2SYSLEVELHDR; +#pragma pack() +AssertCompileSize(OS2SYSLEVELHDR, 0x25); + +/** + * OS/2 syslevel table entry. + */ +#pragma pack(1) +typedef struct OS2SYSLEVELENTRY +{ + uint16_t id; /**< 0x00: ? */ + uint8_t bEdition; /**< 0x02: The OS/2 edition: 0=standard, 1=extended, x=component defined */ + uint8_t bVersion; /**< 0x03: 0x45 = 4.5 */ + uint8_t bModify; /**< 0x04: Lower nibble is added to bVersion, so 0x45 0x02 => 4.52 */ + uint8_t abReserved1[2]; /**< 0x05: Zero. Ignore. */ + char achCsdLevel[8]; /**< 0x07: The current CSD level. */ + char achCsdPrior[8]; /**< 0x0f: The prior CSD level. */ + char szName[80]; /**< 0x5f: System/component name. */ + char achId[9]; /**< 0x67: System/component ID. */ + uint8_t bRefresh; /**< 0x70: Single digit refresh version, ignored if zero. */ + char szType[9]; /**< 0x71: Some kind of type string. Optional */ + uint8_t abReserved2[6]; /**< 0x7a: Zero. Ignore. */ +} OS2SYSLEVELENTRY; +#pragma pack() +AssertCompileSize(OS2SYSLEVELENTRY, 0x80); + + + +/** + * Concatenate image name and version strings and return. + * + * A possible output would be "Windows 10 Home (10.0.19041.330 / x64)". + * + * @returns Name string to use. + * @param r_strName String object that can be formatted into and returned. + */ +const Utf8Str &WIMImage::formatName(Utf8Str &r_strName) const +{ + /* We skip the mFlavor as it's typically part of the description already. */ + + if (mVersion.isEmpty() && mArch.isEmpty() && mDefaultLanguage.isEmpty() && mLanguages.size() == 0) + return mName; + + r_strName = mName; + bool fFirst = true; + if (mVersion.isNotEmpty()) + { + r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mVersion.c_str()); + fFirst = false; + } + if (mArch.isNotEmpty()) + { + r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mArch.c_str()); + fFirst = false; + } + if (mDefaultLanguage.isNotEmpty()) + { + r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mDefaultLanguage.c_str()); + fFirst = false; + } + else + for (size_t i = 0; i < mLanguages.size(); i++) + { + r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mLanguages[i].c_str()); + fFirst = false; + } + r_strName.append(")"); + return r_strName; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////// +/* +* +* +* Implementation Unattended functions +* +*/ +////////////////////////////////////////////////////////////////////////////////////////////////////// + +Unattended::Unattended() + : mhThreadReconfigureVM(NIL_RTNATIVETHREAD), mfRtcUseUtc(false), mfGuestOs64Bit(false) + , mpInstaller(NULL), mpTimeZoneInfo(NULL), mfIsDefaultAuxiliaryBasePath(true), mfDoneDetectIsoOS(false) + , mfAvoidUpdatesOverNetwork(false) +{ } + +Unattended::~Unattended() +{ + if (mpInstaller) + { + delete mpInstaller; + mpInstaller = NULL; + } +} + +HRESULT Unattended::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void Unattended::FinalRelease() +{ + uninit(); + + BaseFinalRelease(); +} + +void Unattended::uninit() +{ + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + unconst(mParent) = NULL; + mMachine.setNull(); +} + +/** + * Initializes the unattended object. + * + * @param aParent Pointer to the parent object. + */ +HRESULT Unattended::initUnattended(VirtualBox *aParent) +{ + LogFlowThisFunc(("aParent=%p\n", aParent)); + ComAssertRet(aParent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mParent) = aParent; + + /* + * Fill public attributes (IUnattended) with useful defaults. + */ + try + { + mStrUser = "vboxuser"; + mStrPassword = "changeme"; + mfInstallGuestAdditions = false; + mfInstallTestExecService = false; + midxImage = 1; + + HRESULT hrc = mParent->i_getSystemProperties()->i_getDefaultAdditionsISO(mStrAdditionsIsoPath); + ComAssertComRCRet(hrc, hrc); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } + + /* + * Confirm a successful initialization + */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +HRESULT Unattended::detectIsoOS() +{ + HRESULT hrc; + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + +/** @todo once UDF is implemented properly and we've tested this code a lot + * more, replace E_NOTIMPL with E_FAIL. */ + + /* + * Reset output state before we start + */ + mStrDetectedOSTypeId.setNull(); + mStrDetectedOSVersion.setNull(); + mStrDetectedOSFlavor.setNull(); + mDetectedOSLanguages.clear(); + mStrDetectedOSHints.setNull(); + mDetectedImages.clear(); + + /* + * Open the ISO. + */ + RTVFSFILE hVfsFileIso; + int vrc = RTVfsFileOpenNormal(mStrIsoPath.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileIso); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' (%Rrc)"), mStrIsoPath.c_str(), vrc); + + RTERRINFOSTATIC ErrInfo; + RTVFS hVfsIso; + vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(vrc)) + { + /* + * Try do the detection. Repeat for different file system variations (nojoliet, noudf). + */ + hrc = i_innerDetectIsoOS(hVfsIso); + + RTVfsRelease(hVfsIso); + if (hrc == S_FALSE) /** @todo Finish the linux and windows detection code. Only OS/2 returns S_OK right now. */ + hrc = E_NOTIMPL; + } + else if (RTErrInfoIsSet(&ErrInfo.Core)) + hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc) - %s"), + mStrIsoPath.c_str(), vrc, ErrInfo.Core.pszMsg); + else + hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc)"), mStrIsoPath.c_str(), vrc); + RTVfsFileRelease(hVfsFileIso); + + /* + * Just fake up some windows installation media locale (for <UILanguage>). + * Note! The translation here isn't perfect. Feel free to send us a patch. + */ + if (mDetectedOSLanguages.size() == 0) + { + char szTmp[16]; + const char *pszFilename = RTPathFilename(mStrIsoPath.c_str()); + if ( pszFilename + && RT_C_IS_ALPHA(pszFilename[0]) + && RT_C_IS_ALPHA(pszFilename[1]) + && (pszFilename[2] == '-' || pszFilename[2] == '_') ) + { + szTmp[0] = (char)RT_C_TO_LOWER(pszFilename[0]); + szTmp[1] = (char)RT_C_TO_LOWER(pszFilename[1]); + szTmp[2] = '-'; + if (szTmp[0] == 'e' && szTmp[1] == 'n') + strcpy(&szTmp[3], "US"); + else if (szTmp[0] == 'a' && szTmp[1] == 'r') + strcpy(&szTmp[3], "SA"); + else if (szTmp[0] == 'd' && szTmp[1] == 'a') + strcpy(&szTmp[3], "DK"); + else if (szTmp[0] == 'e' && szTmp[1] == 't') + strcpy(&szTmp[3], "EE"); + else if (szTmp[0] == 'e' && szTmp[1] == 'l') + strcpy(&szTmp[3], "GR"); + else if (szTmp[0] == 'h' && szTmp[1] == 'e') + strcpy(&szTmp[3], "IL"); + else if (szTmp[0] == 'j' && szTmp[1] == 'a') + strcpy(&szTmp[3], "JP"); + else if (szTmp[0] == 's' && szTmp[1] == 'v') + strcpy(&szTmp[3], "SE"); + else if (szTmp[0] == 'u' && szTmp[1] == 'k') + strcpy(&szTmp[3], "UA"); + else if (szTmp[0] == 'c' && szTmp[1] == 's') + strcpy(szTmp, "cs-CZ"); + else if (szTmp[0] == 'n' && szTmp[1] == 'o') + strcpy(szTmp, "nb-NO"); + else if (szTmp[0] == 'p' && szTmp[1] == 'p') + strcpy(szTmp, "pt-PT"); + else if (szTmp[0] == 'p' && szTmp[1] == 't') + strcpy(szTmp, "pt-BR"); + else if (szTmp[0] == 'c' && szTmp[1] == 'n') + strcpy(szTmp, "zh-CN"); + else if (szTmp[0] == 'h' && szTmp[1] == 'k') + strcpy(szTmp, "zh-HK"); + else if (szTmp[0] == 't' && szTmp[1] == 'w') + strcpy(szTmp, "zh-TW"); + else if (szTmp[0] == 's' && szTmp[1] == 'r') + strcpy(szTmp, "sr-Latn-CS"); /* hmm */ + else + { + szTmp[3] = (char)RT_C_TO_UPPER(pszFilename[0]); + szTmp[4] = (char)RT_C_TO_UPPER(pszFilename[1]); + szTmp[5] = '\0'; + } + } + else + strcpy(szTmp, "en-US"); + try + { + mDetectedOSLanguages.append(szTmp); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } + } + + /** @todo implement actual detection logic. */ + return hrc; +} + +HRESULT Unattended::i_innerDetectIsoOS(RTVFS hVfsIso) +{ + DETECTBUFFER uBuf; + mEnmOsType = VBOXOSTYPE_Unknown; + HRESULT hrc = i_innerDetectIsoOSWindows(hVfsIso, &uBuf); + if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown) + hrc = i_innerDetectIsoOSLinux(hVfsIso, &uBuf); + if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown) + hrc = i_innerDetectIsoOSOs2(hVfsIso, &uBuf); + if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown) + hrc = i_innerDetectIsoOSFreeBsd(hVfsIso, &uBuf); + if (mEnmOsType != VBOXOSTYPE_Unknown) + { + try { mStrDetectedOSTypeId = Global::OSTypeId(mEnmOsType); } + catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; } + } + return hrc; +} + +/** + * Tries to parse a LANGUAGES element, with the following structure. + * @verbatim + * <LANGUAGES> + * <LANGUAGE> + * en-US + * </LANGUAGE> + * <DEFAULT> + * en-US + * </DEFAULT> + * </LANGUAGES> + * @endverbatim + * + * Will set mLanguages and mDefaultLanguage success. + * + * @param pElmLanguages Points to the LANGUAGES XML node. + * @param rImage Out reference to an WIMImage instance. + */ +static void parseLangaguesElement(const xml::ElementNode *pElmLanguages, WIMImage &rImage) +{ + /* + * The languages. + */ + ElementNodesList children; + int cChildren = pElmLanguages->getChildElements(children, "LANGUAGE"); + if (cChildren == 0) + cChildren = pElmLanguages->getChildElements(children, "language"); + if (cChildren == 0) + cChildren = pElmLanguages->getChildElements(children, "Language"); + for (ElementNodesList::iterator iterator = children.begin(); iterator != children.end(); ++iterator) + { + const ElementNode * const pElmLanguage = *(iterator); + if (pElmLanguage) + { + const char *pszValue = pElmLanguage->getValue(); + if (pszValue && *pszValue != '\0') + rImage.mLanguages.append(pszValue); + } + } + + /* + * Default language. + */ + const xml::ElementNode *pElmDefault; + if ( (pElmDefault = pElmLanguages->findChildElement("DEFAULT")) != NULL + || (pElmDefault = pElmLanguages->findChildElement("default")) != NULL + || (pElmDefault = pElmLanguages->findChildElement("Default")) != NULL) + rImage.mDefaultLanguage = pElmDefault->getValue(); +} + + +/** + * Tries to set the image architecture. + * + * Input examples (x86 and amd64 respectively): + * @verbatim + * <ARCH>0</ARCH> + * <ARCH>9</ARCH> + * @endverbatim + * + * Will set mArch and update mOSType on success. + * + * @param pElmArch Points to the ARCH XML node. + * @param rImage Out reference to an WIMImage instance. + */ +static void parseArchElement(const xml::ElementNode *pElmArch, WIMImage &rImage) +{ + /* These are from winnt.h */ + static struct { const char *pszArch; VBOXOSTYPE enmArch; } s_aArches[] = + { + /* PROCESSOR_ARCHITECTURE_INTEL / [0] = */ { "x86", VBOXOSTYPE_x86 }, + /* PROCESSOR_ARCHITECTURE_MIPS / [1] = */ { "mips", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_ALPHA / [2] = */ { "alpha", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_PPC / [3] = */ { "ppc", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_SHX / [4] = */ { "shx", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_ARM / [5] = */ { "arm32", VBOXOSTYPE_arm32 }, + /* PROCESSOR_ARCHITECTURE_IA64 / [6] = */ { "ia64", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_ALPHA64 / [7] = */ { "alpha64", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_MSIL / [8] = */ { "msil", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_AMD64 / [9] = */ { "x64", VBOXOSTYPE_x64 }, + /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 / [10] = */ { "x86-on-x64", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_NEUTRAL / [11] = */ { "noarch", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_ARM64 / [12] = */ { "arm64", VBOXOSTYPE_arm64 }, + /* PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64/ [13] = */ { "arm32-on-arm64", VBOXOSTYPE_UnknownArch }, + /* PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 / [14] = */ { "x86-on-arm32", VBOXOSTYPE_UnknownArch }, + }; + const char *pszArch = pElmArch->getValue(); + if (pszArch && *pszArch) + { + uint32_t uArch; + int vrc = RTStrToUInt32Ex(pszArch, NULL, 10 /*uBase*/, &uArch); + if ( RT_SUCCESS(vrc) + && vrc != VWRN_NUMBER_TOO_BIG + && vrc != VWRN_NEGATIVE_UNSIGNED + && uArch < RT_ELEMENTS(s_aArches)) + { + rImage.mArch = s_aArches[uArch].pszArch; + rImage.mOSType = (VBOXOSTYPE)(s_aArches[uArch].enmArch | (rImage.mOSType & VBOXOSTYPE_OsTypeMask)); + } + else + LogRel(("Unattended: bogus ARCH element value: '%s'\n", pszArch)); + } +} + +/** + * Parses XML Node assuming a structure as follows + * @verbatim + * <VERSION> + * <MAJOR>10</MAJOR> + * <MINOR>0</MINOR> + * <BUILD>19041</BUILD> + * <SPBUILD>1</SPBUILD> + * </VERSION> + * @endverbatim + * + * Will update mOSType, mEnmOsType as well as setting mVersion on success. + * + * @param pNode Points to the vesion XML node, + * @param image Out reference to an WIMImage instance. + */ +static void parseVersionElement(const xml::ElementNode *pNode, WIMImage &image) +{ + /* Major part: */ + const xml::ElementNode *pElmMajor; + if ( (pElmMajor = pNode->findChildElement("MAJOR")) != NULL + || (pElmMajor = pNode->findChildElement("major")) != NULL + || (pElmMajor = pNode->findChildElement("Major")) != NULL) + if (pElmMajor) + { + const char * const pszMajor = pElmMajor->getValue(); + if (pszMajor && *pszMajor) + { + /* Minor part: */ + const ElementNode *pElmMinor; + if ( (pElmMinor = pNode->findChildElement("MINOR")) != NULL + || (pElmMinor = pNode->findChildElement("minor")) != NULL + || (pElmMinor = pNode->findChildElement("Minor")) != NULL) + { + const char * const pszMinor = pElmMinor->getValue(); + if (pszMinor && *pszMinor) + { + /* Build: */ + const ElementNode *pElmBuild; + if ( (pElmBuild = pNode->findChildElement("BUILD")) != NULL + || (pElmBuild = pNode->findChildElement("build")) != NULL + || (pElmBuild = pNode->findChildElement("Build")) != NULL) + { + const char * const pszBuild = pElmBuild->getValue(); + if (pszBuild && *pszBuild) + { + /* SPBuild: */ + const ElementNode *pElmSpBuild; + if ( ( (pElmSpBuild = pNode->findChildElement("SPBUILD")) != NULL + || (pElmSpBuild = pNode->findChildElement("spbuild")) != NULL + || (pElmSpBuild = pNode->findChildElement("Spbuild")) != NULL + || (pElmSpBuild = pNode->findChildElement("SpBuild")) != NULL) + && pElmSpBuild->getValue() + && *pElmSpBuild->getValue() != '\0') + image.mVersion.printf("%s.%s.%s.%s", pszMajor, pszMinor, pszBuild, pElmSpBuild->getValue()); + else + image.mVersion.printf("%s.%s.%s", pszMajor, pszMinor, pszBuild); + + /* + * Convert that to a version windows OS ID (newest first!). + */ + image.mEnmOsType = VBOXOSTYPE_Unknown; + if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.22000.0") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win11_x64; + else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win10; + else if (RTStrVersionCompare(image.mVersion.c_str(), "6.3") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win81; + else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win8; + else if (RTStrVersionCompare(image.mVersion.c_str(), "6.1") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win7; + else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0) + image.mEnmOsType = VBOXOSTYPE_WinVista; + if (image.mFlavor.contains("server", Utf8Str::CaseInsensitive)) + { + if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.20348") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win2k22_x64; + else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.17763") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win2k19_x64; + else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win2k16_x64; + else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win2k12_x64; + else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0) + image.mEnmOsType = VBOXOSTYPE_Win2k8; + } + if (image.mEnmOsType != VBOXOSTYPE_Unknown) + image.mOSType = (VBOXOSTYPE)( (image.mOSType & VBOXOSTYPE_ArchitectureMask) + | (image.mEnmOsType & VBOXOSTYPE_OsTypeMask)); + return; + } + } + } + } + } + } + Log(("Unattended: Warning! Bogus/missing version info for image #%u / %s\n", image.mImageIndex, image.mName.c_str())); +} + +/** + * Parses XML tree assuming th following structure + * @verbatim + * <WIM> + * ... + * <IMAGE INDEX="1"> + * ... + * <DISPLAYNAME>Windows 10 Home</DISPLAYNAME> + * <WINDOWS> + * <ARCH>NN</ARCH> + * <VERSION> + * ... + * </VERSION> + * <LANGUAGES> + * <LANGUAGE> + * en-US + * </LANGUAGE> + * <DEFAULT> + * en-US + * </DEFAULT> + * </LANGUAGES> + * </WINDOWS> + * </IMAGE> + * </WIM> + * @endverbatim + * + * @param pElmRoot Pointer to the root node of the tree, + * @param imageList Detected images are appended to this list. + */ +static void parseWimXMLData(const xml::ElementNode *pElmRoot, RTCList<WIMImage> &imageList) +{ + if (!pElmRoot) + return; + + ElementNodesList children; + int cChildren = pElmRoot->getChildElements(children, "IMAGE"); + if (cChildren == 0) + cChildren = pElmRoot->getChildElements(children, "image"); + if (cChildren == 0) + cChildren = pElmRoot->getChildElements(children, "Image"); + + for (ElementNodesList::iterator iterator = children.begin(); iterator != children.end(); ++iterator) + { + const ElementNode *pChild = *(iterator); + if (!pChild) + continue; + + WIMImage newImage; + + if ( !pChild->getAttributeValue("INDEX", &newImage.mImageIndex) + && !pChild->getAttributeValue("index", &newImage.mImageIndex) + && !pChild->getAttributeValue("Index", &newImage.mImageIndex)) + continue; + + const ElementNode *pElmName; + if ( (pElmName = pChild->findChildElement("DISPLAYNAME")) == NULL + && (pElmName = pChild->findChildElement("displayname")) == NULL + && (pElmName = pChild->findChildElement("Displayname")) == NULL + && (pElmName = pChild->findChildElement("DisplayName")) == NULL + /* Early vista images didn't have DISPLAYNAME. */ + && (pElmName = pChild->findChildElement("NAME")) == NULL + && (pElmName = pChild->findChildElement("name")) == NULL + && (pElmName = pChild->findChildElement("Name")) == NULL) + continue; + newImage.mName = pElmName->getValue(); + if (newImage.mName.isEmpty()) + continue; + + const ElementNode *pElmWindows; + if ( (pElmWindows = pChild->findChildElement("WINDOWS")) != NULL + || (pElmWindows = pChild->findChildElement("windows")) != NULL + || (pElmWindows = pChild->findChildElement("Windows")) != NULL) + { + /* Do edition/flags before the version so it can better determin + the OS version enum value. Old windows version (vista) typically + doesn't have an EDITIONID element, so fall back on the FLAGS element + under IMAGE as it is pretty similar (case differences). */ + const ElementNode *pElmEditionId; + if ( (pElmEditionId = pElmWindows->findChildElement("EDITIONID")) != NULL + || (pElmEditionId = pElmWindows->findChildElement("editionid")) != NULL + || (pElmEditionId = pElmWindows->findChildElement("Editionid")) != NULL + || (pElmEditionId = pElmWindows->findChildElement("EditionId")) != NULL + || (pElmEditionId = pChild->findChildElement("FLAGS")) != NULL + || (pElmEditionId = pChild->findChildElement("flags")) != NULL + || (pElmEditionId = pChild->findChildElement("Flags")) != NULL) + if ( pElmEditionId->getValue() + && *pElmEditionId->getValue() != '\0') + newImage.mFlavor = pElmEditionId->getValue(); + + const ElementNode *pElmVersion; + if ( (pElmVersion = pElmWindows->findChildElement("VERSION")) != NULL + || (pElmVersion = pElmWindows->findChildElement("version")) != NULL + || (pElmVersion = pElmWindows->findChildElement("Version")) != NULL) + parseVersionElement(pElmVersion, newImage); + + /* The ARCH element contains a number from the + PROCESSOR_ARCHITECTURE_XXX set of defines in winnt.h: */ + const ElementNode *pElmArch; + if ( (pElmArch = pElmWindows->findChildElement("ARCH")) != NULL + || (pElmArch = pElmWindows->findChildElement("arch")) != NULL + || (pElmArch = pElmWindows->findChildElement("Arch")) != NULL) + parseArchElement(pElmArch, newImage); + + /* Extract languages and default language: */ + const ElementNode *pElmLang; + if ( (pElmLang = pElmWindows->findChildElement("LANGUAGES")) != NULL + || (pElmLang = pElmWindows->findChildElement("languages")) != NULL + || (pElmLang = pElmWindows->findChildElement("Languages")) != NULL) + parseLangaguesElement(pElmLang, newImage); + } + + + imageList.append(newImage); + } +} + +/** + * Detect Windows ISOs. + * + * @returns COM status code. + * @retval S_OK if detected + * @retval S_FALSE if not fully detected. + * + * @param hVfsIso The ISO file system. + * @param pBuf Read buffer. + */ +HRESULT Unattended::i_innerDetectIsoOSWindows(RTVFS hVfsIso, DETECTBUFFER *pBuf) +{ + /** @todo The 'sources/' path can differ. */ + + // globalinstallorder.xml - vista beta2 + // sources/idwbinfo.txt - ditto. + // sources/lang.ini - ditto. + + /* + * The install.wim file contains an XML document describing the install + * images it contains. This includes all the info we need for a successful + * detection. + */ + RTVFSFILE hVfsFile; + int vrc = RTVfsFileOpen(hVfsIso, "sources/install.wim", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + WIMHEADERV1 header; + size_t cbRead = 0; + vrc = RTVfsFileRead(hVfsFile, &header, sizeof(header), &cbRead); + if (RT_SUCCESS(vrc) && cbRead == sizeof(header)) + { + /* If the xml data is not compressed, xml data is not empty, and not too big. */ + if ( (header.XmlData.bFlags & RESHDR_FLAGS_METADATA) + && !(header.XmlData.bFlags & RESHDR_FLAGS_COMPRESSED) + && header.XmlData.cbOriginal >= 32 + && header.XmlData.cbOriginal < _32M + && header.XmlData.cbOriginal == header.XmlData.cb) + { + size_t const cbXmlData = (size_t)header.XmlData.cbOriginal; + char *pachXmlBuf = (char *)RTMemTmpAlloc(cbXmlData); + if (pachXmlBuf) + { + vrc = RTVfsFileReadAt(hVfsFile, (RTFOFF)header.XmlData.off, pachXmlBuf, cbXmlData, NULL); + if (RT_SUCCESS(vrc)) + { + LogRel2(("XML Data (%#zx bytes):\n%32.*Rhxd\n", cbXmlData, cbXmlData, pachXmlBuf)); + + /* Parse the XML: */ + xml::Document doc; + xml::XmlMemParser parser; + try + { + RTCString strFileName = "source/install.wim"; + parser.read(pachXmlBuf, cbXmlData, strFileName, doc); + } + catch (xml::XmlError &rErr) + { + LogRel(("Unattended: An error has occured during XML parsing: %s\n", rErr.what())); + vrc = VERR_XAR_TOC_XML_PARSE_ERROR; + } + catch (std::bad_alloc &) + { + LogRel(("Unattended: std::bad_alloc\n")); + vrc = VERR_NO_MEMORY; + } + catch (...) + { + LogRel(("Unattended: An unknown error has occured during XML parsing.\n")); + vrc = VERR_UNEXPECTED_EXCEPTION; + } + if (RT_SUCCESS(vrc)) + { + /* Extract the information we need from the XML document: */ + xml::ElementNode *pElmRoot = doc.getRootElement(); + if (pElmRoot) + { + Assert(mDetectedImages.size() == 0); + try + { + mDetectedImages.clear(); /* debugging convenience */ + parseWimXMLData(pElmRoot, mDetectedImages); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + /* + * If we found images, update the detected info attributes. + */ + if (RT_SUCCESS(vrc) && mDetectedImages.size() > 0) + { + size_t i; + for (i = 0; i < mDetectedImages.size(); i++) + if (mDetectedImages[i].mImageIndex == midxImage) + break; + if (i >= mDetectedImages.size()) + i = 0; /* use the first one if midxImage wasn't found */ + if (i_updateDetectedAttributeForImage(mDetectedImages[i])) + { + LogRel2(("Unattended: happy with mDetectedImages[%u]\n", i)); + mEnmOsType = mDetectedImages[i].mOSType; + return S_OK; + } + } + } + else + LogRel(("Unattended: No root element found in XML Metadata of install.wim\n")); + } + } + else + LogRel(("Unattended: Failed during reading XML Metadata out of install.wim\n")); + RTMemTmpFree(pachXmlBuf); + } + else + { + LogRel(("Unattended: Failed to allocate %#zx bytes for XML Metadata\n", cbXmlData)); + vrc = VERR_NO_TMP_MEMORY; + } + } + else + LogRel(("Unattended: XML Metadata of install.wim is either compressed, empty, or too big (bFlags=%#x cbOriginal=%#RX64 cb=%#RX64)\n", + header.XmlData.bFlags, header.XmlData.cbOriginal, header.XmlData.cb)); + } + RTVfsFileRelease(hVfsFile); + + /* Bail out if we ran out of memory here. */ + if (vrc == VERR_NO_MEMORY || vrc == VERR_NO_TMP_MEMORY) + return setErrorBoth(E_OUTOFMEMORY, vrc, tr("Out of memory")); + } + + const char *pszVersion = NULL; + const char *pszProduct = NULL; + /* + * Try look for the 'sources/idwbinfo.txt' file containing windows build info. + * This file appeared with Vista beta 2 from what we can tell. Before windows 10 + * it contains easily decodable branch names, after that things goes weird. + */ + vrc = RTVfsFileOpen(hVfsIso, "sources/idwbinfo.txt", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + mEnmOsType = VBOXOSTYPE_WinNT_x64; + + RTINIFILE hIniFile; + vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + vrc = RTIniFileQueryValue(hIniFile, "BUILDINFO", "BuildArch", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + { + LogRelFlow(("Unattended: sources/idwbinfo.txt: BuildArch=%s\n", pBuf->sz)); + if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("amd64")) == 0 + || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("x64")) == 0 /* just in case */ ) + mEnmOsType = VBOXOSTYPE_WinNT_x64; + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("x86")) == 0) + mEnmOsType = VBOXOSTYPE_WinNT; + else + { + LogRel(("Unattended: sources/idwbinfo.txt: Unknown: BuildArch=%s\n", pBuf->sz)); + mEnmOsType = VBOXOSTYPE_WinNT_x64; + } + } + + vrc = RTIniFileQueryValue(hIniFile, "BUILDINFO", "BuildBranch", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + { + LogRelFlow(("Unattended: sources/idwbinfo.txt: BuildBranch=%s\n", pBuf->sz)); + if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("vista")) == 0 + || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_beta")) == 0) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista); + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("lh_sp2rtm")) == 0) + { + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista); + pszVersion = "sp2"; + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("longhorn_rtm")) == 0) + { + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista); + pszVersion = "sp1"; + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win7")) == 0) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win7); + else if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winblue")) == 0 + || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_blue")) == 0 + || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win81")) == 0 /* not seen, but just in case its out there */ ) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win81); + else if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win8")) == 0 + || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_win8")) == 0 ) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win8); + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("th1")) == 0) + { + pszVersion = "1507"; // aka. GA, retroactively 1507 + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("th2")) == 0) + { + pszVersion = "1511"; // aka. threshold 2 + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs1_release")) == 0) + { + pszVersion = "1607"; // aka. anniversay update; rs=redstone + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs2_release")) == 0) + { + pszVersion = "1703"; // aka. creators update + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs3_release")) == 0) + { + pszVersion = "1709"; // aka. fall creators update + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs4_release")) == 0) + { + pszVersion = "1803"; + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs5_release")) == 0) + { + pszVersion = "1809"; + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("19h1_release")) == 0) + { + pszVersion = "1903"; + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("19h2_release")) == 0) + { + pszVersion = "1909"; // ?? + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("20h1_release")) == 0) + { + pszVersion = "2003"; // ?? + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("vb_release")) == 0) + { + pszVersion = "2004"; // ?? vb=Vibranium + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("20h2_release")) == 0) + { + pszVersion = "2009"; // ?? + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("21h1_release")) == 0) + { + pszVersion = "2103"; // ?? + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("21h2_release")) == 0) + { + pszVersion = "2109"; // ?? + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10); + } + else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("co_release")) == 0) + { + pszVersion = "21H2"; // ?? + mEnmOsType = VBOXOSTYPE_Win11_x64; + } + else + LogRel(("Unattended: sources/idwbinfo.txt: Unknown: BuildBranch=%s\n", pBuf->sz)); + } + RTIniFileRelease(hIniFile); + } + } + bool fClarifyProd = false; + if (RT_FAILURE(vrc)) + { + /* + * Check a INF file with a DriverVer that is updated with each service pack. + * DriverVer=10/01/2002,5.2.3790.3959 + */ + vrc = RTVfsFileOpen(hVfsIso, "AMD64/HIVESYS.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + mEnmOsType = VBOXOSTYPE_WinNT_x64; + else + { + vrc = RTVfsFileOpen(hVfsIso, "I386/HIVESYS.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + mEnmOsType = VBOXOSTYPE_WinNT; + } + if (RT_SUCCESS(vrc)) + { + RTINIFILE hIniFile; + vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + vrc = RTIniFileQueryValue(hIniFile, "Version", "DriverVer", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + { + LogRelFlow(("Unattended: HIVESYS.INF: DriverVer=%s\n", pBuf->sz)); + const char *psz = strchr(pBuf->sz, ','); + psz = psz ? psz + 1 : pBuf->sz; + if (RTStrVersionCompare(psz, "6.0.0") >= 0) + LogRel(("Unattended: HIVESYS.INF: unknown: DriverVer=%s\n", psz)); + else if (RTStrVersionCompare(psz, "5.2.0") >= 0) /* W2K3, XP64 */ + { + fClarifyProd = true; + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k3); + if (RTStrVersionCompare(psz, "5.2.3790.3959") >= 0) + pszVersion = "sp2"; + else if (RTStrVersionCompare(psz, "5.2.3790.1830") >= 0) + pszVersion = "sp1"; + } + else if (RTStrVersionCompare(psz, "5.1.0") >= 0) /* XP */ + { + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinXP); + if (RTStrVersionCompare(psz, "5.1.2600.5512") >= 0) + pszVersion = "sp3"; + else if (RTStrVersionCompare(psz, "5.1.2600.2180") >= 0) + pszVersion = "sp2"; + else if (RTStrVersionCompare(psz, "5.1.2600.1105") >= 0) + pszVersion = "sp1"; + } + else if (RTStrVersionCompare(psz, "5.0.0") >= 0) + { + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k); + if (RTStrVersionCompare(psz, "5.0.2195.6717") >= 0) + pszVersion = "sp4"; + else if (RTStrVersionCompare(psz, "5.0.2195.5438") >= 0) + pszVersion = "sp3"; + else if (RTStrVersionCompare(psz, "5.0.2195.1620") >= 0) + pszVersion = "sp1"; + } + else + LogRel(("Unattended: HIVESYS.INF: unknown: DriverVer=%s\n", psz)); + } + RTIniFileRelease(hIniFile); + } + } + } + if (RT_FAILURE(vrc) || fClarifyProd) + { + /* + * NT 4 and older does not have DriverVer entries, we consult the PRODSPEC.INI, which + * works for NT4 & W2K. It does usually not reflect the service pack. + */ + vrc = RTVfsFileOpen(hVfsIso, "AMD64/PRODSPEC.INI", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + mEnmOsType = VBOXOSTYPE_WinNT_x64; + else + { + vrc = RTVfsFileOpen(hVfsIso, "I386/PRODSPEC.INI", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + mEnmOsType = VBOXOSTYPE_WinNT; + } + if (RT_SUCCESS(vrc)) + { + + RTINIFILE hIniFile; + vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "Version", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + { + LogRelFlow(("Unattended: PRODSPEC.INI: Version=%s\n", pBuf->sz)); + if (RTStrVersionCompare(pBuf->sz, "5.1") >= 0) /* Shipped with XP + W2K3, but version stuck at 5.0. */ + LogRel(("Unattended: PRODSPEC.INI: unknown: DriverVer=%s\n", pBuf->sz)); + else if (RTStrVersionCompare(pBuf->sz, "5.0") >= 0) /* 2000 */ + { + vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "Product", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc) && RTStrNICmp(pBuf->sz, RT_STR_TUPLE("Windows XP")) == 0) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinXP); + else if (RT_SUCCESS(vrc) && RTStrNICmp(pBuf->sz, RT_STR_TUPLE("Windows Server 2003")) == 0) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k3); + else + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k); + + if (RT_SUCCESS(vrc) && (strstr(pBuf->sz, "Server") || strstr(pBuf->sz, "server"))) + pszProduct = "Server"; + } + else if (RTStrVersionCompare(pBuf->sz, "4.0") >= 0) /* NT4 */ + mEnmOsType = VBOXOSTYPE_WinNT4; + else + LogRel(("Unattended: PRODSPEC.INI: unknown: DriverVer=%s\n", pBuf->sz)); + + vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "ProductType", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + pszProduct = strcmp(pBuf->sz, "0") == 0 ? "Workstation" : /* simplification: */ "Server"; + } + RTIniFileRelease(hIniFile); + } + } + if (fClarifyProd) + vrc = VINF_SUCCESS; + } + if (RT_FAILURE(vrc)) + { + /* + * NT 3.x we look at the LoadIdentifier (boot manager) string in TXTSETUP.SIF/TXT. + */ + vrc = RTVfsFileOpen(hVfsIso, "I386/TXTSETUP.SIF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_FAILURE(vrc)) + vrc = RTVfsFileOpen(hVfsIso, "I386/TXTSETUP.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + mEnmOsType = VBOXOSTYPE_WinNT; + + RTINIFILE hIniFile; + vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + vrc = RTIniFileQueryValue(hIniFile, "SetupData", "ProductType", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + pszProduct = strcmp(pBuf->sz, "0") == 0 ? "Workstation" : /* simplification: */ "Server"; + + vrc = RTIniFileQueryValue(hIniFile, "SetupData", "LoadIdentifier", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + { + LogRelFlow(("Unattended: TXTSETUP.SIF: LoadIdentifier=%s\n", pBuf->sz)); + char *psz = pBuf->sz; + while (!RT_C_IS_DIGIT(*psz) && *psz) + psz++; + char *psz2 = psz; + while (RT_C_IS_DIGIT(*psz2) || *psz2 == '.') + psz2++; + *psz2 = '\0'; + if (RTStrVersionCompare(psz, "6.0") >= 0) + LogRel(("Unattended: TXTSETUP.SIF: unknown: LoadIdentifier=%s\n", pBuf->sz)); + else if (RTStrVersionCompare(psz, "4.0") >= 0) + mEnmOsType = VBOXOSTYPE_WinNT4; + else if (RTStrVersionCompare(psz, "3.1") >= 0) + { + mEnmOsType = VBOXOSTYPE_WinNT3x; + pszVersion = psz; + } + else + LogRel(("Unattended: TXTSETUP.SIF: unknown: LoadIdentifier=%s\n", pBuf->sz)); + } + RTIniFileRelease(hIniFile); + } + } + } + + if (pszVersion) + try { mStrDetectedOSVersion = pszVersion; } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + if (pszProduct) + try { mStrDetectedOSFlavor = pszProduct; } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + /* + * Look for sources/lang.ini and try parse it to get the languages out of it. + */ + /** @todo We could also check sources/??-* and boot/??-* if lang.ini is not + * found or unhelpful. */ + vrc = RTVfsFileOpen(hVfsIso, "sources/lang.ini", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + RTINIFILE hIniFile; + vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + mDetectedOSLanguages.clear(); + + uint32_t idxPair; + for (idxPair = 0; idxPair < 256; idxPair++) + { + size_t cbHalf = sizeof(*pBuf) / 2; + char *pszKey = pBuf->sz; + char *pszValue = &pBuf->sz[cbHalf]; + vrc = RTIniFileQueryPair(hIniFile, "Available UI Languages", idxPair, + pszKey, cbHalf, NULL, pszValue, cbHalf, NULL); + if (RT_SUCCESS(vrc)) + { + try + { + mDetectedOSLanguages.append(pszKey); + } + catch (std::bad_alloc &) + { + RTIniFileRelease(hIniFile); + return E_OUTOFMEMORY; + } + } + else if (vrc == VERR_NOT_FOUND) + break; + else + Assert(vrc == VERR_BUFFER_OVERFLOW); + } + if (idxPair == 0) + LogRel(("Unattended: Warning! Empty 'Available UI Languages' section in sources/lang.ini\n")); + RTIniFileRelease(hIniFile); + } + } + + return S_FALSE; +} + +/** + * Architecture strings for Linux and the like. + */ +static struct { const char *pszArch; uint32_t cchArch; VBOXOSTYPE fArch; } const g_aLinuxArches[] = +{ + { RT_STR_TUPLE("amd64"), VBOXOSTYPE_x64 }, + { RT_STR_TUPLE("x86_64"), VBOXOSTYPE_x64 }, + { RT_STR_TUPLE("x86-64"), VBOXOSTYPE_x64 }, /* just in case */ + { RT_STR_TUPLE("x64"), VBOXOSTYPE_x64 }, /* ditto */ + + { RT_STR_TUPLE("x86"), VBOXOSTYPE_x86 }, + { RT_STR_TUPLE("i386"), VBOXOSTYPE_x86 }, + { RT_STR_TUPLE("i486"), VBOXOSTYPE_x86 }, + { RT_STR_TUPLE("i586"), VBOXOSTYPE_x86 }, + { RT_STR_TUPLE("i686"), VBOXOSTYPE_x86 }, + { RT_STR_TUPLE("i786"), VBOXOSTYPE_x86 }, + { RT_STR_TUPLE("i886"), VBOXOSTYPE_x86 }, + { RT_STR_TUPLE("i986"), VBOXOSTYPE_x86 }, +}; + +/** + * Detects linux architecture. + * + * @returns true if detected, false if not. + * @param pszArch The architecture string. + * @param penmOsType Where to return the arch and type on success. + * @param enmBaseOsType The base (x86) OS type to return. + */ +static bool detectLinuxArch(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType) +{ + for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++) + if (RTStrNICmp(pszArch, g_aLinuxArches[i].pszArch, g_aLinuxArches[i].cchArch) == 0) + { + *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch); + return true; + } + /** @todo check for 'noarch' since source CDs have been seen to use that. */ + return false; +} + +/** + * Detects linux architecture by searching for the architecture substring in @p pszArch. + * + * @returns true if detected, false if not. + * @param pszArch The architecture string. + * @param penmOsType Where to return the arch and type on success. + * @param enmBaseOsType The base (x86) OS type to return. + * @param ppszHit Where to return the pointer to the architecture + * specifier. Optional. + * @param ppszNext Where to return the pointer to the char + * following the architecuture specifier. Optional. + */ +static bool detectLinuxArchII(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType, + char **ppszHit = NULL, char **ppszNext = NULL) +{ + for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++) + { + const char *pszHit = RTStrIStr(pszArch, g_aLinuxArches[i].pszArch); + if (pszHit != NULL) + { + if (ppszHit) + *ppszHit = (char *)pszHit; + if (ppszNext) + *ppszNext = (char *)pszHit + g_aLinuxArches[i].cchArch; + *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch); + return true; + } + } + return false; +} + +static bool detectLinuxDistroName(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext) +{ + bool fRet = true; + + if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Red")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[3])) + + { + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3); + if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Hat")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[3])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3); + } + else + fRet = false; + } + else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("OpenSUSE")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[8])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_OpenSUSE); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 8); + } + else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Oracle")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[6])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6); + } + else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("CentOS")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[6])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6); + } + else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Fedora")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[6])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6); + } + else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Ubuntu")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[6])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6); + } + else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Linux Mint")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[10])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 10); + } + else if ( ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Xubuntu")) == 0 + || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Kubuntu")) == 0 + || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Lubuntu")) == 0) + && !RT_C_IS_ALNUM(pszOsAndVersion[7])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 7); + } + else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Debian")) == 0 + && !RT_C_IS_ALNUM(pszOsAndVersion[6])) + { + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian); + pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6); + } + else + fRet = false; + + /* + * Skip forward till we get a number. + */ + if (ppszNext) + { + *ppszNext = pszOsAndVersion; + char ch; + for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++) + if (RT_C_IS_DIGIT(ch)) + { + *ppszNext = pszVersion; + break; + } + } + return fRet; +} + +static bool detectLinuxDistroNameII(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext) +{ + bool fRet = true; + if ( RTStrIStr(pszOsAndVersion, "RedHat") != NULL + || RTStrIStr(pszOsAndVersion, "Red Hat") != NULL) + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat); + else if (RTStrIStr(pszOsAndVersion, "Oracle") != NULL) + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle); + else if (RTStrIStr(pszOsAndVersion, "CentOS") != NULL) + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat); + else if (RTStrIStr(pszOsAndVersion, "Fedora") != NULL) + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore); + else if (RTStrIStr(pszOsAndVersion, "Ubuntu") != NULL) + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu); + else if (RTStrIStr(pszOsAndVersion, "Mint") != NULL) + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu); + else if (RTStrIStr(pszOsAndVersion, "Debian")) + *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian); + else + fRet = false; + + /* + * Skip forward till we get a number. + */ + if (ppszNext) + { + *ppszNext = pszOsAndVersion; + char ch; + for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++) + if (RT_C_IS_DIGIT(ch)) + { + *ppszNext = pszVersion; + break; + } + } + return fRet; +} + + +/** + * Helps detecting linux distro flavor by finding substring position of non numerical + * part of the disk name. + * + * @returns true if detected, false if not. + * @param pszDiskName Name of the disk as it is read from .disk/info or + * README.diskdefines file. + * @param poffVersion String position where first numerical character is + * found. We use substring upto this position as OS flavor + */ +static bool detectLinuxDistroFlavor(const char *pszDiskName, size_t *poffVersion) +{ + Assert(poffVersion); + if (!pszDiskName) + return false; + char ch; + while ((ch = *pszDiskName) != '\0' && !RT_C_IS_DIGIT(ch)) + { + ++pszDiskName; + *poffVersion += 1; + } + return true; +} + +/** + * Detect Linux distro ISOs. + * + * @returns COM status code. + * @retval S_OK if detected + * @retval S_FALSE if not fully detected. + * + * @param hVfsIso The ISO file system. + * @param pBuf Read buffer. + */ +HRESULT Unattended::i_innerDetectIsoOSLinux(RTVFS hVfsIso, DETECTBUFFER *pBuf) +{ + /* + * Redhat and derivatives may have a .treeinfo (ini-file style) with useful info + * or at least a barebone .discinfo file. + */ + + /* + * Start with .treeinfo: https://release-engineering.github.io/productmd/treeinfo-1.0.html + */ + RTVFSFILE hVfsFile; + int vrc = RTVfsFileOpen(hVfsIso, ".treeinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + RTINIFILE hIniFile; + vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + /* Try figure the architecture first (like with windows). */ + vrc = RTIniFileQueryValue(hIniFile, "tree", "arch", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_FAILURE(vrc) || !pBuf->sz[0]) + vrc = RTIniFileQueryValue(hIniFile, "general", "arch", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_FAILURE(vrc)) + LogRel(("Unattended: .treeinfo: No 'arch' property.\n")); + else + { + LogRelFlow(("Unattended: .treeinfo: arch=%s\n", pBuf->sz)); + if (detectLinuxArch(pBuf->sz, &mEnmOsType, VBOXOSTYPE_RedHat)) + { + /* Try figure the release name, it doesn't have to be redhat. */ + vrc = RTIniFileQueryValue(hIniFile, "release", "name", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_FAILURE(vrc) || !pBuf->sz[0]) + vrc = RTIniFileQueryValue(hIniFile, "product", "name", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_FAILURE(vrc) || !pBuf->sz[0]) + vrc = RTIniFileQueryValue(hIniFile, "general", "family", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + { + LogRelFlow(("Unattended: .treeinfo: name/family=%s\n", pBuf->sz)); + if (!detectLinuxDistroName(pBuf->sz, &mEnmOsType, NULL)) + { + LogRel(("Unattended: .treeinfo: Unknown: name/family='%s', assuming Red Hat\n", pBuf->sz)); + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat); + } + } + + /* Try figure the version. */ + vrc = RTIniFileQueryValue(hIniFile, "release", "version", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_FAILURE(vrc) || !pBuf->sz[0]) + vrc = RTIniFileQueryValue(hIniFile, "product", "version", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_FAILURE(vrc) || !pBuf->sz[0]) + vrc = RTIniFileQueryValue(hIniFile, "general", "version", pBuf->sz, sizeof(*pBuf), NULL); + if (RT_SUCCESS(vrc)) + { + LogRelFlow(("Unattended: .treeinfo: version=%s\n", pBuf->sz)); + try { mStrDetectedOSVersion = RTStrStrip(pBuf->sz); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + size_t cchVersionPosition = 0; + if (detectLinuxDistroFlavor(pBuf->sz, &cchVersionPosition)) + { + try { mStrDetectedOSFlavor = Utf8Str(pBuf->sz, cchVersionPosition); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + } + } + } + else + LogRel(("Unattended: .treeinfo: Unknown: arch='%s'\n", pBuf->sz)); + } + + RTIniFileRelease(hIniFile); + } + + if (mEnmOsType != VBOXOSTYPE_Unknown) + return S_FALSE; + } + + /* + * Try .discinfo next: https://release-engineering.github.io/productmd/discinfo-1.0.html + * We will probably need additional info here... + */ + vrc = RTVfsFileOpen(hVfsIso, ".discinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + size_t cchIgn; + vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn); + pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0'; + RTVfsFileRelease(hVfsFile); + + /* Parse and strip the first 5 lines. */ + const char *apszLines[5]; + char *psz = pBuf->sz; + for (unsigned i = 0; i < RT_ELEMENTS(apszLines); i++) + { + apszLines[i] = psz; + if (*psz) + { + char *pszEol = (char *)strchr(psz, '\n'); + if (!pszEol) + psz = strchr(psz, '\0'); + else + { + *pszEol = '\0'; + apszLines[i] = RTStrStrip(psz); + psz = pszEol + 1; + } + } + } + + /* Do we recognize the architecture? */ + LogRelFlow(("Unattended: .discinfo: arch=%s\n", apszLines[2])); + if (detectLinuxArch(apszLines[2], &mEnmOsType, VBOXOSTYPE_RedHat)) + { + /* Do we recognize the release string? */ + LogRelFlow(("Unattended: .discinfo: product+version=%s\n", apszLines[1])); + const char *pszVersion = NULL; + if (!detectLinuxDistroName(apszLines[1], &mEnmOsType, &pszVersion)) + LogRel(("Unattended: .discinfo: Unknown: release='%s'\n", apszLines[1])); + + if (*pszVersion) + { + LogRelFlow(("Unattended: .discinfo: version=%s\n", pszVersion)); + try { mStrDetectedOSVersion = RTStrStripL(pszVersion); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + /* CentOS likes to call their release 'Final' without mentioning the actual version + number (e.g. CentOS-4.7-x86_64-binDVD.iso), so we need to go look elsewhere. + This is only important for centos 4.x and 3.x releases. */ + if (RTStrNICmp(pszVersion, RT_STR_TUPLE("Final")) == 0) + { + static const char * const s_apszDirs[] = { "CentOS/RPMS/", "RedHat/RPMS", "Server", "Workstation" }; + for (unsigned iDir = 0; iDir < RT_ELEMENTS(s_apszDirs); iDir++) + { + RTVFSDIR hVfsDir; + vrc = RTVfsDirOpen(hVfsIso, s_apszDirs[iDir], 0, &hVfsDir); + if (RT_FAILURE(vrc)) + continue; + char szRpmDb[128]; + char szReleaseRpm[128]; + szRpmDb[0] = '\0'; + szReleaseRpm[0] = '\0'; + for (;;) + { + RTDIRENTRYEX DirEntry; + size_t cbDirEntry = sizeof(DirEntry); + vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(vrc)) + break; + + /* redhat-release-4WS-2.4.i386.rpm + centos-release-4-7.x86_64.rpm, centos-release-4-4.3.i386.rpm + centos-release-5-3.el5.centos.1.x86_64.rpm */ + if ( (psz = strstr(DirEntry.szName, "-release-")) != NULL + || (psz = strstr(DirEntry.szName, "-RELEASE-")) != NULL) + { + psz += 9; + if (RT_C_IS_DIGIT(*psz)) + RTStrCopy(szReleaseRpm, sizeof(szReleaseRpm), psz); + } + /* rpmdb-redhat-4WS-2.4.i386.rpm, + rpmdb-CentOS-4.5-0.20070506.i386.rpm, + rpmdb-redhat-3.9-0.20070703.i386.rpm. */ + else if ( ( RTStrStartsWith(DirEntry.szName, "rpmdb-") + || RTStrStartsWith(DirEntry.szName, "RPMDB-")) + && RT_C_IS_DIGIT(DirEntry.szName[6]) ) + RTStrCopy(szRpmDb, sizeof(szRpmDb), &DirEntry.szName[6]); + } + RTVfsDirRelease(hVfsDir); + + /* Did we find anything relvant? */ + psz = szRpmDb; + if (!RT_C_IS_DIGIT(*psz)) + psz = szReleaseRpm; + if (RT_C_IS_DIGIT(*psz)) + { + /* Convert '-' to '.' and strip stuff which doesn't look like a version string. */ + char *pszCur = psz + 1; + for (char ch = *pszCur; ch != '\0'; ch = *++pszCur) + if (ch == '-') + *pszCur = '.'; + else if (ch != '.' && !RT_C_IS_DIGIT(ch)) + { + *pszCur = '\0'; + break; + } + while (&pszCur[-1] != psz && pszCur[-1] == '.') + *--pszCur = '\0'; + + /* Set it and stop looking. */ + try { mStrDetectedOSVersion = psz; } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + break; + } + } + } + } + size_t cchVersionPosition = 0; + if (detectLinuxDistroFlavor(apszLines[1], &cchVersionPosition)) + { + try { mStrDetectedOSFlavor = Utf8Str(apszLines[1], cchVersionPosition); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + } + } + else + LogRel(("Unattended: .discinfo: Unknown: arch='%s'\n", apszLines[2])); + + if (mEnmOsType != VBOXOSTYPE_Unknown) + return S_FALSE; + } + + /* + * Ubuntu has a README.diskdefines file on their ISO (already on 4.10 / warty warthog). + * Example content: + * #define DISKNAME Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1 + * #define TYPE binary + * #define TYPEbinary 1 + * #define ARCH amd64 + * #define ARCHamd64 1 + * #define DISKNUM 1 + * #define DISKNUM1 1 + * #define TOTALNUM 1 + * #define TOTALNUM1 1 + */ + vrc = RTVfsFileOpen(hVfsIso, "README.diskdefines", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + size_t cchIgn; + vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn); + pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0'; + RTVfsFileRelease(hVfsFile); + + /* Find the DISKNAME and ARCH defines. */ + const char *pszDiskName = NULL; + const char *pszArch = NULL; + char *psz = pBuf->sz; + for (unsigned i = 0; *psz != '\0'; i++) + { + while (RT_C_IS_BLANK(*psz)) + psz++; + + /* Match #define: */ + static const char s_szDefine[] = "#define"; + if ( strncmp(psz, s_szDefine, sizeof(s_szDefine) - 1) == 0 + && RT_C_IS_BLANK(psz[sizeof(s_szDefine) - 1])) + { + psz = &psz[sizeof(s_szDefine) - 1]; + while (RT_C_IS_BLANK(*psz)) + psz++; + + /* Match the identifier: */ + char *pszIdentifier = psz; + if (RT_C_IS_ALPHA(*psz) || *psz == '_') + { + do + psz++; + while (RT_C_IS_ALNUM(*psz) || *psz == '_'); + size_t cchIdentifier = (size_t)(psz - pszIdentifier); + + /* Skip to the value. */ + while (RT_C_IS_BLANK(*psz)) + psz++; + char *pszValue = psz; + + /* Skip to EOL and strip the value. */ + char *pszEol = psz = strchr(psz, '\n'); + if (psz) + *psz++ = '\0'; + else + pszEol = strchr(pszValue, '\0'); + while (pszEol > pszValue && RT_C_IS_SPACE(pszEol[-1])) + *--pszEol = '\0'; + + LogRelFlow(("Unattended: README.diskdefines: %.*s=%s\n", cchIdentifier, pszIdentifier, pszValue)); + + /* Do identifier matching: */ + if (cchIdentifier == sizeof("DISKNAME") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("DISKNAME")) == 0) + pszDiskName = pszValue; + else if (cchIdentifier == sizeof("ARCH") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("ARCH")) == 0) + pszArch = pszValue; + else + continue; + if (pszDiskName == NULL || pszArch == NULL) + continue; + break; + } + } + + /* Next line: */ + psz = strchr(psz, '\n'); + if (!psz) + break; + psz++; + } + + /* Did we find both of them? */ + if (pszDiskName && pszArch) + { + if (detectLinuxArch(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu)) + { + const char *pszVersion = NULL; + if (detectLinuxDistroName(pszDiskName, &mEnmOsType, &pszVersion)) + { + LogRelFlow(("Unattended: README.diskdefines: version=%s\n", pszVersion)); + try { mStrDetectedOSVersion = RTStrStripL(pszVersion); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + size_t cchVersionPosition = 0; + if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition)) + { + try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + } + } + else + LogRel(("Unattended: README.diskdefines: Unknown: diskname='%s'\n", pszDiskName)); + } + else + LogRel(("Unattended: README.diskdefines: Unknown: arch='%s'\n", pszArch)); + } + else + LogRel(("Unattended: README.diskdefines: Did not find both DISKNAME and ARCH. :-/\n")); + + if (mEnmOsType != VBOXOSTYPE_Unknown) + return S_FALSE; + } + + /* + * All of the debian based distro versions I checked have a single line ./disk/info + * file. Only info I could find related to .disk folder is: + * https://lists.debian.org/debian-cd/2004/01/msg00069.html + * + * Some example content from several install ISOs is as follows: + * Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1 (20041020) + * Linux Mint 20.3 "Una" - Release amd64 20220104 + * Debian GNU/Linux 11.2.0 "Bullseye" - Official amd64 NETINST 20211218-11:12 + * Debian GNU/Linux 9.13.0 "Stretch" - Official amd64 DVD Binary-1 20200718-11:07 + * Xubuntu 20.04.2.0 LTS "Focal Fossa" - Release amd64 (20210209.1) + * Ubuntu 17.10 "Artful Aardvark" - Release amd64 (20180105.1) + * Ubuntu 16.04.6 LTS "Xenial Xerus" - Release i386 (20190227.1) + * Debian GNU/Linux 8.11.1 "Jessie" - Official amd64 CD Binary-1 20190211-02:10 + * Kali GNU/Linux 2021.3a "Kali-last-snapshot" - Official amd64 BD Binary-1 with firmware 20211015-16:55 + * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13 + */ + vrc = RTVfsFileOpen(hVfsIso, ".disk/info", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + size_t cchIgn; + vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn); + pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0'; + + pBuf->sz[sizeof(*pBuf) - 1] = '\0'; + RTVfsFileRelease(hVfsFile); + + char *psz = pBuf->sz; + char *pszDiskName = psz; + char *pszArch = NULL; + + /* Only care about the first line of the file even if it is multi line and assume disk name ended with ' - '.*/ + psz = RTStrStr(pBuf->sz, " - "); + if (psz && memchr(pBuf->sz, '\n', (size_t)(psz - pBuf->sz)) == NULL) + { + *psz = '\0'; + psz += 3; + if (*psz) + pszArch = psz; + } + + /* Some Debian Live ISO's have info file content as follows: + * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13 + * thus pszArch stays empty. Try Volume Id (label) if we get lucky and get architecture from that. */ + if (!pszArch) + { + char szVolumeId[128]; + vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL); + if (RT_SUCCESS(vrc)) + { + if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_Ubuntu)) + LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", szVolumeId)); + } + else + LogRel(("Unattended: .disk/info No Volume Label found\n")); + } + else + { + if (!detectLinuxArchII(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu)) + LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", pszArch)); + } + + if (pszDiskName) + { + const char *pszVersion = NULL; + if (detectLinuxDistroNameII(pszDiskName, &mEnmOsType, &pszVersion)) + { + LogRelFlow(("Unattended: .disk/info: version=%s\n", pszVersion)); + try { mStrDetectedOSVersion = RTStrStripL(pszVersion); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + size_t cchVersionPosition = 0; + if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition)) + { + try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + } + } + else + LogRel(("Unattended: .disk/info: Unknown: diskname='%s'\n", pszDiskName)); + } + + if (mEnmOsType == VBOXOSTYPE_Unknown) + LogRel(("Unattended: .disk/info: Did not find DISKNAME or/and ARCH. :-/\n")); + else + return S_FALSE; + } + + /* + * Fedora live iso should be recognizable from the primary volume ID (the + * joliet one is usually truncated). We set fAlternative = true here to + * get the primary volume ID. + */ + char szVolumeId[128]; + vrc = RTVfsQueryLabel(hVfsIso, true /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL); + if (RT_SUCCESS(vrc) && RTStrStartsWith(szVolumeId, "Fedora-")) + return i_innerDetectIsoOSLinuxFedora(hVfsIso, pBuf, &szVolumeId[sizeof("Fedora-") - 1]); + return S_FALSE; +} + + +/** + * Continues working a Fedora ISO image after the caller found a "Fedora-*" + * volume ID. + * + * Sample Volume IDs: + * - Fedora-WS-Live-34-1-2 (joliet: Fedora-WS-Live-3) + * - Fedora-S-dvd-x86_64-34 (joliet: Fedora-S-dvd-x86) + * - Fedora-WS-dvd-i386-25 (joliet: Fedora-WS-dvd-i3) + */ +HRESULT Unattended::i_innerDetectIsoOSLinuxFedora(RTVFS hVfsIso, DETECTBUFFER *pBuf, char *pszVolId) +{ + char * const pszFlavor = pszVolId; + char * psz = pszVolId; + + /* The volume id may or may not include an arch, component. + We ASSUME that it includes a numeric part with the version, or at least + part of it. */ + char *pszVersion = NULL; + char *pszArch = NULL; + if (detectLinuxArchII(psz, &mEnmOsType, VBOXOSTYPE_FedoraCore, &pszArch, &pszVersion)) + { + while (*pszVersion == '-') + pszVersion++; + *pszArch = '\0'; + } + else + { + mEnmOsType = (VBOXOSTYPE)(VBOXOSTYPE_FedoraCore | VBOXOSTYPE_UnknownArch); + + char ch; + while ((ch = *psz) != '\0' && (!RT_C_IS_DIGIT(ch) || !RT_C_IS_PUNCT(psz[-1]))) + psz++; + if (ch != '\0') + pszVersion = psz; + } + + /* + * Replace '-' with '.' in the version part and use it as the version. + */ + if (pszVersion) + { + psz = pszVersion; + while ((psz = strchr(psz, '-')) != NULL) + *psz++ = '.'; + try { mStrDetectedOSVersion = RTStrStrip(pszVersion); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + *pszVersion = '\0'; /* don't include in flavor */ + } + + /* + * Split up the pre-arch/version bits into words and use them as the flavor. + */ + psz = pszFlavor; + while ((psz = strchr(psz, '-')) != NULL) + *psz++ = ' '; + try { mStrDetectedOSFlavor = RTStrStrip(pszFlavor); } + catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + /* + * If we don't have an architecture, we look at the vmlinuz file as the x86 + * and AMD64 versions starts with a MZ+PE header giving the architecture. + */ + if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch) + { + static const char * const s_apszVmLinuz[] = { "images/pxeboot/vmlinuz", "isolinux/vmlinuz" }; + for (size_t i = 0; i < RT_ELEMENTS(s_apszVmLinuz); i++) + { + RTVFSFILE hVfsFileLinuz = NIL_RTVFSFILE; + int vrc = RTVfsFileOpen(hVfsIso, s_apszVmLinuz[i], RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, + &hVfsFileLinuz); + if (RT_SUCCESS(vrc)) + { + /* DOS signature: */ + PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pBuf->ab[0]; + AssertCompile(sizeof(*pBuf) > sizeof(*pDosHdr)); + vrc = RTVfsFileReadAt(hVfsFileLinuz, 0, pDosHdr, sizeof(*pDosHdr), NULL); + if (RT_SUCCESS(vrc) && pDosHdr->e_magic == IMAGE_DOS_SIGNATURE) + { + /* NT signature - only need magic + file header, so use the 64 version for better debugging: */ + PIMAGE_NT_HEADERS64 pNtHdrs = (PIMAGE_NT_HEADERS64)&pBuf->ab[0]; + vrc = RTVfsFileReadAt(hVfsFileLinuz, pDosHdr->e_lfanew, pNtHdrs, sizeof(*pNtHdrs), NULL); + AssertCompile(sizeof(*pBuf) > sizeof(*pNtHdrs)); + if (RT_SUCCESS(vrc) && pNtHdrs->Signature == IMAGE_NT_SIGNATURE) + { + if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x86); + else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x64); + else + AssertFailed(); + } + } + + RTVfsFileRelease(hVfsFileLinuz); + if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch) + break; + } + } + } + + /* + * If that failed, look for other files that gives away the arch. + */ + if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch) + { + static struct { const char *pszFile; VBOXOSTYPE fArch; } const s_aArchSpecificFiles[] = + { + { "EFI/BOOT/grubaa64.efi", VBOXOSTYPE_arm64 }, + { "EFI/BOOT/BOOTAA64.EFI", VBOXOSTYPE_arm64 }, + }; + PRTFSOBJINFO pObjInfo = (PRTFSOBJINFO)&pBuf->ab[0]; + AssertCompile(sizeof(*pBuf) > sizeof(*pObjInfo)); + for (size_t i = 0; i < RT_ELEMENTS(s_aArchSpecificFiles); i++) + { + int vrc = RTVfsQueryPathInfo(hVfsIso, s_aArchSpecificFiles[i].pszFile, pObjInfo, + RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(vrc) && RTFS_IS_FILE(pObjInfo->Attr.fMode)) + { + mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | s_aArchSpecificFiles[i].fArch); + break; + } + } + } + + /* + * If we like, we could parse grub.conf to look for fullly spelled out + * flavor, though the menu items typically only contains the major version + * number, so little else to add, really. + */ + + return (mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch ? S_OK : S_FALSE; +} + + +/** + * Detect OS/2 installation ISOs. + * + * Mainly aiming at ACP2/MCP2 as that's what we currently use in our testing. + * + * @returns COM status code. + * @retval S_OK if detected + * @retval S_FALSE if not fully detected. + * + * @param hVfsIso The ISO file system. + * @param pBuf Read buffer. + */ +HRESULT Unattended::i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *pBuf) +{ + /* + * The OS2SE20.SRC contains the location of the tree with the diskette + * images, typically "\OS2IMAGE". + */ + RTVFSFILE hVfsFile; + int vrc = RTVfsFileOpen(hVfsIso, "OS2SE20.SRC", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + size_t cbRead = 0; + vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + pBuf->sz[cbRead] = '\0'; + RTStrStrip(pBuf->sz); + vrc = RTStrValidateEncoding(pBuf->sz); + if (RT_SUCCESS(vrc)) + LogRelFlow(("Unattended: OS2SE20.SRC=%s\n", pBuf->sz)); + else + LogRel(("Unattended: OS2SE20.SRC invalid encoding: %Rrc, %.*Rhxs\n", vrc, cbRead, pBuf->sz)); + } + else + LogRel(("Unattended: Error reading OS2SE20.SRC: %\n", vrc)); + } + /* + * ArcaOS has dropped the file, assume it's \OS2IMAGE and see if it's there. + */ + else if (vrc == VERR_FILE_NOT_FOUND) + RTStrCopy(pBuf->sz, sizeof(pBuf->sz), "\\OS2IMAGE"); + else + return S_FALSE; + + /* + * Check that the directory directory exists and has a DISK_0 under it + * with an OS2LDR on it. + */ + size_t const cchOs2Image = strlen(pBuf->sz); + vrc = RTPathAppend(pBuf->sz, sizeof(pBuf->sz), "DISK_0/OS2LDR"); + RTFSOBJINFO ObjInfo = {0}; + vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (vrc == VERR_FILE_NOT_FOUND) + { + RTStrCat(pBuf->sz, sizeof(pBuf->sz), "."); /* eCS 2.0 image includes the dot from the 8.3 name. */ + vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + } + if ( RT_FAILURE(vrc) + || !RTFS_IS_FILE(ObjInfo.Attr.fMode)) + { + LogRel(("Unattended: RTVfsQueryPathInfo(, '%s' (from OS2SE20.SRC),) -> %Rrc, fMode=%#x\n", + pBuf->sz, vrc, ObjInfo.Attr.fMode)); + return S_FALSE; + } + + /* + * So, it's some kind of OS/2 2.x or later ISO alright. + */ + mEnmOsType = VBOXOSTYPE_OS2; + mStrDetectedOSHints.printf("OS2SE20.SRC=%.*s", cchOs2Image, pBuf->sz); + + /* + * ArcaOS ISOs seems to have a AOSBOOT dir on them. + * This contains a ARCANOAE.FLG file with content we can use for the version: + * ArcaOS 5.0.7 EN + * Built 2021-12-07 18:34:34 + * We drop the "ArcaOS" bit, as it's covered by mEnmOsType. Then we pull up + * the second line. + * + * Note! Yet to find a way to do unattended install of ArcaOS, as it comes + * with no CD-boot floppy images, only simple .PF archive files for + * unpacking onto the ram disk or whatever. Modifying these is + * possible (ibsen's aPLib v0.36 compression with some simple custom + * headers), but it would probably be a royal pain. Could perhaps + * cook something from OS2IMAGE\DISK_0 thru 3... + */ + vrc = RTVfsQueryPathInfo(hVfsIso, "AOSBOOT", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if ( RT_SUCCESS(vrc) + && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + { + mEnmOsType = VBOXOSTYPE_ArcaOS; + + /* Read the version file: */ + vrc = RTVfsFileOpen(hVfsIso, "SYS/ARCANOAE.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + size_t cbRead = 0; + vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead); + RTVfsFileRelease(hVfsFile); + pBuf->sz[cbRead] = '\0'; + if (RT_SUCCESS(vrc)) + { + /* Strip the OS name: */ + char *pszVersion = RTStrStrip(pBuf->sz); + static char s_szArcaOS[] = "ArcaOS"; + if (RTStrStartsWith(pszVersion, s_szArcaOS)) + pszVersion = RTStrStripL(pszVersion + sizeof(s_szArcaOS) - 1); + + /* Pull up the 2nd line if it, condensing the \r\n into a single space. */ + char *pszNewLine = strchr(pszVersion, '\n'); + if (pszNewLine && RTStrStartsWith(pszNewLine + 1, "Built 20")) + { + size_t offRemove = 0; + while (RT_C_IS_SPACE(pszNewLine[-1 - (ssize_t)offRemove])) + offRemove++; + if (offRemove > 0) + { + pszNewLine -= offRemove; + memmove(pszNewLine, pszNewLine + offRemove, strlen(pszNewLine + offRemove) - 1); + } + *pszNewLine = ' '; + } + + /* Drop any additional lines: */ + pszNewLine = strchr(pszVersion, '\n'); + if (pszNewLine) + *pszNewLine = '\0'; + RTStrStripR(pszVersion); + + /* Done (hope it makes some sense). */ + mStrDetectedOSVersion = pszVersion; + } + else + LogRel(("Unattended: failed to read AOSBOOT/ARCANOAE.FLG: %Rrc\n", vrc)); + } + else + LogRel(("Unattended: failed to open AOSBOOT/ARCANOAE.FLG for reading: %Rrc\n", vrc)); + } + /* + * Similarly, eCS has an ECS directory and it typically contains a + * ECS_INST.FLG file with the version info. Content differs a little: + * eComStation 2.0 EN_US Thu May 13 10:27:54 pm 2010 + * Built on ECS60441318 + * Here we drop the "eComStation" bit and leave the 2nd line as it. + * + * Note! At least 2.0 has a DISKIMGS folder with what looks like boot + * disks, so we could probably get something going here without + * needing to write an OS2 boot sector... + */ + else + { + vrc = RTVfsQueryPathInfo(hVfsIso, "ECS", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if ( RT_SUCCESS(vrc) + && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + { + mEnmOsType = VBOXOSTYPE_ECS; + + /* Read the version file: */ + vrc = RTVfsFileOpen(hVfsIso, "ECS/ECS_INST.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + size_t cbRead = 0; + vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead); + RTVfsFileRelease(hVfsFile); + pBuf->sz[cbRead] = '\0'; + if (RT_SUCCESS(vrc)) + { + /* Strip the OS name: */ + char *pszVersion = RTStrStrip(pBuf->sz); + static char s_szECS[] = "eComStation"; + if (RTStrStartsWith(pszVersion, s_szECS)) + pszVersion = RTStrStripL(pszVersion + sizeof(s_szECS) - 1); + + /* Drop any additional lines: */ + char *pszNewLine = strchr(pszVersion, '\n'); + if (pszNewLine) + *pszNewLine = '\0'; + RTStrStripR(pszVersion); + + /* Done (hope it makes some sense). */ + mStrDetectedOSVersion = pszVersion; + } + else + LogRel(("Unattended: failed to read ECS/ECS_INST.FLG: %Rrc\n", vrc)); + } + else + LogRel(("Unattended: failed to open ECS/ECS_INST.FLG for reading: %Rrc\n", vrc)); + } + else + { + /* + * Official IBM OS/2 builds doesn't have any .FLG file on them, + * so need to pry the information out in some other way. Best way + * is to read the SYSLEVEL.OS2 file, which is typically on disk #2, + * though on earlier versions (warp3) it was disk #1. + */ + vrc = RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, + "/DISK_2/SYSLEVEL.OS2"); + if (RT_SUCCESS(vrc)) + { + vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (vrc == VERR_FILE_NOT_FOUND) + { + RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, "/DISK_1/SYSLEVEL.OS2"); + vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + } + if (RT_SUCCESS(vrc)) + { + RT_ZERO(pBuf->ab); + size_t cbRead = 0; + vrc = RTVfsFileRead(hVfsFile, pBuf->ab, sizeof(pBuf->ab), &cbRead); + RTVfsFileRelease(hVfsFile); + if (RT_SUCCESS(vrc)) + { + /* Check the header. */ + OS2SYSLEVELHDR const *pHdr = (OS2SYSLEVELHDR const *)&pBuf->ab[0]; + if ( pHdr->uMinusOne == UINT16_MAX + && pHdr->uSyslevelFileVer == 1 + && memcmp(pHdr->achSignature, RT_STR_TUPLE("SYSLEVEL")) == 0 + && pHdr->offTable < cbRead + && pHdr->offTable + sizeof(OS2SYSLEVELENTRY) <= cbRead) + { + OS2SYSLEVELENTRY *pEntry = (OS2SYSLEVELENTRY *)&pBuf->ab[pHdr->offTable]; + if ( RT_SUCCESS(RTStrValidateEncodingEx(pEntry->szName, sizeof(pEntry->szName), + RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED)) + && RT_SUCCESS(RTStrValidateEncodingEx(pEntry->achCsdLevel, sizeof(pEntry->achCsdLevel), 0)) + && pEntry->bVersion != 0 + && ((pEntry->bVersion >> 4) & 0xf) < 10 + && (pEntry->bVersion & 0xf) < 10 + && pEntry->bModify < 10 + && pEntry->bRefresh < 10) + { + /* Flavor: */ + char *pszName = RTStrStrip(pEntry->szName); + if (pszName) + mStrDetectedOSFlavor = pszName; + + /* Version: */ + if (pEntry->bRefresh != 0) + mStrDetectedOSVersion.printf("%d.%d%d.%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf, + pEntry->bModify, pEntry->bRefresh); + else + mStrDetectedOSVersion.printf("%d.%d%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf, + pEntry->bModify); + pEntry->achCsdLevel[sizeof(pEntry->achCsdLevel) - 1] = '\0'; + char *pszCsd = RTStrStrip(pEntry->achCsdLevel); + if (*pszCsd != '\0') + { + mStrDetectedOSVersion.append(' '); + mStrDetectedOSVersion.append(pszCsd); + } + if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.50") >= 0) + mEnmOsType = VBOXOSTYPE_OS2Warp45; + else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.00") >= 0) + mEnmOsType = VBOXOSTYPE_OS2Warp4; + else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "3.00") >= 0) + mEnmOsType = VBOXOSTYPE_OS2Warp3; + } + else + LogRel(("Unattended: bogus SYSLEVEL.OS2 file entry: %.128Rhxd\n", pEntry)); + } + else + LogRel(("Unattended: bogus SYSLEVEL.OS2 file header: uMinusOne=%#x uSyslevelFileVer=%#x achSignature=%.8Rhxs offTable=%#x vs cbRead=%#zx\n", + pHdr->uMinusOne, pHdr->uSyslevelFileVer, pHdr->achSignature, pHdr->offTable, cbRead)); + } + else + LogRel(("Unattended: failed to read SYSLEVEL.OS2: %Rrc\n", vrc)); + } + else + LogRel(("Unattended: failed to open '%s' for reading: %Rrc\n", pBuf->sz, vrc)); + } + } + } + + /** @todo language detection? */ + + /* + * Only tested ACP2, so only return S_OK for it. + */ + if ( mEnmOsType == VBOXOSTYPE_OS2Warp45 + && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.52") >= 0 + && mStrDetectedOSFlavor.contains("Server", RTCString::CaseInsensitive)) + return S_OK; + + return S_FALSE; +} + + +/** + * Detect FreeBSD distro ISOs. + * + * @returns COM status code. + * @retval S_OK if detected + * @retval S_FALSE if not fully detected. + * + * @param hVfsIso The ISO file system. + * @param pBuf Read buffer. + */ +HRESULT Unattended::i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *pBuf) +{ + RT_NOREF(pBuf); + + /* + * FreeBSD since 10.0 has a .profile file in the root which can be used to determine that this is FreeBSD + * along with the version. + */ + + RTVFSFILE hVfsFile; + HRESULT hrc = S_FALSE; + int vrc = RTVfsFileOpen(hVfsIso, ".profile", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + static const uint8_t s_abFreeBsdHdr[] = "# $FreeBSD: releng/"; + char abRead[32]; + + vrc = RTVfsFileRead(hVfsFile, &abRead[0], sizeof(abRead), NULL /*pcbRead*/); + if ( RT_SUCCESS(vrc) + && !memcmp(&abRead[0], &s_abFreeBsdHdr[0], sizeof(s_abFreeBsdHdr) - 1)) /* Skip terminator */ + { + abRead[sizeof(abRead) - 1] = '\0'; + + /* Detect the architecture using the volume label. */ + char szVolumeId[128]; + size_t cchVolumeId; + vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, 128, &cchVolumeId); + if (RT_SUCCESS(vrc)) + { + /* Can re-use the Linux code here. */ + if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_FreeBSD)) + LogRel(("Unattended/FBSD: Unknown: arch='%s'\n", szVolumeId)); + + /* Detect the version from the string coming after the needle in .profile. */ + AssertCompile(sizeof(s_abFreeBsdHdr) - 1 < sizeof(abRead)); + + char *pszVersionStart = &abRead[sizeof(s_abFreeBsdHdr) - 1]; + char *pszVersionEnd = pszVersionStart; + + while (RT_C_IS_DIGIT(*pszVersionEnd)) + pszVersionEnd++; + if (*pszVersionEnd == '.') + { + pszVersionEnd++; /* Skip the . */ + + while (RT_C_IS_DIGIT(*pszVersionEnd)) + pszVersionEnd++; + + /* Terminate the version string. */ + *pszVersionEnd = '\0'; + + try { mStrDetectedOSVersion = pszVersionStart; } + catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; } + } + else + LogRel(("Unattended/FBSD: Unknown: version='%s'\n", &abRead[0])); + } + else + { + LogRel(("Unattended/FBSD: No Volume Label found\n")); + mEnmOsType = VBOXOSTYPE_FreeBSD; + } + + hrc = S_OK; + } + + RTVfsFileRelease(hVfsFile); + } + + return hrc; +} + + +HRESULT Unattended::prepare() +{ + LogFlow(("Unattended::prepare: enter\n")); + + /* + * Must have a machine. + */ + ComPtr<Machine> ptrMachine; + Guid MachineUuid; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrMachine = mMachine; + if (ptrMachine.isNull()) + return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("No machine associated with this IUnatteded instance")); + MachineUuid = mMachineUuid; + } + + /* + * Before we write lock ourselves, we must get stuff from Machine and + * VirtualBox because their locks have higher priorities than ours. + */ + Utf8Str strGuestOsTypeId; + Utf8Str strMachineName; + Utf8Str strDefaultAuxBasePath; + HRESULT hrc; + try + { + Bstr bstrTmp; + hrc = ptrMachine->COMGETTER(OSTypeId)(bstrTmp.asOutParam()); + if (SUCCEEDED(hrc)) + { + strGuestOsTypeId = bstrTmp; + hrc = ptrMachine->COMGETTER(Name)(bstrTmp.asOutParam()); + if (SUCCEEDED(hrc)) + strMachineName = bstrTmp; + } + int vrc = ptrMachine->i_calculateFullPath(Utf8StrFmt("Unattended-%RTuuid-", MachineUuid.raw()), strDefaultAuxBasePath); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } + bool const fIs64Bit = i_isGuestOSArchX64(strGuestOsTypeId); + + BOOL fRtcUseUtc = FALSE; + hrc = ptrMachine->COMGETTER(RTCUseUTC)(&fRtcUseUtc); + if (FAILED(hrc)) + return hrc; + + FirmwareType_T enmFirmware = FirmwareType_BIOS; + hrc = ptrMachine->COMGETTER(FirmwareType)(&enmFirmware); + if (FAILED(hrc)) + return hrc; + + /* + * Write lock this object and set attributes we got from IMachine. + */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mStrGuestOsTypeId = strGuestOsTypeId; + mfGuestOs64Bit = fIs64Bit; + mfRtcUseUtc = RT_BOOL(fRtcUseUtc); + menmFirmwareType = enmFirmware; + + /* + * Do some state checks. + */ + if (mpInstaller != NULL) + return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The prepare method has been called (must call done to restart)")); + if ((Machine *)ptrMachine != (Machine *)mMachine) + return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The 'machine' while we were using it - please don't do that")); + + /* + * Check if the specified ISOs and files exist. + */ + if (!RTFileExists(mStrIsoPath.c_str())) + return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the installation ISO file '%s'"), + mStrIsoPath.c_str()); + if (mfInstallGuestAdditions && !RTFileExists(mStrAdditionsIsoPath.c_str())) + return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the Guest Additions ISO file '%s'"), + mStrAdditionsIsoPath.c_str()); + if (mfInstallTestExecService && !RTFileExists(mStrValidationKitIsoPath.c_str())) + return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the validation kit ISO file '%s'"), + mStrValidationKitIsoPath.c_str()); + if (mStrScriptTemplatePath.isNotEmpty() && !RTFileExists(mStrScriptTemplatePath.c_str())) + return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate unattended installation script template '%s'"), + mStrScriptTemplatePath.c_str()); + + /* + * Do media detection if it haven't been done yet. + */ + if (!mfDoneDetectIsoOS) + { + hrc = detectIsoOS(); + if (FAILED(hrc) && hrc != E_NOTIMPL) + return hrc; + } + + /* + * We can now check midxImage against mDetectedImages, since the latter is + * populated during the detectIsoOS call. We ignore midxImage if no images + * were detected, assuming that it's not relevant or used for different purposes. + */ + if (mDetectedImages.size() > 0) + { + bool fImageFound = false; + for (size_t i = 0; i < mDetectedImages.size(); ++i) + if (midxImage == mDetectedImages[i].mImageIndex) + { + i_updateDetectedAttributeForImage(mDetectedImages[i]); + fImageFound = true; + break; + } + if (!fImageFound) + return setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("imageIndex value %u not found in detectedImageIndices"), midxImage); + } + + /* + * Get the ISO's detect guest OS type info and make it's a known one (just + * in case the above step doesn't work right). + */ + uint32_t const idxIsoOSType = Global::getOSTypeIndexFromId(mStrDetectedOSTypeId.c_str()); + VBOXOSTYPE const enmIsoOSType = idxIsoOSType < Global::cOSTypes ? Global::sOSTypes[idxIsoOSType].osType : VBOXOSTYPE_Unknown; + if ((enmIsoOSType & VBOXOSTYPE_OsTypeMask) == VBOXOSTYPE_Unknown) + return setError(E_FAIL, tr("The supplied ISO file does not contain an OS currently supported for unattended installation")); + + /* + * Get the VM's configured guest OS type info. + */ + uint32_t const idxMachineOSType = Global::getOSTypeIndexFromId(mStrGuestOsTypeId.c_str()); + VBOXOSTYPE const enmMachineOSType = idxMachineOSType < Global::cOSTypes + ? Global::sOSTypes[idxMachineOSType].osType : VBOXOSTYPE_Unknown; + + /* + * Check that the detected guest OS type for the ISO is compatible with + * that of the VM, boardly speaking. + */ + if (idxMachineOSType != idxIsoOSType) + { + /* Check that the architecture is compatible: */ + if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) + && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86 + || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64)) + return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch")); + + /** @todo check BIOS/EFI requirement */ + } + + /* + * Do some default property stuff and check other properties. + */ + try + { + char szTmp[128]; + + if (mStrLocale.isEmpty()) + { + int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp)); + if ( RT_SUCCESS(vrc) + && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp)) + mStrLocale.assign(szTmp, 5); + else + mStrLocale = "en_US"; + Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale)); + } + + if (mStrLanguage.isEmpty()) + { + if (mDetectedOSLanguages.size() > 0) + mStrLanguage = mDetectedOSLanguages[0]; + else + mStrLanguage.assign(mStrLocale).findReplace('_', '-'); + } + + if (mStrCountry.isEmpty()) + { + int vrc = RTLocaleQueryUserCountryCode(szTmp); + if (RT_SUCCESS(vrc)) + mStrCountry = szTmp; + else if ( mStrLocale.isNotEmpty() + && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale)) + mStrCountry.assign(mStrLocale, 3, 2); + else + mStrCountry = "US"; + } + + if (mStrTimeZone.isEmpty()) + { + int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp)); + if ( RT_SUCCESS(vrc) + && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */) + mStrTimeZone = szTmp; + else + mStrTimeZone = "Etc/UTC"; + Assert(mStrTimeZone.isNotEmpty()); + } + mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str()); + if (!mpTimeZoneInfo) + mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str()); + Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC"); + if (!mpTimeZoneInfo) + LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str())); + + if (mStrHostname.isEmpty()) + { + /* Mangle the VM name into a valid hostname. */ + for (size_t i = 0; i < strMachineName.length(); i++) + { + char ch = strMachineName[i]; + if ( (unsigned)ch < 127 + && RT_C_IS_ALNUM(ch)) + mStrHostname.append(ch); + else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-")) + mStrHostname.append('-'); + } + if (mStrHostname.length() == 0) + mStrHostname.printf("%RTuuid-vm", MachineUuid.raw()); + else if (mStrHostname.length() < 3) + mStrHostname.append("-vm"); + mStrHostname.append(".myguest.virtualbox.org"); + } + + if (mStrAuxiliaryBasePath.isEmpty()) + { + mStrAuxiliaryBasePath = strDefaultAuxBasePath; + mfIsDefaultAuxiliaryBasePath = true; + } + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } + + /* + * Instatiate the guest installer matching the ISO. + */ + mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion, + mStrDetectedOSFlavor, mStrDetectedOSHints, this); + if (mpInstaller != NULL) + { + hrc = mpInstaller->initInstaller(); + if (SUCCEEDED(hrc)) + { + /* + * Do the script preps (just reads them). + */ + hrc = mpInstaller->prepareUnattendedScripts(); + if (SUCCEEDED(hrc)) + { + LogFlow(("Unattended::prepare: returns S_OK\n")); + return S_OK; + } + } + + /* Destroy the installer instance. */ + delete mpInstaller; + mpInstaller = NULL; + } + else + hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND, + tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str()); + LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc)); + return hrc; +} + +HRESULT Unattended::constructMedia() +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlow(("===========================================================\n")); + LogFlow(("Call Unattended::constructMedia()\n")); + + if (mpInstaller == NULL) + return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called"); + + return mpInstaller->prepareMedia(); +} + +HRESULT Unattended::reconfigureVM() +{ + LogFlow(("===========================================================\n")); + LogFlow(("Call Unattended::reconfigureVM()\n")); + + /* + * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues. + */ + StorageBus_T enmRecommendedStorageBus = StorageBus_IDE; + { + Bstr bstrGuestOsTypeId; + Bstr bstrDetectedOSTypeId; + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (mpInstaller == NULL) + return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called")); + bstrGuestOsTypeId = mStrGuestOsTypeId; + bstrDetectedOSTypeId = mStrDetectedOSTypeId; + } + ComPtr<IGuestOSType> ptrGuestOSType; + HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam()); + if (SUCCEEDED(hrc)) + { + if (!ptrGuestOSType.isNull()) + hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus); + } + if (FAILED(hrc)) + return hrc; + + /* If the detected guest OS type differs, log a warning if their DVD storage + bus recommendations differ. */ + if (bstrGuestOsTypeId != bstrDetectedOSTypeId) + { + StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE; + hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam()); + if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull()) + hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2); + if (FAILED(hrc)) + return hrc; + + if (enmRecommendedStorageBus != enmRecommendedStorageBus2) + LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n", + ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(), + ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() )); + } + } + + /* + * Take write lock (for lock order reasons, write lock our parent object too) + * then make sure we're the only caller of this method. + */ + AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS); + HRESULT hrc; + if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD) + { + RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf(); + mhThreadReconfigureVM = hNativeSelf; + + /* + * Create a new session, lock the machine and get the session machine object. + * Do the locking without pinning down the write locks, just to be on the safe side. + */ + ComPtr<ISession> ptrSession; + try + { + hrc = ptrSession.createInprocObject(CLSID_Session); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (SUCCEEDED(hrc)) + { + alock.release(); + hrc = mMachine->LockMachine(ptrSession, LockType_Shared); + alock.acquire(); + if (SUCCEEDED(hrc)) + { + ComPtr<IMachine> ptrSessionMachine; + hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam()); + if (SUCCEEDED(hrc)) + { + /* + * Hand the session to the inner work and let it do it job. + */ + try + { + hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine); + } + catch (...) + { + hrc = E_UNEXPECTED; + } + } + + /* Paranoia: release early in case we it a bump below. */ + Assert(mhThreadReconfigureVM == hNativeSelf); + mhThreadReconfigureVM = NIL_RTNATIVETHREAD; + + /* + * While unlocking the machine we'll have to drop the locks again. + */ + alock.release(); + + ptrSessionMachine.setNull(); + HRESULT hrc2 = ptrSession->UnlockMachine(); + AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2)); + + ptrSession.setNull(); + + alock.acquire(); + } + else + mhThreadReconfigureVM = NIL_RTNATIVETHREAD; + } + else + mhThreadReconfigureVM = NIL_RTNATIVETHREAD; + } + else + hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread")); + return hrc; +} + + +HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus, + ComPtr<IMachine> const &rPtrSessionMachine) +{ + if (mpInstaller == NULL) + return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called")); + + // Fetch all available storage controllers + com::SafeIfaceArray<IStorageController> arrayOfControllers; + HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers)); + AssertComRCReturn(hrc, hrc); + + /* + * Figure out where the images are to be mounted, adding controllers/ports as needed. + */ + std::vector<UnattendedInstallationDisk> vecInstallationDisks; + if (mpInstaller->isAuxiliaryFloppyNeeded()) + { + hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock); + if (FAILED(hrc)) + return hrc; + } + + hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus); + if (FAILED(hrc)) + return hrc; + + /* + * Mount the images. + */ + for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++) + { + UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage); + Assert(pImage->strImagePath.isNotEmpty()); + hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock); + if (FAILED(hrc)) + return hrc; + } + + /* + * Set the boot order. + * + * ASSUME that the HD isn't bootable when we start out, but it will be what + * we boot from after the first stage of the installation is done. Setting + * it first prevents endless reboot cylces. + */ + /** @todo consider making 100% sure the disk isn't bootable (edit partition + * table active bits and EFI stuff). */ + Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD + || mpInstaller->getBootableDeviceType() == DeviceType_Floppy); + hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk); + if (SUCCEEDED(hrc)) + hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType()); + if (SUCCEEDED(hrc)) + hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD + ? DeviceType_Floppy : DeviceType_DVD); + if (FAILED(hrc)) + return hrc; + + /* + * Essential step. + * + * HACK ALERT! We have to release the lock here or we'll get into trouble with + * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType). + */ + if (SUCCEEDED(hrc)) + { + rAutoLock.release(); + hrc = rPtrSessionMachine->SaveSettings(); + rAutoLock.acquire(); + } + + return hrc; +} + +/** + * Makes sure we've got a floppy drive attached to a floppy controller, adding + * the auxiliary floppy image to the installation disk vector. + * + * @returns COM status code. + * @param rControllers The existing controllers. + * @param rVecInstallatationDisks The list of image to mount. + * @param rPtrSessionMachine The session machine smart pointer. + * @param rAutoLock The lock. + */ +HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers, + std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks, + ComPtr<IMachine> const &rPtrSessionMachine, + AutoMultiWriteLock2 &rAutoLock) +{ + Assert(mpInstaller->isAuxiliaryFloppyNeeded()); + + /* + * Look for a floppy controller with a primary drive (A:) we can "insert" + * the auxiliary floppy image. Add a controller and/or a drive if necessary. + */ + bool fFoundPort0Dev0 = false; + Bstr bstrControllerName; + Utf8Str strControllerName; + + for (size_t i = 0; i < rControllers.size(); ++i) + { + StorageBus_T enmStorageBus; + HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus); + AssertComRCReturn(hrc, hrc); + if (enmStorageBus == StorageBus_Floppy) + { + + /* + * Found a floppy controller. + */ + hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam()); + AssertComRCReturn(hrc, hrc); + + /* + * Check the attchments to see if we've got a device 0 attached on port 0. + * + * While we're at it we eject flppies from all floppy drives we encounter, + * we don't want any confusion at boot or during installation. + */ + com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments; + hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(), + ComSafeArrayAsOutParam(arrayOfMediumAttachments)); + AssertComRCReturn(hrc, hrc); + strControllerName = bstrControllerName; + AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2)); + + for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++) + { + LONG iPort = -1; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort); + AssertComRCReturn(hrc, hrc); + + LONG iDevice = -1; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice); + AssertComRCReturn(hrc, hrc); + + DeviceType_T enmType; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType); + AssertComRCReturn(hrc, hrc); + + if (enmType == DeviceType_Floppy) + { + ComPtr<IMedium> ptrMedium; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam()); + AssertComRCReturn(hrc, hrc); + + if (ptrMedium.isNotNull()) + { + ptrMedium.setNull(); + rAutoLock.release(); + hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/); + rAutoLock.acquire(); + } + + if (iPort == 0 && iDevice == 0) + fFoundPort0Dev0 = true; + } + else if (iPort == 0 && iDevice == 0) + return setError(E_FAIL, + tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"), + bstrControllerName.raw()); + } + } + } + + /* + * Add a floppy controller if we need to. + */ + if (strControllerName.isEmpty()) + { + bstrControllerName = strControllerName = "Floppy"; + ComPtr<IStorageController> ptrControllerIgnored; + HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy, + ptrControllerIgnored.asOutParam()); + LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc)); + if (FAILED(hrc)) + return hrc; + } + + /* + * Adding a floppy drive (if needed) and mounting the auxiliary image is + * done later together with the ISOs. + */ + rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName, + DeviceType_Floppy, AccessMode_ReadWrite, + 0, 0, + fFoundPort0Dev0 /*fMountOnly*/, + mpInstaller->getAuxiliaryFloppyFilePath(), false)); + return S_OK; +} + +/** + * Reconfigures DVD drives of the VM to mount all the ISOs we need. + * + * This will umount all DVD media. + * + * @returns COM status code. + * @param rControllers The existing controllers. + * @param rVecInstallatationDisks The list of image to mount. + * @param rPtrSessionMachine The session machine smart pointer. + * @param rAutoLock The lock. + * @param enmRecommendedStorageBus The recommended storage bus type for adding + * DVD drives on. + */ +HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers, + std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks, + ComPtr<IMachine> const &rPtrSessionMachine, + AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus) +{ + /* + * Enumerate the attachements of every controller, looking for DVD drives, + * ASSUMEING all drives are bootable. + * + * Eject the medium from all the drives (don't want any confusion) and look + * for the recommended storage bus in case we need to add more drives. + */ + HRESULT hrc; + std::list<ControllerSlot> lstControllerDvdSlots; + Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */ + Utf8Str strControllerName; + Bstr bstrControllerName; + for (size_t i = 0; i < rControllers.size(); ++i) + { + hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam()); + AssertComRCReturn(hrc, hrc); + strControllerName = bstrControllerName; + + /* Look for recommended storage bus. */ + StorageBus_T enmStorageBus; + hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus); + AssertComRCReturn(hrc, hrc); + if (enmStorageBus == enmRecommendedStorageBus) + { + strRecommendedControllerName = bstrControllerName; + AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2)); + } + + /* Scan the controller attachments. */ + com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments; + hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(), + ComSafeArrayAsOutParam(arrayOfMediumAttachments)); + AssertComRCReturn(hrc, hrc); + + for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++) + { + DeviceType_T enmType; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType); + AssertComRCReturn(hrc, hrc); + if (enmType == DeviceType_DVD) + { + LONG iPort = -1; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort); + AssertComRCReturn(hrc, hrc); + + LONG iDevice = -1; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice); + AssertComRCReturn(hrc, hrc); + + /* Remeber it. */ + lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/)); + + /* Eject the medium, if any. */ + ComPtr<IMedium> ptrMedium; + hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam()); + AssertComRCReturn(hrc, hrc); + if (ptrMedium.isNotNull()) + { + ptrMedium.setNull(); + + rAutoLock.release(); + hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/); + rAutoLock.acquire(); + } + } + } + } + + /* + * How many drives do we need? Add more if necessary. + */ + ULONG cDvdDrivesNeeded = 0; + if (mpInstaller->isAuxiliaryIsoNeeded()) + cDvdDrivesNeeded++; + if (mpInstaller->isOriginalIsoNeeded()) + cDvdDrivesNeeded++; +#if 0 /* These are now in the AUX VISO. */ + if (mpInstaller->isAdditionsIsoNeeded()) + cDvdDrivesNeeded++; + if (mpInstaller->isValidationKitIsoNeeded()) + cDvdDrivesNeeded++; +#endif + Assert(cDvdDrivesNeeded > 0); + if (cDvdDrivesNeeded > lstControllerDvdSlots.size()) + { + /* Do we need to add the recommended controller? */ + if (strRecommendedControllerName.isEmpty()) + { + switch (enmRecommendedStorageBus) + { + case StorageBus_IDE: strRecommendedControllerName = "IDE"; break; + case StorageBus_SATA: strRecommendedControllerName = "SATA"; break; + case StorageBus_SCSI: strRecommendedControllerName = "SCSI"; break; + case StorageBus_SAS: strRecommendedControllerName = "SAS"; break; + case StorageBus_USB: strRecommendedControllerName = "USB"; break; + case StorageBus_PCIe: strRecommendedControllerName = "PCIe"; break; + default: + return setError(E_FAIL, tr("Support for recommended storage bus %d not implemented"), + (int)enmRecommendedStorageBus); + } + ComPtr<IStorageController> ptrControllerIgnored; + hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus, + ptrControllerIgnored.asOutParam()); + LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc)); + if (FAILED(hrc)) + return hrc; + } + + /* Add free controller slots, maybe raising the port limit on the controller if we can. */ + hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine, + cDvdDrivesNeeded, lstControllerDvdSlots); + if (FAILED(hrc)) + return hrc; + if (cDvdDrivesNeeded > lstControllerDvdSlots.size()) + { + /* We could in many cases create another controller here, but it's not worth the effort. */ + return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "", + cDvdDrivesNeeded - lstControllerDvdSlots.size()), + strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size()); + } + Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size()); + } + + /* + * Sort the DVD slots in boot order. + */ + lstControllerDvdSlots.sort(); + + /* + * Prepare ISO mounts. + * + * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots + * according to the boot order. + */ + std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin(); + if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso()) + { + rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true)); + ++itDvdSlot; + } + + if (mpInstaller->isOriginalIsoNeeded()) + { + rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath(), false)); + ++itDvdSlot; + } + + if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso()) + { + rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true)); + ++itDvdSlot; + } + +#if 0 /* These are now in the AUX VISO. */ + if (mpInstaller->isAdditionsIsoNeeded()) + { + rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath(), false)); + ++itDvdSlot; + } + + if (mpInstaller->isValidationKitIsoNeeded()) + { + rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath(), false)); + ++itDvdSlot; + } +#endif + + return S_OK; +} + +/** + * Used to find more free slots for DVD drives during VM reconfiguration. + * + * This may modify the @a portCount property of the given controller. + * + * @returns COM status code. + * @param rStrControllerName The name of the controller to find/create + * free slots on. + * @param enmStorageBus The storage bus type. + * @param rPtrSessionMachine Reference to the session machine. + * @param cSlotsNeeded Total slots needed (including those we've + * already found). + * @param rDvdSlots The slot collection for DVD drives to add + * free slots to as we find/create them. + */ +HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus, + ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded, + std::list<ControllerSlot> &rDvdSlots) +{ + Assert(cSlotsNeeded > rDvdSlots.size()); + + /* + * Get controlleer stats. + */ + ComPtr<IStorageController> pController; + HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam()); + AssertComRCReturn(hrc, hrc); + + ULONG cMaxDevicesPerPort = 1; + hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort); + AssertComRCReturn(hrc, hrc); + AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED); + + ULONG cPorts = 0; + hrc = pController->COMGETTER(PortCount)(&cPorts); + AssertComRCReturn(hrc, hrc); + + /* + * Get the attachment list and turn into an internal list for lookup speed. + */ + com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments; + hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(), + ComSafeArrayAsOutParam(arrayOfMediumAttachments)); + AssertComRCReturn(hrc, hrc); + + std::vector<ControllerSlot> arrayOfUsedSlots; + for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++) + { + LONG iPort = -1; + hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort); + AssertComRCReturn(hrc, hrc); + + LONG iDevice = -1; + hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice); + AssertComRCReturn(hrc, hrc); + + arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/)); + } + + /* + * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots. + */ + for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++) + for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++) + { + bool fFound = false; + for (size_t i = 0; i < arrayOfUsedSlots.size(); i++) + if ( arrayOfUsedSlots[i].iPort == iPort + && arrayOfUsedSlots[i].iDevice == iDevice) + { + fFound = true; + break; + } + if (!fFound) + { + rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/)); + if (rDvdSlots.size() >= cSlotsNeeded) + return S_OK; + } + } + + /* + * Okay we still need more ports. See if increasing the number of controller + * ports would solve it. + */ + ULONG cMaxPorts = 1; + hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts); + AssertComRCReturn(hrc, hrc); + if (cMaxPorts <= cPorts) + return S_OK; + size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort; + if (cPorts + cNewPortsNeeded > cMaxPorts) + return S_OK; + + /* + * Raise the port count and add the free slots we've just created. + */ + hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded); + AssertComRCReturn(hrc, hrc); + int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded); + for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++) + for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++) + { + rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/)); + if (rDvdSlots.size() >= cSlotsNeeded) + return S_OK; + } + + /* We should not get here! */ + AssertLogRelFailedReturn(E_UNEXPECTED); +} + +HRESULT Unattended::done() +{ + LogFlow(("Unattended::done\n")); + if (mpInstaller) + { + LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller)); + delete mpInstaller; + mpInstaller = NULL; + } + return S_OK; +} + +HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + isoPath = mStrIsoPath; + return S_OK; +} + +HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrIsoPath = isoPath; + mfDoneDetectIsoOS = false; + return S_OK; +} + +HRESULT Unattended::getUser(com::Utf8Str &user) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + user = mStrUser; + return S_OK; +} + + +HRESULT Unattended::setUser(const com::Utf8Str &user) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrUser = user; + return S_OK; +} + +HRESULT Unattended::getPassword(com::Utf8Str &password) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + password = mStrPassword; + return S_OK; +} + +HRESULT Unattended::setPassword(const com::Utf8Str &password) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrPassword = password; + return S_OK; +} + +HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fullUserName = mStrFullUserName; + return S_OK; +} + +HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrFullUserName = fullUserName; + return S_OK; +} + +HRESULT Unattended::getProductKey(com::Utf8Str &productKey) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + productKey = mStrProductKey; + return S_OK; +} + +HRESULT Unattended::setProductKey(const com::Utf8Str &productKey) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrProductKey = productKey; + return S_OK; +} + +HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + additionsIsoPath = mStrAdditionsIsoPath; + return S_OK; +} + +HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrAdditionsIsoPath = additionsIsoPath; + return S_OK; +} + +HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *installGuestAdditions = mfInstallGuestAdditions; + return S_OK; +} + +HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mfInstallGuestAdditions = installGuestAdditions != FALSE; + return S_OK; +} + +HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aValidationKitIsoPath = mStrValidationKitIsoPath; + return S_OK; +} + +HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrValidationKitIsoPath = aValidationKitIsoPath; + return S_OK; +} + +HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aInstallTestExecService = mfInstallTestExecService; + return S_OK; +} + +HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mfInstallTestExecService = aInstallTestExecService != FALSE; + return S_OK; +} + +HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aTimeZone = mStrTimeZone; + return S_OK; +} + +HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrTimeZone = aTimezone; + return S_OK; +} + +HRESULT Unattended::getLocale(com::Utf8Str &aLocale) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aLocale = mStrLocale; + return S_OK; +} + +HRESULT Unattended::setLocale(const com::Utf8Str &aLocale) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + if ( aLocale.isEmpty() /* use default */ + || ( aLocale.length() == 5 + && RT_C_IS_LOWER(aLocale[0]) + && RT_C_IS_LOWER(aLocale[1]) + && aLocale[2] == '_' + && RT_C_IS_UPPER(aLocale[3]) + && RT_C_IS_UPPER(aLocale[4])) ) + { + mStrLocale = aLocale; + return S_OK; + } + return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters")); +} + +HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aLanguage = mStrLanguage; + return S_OK; +} + +HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrLanguage = aLanguage; + return S_OK; +} + +HRESULT Unattended::getCountry(com::Utf8Str &aCountry) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aCountry = mStrCountry; + return S_OK; +} + +HRESULT Unattended::setCountry(const com::Utf8Str &aCountry) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + if ( aCountry.isEmpty() + || ( aCountry.length() == 2 + && RT_C_IS_UPPER(aCountry[0]) + && RT_C_IS_UPPER(aCountry[1])) ) + { + mStrCountry = aCountry; + return S_OK; + } + return setError(E_INVALIDARG, tr("Expected two upper cased letters")); +} + +HRESULT Unattended::getProxy(com::Utf8Str &aProxy) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aProxy = mStrProxy; /// @todo turn schema map into string or something. + return S_OK; +} + +HRESULT Unattended::setProxy(const com::Utf8Str &aProxy) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + if (aProxy.isEmpty()) + { + /* set default proxy */ + /** @todo BUGBUG! implement this */ + } + else if (aProxy.equalsIgnoreCase("none")) + { + /* clear proxy config */ + mStrProxy.setNull(); + } + else + { + /** @todo Parse and set proxy config into a schema map or something along those lines. */ + /** @todo BUGBUG! implement this */ + // return E_NOTIMPL; + mStrProxy = aProxy; + } + return S_OK; +} + +HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";"); + return S_OK; +} + +HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + if (aPackageSelectionAdjustments.isEmpty()) + mPackageSelectionAdjustments.clear(); + else + { + RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";"); + for (size_t i = 0; i < arrayStrSplit.size(); i++) + { + if (arrayStrSplit[i].equals("minimal")) + { /* okay */ } + else + return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str()); + } + mPackageSelectionAdjustments = arrayStrSplit; + } + return S_OK; +} + +HRESULT Unattended::getHostname(com::Utf8Str &aHostname) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aHostname = mStrHostname; + return S_OK; +} + +HRESULT Unattended::setHostname(const com::Utf8Str &aHostname) +{ + /* + * Validate input. + */ + if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U)) + return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME, + tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()), + aHostname.c_str(), aHostname.length()); + size_t cLabels = 0; + const char *pszSrc = aHostname.c_str(); + for (;;) + { + size_t cchLabel = 1; + char ch = *pszSrc++; + if (RT_C_IS_ALNUM(ch)) + { + cLabels++; + while ((ch = *pszSrc++) != '.' && ch != '\0') + { + if (RT_C_IS_ALNUM(ch) || ch == '-') + { + if (cchLabel < 63) + cchLabel++; + else + return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME, + tr("Invalid hostname '%s' - label %u is too long, max is 63."), + aHostname.c_str(), cLabels); + } + else + return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME, + tr("Invalid hostname '%s' - illegal char '%c' at position %zu"), + aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1); + } + if (cLabels == 1 && cchLabel < 2) + return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME, + tr("Invalid hostname '%s' - the name part must be at least two characters long"), + aHostname.c_str()); + if (ch == '\0') + break; + } + else if (ch != '\0') + return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME, + tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"), + aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1); + else + return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME, + tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str()); + } + if (cLabels < 2) + return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME, + tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str()); + + /* + * Make the change. + */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrHostname = aHostname; + return S_OK; +} + +HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aAuxiliaryBasePath = mStrAuxiliaryBasePath; + return S_OK; +} + +HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath) +{ + if (aAuxiliaryBasePath.isEmpty()) + return setError(E_INVALIDARG, tr("Empty base path is not allowed")); + if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str())) + return setError(E_INVALIDARG, tr("Base path must be absolute")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrAuxiliaryBasePath = aAuxiliaryBasePath; + mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty(); + return S_OK; +} + +HRESULT Unattended::getImageIndex(ULONG *index) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *index = midxImage; + return S_OK; +} + +HRESULT Unattended::setImageIndex(ULONG index) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + + /* Validate the selection if detection was done already: */ + if (mDetectedImages.size() > 0) + { + for (size_t i = 0; i < mDetectedImages.size(); i++) + if (mDetectedImages[i].mImageIndex == index) + { + midxImage = index; + i_updateDetectedAttributeForImage(mDetectedImages[i]); + return S_OK; + } + LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */ + } + + midxImage = index; + return S_OK; +} + +HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + return mMachine.queryInterfaceTo(aMachine.asOutParam()); +} + +HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine) +{ + /* + * Lookup the VM so we can safely get the Machine instance. + * (Don't want to test how reliable XPCOM and COM are with finding + * the local object instance when a client passes a stub back.) + */ + Bstr bstrUuidMachine; + HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam()); + if (SUCCEEDED(hrc)) + { + Guid UuidMachine(bstrUuidMachine); + ComObjPtr<Machine> ptrMachine; + hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine); + if (SUCCEEDED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, + tr("Cannot change after prepare() has been called"))); + mMachine = ptrMachine; + mMachineUuid = UuidMachine; + if (mfIsDefaultAuxiliaryBasePath) + mStrAuxiliaryBasePath.setNull(); + hrc = S_OK; + } + } + return hrc; +} + +HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if ( mStrScriptTemplatePath.isNotEmpty() + || mpInstaller == NULL) + aScriptTemplatePath = mStrScriptTemplatePath; + else + aScriptTemplatePath = mpInstaller->getTemplateFilePath(); + return S_OK; +} + +HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrScriptTemplatePath = aScriptTemplatePath; + return S_OK; +} + +HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if ( mStrPostInstallScriptTemplatePath.isNotEmpty() + || mpInstaller == NULL) + aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath; + else + aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath(); + return S_OK; +} + +HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath; + return S_OK; +} + +HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aPostInstallCommand = mStrPostInstallCommand; + return S_OK; +} + +HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrPostInstallCommand = aPostInstallCommand; + return S_OK; +} + +HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if ( mStrExtraInstallKernelParameters.isNotEmpty() + || mpInstaller == NULL) + aExtraInstallKernelParameters = mStrExtraInstallKernelParameters; + else + aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters(); + return S_OK; +} + +HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mStrExtraInstallKernelParameters = aExtraInstallKernelParameters; + return S_OK; +} + +HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aDetectedOSTypeId = mStrDetectedOSTypeId; + return S_OK; +} + +HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aDetectedOSVersion = mStrDetectedOSVersion; + return S_OK; +} + +HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aDetectedOSFlavor = mStrDetectedOSFlavor; + return S_OK; +} + +HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " "); + return S_OK; +} + +HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aDetectedOSHints = mStrDetectedOSHints; + return S_OK; +} + +HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aDetectedImageNames.clear(); + for (size_t i = 0; i < mDetectedImages.size(); ++i) + { + Utf8Str strTmp; + aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp)); + } + return S_OK; +} + +HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aDetectedImageIndices.clear(); + for (size_t i = 0; i < mDetectedImages.size(); ++i) + aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex); + return S_OK; +} + +HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported) +{ + /* + * Take the initial position that it's not supported, so we can return + * right away when we decide it's not possible. + */ + *aIsUnattendedInstallSupported = false; + + /* Unattended is disabled by default if we could not detect OS type. */ + if (mStrDetectedOSTypeId.isEmpty()) + return S_OK; + + const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask); + + /* We require a version to have been detected, except for windows where the + field is generally only used for the service pack number at present and + will be empty for RTMs isos. */ + if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT + || enmOsTypeMasked >= VBOXOSTYPE_OS2) + && mStrDetectedOSVersion.isEmpty()) + return S_OK; + + /* + * Sort out things that we know doesn't work. Order by VBOXOSTYPE value. + */ + + /* We do not support any of the DOS based windows version, nor DOS, in case + any of that gets detected (it shouldn't): */ + if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT) + return S_OK; + + /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */ + if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4) + return S_OK; + + /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been + tested, but we'll get to the others eventually): */ + if ( enmOsTypeMasked >= VBOXOSTYPE_OS2 + && enmOsTypeMasked < VBOXOSTYPE_Linux + && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ ) + return S_OK; + + /* Old Debians fail since package repos have been move to some other mirror location. */ + if ( enmOsTypeMasked == VBOXOSTYPE_Debian + && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0) + return S_OK; + + /* Skip all OpenSUSE variants for now. */ + if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE) + return S_OK; + + if (enmOsTypeMasked == VBOXOSTYPE_Ubuntu) + { + /* We cannot install Ubuntus older than 11.04. */ + if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0) + return S_OK; + /* Lubuntu, starting with 20.04, has switched to calamares, which cannot be automated. */ + if ( RTStrIStr(mStrDetectedOSFlavor.c_str(), "lubuntu") + && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "20.04") > 0) + return S_OK; + } + + /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */ + if ( enmOsTypeMasked == VBOXOSTYPE_Oracle + && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0) + return S_OK; + + /* Fredora ISOs cannot be installed at present. */ + if (enmOsTypeMasked == VBOXOSTYPE_FedoraCore) + return S_OK; + + /* + * Assume the rest works. + */ + *aIsUnattendedInstallSupported = true; + return S_OK; +} + +HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork) +{ + *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork; + return S_OK; +} + +HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called"))); + mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork); + return S_OK; +} + +/* + * Getters that the installer and script classes can use. + */ +Utf8Str const &Unattended::i_getIsoPath() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrIsoPath; +} + +Utf8Str const &Unattended::i_getUser() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrUser; +} + +Utf8Str const &Unattended::i_getPassword() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrPassword; +} + +Utf8Str const &Unattended::i_getFullUserName() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser; +} + +Utf8Str const &Unattended::i_getProductKey() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrProductKey; +} + +Utf8Str const &Unattended::i_getProxy() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrProxy; +} + +Utf8Str const &Unattended::i_getAdditionsIsoPath() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrAdditionsIsoPath; +} + +bool Unattended::i_getInstallGuestAdditions() const +{ + Assert(isReadLockedOnCurrentThread()); + return mfInstallGuestAdditions; +} + +Utf8Str const &Unattended::i_getValidationKitIsoPath() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrValidationKitIsoPath; +} + +bool Unattended::i_getInstallTestExecService() const +{ + Assert(isReadLockedOnCurrentThread()); + return mfInstallTestExecService; +} + +Utf8Str const &Unattended::i_getTimeZone() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrTimeZone; +} + +PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const +{ + Assert(isReadLockedOnCurrentThread()); + return mpTimeZoneInfo; +} + +Utf8Str const &Unattended::i_getLocale() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrLocale; +} + +Utf8Str const &Unattended::i_getLanguage() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrLanguage; +} + +Utf8Str const &Unattended::i_getCountry() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrCountry; +} + +bool Unattended::i_isMinimalInstallation() const +{ + size_t i = mPackageSelectionAdjustments.size(); + while (i-- > 0) + if (mPackageSelectionAdjustments[i].equals("minimal")) + return true; + return false; +} + +Utf8Str const &Unattended::i_getHostname() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrHostname; +} + +Utf8Str const &Unattended::i_getAuxiliaryBasePath() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrAuxiliaryBasePath; +} + +ULONG Unattended::i_getImageIndex() const +{ + Assert(isReadLockedOnCurrentThread()); + return midxImage; +} + +Utf8Str const &Unattended::i_getScriptTemplatePath() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrScriptTemplatePath; +} + +Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrPostInstallScriptTemplatePath; +} + +Utf8Str const &Unattended::i_getPostInstallCommand() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrPostInstallCommand; +} + +Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const +{ + Assert(isReadLockedOnCurrentThread()); + /* Only the installer knows, forward the call. */ + AssertReturn(mpInstaller != NULL, Utf8Str::Empty); + return mpInstaller->getAuxiliaryInstallDir(); +} + +Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const +{ + Assert(isReadLockedOnCurrentThread()); + return mStrExtraInstallKernelParameters; +} + +bool Unattended::i_isRtcUsingUtc() const +{ + Assert(isReadLockedOnCurrentThread()); + return mfRtcUseUtc; +} + +bool Unattended::i_isGuestOs64Bit() const +{ + Assert(isReadLockedOnCurrentThread()); + return mfGuestOs64Bit; +} + +bool Unattended::i_isFirmwareEFI() const +{ + Assert(isReadLockedOnCurrentThread()); + return menmFirmwareType != FirmwareType_BIOS; +} + +Utf8Str const &Unattended::i_getDetectedOSVersion() +{ + Assert(isReadLockedOnCurrentThread()); + return mStrDetectedOSVersion; +} + +bool Unattended::i_getAvoidUpdatesOverNetwork() const +{ + Assert(isReadLockedOnCurrentThread()); + return mfAvoidUpdatesOverNetwork; +} + +HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine, + AutoMultiWriteLock2 &rLock) +{ + /* + * Attach the disk image + * HACK ALERT! Temporarily release the Unattended lock. + */ + rLock.release(); + + ComPtr<IMedium> ptrMedium; + HRESULT hrc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(), + pImage->enmDeviceType, + pImage->enmAccessType, + true, + ptrMedium.asOutParam()); + LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", hrc)); + if (SUCCEEDED(hrc)) + { + if (pImage->fAuxiliary && pImage->strImagePath.endsWith(".viso")) + { + hrc = ptrMedium->SetProperty(Bstr("UnattendedInstall").raw(), Bstr("1").raw()); + LogRelFlowFunc(("Medium::SetProperty -> %Rhrc\n", hrc)); + } + if (pImage->fMountOnly) + { + // mount the opened disk image + hrc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort, + pImage->iDevice, ptrMedium, TRUE /*fForce*/); + LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", hrc)); + } + else + { + //attach the opened disk image to the controller + hrc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort, + pImage->iDevice, pImage->enmDeviceType, ptrMedium); + LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", hrc)); + } + } + + rLock.acquire(); + return hrc; +} + +bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId) +{ + ComPtr<IGuestOSType> pGuestOSType; + HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam()); + if (SUCCEEDED(hrc)) + { + BOOL fIs64Bit = FALSE; + if (!pGuestOSType.isNull()) + hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit); + if (SUCCEEDED(hrc)) + return fIs64Bit != FALSE; + } + return false; +} + + +bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage) +{ + bool fRet = true; + + /* + * If the image doesn't have a valid value, we don't change it. + * This is obviously a little bit bogus, but what can we do... + */ + const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType); + if (pszOSTypeId && strcmp(pszOSTypeId, "Other") != 0) + mStrDetectedOSTypeId = pszOSTypeId; + else + fRet = false; + + if (rImage.mVersion.isNotEmpty()) + mStrDetectedOSVersion = rImage.mVersion; + else + fRet = false; + + if (rImage.mFlavor.isNotEmpty()) + mStrDetectedOSFlavor = rImage.mFlavor; + else + fRet = false; + + if (rImage.mLanguages.size() > 0) + mDetectedOSLanguages = rImage.mLanguages; + else + fRet = false; + + mEnmOsType = rImage.mEnmOsType; + + return fRet; +} |