VirtualBox

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

Last change on this file since 94296 was 94296, checked in by vboxsync, 3 years ago

Main/Unattended: ​​bugref:9781. Detect OS type, version, and version of Debian Live ISOs.

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

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