VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/UnattendedImpl.cpp@ 107553

最後變更 在這個檔案從107553是 107533,由 vboxsync 提交於 2 月 前

src/VBox/Main/src-server/UnattendedImpl.cpp: Fixed warnings found by Parfait (uninitialized attributes). Docs. jiraref:VBP-1424

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 177.3 KB
 
1/* $Id: UnattendedImpl.cpp 107533 2025-01-08 16:06:12Z vboxsync $ */
2/** @file
3 * Unattended class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
33#include "LoggingNew.h"
34#include "VirtualBoxBase.h"
35#include "UnattendedImpl.h"
36#include "UnattendedInstaller.h"
37#include "UnattendedScript.h"
38#include "VirtualBoxImpl.h"
39#include "SystemPropertiesImpl.h"
40#include "MachineImpl.h"
41#include "Global.h"
42#include "StringifyEnums.h"
43
44#include <VBox/err.h>
45#include <iprt/cpp/xml.h>
46#include <iprt/ctype.h>
47#include <iprt/file.h>
48#ifndef RT_OS_WINDOWS
49# include <iprt/formats/mz.h>
50# include <iprt/formats/pecoff.h>
51#endif
52#include <iprt/formats/wim.h>
53#include <iprt/fsvfs.h>
54#include <iprt/inifile.h>
55#include <iprt/locale.h>
56#include <iprt/path.h>
57#include <iprt/vfs.h>
58
59using namespace std;
60
61
62/*********************************************************************************************************************************
63* Structures and Typedefs *
64*********************************************************************************************************************************/
65/**
66 * Controller slot for a DVD drive.
67 *
68 * The slot can be free and needing a drive to be attached along with the ISO
69 * image, or it may already be there and only need mounting the ISO. The
70 * ControllerSlot::fFree member indicates which it is.
71 */
72struct ControllerSlot
73{
74 StorageBus_T enmBus;
75 Utf8Str strControllerName;
76 LONG iPort;
77 LONG iDevice;
78 bool fFree;
79
80 ControllerSlot(StorageBus_T a_enmBus, const Utf8Str &a_rName, LONG a_iPort, LONG a_iDevice, bool a_fFree)
81 : enmBus(a_enmBus), strControllerName(a_rName), iPort(a_iPort), iDevice(a_iDevice), fFree(a_fFree)
82 {}
83
84 bool operator<(const ControllerSlot &rThat) const
85 {
86 if (enmBus == rThat.enmBus)
87 {
88 if (strControllerName == rThat.strControllerName)
89 {
90 if (iPort == rThat.iPort)
91 return iDevice < rThat.iDevice;
92 return iPort < rThat.iPort;
93 }
94 return strControllerName < rThat.strControllerName;
95 }
96
97 /*
98 * Bus comparsion in boot priority order.
99 */
100 /* IDE first. */
101 if (enmBus == StorageBus_IDE)
102 return true;
103 if (rThat.enmBus == StorageBus_IDE)
104 return false;
105 /* SATA next */
106 if (enmBus == StorageBus_SATA)
107 return true;
108 if (rThat.enmBus == StorageBus_SATA)
109 return false;
110 /* SCSI next */
111 if (enmBus == StorageBus_SCSI)
112 return true;
113 if (rThat.enmBus == StorageBus_SCSI)
114 return false;
115 /* numerical */
116 return (int)enmBus < (int)rThat.enmBus;
117 }
118
119 bool operator==(const ControllerSlot &rThat) const
120 {
121 return enmBus == rThat.enmBus
122 && strControllerName == rThat.strControllerName
123 && iPort == rThat.iPort
124 && iDevice == rThat.iDevice;
125 }
126};
127
128/**
129 * Installation disk.
130 *
131 * Used when reconfiguring the VM.
132 */
133typedef struct UnattendedInstallationDisk
134{
135 StorageBus_T enmBusType; /**< @todo nobody is using this... */
136 Utf8Str strControllerName;
137 DeviceType_T enmDeviceType;
138 AccessMode_T enmAccessType;
139 LONG iPort;
140 LONG iDevice;
141 bool fMountOnly;
142 Utf8Str strImagePath;
143 bool fAuxiliary;
144
145 UnattendedInstallationDisk(StorageBus_T a_enmBusType, Utf8Str const &a_rBusName, DeviceType_T a_enmDeviceType,
146 AccessMode_T a_enmAccessType, LONG a_iPort, LONG a_iDevice, bool a_fMountOnly,
147 Utf8Str const &a_rImagePath, bool a_fAuxiliary)
148 : enmBusType(a_enmBusType), strControllerName(a_rBusName), enmDeviceType(a_enmDeviceType), enmAccessType(a_enmAccessType)
149 , iPort(a_iPort), iDevice(a_iDevice), fMountOnly(a_fMountOnly), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
150 {
151 Assert(strControllerName.length() > 0);
152 }
153
154 UnattendedInstallationDisk(std::list<ControllerSlot>::const_iterator const &itDvdSlot, Utf8Str const &a_rImagePath,
155 bool a_fAuxiliary)
156 : enmBusType(itDvdSlot->enmBus), strControllerName(itDvdSlot->strControllerName), enmDeviceType(DeviceType_DVD)
157 , enmAccessType(AccessMode_ReadOnly), iPort(itDvdSlot->iPort), iDevice(itDvdSlot->iDevice)
158 , fMountOnly(!itDvdSlot->fFree), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
159 {
160 Assert(strControllerName.length() > 0);
161 }
162} UnattendedInstallationDisk;
163
164
165/**
166 * OS/2 syslevel file header.
167 */
168#pragma pack(1)
169typedef struct OS2SYSLEVELHDR
170{
171 uint16_t uMinusOne; /**< 0x00: UINT16_MAX */
172 char achSignature[8]; /**< 0x02: "SYSLEVEL" */
173 uint8_t abReserved1[5]; /**< 0x0a: Usually zero. Ignore. */
174 uint16_t uSyslevelFileVer; /**< 0x0f: The syslevel file version: 1. */
175 uint8_t abReserved2[16]; /**< 0x11: Zero. Ignore. */
176 uint32_t offTable; /**< 0x21: Offset of the syslevel table. */
177} OS2SYSLEVELHDR;
178#pragma pack()
179AssertCompileSize(OS2SYSLEVELHDR, 0x25);
180
181/**
182 * OS/2 syslevel table entry.
183 */
184#pragma pack(1)
185typedef struct OS2SYSLEVELENTRY
186{
187 uint16_t id; /**< 0x00: ? */
188 uint8_t bEdition; /**< 0x02: The OS/2 edition: 0=standard, 1=extended, x=component defined */
189 uint8_t bVersion; /**< 0x03: 0x45 = 4.5 */
190 uint8_t bModify; /**< 0x04: Lower nibble is added to bVersion, so 0x45 0x02 => 4.52 */
191 uint8_t abReserved1[2]; /**< 0x05: Zero. Ignore. */
192 char achCsdLevel[8]; /**< 0x07: The current CSD level. */
193 char achCsdPrior[8]; /**< 0x0f: The prior CSD level. */
194 char szName[80]; /**< 0x5f: System/component name. */
195 char achId[9]; /**< 0x67: System/component ID. */
196 uint8_t bRefresh; /**< 0x70: Single digit refresh version, ignored if zero. */
197 char szType[9]; /**< 0x71: Some kind of type string. Optional */
198 uint8_t abReserved2[6]; /**< 0x7a: Zero. Ignore. */
199} OS2SYSLEVELENTRY;
200#pragma pack()
201AssertCompileSize(OS2SYSLEVELENTRY, 0x80);
202
203
204
205/**
206 * Concatenate image name and version strings and return.
207 *
208 * A possible output would be "Windows 10 Home (10.0.19041.330 / x64)".
209 *
210 * @returns Name string to use.
211 * @param r_strName String object that can be formatted into and returned.
212 */
213const Utf8Str &WIMImage::formatName(Utf8Str &r_strName) const
214{
215 /* We skip the mFlavor as it's typically part of the description already. */
216
217 if (mVersion.isEmpty() && mArch.isEmpty() && mDefaultLanguage.isEmpty() && mLanguages.size() == 0)
218 return mName;
219
220 r_strName = mName;
221 bool fFirst = true;
222 if (mVersion.isNotEmpty())
223 {
224 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mVersion.c_str());
225 fFirst = false;
226 }
227 if (mArch.isNotEmpty())
228 {
229 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mArch.c_str());
230 fFirst = false;
231 }
232 if (mDefaultLanguage.isNotEmpty())
233 {
234 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mDefaultLanguage.c_str());
235 fFirst = false;
236 }
237 else
238 for (size_t i = 0; i < mLanguages.size(); i++)
239 {
240 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mLanguages[i].c_str());
241 fFirst = false;
242 }
243 r_strName.append(")");
244 return r_strName;
245}
246
247
248//////////////////////////////////////////////////////////////////////////////////////////////////////
249/*
250*
251*
252* Implementation Unattended functions
253*
254*/
255//////////////////////////////////////////////////////////////////////////////////////////////////////
256
257Unattended::Unattended()
258 : mhThreadReconfigureVM(NIL_RTNATIVETHREAD), mfRtcUseUtc(false), mfGuestOs64Bit(false)
259 , menmFirmwareType(FirmwareType_BIOS), mpInstaller(NULL)
260 , mfInstallGuestAdditions(false), mfInstallTestExecService(false), mfInstallUserPayload(false)
261 , mpTimeZoneInfo(NULL), mfIsDefaultAuxiliaryBasePath(true), midxImage(0), mfDoneDetectIsoOS(false)
262 , mEnmOsType(VBOXOSTYPE_Unknown)
263 , mfAvoidUpdatesOverNetwork(false), mfDoneSupportedGuestOSList(false)
264{ }
265
266Unattended::~Unattended()
267{
268 if (mpInstaller)
269 {
270 delete mpInstaller;
271 mpInstaller = NULL;
272 }
273}
274
275HRESULT Unattended::FinalConstruct()
276{
277 return BaseFinalConstruct();
278}
279
280void Unattended::FinalRelease()
281{
282 uninit();
283
284 BaseFinalRelease();
285}
286
287void Unattended::uninit()
288{
289 /* Enclose the state transition Ready->InUninit->NotReady */
290 AutoUninitSpan autoUninitSpan(this);
291 if (autoUninitSpan.uninitDone())
292 return;
293
294 unconst(mParent) = NULL;
295 mMachine.setNull();
296}
297
298/**
299 * Initializes the unattended object.
300 *
301 * @param aParent Pointer to the parent object.
302 */
303HRESULT Unattended::initUnattended(VirtualBox *aParent)
304{
305 LogFlowThisFunc(("aParent=%p\n", aParent));
306 ComAssertRet(aParent, E_INVALIDARG);
307
308 /* Enclose the state transition NotReady->InInit->Ready */
309 AutoInitSpan autoInitSpan(this);
310 AssertReturn(autoInitSpan.isOk(), E_FAIL);
311
312 unconst(mParent) = aParent;
313
314 /*
315 * Fill public attributes (IUnattended) with useful defaults.
316 */
317 try
318 {
319 mStrUser = "vboxuser";
320 mStrUserPassword = "changeme";
321 /* Note: For mStrAdminPassword see Unattended::i_getAdminPassword(). */
322 mfInstallGuestAdditions = false;
323 mfInstallTestExecService = false;
324 mfInstallUserPayload = false;
325 midxImage = 1;
326
327 HRESULT hrc = mParent->i_getSystemProperties()->i_getDefaultAdditionsISO(mStrAdditionsIsoPath);
328 ComAssertComRCRet(hrc, hrc);
329 }
330 catch (std::bad_alloc &)
331 {
332 return E_OUTOFMEMORY;
333 }
334
335 /*
336 * Confirm a successful initialization
337 */
338 autoInitSpan.setSucceeded();
339
340 return S_OK;
341}
342
343HRESULT Unattended::detectIsoOS()
344{
345 HRESULT hrc;
346
347 /* Populate list of supported guest OSs in case it has not been done yet. Do this before locking. */
348 if (!mfDoneSupportedGuestOSList)
349 i_getListOfSupportedGuestOS();
350
351 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
352
353 /** @todo once UDF is implemented properly and we've tested this code a lot
354 * more, replace E_NOTIMPL with E_FAIL. */
355
356 /*
357 * Reset output state before we start
358 */
359 mStrDetectedOSTypeId.setNull();
360 mStrDetectedOSVersion.setNull();
361 mStrDetectedOSFlavor.setNull();
362 mDetectedOSLanguages.clear();
363 mStrDetectedOSHints.setNull();
364 mDetectedImages.clear();
365
366 /*
367 * Open the ISO.
368 */
369 RTVFSFILE hVfsFileIso;
370 int vrc = RTVfsFileOpenNormal(mStrIsoPath.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileIso);
371 if (RT_FAILURE(vrc))
372 return setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' (%Rrc)"), mStrIsoPath.c_str(), vrc);
373
374 RTERRINFOSTATIC ErrInfo;
375 RTVFS hVfsIso;
376 vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, RTErrInfoInitStatic(&ErrInfo));
377 if (RT_SUCCESS(vrc))
378 {
379 /*
380 * Try do the detection. Repeat for different file system variations (nojoliet, noudf).
381 */
382 hrc = i_innerDetectIsoOS(hVfsIso);
383
384 RTVfsRelease(hVfsIso);
385
386 /* If detecting the ISO failed, print everything we got to the VBoxSVC release log,
387 * to (hopefully) provide us more clues about which distros don't work. */
388 if (FAILED(hrc))
389 {
390 Utf8Str strLangs;
391 for (size_t i = 0; i < mDetectedOSLanguages.size(); i++)
392 {
393 if (i)
394 strLangs += ", ";
395 strLangs += mDetectedOSLanguages[i];
396 }
397
398 Utf8Str strImages;
399 for (size_t i = 0; i < mDetectedImages.size(); i++)
400 {
401 if (i)
402 strImages += ", ";
403 strImages += mDetectedImages[i].mName;
404 }
405
406 LogRel(("Unattended: Detection summary:\n"
407 "Unattended: OS type ID : %s\n"
408 "Unattended: OS version : %s\n"
409 "Unattended: OS flavor : %s\n"
410 "Unattended: OS language: %s\n"
411 "Unattended: OS hints : %s\n"
412 "Unattended: Images : %s\n",
413 mStrDetectedOSTypeId.c_str(),
414 mStrDetectedOSVersion.c_str(),
415 mStrDetectedOSFlavor.c_str(),
416 strLangs.c_str(),
417 mStrDetectedOSHints.c_str(),
418 strImages.c_str()));
419 }
420
421 if (hrc == S_FALSE) /** @todo Finish the linux and windows detection code. Only OS/2 returns S_OK right now. */
422 hrc = E_NOTIMPL;
423 }
424 else if (RTErrInfoIsSet(&ErrInfo.Core))
425 hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc) - %s"),
426 mStrIsoPath.c_str(), vrc, ErrInfo.Core.pszMsg);
427 else
428 hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc)"), mStrIsoPath.c_str(), vrc);
429 RTVfsFileRelease(hVfsFileIso);
430
431 /*
432 * Just fake up some windows installation media locale (for <UILanguage>).
433 * Note! The translation here isn't perfect. Feel free to send us a patch.
434 */
435 if (mDetectedOSLanguages.size() == 0)
436 {
437 char szTmp[16];
438 const char *pszFilename = RTPathFilename(mStrIsoPath.c_str());
439 if ( pszFilename
440 && RT_C_IS_ALPHA(pszFilename[0])
441 && RT_C_IS_ALPHA(pszFilename[1])
442 && (pszFilename[2] == '-' || pszFilename[2] == '_') )
443 {
444 szTmp[0] = (char)RT_C_TO_LOWER(pszFilename[0]);
445 szTmp[1] = (char)RT_C_TO_LOWER(pszFilename[1]);
446 szTmp[2] = '-';
447 if (szTmp[0] == 'e' && szTmp[1] == 'n')
448 strcpy(&szTmp[3], "US");
449 else if (szTmp[0] == 'a' && szTmp[1] == 'r')
450 strcpy(&szTmp[3], "SA");
451 else if (szTmp[0] == 'd' && szTmp[1] == 'a')
452 strcpy(&szTmp[3], "DK");
453 else if (szTmp[0] == 'e' && szTmp[1] == 't')
454 strcpy(&szTmp[3], "EE");
455 else if (szTmp[0] == 'e' && szTmp[1] == 'l')
456 strcpy(&szTmp[3], "GR");
457 else if (szTmp[0] == 'h' && szTmp[1] == 'e')
458 strcpy(&szTmp[3], "IL");
459 else if (szTmp[0] == 'j' && szTmp[1] == 'a')
460 strcpy(&szTmp[3], "JP");
461 else if (szTmp[0] == 's' && szTmp[1] == 'v')
462 strcpy(&szTmp[3], "SE");
463 else if (szTmp[0] == 'u' && szTmp[1] == 'k')
464 strcpy(&szTmp[3], "UA");
465 else if (szTmp[0] == 'c' && szTmp[1] == 's')
466 strcpy(szTmp, "cs-CZ");
467 else if (szTmp[0] == 'n' && szTmp[1] == 'o')
468 strcpy(szTmp, "nb-NO");
469 else if (szTmp[0] == 'p' && szTmp[1] == 'p')
470 strcpy(szTmp, "pt-PT");
471 else if (szTmp[0] == 'p' && szTmp[1] == 't')
472 strcpy(szTmp, "pt-BR");
473 else if (szTmp[0] == 'c' && szTmp[1] == 'n')
474 strcpy(szTmp, "zh-CN");
475 else if (szTmp[0] == 'h' && szTmp[1] == 'k')
476 strcpy(szTmp, "zh-HK");
477 else if (szTmp[0] == 't' && szTmp[1] == 'w')
478 strcpy(szTmp, "zh-TW");
479 else if (szTmp[0] == 's' && szTmp[1] == 'r')
480 strcpy(szTmp, "sr-Latn-CS"); /* hmm */
481 else
482 {
483 szTmp[3] = (char)RT_C_TO_UPPER(pszFilename[0]);
484 szTmp[4] = (char)RT_C_TO_UPPER(pszFilename[1]);
485 szTmp[5] = '\0';
486 }
487 }
488 else
489 strcpy(szTmp, "en-US");
490 try
491 {
492 mDetectedOSLanguages.append(szTmp);
493 }
494 catch (std::bad_alloc &)
495 {
496 return E_OUTOFMEMORY;
497 }
498 }
499
500 /* Check if detected OS type is supported (covers platform architecture). */
501 bool fSupported = false;
502 for (size_t i = 0; i < mSupportedGuestOSTypes.size() && !fSupported; ++i)
503 {
504 ComPtr<IGuestOSType> guestOSType = mSupportedGuestOSTypes[i];
505
506 Bstr guestId;
507 guestOSType->COMGETTER(Id)(guestId.asOutParam());
508 if (guestId == mStrDetectedOSTypeId)
509 fSupported = true;
510 }
511 if (!fSupported)
512 {
513 mStrDetectedOSTypeId.setNull();
514 mStrDetectedOSVersion.setNull();
515 mStrDetectedOSFlavor.setNull();
516 mDetectedOSLanguages.clear();
517 mStrDetectedOSHints.setNull();
518 mDetectedImages.clear();
519 hrc = E_FAIL;
520 }
521
522 /** @todo implement actual detection logic. */
523 return hrc;
524}
525
526HRESULT Unattended::i_innerDetectIsoOS(RTVFS hVfsIso)
527{
528 DETECTBUFFER uBuf;
529 mEnmOsType = VBOXOSTYPE_Unknown;
530 HRESULT hrc = i_innerDetectIsoOSWindows(hVfsIso, &uBuf);
531 if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
532 hrc = i_innerDetectIsoOSLinux(hVfsIso, &uBuf);
533 if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
534 hrc = i_innerDetectIsoOSOs2(hVfsIso, &uBuf);
535 if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
536 hrc = i_innerDetectIsoOSFreeBsd(hVfsIso, &uBuf);
537 if (mEnmOsType != VBOXOSTYPE_Unknown)
538 {
539 try { mStrDetectedOSTypeId = Global::OSTypeId(mEnmOsType); }
540 catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
541 }
542 return hrc;
543}
544
545/**
546 * Tries to parse a LANGUAGES element, with the following structure.
547 * @verbatim
548 * <LANGUAGES>
549 * <LANGUAGE>
550 * en-US
551 * </LANGUAGE>
552 * <DEFAULT>
553 * en-US
554 * </DEFAULT>
555 * </LANGUAGES>
556 * @endverbatim
557 *
558 * Will set mLanguages and mDefaultLanguage success.
559 *
560 * @param pElmLanguages Points to the LANGUAGES XML node.
561 * @param rImage Out reference to an WIMImage instance.
562 */
563static void parseLangaguesElement(const xml::ElementNode *pElmLanguages, WIMImage &rImage)
564{
565 /*
566 * The languages.
567 */
568 ElementNodesList children;
569 int cChildren = pElmLanguages->getChildElements(children, "LANGUAGE");
570 if (cChildren == 0)
571 cChildren = pElmLanguages->getChildElements(children, "language");
572 if (cChildren == 0)
573 cChildren = pElmLanguages->getChildElements(children, "Language");
574 for (ElementNodesList::iterator iterator = children.begin(); iterator != children.end(); ++iterator)
575 {
576 const ElementNode * const pElmLanguage = *(iterator);
577 if (pElmLanguage)
578 {
579 const char *pszValue = pElmLanguage->getValue();
580 if (pszValue && *pszValue != '\0')
581 rImage.mLanguages.append(pszValue);
582 }
583 }
584
585 /*
586 * Default language.
587 */
588 const xml::ElementNode *pElmDefault;
589 if ( (pElmDefault = pElmLanguages->findChildElement("DEFAULT")) != NULL
590 || (pElmDefault = pElmLanguages->findChildElement("default")) != NULL
591 || (pElmDefault = pElmLanguages->findChildElement("Default")) != NULL)
592 rImage.mDefaultLanguage = pElmDefault->getValue();
593}
594
595
596/**
597 * Tries to set the image architecture.
598 *
599 * Input examples (x86 and amd64 respectively):
600 * @verbatim
601 * <ARCH>0</ARCH>
602 * <ARCH>9</ARCH>
603 * @endverbatim
604 *
605 * Will set mArch and update mOSType on success.
606 *
607 * @param pElmArch Points to the ARCH XML node.
608 * @param rImage Out reference to an WIMImage instance.
609 */
610static void parseArchElement(const xml::ElementNode *pElmArch, WIMImage &rImage)
611{
612 /* These are from winnt.h */
613 static struct { const char *pszArch; VBOXOSTYPE enmArch; } s_aArches[] =
614 {
615 /* PROCESSOR_ARCHITECTURE_INTEL / [0] = */ { "x86", VBOXOSTYPE_x86 },
616 /* PROCESSOR_ARCHITECTURE_MIPS / [1] = */ { "mips", VBOXOSTYPE_UnknownArch },
617 /* PROCESSOR_ARCHITECTURE_ALPHA / [2] = */ { "alpha", VBOXOSTYPE_UnknownArch },
618 /* PROCESSOR_ARCHITECTURE_PPC / [3] = */ { "ppc", VBOXOSTYPE_UnknownArch },
619 /* PROCESSOR_ARCHITECTURE_SHX / [4] = */ { "shx", VBOXOSTYPE_UnknownArch },
620 /* PROCESSOR_ARCHITECTURE_ARM / [5] = */ { "arm32", VBOXOSTYPE_arm32 },
621 /* PROCESSOR_ARCHITECTURE_IA64 / [6] = */ { "ia64", VBOXOSTYPE_UnknownArch },
622 /* PROCESSOR_ARCHITECTURE_ALPHA64 / [7] = */ { "alpha64", VBOXOSTYPE_UnknownArch },
623 /* PROCESSOR_ARCHITECTURE_MSIL / [8] = */ { "msil", VBOXOSTYPE_UnknownArch },
624 /* PROCESSOR_ARCHITECTURE_AMD64 / [9] = */ { "x64", VBOXOSTYPE_x64 },
625 /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 / [10] = */ { "x86-on-x64", VBOXOSTYPE_UnknownArch },
626 /* PROCESSOR_ARCHITECTURE_NEUTRAL / [11] = */ { "noarch", VBOXOSTYPE_UnknownArch },
627 /* PROCESSOR_ARCHITECTURE_ARM64 / [12] = */ { "arm64", VBOXOSTYPE_arm64 },
628 /* PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64/ [13] = */ { "arm32-on-arm64", VBOXOSTYPE_UnknownArch },
629 /* PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 / [14] = */ { "x86-on-arm32", VBOXOSTYPE_UnknownArch },
630 };
631 const char *pszArch = pElmArch->getValue();
632 if (pszArch && *pszArch)
633 {
634 uint32_t uArch;
635 int vrc = RTStrToUInt32Ex(pszArch, NULL, 10 /*uBase*/, &uArch);
636 if ( RT_SUCCESS(vrc)
637 && vrc != VWRN_NUMBER_TOO_BIG
638 && vrc != VWRN_NEGATIVE_UNSIGNED
639 && uArch < RT_ELEMENTS(s_aArches))
640 {
641 rImage.mArch = s_aArches[uArch].pszArch;
642 rImage.mOSType = (VBOXOSTYPE)(s_aArches[uArch].enmArch | (rImage.mOSType & VBOXOSTYPE_OsMask));
643 }
644 else
645 LogRel(("Unattended: bogus ARCH element value: '%s'\n", pszArch));
646 }
647}
648
649/**
650 * Parses XML Node assuming a structure as follows
651 * @verbatim
652 * <VERSION>
653 * <MAJOR>10</MAJOR>
654 * <MINOR>0</MINOR>
655 * <BUILD>19041</BUILD>
656 * <SPBUILD>1</SPBUILD>
657 * </VERSION>
658 * @endverbatim
659 *
660 * Will update mOSType, mEnmOsType as well as setting mVersion on success.
661 *
662 * @param pNode Points to the vesion XML node,
663 * @param image Out reference to an WIMImage instance.
664 */
665static void parseVersionElement(const xml::ElementNode *pNode, WIMImage &image)
666{
667 /* Major part: */
668 const xml::ElementNode *pElmMajor;
669 if ( (pElmMajor = pNode->findChildElement("MAJOR")) != NULL
670 || (pElmMajor = pNode->findChildElement("major")) != NULL
671 || (pElmMajor = pNode->findChildElement("Major")) != NULL)
672 if (pElmMajor)
673 {
674 const char * const pszMajor = pElmMajor->getValue();
675 if (pszMajor && *pszMajor)
676 {
677 /* Minor part: */
678 const ElementNode *pElmMinor;
679 if ( (pElmMinor = pNode->findChildElement("MINOR")) != NULL
680 || (pElmMinor = pNode->findChildElement("minor")) != NULL
681 || (pElmMinor = pNode->findChildElement("Minor")) != NULL)
682 {
683 const char * const pszMinor = pElmMinor->getValue();
684 if (pszMinor && *pszMinor)
685 {
686 /* Build: */
687 const ElementNode *pElmBuild;
688 if ( (pElmBuild = pNode->findChildElement("BUILD")) != NULL
689 || (pElmBuild = pNode->findChildElement("build")) != NULL
690 || (pElmBuild = pNode->findChildElement("Build")) != NULL)
691 {
692 const char * const pszBuild = pElmBuild->getValue();
693 if (pszBuild && *pszBuild)
694 {
695 /* SPBuild: */
696 const ElementNode *pElmSpBuild;
697 if ( ( (pElmSpBuild = pNode->findChildElement("SPBUILD")) != NULL
698 || (pElmSpBuild = pNode->findChildElement("spbuild")) != NULL
699 || (pElmSpBuild = pNode->findChildElement("Spbuild")) != NULL
700 || (pElmSpBuild = pNode->findChildElement("SpBuild")) != NULL)
701 && pElmSpBuild->getValue()
702 && *pElmSpBuild->getValue() != '\0')
703 image.mVersion.printf("%s.%s.%s.%s", pszMajor, pszMinor, pszBuild, pElmSpBuild->getValue());
704 else
705 image.mVersion.printf("%s.%s.%s", pszMajor, pszMinor, pszBuild);
706
707 /*
708 * Convert that to a version windows OS ID (newest first!).
709 */
710 VBOXOSTYPE enmVersion = VBOXOSTYPE_Unknown;
711 if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.22000.0") >= 0)
712 enmVersion = VBOXOSTYPE_Win11_x64;
713 else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0)
714 enmVersion = VBOXOSTYPE_Win10;
715 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.3") >= 0)
716 enmVersion = VBOXOSTYPE_Win81;
717 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0)
718 enmVersion = VBOXOSTYPE_Win8;
719 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.1") >= 0)
720 enmVersion = VBOXOSTYPE_Win7;
721 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0)
722 enmVersion = VBOXOSTYPE_WinVista;
723 if (image.mFlavor.contains("server", Utf8Str::CaseInsensitive))
724 {
725 if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.20348") >= 0)
726 enmVersion = VBOXOSTYPE_Win2k22_x64;
727 else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.17763") >= 0)
728 enmVersion = VBOXOSTYPE_Win2k19_x64;
729 else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0)
730 enmVersion = VBOXOSTYPE_Win2k16_x64;
731 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0)
732 enmVersion = VBOXOSTYPE_Win2k12_x64;
733 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0)
734 enmVersion = VBOXOSTYPE_Win2k8;
735 }
736 if (enmVersion != VBOXOSTYPE_Unknown)
737 image.mOSType = (VBOXOSTYPE)( (image.mOSType & VBOXOSTYPE_ArchitectureMask)
738 | (enmVersion & VBOXOSTYPE_OsMask));
739 return;
740 }
741 }
742 }
743 }
744 }
745 }
746 Log(("Unattended: Warning! Bogus/missing version info for image #%u / %s\n", image.mImageIndex, image.mName.c_str()));
747}
748
749/**
750 * Parses XML tree assuming th following structure
751 * @verbatim
752 * <WIM>
753 * ...
754 * <IMAGE INDEX="1">
755 * ...
756 * <DISPLAYNAME>Windows 10 Home</DISPLAYNAME>
757 * <WINDOWS>
758 * <ARCH>NN</ARCH>
759 * <VERSION>
760 * ...
761 * </VERSION>
762 * <LANGUAGES>
763 * <LANGUAGE>
764 * en-US
765 * </LANGUAGE>
766 * <DEFAULT>
767 * en-US
768 * </DEFAULT>
769 * </LANGUAGES>
770 * </WINDOWS>
771 * </IMAGE>
772 * </WIM>
773 * @endverbatim
774 *
775 * @param pElmRoot Pointer to the root node of the tree,
776 * @param imageList Detected images are appended to this list.
777 */
778static void parseWimXMLData(const xml::ElementNode *pElmRoot, RTCList<WIMImage> &imageList)
779{
780 if (!pElmRoot)
781 return;
782
783 ElementNodesList children;
784 int cChildren = pElmRoot->getChildElements(children, "IMAGE");
785 if (cChildren == 0)
786 cChildren = pElmRoot->getChildElements(children, "image");
787 if (cChildren == 0)
788 cChildren = pElmRoot->getChildElements(children, "Image");
789
790 for (ElementNodesList::iterator iterator = children.begin(); iterator != children.end(); ++iterator)
791 {
792 const ElementNode *pChild = *(iterator);
793 if (!pChild)
794 continue;
795
796 WIMImage newImage;
797
798 if ( !pChild->getAttributeValue("INDEX", &newImage.mImageIndex)
799 && !pChild->getAttributeValue("index", &newImage.mImageIndex)
800 && !pChild->getAttributeValue("Index", &newImage.mImageIndex))
801 continue;
802
803 const ElementNode *pElmName;
804 if ( (pElmName = pChild->findChildElement("DISPLAYNAME")) == NULL
805 && (pElmName = pChild->findChildElement("displayname")) == NULL
806 && (pElmName = pChild->findChildElement("Displayname")) == NULL
807 && (pElmName = pChild->findChildElement("DisplayName")) == NULL
808 /* Early vista images didn't have DISPLAYNAME. */
809 && (pElmName = pChild->findChildElement("NAME")) == NULL
810 && (pElmName = pChild->findChildElement("name")) == NULL
811 && (pElmName = pChild->findChildElement("Name")) == NULL)
812 continue;
813 newImage.mName = pElmName->getValue();
814 if (newImage.mName.isEmpty())
815 continue;
816
817 const ElementNode *pElmWindows;
818 if ( (pElmWindows = pChild->findChildElement("WINDOWS")) != NULL
819 || (pElmWindows = pChild->findChildElement("windows")) != NULL
820 || (pElmWindows = pChild->findChildElement("Windows")) != NULL)
821 {
822 /* Do edition/flags before the version so it can better determin
823 the OS version enum value. Old windows version (vista) typically
824 doesn't have an EDITIONID element, so fall back on the FLAGS element
825 under IMAGE as it is pretty similar (case differences). */
826 const ElementNode *pElmEditionId;
827 if ( (pElmEditionId = pElmWindows->findChildElement("EDITIONID")) != NULL
828 || (pElmEditionId = pElmWindows->findChildElement("editionid")) != NULL
829 || (pElmEditionId = pElmWindows->findChildElement("Editionid")) != NULL
830 || (pElmEditionId = pElmWindows->findChildElement("EditionId")) != NULL
831 || (pElmEditionId = pChild->findChildElement("FLAGS")) != NULL
832 || (pElmEditionId = pChild->findChildElement("flags")) != NULL
833 || (pElmEditionId = pChild->findChildElement("Flags")) != NULL)
834 if ( pElmEditionId->getValue()
835 && *pElmEditionId->getValue() != '\0')
836 newImage.mFlavor = pElmEditionId->getValue();
837
838 const ElementNode *pElmVersion;
839 if ( (pElmVersion = pElmWindows->findChildElement("VERSION")) != NULL
840 || (pElmVersion = pElmWindows->findChildElement("version")) != NULL
841 || (pElmVersion = pElmWindows->findChildElement("Version")) != NULL)
842 parseVersionElement(pElmVersion, newImage);
843
844 /* The ARCH element contains a number from the
845 PROCESSOR_ARCHITECTURE_XXX set of defines in winnt.h: */
846 const ElementNode *pElmArch;
847 if ( (pElmArch = pElmWindows->findChildElement("ARCH")) != NULL
848 || (pElmArch = pElmWindows->findChildElement("arch")) != NULL
849 || (pElmArch = pElmWindows->findChildElement("Arch")) != NULL)
850 parseArchElement(pElmArch, newImage);
851
852 /* Extract languages and default language: */
853 const ElementNode *pElmLang;
854 if ( (pElmLang = pElmWindows->findChildElement("LANGUAGES")) != NULL
855 || (pElmLang = pElmWindows->findChildElement("languages")) != NULL
856 || (pElmLang = pElmWindows->findChildElement("Languages")) != NULL)
857 parseLangaguesElement(pElmLang, newImage);
858 }
859
860
861 imageList.append(newImage);
862 }
863}
864
865/**
866 * Detect Windows ISOs.
867 *
868 * @returns COM status code.
869 * @retval S_OK if detected
870 * @retval S_FALSE if not fully detected.
871 *
872 * @param hVfsIso The ISO file system.
873 * @param pBuf Read buffer.
874 */
875HRESULT Unattended::i_innerDetectIsoOSWindows(RTVFS hVfsIso, DETECTBUFFER *pBuf)
876{
877 /** @todo The 'sources/' path can differ. */
878
879 // globalinstallorder.xml - vista beta2
880 // sources/idwbinfo.txt - ditto.
881 // sources/lang.ini - ditto.
882
883 /*
884 * The install.wim file contains an XML document describing the install
885 * images it contains. This includes all the info we need for a successful
886 * detection.
887 */
888 RTVFSFILE hVfsFile;
889 int vrc = RTVfsFileOpen(hVfsIso, "sources/install.wim", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
890 if (RT_SUCCESS(vrc))
891 {
892 WIMHEADERV1 header;
893 size_t cbRead = 0;
894 vrc = RTVfsFileRead(hVfsFile, &header, sizeof(header), &cbRead);
895 if (RT_SUCCESS(vrc) && cbRead == sizeof(header))
896 {
897 /* If the xml data is not compressed, xml data is not empty, and not too big. */
898 if ( (header.XmlData.bFlags & RESHDR_FLAGS_METADATA)
899 && !(header.XmlData.bFlags & RESHDR_FLAGS_COMPRESSED)
900 && header.XmlData.cbOriginal >= 32
901 && header.XmlData.cbOriginal < _32M
902 && header.XmlData.cbOriginal == header.XmlData.cb)
903 {
904 size_t const cbXmlData = (size_t)header.XmlData.cbOriginal;
905 char *pachXmlBuf = (char *)RTMemTmpAlloc(cbXmlData);
906 if (pachXmlBuf)
907 {
908 vrc = RTVfsFileReadAt(hVfsFile, (RTFOFF)header.XmlData.off, pachXmlBuf, cbXmlData, NULL);
909 if (RT_SUCCESS(vrc))
910 {
911 LogRel2(("Unattended: XML Data (%#zx bytes):\n%32.*Rhxd\n", cbXmlData, cbXmlData, pachXmlBuf));
912
913 /* Parse the XML: */
914 xml::Document doc;
915 xml::XmlMemParser parser;
916 try
917 {
918 RTCString strFileName = "source/install.wim";
919 parser.read(pachXmlBuf, cbXmlData, strFileName, doc);
920 }
921 catch (xml::XmlError &rErr)
922 {
923 LogRel(("Unattended: An error has occured during XML parsing: %s\n", rErr.what()));
924 vrc = VERR_XAR_TOC_XML_PARSE_ERROR;
925 }
926 catch (std::bad_alloc &)
927 {
928 LogRel(("Unattended: std::bad_alloc\n"));
929 vrc = VERR_NO_MEMORY;
930 }
931 catch (...)
932 {
933 LogRel(("Unattended: An unknown error has occured during XML parsing.\n"));
934 vrc = VERR_UNEXPECTED_EXCEPTION;
935 }
936 if (RT_SUCCESS(vrc))
937 {
938 /* Extract the information we need from the XML document: */
939 xml::ElementNode *pElmRoot = doc.getRootElement();
940 if (pElmRoot)
941 {
942 Assert(mDetectedImages.size() == 0);
943 try
944 {
945 mDetectedImages.clear(); /* debugging convenience */
946 parseWimXMLData(pElmRoot, mDetectedImages);
947 }
948 catch (std::bad_alloc &)
949 {
950 vrc = VERR_NO_MEMORY;
951 }
952
953 /*
954 * If we found images, update the detected info attributes.
955 */
956 if (RT_SUCCESS(vrc) && mDetectedImages.size() > 0)
957 {
958 size_t i;
959 for (i = 0; i < mDetectedImages.size(); i++)
960 if (mDetectedImages[i].mImageIndex == midxImage)
961 break;
962 if (i >= mDetectedImages.size())
963 i = 0; /* use the first one if midxImage wasn't found */
964 if (i_updateDetectedAttributeForImage(mDetectedImages[i]))
965 {
966 LogRel2(("Unattended: happy with mDetectedImages[%u]\n", i));
967 mEnmOsType = mDetectedImages[i].mOSType;
968 return S_OK;
969 }
970 }
971 }
972 else
973 LogRel(("Unattended: No root element found in XML Metadata of install.wim\n"));
974 }
975 }
976 else
977 LogRel(("Unattended: Failed during reading XML Metadata out of install.wim\n"));
978 RTMemTmpFree(pachXmlBuf);
979 }
980 else
981 {
982 LogRel(("Unattended: Failed to allocate %#zx bytes for XML Metadata\n", cbXmlData));
983 vrc = VERR_NO_TMP_MEMORY;
984 }
985 }
986 else
987 LogRel(("Unattended: XML Metadata of install.wim is either compressed, empty, or too big (bFlags=%#x cbOriginal=%#RX64 cb=%#RX64)\n",
988 header.XmlData.bFlags, header.XmlData.cbOriginal, header.XmlData.cb));
989 }
990 RTVfsFileRelease(hVfsFile);
991
992 /* Bail out if we ran out of memory here. */
993 if (vrc == VERR_NO_MEMORY || vrc == VERR_NO_TMP_MEMORY)
994 return setErrorBoth(E_OUTOFMEMORY, vrc, tr("Out of memory"));
995 }
996
997 const char *pszVersion = NULL;
998 const char *pszProduct = NULL;
999 /*
1000 * Try look for the 'sources/idwbinfo.txt' file containing windows build info.
1001 * This file appeared with Vista beta 2 from what we can tell. Before windows 10
1002 * it contains easily decodable branch names, after that things goes weird.
1003 */
1004 vrc = RTVfsFileOpen(hVfsIso, "sources/idwbinfo.txt", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1005 if (RT_SUCCESS(vrc))
1006 {
1007 mEnmOsType = VBOXOSTYPE_WinNT_x64;
1008
1009 RTINIFILE hIniFile;
1010 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1011 RTVfsFileRelease(hVfsFile);
1012 if (RT_SUCCESS(vrc))
1013 {
1014 vrc = RTIniFileQueryValue(hIniFile, "BUILDINFO", "BuildArch", pBuf->sz, sizeof(*pBuf), NULL);
1015 if (RT_SUCCESS(vrc))
1016 {
1017 LogRelFlow(("Unattended: sources/idwbinfo.txt: BuildArch=%s\n", pBuf->sz));
1018 if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("amd64")) == 0
1019 || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("x64")) == 0 /* just in case */ )
1020 mEnmOsType = VBOXOSTYPE_WinNT_x64;
1021 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("x86")) == 0)
1022 mEnmOsType = VBOXOSTYPE_WinNT;
1023 else
1024 {
1025 LogRel(("Unattended: sources/idwbinfo.txt: Unknown: BuildArch=%s\n", pBuf->sz));
1026 mEnmOsType = VBOXOSTYPE_WinNT_x64;
1027 }
1028 }
1029
1030 vrc = RTIniFileQueryValue(hIniFile, "BUILDINFO", "BuildBranch", pBuf->sz, sizeof(*pBuf), NULL);
1031 if (RT_SUCCESS(vrc))
1032 {
1033 LogRelFlow(("Unattended: sources/idwbinfo.txt: BuildBranch=%s\n", pBuf->sz));
1034 if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("vista")) == 0
1035 || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_beta")) == 0)
1036 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista);
1037 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("lh_sp2rtm")) == 0)
1038 {
1039 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista);
1040 pszVersion = "sp2";
1041 }
1042 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("longhorn_rtm")) == 0)
1043 {
1044 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista);
1045 pszVersion = "sp1";
1046 }
1047 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win7")) == 0)
1048 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win7);
1049 else if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winblue")) == 0
1050 || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_blue")) == 0
1051 || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win81")) == 0 /* not seen, but just in case its out there */ )
1052 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win81);
1053 else if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win8")) == 0
1054 || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_win8")) == 0 )
1055 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win8);
1056 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("th1")) == 0)
1057 {
1058 pszVersion = "1507"; // aka. GA, retroactively 1507
1059 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1060 }
1061 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("th2")) == 0)
1062 {
1063 pszVersion = "1511"; // aka. threshold 2
1064 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1065 }
1066 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs1_release")) == 0)
1067 {
1068 pszVersion = "1607"; // aka. anniversay update; rs=redstone
1069 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1070 }
1071 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs2_release")) == 0)
1072 {
1073 pszVersion = "1703"; // aka. creators update
1074 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1075 }
1076 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs3_release")) == 0)
1077 {
1078 pszVersion = "1709"; // aka. fall creators update
1079 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1080 }
1081 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs4_release")) == 0)
1082 {
1083 pszVersion = "1803";
1084 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1085 }
1086 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs5_release")) == 0)
1087 {
1088 pszVersion = "1809";
1089 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1090 }
1091 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("19h1_release")) == 0)
1092 {
1093 pszVersion = "1903";
1094 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1095 }
1096 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("19h2_release")) == 0)
1097 {
1098 pszVersion = "1909"; // ??
1099 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1100 }
1101 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("20h1_release")) == 0)
1102 {
1103 pszVersion = "2003"; // ??
1104 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1105 }
1106 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("vb_release")) == 0)
1107 {
1108 pszVersion = "2004"; // ?? vb=Vibranium
1109 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1110 }
1111 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("20h2_release")) == 0)
1112 {
1113 pszVersion = "2009"; // ??
1114 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1115 }
1116 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("21h1_release")) == 0)
1117 {
1118 pszVersion = "2103"; // ??
1119 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1120 }
1121 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("21h2_release")) == 0)
1122 {
1123 pszVersion = "2109"; // ??
1124 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
1125 }
1126 else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("co_release")) == 0)
1127 {
1128 pszVersion = "21H2"; // ??
1129 mEnmOsType = VBOXOSTYPE_Win11_x64;
1130 }
1131 else
1132 LogRel(("Unattended: sources/idwbinfo.txt: Unknown: BuildBranch=%s\n", pBuf->sz));
1133 }
1134 RTIniFileRelease(hIniFile);
1135 }
1136 }
1137 bool fClarifyProd = false;
1138 if (RT_FAILURE(vrc))
1139 {
1140 /*
1141 * Check a INF file with a DriverVer that is updated with each service pack.
1142 * DriverVer=10/01/2002,5.2.3790.3959
1143 */
1144 vrc = RTVfsFileOpen(hVfsIso, "AMD64/HIVESYS.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1145 if (RT_SUCCESS(vrc))
1146 mEnmOsType = VBOXOSTYPE_WinNT_x64;
1147 else
1148 {
1149 vrc = RTVfsFileOpen(hVfsIso, "I386/HIVESYS.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1150 if (RT_SUCCESS(vrc))
1151 mEnmOsType = VBOXOSTYPE_WinNT;
1152 }
1153 if (RT_SUCCESS(vrc))
1154 {
1155 RTINIFILE hIniFile;
1156 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1157 RTVfsFileRelease(hVfsFile);
1158 if (RT_SUCCESS(vrc))
1159 {
1160 vrc = RTIniFileQueryValue(hIniFile, "Version", "DriverVer", pBuf->sz, sizeof(*pBuf), NULL);
1161 if (RT_SUCCESS(vrc))
1162 {
1163 LogRelFlow(("Unattended: HIVESYS.INF: DriverVer=%s\n", pBuf->sz));
1164 const char *psz = strchr(pBuf->sz, ',');
1165 psz = psz ? psz + 1 : pBuf->sz;
1166 if (RTStrVersionCompare(psz, "6.0.0") >= 0)
1167 LogRel(("Unattended: HIVESYS.INF: unknown: DriverVer=%s\n", psz));
1168 else if (RTStrVersionCompare(psz, "5.2.0") >= 0) /* W2K3, XP64 */
1169 {
1170 fClarifyProd = true;
1171 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k3);
1172 if (RTStrVersionCompare(psz, "5.2.3790.3959") >= 0)
1173 pszVersion = "sp2";
1174 else if (RTStrVersionCompare(psz, "5.2.3790.1830") >= 0)
1175 pszVersion = "sp1";
1176 }
1177 else if (RTStrVersionCompare(psz, "5.1.0") >= 0) /* XP */
1178 {
1179 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinXP);
1180 if (RTStrVersionCompare(psz, "5.1.2600.5512") >= 0)
1181 pszVersion = "sp3";
1182 else if (RTStrVersionCompare(psz, "5.1.2600.2180") >= 0)
1183 pszVersion = "sp2";
1184 else if (RTStrVersionCompare(psz, "5.1.2600.1105") >= 0)
1185 pszVersion = "sp1";
1186 }
1187 else if (RTStrVersionCompare(psz, "5.0.0") >= 0)
1188 {
1189 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k);
1190 if (RTStrVersionCompare(psz, "5.0.2195.6717") >= 0)
1191 pszVersion = "sp4";
1192 else if (RTStrVersionCompare(psz, "5.0.2195.5438") >= 0)
1193 pszVersion = "sp3";
1194 else if (RTStrVersionCompare(psz, "5.0.2195.1620") >= 0)
1195 pszVersion = "sp1";
1196 }
1197 else
1198 LogRel(("Unattended: HIVESYS.INF: unknown: DriverVer=%s\n", psz));
1199 }
1200 RTIniFileRelease(hIniFile);
1201 }
1202 }
1203 }
1204 if (RT_FAILURE(vrc) || fClarifyProd)
1205 {
1206 /*
1207 * NT 4 and older does not have DriverVer entries, we consult the PRODSPEC.INI, which
1208 * works for NT4 & W2K. It does usually not reflect the service pack.
1209 */
1210 vrc = RTVfsFileOpen(hVfsIso, "AMD64/PRODSPEC.INI", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1211 if (RT_SUCCESS(vrc))
1212 mEnmOsType = VBOXOSTYPE_WinNT_x64;
1213 else
1214 {
1215 vrc = RTVfsFileOpen(hVfsIso, "I386/PRODSPEC.INI", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1216 if (RT_SUCCESS(vrc))
1217 mEnmOsType = VBOXOSTYPE_WinNT;
1218 }
1219 if (RT_SUCCESS(vrc))
1220 {
1221
1222 RTINIFILE hIniFile;
1223 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1224 RTVfsFileRelease(hVfsFile);
1225 if (RT_SUCCESS(vrc))
1226 {
1227 vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "Version", pBuf->sz, sizeof(*pBuf), NULL);
1228 if (RT_SUCCESS(vrc))
1229 {
1230 LogRelFlow(("Unattended: PRODSPEC.INI: Version=%s\n", pBuf->sz));
1231 if (RTStrVersionCompare(pBuf->sz, "5.1") >= 0) /* Shipped with XP + W2K3, but version stuck at 5.0. */
1232 LogRel(("Unattended: PRODSPEC.INI: unknown: DriverVer=%s\n", pBuf->sz));
1233 else if (RTStrVersionCompare(pBuf->sz, "5.0") >= 0) /* 2000 */
1234 {
1235 vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "Product", pBuf->sz, sizeof(*pBuf), NULL);
1236 if (RT_SUCCESS(vrc) && RTStrNICmp(pBuf->sz, RT_STR_TUPLE("Windows XP")) == 0)
1237 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinXP);
1238 else if (RT_SUCCESS(vrc) && RTStrNICmp(pBuf->sz, RT_STR_TUPLE("Windows Server 2003")) == 0)
1239 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k3);
1240 else
1241 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k);
1242
1243 if (RT_SUCCESS(vrc) && (strstr(pBuf->sz, "Server") || strstr(pBuf->sz, "server")))
1244 pszProduct = "Server";
1245 }
1246 else if (RTStrVersionCompare(pBuf->sz, "4.0") >= 0) /* NT4 */
1247 mEnmOsType = VBOXOSTYPE_WinNT4;
1248 else
1249 LogRel(("Unattended: PRODSPEC.INI: unknown: DriverVer=%s\n", pBuf->sz));
1250
1251 vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "ProductType", pBuf->sz, sizeof(*pBuf), NULL);
1252 if (RT_SUCCESS(vrc))
1253 pszProduct = strcmp(pBuf->sz, "0") == 0 ? "Workstation" : /* simplification: */ "Server";
1254 }
1255 RTIniFileRelease(hIniFile);
1256 }
1257 }
1258 if (fClarifyProd)
1259 vrc = VINF_SUCCESS;
1260 }
1261 if (RT_FAILURE(vrc))
1262 {
1263 /*
1264 * NT 3.x we look at the LoadIdentifier (boot manager) string in TXTSETUP.SIF/TXT.
1265 */
1266 vrc = RTVfsFileOpen(hVfsIso, "I386/TXTSETUP.SIF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1267 if (RT_FAILURE(vrc))
1268 vrc = RTVfsFileOpen(hVfsIso, "I386/TXTSETUP.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1269 if (RT_SUCCESS(vrc))
1270 {
1271 mEnmOsType = VBOXOSTYPE_WinNT;
1272
1273 RTINIFILE hIniFile;
1274 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1275 RTVfsFileRelease(hVfsFile);
1276 if (RT_SUCCESS(vrc))
1277 {
1278 vrc = RTIniFileQueryValue(hIniFile, "SetupData", "ProductType", pBuf->sz, sizeof(*pBuf), NULL);
1279 if (RT_SUCCESS(vrc))
1280 pszProduct = strcmp(pBuf->sz, "0") == 0 ? "Workstation" : /* simplification: */ "Server";
1281
1282 vrc = RTIniFileQueryValue(hIniFile, "SetupData", "LoadIdentifier", pBuf->sz, sizeof(*pBuf), NULL);
1283 if (RT_SUCCESS(vrc))
1284 {
1285 LogRelFlow(("Unattended: TXTSETUP.SIF: LoadIdentifier=%s\n", pBuf->sz));
1286 char *psz = pBuf->sz;
1287 while (!RT_C_IS_DIGIT(*psz) && *psz)
1288 psz++;
1289 char *psz2 = psz;
1290 while (RT_C_IS_DIGIT(*psz2) || *psz2 == '.')
1291 psz2++;
1292 *psz2 = '\0';
1293 if (RTStrVersionCompare(psz, "6.0") >= 0)
1294 LogRel(("Unattended: TXTSETUP.SIF: unknown: LoadIdentifier=%s\n", pBuf->sz));
1295 else if (RTStrVersionCompare(psz, "4.0") >= 0)
1296 mEnmOsType = VBOXOSTYPE_WinNT4;
1297 else if (RTStrVersionCompare(psz, "3.1") >= 0)
1298 {
1299 mEnmOsType = VBOXOSTYPE_WinNT3x;
1300 pszVersion = psz;
1301 }
1302 else
1303 LogRel(("Unattended: TXTSETUP.SIF: unknown: LoadIdentifier=%s\n", pBuf->sz));
1304 }
1305 RTIniFileRelease(hIniFile);
1306 }
1307 }
1308 }
1309
1310 if (pszVersion)
1311 try { mStrDetectedOSVersion = pszVersion; }
1312 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1313 if (pszProduct)
1314 try { mStrDetectedOSFlavor = pszProduct; }
1315 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1316
1317 /*
1318 * Look for sources/lang.ini and try parse it to get the languages out of it.
1319 */
1320 /** @todo We could also check sources/??-* and boot/??-* if lang.ini is not
1321 * found or unhelpful. */
1322 vrc = RTVfsFileOpen(hVfsIso, "sources/lang.ini", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1323 if (RT_SUCCESS(vrc))
1324 {
1325 RTINIFILE hIniFile;
1326 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1327 RTVfsFileRelease(hVfsFile);
1328 if (RT_SUCCESS(vrc))
1329 {
1330 mDetectedOSLanguages.clear();
1331
1332 uint32_t idxPair;
1333 for (idxPair = 0; idxPair < 256; idxPair++)
1334 {
1335 size_t cbHalf = sizeof(*pBuf) / 2;
1336 char *pszKey = pBuf->sz;
1337 char *pszValue = &pBuf->sz[cbHalf];
1338 vrc = RTIniFileQueryPair(hIniFile, "Available UI Languages", idxPair,
1339 pszKey, cbHalf, NULL, pszValue, cbHalf, NULL);
1340 if (RT_SUCCESS(vrc))
1341 {
1342 try
1343 {
1344 mDetectedOSLanguages.append(pszKey);
1345 }
1346 catch (std::bad_alloc &)
1347 {
1348 RTIniFileRelease(hIniFile);
1349 return E_OUTOFMEMORY;
1350 }
1351 }
1352 else if (vrc == VERR_NOT_FOUND)
1353 break;
1354 else
1355 Assert(vrc == VERR_BUFFER_OVERFLOW);
1356 }
1357 if (idxPair == 0)
1358 LogRel(("Unattended: Warning! Empty 'Available UI Languages' section in sources/lang.ini\n"));
1359 RTIniFileRelease(hIniFile);
1360 }
1361 }
1362
1363 return S_FALSE;
1364}
1365
1366/**
1367 * Architecture strings for Linux and the like.
1368 */
1369static struct { const char *pszArch; uint32_t cchArch; VBOXOSTYPE fArch; } const g_aLinuxArches[] =
1370{
1371 { RT_STR_TUPLE("amd64"), VBOXOSTYPE_x64 },
1372 { RT_STR_TUPLE("x86_64"), VBOXOSTYPE_x64 },
1373 { RT_STR_TUPLE("x86-64"), VBOXOSTYPE_x64 }, /* just in case */
1374 { RT_STR_TUPLE("x64"), VBOXOSTYPE_x64 }, /* ditto */
1375
1376 { RT_STR_TUPLE("arm"), VBOXOSTYPE_arm64 },
1377 { RT_STR_TUPLE("arm64"), VBOXOSTYPE_arm64 },
1378 { RT_STR_TUPLE("arm-64"), VBOXOSTYPE_arm64 },
1379 { RT_STR_TUPLE("arm_64"), VBOXOSTYPE_arm64 },
1380 { RT_STR_TUPLE("aarch64"), VBOXOSTYPE_arm64 }, /* mostly RHEL. */
1381
1382 { RT_STR_TUPLE("arm32"), VBOXOSTYPE_arm32 },
1383 { RT_STR_TUPLE("arm-32"), VBOXOSTYPE_arm32 },
1384 { RT_STR_TUPLE("arm_32"), VBOXOSTYPE_arm32 },
1385 { RT_STR_TUPLE("armel"), VBOXOSTYPE_arm32 }, /* mostly Debians. */
1386
1387 { RT_STR_TUPLE("x86"), VBOXOSTYPE_x86 },
1388 { RT_STR_TUPLE("i386"), VBOXOSTYPE_x86 },
1389 { RT_STR_TUPLE("i486"), VBOXOSTYPE_x86 },
1390 { RT_STR_TUPLE("i586"), VBOXOSTYPE_x86 },
1391 { RT_STR_TUPLE("i686"), VBOXOSTYPE_x86 },
1392 { RT_STR_TUPLE("i786"), VBOXOSTYPE_x86 },
1393 { RT_STR_TUPLE("i886"), VBOXOSTYPE_x86 },
1394 { RT_STR_TUPLE("i986"), VBOXOSTYPE_x86 },
1395};
1396
1397/**
1398 * Detects linux architecture.
1399 *
1400 * @returns true if detected, false if not.
1401 * @param pszArch The architecture string.
1402 * @param penmOsType Where to return the arch and type on success.
1403 * @param enmBaseOsType The base (x86) OS type to return.
1404 */
1405static bool detectLinuxArch(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType)
1406{
1407 for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++)
1408 if (RTStrNICmp(pszArch, g_aLinuxArches[i].pszArch, g_aLinuxArches[i].cchArch) == 0)
1409 {
1410 *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch);
1411 return true;
1412 }
1413 /** @todo check for 'noarch' since source CDs have been seen to use that. */
1414 return false;
1415}
1416
1417/**
1418 * Detects linux architecture by searching for the architecture substring in @p pszArch.
1419 *
1420 * @returns true if detected, false if not.
1421 * @param pszArch The architecture string.
1422 * @param penmOsType Where to return the arch and type on success.
1423 * @param enmBaseOsType The base (x86) OS type to return.
1424 * @param ppszHit Where to return the pointer to the architecture
1425 * specifier. Optional.
1426 * @param ppszNext Where to return the pointer to the char
1427 * following the architecuture specifier. Optional.
1428 */
1429static bool detectLinuxArchII(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType,
1430 char **ppszHit = NULL, char **ppszNext = NULL)
1431{
1432 for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++)
1433 {
1434 const char *pszHit = RTStrIStr(pszArch, g_aLinuxArches[i].pszArch);
1435 if (pszHit != NULL)
1436 {
1437 if (ppszHit)
1438 *ppszHit = (char *)pszHit;
1439 if (ppszNext)
1440 *ppszNext = (char *)pszHit + g_aLinuxArches[i].cchArch;
1441 *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch);
1442 return true;
1443 }
1444 }
1445 return false;
1446}
1447
1448static bool detectLinuxDistroName(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext)
1449{
1450 bool fRet = true;
1451
1452 if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Red")) == 0
1453 && !RT_C_IS_ALNUM(pszOsAndVersion[3]))
1454
1455 {
1456 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3);
1457 if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Hat")) == 0
1458 && !RT_C_IS_ALNUM(pszOsAndVersion[3]))
1459 {
1460 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1461 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3);
1462 }
1463 else
1464 fRet = false;
1465 }
1466 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("OpenSUSE")) == 0
1467 && !RT_C_IS_ALNUM(pszOsAndVersion[8]))
1468 {
1469 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_OpenSUSE);
1470 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 8);
1471 }
1472 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Oracle")) == 0
1473 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1474 {
1475 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle);
1476 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1477 }
1478 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("CentOS")) == 0
1479 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1480 {
1481 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1482 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1483 }
1484 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Fedora")) == 0
1485 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1486 {
1487 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore);
1488 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1489 }
1490 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Ubuntu")) == 0
1491 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1492 {
1493 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1494 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1495 }
1496 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Linux Mint")) == 0
1497 && !RT_C_IS_ALNUM(pszOsAndVersion[10]))
1498 {
1499 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1500 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 10);
1501 }
1502 else if ( ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Xubuntu")) == 0
1503 || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Kubuntu")) == 0
1504 || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Lubuntu")) == 0)
1505 && !RT_C_IS_ALNUM(pszOsAndVersion[7]))
1506 {
1507 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1508 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 7);
1509 }
1510 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Debian")) == 0
1511 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1512 {
1513 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian);
1514 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1515 }
1516 else
1517 fRet = false;
1518
1519 /*
1520 * Skip forward till we get a number.
1521 */
1522 if (ppszNext)
1523 {
1524 *ppszNext = pszOsAndVersion;
1525 char ch;
1526 for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
1527 if (RT_C_IS_DIGIT(ch))
1528 {
1529 *ppszNext = pszVersion;
1530 break;
1531 }
1532 }
1533 return fRet;
1534}
1535
1536static bool detectLinuxDistroNameII(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext)
1537{
1538 bool fRet = true;
1539 if ( RTStrIStr(pszOsAndVersion, "RedHat") != NULL
1540 || RTStrIStr(pszOsAndVersion, "Red Hat") != NULL)
1541 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1542 else if (RTStrIStr(pszOsAndVersion, "Oracle") != NULL)
1543 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle);
1544 else if (RTStrIStr(pszOsAndVersion, "CentOS") != NULL)
1545 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1546 else if (RTStrIStr(pszOsAndVersion, "Fedora") != NULL)
1547 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore);
1548 else if (RTStrIStr(pszOsAndVersion, "Ubuntu") != NULL)
1549 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1550 else if (RTStrIStr(pszOsAndVersion, "Mint") != NULL)
1551 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1552 else if (RTStrIStr(pszOsAndVersion, "Debian"))
1553 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian);
1554 else
1555 fRet = false;
1556
1557 /*
1558 * Skip forward till we get a number.
1559 */
1560 if (ppszNext)
1561 {
1562 *ppszNext = pszOsAndVersion;
1563 char ch;
1564 for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
1565 if (RT_C_IS_DIGIT(ch))
1566 {
1567 *ppszNext = pszVersion;
1568 break;
1569 }
1570 }
1571 return fRet;
1572}
1573
1574
1575/**
1576 * Helps detecting linux distro flavor by finding substring position of non numerical
1577 * part of the disk name.
1578 *
1579 * @returns true if detected, false if not.
1580 * @param pszDiskName Name of the disk as it is read from .disk/info or
1581 * README.diskdefines file.
1582 * @param poffVersion String position where first numerical character is
1583 * found. We use substring upto this position as OS flavor
1584 */
1585static bool detectLinuxDistroFlavor(const char *pszDiskName, size_t *poffVersion)
1586{
1587 Assert(poffVersion);
1588 if (!pszDiskName)
1589 return false;
1590 char ch;
1591 while ((ch = *pszDiskName) != '\0' && !RT_C_IS_DIGIT(ch))
1592 {
1593 ++pszDiskName;
1594 *poffVersion += 1;
1595 }
1596 return true;
1597}
1598
1599/**
1600 * Detect Linux distro ISOs.
1601 *
1602 * @returns COM status code.
1603 * @retval S_OK if detected
1604 * @retval S_FALSE if not fully detected.
1605 *
1606 * @param hVfsIso The ISO file system.
1607 * @param pBuf Read buffer.
1608 */
1609HRESULT Unattended::i_innerDetectIsoOSLinux(RTVFS hVfsIso, DETECTBUFFER *pBuf)
1610{
1611 /*
1612 * Redhat and derivatives may have a .treeinfo (ini-file style) with useful info
1613 * or at least a barebone .discinfo file.
1614 */
1615
1616 /*
1617 * Start with .treeinfo: https://release-engineering.github.io/productmd/treeinfo-1.0.html
1618 */
1619 RTVFSFILE hVfsFile;
1620 int vrc = RTVfsFileOpen(hVfsIso, ".treeinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1621 if (RT_SUCCESS(vrc))
1622 {
1623 RTINIFILE hIniFile;
1624 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1625 RTVfsFileRelease(hVfsFile);
1626 if (RT_SUCCESS(vrc))
1627 {
1628 /* Try figure the architecture first (like with windows). */
1629 vrc = RTIniFileQueryValue(hIniFile, "tree", "arch", pBuf->sz, sizeof(*pBuf), NULL);
1630 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1631 vrc = RTIniFileQueryValue(hIniFile, "general", "arch", pBuf->sz, sizeof(*pBuf), NULL);
1632 if (RT_FAILURE(vrc))
1633 LogRel(("Unattended: .treeinfo: No 'arch' property.\n"));
1634 else
1635 {
1636 LogRelFlow(("Unattended: .treeinfo: arch=%s\n", pBuf->sz));
1637 if (detectLinuxArch(pBuf->sz, &mEnmOsType, VBOXOSTYPE_RedHat))
1638 {
1639 /* Try figure the release name, it doesn't have to be redhat. */
1640 vrc = RTIniFileQueryValue(hIniFile, "release", "name", pBuf->sz, sizeof(*pBuf), NULL);
1641 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1642 vrc = RTIniFileQueryValue(hIniFile, "product", "name", pBuf->sz, sizeof(*pBuf), NULL);
1643 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1644 vrc = RTIniFileQueryValue(hIniFile, "general", "family", pBuf->sz, sizeof(*pBuf), NULL);
1645 if (RT_SUCCESS(vrc))
1646 {
1647 LogRelFlow(("Unattended: .treeinfo: name/family=%s\n", pBuf->sz));
1648 if (!detectLinuxDistroName(pBuf->sz, &mEnmOsType, NULL))
1649 {
1650 LogRel(("Unattended: .treeinfo: Unknown: name/family='%s', assuming Red Hat\n", pBuf->sz));
1651 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1652 }
1653 }
1654
1655 /* Try figure the version. */
1656 vrc = RTIniFileQueryValue(hIniFile, "release", "version", pBuf->sz, sizeof(*pBuf), NULL);
1657 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1658 vrc = RTIniFileQueryValue(hIniFile, "product", "version", pBuf->sz, sizeof(*pBuf), NULL);
1659 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1660 vrc = RTIniFileQueryValue(hIniFile, "general", "version", pBuf->sz, sizeof(*pBuf), NULL);
1661 if (RT_SUCCESS(vrc))
1662 {
1663 LogRelFlow(("Unattended: .treeinfo: version=%s\n", pBuf->sz));
1664 try { mStrDetectedOSVersion = RTStrStrip(pBuf->sz); }
1665 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1666
1667 size_t cchVersionPosition = 0;
1668 if (detectLinuxDistroFlavor(pBuf->sz, &cchVersionPosition))
1669 {
1670 try { mStrDetectedOSFlavor = Utf8Str(pBuf->sz, cchVersionPosition); }
1671 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1672 }
1673 }
1674 }
1675 else
1676 LogRel(("Unattended: .treeinfo: Unknown: arch='%s'\n", pBuf->sz));
1677 }
1678
1679 RTIniFileRelease(hIniFile);
1680 }
1681
1682 if (mEnmOsType != VBOXOSTYPE_Unknown)
1683 return S_FALSE;
1684 }
1685
1686 /*
1687 * Try .discinfo next: https://release-engineering.github.io/productmd/discinfo-1.0.html
1688 * We will probably need additional info here...
1689 */
1690 vrc = RTVfsFileOpen(hVfsIso, ".discinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1691 if (RT_SUCCESS(vrc))
1692 {
1693 size_t cchIgn;
1694 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1695 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1696 RTVfsFileRelease(hVfsFile);
1697
1698 /* Parse and strip the first 5 lines. */
1699 const char *apszLines[5];
1700 char *psz = pBuf->sz;
1701 for (unsigned i = 0; i < RT_ELEMENTS(apszLines); i++)
1702 {
1703 apszLines[i] = psz;
1704 if (*psz)
1705 {
1706 char *pszEol = (char *)strchr(psz, '\n');
1707 if (!pszEol)
1708 psz = strchr(psz, '\0');
1709 else
1710 {
1711 *pszEol = '\0';
1712 apszLines[i] = RTStrStrip(psz);
1713 psz = pszEol + 1;
1714 }
1715 }
1716 }
1717
1718 /* Do we recognize the architecture? */
1719 LogRelFlow(("Unattended: .discinfo: arch=%s\n", apszLines[2]));
1720 if (detectLinuxArch(apszLines[2], &mEnmOsType, VBOXOSTYPE_RedHat))
1721 {
1722 /* Do we recognize the release string? */
1723 LogRelFlow(("Unattended: .discinfo: product+version=%s\n", apszLines[1]));
1724 const char *pszVersion = NULL;
1725 if (!detectLinuxDistroName(apszLines[1], &mEnmOsType, &pszVersion))
1726 LogRel(("Unattended: .discinfo: Unknown: release='%s'\n", apszLines[1]));
1727
1728 if (*pszVersion)
1729 {
1730 LogRelFlow(("Unattended: .discinfo: version=%s\n", pszVersion));
1731 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1732 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1733
1734 /* CentOS likes to call their release 'Final' without mentioning the actual version
1735 number (e.g. CentOS-4.7-x86_64-binDVD.iso), so we need to go look elsewhere.
1736 This is only important for centos 4.x and 3.x releases. */
1737 if (RTStrNICmp(pszVersion, RT_STR_TUPLE("Final")) == 0)
1738 {
1739 static const char * const s_apszDirs[] = { "CentOS/RPMS/", "RedHat/RPMS", "Server", "Workstation" };
1740 for (unsigned iDir = 0; iDir < RT_ELEMENTS(s_apszDirs); iDir++)
1741 {
1742 RTVFSDIR hVfsDir;
1743 vrc = RTVfsDirOpen(hVfsIso, s_apszDirs[iDir], 0, &hVfsDir);
1744 if (RT_FAILURE(vrc))
1745 continue;
1746 char szRpmDb[128];
1747 char szReleaseRpm[128];
1748 szRpmDb[0] = '\0';
1749 szReleaseRpm[0] = '\0';
1750 for (;;)
1751 {
1752 RTDIRENTRYEX DirEntry;
1753 size_t cbDirEntry = sizeof(DirEntry);
1754 vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING);
1755 if (RT_FAILURE(vrc))
1756 break;
1757
1758 /* redhat-release-4WS-2.4.i386.rpm
1759 centos-release-4-7.x86_64.rpm, centos-release-4-4.3.i386.rpm
1760 centos-release-5-3.el5.centos.1.x86_64.rpm */
1761 if ( (psz = strstr(DirEntry.szName, "-release-")) != NULL
1762 || (psz = strstr(DirEntry.szName, "-RELEASE-")) != NULL)
1763 {
1764 psz += 9;
1765 if (RT_C_IS_DIGIT(*psz))
1766 RTStrCopy(szReleaseRpm, sizeof(szReleaseRpm), psz);
1767 }
1768 /* rpmdb-redhat-4WS-2.4.i386.rpm,
1769 rpmdb-CentOS-4.5-0.20070506.i386.rpm,
1770 rpmdb-redhat-3.9-0.20070703.i386.rpm. */
1771 else if ( ( RTStrStartsWith(DirEntry.szName, "rpmdb-")
1772 || RTStrStartsWith(DirEntry.szName, "RPMDB-"))
1773 && RT_C_IS_DIGIT(DirEntry.szName[6]) )
1774 RTStrCopy(szRpmDb, sizeof(szRpmDb), &DirEntry.szName[6]);
1775 }
1776 RTVfsDirRelease(hVfsDir);
1777
1778 /* Did we find anything relvant? */
1779 psz = szRpmDb;
1780 if (!RT_C_IS_DIGIT(*psz))
1781 psz = szReleaseRpm;
1782 if (RT_C_IS_DIGIT(*psz))
1783 {
1784 /* Convert '-' to '.' and strip stuff which doesn't look like a version string. */
1785 char *pszCur = psz + 1;
1786 for (char ch = *pszCur; ch != '\0'; ch = *++pszCur)
1787 if (ch == '-')
1788 *pszCur = '.';
1789 else if (ch != '.' && !RT_C_IS_DIGIT(ch))
1790 {
1791 *pszCur = '\0';
1792 break;
1793 }
1794 while (&pszCur[-1] != psz && pszCur[-1] == '.')
1795 *--pszCur = '\0';
1796
1797 /* Set it and stop looking. */
1798 try { mStrDetectedOSVersion = psz; }
1799 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1800 break;
1801 }
1802 }
1803 }
1804 }
1805 size_t cchVersionPosition = 0;
1806 if (detectLinuxDistroFlavor(apszLines[1], &cchVersionPosition))
1807 {
1808 try { mStrDetectedOSFlavor = Utf8Str(apszLines[1], cchVersionPosition); }
1809 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1810 }
1811 }
1812 else
1813 LogRel(("Unattended: .discinfo: Unknown: arch='%s'\n", apszLines[2]));
1814
1815 if (mEnmOsType != VBOXOSTYPE_Unknown)
1816 return S_FALSE;
1817 }
1818
1819 /*
1820 * Ubuntu has a README.diskdefines file on their ISO (already on 4.10 / warty warthog).
1821 * Example content:
1822 * #define DISKNAME Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1
1823 * #define TYPE binary
1824 * #define TYPEbinary 1
1825 * #define ARCH amd64
1826 * #define ARCHamd64 1
1827 * #define DISKNUM 1
1828 * #define DISKNUM1 1
1829 * #define TOTALNUM 1
1830 * #define TOTALNUM1 1
1831 */
1832 vrc = RTVfsFileOpen(hVfsIso, "README.diskdefines", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1833 if (RT_SUCCESS(vrc))
1834 {
1835 size_t cchIgn;
1836 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1837 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1838 RTVfsFileRelease(hVfsFile);
1839
1840 /* Find the DISKNAME and ARCH defines. */
1841 const char *pszDiskName = NULL;
1842 const char *pszArch = NULL;
1843 char *psz = pBuf->sz;
1844 while (*psz != '\0')
1845 {
1846 while (RT_C_IS_BLANK(*psz))
1847 psz++;
1848
1849 /* Match #define: */
1850 static const char s_szDefine[] = "#define";
1851 if ( strncmp(psz, s_szDefine, sizeof(s_szDefine) - 1) == 0
1852 && RT_C_IS_BLANK(psz[sizeof(s_szDefine) - 1]))
1853 {
1854 psz = &psz[sizeof(s_szDefine) - 1];
1855 while (RT_C_IS_BLANK(*psz))
1856 psz++;
1857
1858 /* Match the identifier: */
1859 char *pszIdentifier = psz;
1860 if (RT_C_IS_ALPHA(*psz) || *psz == '_')
1861 {
1862 do
1863 psz++;
1864 while (RT_C_IS_ALNUM(*psz) || *psz == '_');
1865 size_t cchIdentifier = (size_t)(psz - pszIdentifier);
1866
1867 /* Skip to the value. */
1868 while (RT_C_IS_BLANK(*psz))
1869 psz++;
1870 char *pszValue = psz;
1871
1872 /* Skip to EOL and strip the value. */
1873 char *pszEol = psz = strchr(psz, '\n');
1874 if (psz)
1875 *psz++ = '\0';
1876 else
1877 pszEol = strchr(pszValue, '\0');
1878 while (pszEol > pszValue && RT_C_IS_SPACE(pszEol[-1]))
1879 *--pszEol = '\0';
1880
1881 LogRelFlow(("Unattended: README.diskdefines: %.*s=%s\n", cchIdentifier, pszIdentifier, pszValue));
1882
1883 /* Do identifier matching: */
1884 if (cchIdentifier == sizeof("DISKNAME") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("DISKNAME")) == 0)
1885 pszDiskName = pszValue;
1886 else if (cchIdentifier == sizeof("ARCH") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("ARCH")) == 0)
1887 pszArch = pszValue;
1888 else
1889 continue;
1890 if (pszDiskName == NULL || pszArch == NULL)
1891 continue;
1892 break;
1893 }
1894 }
1895
1896 /* Next line: */
1897 psz = strchr(psz, '\n');
1898 if (!psz)
1899 break;
1900 psz++;
1901 }
1902
1903 /* Did we find both of them? */
1904 if (pszDiskName && pszArch)
1905 {
1906 if (detectLinuxArch(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1907 {
1908 const char *pszVersion = NULL;
1909 if (detectLinuxDistroName(pszDiskName, &mEnmOsType, &pszVersion))
1910 {
1911 LogRelFlow(("Unattended: README.diskdefines: version=%s\n", pszVersion));
1912 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1913 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1914
1915 size_t cchVersionPosition = 0;
1916 if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition))
1917 {
1918 try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); }
1919 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1920 }
1921 }
1922 else
1923 LogRel(("Unattended: README.diskdefines: Unknown: diskname='%s'\n", pszDiskName));
1924 }
1925 else
1926 LogRel(("Unattended: README.diskdefines: Unknown: arch='%s'\n", pszArch));
1927 }
1928 else
1929 LogRel(("Unattended: README.diskdefines: Did not find both DISKNAME and ARCH. :-/\n"));
1930
1931 if (mEnmOsType != VBOXOSTYPE_Unknown)
1932 return S_FALSE;
1933 }
1934
1935 /*
1936 * All of the debian based distro versions I checked have a single line ./disk/info
1937 * file. Only info I could find related to .disk folder is:
1938 * https://lists.debian.org/debian-cd/2004/01/msg00069.html
1939 *
1940 * Some example content from several install ISOs is as follows:
1941 * Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1 (20041020)
1942 * Linux Mint 20.3 "Una" - Release amd64 20220104
1943 * Debian GNU/Linux 11.2.0 "Bullseye" - Official amd64 NETINST 20211218-11:12
1944 * Debian GNU/Linux 9.13.0 "Stretch" - Official amd64 DVD Binary-1 20200718-11:07
1945 * Xubuntu 20.04.2.0 LTS "Focal Fossa" - Release amd64 (20210209.1)
1946 * Ubuntu 17.10 "Artful Aardvark" - Release amd64 (20180105.1)
1947 * Ubuntu 16.04.6 LTS "Xenial Xerus" - Release i386 (20190227.1)
1948 * Debian GNU/Linux 8.11.1 "Jessie" - Official amd64 CD Binary-1 20190211-02:10
1949 * Kali GNU/Linux 2021.3a "Kali-last-snapshot" - Official amd64 BD Binary-1 with firmware 20211015-16:55
1950 * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
1951 * Ubuntu 23.10.1 "Mantic Minotaur" - Release amd64 (20231016.1)
1952 * Ubuntu-Server 22.04.3 LTS "Jammy Jellyfish" - Release amd64 (20230810)
1953 */
1954 vrc = RTVfsFileOpen(hVfsIso, ".disk/info", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1955 if (RT_SUCCESS(vrc))
1956 {
1957 size_t cchIgn;
1958 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1959 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1960
1961 pBuf->sz[sizeof(*pBuf) - 1] = '\0';
1962 RTVfsFileRelease(hVfsFile);
1963
1964 char *psz = pBuf->sz;
1965 char *pszDiskName = psz;
1966 char *pszArch = NULL;
1967
1968 /* Only care about the first line of the file even if it is multi line and assume disk name ended with ' - '.*/
1969 psz = RTStrStr(pBuf->sz, " - ");
1970 if (psz && memchr(pBuf->sz, '\n', (size_t)(psz - pBuf->sz)) == NULL)
1971 {
1972 *psz = '\0';
1973 psz += 3;
1974 if (*psz)
1975 pszArch = psz;
1976 }
1977
1978 /* Some Debian Live ISO's have info file content as follows:
1979 * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
1980 * thus pszArch stays empty. Try Volume Id (label) if we get lucky and get architecture from that. */
1981 if (!pszArch)
1982 {
1983 char szVolumeId[128];
1984 vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
1985 if (RT_SUCCESS(vrc))
1986 {
1987 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1988 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", szVolumeId));
1989 }
1990 else
1991 LogRel(("Unattended: .disk/info No Volume Label found\n"));
1992 }
1993 else
1994 {
1995 if (!detectLinuxArchII(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1996 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", pszArch));
1997 }
1998
1999 if (pszDiskName)
2000 {
2001 const char *pszVersion = NULL;
2002 if (detectLinuxDistroNameII(pszDiskName, &mEnmOsType, &pszVersion))
2003 {
2004 LogRelFlow(("Unattended: .disk/info: version=%s\n", pszVersion));
2005 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
2006 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2007
2008 size_t cchVersionPosition = 0;
2009 if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition))
2010 {
2011 try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); }
2012 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2013 }
2014 }
2015 else
2016 LogRel(("Unattended: .disk/info: Unknown: diskname='%s'\n", pszDiskName));
2017 }
2018
2019 if (mEnmOsType == VBOXOSTYPE_Unknown)
2020 LogRel(("Unattended: .disk/info: Did not find DISKNAME or/and ARCH. :-/\n"));
2021 else
2022 return S_FALSE;
2023 }
2024
2025 /*
2026 * Fedora live iso should be recognizable from the primary volume ID (the
2027 * joliet one is usually truncated). We set fAlternative = true here to
2028 * get the primary volume ID.
2029 */
2030 char szVolumeId[128];
2031 vrc = RTVfsQueryLabel(hVfsIso, true /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
2032 if (RT_SUCCESS(vrc) && RTStrStartsWith(szVolumeId, "Fedora-"))
2033 return i_innerDetectIsoOSLinuxFedora(hVfsIso, pBuf, &szVolumeId[sizeof("Fedora-") - 1]);
2034 return S_FALSE;
2035}
2036
2037
2038/**
2039 * Continues working a Fedora ISO image after the caller found a "Fedora-*"
2040 * volume ID.
2041 *
2042 * Sample Volume IDs:
2043 * - Fedora-WS-Live-34-1-2 (joliet: Fedora-WS-Live-3)
2044 * - Fedora-S-dvd-x86_64-34 (joliet: Fedora-S-dvd-x86)
2045 * - Fedora-WS-dvd-i386-25 (joliet: Fedora-WS-dvd-i3)
2046 */
2047HRESULT Unattended::i_innerDetectIsoOSLinuxFedora(RTVFS hVfsIso, DETECTBUFFER *pBuf, char *pszVolId)
2048{
2049 char * const pszFlavor = pszVolId;
2050 char * psz = pszVolId;
2051
2052 /* The volume id may or may not include an arch, component.
2053 We ASSUME that it includes a numeric part with the version, or at least
2054 part of it. */
2055 char *pszVersion = NULL;
2056 char *pszArch = NULL;
2057 if (detectLinuxArchII(psz, &mEnmOsType, VBOXOSTYPE_FedoraCore, &pszArch, &pszVersion))
2058 {
2059 while (*pszVersion == '-')
2060 pszVersion++;
2061 *pszArch = '\0';
2062 }
2063 else
2064 {
2065 mEnmOsType = (VBOXOSTYPE)(VBOXOSTYPE_FedoraCore | VBOXOSTYPE_UnknownArch);
2066
2067 char ch;
2068 while ((ch = *psz) != '\0' && (!RT_C_IS_DIGIT(ch) || !RT_C_IS_PUNCT(psz[-1])))
2069 psz++;
2070 if (ch != '\0')
2071 pszVersion = psz;
2072 }
2073
2074 /*
2075 * Replace '-' with '.' in the version part and use it as the version.
2076 */
2077 if (pszVersion)
2078 {
2079 psz = pszVersion;
2080 while ((psz = strchr(psz, '-')) != NULL)
2081 *psz++ = '.';
2082 try { mStrDetectedOSVersion = RTStrStrip(pszVersion); }
2083 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2084
2085 *pszVersion = '\0'; /* don't include in flavor */
2086 }
2087
2088 /*
2089 * Split up the pre-arch/version bits into words and use them as the flavor.
2090 */
2091 psz = pszFlavor;
2092 while ((psz = strchr(psz, '-')) != NULL)
2093 *psz++ = ' ';
2094 try { mStrDetectedOSFlavor = RTStrStrip(pszFlavor); }
2095 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2096
2097 /*
2098 * If we don't have an architecture, we look at the vmlinuz file as the x86
2099 * and AMD64 versions starts with a MZ+PE header giving the architecture.
2100 */
2101 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2102 {
2103 static const char * const s_apszVmLinuz[] = { "images/pxeboot/vmlinuz", "isolinux/vmlinuz" };
2104 for (size_t i = 0; i < RT_ELEMENTS(s_apszVmLinuz); i++)
2105 {
2106 RTVFSFILE hVfsFileLinuz = NIL_RTVFSFILE;
2107 int vrc = RTVfsFileOpen(hVfsIso, s_apszVmLinuz[i], RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
2108 &hVfsFileLinuz);
2109 if (RT_SUCCESS(vrc))
2110 {
2111 /* DOS signature: */
2112 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pBuf->ab[0];
2113 AssertCompile(sizeof(*pBuf) > sizeof(*pDosHdr));
2114 vrc = RTVfsFileReadAt(hVfsFileLinuz, 0, pDosHdr, sizeof(*pDosHdr), NULL);
2115 if (RT_SUCCESS(vrc) && pDosHdr->e_magic == IMAGE_DOS_SIGNATURE)
2116 {
2117 /* NT signature - only need magic + file header, so use the 64 version for better debugging: */
2118 PIMAGE_NT_HEADERS64 pNtHdrs = (PIMAGE_NT_HEADERS64)&pBuf->ab[0];
2119 vrc = RTVfsFileReadAt(hVfsFileLinuz, pDosHdr->e_lfanew, pNtHdrs, sizeof(*pNtHdrs), NULL);
2120 AssertCompile(sizeof(*pBuf) > sizeof(*pNtHdrs));
2121 if (RT_SUCCESS(vrc) && pNtHdrs->Signature == IMAGE_NT_SIGNATURE)
2122 {
2123 if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
2124 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x86);
2125 else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
2126 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x64);
2127 else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM64)
2128 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_arm64);
2129 else
2130 AssertFailed();
2131 }
2132 }
2133
2134 RTVfsFileRelease(hVfsFileLinuz);
2135 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch)
2136 break;
2137 }
2138 }
2139 }
2140
2141 /*
2142 * If that failed, look for other files that gives away the arch.
2143 */
2144 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2145 {
2146 static struct { const char *pszFile; VBOXOSTYPE fArch; } const s_aArchSpecificFiles[] =
2147 {
2148 { "EFI/BOOT/grubaa64.efi", VBOXOSTYPE_arm64 },
2149 { "EFI/BOOT/BOOTAA64.EFI", VBOXOSTYPE_arm64 },
2150 };
2151 PRTFSOBJINFO pObjInfo = (PRTFSOBJINFO)&pBuf->ab[0];
2152 AssertCompile(sizeof(*pBuf) > sizeof(*pObjInfo));
2153 for (size_t i = 0; i < RT_ELEMENTS(s_aArchSpecificFiles); i++)
2154 {
2155 int vrc = RTVfsQueryPathInfo(hVfsIso, s_aArchSpecificFiles[i].pszFile, pObjInfo,
2156 RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2157 if (RT_SUCCESS(vrc) && RTFS_IS_FILE(pObjInfo->Attr.fMode))
2158 {
2159 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | s_aArchSpecificFiles[i].fArch);
2160 break;
2161 }
2162 }
2163 }
2164
2165 /*
2166 * If we like, we could parse grub.conf to look for fullly spelled out
2167 * flavor, though the menu items typically only contains the major version
2168 * number, so little else to add, really.
2169 */
2170
2171 return (mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch ? S_OK : S_FALSE;
2172}
2173
2174
2175/**
2176 * Detect OS/2 installation ISOs.
2177 *
2178 * Mainly aiming at ACP2/MCP2 as that's what we currently use in our testing.
2179 *
2180 * @returns COM status code.
2181 * @retval S_OK if detected
2182 * @retval S_FALSE if not fully detected.
2183 *
2184 * @param hVfsIso The ISO file system.
2185 * @param pBuf Read buffer.
2186 */
2187HRESULT Unattended::i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2188{
2189 /*
2190 * The OS2SE20.SRC contains the location of the tree with the diskette
2191 * images, typically "\OS2IMAGE".
2192 */
2193 RTVFSFILE hVfsFile;
2194 int vrc = RTVfsFileOpen(hVfsIso, "OS2SE20.SRC", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2195 if (RT_SUCCESS(vrc))
2196 {
2197 size_t cbRead = 0;
2198 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2199 RTVfsFileRelease(hVfsFile);
2200 if (RT_SUCCESS(vrc))
2201 {
2202 pBuf->sz[cbRead] = '\0';
2203 RTStrStrip(pBuf->sz);
2204 vrc = RTStrValidateEncoding(pBuf->sz);
2205 if (RT_SUCCESS(vrc))
2206 LogRelFlow(("Unattended: OS2SE20.SRC=%s\n", pBuf->sz));
2207 else
2208 LogRel(("Unattended: OS2SE20.SRC invalid encoding: %Rrc, %.*Rhxs\n", vrc, cbRead, pBuf->sz));
2209 }
2210 else
2211 LogRel(("Unattended: Error reading OS2SE20.SRC: %\n", vrc));
2212 }
2213 /*
2214 * ArcaOS has dropped the file, assume it's \OS2IMAGE and see if it's there.
2215 */
2216 else if (vrc == VERR_FILE_NOT_FOUND)
2217 RTStrCopy(pBuf->sz, sizeof(pBuf->sz), "\\OS2IMAGE");
2218 else
2219 return S_FALSE;
2220
2221 /*
2222 * Check that the directory directory exists and has a DISK_0 under it
2223 * with an OS2LDR on it.
2224 */
2225 size_t const cchOs2Image = strlen(pBuf->sz);
2226 vrc = RTPathAppend(pBuf->sz, sizeof(pBuf->sz), "DISK_0/OS2LDR");
2227 RTFSOBJINFO ObjInfo = {0};
2228 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2229 if (vrc == VERR_FILE_NOT_FOUND)
2230 {
2231 RTStrCat(pBuf->sz, sizeof(pBuf->sz), "."); /* eCS 2.0 image includes the dot from the 8.3 name. */
2232 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2233 }
2234 if ( RT_FAILURE(vrc)
2235 || !RTFS_IS_FILE(ObjInfo.Attr.fMode))
2236 {
2237 LogRel(("Unattended: RTVfsQueryPathInfo(, '%s' (from OS2SE20.SRC),) -> %Rrc, fMode=%#x\n",
2238 pBuf->sz, vrc, ObjInfo.Attr.fMode));
2239 return S_FALSE;
2240 }
2241
2242 /*
2243 * So, it's some kind of OS/2 2.x or later ISO alright.
2244 */
2245 mEnmOsType = VBOXOSTYPE_OS2;
2246 mStrDetectedOSHints.printf("OS2SE20.SRC=%.*s", cchOs2Image, pBuf->sz);
2247
2248 /*
2249 * ArcaOS ISOs seems to have a AOSBOOT dir on them.
2250 * This contains a ARCANOAE.FLG file with content we can use for the version:
2251 * ArcaOS 5.0.7 EN
2252 * Built 2021-12-07 18:34:34
2253 * We drop the "ArcaOS" bit, as it's covered by mEnmOsType. Then we pull up
2254 * the second line.
2255 *
2256 * Note! Yet to find a way to do unattended install of ArcaOS, as it comes
2257 * with no CD-boot floppy images, only simple .PF archive files for
2258 * unpacking onto the ram disk or whatever. Modifying these is
2259 * possible (ibsen's aPLib v0.36 compression with some simple custom
2260 * headers), but it would probably be a royal pain. Could perhaps
2261 * cook something from OS2IMAGE\DISK_0 thru 3...
2262 */
2263 vrc = RTVfsQueryPathInfo(hVfsIso, "AOSBOOT", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2264 if ( RT_SUCCESS(vrc)
2265 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2266 {
2267 mEnmOsType = VBOXOSTYPE_ArcaOS;
2268
2269 /* Read the version file: */
2270 vrc = RTVfsFileOpen(hVfsIso, "SYS/ARCANOAE.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2271 if (RT_SUCCESS(vrc))
2272 {
2273 size_t cbRead = 0;
2274 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2275 RTVfsFileRelease(hVfsFile);
2276 pBuf->sz[cbRead] = '\0';
2277 if (RT_SUCCESS(vrc))
2278 {
2279 /* Strip the OS name: */
2280 char *pszVersion = RTStrStrip(pBuf->sz);
2281 static char s_szArcaOS[] = "ArcaOS";
2282 if (RTStrStartsWith(pszVersion, s_szArcaOS))
2283 pszVersion = RTStrStripL(pszVersion + sizeof(s_szArcaOS) - 1);
2284
2285 /* Pull up the 2nd line if it, condensing the \r\n into a single space. */
2286 char *pszNewLine = strchr(pszVersion, '\n');
2287 if (pszNewLine && RTStrStartsWith(pszNewLine + 1, "Built 20"))
2288 {
2289 size_t offRemove = 0;
2290 while (RT_C_IS_SPACE(pszNewLine[-1 - (ssize_t)offRemove]))
2291 offRemove++;
2292 if (offRemove > 0)
2293 {
2294 pszNewLine -= offRemove;
2295 memmove(pszNewLine, pszNewLine + offRemove, strlen(pszNewLine + offRemove) - 1);
2296 }
2297 *pszNewLine = ' ';
2298 }
2299
2300 /* Drop any additional lines: */
2301 pszNewLine = strchr(pszVersion, '\n');
2302 if (pszNewLine)
2303 *pszNewLine = '\0';
2304 RTStrStripR(pszVersion);
2305
2306 /* Done (hope it makes some sense). */
2307 mStrDetectedOSVersion = pszVersion;
2308 }
2309 else
2310 LogRel(("Unattended: failed to read AOSBOOT/ARCANOAE.FLG: %Rrc\n", vrc));
2311 }
2312 else
2313 LogRel(("Unattended: failed to open AOSBOOT/ARCANOAE.FLG for reading: %Rrc\n", vrc));
2314 }
2315 /*
2316 * Similarly, eCS has an ECS directory and it typically contains a
2317 * ECS_INST.FLG file with the version info. Content differs a little:
2318 * eComStation 2.0 EN_US Thu May 13 10:27:54 pm 2010
2319 * Built on ECS60441318
2320 * Here we drop the "eComStation" bit and leave the 2nd line as it.
2321 *
2322 * Note! At least 2.0 has a DISKIMGS folder with what looks like boot
2323 * disks, so we could probably get something going here without
2324 * needing to write an OS2 boot sector...
2325 */
2326 else
2327 {
2328 vrc = RTVfsQueryPathInfo(hVfsIso, "ECS", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2329 if ( RT_SUCCESS(vrc)
2330 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2331 {
2332 mEnmOsType = VBOXOSTYPE_ECS;
2333
2334 /* Read the version file: */
2335 vrc = RTVfsFileOpen(hVfsIso, "ECS/ECS_INST.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2336 if (RT_SUCCESS(vrc))
2337 {
2338 size_t cbRead = 0;
2339 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2340 RTVfsFileRelease(hVfsFile);
2341 pBuf->sz[cbRead] = '\0';
2342 if (RT_SUCCESS(vrc))
2343 {
2344 /* Strip the OS name: */
2345 char *pszVersion = RTStrStrip(pBuf->sz);
2346 static char s_szECS[] = "eComStation";
2347 if (RTStrStartsWith(pszVersion, s_szECS))
2348 pszVersion = RTStrStripL(pszVersion + sizeof(s_szECS) - 1);
2349
2350 /* Drop any additional lines: */
2351 char *pszNewLine = strchr(pszVersion, '\n');
2352 if (pszNewLine)
2353 *pszNewLine = '\0';
2354 RTStrStripR(pszVersion);
2355
2356 /* Done (hope it makes some sense). */
2357 mStrDetectedOSVersion = pszVersion;
2358 }
2359 else
2360 LogRel(("Unattended: failed to read ECS/ECS_INST.FLG: %Rrc\n", vrc));
2361 }
2362 else
2363 LogRel(("Unattended: failed to open ECS/ECS_INST.FLG for reading: %Rrc\n", vrc));
2364 }
2365 else
2366 {
2367 /*
2368 * Official IBM OS/2 builds doesn't have any .FLG file on them,
2369 * so need to pry the information out in some other way. Best way
2370 * is to read the SYSLEVEL.OS2 file, which is typically on disk #2,
2371 * though on earlier versions (warp3) it was disk #1.
2372 */
2373 vrc = RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1,
2374 "/DISK_2/SYSLEVEL.OS2");
2375 if (RT_SUCCESS(vrc))
2376 {
2377 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2378 if (vrc == VERR_FILE_NOT_FOUND)
2379 {
2380 RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, "/DISK_1/SYSLEVEL.OS2");
2381 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2382 }
2383 if (RT_SUCCESS(vrc))
2384 {
2385 RT_ZERO(pBuf->ab);
2386 size_t cbRead = 0;
2387 vrc = RTVfsFileRead(hVfsFile, pBuf->ab, sizeof(pBuf->ab), &cbRead);
2388 RTVfsFileRelease(hVfsFile);
2389 if (RT_SUCCESS(vrc))
2390 {
2391 /* Check the header. */
2392 OS2SYSLEVELHDR const *pHdr = (OS2SYSLEVELHDR const *)&pBuf->ab[0];
2393 if ( pHdr->uMinusOne == UINT16_MAX
2394 && pHdr->uSyslevelFileVer == 1
2395 && memcmp(pHdr->achSignature, RT_STR_TUPLE("SYSLEVEL")) == 0
2396 && pHdr->offTable < cbRead
2397 && pHdr->offTable + sizeof(OS2SYSLEVELENTRY) <= cbRead)
2398 {
2399 OS2SYSLEVELENTRY *pEntry = (OS2SYSLEVELENTRY *)&pBuf->ab[pHdr->offTable];
2400 if ( RT_SUCCESS(RTStrValidateEncodingEx(pEntry->szName, sizeof(pEntry->szName),
2401 RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED))
2402 && RT_SUCCESS(RTStrValidateEncodingEx(pEntry->achCsdLevel, sizeof(pEntry->achCsdLevel), 0))
2403 && pEntry->bVersion != 0
2404 && ((pEntry->bVersion >> 4) & 0xf) < 10
2405 && (pEntry->bVersion & 0xf) < 10
2406 && pEntry->bModify < 10
2407 && pEntry->bRefresh < 10)
2408 {
2409 /* Flavor: */
2410 char *pszName = RTStrStrip(pEntry->szName);
2411 if (pszName)
2412 mStrDetectedOSFlavor = pszName;
2413
2414 /* Version: */
2415 if (pEntry->bRefresh != 0)
2416 mStrDetectedOSVersion.printf("%d.%d%d.%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2417 pEntry->bModify, pEntry->bRefresh);
2418 else
2419 mStrDetectedOSVersion.printf("%d.%d%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2420 pEntry->bModify);
2421 pEntry->achCsdLevel[sizeof(pEntry->achCsdLevel) - 1] = '\0';
2422 char *pszCsd = RTStrStrip(pEntry->achCsdLevel);
2423 if (*pszCsd != '\0')
2424 {
2425 mStrDetectedOSVersion.append(' ');
2426 mStrDetectedOSVersion.append(pszCsd);
2427 }
2428 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.50") >= 0)
2429 mEnmOsType = VBOXOSTYPE_OS2Warp45;
2430 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.00") >= 0)
2431 mEnmOsType = VBOXOSTYPE_OS2Warp4;
2432 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "3.00") >= 0)
2433 mEnmOsType = VBOXOSTYPE_OS2Warp3;
2434 }
2435 else
2436 LogRel(("Unattended: bogus SYSLEVEL.OS2 file entry: %.128Rhxd\n", pEntry));
2437 }
2438 else
2439 LogRel(("Unattended: bogus SYSLEVEL.OS2 file header: uMinusOne=%#x uSyslevelFileVer=%#x achSignature=%.8Rhxs offTable=%#x vs cbRead=%#zx\n",
2440 pHdr->uMinusOne, pHdr->uSyslevelFileVer, pHdr->achSignature, pHdr->offTable, cbRead));
2441 }
2442 else
2443 LogRel(("Unattended: failed to read SYSLEVEL.OS2: %Rrc\n", vrc));
2444 }
2445 else
2446 LogRel(("Unattended: failed to open '%s' for reading: %Rrc\n", pBuf->sz, vrc));
2447 }
2448 }
2449 }
2450
2451 /** @todo language detection? */
2452
2453 /*
2454 * Only tested ACP2, so only return S_OK for it.
2455 */
2456 if ( mEnmOsType == VBOXOSTYPE_OS2Warp45
2457 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.52") >= 0
2458 && mStrDetectedOSFlavor.contains("Server", RTCString::CaseInsensitive))
2459 return S_OK;
2460
2461 return S_FALSE;
2462}
2463
2464
2465/**
2466 * Detect FreeBSD distro ISOs.
2467 *
2468 * @returns COM status code.
2469 * @retval S_OK if detected
2470 * @retval S_FALSE if not fully detected.
2471 *
2472 * @param hVfsIso The ISO file system.
2473 * @param pBuf Read buffer.
2474 */
2475HRESULT Unattended::i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2476{
2477 RT_NOREF(pBuf);
2478
2479 /*
2480 * FreeBSD since 10.0 has a .profile file in the root which can be used to determine that this is FreeBSD
2481 * along with the version.
2482 */
2483
2484 RTVFSFILE hVfsFile;
2485 HRESULT hrc = S_FALSE;
2486 int vrc = RTVfsFileOpen(hVfsIso, ".profile", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2487 if (RT_SUCCESS(vrc))
2488 {
2489 static const uint8_t s_abFreeBsdHdr[] = "# $FreeBSD: releng/";
2490 char abRead[32];
2491
2492 vrc = RTVfsFileRead(hVfsFile, &abRead[0], sizeof(abRead), NULL /*pcbRead*/);
2493 if ( RT_SUCCESS(vrc)
2494 && !memcmp(&abRead[0], &s_abFreeBsdHdr[0], sizeof(s_abFreeBsdHdr) - 1)) /* Skip terminator */
2495 {
2496 abRead[sizeof(abRead) - 1] = '\0';
2497
2498 /* Detect the architecture using the volume label. */
2499 char szVolumeId[128];
2500 size_t cchVolumeId;
2501 vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, 128, &cchVolumeId);
2502 if (RT_SUCCESS(vrc))
2503 {
2504 /* Can re-use the Linux code here. */
2505 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_FreeBSD))
2506 LogRel(("Unattended/FBSD: Unknown: arch='%s'\n", szVolumeId));
2507
2508 /* Detect the version from the string coming after the needle in .profile. */
2509 AssertCompile(sizeof(s_abFreeBsdHdr) - 1 < sizeof(abRead));
2510
2511 char *pszVersionStart = &abRead[sizeof(s_abFreeBsdHdr) - 1];
2512 char *pszVersionEnd = pszVersionStart;
2513
2514 while (RT_C_IS_DIGIT(*pszVersionEnd))
2515 pszVersionEnd++;
2516 if (*pszVersionEnd == '.')
2517 {
2518 pszVersionEnd++; /* Skip the . */
2519
2520 while (RT_C_IS_DIGIT(*pszVersionEnd))
2521 pszVersionEnd++;
2522
2523 /* Terminate the version string. */
2524 *pszVersionEnd = '\0';
2525
2526 try { mStrDetectedOSVersion = pszVersionStart; }
2527 catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
2528 }
2529 else
2530 LogRel(("Unattended/FBSD: Unknown: version='%s'\n", &abRead[0]));
2531 }
2532 else
2533 {
2534 LogRel(("Unattended/FBSD: No Volume Label found\n"));
2535 mEnmOsType = VBOXOSTYPE_FreeBSD;
2536 }
2537
2538 hrc = S_OK;
2539 }
2540
2541 RTVfsFileRelease(hVfsFile);
2542 }
2543
2544 return hrc;
2545}
2546
2547
2548HRESULT Unattended::prepare()
2549{
2550 LogFlow(("Unattended::prepare: enter\n"));
2551
2552 /*
2553 * Must have a machine.
2554 */
2555 ComPtr<Machine> ptrMachine;
2556 Guid MachineUuid;
2557 {
2558 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2559 ptrMachine = mMachine;
2560 if (ptrMachine.isNull())
2561 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("No machine associated with this IUnatteded instance"));
2562 MachineUuid = mMachineUuid;
2563 }
2564
2565 /*
2566 * Before we write lock ourselves, we must get stuff from Machine and
2567 * VirtualBox because their locks have higher priorities than ours.
2568 */
2569 Utf8Str strGuestOsTypeId;
2570 Utf8Str strMachineName;
2571 Utf8Str strDefaultAuxBasePath;
2572 HRESULT hrc;
2573 try
2574 {
2575 Bstr bstrTmp;
2576 hrc = ptrMachine->COMGETTER(OSTypeId)(bstrTmp.asOutParam());
2577 if (SUCCEEDED(hrc))
2578 {
2579 strGuestOsTypeId = bstrTmp;
2580 hrc = ptrMachine->COMGETTER(Name)(bstrTmp.asOutParam());
2581 if (SUCCEEDED(hrc))
2582 strMachineName = bstrTmp;
2583 }
2584 int vrc = ptrMachine->i_calculateFullPath(Utf8StrFmt("Unattended-%RTuuid-", MachineUuid.raw()), strDefaultAuxBasePath);
2585 if (RT_FAILURE(vrc))
2586 return setErrorBoth(E_FAIL, vrc);
2587 }
2588 catch (std::bad_alloc &)
2589 {
2590 return E_OUTOFMEMORY;
2591 }
2592 bool const fIs64Bit = i_isGuestOSArchX64(strGuestOsTypeId);
2593
2594 ComPtr<IPlatform> pPlatform;
2595 hrc = ptrMachine->COMGETTER(Platform)(pPlatform.asOutParam());
2596 AssertComRCReturn(hrc, hrc);
2597
2598 BOOL fRtcUseUtc = FALSE;
2599 hrc = pPlatform->COMGETTER(RTCUseUTC)(&fRtcUseUtc);
2600 if (FAILED(hrc))
2601 return hrc;
2602
2603 ComPtr<IFirmwareSettings> pFirmwareSettings;
2604 hrc = ptrMachine->COMGETTER(FirmwareSettings)(pFirmwareSettings.asOutParam());
2605 AssertComRCReturn(hrc, hrc);
2606
2607 FirmwareType_T enmFirmware = FirmwareType_BIOS;
2608 hrc = pFirmwareSettings->COMGETTER(FirmwareType)(&enmFirmware);
2609 if (FAILED(hrc))
2610 return hrc;
2611
2612 /* Populate list of supported guest OSs in case it has not been done yet. Do this before locking. */
2613 if (!mfDoneSupportedGuestOSList)
2614 i_getListOfSupportedGuestOS();
2615
2616 /*
2617 * Write lock this object and set attributes we got from IMachine.
2618 */
2619 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2620
2621 mStrGuestOsTypeId = strGuestOsTypeId;
2622 mfGuestOs64Bit = fIs64Bit;
2623 mfRtcUseUtc = RT_BOOL(fRtcUseUtc);
2624 menmFirmwareType = enmFirmware;
2625
2626 /*
2627 * Do some state checks.
2628 */
2629 if (mpInstaller != NULL)
2630 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The prepare method has been called (must call done to restart)"));
2631 if ((Machine *)ptrMachine != (Machine *)mMachine)
2632 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The 'machine' while we were using it - please don't do that"));
2633
2634 /*
2635 * Check if the specified ISOs and files exist.
2636 */
2637 if (!RTFileExists(mStrIsoPath.c_str()))
2638 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the installation ISO file '%s'"),
2639 mStrIsoPath.c_str());
2640 if (mfInstallGuestAdditions && !RTFileExists(mStrAdditionsIsoPath.c_str()))
2641 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the Guest Additions ISO file '%s'"),
2642 mStrAdditionsIsoPath.c_str());
2643 if (mfInstallTestExecService && !RTFileExists(mStrValidationKitIsoPath.c_str()))
2644 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the validation kit ISO file '%s'"),
2645 mStrValidationKitIsoPath.c_str());
2646 if (mfInstallUserPayload && !RTFileExists(mStrUserPayloadIsoPath.c_str()))
2647 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the User Payload ISO file '%s'"),
2648 mStrUserPayloadIsoPath.c_str());
2649 if (mStrScriptTemplatePath.isNotEmpty() && !RTFileExists(mStrScriptTemplatePath.c_str()))
2650 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate unattended installation script template '%s'"),
2651 mStrScriptTemplatePath.c_str());
2652
2653 /*
2654 * Do media detection if it haven't been done yet.
2655 */
2656 if (!mfDoneDetectIsoOS)
2657 {
2658 hrc = detectIsoOS();
2659 if (FAILED(hrc) && hrc != E_NOTIMPL)
2660 return hrc;
2661 }
2662
2663 /*
2664 * We can now check midxImage against mDetectedImages, since the latter is
2665 * populated during the detectIsoOS call. We ignore midxImage if no images
2666 * were detected, assuming that it's not relevant or used for different purposes.
2667 */
2668 if (mDetectedImages.size() > 0)
2669 {
2670 bool fImageFound = false;
2671 for (size_t i = 0; i < mDetectedImages.size(); ++i)
2672 if (midxImage == mDetectedImages[i].mImageIndex)
2673 {
2674 i_updateDetectedAttributeForImage(mDetectedImages[i]);
2675 fImageFound = true;
2676 break;
2677 }
2678 if (!fImageFound)
2679 return setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("imageIndex value %u not found in detectedImageIndices"), midxImage);
2680 }
2681
2682 /*
2683 * Get the ISO's detect guest OS type info and make it's a known one (just
2684 * in case the above step doesn't work right).
2685 */
2686 uint32_t const idxIsoOSType = Global::getOSTypeIndexFromId(mStrDetectedOSTypeId.c_str());
2687 VBOXOSTYPE const enmIsoOSType = idxIsoOSType < Global::cOSTypes ? Global::sOSTypes[idxIsoOSType].osType : VBOXOSTYPE_Unknown;
2688 if ((enmIsoOSType & VBOXOSTYPE_OsFamilyMask) == VBOXOSTYPE_Unknown)
2689 return setError(E_FAIL, tr("The supplied ISO file does not contain an OS currently supported for unattended installation"));
2690
2691 /*
2692 * Get the VM's configured guest OS type info.
2693 */
2694 uint32_t const idxMachineOSType = Global::getOSTypeIndexFromId(mStrGuestOsTypeId.c_str());
2695 VBOXOSTYPE const enmMachineOSType = idxMachineOSType < Global::cOSTypes
2696 ? Global::sOSTypes[idxMachineOSType].osType : VBOXOSTYPE_Unknown;
2697#if 0 /** @todo r=bird: Unused, see below. */
2698 uint32_t const fMachineOsHints = idxMachineOSType < Global::cOSTypes
2699 ? Global::sOSTypes[idxMachineOSType].osHint : 0;
2700#endif
2701
2702 /*
2703 * Check that the detected guest OS type for the ISO is compatible with
2704 * that of the VM, broadly speaking.
2705 */
2706 if (idxMachineOSType != idxIsoOSType)
2707 {
2708 /* Check that the architecture is compatible: */
2709 if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask)
2710 && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86
2711 || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64))
2712 return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch"));
2713
2714 /** @todo check BIOS/EFI requirement */
2715 }
2716
2717#if 0 /** @todo r=bird: this is misleading, since it is checking the machine. */
2718 /* We don't support guest OSes w/ EFI, as that requires UDF remastering support we don't have yet. */
2719 if ( (fMachineOsHints & VBOXOSHINT_EFI)
2720 && (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_arm64)
2721 return setError(E_FAIL, tr("The machine is configured with EFI which is currently not supported by unatteded installation"));
2722#endif
2723
2724 /* Set the guest additions install package name. */
2725 mStrAdditionsInstallPackage = Global::sOSTypes[idxMachineOSType].guestAdditionsInstallPkgName;
2726
2727 /*
2728 * Do some default property stuff and check other properties.
2729 */
2730 try
2731 {
2732 char szTmp[128];
2733
2734 if (mStrLocale.isEmpty())
2735 {
2736 int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp));
2737 if ( RT_SUCCESS(vrc)
2738 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp))
2739 mStrLocale.assign(szTmp, 5);
2740 else
2741 mStrLocale = "en_US";
2742 Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale));
2743 }
2744
2745 if (mStrLanguage.isEmpty())
2746 {
2747 if (mDetectedOSLanguages.size() > 0)
2748 mStrLanguage = mDetectedOSLanguages[0];
2749 else
2750 mStrLanguage.assign(mStrLocale).findReplace('_', '-');
2751 }
2752
2753 if (mStrCountry.isEmpty())
2754 {
2755 int vrc = RTLocaleQueryUserCountryCode(szTmp);
2756 if (RT_SUCCESS(vrc))
2757 mStrCountry = szTmp;
2758 else if ( mStrLocale.isNotEmpty()
2759 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale))
2760 mStrCountry.assign(mStrLocale, 3, 2);
2761 else
2762 mStrCountry = "US";
2763 }
2764
2765 if (mStrTimeZone.isEmpty())
2766 {
2767 int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp));
2768 if ( RT_SUCCESS(vrc)
2769 && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */)
2770 mStrTimeZone = szTmp;
2771 else
2772 mStrTimeZone = "Etc/UTC";
2773 Assert(mStrTimeZone.isNotEmpty());
2774 }
2775 mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str());
2776 if (!mpTimeZoneInfo)
2777 mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str());
2778 Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC");
2779 if (!mpTimeZoneInfo)
2780 LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str()));
2781
2782 if (mStrHostname.isEmpty())
2783 {
2784 /* Mangle the VM name into a valid hostname. */
2785 for (size_t i = 0; i < strMachineName.length(); i++)
2786 {
2787 char ch = strMachineName[i];
2788 if ( (unsigned)ch < 127
2789 && RT_C_IS_ALNUM(ch))
2790 mStrHostname.append(ch);
2791 else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-"))
2792 mStrHostname.append('-');
2793 }
2794 if (mStrHostname.length() == 0)
2795 mStrHostname.printf("%RTuuid-vm", MachineUuid.raw());
2796 else if (mStrHostname.length() < 3)
2797 mStrHostname.append("-vm");
2798 mStrHostname.append(".myguest.virtualbox.org");
2799 }
2800
2801 if (mStrAuxiliaryBasePath.isEmpty())
2802 {
2803 mStrAuxiliaryBasePath = strDefaultAuxBasePath;
2804 mfIsDefaultAuxiliaryBasePath = true;
2805 }
2806 }
2807 catch (std::bad_alloc &)
2808 {
2809 return E_OUTOFMEMORY;
2810 }
2811
2812 /*
2813 * Instatiate the guest installer matching the ISO.
2814 */
2815 mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion,
2816 mStrDetectedOSFlavor, mStrDetectedOSHints, this);
2817 if (mpInstaller != NULL)
2818 {
2819 hrc = mpInstaller->initInstaller();
2820 if (SUCCEEDED(hrc))
2821 {
2822 /*
2823 * Do the script preps (just reads them).
2824 */
2825 hrc = mpInstaller->prepareUnattendedScripts();
2826 if (SUCCEEDED(hrc))
2827 {
2828 LogFlow(("Unattended::prepare: returns S_OK\n"));
2829 return S_OK;
2830 }
2831 }
2832
2833 /* Destroy the installer instance. */
2834 delete mpInstaller;
2835 mpInstaller = NULL;
2836 }
2837 else
2838 hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND,
2839 tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str());
2840 LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc));
2841 return hrc;
2842}
2843
2844HRESULT Unattended::constructMedia()
2845{
2846 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2847
2848 LogFlow(("===========================================================\n"));
2849 LogFlow(("Call Unattended::constructMedia()\n"));
2850
2851 if (mpInstaller == NULL)
2852 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called");
2853
2854 return mpInstaller->prepareMedia();
2855}
2856
2857HRESULT Unattended::reconfigureVM()
2858{
2859 LogFlow(("===========================================================\n"));
2860 LogFlow(("Call Unattended::reconfigureVM()\n"));
2861
2862 /*
2863 * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues.
2864 */
2865 StorageBus_T enmRecommendedStorageBus = StorageBus_IDE;
2866 {
2867 Bstr bstrGuestOsTypeId;
2868 Bstr bstrDetectedOSTypeId;
2869 {
2870 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2871 if (mpInstaller == NULL)
2872 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2873 bstrGuestOsTypeId = mStrGuestOsTypeId;
2874 bstrDetectedOSTypeId = mStrDetectedOSTypeId;
2875 }
2876 ComPtr<IGuestOSType> ptrGuestOSType;
2877 HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam());
2878 if (SUCCEEDED(hrc))
2879 {
2880 if (!ptrGuestOSType.isNull())
2881 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus);
2882 }
2883 if (FAILED(hrc))
2884 return hrc;
2885
2886 /* If the detected guest OS type differs, log a warning if their DVD storage
2887 bus recommendations differ. */
2888 if (bstrGuestOsTypeId != bstrDetectedOSTypeId)
2889 {
2890 StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE;
2891 hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam());
2892 if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull())
2893 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2);
2894 if (FAILED(hrc))
2895 return hrc;
2896
2897 if (enmRecommendedStorageBus != enmRecommendedStorageBus2)
2898 LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n",
2899 ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(),
2900 ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() ));
2901 }
2902 }
2903
2904 /*
2905 * Take write lock (for lock order reasons, write lock our parent object too)
2906 * then make sure we're the only caller of this method.
2907 */
2908 AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS);
2909 HRESULT hrc;
2910 if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD)
2911 {
2912 RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf();
2913 mhThreadReconfigureVM = hNativeSelf;
2914
2915 /*
2916 * Create a new session, lock the machine and get the session machine object.
2917 * Do the locking without pinning down the write locks, just to be on the safe side.
2918 */
2919 ComPtr<ISession> ptrSession;
2920 try
2921 {
2922 hrc = ptrSession.createInprocObject(CLSID_Session);
2923 }
2924 catch (std::bad_alloc &)
2925 {
2926 hrc = E_OUTOFMEMORY;
2927 }
2928 if (SUCCEEDED(hrc))
2929 {
2930 alock.release();
2931 hrc = mMachine->LockMachine(ptrSession, LockType_Shared);
2932 alock.acquire();
2933 if (SUCCEEDED(hrc))
2934 {
2935 ComPtr<IMachine> ptrSessionMachine;
2936 hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam());
2937 if (SUCCEEDED(hrc))
2938 {
2939 /*
2940 * Hand the session to the inner work and let it do it job.
2941 */
2942 try
2943 {
2944 hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine);
2945 }
2946 catch (...)
2947 {
2948 hrc = E_UNEXPECTED;
2949 }
2950 }
2951
2952 /* Paranoia: release early in case we it a bump below. */
2953 Assert(mhThreadReconfigureVM == hNativeSelf);
2954 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2955
2956 /*
2957 * While unlocking the machine we'll have to drop the locks again.
2958 */
2959 alock.release();
2960
2961 ptrSessionMachine.setNull();
2962 HRESULT hrc2 = ptrSession->UnlockMachine();
2963 AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2));
2964
2965 ptrSession.setNull();
2966
2967 alock.acquire();
2968 }
2969 else
2970 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2971 }
2972 else
2973 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2974 }
2975 else
2976 hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread"));
2977 return hrc;
2978}
2979
2980
2981HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
2982 ComPtr<IMachine> const &rPtrSessionMachine)
2983{
2984 if (mpInstaller == NULL)
2985 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2986
2987 // Fetch all available storage controllers
2988 com::SafeIfaceArray<IStorageController> arrayOfControllers;
2989 HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers));
2990 AssertComRCReturn(hrc, hrc);
2991
2992 /*
2993 * Figure out where the images are to be mounted, adding controllers/ports as needed.
2994 */
2995 std::vector<UnattendedInstallationDisk> vecInstallationDisks;
2996 if (mpInstaller->isAuxiliaryFloppyNeeded())
2997 {
2998 hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock);
2999 if (FAILED(hrc))
3000 return hrc;
3001 }
3002
3003 hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus);
3004 if (FAILED(hrc))
3005 return hrc;
3006
3007 /*
3008 * Mount the images.
3009 */
3010 for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++)
3011 {
3012 UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage);
3013 Assert(pImage->strImagePath.isNotEmpty());
3014 hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock);
3015 if (FAILED(hrc))
3016 return hrc;
3017 }
3018
3019 /*
3020 * Set the boot order.
3021 *
3022 * ASSUME that the HD isn't bootable when we start out, but it will be what
3023 * we boot from after the first stage of the installation is done. Setting
3024 * it first prevents endless reboot cylces.
3025 */
3026 /** @todo consider making 100% sure the disk isn't bootable (edit partition
3027 * table active bits and EFI stuff). */
3028 Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD
3029 || mpInstaller->getBootableDeviceType() == DeviceType_Floppy);
3030 hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk);
3031 if (SUCCEEDED(hrc))
3032 hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType());
3033 if (SUCCEEDED(hrc))
3034 hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD
3035 ? DeviceType_Floppy : DeviceType_DVD);
3036 if (FAILED(hrc))
3037 return hrc;
3038
3039 /*
3040 * Essential step.
3041 *
3042 * HACK ALERT! We have to release the lock here or we'll get into trouble with
3043 * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType).
3044 */
3045 if (SUCCEEDED(hrc))
3046 {
3047 rAutoLock.release();
3048 hrc = rPtrSessionMachine->SaveSettings();
3049 rAutoLock.acquire();
3050 }
3051
3052 return hrc;
3053}
3054
3055/**
3056 * Makes sure we've got a floppy drive attached to a floppy controller, adding
3057 * the auxiliary floppy image to the installation disk vector.
3058 *
3059 * @returns COM status code.
3060 * @param rControllers The existing controllers.
3061 * @param rVecInstallatationDisks The list of image to mount.
3062 * @param rPtrSessionMachine The session machine smart pointer.
3063 * @param rAutoLock The lock.
3064 */
3065HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
3066 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
3067 ComPtr<IMachine> const &rPtrSessionMachine,
3068 AutoMultiWriteLock2 &rAutoLock)
3069{
3070 Assert(mpInstaller->isAuxiliaryFloppyNeeded());
3071
3072 /*
3073 * Look for a floppy controller with a primary drive (A:) we can "insert"
3074 * the auxiliary floppy image. Add a controller and/or a drive if necessary.
3075 */
3076 bool fFoundPort0Dev0 = false;
3077 Bstr bstrControllerName;
3078 Utf8Str strControllerName;
3079
3080 for (size_t i = 0; i < rControllers.size(); ++i)
3081 {
3082 StorageBus_T enmStorageBus;
3083 HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3084 AssertComRCReturn(hrc, hrc);
3085 if (enmStorageBus == StorageBus_Floppy)
3086 {
3087
3088 /*
3089 * Found a floppy controller.
3090 */
3091 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3092 AssertComRCReturn(hrc, hrc);
3093
3094 /*
3095 * Check the attchments to see if we've got a device 0 attached on port 0.
3096 *
3097 * While we're at it we eject flppies from all floppy drives we encounter,
3098 * we don't want any confusion at boot or during installation.
3099 */
3100 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3101 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3102 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3103 AssertComRCReturn(hrc, hrc);
3104 strControllerName = bstrControllerName;
3105 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3106
3107 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3108 {
3109 LONG iPort = -1;
3110 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3111 AssertComRCReturn(hrc, hrc);
3112
3113 LONG iDevice = -1;
3114 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3115 AssertComRCReturn(hrc, hrc);
3116
3117 DeviceType_T enmType;
3118 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3119 AssertComRCReturn(hrc, hrc);
3120
3121 if (enmType == DeviceType_Floppy)
3122 {
3123 ComPtr<IMedium> ptrMedium;
3124 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3125 AssertComRCReturn(hrc, hrc);
3126
3127 if (ptrMedium.isNotNull())
3128 {
3129 ptrMedium.setNull();
3130 rAutoLock.release();
3131 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3132 rAutoLock.acquire();
3133 }
3134
3135 if (iPort == 0 && iDevice == 0)
3136 fFoundPort0Dev0 = true;
3137 }
3138 else if (iPort == 0 && iDevice == 0)
3139 return setError(E_FAIL,
3140 tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"),
3141 bstrControllerName.raw());
3142 }
3143 }
3144 }
3145
3146 /*
3147 * Add a floppy controller if we need to.
3148 */
3149 if (strControllerName.isEmpty())
3150 {
3151 bstrControllerName = strControllerName = "Floppy";
3152 ComPtr<IStorageController> ptrControllerIgnored;
3153 HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy,
3154 ptrControllerIgnored.asOutParam());
3155 LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc));
3156 if (FAILED(hrc))
3157 return hrc;
3158 }
3159
3160 /*
3161 * Adding a floppy drive (if needed) and mounting the auxiliary image is
3162 * done later together with the ISOs.
3163 */
3164 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName,
3165 DeviceType_Floppy, AccessMode_ReadWrite,
3166 0, 0,
3167 fFoundPort0Dev0 /*fMountOnly*/,
3168 mpInstaller->getAuxiliaryFloppyFilePath(), false));
3169 return S_OK;
3170}
3171
3172/**
3173 * Reconfigures DVD drives of the VM to mount all the ISOs we need.
3174 *
3175 * This will umount all DVD media.
3176 *
3177 * @returns COM status code.
3178 * @param rControllers The existing controllers.
3179 * @param rVecInstallatationDisks The list of image to mount.
3180 * @param rPtrSessionMachine The session machine smart pointer.
3181 * @param rAutoLock The lock.
3182 * @param enmRecommendedStorageBus The recommended storage bus type for adding
3183 * DVD drives on.
3184 */
3185HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
3186 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
3187 ComPtr<IMachine> const &rPtrSessionMachine,
3188 AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus)
3189{
3190 /*
3191 * Enumerate the attachements of every controller, looking for DVD drives,
3192 * ASSUMEING all drives are bootable.
3193 *
3194 * Eject the medium from all the drives (don't want any confusion) and look
3195 * for the recommended storage bus in case we need to add more drives.
3196 */
3197 HRESULT hrc;
3198 std::list<ControllerSlot> lstControllerDvdSlots;
3199 Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */
3200 Utf8Str strControllerName;
3201 Bstr bstrControllerName;
3202 for (size_t i = 0; i < rControllers.size(); ++i)
3203 {
3204 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3205 AssertComRCReturn(hrc, hrc);
3206 strControllerName = bstrControllerName;
3207
3208 /* Look for recommended storage bus. */
3209 StorageBus_T enmStorageBus;
3210 hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3211 AssertComRCReturn(hrc, hrc);
3212 if (enmStorageBus == enmRecommendedStorageBus)
3213 {
3214 strRecommendedControllerName = bstrControllerName;
3215 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3216 }
3217
3218 /* Scan the controller attachments. */
3219 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3220 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3221 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3222 AssertComRCReturn(hrc, hrc);
3223
3224 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3225 {
3226 DeviceType_T enmType;
3227 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3228 AssertComRCReturn(hrc, hrc);
3229 if (enmType == DeviceType_DVD)
3230 {
3231 LONG iPort = -1;
3232 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3233 AssertComRCReturn(hrc, hrc);
3234
3235 LONG iDevice = -1;
3236 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3237 AssertComRCReturn(hrc, hrc);
3238
3239 /* Remeber it. */
3240 lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/));
3241
3242 /* Eject the medium, if any. */
3243 ComPtr<IMedium> ptrMedium;
3244 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3245 AssertComRCReturn(hrc, hrc);
3246 if (ptrMedium.isNotNull())
3247 {
3248 ptrMedium.setNull();
3249
3250 rAutoLock.release();
3251 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3252 rAutoLock.acquire();
3253 }
3254 }
3255 }
3256 }
3257
3258 /*
3259 * How many drives do we need? Add more if necessary.
3260 */
3261 ULONG cDvdDrivesNeeded = 0;
3262 if (mpInstaller->isAuxiliaryIsoNeeded())
3263 cDvdDrivesNeeded++;
3264 if (mpInstaller->isOriginalIsoNeeded())
3265 cDvdDrivesNeeded++;
3266#if 0 /* These are now in the AUX VISO. */
3267 if (mpInstaller->isAdditionsIsoNeeded())
3268 cDvdDrivesNeeded++;
3269 if (mpInstaller->isValidationKitIsoNeeded())
3270 cDvdDrivesNeeded++;
3271#endif
3272 Assert(cDvdDrivesNeeded > 0);
3273 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3274 {
3275 /* Do we need to add the recommended controller? */
3276 if (strRecommendedControllerName.isEmpty())
3277 {
3278 strRecommendedControllerName = StorageController::i_controllerNameFromBusType(enmRecommendedStorageBus);
3279
3280 ComPtr<IStorageController> ptrControllerIgnored;
3281 hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus,
3282 ptrControllerIgnored.asOutParam());
3283 LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc));
3284 if (FAILED(hrc))
3285 return hrc;
3286 }
3287
3288 /* Add free controller slots, maybe raising the port limit on the controller if we can. */
3289 hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine,
3290 cDvdDrivesNeeded, lstControllerDvdSlots);
3291 if (FAILED(hrc))
3292 return hrc;
3293 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3294 {
3295 /* We could in many cases create another controller here, but it's not worth the effort. */
3296 return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "",
3297 cDvdDrivesNeeded - lstControllerDvdSlots.size()),
3298 strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size());
3299 }
3300 Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size());
3301 }
3302
3303 /*
3304 * Sort the DVD slots in boot order.
3305 */
3306 lstControllerDvdSlots.sort();
3307
3308 /*
3309 * Prepare ISO mounts.
3310 *
3311 * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots
3312 * according to the boot order.
3313 */
3314 std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin();
3315 if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso())
3316 {
3317 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3318 ++itDvdSlot;
3319 }
3320
3321 if (mpInstaller->isOriginalIsoNeeded())
3322 {
3323 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath(), false));
3324 ++itDvdSlot;
3325 }
3326
3327 if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso())
3328 {
3329 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3330 ++itDvdSlot;
3331 }
3332
3333#if 0 /* These are now in the AUX VISO. */
3334 if (mpInstaller->isAdditionsIsoNeeded())
3335 {
3336 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath(), false));
3337 ++itDvdSlot;
3338 }
3339
3340 if (mpInstaller->isValidationKitIsoNeeded())
3341 {
3342 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath(), false));
3343 ++itDvdSlot;
3344 }
3345#endif
3346
3347 return S_OK;
3348}
3349
3350/**
3351 * Used to find more free slots for DVD drives during VM reconfiguration.
3352 *
3353 * This may modify the @a portCount property of the given controller.
3354 *
3355 * @returns COM status code.
3356 * @param rStrControllerName The name of the controller to find/create
3357 * free slots on.
3358 * @param enmStorageBus The storage bus type.
3359 * @param rPtrSessionMachine Reference to the session machine.
3360 * @param cSlotsNeeded Total slots needed (including those we've
3361 * already found).
3362 * @param rDvdSlots The slot collection for DVD drives to add
3363 * free slots to as we find/create them.
3364 */
3365HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
3366 ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
3367 std::list<ControllerSlot> &rDvdSlots)
3368{
3369 Assert(cSlotsNeeded > rDvdSlots.size());
3370
3371 /*
3372 * Get controlleer stats.
3373 */
3374 ComPtr<IStorageController> pController;
3375 HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam());
3376 AssertComRCReturn(hrc, hrc);
3377
3378 ULONG cMaxDevicesPerPort = 1;
3379 hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort);
3380 AssertComRCReturn(hrc, hrc);
3381 AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED);
3382
3383 ULONG cPorts = 0;
3384 hrc = pController->COMGETTER(PortCount)(&cPorts);
3385 AssertComRCReturn(hrc, hrc);
3386
3387 /*
3388 * Get the attachment list and turn into an internal list for lookup speed.
3389 */
3390 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3391 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(),
3392 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3393 AssertComRCReturn(hrc, hrc);
3394
3395 std::vector<ControllerSlot> arrayOfUsedSlots;
3396 for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++)
3397 {
3398 LONG iPort = -1;
3399 hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort);
3400 AssertComRCReturn(hrc, hrc);
3401
3402 LONG iDevice = -1;
3403 hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice);
3404 AssertComRCReturn(hrc, hrc);
3405
3406 arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/));
3407 }
3408
3409 /*
3410 * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots.
3411 */
3412 for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++)
3413 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3414 {
3415 bool fFound = false;
3416 for (size_t i = 0; i < arrayOfUsedSlots.size(); i++)
3417 if ( arrayOfUsedSlots[i].iPort == iPort
3418 && arrayOfUsedSlots[i].iDevice == iDevice)
3419 {
3420 fFound = true;
3421 break;
3422 }
3423 if (!fFound)
3424 {
3425 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3426 if (rDvdSlots.size() >= cSlotsNeeded)
3427 return S_OK;
3428 }
3429 }
3430
3431 /*
3432 * Okay we still need more ports. See if increasing the number of controller
3433 * ports would solve it.
3434 */
3435 ULONG cMaxPorts = 1;
3436 hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts);
3437 AssertComRCReturn(hrc, hrc);
3438 if (cMaxPorts <= cPorts)
3439 return S_OK;
3440 size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort;
3441 if (cPorts + cNewPortsNeeded > cMaxPorts)
3442 return S_OK;
3443
3444 /*
3445 * Raise the port count and add the free slots we've just created.
3446 */
3447 hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded);
3448 AssertComRCReturn(hrc, hrc);
3449 int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded);
3450 for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++)
3451 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3452 {
3453 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3454 if (rDvdSlots.size() >= cSlotsNeeded)
3455 return S_OK;
3456 }
3457
3458 /* We should not get here! */
3459 AssertLogRelFailedReturn(E_UNEXPECTED);
3460}
3461
3462HRESULT Unattended::done()
3463{
3464 LogFlow(("Unattended::done\n"));
3465 if (mpInstaller)
3466 {
3467 LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller));
3468 delete mpInstaller;
3469 mpInstaller = NULL;
3470 }
3471 return S_OK;
3472}
3473
3474HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
3475{
3476 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3477 isoPath = mStrIsoPath;
3478 return S_OK;
3479}
3480
3481HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
3482{
3483 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3484 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3485 mStrIsoPath = isoPath;
3486 mfDoneDetectIsoOS = false;
3487 return S_OK;
3488}
3489
3490HRESULT Unattended::getUser(com::Utf8Str &user)
3491{
3492 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3493 user = mStrUser;
3494 return S_OK;
3495}
3496
3497
3498HRESULT Unattended::setUser(const com::Utf8Str &user)
3499{
3500 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3501 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3502 mStrUser = user;
3503 return S_OK;
3504}
3505
3506HRESULT Unattended::getUserPassword(com::Utf8Str &password)
3507{
3508 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3509 password = mStrUserPassword;
3510 return S_OK;
3511}
3512
3513HRESULT Unattended::setUserPassword(const com::Utf8Str &password)
3514{
3515 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3516 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3517 mStrUserPassword = password;
3518 return S_OK;
3519}
3520
3521HRESULT Unattended::getAdminPassword(com::Utf8Str &password)
3522{
3523 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3524 password = mStrAdminPassword;
3525 return S_OK;
3526}
3527
3528HRESULT Unattended::setAdminPassword(const com::Utf8Str &password)
3529{
3530 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3531 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3532 mStrAdminPassword = password;
3533 return S_OK;
3534}
3535
3536HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
3537{
3538 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3539 fullUserName = mStrFullUserName;
3540 return S_OK;
3541}
3542
3543HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
3544{
3545 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3546 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3547 mStrFullUserName = fullUserName;
3548 return S_OK;
3549}
3550
3551HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
3552{
3553 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3554 productKey = mStrProductKey;
3555 return S_OK;
3556}
3557
3558HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
3559{
3560 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3561 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3562 mStrProductKey = productKey;
3563 return S_OK;
3564}
3565
3566HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
3567{
3568 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3569 additionsIsoPath = mStrAdditionsIsoPath;
3570 return S_OK;
3571}
3572
3573HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
3574{
3575 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3576 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3577 mStrAdditionsIsoPath = additionsIsoPath;
3578 return S_OK;
3579}
3580
3581HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
3582{
3583 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3584 *installGuestAdditions = mfInstallGuestAdditions;
3585 return S_OK;
3586}
3587
3588HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
3589{
3590 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3591 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3592 mfInstallGuestAdditions = installGuestAdditions != FALSE;
3593 return S_OK;
3594}
3595
3596HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
3597{
3598 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3599 aValidationKitIsoPath = mStrValidationKitIsoPath;
3600 return S_OK;
3601}
3602
3603HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
3604{
3605 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3606 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3607 mStrValidationKitIsoPath = aValidationKitIsoPath;
3608 return S_OK;
3609}
3610
3611HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
3612{
3613 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3614 *aInstallTestExecService = mfInstallTestExecService;
3615 return S_OK;
3616}
3617
3618HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
3619{
3620 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3621 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3622 mfInstallTestExecService = aInstallTestExecService != FALSE;
3623 return S_OK;
3624}
3625
3626HRESULT Unattended::getUserPayloadIsoPath(com::Utf8Str &aUserPayloadIsoPath)
3627{
3628 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3629 aUserPayloadIsoPath = mStrUserPayloadIsoPath;
3630 return S_OK;
3631}
3632
3633HRESULT Unattended::setUserPayloadIsoPath(const com::Utf8Str &aUserPayloadIsoPath)
3634{
3635 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3636 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3637 mStrUserPayloadIsoPath = aUserPayloadIsoPath;
3638 return S_OK;
3639}
3640
3641HRESULT Unattended::getInstallUserPayload(BOOL *aInstallUserPayload)
3642{
3643 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3644 *aInstallUserPayload = mfInstallUserPayload;
3645 return S_OK;
3646}
3647
3648HRESULT Unattended::setInstallUserPayload(BOOL aInstallUserPayload)
3649{
3650 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3651 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3652 mfInstallUserPayload = aInstallUserPayload != FALSE;
3653 return S_OK;
3654}
3655
3656HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
3657{
3658 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3659 aTimeZone = mStrTimeZone;
3660 return S_OK;
3661}
3662
3663HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
3664{
3665 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3666 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3667 mStrTimeZone = aTimezone;
3668 return S_OK;
3669}
3670
3671HRESULT Unattended::getKeyboardLayout(com::Utf8Str &aKeyboardLayout)
3672{
3673 RT_NOREF(aKeyboardLayout);
3674 return E_NOTIMPL;
3675}
3676
3677HRESULT Unattended::setKeyboardLayout(const com::Utf8Str &aKeyboardLayout)
3678{
3679 RT_NOREF(aKeyboardLayout);
3680 return E_NOTIMPL;
3681}
3682
3683HRESULT Unattended::getKeyboardVariant(com::Utf8Str &aKeyboardVariant)
3684{
3685 RT_NOREF(aKeyboardVariant);
3686 return E_NOTIMPL;
3687}
3688
3689HRESULT Unattended::setKeyboardVariant(const com::Utf8Str &aKeyboardVariant)
3690{
3691 RT_NOREF(aKeyboardVariant);
3692 return E_NOTIMPL;
3693}
3694
3695HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
3696{
3697 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3698 aLocale = mStrLocale;
3699 return S_OK;
3700}
3701
3702HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
3703{
3704 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3705 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3706 if ( aLocale.isEmpty() /* use default */
3707 || ( aLocale.length() == 5
3708 && RT_C_IS_LOWER(aLocale[0])
3709 && RT_C_IS_LOWER(aLocale[1])
3710 && aLocale[2] == '_'
3711 && RT_C_IS_UPPER(aLocale[3])
3712 && RT_C_IS_UPPER(aLocale[4])) )
3713 {
3714 mStrLocale = aLocale;
3715 return S_OK;
3716 }
3717 return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters"));
3718}
3719
3720HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
3721{
3722 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3723 aLanguage = mStrLanguage;
3724 return S_OK;
3725}
3726
3727HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
3728{
3729 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3730 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3731 mStrLanguage = aLanguage;
3732 return S_OK;
3733}
3734
3735HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
3736{
3737 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3738 aCountry = mStrCountry;
3739 return S_OK;
3740}
3741
3742HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
3743{
3744 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3745 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3746 if ( aCountry.isEmpty()
3747 || ( aCountry.length() == 2
3748 && RT_C_IS_UPPER(aCountry[0])
3749 && RT_C_IS_UPPER(aCountry[1])) )
3750 {
3751 mStrCountry = aCountry;
3752 return S_OK;
3753 }
3754 return setError(E_INVALIDARG, tr("Expected two upper cased letters"));
3755}
3756
3757HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
3758{
3759 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3760 aProxy = mStrProxy; /// @todo turn schema map into string or something.
3761 return S_OK;
3762}
3763
3764HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
3765{
3766 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3767 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3768 if (aProxy.isEmpty())
3769 {
3770 /* set default proxy */
3771 /** @todo BUGBUG! implement this */
3772 }
3773 else if (aProxy.equalsIgnoreCase("none"))
3774 {
3775 /* clear proxy config */
3776 mStrProxy.setNull();
3777 }
3778 else
3779 {
3780 /** @todo Parse and set proxy config into a schema map or something along those lines. */
3781 /** @todo BUGBUG! implement this */
3782 // return E_NOTIMPL;
3783 mStrProxy = aProxy;
3784 }
3785 return S_OK;
3786}
3787
3788HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
3789{
3790 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3791 aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";");
3792 return S_OK;
3793}
3794
3795HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
3796{
3797 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3798 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3799 if (aPackageSelectionAdjustments.isEmpty())
3800 mPackageSelectionAdjustments.clear();
3801 else
3802 {
3803 RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";");
3804 for (size_t i = 0; i < arrayStrSplit.size(); i++)
3805 {
3806 if (arrayStrSplit[i].equals("minimal"))
3807 { /* okay */ }
3808 else
3809 return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str());
3810 }
3811 mPackageSelectionAdjustments = arrayStrSplit;
3812 }
3813 return S_OK;
3814}
3815
3816HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
3817{
3818 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3819 aHostname = mStrHostname;
3820 return S_OK;
3821}
3822
3823HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
3824{
3825 /*
3826 * Validate input.
3827 */
3828 if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U))
3829 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3830 tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()),
3831 aHostname.c_str(), aHostname.length());
3832 size_t cLabels = 0;
3833 const char *pszSrc = aHostname.c_str();
3834 for (;;)
3835 {
3836 size_t cchLabel = 1;
3837 char ch = *pszSrc++;
3838 if (RT_C_IS_ALNUM(ch))
3839 {
3840 cLabels++;
3841 while ((ch = *pszSrc++) != '.' && ch != '\0')
3842 {
3843 if (RT_C_IS_ALNUM(ch) || ch == '-')
3844 {
3845 if (cchLabel < 63)
3846 cchLabel++;
3847 else
3848 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3849 tr("Invalid hostname '%s' - label %u is too long, max is 63."),
3850 aHostname.c_str(), cLabels);
3851 }
3852 else
3853 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3854 tr("Invalid hostname '%s' - illegal char '%c' at position %zu"),
3855 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3856 }
3857 if (cLabels == 1 && cchLabel < 2)
3858 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3859 tr("Invalid hostname '%s' - the name part must be at least two characters long"),
3860 aHostname.c_str());
3861 if (ch == '\0')
3862 break;
3863 }
3864 else if (ch != '\0')
3865 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3866 tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"),
3867 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3868 else
3869 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3870 tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str());
3871 }
3872 if (cLabels < 2)
3873 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3874 tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str());
3875
3876 /*
3877 * Make the change.
3878 */
3879 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3880 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3881 mStrHostname = aHostname;
3882 return S_OK;
3883}
3884
3885HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
3886{
3887 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3888 aAuxiliaryBasePath = mStrAuxiliaryBasePath;
3889 return S_OK;
3890}
3891
3892HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
3893{
3894 if (aAuxiliaryBasePath.isEmpty())
3895 return setError(E_INVALIDARG, tr("Empty base path is not allowed"));
3896 if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str()))
3897 return setError(E_INVALIDARG, tr("Base path must be absolute"));
3898
3899 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3900 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3901 mStrAuxiliaryBasePath = aAuxiliaryBasePath;
3902 mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty();
3903 return S_OK;
3904}
3905
3906HRESULT Unattended::getImageIndex(ULONG *index)
3907{
3908 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3909 *index = midxImage;
3910 return S_OK;
3911}
3912
3913HRESULT Unattended::setImageIndex(ULONG index)
3914{
3915 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3916 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3917
3918 /* Validate the selection if detection was done already: */
3919 if (mDetectedImages.size() > 0)
3920 {
3921 for (size_t i = 0; i < mDetectedImages.size(); i++)
3922 if (mDetectedImages[i].mImageIndex == index)
3923 {
3924 midxImage = index;
3925 i_updateDetectedAttributeForImage(mDetectedImages[i]);
3926 return S_OK;
3927 }
3928 LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */
3929 }
3930
3931 midxImage = index;
3932 return S_OK;
3933}
3934
3935HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
3936{
3937 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3938 return mMachine.queryInterfaceTo(aMachine.asOutParam());
3939}
3940
3941HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
3942{
3943 /*
3944 * Lookup the VM so we can safely get the Machine instance.
3945 * (Don't want to test how reliable XPCOM and COM are with finding
3946 * the local object instance when a client passes a stub back.)
3947 */
3948 Bstr bstrUuidMachine;
3949 HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam());
3950 if (SUCCEEDED(hrc))
3951 {
3952 Guid UuidMachine(bstrUuidMachine);
3953 ComObjPtr<Machine> ptrMachine;
3954 hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
3955 if (SUCCEEDED(hrc))
3956 {
3957 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3958 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER,
3959 tr("Cannot change after prepare() has been called")));
3960 mMachine = ptrMachine;
3961 mMachineUuid = UuidMachine;
3962 if (mfIsDefaultAuxiliaryBasePath)
3963 mStrAuxiliaryBasePath.setNull();
3964 hrc = S_OK;
3965 }
3966 }
3967 return hrc;
3968}
3969
3970HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
3971{
3972 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3973 if ( mStrScriptTemplatePath.isNotEmpty()
3974 || mpInstaller == NULL)
3975 aScriptTemplatePath = mStrScriptTemplatePath;
3976 else
3977 aScriptTemplatePath = mpInstaller->getTemplateFilePath();
3978 return S_OK;
3979}
3980
3981HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
3982{
3983 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3984 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3985 mStrScriptTemplatePath = aScriptTemplatePath;
3986 return S_OK;
3987}
3988
3989HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
3990{
3991 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3992 if ( mStrPostInstallScriptTemplatePath.isNotEmpty()
3993 || mpInstaller == NULL)
3994 aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath;
3995 else
3996 aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath();
3997 return S_OK;
3998}
3999
4000HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
4001{
4002 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4003 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4004 mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath;
4005 return S_OK;
4006}
4007
4008HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
4009{
4010 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4011 aPostInstallCommand = mStrPostInstallCommand;
4012 return S_OK;
4013}
4014
4015HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
4016{
4017 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4018 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4019 mStrPostInstallCommand = aPostInstallCommand;
4020 return S_OK;
4021}
4022
4023HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
4024{
4025 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4026 if ( mStrExtraInstallKernelParameters.isNotEmpty()
4027 || mpInstaller == NULL)
4028 aExtraInstallKernelParameters = mStrExtraInstallKernelParameters;
4029 else
4030 aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters();
4031 return S_OK;
4032}
4033
4034HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
4035{
4036 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4037 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4038 mStrExtraInstallKernelParameters = aExtraInstallKernelParameters;
4039 return S_OK;
4040}
4041
4042HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
4043{
4044 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4045 aDetectedOSTypeId = mStrDetectedOSTypeId;
4046 return S_OK;
4047}
4048
4049HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
4050{
4051 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4052 aDetectedOSVersion = mStrDetectedOSVersion;
4053 return S_OK;
4054}
4055
4056HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
4057{
4058 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4059 aDetectedOSFlavor = mStrDetectedOSFlavor;
4060 return S_OK;
4061}
4062
4063HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
4064{
4065 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4066 aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " ");
4067 return S_OK;
4068}
4069
4070HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
4071{
4072 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4073 aDetectedOSHints = mStrDetectedOSHints;
4074 return S_OK;
4075}
4076
4077HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
4078{
4079 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4080 aDetectedImageNames.clear();
4081 for (size_t i = 0; i < mDetectedImages.size(); ++i)
4082 {
4083 Utf8Str strTmp;
4084 aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp));
4085 }
4086 return S_OK;
4087}
4088
4089HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
4090{
4091 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4092 aDetectedImageIndices.clear();
4093 for (size_t i = 0; i < mDetectedImages.size(); ++i)
4094 aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex);
4095 return S_OK;
4096}
4097
4098HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
4099{
4100 /*
4101 * Take the initial position that it's not supported, so we can return
4102 * right away when we decide it's not possible.
4103 */
4104 *aIsUnattendedInstallSupported = false;
4105
4106 /* Unattended is disabled by default if we could not detect OS type. */
4107 if (mStrDetectedOSTypeId.isEmpty())
4108 return S_OK;
4109
4110 /* Note! Includes the OS family and the distro (linux) or (part) of the
4111 major OS version. Use with care. */
4112 const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask);
4113
4114 /* We require a version to have been detected, except for windows where the
4115 field is generally only used for the service pack number at present and
4116 will be empty for RTMs isos. */
4117 if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT
4118 || enmOsTypeMasked >= VBOXOSTYPE_OS2)
4119 && mStrDetectedOSVersion.isEmpty())
4120 return S_OK;
4121
4122 /*
4123 * Sort out things that we know doesn't work. Order by VBOXOSTYPE value.
4124 */
4125
4126 /* We do not support any of the DOS based windows version, nor DOS, in case
4127 any of that gets detected (it shouldn't): */
4128 if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT)
4129 return S_OK;
4130
4131 /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */
4132 if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4)
4133 return S_OK;
4134
4135 /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been
4136 tested, but we'll get to the others eventually): */
4137 if ( enmOsTypeMasked >= VBOXOSTYPE_OS2
4138 && enmOsTypeMasked < VBOXOSTYPE_Linux
4139 && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ )
4140 return S_OK;
4141
4142 /* Old Debians fail since package repos have been move to some other mirror location. */
4143 if ( enmOsTypeMasked == VBOXOSTYPE_Debian
4144 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0)
4145 return S_OK;
4146
4147 /* Skip all OpenSUSE variants for now. */
4148 if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE)
4149 return S_OK;
4150
4151 if (enmOsTypeMasked == VBOXOSTYPE_Ubuntu)
4152 {
4153 /* We cannot install Ubuntus older than 11.04. */
4154 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0)
4155 return S_OK;
4156 /* Lubuntu, starting with 20.04, has switched to calamares, which cannot be automated. */
4157 if ( RTStrIStr(mStrDetectedOSFlavor.c_str(), "lubuntu")
4158 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "20.04") > 0)
4159 return S_OK;
4160 }
4161
4162 /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */
4163 if ( enmOsTypeMasked == VBOXOSTYPE_Oracle
4164 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0)
4165 return S_OK;
4166
4167 /* Fredora ISOs cannot be installed at present. */
4168 if (enmOsTypeMasked == VBOXOSTYPE_FedoraCore)
4169 return S_OK;
4170
4171 /*
4172 * Assume the rest works.
4173 */
4174 *aIsUnattendedInstallSupported = true;
4175 return S_OK;
4176}
4177
4178HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
4179{
4180 *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork;
4181 return S_OK;
4182}
4183
4184HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
4185{
4186 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4187 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4188 mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork);
4189 return S_OK;
4190}
4191
4192/*
4193 * Getters that the installer and script classes can use.
4194 */
4195Utf8Str const &Unattended::i_getIsoPath() const
4196{
4197 Assert(isReadLockedOnCurrentThread());
4198 return mStrIsoPath;
4199}
4200
4201Utf8Str const &Unattended::i_getUser() const
4202{
4203 Assert(isReadLockedOnCurrentThread());
4204 return mStrUser;
4205}
4206
4207Utf8Str const &Unattended::i_getUserPassword() const
4208{
4209 Assert(isReadLockedOnCurrentThread());
4210 return mStrUserPassword;
4211}
4212
4213Utf8Str const &Unattended::i_getAdminPassword() const
4214{
4215 Assert(isReadLockedOnCurrentThread());
4216
4217 /* If no Administrator / 'root' password is being set, the user password will be used instead.
4218 * Also see API documentation. */
4219 return mStrAdminPassword.isEmpty() ? mStrUserPassword : mStrAdminPassword;
4220}
4221
4222Utf8Str const &Unattended::i_getFullUserName() const
4223{
4224 Assert(isReadLockedOnCurrentThread());
4225 return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
4226}
4227
4228Utf8Str const &Unattended::i_getProductKey() const
4229{
4230 Assert(isReadLockedOnCurrentThread());
4231 return mStrProductKey;
4232}
4233
4234Utf8Str const &Unattended::i_getProxy() const
4235{
4236 Assert(isReadLockedOnCurrentThread());
4237 return mStrProxy;
4238}
4239
4240Utf8Str const &Unattended::i_getAdditionsIsoPath() const
4241{
4242 Assert(isReadLockedOnCurrentThread());
4243 return mStrAdditionsIsoPath;
4244}
4245
4246bool Unattended::i_getInstallGuestAdditions() const
4247{
4248 Assert(isReadLockedOnCurrentThread());
4249 return mfInstallGuestAdditions;
4250}
4251
4252Utf8Str const &Unattended::i_getValidationKitIsoPath() const
4253{
4254 Assert(isReadLockedOnCurrentThread());
4255 return mStrValidationKitIsoPath;
4256}
4257
4258bool Unattended::i_getInstallTestExecService() const
4259{
4260 Assert(isReadLockedOnCurrentThread());
4261 return mfInstallTestExecService;
4262}
4263
4264Utf8Str const &Unattended::i_getUserPayloadIsoPath() const
4265{
4266 Assert(isReadLockedOnCurrentThread());
4267 return mStrUserPayloadIsoPath;
4268}
4269
4270bool Unattended::i_getInstallUserPayload() const
4271{
4272 Assert(isReadLockedOnCurrentThread());
4273 return mfInstallUserPayload;
4274}
4275
4276Utf8Str const &Unattended::i_getTimeZone() const
4277{
4278 Assert(isReadLockedOnCurrentThread());
4279 return mStrTimeZone;
4280}
4281
4282PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
4283{
4284 Assert(isReadLockedOnCurrentThread());
4285 return mpTimeZoneInfo;
4286}
4287
4288Utf8Str const &Unattended::i_getLocale() const
4289{
4290 Assert(isReadLockedOnCurrentThread());
4291 return mStrLocale;
4292}
4293
4294Utf8Str const &Unattended::i_getLanguage() const
4295{
4296 Assert(isReadLockedOnCurrentThread());
4297 return mStrLanguage;
4298}
4299
4300Utf8Str const &Unattended::i_getCountry() const
4301{
4302 Assert(isReadLockedOnCurrentThread());
4303 return mStrCountry;
4304}
4305
4306bool Unattended::i_isMinimalInstallation() const
4307{
4308 size_t i = mPackageSelectionAdjustments.size();
4309 while (i-- > 0)
4310 if (mPackageSelectionAdjustments[i].equals("minimal"))
4311 return true;
4312 return false;
4313}
4314
4315Utf8Str const &Unattended::i_getHostname() const
4316{
4317 Assert(isReadLockedOnCurrentThread());
4318 return mStrHostname;
4319}
4320
4321Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
4322{
4323 Assert(isReadLockedOnCurrentThread());
4324 return mStrAuxiliaryBasePath;
4325}
4326
4327ULONG Unattended::i_getImageIndex() const
4328{
4329 Assert(isReadLockedOnCurrentThread());
4330 return midxImage;
4331}
4332
4333Utf8Str const &Unattended::i_getScriptTemplatePath() const
4334{
4335 Assert(isReadLockedOnCurrentThread());
4336 return mStrScriptTemplatePath;
4337}
4338
4339Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
4340{
4341 Assert(isReadLockedOnCurrentThread());
4342 return mStrPostInstallScriptTemplatePath;
4343}
4344
4345Utf8Str const &Unattended::i_getPostInstallCommand() const
4346{
4347 Assert(isReadLockedOnCurrentThread());
4348 return mStrPostInstallCommand;
4349}
4350
4351Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
4352{
4353 Assert(isReadLockedOnCurrentThread());
4354 /* Only the installer knows, forward the call. */
4355 AssertReturn(mpInstaller != NULL, Utf8Str::Empty);
4356 return mpInstaller->getAuxiliaryInstallDir();
4357}
4358
4359Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
4360{
4361 Assert(isReadLockedOnCurrentThread());
4362 return mStrExtraInstallKernelParameters;
4363}
4364
4365Utf8Str const &Unattended::i_getAdditionsInstallPackage() const
4366{
4367 Assert(isReadLockedOnCurrentThread());
4368 return mStrAdditionsInstallPackage;
4369}
4370
4371bool Unattended::i_isRtcUsingUtc() const
4372{
4373 Assert(isReadLockedOnCurrentThread());
4374 return mfRtcUseUtc;
4375}
4376
4377bool Unattended::i_isGuestOs64Bit() const
4378{
4379 Assert(isReadLockedOnCurrentThread());
4380 return mfGuestOs64Bit;
4381}
4382
4383bool Unattended::i_isFirmwareEFI() const
4384{
4385 Assert(isReadLockedOnCurrentThread());
4386 return menmFirmwareType != FirmwareType_BIOS;
4387}
4388
4389Utf8Str const &Unattended::i_getDetectedOSVersion()
4390{
4391 Assert(isReadLockedOnCurrentThread());
4392 return mStrDetectedOSVersion;
4393}
4394
4395bool Unattended::i_getAvoidUpdatesOverNetwork() const
4396{
4397 Assert(isReadLockedOnCurrentThread());
4398 return mfAvoidUpdatesOverNetwork;
4399}
4400
4401HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
4402 AutoMultiWriteLock2 &rLock)
4403{
4404 /*
4405 * Attach the disk image
4406 * HACK ALERT! Temporarily release the Unattended lock.
4407 */
4408 rLock.release();
4409
4410 ComPtr<IMedium> ptrMedium;
4411 HRESULT hrc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(),
4412 pImage->enmDeviceType,
4413 pImage->enmAccessType,
4414 true,
4415 ptrMedium.asOutParam());
4416 LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", hrc));
4417 if (SUCCEEDED(hrc))
4418 {
4419 if (pImage->fAuxiliary && pImage->strImagePath.endsWith(".viso"))
4420 {
4421 hrc = ptrMedium->SetProperty(Bstr("UnattendedInstall").raw(), Bstr("1").raw());
4422 LogRelFlowFunc(("Medium::SetProperty -> %Rhrc\n", hrc));
4423 }
4424 if (pImage->fMountOnly)
4425 {
4426 // mount the opened disk image
4427 hrc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4428 pImage->iDevice, ptrMedium, TRUE /*fForce*/);
4429 LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", hrc));
4430 }
4431 else
4432 {
4433 //attach the opened disk image to the controller
4434 hrc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4435 pImage->iDevice, pImage->enmDeviceType, ptrMedium);
4436 LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", hrc));
4437 }
4438 }
4439
4440 rLock.acquire();
4441 return hrc;
4442}
4443
4444bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId)
4445{
4446 ComPtr<IGuestOSType> pGuestOSType;
4447 HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam());
4448 if (SUCCEEDED(hrc))
4449 {
4450 BOOL fIs64Bit = FALSE;
4451 if (!pGuestOSType.isNull())
4452 hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit);
4453 if (SUCCEEDED(hrc))
4454 return fIs64Bit != FALSE;
4455 }
4456 return false;
4457}
4458
4459
4460bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage)
4461{
4462 bool fRet = true;
4463
4464 /*
4465 * If the image doesn't have a valid value, we don't change it.
4466 * This is obviously a little bit bogus, but what can we do...
4467 */
4468 const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType);
4469 if (pszOSTypeId && !RTStrStartsWith(pszOSTypeId, GUEST_OS_ID_STR_PARTIAL("Other"))) /** @todo set x64/a64 other variants or not? */
4470 mStrDetectedOSTypeId = pszOSTypeId;
4471 else
4472 fRet = false;
4473
4474 if (rImage.mVersion.isNotEmpty())
4475 mStrDetectedOSVersion = rImage.mVersion;
4476 else
4477 fRet = false;
4478
4479 if (rImage.mFlavor.isNotEmpty())
4480 mStrDetectedOSFlavor = rImage.mFlavor;
4481 else
4482 fRet = false;
4483
4484 if (rImage.mLanguages.size() > 0)
4485 mDetectedOSLanguages = rImage.mLanguages;
4486 else
4487 fRet = false;
4488
4489 mEnmOsType = rImage.mOSType;
4490
4491 return fRet;
4492}
4493
4494HRESULT Unattended::i_getListOfSupportedGuestOS()
4495{
4496 HRESULT hrc;
4497 if (mfDoneSupportedGuestOSList)
4498 return S_OK;
4499 mfDoneSupportedGuestOSList = true;
4500
4501 /* Get a list of guest OS Type Ids supported by the host. */
4502 ComPtr<ISystemProperties> pSystemProperties;
4503
4504 hrc = mParent->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
4505 if (SUCCEEDED(hrc))
4506 {
4507 ComPtr<IPlatformProperties> pPlatformProperties;
4508 hrc = pSystemProperties->COMGETTER(Platform)(pPlatformProperties.asOutParam());
4509 if (SUCCEEDED(hrc))
4510 {
4511 hrc = pPlatformProperties->COMGETTER(SupportedGuestOSTypes)(ComSafeArrayAsOutParam(mSupportedGuestOSTypes));
4512 if (!SUCCEEDED(hrc))
4513 mSupportedGuestOSTypes.resize(0);
4514 }
4515 }
4516 return hrc;
4517}
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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