VirtualBox

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

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

Unattended: bugref:9781. Using the first part of the read disk name string as OS flavor and black listing newer lubuntus.

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

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