VirtualBox

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

最後變更 在這個檔案從94533是 94533,由 vboxsync 提交於 3 年 前

Main/Unattended: ​​bugref:9781. Renaming the newly added attribute.

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

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