VirtualBox

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

Last change on this file since 96619 was 96619, checked in by vboxsync, 2 years ago

Main/Unattended: Added is-install-supported checks for windows and OS/2. bugref:9781

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 159.3 KB
Line 
1/* $Id: UnattendedImpl.cpp 96619 2022-09-06 21:12:59Z 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.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
33#include "LoggingNew.h"
34#include "VirtualBoxBase.h"
35#include "UnattendedImpl.h"
36#include "UnattendedInstaller.h"
37#include "UnattendedScript.h"
38#include "VirtualBoxImpl.h"
39#include "SystemPropertiesImpl.h"
40#include "MachineImpl.h"
41#include "Global.h"
42#include "StringifyEnums.h"
43
44#include <VBox/err.h>
45#include <iprt/cpp/xml.h>
46#include <iprt/ctype.h>
47#include <iprt/file.h>
48#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("Xubuntu")) == 0
1411 || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Kubuntu")) == 0
1412 || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Lubuntu")) == 0)
1413 && !RT_C_IS_ALNUM(pszOsAndVersion[7]))
1414 {
1415 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_Ubuntu);
1416 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 7);
1417 }
1418 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Debian")) == 0
1419 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1420 {
1421 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_Debian);
1422 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1423 }
1424 else
1425 fRet = false;
1426
1427 /*
1428 * Skip forward till we get a number.
1429 */
1430 if (ppszNext)
1431 {
1432 *ppszNext = pszOsAndVersion;
1433 char ch;
1434 for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
1435 if (RT_C_IS_DIGIT(ch))
1436 {
1437 *ppszNext = pszVersion;
1438 break;
1439 }
1440 }
1441 return fRet;
1442}
1443
1444static bool detectLinuxDistroNameII(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext)
1445{
1446 bool fRet = true;
1447 if ( RTStrIStr(pszOsAndVersion, "RedHat") != NULL
1448 || RTStrIStr(pszOsAndVersion, "Red Hat") != NULL)
1449 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_RedHat);
1450 else if (RTStrIStr(pszOsAndVersion, "Oracle") != NULL)
1451 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_Oracle);
1452 else if (RTStrIStr(pszOsAndVersion, "CentOS") != NULL)
1453 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_RedHat);
1454 else if (RTStrIStr(pszOsAndVersion, "Fedora") != NULL)
1455 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_FedoraCore);
1456 else if (RTStrIStr(pszOsAndVersion, "Ubuntu") != NULL)
1457 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_Ubuntu);
1458 else if (RTStrIStr(pszOsAndVersion, "Debian"))
1459 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_Debian);
1460 else
1461 fRet = false;
1462
1463 /*
1464 * Skip forward till we get a number.
1465 */
1466 if (ppszNext)
1467 {
1468 *ppszNext = pszOsAndVersion;
1469 char ch;
1470 for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
1471 if (RT_C_IS_DIGIT(ch))
1472 {
1473 *ppszNext = pszVersion;
1474 break;
1475 }
1476 }
1477 return fRet;
1478}
1479
1480/**
1481 * Detect Linux distro ISOs.
1482 *
1483 * @returns COM status code.
1484 * @retval S_OK if detected
1485 * @retval S_FALSE if not fully detected.
1486 *
1487 * @param hVfsIso The ISO file system.
1488 * @param pBuf Read buffer.
1489 */
1490HRESULT Unattended::i_innerDetectIsoOSLinux(RTVFS hVfsIso, DETECTBUFFER *pBuf)
1491{
1492 /*
1493 * Redhat and derivatives may have a .treeinfo (ini-file style) with useful info
1494 * or at least a barebone .discinfo file.
1495 */
1496
1497 /*
1498 * Start with .treeinfo: https://release-engineering.github.io/productmd/treeinfo-1.0.html
1499 */
1500 RTVFSFILE hVfsFile;
1501 int vrc = RTVfsFileOpen(hVfsIso, ".treeinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1502 if (RT_SUCCESS(vrc))
1503 {
1504 RTINIFILE hIniFile;
1505 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1506 RTVfsFileRelease(hVfsFile);
1507 if (RT_SUCCESS(vrc))
1508 {
1509 /* Try figure the architecture first (like with windows). */
1510 vrc = RTIniFileQueryValue(hIniFile, "tree", "arch", pBuf->sz, sizeof(*pBuf), NULL);
1511 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1512 vrc = RTIniFileQueryValue(hIniFile, "general", "arch", pBuf->sz, sizeof(*pBuf), NULL);
1513 if (RT_FAILURE(vrc))
1514 LogRel(("Unattended: .treeinfo: No 'arch' property.\n"));
1515 else
1516 {
1517 LogRelFlow(("Unattended: .treeinfo: arch=%s\n", pBuf->sz));
1518 if (detectLinuxArch(pBuf->sz, &mEnmOsType, VBOXOSTYPE_RedHat))
1519 {
1520 /* Try figure the release name, it doesn't have to be redhat. */
1521 vrc = RTIniFileQueryValue(hIniFile, "release", "name", pBuf->sz, sizeof(*pBuf), NULL);
1522 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1523 vrc = RTIniFileQueryValue(hIniFile, "product", "name", pBuf->sz, sizeof(*pBuf), NULL);
1524 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1525 vrc = RTIniFileQueryValue(hIniFile, "general", "family", pBuf->sz, sizeof(*pBuf), NULL);
1526 if (RT_SUCCESS(vrc))
1527 {
1528 LogRelFlow(("Unattended: .treeinfo: name/family=%s\n", pBuf->sz));
1529 if (!detectLinuxDistroName(pBuf->sz, &mEnmOsType, NULL))
1530 {
1531 LogRel(("Unattended: .treeinfo: Unknown: name/family='%s', assuming Red Hat\n", pBuf->sz));
1532 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_x64) | VBOXOSTYPE_RedHat);
1533 }
1534 }
1535
1536 /* Try figure the version. */
1537 vrc = RTIniFileQueryValue(hIniFile, "release", "version", pBuf->sz, sizeof(*pBuf), NULL);
1538 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1539 vrc = RTIniFileQueryValue(hIniFile, "product", "version", pBuf->sz, sizeof(*pBuf), NULL);
1540 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1541 vrc = RTIniFileQueryValue(hIniFile, "general", "version", pBuf->sz, sizeof(*pBuf), NULL);
1542 if (RT_SUCCESS(vrc))
1543 {
1544 LogRelFlow(("Unattended: .treeinfo: version=%s\n", pBuf->sz));
1545 try { mStrDetectedOSVersion = RTStrStrip(pBuf->sz); }
1546 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1547 }
1548 }
1549 else
1550 LogRel(("Unattended: .treeinfo: Unknown: arch='%s'\n", pBuf->sz));
1551 }
1552
1553 RTIniFileRelease(hIniFile);
1554 }
1555
1556 if (mEnmOsType != VBOXOSTYPE_Unknown)
1557 return S_FALSE;
1558 }
1559
1560 /*
1561 * Try .discinfo next: https://release-engineering.github.io/productmd/discinfo-1.0.html
1562 * We will probably need additional info here...
1563 */
1564 vrc = RTVfsFileOpen(hVfsIso, ".discinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1565 if (RT_SUCCESS(vrc))
1566 {
1567 size_t cchIgn;
1568 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1569 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1570 RTVfsFileRelease(hVfsFile);
1571
1572 /* Parse and strip the first 5 lines. */
1573 const char *apszLines[5];
1574 char *psz = pBuf->sz;
1575 for (unsigned i = 0; i < RT_ELEMENTS(apszLines); i++)
1576 {
1577 apszLines[i] = psz;
1578 if (*psz)
1579 {
1580 char *pszEol = (char *)strchr(psz, '\n');
1581 if (!pszEol)
1582 psz = strchr(psz, '\0');
1583 else
1584 {
1585 *pszEol = '\0';
1586 apszLines[i] = RTStrStrip(psz);
1587 psz = pszEol + 1;
1588 }
1589 }
1590 }
1591
1592 /* Do we recognize the architecture? */
1593 LogRelFlow(("Unattended: .discinfo: arch=%s\n", apszLines[2]));
1594 if (detectLinuxArch(apszLines[2], &mEnmOsType, VBOXOSTYPE_RedHat))
1595 {
1596 /* Do we recognize the release string? */
1597 LogRelFlow(("Unattended: .discinfo: product+version=%s\n", apszLines[1]));
1598 const char *pszVersion = NULL;
1599 if (!detectLinuxDistroName(apszLines[1], &mEnmOsType, &pszVersion))
1600 LogRel(("Unattended: .discinfo: Unknown: release='%s'\n", apszLines[1]));
1601
1602 if (*pszVersion)
1603 {
1604 LogRelFlow(("Unattended: .discinfo: version=%s\n", pszVersion));
1605 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1606 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1607
1608 /* CentOS likes to call their release 'Final' without mentioning the actual version
1609 number (e.g. CentOS-4.7-x86_64-binDVD.iso), so we need to go look elsewhere.
1610 This is only important for centos 4.x and 3.x releases. */
1611 if (RTStrNICmp(pszVersion, RT_STR_TUPLE("Final")) == 0)
1612 {
1613 static const char * const s_apszDirs[] = { "CentOS/RPMS/", "RedHat/RPMS", "Server", "Workstation" };
1614 for (unsigned iDir = 0; iDir < RT_ELEMENTS(s_apszDirs); iDir++)
1615 {
1616 RTVFSDIR hVfsDir;
1617 vrc = RTVfsDirOpen(hVfsIso, s_apszDirs[iDir], 0, &hVfsDir);
1618 if (RT_FAILURE(vrc))
1619 continue;
1620 char szRpmDb[128];
1621 char szReleaseRpm[128];
1622 szRpmDb[0] = '\0';
1623 szReleaseRpm[0] = '\0';
1624 for (;;)
1625 {
1626 RTDIRENTRYEX DirEntry;
1627 size_t cbDirEntry = sizeof(DirEntry);
1628 vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING);
1629 if (RT_FAILURE(vrc))
1630 break;
1631
1632 /* redhat-release-4WS-2.4.i386.rpm
1633 centos-release-4-7.x86_64.rpm, centos-release-4-4.3.i386.rpm
1634 centos-release-5-3.el5.centos.1.x86_64.rpm */
1635 if ( (psz = strstr(DirEntry.szName, "-release-")) != NULL
1636 || (psz = strstr(DirEntry.szName, "-RELEASE-")) != NULL)
1637 {
1638 psz += 9;
1639 if (RT_C_IS_DIGIT(*psz))
1640 RTStrCopy(szReleaseRpm, sizeof(szReleaseRpm), psz);
1641 }
1642 /* rpmdb-redhat-4WS-2.4.i386.rpm,
1643 rpmdb-CentOS-4.5-0.20070506.i386.rpm,
1644 rpmdb-redhat-3.9-0.20070703.i386.rpm. */
1645 else if ( ( RTStrStartsWith(DirEntry.szName, "rpmdb-")
1646 || RTStrStartsWith(DirEntry.szName, "RPMDB-"))
1647 && RT_C_IS_DIGIT(DirEntry.szName[6]) )
1648 RTStrCopy(szRpmDb, sizeof(szRpmDb), &DirEntry.szName[6]);
1649 }
1650 RTVfsDirRelease(hVfsDir);
1651
1652 /* Did we find anything relvant? */
1653 psz = szRpmDb;
1654 if (!RT_C_IS_DIGIT(*psz))
1655 psz = szReleaseRpm;
1656 if (RT_C_IS_DIGIT(*psz))
1657 {
1658 /* Convert '-' to '.' and strip stuff which doesn't look like a version string. */
1659 char *pszCur = psz + 1;
1660 for (char ch = *pszCur; ch != '\0'; ch = *++pszCur)
1661 if (ch == '-')
1662 *pszCur = '.';
1663 else if (ch != '.' && !RT_C_IS_DIGIT(ch))
1664 {
1665 *pszCur = '\0';
1666 break;
1667 }
1668 while (&pszCur[-1] != psz && pszCur[-1] == '.')
1669 *--pszCur = '\0';
1670
1671 /* Set it and stop looking. */
1672 try { mStrDetectedOSVersion = psz; }
1673 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1674 break;
1675 }
1676 }
1677 }
1678 }
1679 }
1680 else
1681 LogRel(("Unattended: .discinfo: Unknown: arch='%s'\n", apszLines[2]));
1682
1683 if (mEnmOsType != VBOXOSTYPE_Unknown)
1684 return S_FALSE;
1685 }
1686
1687 /*
1688 * Ubuntu has a README.diskdefins file on their ISO (already on 4.10 / warty warthog).
1689 * Example content:
1690 * #define DISKNAME Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1
1691 * #define TYPE binary
1692 * #define TYPEbinary 1
1693 * #define ARCH amd64
1694 * #define ARCHamd64 1
1695 * #define DISKNUM 1
1696 * #define DISKNUM1 1
1697 * #define TOTALNUM 1
1698 * #define TOTALNUM1 1
1699 */
1700 vrc = RTVfsFileOpen(hVfsIso, "README.diskdefines", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1701 if (RT_SUCCESS(vrc))
1702 {
1703 size_t cchIgn;
1704 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1705 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1706 RTVfsFileRelease(hVfsFile);
1707
1708 /* Find the DISKNAME and ARCH defines. */
1709 const char *pszDiskName = NULL;
1710 const char *pszArch = NULL;
1711 char *psz = pBuf->sz;
1712 for (unsigned i = 0; *psz != '\0'; i++)
1713 {
1714 while (RT_C_IS_BLANK(*psz))
1715 psz++;
1716
1717 /* Match #define: */
1718 static const char s_szDefine[] = "#define";
1719 if ( strncmp(psz, s_szDefine, sizeof(s_szDefine) - 1) == 0
1720 && RT_C_IS_BLANK(psz[sizeof(s_szDefine) - 1]))
1721 {
1722 psz = &psz[sizeof(s_szDefine) - 1];
1723 while (RT_C_IS_BLANK(*psz))
1724 psz++;
1725
1726 /* Match the identifier: */
1727 char *pszIdentifier = psz;
1728 if (RT_C_IS_ALPHA(*psz) || *psz == '_')
1729 {
1730 do
1731 psz++;
1732 while (RT_C_IS_ALNUM(*psz) || *psz == '_');
1733 size_t cchIdentifier = (size_t)(psz - pszIdentifier);
1734
1735 /* Skip to the value. */
1736 while (RT_C_IS_BLANK(*psz))
1737 psz++;
1738 char *pszValue = psz;
1739
1740 /* Skip to EOL and strip the value. */
1741 char *pszEol = psz = strchr(psz, '\n');
1742 if (psz)
1743 *psz++ = '\0';
1744 else
1745 pszEol = strchr(pszValue, '\0');
1746 while (pszEol > pszValue && RT_C_IS_SPACE(pszEol[-1]))
1747 *--pszEol = '\0';
1748
1749 LogRelFlow(("Unattended: README.diskdefines: %.*s=%s\n", cchIdentifier, pszIdentifier, pszValue));
1750
1751 /* Do identifier matching: */
1752 if (cchIdentifier == sizeof("DISKNAME") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("DISKNAME")) == 0)
1753 pszDiskName = pszValue;
1754 else if (cchIdentifier == sizeof("ARCH") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("ARCH")) == 0)
1755 pszArch = pszValue;
1756 else
1757 continue;
1758 if (pszDiskName == NULL || pszArch == NULL)
1759 continue;
1760 break;
1761 }
1762 }
1763
1764 /* Next line: */
1765 psz = strchr(psz, '\n');
1766 if (!psz)
1767 break;
1768 psz++;
1769 }
1770
1771 /* Did we find both of them? */
1772 if (pszDiskName && pszArch)
1773 {
1774 if (detectLinuxArch(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1775 {
1776 const char *pszVersion = NULL;
1777 if (detectLinuxDistroName(pszDiskName, &mEnmOsType, &pszVersion))
1778 {
1779 LogRelFlow(("Unattended: README.diskdefines: version=%s\n", pszVersion));
1780 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1781 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1782 }
1783 else
1784 LogRel(("Unattended: README.diskdefines: Unknown: diskname='%s'\n", pszDiskName));
1785 }
1786 else
1787 LogRel(("Unattended: README.diskdefines: Unknown: arch='%s'\n", pszArch));
1788 }
1789 else
1790 LogRel(("Unattended: README.diskdefines: Did not find both DISKNAME and ARCH. :-/\n"));
1791
1792 if (mEnmOsType != VBOXOSTYPE_Unknown)
1793 return S_FALSE;
1794 }
1795
1796 /*
1797 * All of the debian based distro versions I checked have a single line ./disk/info file.
1798 * Only info I could find related to .disk folder is: https://lists.debian.org/debian-cd/2004/01/msg00069.html
1799 * Some example content from several install ISOs is as follows:
1800 * Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1 (20041020)
1801 * Linux Mint 20.3 "Una" - Release amd64 20220104
1802 * Debian GNU/Linux 11.2.0 "Bullseye" - Official amd64 NETINST 20211218-11:12
1803 * Debian GNU/Linux 9.13.0 "Stretch" - Official amd64 DVD Binary-1 20200718-11:07
1804 * Xubuntu 20.04.2.0 LTS "Focal Fossa" - Release amd64 (20210209.1)
1805 * Ubuntu 17.10 "Artful Aardvark" - Release amd64 (20180105.1)
1806 * Ubuntu 16.04.6 LTS "Xenial Xerus" - Release i386 (20190227.1)
1807 * Debian GNU/Linux 8.11.1 "Jessie" - Official amd64 CD Binary-1 20190211-02:10
1808 * Kali GNU/Linux 2021.3a "Kali-last-snapshot" - Official amd64 BD Binary-1 with firmware 20211015-16:55
1809 * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
1810 */
1811 vrc = RTVfsFileOpen(hVfsIso, ".disk/info", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1812 if (RT_SUCCESS(vrc))
1813 {
1814 size_t cchIgn;
1815 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1816 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1817
1818 pBuf->sz[sizeof(*pBuf) - 1] = '\0';
1819 RTVfsFileRelease(hVfsFile);
1820
1821 char *psz = pBuf->sz;
1822 char *pszDiskName = psz;
1823 char *pszArch = NULL;
1824
1825 /* Only care about the first line of the file even if it is multi line and assume disk name ended with ' - '.*/
1826 psz = RTStrStr(pBuf->sz, " - ");
1827 if (psz && memchr(pBuf->sz, '\n', (size_t)(psz - pBuf->sz)) == NULL)
1828 {
1829 *psz = '\0';
1830 psz += 3;
1831 if (*psz)
1832 pszArch = psz;
1833 }
1834
1835 /* Some Debian Live ISO's have info file content as follows:
1836 * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
1837 * thus pszArch stays empty. Try Volume Id (label) if we get lucky and get architecture from that. */
1838 if (!pszArch)
1839 {
1840 char szVolumeId[128];
1841 size_t cchVolumeId;
1842 vrc = RTVfsQueryLabel(hVfsIso, szVolumeId, 128, &cchVolumeId);
1843 if (RT_SUCCESS(vrc))
1844 {
1845 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1846 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", szVolumeId));
1847 }
1848 else
1849 LogRel(("Unattended: .disk/info No Volume Label found\n"));
1850 }
1851 else
1852 {
1853 if (!detectLinuxArchII(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1854 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", pszArch));
1855 }
1856
1857 if (pszDiskName)
1858 {
1859 const char *pszVersion = NULL;
1860 if (detectLinuxDistroNameII(pszDiskName, &mEnmOsType, &pszVersion))
1861 {
1862 LogRelFlow(("Unattended: .disk/info: version=%s\n", pszVersion));
1863 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1864 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1865 }
1866 else
1867 LogRel(("Unattended: .disk/info: Unknown: diskname='%s'\n", pszDiskName));
1868 }
1869
1870 if (mEnmOsType == VBOXOSTYPE_Unknown)
1871 LogRel(("Unattended: .disk/info: Did not find DISKNAME or/and ARCH. :-/\n"));
1872 else
1873 return S_FALSE;
1874 }
1875
1876 return S_FALSE;
1877}
1878
1879
1880/**
1881 * Detect OS/2 installation ISOs.
1882 *
1883 * Mainly aiming at ACP2/MCP2 as that's what we currently use in our testing.
1884 *
1885 * @returns COM status code.
1886 * @retval S_OK if detected
1887 * @retval S_FALSE if not fully detected.
1888 *
1889 * @param hVfsIso The ISO file system.
1890 * @param pBuf Read buffer.
1891 */
1892HRESULT Unattended::i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *pBuf)
1893{
1894 /*
1895 * The OS2SE20.SRC contains the location of the tree with the diskette
1896 * images, typically "\OS2IMAGE".
1897 */
1898 RTVFSFILE hVfsFile;
1899 int vrc = RTVfsFileOpen(hVfsIso, "OS2SE20.SRC", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1900 if (RT_SUCCESS(vrc))
1901 {
1902 size_t cbRead = 0;
1903 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
1904 RTVfsFileRelease(hVfsFile);
1905 if (RT_SUCCESS(vrc))
1906 {
1907 pBuf->sz[cbRead] = '\0';
1908 RTStrStrip(pBuf->sz);
1909 vrc = RTStrValidateEncoding(pBuf->sz);
1910 if (RT_SUCCESS(vrc))
1911 LogRelFlow(("Unattended: OS2SE20.SRC=%s\n", pBuf->sz));
1912 else
1913 LogRel(("Unattended: OS2SE20.SRC invalid encoding: %Rrc, %.*Rhxs\n", vrc, cbRead, pBuf->sz));
1914 }
1915 else
1916 LogRel(("Unattended: Error reading OS2SE20.SRC: %\n", vrc));
1917 }
1918 /*
1919 * ArcaOS has dropped the file, assume it's \OS2IMAGE and see if it's there.
1920 */
1921 else if (vrc == VERR_FILE_NOT_FOUND)
1922 RTStrCopy(pBuf->sz, sizeof(pBuf->sz), "\\OS2IMAGE");
1923 else
1924 return S_FALSE;
1925
1926 /*
1927 * Check that the directory directory exists and has a DISK_0 under it
1928 * with an OS2LDR on it.
1929 */
1930 size_t const cchOs2Image = strlen(pBuf->sz);
1931 vrc = RTPathAppend(pBuf->sz, sizeof(pBuf->sz), "DISK_0/OS2LDR");
1932 RTFSOBJINFO ObjInfo = {0};
1933 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1934 if (vrc == VERR_FILE_NOT_FOUND)
1935 {
1936 RTStrCat(pBuf->sz, sizeof(pBuf->sz), "."); /* eCS 2.0 image includes the dot from the 8.3 name. */
1937 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1938 }
1939 if ( RT_FAILURE(vrc)
1940 || !RTFS_IS_FILE(ObjInfo.Attr.fMode))
1941 {
1942 LogRel(("Unattended: RTVfsQueryPathInfo(, '%s' (from OS2SE20.SRC),) -> %Rrc, fMode=%#x\n",
1943 pBuf->sz, vrc, ObjInfo.Attr.fMode));
1944 return S_FALSE;
1945 }
1946
1947 /*
1948 * So, it's some kind of OS/2 2.x or later ISO alright.
1949 */
1950 mEnmOsType = VBOXOSTYPE_OS2;
1951 mStrDetectedOSHints.printf("OS2SE20.SRC=%.*s", cchOs2Image, pBuf->sz);
1952
1953 /*
1954 * ArcaOS ISOs seems to have a AOSBOOT dir on them.
1955 * This contains a ARCANOAE.FLG file with content we can use for the version:
1956 * ArcaOS 5.0.7 EN
1957 * Built 2021-12-07 18:34:34
1958 * We drop the "ArcaOS" bit, as it's covered by mEnmOsType. Then we pull up
1959 * the second line.
1960 *
1961 * Note! Yet to find a way to do unattended install of ArcaOS, as it comes
1962 * with no CD-boot floppy images, only simple .PF archive files for
1963 * unpacking onto the ram disk or whatever. Modifying these is
1964 * possible (ibsen's aPLib v0.36 compression with some simple custom
1965 * headers), but it would probably be a royal pain. Could perhaps
1966 * cook something from OS2IMAGE\DISK_0 thru 3...
1967 */
1968 vrc = RTVfsQueryPathInfo(hVfsIso, "AOSBOOT", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1969 if ( RT_SUCCESS(vrc)
1970 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
1971 {
1972 mEnmOsType = VBOXOSTYPE_ArcaOS;
1973
1974 /* Read the version file: */
1975 vrc = RTVfsFileOpen(hVfsIso, "SYS/ARCANOAE.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1976 if (RT_SUCCESS(vrc))
1977 {
1978 size_t cbRead = 0;
1979 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
1980 RTVfsFileRelease(hVfsFile);
1981 pBuf->sz[cbRead] = '\0';
1982 if (RT_SUCCESS(vrc))
1983 {
1984 /* Strip the OS name: */
1985 char *pszVersion = RTStrStrip(pBuf->sz);
1986 static char s_szArcaOS[] = "ArcaOS";
1987 if (RTStrStartsWith(pszVersion, s_szArcaOS))
1988 pszVersion = RTStrStripL(pszVersion + sizeof(s_szArcaOS) - 1);
1989
1990 /* Pull up the 2nd line if it, condensing the \r\n into a single space. */
1991 char *pszNewLine = strchr(pszVersion, '\n');
1992 if (pszNewLine && RTStrStartsWith(pszNewLine + 1, "Built 20"))
1993 {
1994 size_t offRemove = 0;
1995 while (RT_C_IS_SPACE(pszNewLine[-1 - (ssize_t)offRemove]))
1996 offRemove++;
1997 if (offRemove > 0)
1998 {
1999 pszNewLine -= offRemove;
2000 memmove(pszNewLine, pszNewLine + offRemove, strlen(pszNewLine + offRemove) - 1);
2001 }
2002 *pszNewLine = ' ';
2003 }
2004
2005 /* Drop any additional lines: */
2006 pszNewLine = strchr(pszVersion, '\n');
2007 if (pszNewLine)
2008 *pszNewLine = '\0';
2009 RTStrStripR(pszVersion);
2010
2011 /* Done (hope it makes some sense). */
2012 mStrDetectedOSVersion = pszVersion;
2013 }
2014 else
2015 LogRel(("Unattended: failed to read AOSBOOT/ARCANOAE.FLG: %Rrc\n", vrc));
2016 }
2017 else
2018 LogRel(("Unattended: failed to open AOSBOOT/ARCANOAE.FLG for reading: %Rrc\n", vrc));
2019 }
2020 /*
2021 * Similarly, eCS has an ECS directory and it typically contains a
2022 * ECS_INST.FLG file with the version info. Content differs a little:
2023 * eComStation 2.0 EN_US Thu May 13 10:27:54 pm 2010
2024 * Built on ECS60441318
2025 * Here we drop the "eComStation" bit and leave the 2nd line as it.
2026 *
2027 * Note! At least 2.0 has a DISKIMGS folder with what looks like boot
2028 * disks, so we could probably get something going here without
2029 * needing to write an OS2 boot sector...
2030 */
2031 else
2032 {
2033 vrc = RTVfsQueryPathInfo(hVfsIso, "ECS", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2034 if ( RT_SUCCESS(vrc)
2035 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2036 {
2037 mEnmOsType = VBOXOSTYPE_ECS;
2038
2039 /* Read the version file: */
2040 vrc = RTVfsFileOpen(hVfsIso, "ECS/ECS_INST.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2041 if (RT_SUCCESS(vrc))
2042 {
2043 size_t cbRead = 0;
2044 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2045 RTVfsFileRelease(hVfsFile);
2046 pBuf->sz[cbRead] = '\0';
2047 if (RT_SUCCESS(vrc))
2048 {
2049 /* Strip the OS name: */
2050 char *pszVersion = RTStrStrip(pBuf->sz);
2051 static char s_szECS[] = "eComStation";
2052 if (RTStrStartsWith(pszVersion, s_szECS))
2053 pszVersion = RTStrStripL(pszVersion + sizeof(s_szECS) - 1);
2054
2055 /* Drop any additional lines: */
2056 char *pszNewLine = strchr(pszVersion, '\n');
2057 if (pszNewLine)
2058 *pszNewLine = '\0';
2059 RTStrStripR(pszVersion);
2060
2061 /* Done (hope it makes some sense). */
2062 mStrDetectedOSVersion = pszVersion;
2063 }
2064 else
2065 LogRel(("Unattended: failed to read ECS/ECS_INST.FLG: %Rrc\n", vrc));
2066 }
2067 else
2068 LogRel(("Unattended: failed to open ECS/ECS_INST.FLG for reading: %Rrc\n", vrc));
2069 }
2070 else
2071 {
2072 /*
2073 * Official IBM OS/2 builds doesn't have any .FLG file on them,
2074 * so need to pry the information out in some other way. Best way
2075 * is to read the SYSLEVEL.OS2 file, which is typically on disk #2,
2076 * though on earlier versions (warp3) it was disk #1.
2077 */
2078 vrc = RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1,
2079 "/DISK_2/SYSLEVEL.OS2");
2080 if (RT_SUCCESS(vrc))
2081 {
2082 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2083 if (vrc == VERR_FILE_NOT_FOUND)
2084 {
2085 RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, "/DISK_1/SYSLEVEL.OS2");
2086 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2087 }
2088 if (RT_SUCCESS(vrc))
2089 {
2090 RT_ZERO(pBuf->ab);
2091 size_t cbRead = 0;
2092 vrc = RTVfsFileRead(hVfsFile, pBuf->ab, sizeof(pBuf->ab), &cbRead);
2093 RTVfsFileRelease(hVfsFile);
2094 if (RT_SUCCESS(vrc))
2095 {
2096 /* Check the header. */
2097 OS2SYSLEVELHDR const *pHdr = (OS2SYSLEVELHDR const *)&pBuf->ab[0];
2098 if ( pHdr->uMinusOne == UINT16_MAX
2099 && pHdr->uSyslevelFileVer == 1
2100 && memcmp(pHdr->achSignature, RT_STR_TUPLE("SYSLEVEL")) == 0
2101 && pHdr->offTable < cbRead
2102 && pHdr->offTable + sizeof(OS2SYSLEVELENTRY) <= cbRead)
2103 {
2104 OS2SYSLEVELENTRY *pEntry = (OS2SYSLEVELENTRY *)&pBuf->ab[pHdr->offTable];
2105 if ( RT_SUCCESS(RTStrValidateEncodingEx(pEntry->szName, sizeof(pEntry->szName),
2106 RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED))
2107 && RT_SUCCESS(RTStrValidateEncodingEx(pEntry->achCsdLevel, sizeof(pEntry->achCsdLevel), 0))
2108 && pEntry->bVersion != 0
2109 && ((pEntry->bVersion >> 4) & 0xf) < 10
2110 && (pEntry->bVersion & 0xf) < 10
2111 && pEntry->bModify < 10
2112 && pEntry->bRefresh < 10)
2113 {
2114 /* Flavor: */
2115 char *pszName = RTStrStrip(pEntry->szName);
2116 if (pszName)
2117 mStrDetectedOSFlavor = pszName;
2118
2119 /* Version: */
2120 if (pEntry->bRefresh != 0)
2121 mStrDetectedOSVersion.printf("%d.%d%d.%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2122 pEntry->bModify, pEntry->bRefresh);
2123 else
2124 mStrDetectedOSVersion.printf("%d.%d%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2125 pEntry->bModify);
2126 pEntry->achCsdLevel[sizeof(pEntry->achCsdLevel) - 1] = '\0';
2127 char *pszCsd = RTStrStrip(pEntry->achCsdLevel);
2128 if (*pszCsd != '\0')
2129 {
2130 mStrDetectedOSVersion.append(' ');
2131 mStrDetectedOSVersion.append(pszCsd);
2132 }
2133 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.50") >= 0)
2134 mEnmOsType = VBOXOSTYPE_OS2Warp45;
2135 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.00") >= 0)
2136 mEnmOsType = VBOXOSTYPE_OS2Warp4;
2137 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "3.00") >= 0)
2138 mEnmOsType = VBOXOSTYPE_OS2Warp3;
2139 }
2140 else
2141 LogRel(("Unattended: bogus SYSLEVEL.OS2 file entry: %.128Rhxd\n", pEntry));
2142 }
2143 else
2144 LogRel(("Unattended: bogus SYSLEVEL.OS2 file header: uMinusOne=%#x uSyslevelFileVer=%#x achSignature=%.8Rhxs offTable=%#x vs cbRead=%#zx\n",
2145 pHdr->uMinusOne, pHdr->uSyslevelFileVer, pHdr->achSignature, pHdr->offTable, cbRead));
2146 }
2147 else
2148 LogRel(("Unattended: failed to read SYSLEVEL.OS2: %Rrc\n", vrc));
2149 }
2150 else
2151 LogRel(("Unattended: failed to open '%s' for reading: %Rrc\n", pBuf->sz, vrc));
2152 }
2153 }
2154 }
2155
2156 /** @todo language detection? */
2157
2158 /*
2159 * Only tested ACP2, so only return S_OK for it.
2160 */
2161 if ( mEnmOsType == VBOXOSTYPE_OS2Warp45
2162 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.52") >= 0
2163 && mStrDetectedOSFlavor.contains("Server", RTCString::CaseInsensitive))
2164 return S_OK;
2165
2166 return S_FALSE;
2167}
2168
2169
2170/**
2171 * Detect FreeBSD distro ISOs.
2172 *
2173 * @returns COM status code.
2174 * @retval S_OK if detected
2175 * @retval S_FALSE if not fully detected.
2176 *
2177 * @param hVfsIso The ISO file system.
2178 * @param pBuf Read buffer.
2179 */
2180HRESULT Unattended::i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2181{
2182 RT_NOREF(pBuf);
2183
2184 /*
2185 * FreeBSD since 10.0 has a .profile file in the root which can be used to determine that this is FreeBSD
2186 * along with the version.
2187 */
2188
2189 RTVFSFILE hVfsFile;
2190 HRESULT hrc = S_FALSE;
2191 int vrc = RTVfsFileOpen(hVfsIso, ".profile", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2192 if (RT_SUCCESS(vrc))
2193 {
2194 static const uint8_t s_abFreeBsdHdr[] = "# $FreeBSD: releng/";
2195 char abRead[32];
2196
2197 vrc = RTVfsFileRead(hVfsFile, &abRead[0], sizeof(abRead), NULL /*pcbRead*/);
2198 if ( RT_SUCCESS(vrc)
2199 && !memcmp(&abRead[0], &s_abFreeBsdHdr[0], sizeof(s_abFreeBsdHdr) - 1)) /* Skip terminator */
2200 {
2201 abRead[sizeof(abRead) - 1] = '\0';
2202
2203 /* Detect the architecture using the volume label. */
2204 char szVolumeId[128];
2205 size_t cchVolumeId;
2206 vrc = RTVfsQueryLabel(hVfsIso, szVolumeId, 128, &cchVolumeId);
2207 if (RT_SUCCESS(vrc))
2208 {
2209 /* Can re-use the Linux code here. */
2210 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_FreeBSD))
2211 LogRel(("Unattended/FBSD: Unknown: arch='%s'\n", szVolumeId));
2212
2213 /* Detect the version from the string coming after the needle in .profile. */
2214 AssertCompile(sizeof(s_abFreeBsdHdr) - 1 < sizeof(abRead));
2215
2216 char *pszVersionStart = &abRead[sizeof(s_abFreeBsdHdr) - 1];
2217 char *pszVersionEnd = pszVersionStart;
2218
2219 while (RT_C_IS_DIGIT(*pszVersionEnd))
2220 pszVersionEnd++;
2221 if (*pszVersionEnd == '.')
2222 {
2223 pszVersionEnd++; /* Skip the . */
2224
2225 while (RT_C_IS_DIGIT(*pszVersionEnd))
2226 pszVersionEnd++;
2227
2228 /* Terminate the version string. */
2229 *pszVersionEnd = '\0';
2230
2231 try { mStrDetectedOSVersion = pszVersionStart; }
2232 catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
2233 }
2234 else
2235 LogRel(("Unattended/FBSD: Unknown: version='%s'\n", &abRead[0]));
2236 }
2237 else
2238 {
2239 LogRel(("Unattended/FBSD: No Volume Label found\n"));
2240 mEnmOsType = VBOXOSTYPE_FreeBSD;
2241 }
2242
2243 hrc = S_OK;
2244 }
2245
2246 RTVfsFileRelease(hVfsFile);
2247 }
2248
2249 return hrc;
2250}
2251
2252
2253HRESULT Unattended::prepare()
2254{
2255 LogFlow(("Unattended::prepare: enter\n"));
2256
2257 /*
2258 * Must have a machine.
2259 */
2260 ComPtr<Machine> ptrMachine;
2261 Guid MachineUuid;
2262 {
2263 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2264 ptrMachine = mMachine;
2265 if (ptrMachine.isNull())
2266 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("No machine associated with this IUnatteded instance"));
2267 MachineUuid = mMachineUuid;
2268 }
2269
2270 /*
2271 * Before we write lock ourselves, we must get stuff from Machine and
2272 * VirtualBox because their locks have higher priorities than ours.
2273 */
2274 Utf8Str strGuestOsTypeId;
2275 Utf8Str strMachineName;
2276 Utf8Str strDefaultAuxBasePath;
2277 HRESULT hrc;
2278 try
2279 {
2280 Bstr bstrTmp;
2281 hrc = ptrMachine->COMGETTER(OSTypeId)(bstrTmp.asOutParam());
2282 if (SUCCEEDED(hrc))
2283 {
2284 strGuestOsTypeId = bstrTmp;
2285 hrc = ptrMachine->COMGETTER(Name)(bstrTmp.asOutParam());
2286 if (SUCCEEDED(hrc))
2287 strMachineName = bstrTmp;
2288 }
2289 int vrc = ptrMachine->i_calculateFullPath(Utf8StrFmt("Unattended-%RTuuid-", MachineUuid.raw()), strDefaultAuxBasePath);
2290 if (RT_FAILURE(vrc))
2291 return setErrorBoth(E_FAIL, vrc);
2292 }
2293 catch (std::bad_alloc &)
2294 {
2295 return E_OUTOFMEMORY;
2296 }
2297 bool const fIs64Bit = i_isGuestOSArchX64(strGuestOsTypeId);
2298
2299 BOOL fRtcUseUtc = FALSE;
2300 hrc = ptrMachine->COMGETTER(RTCUseUTC)(&fRtcUseUtc);
2301 if (FAILED(hrc))
2302 return hrc;
2303
2304 FirmwareType_T enmFirmware = FirmwareType_BIOS;
2305 hrc = ptrMachine->COMGETTER(FirmwareType)(&enmFirmware);
2306 if (FAILED(hrc))
2307 return hrc;
2308
2309 /*
2310 * Write lock this object and set attributes we got from IMachine.
2311 */
2312 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2313
2314 mStrGuestOsTypeId = strGuestOsTypeId;
2315 mfGuestOs64Bit = fIs64Bit;
2316 mfRtcUseUtc = RT_BOOL(fRtcUseUtc);
2317 menmFirmwareType = enmFirmware;
2318
2319 /*
2320 * Do some state checks.
2321 */
2322 if (mpInstaller != NULL)
2323 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The prepare method has been called (must call done to restart)"));
2324 if ((Machine *)ptrMachine != (Machine *)mMachine)
2325 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The 'machine' while we were using it - please don't do that"));
2326
2327 /*
2328 * Check if the specified ISOs and files exist.
2329 */
2330 if (!RTFileExists(mStrIsoPath.c_str()))
2331 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the installation ISO file '%s'"),
2332 mStrIsoPath.c_str());
2333 if (mfInstallGuestAdditions && !RTFileExists(mStrAdditionsIsoPath.c_str()))
2334 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the Guest Additions ISO file '%s'"),
2335 mStrAdditionsIsoPath.c_str());
2336 if (mfInstallTestExecService && !RTFileExists(mStrValidationKitIsoPath.c_str()))
2337 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the validation kit ISO file '%s'"),
2338 mStrValidationKitIsoPath.c_str());
2339 if (mStrScriptTemplatePath.isNotEmpty() && !RTFileExists(mStrScriptTemplatePath.c_str()))
2340 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate unattended installation script template '%s'"),
2341 mStrScriptTemplatePath.c_str());
2342
2343 /*
2344 * Do media detection if it haven't been done yet.
2345 */
2346 if (!mfDoneDetectIsoOS)
2347 {
2348 hrc = detectIsoOS();
2349 if (FAILED(hrc) && hrc != E_NOTIMPL)
2350 return hrc;
2351 }
2352
2353 /*
2354 * We can now check midxImage against mDetectedImages, since the latter is
2355 * populated during the detectIsoOS call. We ignore midxImage if no images
2356 * were detected, assuming that it's not relevant or used for different purposes.
2357 */
2358 if (mDetectedImages.size() > 0)
2359 {
2360 bool fImageFound = false;
2361 for (size_t i = 0; i < mDetectedImages.size(); ++i)
2362 if (midxImage == mDetectedImages[i].mImageIndex)
2363 {
2364 i_updateDetectedAttributeForImage(mDetectedImages[i]);
2365 fImageFound = true;
2366 break;
2367 }
2368 if (!fImageFound)
2369 return setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("imageIndex value %u not found in detectedImageIndices"), midxImage);
2370 }
2371
2372 /*
2373 * Get the ISO's detect guest OS type info and make it's a known one (just
2374 * in case the above step doesn't work right).
2375 */
2376 uint32_t const idxIsoOSType = Global::getOSTypeIndexFromId(mStrDetectedOSTypeId.c_str());
2377 VBOXOSTYPE const enmIsoOSType = idxIsoOSType < Global::cOSTypes ? Global::sOSTypes[idxIsoOSType].osType : VBOXOSTYPE_Unknown;
2378 if ((enmIsoOSType & VBOXOSTYPE_OsTypeMask) == VBOXOSTYPE_Unknown)
2379 return setError(E_FAIL, tr("The supplied ISO file does not contain an OS currently supported for unattended installation"));
2380
2381 /*
2382 * Get the VM's configured guest OS type info.
2383 */
2384 uint32_t const idxMachineOSType = Global::getOSTypeIndexFromId(mStrGuestOsTypeId.c_str());
2385 VBOXOSTYPE const enmMachineOSType = idxMachineOSType < Global::cOSTypes
2386 ? Global::sOSTypes[idxMachineOSType].osType : VBOXOSTYPE_Unknown;
2387
2388 /*
2389 * Check that the detected guest OS type for the ISO is compatible with
2390 * that of the VM, boardly speaking.
2391 */
2392 if (idxMachineOSType != idxIsoOSType)
2393 {
2394 /* Check that the architecture is compatible: */
2395 if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask)
2396 && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86
2397 || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64))
2398 return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch"));
2399
2400 /** @todo check BIOS/EFI requirement */
2401 }
2402
2403 /*
2404 * Do some default property stuff and check other properties.
2405 */
2406 try
2407 {
2408 char szTmp[128];
2409
2410 if (mStrLocale.isEmpty())
2411 {
2412 int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp));
2413 if ( RT_SUCCESS(vrc)
2414 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp))
2415 mStrLocale.assign(szTmp, 5);
2416 else
2417 mStrLocale = "en_US";
2418 Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale));
2419 }
2420
2421 if (mStrLanguage.isEmpty())
2422 {
2423 if (mDetectedOSLanguages.size() > 0)
2424 mStrLanguage = mDetectedOSLanguages[0];
2425 else
2426 mStrLanguage.assign(mStrLocale).findReplace('_', '-');
2427 }
2428
2429 if (mStrCountry.isEmpty())
2430 {
2431 int vrc = RTLocaleQueryUserCountryCode(szTmp);
2432 if (RT_SUCCESS(vrc))
2433 mStrCountry = szTmp;
2434 else if ( mStrLocale.isNotEmpty()
2435 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale))
2436 mStrCountry.assign(mStrLocale, 3, 2);
2437 else
2438 mStrCountry = "US";
2439 }
2440
2441 if (mStrTimeZone.isEmpty())
2442 {
2443 int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp));
2444 if ( RT_SUCCESS(vrc)
2445 && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */)
2446 mStrTimeZone = szTmp;
2447 else
2448 mStrTimeZone = "Etc/UTC";
2449 Assert(mStrTimeZone.isNotEmpty());
2450 }
2451 mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str());
2452 if (!mpTimeZoneInfo)
2453 mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str());
2454 Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC");
2455 if (!mpTimeZoneInfo)
2456 LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str()));
2457
2458 if (mStrHostname.isEmpty())
2459 {
2460 /* Mangle the VM name into a valid hostname. */
2461 for (size_t i = 0; i < strMachineName.length(); i++)
2462 {
2463 char ch = strMachineName[i];
2464 if ( (unsigned)ch < 127
2465 && RT_C_IS_ALNUM(ch))
2466 mStrHostname.append(ch);
2467 else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-"))
2468 mStrHostname.append('-');
2469 }
2470 if (mStrHostname.length() == 0)
2471 mStrHostname.printf("%RTuuid-vm", MachineUuid.raw());
2472 else if (mStrHostname.length() < 3)
2473 mStrHostname.append("-vm");
2474 mStrHostname.append(".myguest.virtualbox.org");
2475 }
2476
2477 if (mStrAuxiliaryBasePath.isEmpty())
2478 {
2479 mStrAuxiliaryBasePath = strDefaultAuxBasePath;
2480 mfIsDefaultAuxiliaryBasePath = true;
2481 }
2482 }
2483 catch (std::bad_alloc &)
2484 {
2485 return E_OUTOFMEMORY;
2486 }
2487
2488 /*
2489 * Instatiate the guest installer matching the ISO.
2490 */
2491 mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion,
2492 mStrDetectedOSFlavor, mStrDetectedOSHints, this);
2493 if (mpInstaller != NULL)
2494 {
2495 hrc = mpInstaller->initInstaller();
2496 if (SUCCEEDED(hrc))
2497 {
2498 /*
2499 * Do the script preps (just reads them).
2500 */
2501 hrc = mpInstaller->prepareUnattendedScripts();
2502 if (SUCCEEDED(hrc))
2503 {
2504 LogFlow(("Unattended::prepare: returns S_OK\n"));
2505 return S_OK;
2506 }
2507 }
2508
2509 /* Destroy the installer instance. */
2510 delete mpInstaller;
2511 mpInstaller = NULL;
2512 }
2513 else
2514 hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND,
2515 tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str());
2516 LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc));
2517 return hrc;
2518}
2519
2520HRESULT Unattended::constructMedia()
2521{
2522 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2523
2524 LogFlow(("===========================================================\n"));
2525 LogFlow(("Call Unattended::constructMedia()\n"));
2526
2527 if (mpInstaller == NULL)
2528 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called");
2529
2530 return mpInstaller->prepareMedia();
2531}
2532
2533HRESULT Unattended::reconfigureVM()
2534{
2535 LogFlow(("===========================================================\n"));
2536 LogFlow(("Call Unattended::reconfigureVM()\n"));
2537
2538 /*
2539 * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues.
2540 */
2541 StorageBus_T enmRecommendedStorageBus = StorageBus_IDE;
2542 {
2543 Bstr bstrGuestOsTypeId;
2544 Bstr bstrDetectedOSTypeId;
2545 {
2546 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2547 if (mpInstaller == NULL)
2548 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2549 bstrGuestOsTypeId = mStrGuestOsTypeId;
2550 bstrDetectedOSTypeId = mStrDetectedOSTypeId;
2551 }
2552 ComPtr<IGuestOSType> ptrGuestOSType;
2553 HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam());
2554 if (SUCCEEDED(hrc))
2555 {
2556 if (!ptrGuestOSType.isNull())
2557 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus);
2558 }
2559 if (FAILED(hrc))
2560 return hrc;
2561
2562 /* If the detected guest OS type differs, log a warning if their DVD storage
2563 bus recommendations differ. */
2564 if (bstrGuestOsTypeId != bstrDetectedOSTypeId)
2565 {
2566 StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE;
2567 hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam());
2568 if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull())
2569 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2);
2570 if (FAILED(hrc))
2571 return hrc;
2572
2573 if (enmRecommendedStorageBus != enmRecommendedStorageBus2)
2574 LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n",
2575 ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(),
2576 ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() ));
2577 }
2578 }
2579
2580 /*
2581 * Take write lock (for lock order reasons, write lock our parent object too)
2582 * then make sure we're the only caller of this method.
2583 */
2584 AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS);
2585 HRESULT hrc;
2586 if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD)
2587 {
2588 RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf();
2589 mhThreadReconfigureVM = hNativeSelf;
2590
2591 /*
2592 * Create a new session, lock the machine and get the session machine object.
2593 * Do the locking without pinning down the write locks, just to be on the safe side.
2594 */
2595 ComPtr<ISession> ptrSession;
2596 try
2597 {
2598 hrc = ptrSession.createInprocObject(CLSID_Session);
2599 }
2600 catch (std::bad_alloc &)
2601 {
2602 hrc = E_OUTOFMEMORY;
2603 }
2604 if (SUCCEEDED(hrc))
2605 {
2606 alock.release();
2607 hrc = mMachine->LockMachine(ptrSession, LockType_Shared);
2608 alock.acquire();
2609 if (SUCCEEDED(hrc))
2610 {
2611 ComPtr<IMachine> ptrSessionMachine;
2612 hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam());
2613 if (SUCCEEDED(hrc))
2614 {
2615 /*
2616 * Hand the session to the inner work and let it do it job.
2617 */
2618 try
2619 {
2620 hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine);
2621 }
2622 catch (...)
2623 {
2624 hrc = E_UNEXPECTED;
2625 }
2626 }
2627
2628 /* Paranoia: release early in case we it a bump below. */
2629 Assert(mhThreadReconfigureVM == hNativeSelf);
2630 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2631
2632 /*
2633 * While unlocking the machine we'll have to drop the locks again.
2634 */
2635 alock.release();
2636
2637 ptrSessionMachine.setNull();
2638 HRESULT hrc2 = ptrSession->UnlockMachine();
2639 AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2));
2640
2641 ptrSession.setNull();
2642
2643 alock.acquire();
2644 }
2645 else
2646 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2647 }
2648 else
2649 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2650 }
2651 else
2652 hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread"));
2653 return hrc;
2654}
2655
2656
2657HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
2658 ComPtr<IMachine> const &rPtrSessionMachine)
2659{
2660 if (mpInstaller == NULL)
2661 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2662
2663 // Fetch all available storage controllers
2664 com::SafeIfaceArray<IStorageController> arrayOfControllers;
2665 HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers));
2666 AssertComRCReturn(hrc, hrc);
2667
2668 /*
2669 * Figure out where the images are to be mounted, adding controllers/ports as needed.
2670 */
2671 std::vector<UnattendedInstallationDisk> vecInstallationDisks;
2672 if (mpInstaller->isAuxiliaryFloppyNeeded())
2673 {
2674 hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock);
2675 if (FAILED(hrc))
2676 return hrc;
2677 }
2678
2679 hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus);
2680 if (FAILED(hrc))
2681 return hrc;
2682
2683 /*
2684 * Mount the images.
2685 */
2686 for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++)
2687 {
2688 UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage);
2689 Assert(pImage->strImagePath.isNotEmpty());
2690 hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock);
2691 if (FAILED(hrc))
2692 return hrc;
2693 }
2694
2695 /*
2696 * Set the boot order.
2697 *
2698 * ASSUME that the HD isn't bootable when we start out, but it will be what
2699 * we boot from after the first stage of the installation is done. Setting
2700 * it first prevents endless reboot cylces.
2701 */
2702 /** @todo consider making 100% sure the disk isn't bootable (edit partition
2703 * table active bits and EFI stuff). */
2704 Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD
2705 || mpInstaller->getBootableDeviceType() == DeviceType_Floppy);
2706 hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk);
2707 if (SUCCEEDED(hrc))
2708 hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType());
2709 if (SUCCEEDED(hrc))
2710 hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD
2711 ? DeviceType_Floppy : DeviceType_DVD);
2712 if (FAILED(hrc))
2713 return hrc;
2714
2715 /*
2716 * Essential step.
2717 *
2718 * HACK ALERT! We have to release the lock here or we'll get into trouble with
2719 * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType).
2720 */
2721 if (SUCCEEDED(hrc))
2722 {
2723 rAutoLock.release();
2724 hrc = rPtrSessionMachine->SaveSettings();
2725 rAutoLock.acquire();
2726 }
2727
2728 return hrc;
2729}
2730
2731/**
2732 * Makes sure we've got a floppy drive attached to a floppy controller, adding
2733 * the auxiliary floppy image to the installation disk vector.
2734 *
2735 * @returns COM status code.
2736 * @param rControllers The existing controllers.
2737 * @param rVecInstallatationDisks The list of image to mount.
2738 * @param rPtrSessionMachine The session machine smart pointer.
2739 * @param rAutoLock The lock.
2740 */
2741HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
2742 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
2743 ComPtr<IMachine> const &rPtrSessionMachine,
2744 AutoMultiWriteLock2 &rAutoLock)
2745{
2746 Assert(mpInstaller->isAuxiliaryFloppyNeeded());
2747
2748 /*
2749 * Look for a floppy controller with a primary drive (A:) we can "insert"
2750 * the auxiliary floppy image. Add a controller and/or a drive if necessary.
2751 */
2752 bool fFoundPort0Dev0 = false;
2753 Bstr bstrControllerName;
2754 Utf8Str strControllerName;
2755
2756 for (size_t i = 0; i < rControllers.size(); ++i)
2757 {
2758 StorageBus_T enmStorageBus;
2759 HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
2760 AssertComRCReturn(hrc, hrc);
2761 if (enmStorageBus == StorageBus_Floppy)
2762 {
2763
2764 /*
2765 * Found a floppy controller.
2766 */
2767 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
2768 AssertComRCReturn(hrc, hrc);
2769
2770 /*
2771 * Check the attchments to see if we've got a device 0 attached on port 0.
2772 *
2773 * While we're at it we eject flppies from all floppy drives we encounter,
2774 * we don't want any confusion at boot or during installation.
2775 */
2776 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
2777 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
2778 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
2779 AssertComRCReturn(hrc, hrc);
2780 strControllerName = bstrControllerName;
2781 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
2782
2783 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
2784 {
2785 LONG iPort = -1;
2786 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
2787 AssertComRCReturn(hrc, hrc);
2788
2789 LONG iDevice = -1;
2790 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
2791 AssertComRCReturn(hrc, hrc);
2792
2793 DeviceType_T enmType;
2794 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
2795 AssertComRCReturn(hrc, hrc);
2796
2797 if (enmType == DeviceType_Floppy)
2798 {
2799 ComPtr<IMedium> ptrMedium;
2800 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
2801 AssertComRCReturn(hrc, hrc);
2802
2803 if (ptrMedium.isNotNull())
2804 {
2805 ptrMedium.setNull();
2806 rAutoLock.release();
2807 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
2808 rAutoLock.acquire();
2809 }
2810
2811 if (iPort == 0 && iDevice == 0)
2812 fFoundPort0Dev0 = true;
2813 }
2814 else if (iPort == 0 && iDevice == 0)
2815 return setError(E_FAIL,
2816 tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"),
2817 bstrControllerName.raw());
2818 }
2819 }
2820 }
2821
2822 /*
2823 * Add a floppy controller if we need to.
2824 */
2825 if (strControllerName.isEmpty())
2826 {
2827 bstrControllerName = strControllerName = "Floppy";
2828 ComPtr<IStorageController> ptrControllerIgnored;
2829 HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy,
2830 ptrControllerIgnored.asOutParam());
2831 LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc));
2832 if (FAILED(hrc))
2833 return hrc;
2834 }
2835
2836 /*
2837 * Adding a floppy drive (if needed) and mounting the auxiliary image is
2838 * done later together with the ISOs.
2839 */
2840 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName,
2841 DeviceType_Floppy, AccessMode_ReadWrite,
2842 0, 0,
2843 fFoundPort0Dev0 /*fMountOnly*/,
2844 mpInstaller->getAuxiliaryFloppyFilePath()));
2845 return S_OK;
2846}
2847
2848/**
2849 * Reconfigures DVD drives of the VM to mount all the ISOs we need.
2850 *
2851 * This will umount all DVD media.
2852 *
2853 * @returns COM status code.
2854 * @param rControllers The existing controllers.
2855 * @param rVecInstallatationDisks The list of image to mount.
2856 * @param rPtrSessionMachine The session machine smart pointer.
2857 * @param rAutoLock The lock.
2858 * @param enmRecommendedStorageBus The recommended storage bus type for adding
2859 * DVD drives on.
2860 */
2861HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
2862 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
2863 ComPtr<IMachine> const &rPtrSessionMachine,
2864 AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus)
2865{
2866 /*
2867 * Enumerate the attachements of every controller, looking for DVD drives,
2868 * ASSUMEING all drives are bootable.
2869 *
2870 * Eject the medium from all the drives (don't want any confusion) and look
2871 * for the recommended storage bus in case we need to add more drives.
2872 */
2873 HRESULT hrc;
2874 std::list<ControllerSlot> lstControllerDvdSlots;
2875 Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */
2876 Utf8Str strControllerName;
2877 Bstr bstrControllerName;
2878 for (size_t i = 0; i < rControllers.size(); ++i)
2879 {
2880 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
2881 AssertComRCReturn(hrc, hrc);
2882 strControllerName = bstrControllerName;
2883
2884 /* Look for recommended storage bus. */
2885 StorageBus_T enmStorageBus;
2886 hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
2887 AssertComRCReturn(hrc, hrc);
2888 if (enmStorageBus == enmRecommendedStorageBus)
2889 {
2890 strRecommendedControllerName = bstrControllerName;
2891 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
2892 }
2893
2894 /* Scan the controller attachments. */
2895 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
2896 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
2897 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
2898 AssertComRCReturn(hrc, hrc);
2899
2900 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
2901 {
2902 DeviceType_T enmType;
2903 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
2904 AssertComRCReturn(hrc, hrc);
2905 if (enmType == DeviceType_DVD)
2906 {
2907 LONG iPort = -1;
2908 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
2909 AssertComRCReturn(hrc, hrc);
2910
2911 LONG iDevice = -1;
2912 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
2913 AssertComRCReturn(hrc, hrc);
2914
2915 /* Remeber it. */
2916 lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/));
2917
2918 /* Eject the medium, if any. */
2919 ComPtr<IMedium> ptrMedium;
2920 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
2921 AssertComRCReturn(hrc, hrc);
2922 if (ptrMedium.isNotNull())
2923 {
2924 ptrMedium.setNull();
2925
2926 rAutoLock.release();
2927 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
2928 rAutoLock.acquire();
2929 }
2930 }
2931 }
2932 }
2933
2934 /*
2935 * How many drives do we need? Add more if necessary.
2936 */
2937 ULONG cDvdDrivesNeeded = 0;
2938 if (mpInstaller->isAuxiliaryIsoNeeded())
2939 cDvdDrivesNeeded++;
2940 if (mpInstaller->isOriginalIsoNeeded())
2941 cDvdDrivesNeeded++;
2942#if 0 /* These are now in the AUX VISO. */
2943 if (mpInstaller->isAdditionsIsoNeeded())
2944 cDvdDrivesNeeded++;
2945 if (mpInstaller->isValidationKitIsoNeeded())
2946 cDvdDrivesNeeded++;
2947#endif
2948 Assert(cDvdDrivesNeeded > 0);
2949 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
2950 {
2951 /* Do we need to add the recommended controller? */
2952 if (strRecommendedControllerName.isEmpty())
2953 {
2954 switch (enmRecommendedStorageBus)
2955 {
2956 case StorageBus_IDE: strRecommendedControllerName = "IDE"; break;
2957 case StorageBus_SATA: strRecommendedControllerName = "SATA"; break;
2958 case StorageBus_SCSI: strRecommendedControllerName = "SCSI"; break;
2959 case StorageBus_SAS: strRecommendedControllerName = "SAS"; break;
2960 case StorageBus_USB: strRecommendedControllerName = "USB"; break;
2961 case StorageBus_PCIe: strRecommendedControllerName = "PCIe"; break;
2962 default:
2963 return setError(E_FAIL, tr("Support for recommended storage bus %d not implemented"),
2964 (int)enmRecommendedStorageBus);
2965 }
2966 ComPtr<IStorageController> ptrControllerIgnored;
2967 hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus,
2968 ptrControllerIgnored.asOutParam());
2969 LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc));
2970 if (FAILED(hrc))
2971 return hrc;
2972 }
2973
2974 /* Add free controller slots, maybe raising the port limit on the controller if we can. */
2975 hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine,
2976 cDvdDrivesNeeded, lstControllerDvdSlots);
2977 if (FAILED(hrc))
2978 return hrc;
2979 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
2980 {
2981 /* We could in many cases create another controller here, but it's not worth the effort. */
2982 return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "",
2983 cDvdDrivesNeeded - lstControllerDvdSlots.size()),
2984 strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size());
2985 }
2986 Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size());
2987 }
2988
2989 /*
2990 * Sort the DVD slots in boot order.
2991 */
2992 lstControllerDvdSlots.sort();
2993
2994 /*
2995 * Prepare ISO mounts.
2996 *
2997 * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots
2998 * according to the boot order.
2999 */
3000 std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin();
3001 if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso())
3002 {
3003 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath()));
3004 ++itDvdSlot;
3005 }
3006
3007 if (mpInstaller->isOriginalIsoNeeded())
3008 {
3009 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath()));
3010 ++itDvdSlot;
3011 }
3012
3013 if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso())
3014 {
3015 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath()));
3016 ++itDvdSlot;
3017 }
3018
3019#if 0 /* These are now in the AUX VISO. */
3020 if (mpInstaller->isAdditionsIsoNeeded())
3021 {
3022 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath()));
3023 ++itDvdSlot;
3024 }
3025
3026 if (mpInstaller->isValidationKitIsoNeeded())
3027 {
3028 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath()));
3029 ++itDvdSlot;
3030 }
3031#endif
3032
3033 return S_OK;
3034}
3035
3036/**
3037 * Used to find more free slots for DVD drives during VM reconfiguration.
3038 *
3039 * This may modify the @a portCount property of the given controller.
3040 *
3041 * @returns COM status code.
3042 * @param rStrControllerName The name of the controller to find/create
3043 * free slots on.
3044 * @param enmStorageBus The storage bus type.
3045 * @param rPtrSessionMachine Reference to the session machine.
3046 * @param cSlotsNeeded Total slots needed (including those we've
3047 * already found).
3048 * @param rDvdSlots The slot collection for DVD drives to add
3049 * free slots to as we find/create them.
3050 */
3051HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
3052 ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
3053 std::list<ControllerSlot> &rDvdSlots)
3054{
3055 Assert(cSlotsNeeded > rDvdSlots.size());
3056
3057 /*
3058 * Get controlleer stats.
3059 */
3060 ComPtr<IStorageController> pController;
3061 HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam());
3062 AssertComRCReturn(hrc, hrc);
3063
3064 ULONG cMaxDevicesPerPort = 1;
3065 hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort);
3066 AssertComRCReturn(hrc, hrc);
3067 AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED);
3068
3069 ULONG cPorts = 0;
3070 hrc = pController->COMGETTER(PortCount)(&cPorts);
3071 AssertComRCReturn(hrc, hrc);
3072
3073 /*
3074 * Get the attachment list and turn into an internal list for lookup speed.
3075 */
3076 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3077 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(),
3078 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3079 AssertComRCReturn(hrc, hrc);
3080
3081 std::vector<ControllerSlot> arrayOfUsedSlots;
3082 for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++)
3083 {
3084 LONG iPort = -1;
3085 hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort);
3086 AssertComRCReturn(hrc, hrc);
3087
3088 LONG iDevice = -1;
3089 hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice);
3090 AssertComRCReturn(hrc, hrc);
3091
3092 arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/));
3093 }
3094
3095 /*
3096 * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots.
3097 */
3098 for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++)
3099 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3100 {
3101 bool fFound = false;
3102 for (size_t i = 0; i < arrayOfUsedSlots.size(); i++)
3103 if ( arrayOfUsedSlots[i].iPort == iPort
3104 && arrayOfUsedSlots[i].iDevice == iDevice)
3105 {
3106 fFound = true;
3107 break;
3108 }
3109 if (!fFound)
3110 {
3111 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3112 if (rDvdSlots.size() >= cSlotsNeeded)
3113 return S_OK;
3114 }
3115 }
3116
3117 /*
3118 * Okay we still need more ports. See if increasing the number of controller
3119 * ports would solve it.
3120 */
3121 ULONG cMaxPorts = 1;
3122 hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts);
3123 AssertComRCReturn(hrc, hrc);
3124 if (cMaxPorts <= cPorts)
3125 return S_OK;
3126 size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort;
3127 if (cPorts + cNewPortsNeeded > cMaxPorts)
3128 return S_OK;
3129
3130 /*
3131 * Raise the port count and add the free slots we've just created.
3132 */
3133 hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded);
3134 AssertComRCReturn(hrc, hrc);
3135 int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded);
3136 for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++)
3137 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3138 {
3139 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3140 if (rDvdSlots.size() >= cSlotsNeeded)
3141 return S_OK;
3142 }
3143
3144 /* We should not get here! */
3145 AssertLogRelFailedReturn(E_UNEXPECTED);
3146}
3147
3148HRESULT Unattended::done()
3149{
3150 LogFlow(("Unattended::done\n"));
3151 if (mpInstaller)
3152 {
3153 LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller));
3154 delete mpInstaller;
3155 mpInstaller = NULL;
3156 }
3157 return S_OK;
3158}
3159
3160HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
3161{
3162 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3163 isoPath = mStrIsoPath;
3164 return S_OK;
3165}
3166
3167HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
3168{
3169 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3170 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3171 mStrIsoPath = isoPath;
3172 mfDoneDetectIsoOS = false;
3173 return S_OK;
3174}
3175
3176HRESULT Unattended::getUser(com::Utf8Str &user)
3177{
3178 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3179 user = mStrUser;
3180 return S_OK;
3181}
3182
3183
3184HRESULT Unattended::setUser(const com::Utf8Str &user)
3185{
3186 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3187 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3188 mStrUser = user;
3189 return S_OK;
3190}
3191
3192HRESULT Unattended::getPassword(com::Utf8Str &password)
3193{
3194 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3195 password = mStrPassword;
3196 return S_OK;
3197}
3198
3199HRESULT Unattended::setPassword(const com::Utf8Str &password)
3200{
3201 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3202 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3203 mStrPassword = password;
3204 return S_OK;
3205}
3206
3207HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
3208{
3209 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3210 fullUserName = mStrFullUserName;
3211 return S_OK;
3212}
3213
3214HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
3215{
3216 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3217 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3218 mStrFullUserName = fullUserName;
3219 return S_OK;
3220}
3221
3222HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
3223{
3224 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3225 productKey = mStrProductKey;
3226 return S_OK;
3227}
3228
3229HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
3230{
3231 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3232 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3233 mStrProductKey = productKey;
3234 return S_OK;
3235}
3236
3237HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
3238{
3239 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3240 additionsIsoPath = mStrAdditionsIsoPath;
3241 return S_OK;
3242}
3243
3244HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
3245{
3246 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3247 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3248 mStrAdditionsIsoPath = additionsIsoPath;
3249 return S_OK;
3250}
3251
3252HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
3253{
3254 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3255 *installGuestAdditions = mfInstallGuestAdditions;
3256 return S_OK;
3257}
3258
3259HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
3260{
3261 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3262 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3263 mfInstallGuestAdditions = installGuestAdditions != FALSE;
3264 return S_OK;
3265}
3266
3267HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
3268{
3269 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3270 aValidationKitIsoPath = mStrValidationKitIsoPath;
3271 return S_OK;
3272}
3273
3274HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
3275{
3276 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3277 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3278 mStrValidationKitIsoPath = aValidationKitIsoPath;
3279 return S_OK;
3280}
3281
3282HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
3283{
3284 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3285 *aInstallTestExecService = mfInstallTestExecService;
3286 return S_OK;
3287}
3288
3289HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
3290{
3291 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3292 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3293 mfInstallTestExecService = aInstallTestExecService != FALSE;
3294 return S_OK;
3295}
3296
3297HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
3298{
3299 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3300 aTimeZone = mStrTimeZone;
3301 return S_OK;
3302}
3303
3304HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
3305{
3306 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3307 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3308 mStrTimeZone = aTimezone;
3309 return S_OK;
3310}
3311
3312HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
3313{
3314 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3315 aLocale = mStrLocale;
3316 return S_OK;
3317}
3318
3319HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
3320{
3321 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3322 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3323 if ( aLocale.isEmpty() /* use default */
3324 || ( aLocale.length() == 5
3325 && RT_C_IS_LOWER(aLocale[0])
3326 && RT_C_IS_LOWER(aLocale[1])
3327 && aLocale[2] == '_'
3328 && RT_C_IS_UPPER(aLocale[3])
3329 && RT_C_IS_UPPER(aLocale[4])) )
3330 {
3331 mStrLocale = aLocale;
3332 return S_OK;
3333 }
3334 return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters"));
3335}
3336
3337HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
3338{
3339 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3340 aLanguage = mStrLanguage;
3341 return S_OK;
3342}
3343
3344HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
3345{
3346 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3347 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3348 mStrLanguage = aLanguage;
3349 return S_OK;
3350}
3351
3352HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
3353{
3354 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3355 aCountry = mStrCountry;
3356 return S_OK;
3357}
3358
3359HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
3360{
3361 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3362 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3363 if ( aCountry.isEmpty()
3364 || ( aCountry.length() == 2
3365 && RT_C_IS_UPPER(aCountry[0])
3366 && RT_C_IS_UPPER(aCountry[1])) )
3367 {
3368 mStrCountry = aCountry;
3369 return S_OK;
3370 }
3371 return setError(E_INVALIDARG, tr("Expected two upper cased letters"));
3372}
3373
3374HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
3375{
3376 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3377 aProxy = mStrProxy; /// @todo turn schema map into string or something.
3378 return S_OK;
3379}
3380
3381HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
3382{
3383 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3384 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3385 if (aProxy.isEmpty())
3386 {
3387 /* set default proxy */
3388 /** @todo BUGBUG! implement this */
3389 }
3390 else if (aProxy.equalsIgnoreCase("none"))
3391 {
3392 /* clear proxy config */
3393 mStrProxy.setNull();
3394 }
3395 else
3396 {
3397 /** @todo Parse and set proxy config into a schema map or something along those lines. */
3398 /** @todo BUGBUG! implement this */
3399 // return E_NOTIMPL;
3400 mStrProxy = aProxy;
3401 }
3402 return S_OK;
3403}
3404
3405HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
3406{
3407 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3408 aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";");
3409 return S_OK;
3410}
3411
3412HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
3413{
3414 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3415 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3416 if (aPackageSelectionAdjustments.isEmpty())
3417 mPackageSelectionAdjustments.clear();
3418 else
3419 {
3420 RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";");
3421 for (size_t i = 0; i < arrayStrSplit.size(); i++)
3422 {
3423 if (arrayStrSplit[i].equals("minimal"))
3424 { /* okay */ }
3425 else
3426 return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str());
3427 }
3428 mPackageSelectionAdjustments = arrayStrSplit;
3429 }
3430 return S_OK;
3431}
3432
3433HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
3434{
3435 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3436 aHostname = mStrHostname;
3437 return S_OK;
3438}
3439
3440HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
3441{
3442 /*
3443 * Validate input.
3444 */
3445 if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U))
3446 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3447 tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()),
3448 aHostname.c_str(), aHostname.length());
3449 size_t cLabels = 0;
3450 const char *pszSrc = aHostname.c_str();
3451 for (;;)
3452 {
3453 size_t cchLabel = 1;
3454 char ch = *pszSrc++;
3455 if (RT_C_IS_ALNUM(ch))
3456 {
3457 cLabels++;
3458 while ((ch = *pszSrc++) != '.' && ch != '\0')
3459 {
3460 if (RT_C_IS_ALNUM(ch) || ch == '-')
3461 {
3462 if (cchLabel < 63)
3463 cchLabel++;
3464 else
3465 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3466 tr("Invalid hostname '%s' - label %u is too long, max is 63."),
3467 aHostname.c_str(), cLabels);
3468 }
3469 else
3470 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3471 tr("Invalid hostname '%s' - illegal char '%c' at position %zu"),
3472 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3473 }
3474 if (cLabels == 1 && cchLabel < 2)
3475 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3476 tr("Invalid hostname '%s' - the name part must be at least two characters long"),
3477 aHostname.c_str());
3478 if (ch == '\0')
3479 break;
3480 }
3481 else if (ch != '\0')
3482 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3483 tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"),
3484 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3485 else
3486 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3487 tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str());
3488 }
3489 if (cLabels < 2)
3490 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3491 tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str());
3492
3493 /*
3494 * Make the change.
3495 */
3496 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3497 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3498 mStrHostname = aHostname;
3499 return S_OK;
3500}
3501
3502HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
3503{
3504 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3505 aAuxiliaryBasePath = mStrAuxiliaryBasePath;
3506 return S_OK;
3507}
3508
3509HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
3510{
3511 if (aAuxiliaryBasePath.isEmpty())
3512 return setError(E_INVALIDARG, tr("Empty base path is not allowed"));
3513 if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str()))
3514 return setError(E_INVALIDARG, tr("Base path must be absolute"));
3515
3516 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3517 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3518 mStrAuxiliaryBasePath = aAuxiliaryBasePath;
3519 mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty();
3520 return S_OK;
3521}
3522
3523HRESULT Unattended::getImageIndex(ULONG *index)
3524{
3525 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3526 *index = midxImage;
3527 return S_OK;
3528}
3529
3530HRESULT Unattended::setImageIndex(ULONG index)
3531{
3532 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3533 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3534
3535 /* Validate the selection if detection was done already: */
3536 if (mDetectedImages.size() > 0)
3537 {
3538 for (size_t i = 0; i < mDetectedImages.size(); i++)
3539 if (mDetectedImages[i].mImageIndex == index)
3540 {
3541 midxImage = index;
3542 i_updateDetectedAttributeForImage(mDetectedImages[i]);
3543 return S_OK;
3544 }
3545 LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */
3546 }
3547
3548 midxImage = index;
3549 return S_OK;
3550}
3551
3552HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
3553{
3554 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3555 return mMachine.queryInterfaceTo(aMachine.asOutParam());
3556}
3557
3558HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
3559{
3560 /*
3561 * Lookup the VM so we can safely get the Machine instance.
3562 * (Don't want to test how reliable XPCOM and COM are with finding
3563 * the local object instance when a client passes a stub back.)
3564 */
3565 Bstr bstrUuidMachine;
3566 HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam());
3567 if (SUCCEEDED(hrc))
3568 {
3569 Guid UuidMachine(bstrUuidMachine);
3570 ComObjPtr<Machine> ptrMachine;
3571 hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
3572 if (SUCCEEDED(hrc))
3573 {
3574 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3575 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER,
3576 tr("Cannot change after prepare() has been called")));
3577 mMachine = ptrMachine;
3578 mMachineUuid = UuidMachine;
3579 if (mfIsDefaultAuxiliaryBasePath)
3580 mStrAuxiliaryBasePath.setNull();
3581 hrc = S_OK;
3582 }
3583 }
3584 return hrc;
3585}
3586
3587HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
3588{
3589 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3590 if ( mStrScriptTemplatePath.isNotEmpty()
3591 || mpInstaller == NULL)
3592 aScriptTemplatePath = mStrScriptTemplatePath;
3593 else
3594 aScriptTemplatePath = mpInstaller->getTemplateFilePath();
3595 return S_OK;
3596}
3597
3598HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
3599{
3600 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3601 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3602 mStrScriptTemplatePath = aScriptTemplatePath;
3603 return S_OK;
3604}
3605
3606HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
3607{
3608 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3609 if ( mStrPostInstallScriptTemplatePath.isNotEmpty()
3610 || mpInstaller == NULL)
3611 aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath;
3612 else
3613 aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath();
3614 return S_OK;
3615}
3616
3617HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
3618{
3619 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3620 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3621 mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath;
3622 return S_OK;
3623}
3624
3625HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
3626{
3627 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3628 aPostInstallCommand = mStrPostInstallCommand;
3629 return S_OK;
3630}
3631
3632HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
3633{
3634 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3635 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3636 mStrPostInstallCommand = aPostInstallCommand;
3637 return S_OK;
3638}
3639
3640HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
3641{
3642 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3643 if ( mStrExtraInstallKernelParameters.isNotEmpty()
3644 || mpInstaller == NULL)
3645 aExtraInstallKernelParameters = mStrExtraInstallKernelParameters;
3646 else
3647 aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters();
3648 return S_OK;
3649}
3650
3651HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
3652{
3653 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3654 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3655 mStrExtraInstallKernelParameters = aExtraInstallKernelParameters;
3656 return S_OK;
3657}
3658
3659HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
3660{
3661 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3662 aDetectedOSTypeId = mStrDetectedOSTypeId;
3663 return S_OK;
3664}
3665
3666HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
3667{
3668 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3669 aDetectedOSVersion = mStrDetectedOSVersion;
3670 return S_OK;
3671}
3672
3673HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
3674{
3675 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3676 aDetectedOSFlavor = mStrDetectedOSFlavor;
3677 return S_OK;
3678}
3679
3680HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
3681{
3682 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3683 aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " ");
3684 return S_OK;
3685}
3686
3687HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
3688{
3689 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3690 aDetectedOSHints = mStrDetectedOSHints;
3691 return S_OK;
3692}
3693
3694HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
3695{
3696 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3697 aDetectedImageNames.clear();
3698 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3699 {
3700 Utf8Str strTmp;
3701 aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp));
3702 }
3703 return S_OK;
3704}
3705
3706HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
3707{
3708 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3709 aDetectedImageIndices.clear();
3710 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3711 aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex);
3712 return S_OK;
3713}
3714
3715HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
3716{
3717 /*
3718 * Take the initial position that it's not supported, so we can return
3719 * right away when we decide it's not possible.
3720 */
3721 *aIsUnattendedInstallSupported = false;
3722
3723 /* Unattended is disabled by default if we could not detect OS type. */
3724 if (mStrDetectedOSTypeId.isEmpty())
3725 return S_OK;
3726
3727 const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask);
3728
3729 /* We require a version to have been detected, except for windows where the
3730 field is generally only used for the service pack number at present and
3731 will be empty for RTMs isos. */
3732 if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT
3733 || enmOsTypeMasked >= VBOXOSTYPE_OS2)
3734 && mStrDetectedOSVersion.isEmpty())
3735 return S_OK;
3736
3737 /*
3738 * Sort out things that we know doesn't work. Order by VBOXOSTYPE value.
3739 */
3740
3741 /* We do not support any of the DOS based windows version, nor DOS, in case
3742 any of that gets detected (it shouldn't): */
3743 if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT)
3744 return S_OK;
3745
3746 /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */
3747 if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4)
3748 return S_OK;
3749
3750 /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been
3751 tested, but we'll get to the others eventually): */
3752 if ( enmOsTypeMasked >= VBOXOSTYPE_OS2
3753 && enmOsTypeMasked < VBOXOSTYPE_Linux
3754 && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ )
3755 return S_OK;
3756
3757 /* Old Debians fail since package repos have been move to some other mirror location. */
3758 if ( enmOsTypeMasked == VBOXOSTYPE_Debian
3759 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0)
3760 return S_OK;
3761
3762 /* Skip all OpenSUSE variants for now. */
3763 if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE)
3764 return S_OK;
3765
3766 /* We cannot install Ubuntus older than 11.04. */
3767 if ( enmOsTypeMasked == VBOXOSTYPE_Ubuntu
3768 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0)
3769 return S_OK;
3770
3771 /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */
3772 if ( enmOsTypeMasked == VBOXOSTYPE_Oracle
3773 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0)
3774 return S_OK;
3775
3776 /*
3777 * Assume the rest works.
3778 */
3779 *aIsUnattendedInstallSupported = true;
3780 return S_OK;
3781}
3782
3783HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
3784{
3785 *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork;
3786 return S_OK;
3787}
3788
3789HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
3790{
3791 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3792 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3793 mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork);
3794 return S_OK;
3795}
3796
3797/*
3798 * Getters that the installer and script classes can use.
3799 */
3800Utf8Str const &Unattended::i_getIsoPath() const
3801{
3802 Assert(isReadLockedOnCurrentThread());
3803 return mStrIsoPath;
3804}
3805
3806Utf8Str const &Unattended::i_getUser() const
3807{
3808 Assert(isReadLockedOnCurrentThread());
3809 return mStrUser;
3810}
3811
3812Utf8Str const &Unattended::i_getPassword() const
3813{
3814 Assert(isReadLockedOnCurrentThread());
3815 return mStrPassword;
3816}
3817
3818Utf8Str const &Unattended::i_getFullUserName() const
3819{
3820 Assert(isReadLockedOnCurrentThread());
3821 return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
3822}
3823
3824Utf8Str const &Unattended::i_getProductKey() const
3825{
3826 Assert(isReadLockedOnCurrentThread());
3827 return mStrProductKey;
3828}
3829
3830Utf8Str const &Unattended::i_getProxy() const
3831{
3832 Assert(isReadLockedOnCurrentThread());
3833 return mStrProxy;
3834}
3835
3836Utf8Str const &Unattended::i_getAdditionsIsoPath() const
3837{
3838 Assert(isReadLockedOnCurrentThread());
3839 return mStrAdditionsIsoPath;
3840}
3841
3842bool Unattended::i_getInstallGuestAdditions() const
3843{
3844 Assert(isReadLockedOnCurrentThread());
3845 return mfInstallGuestAdditions;
3846}
3847
3848Utf8Str const &Unattended::i_getValidationKitIsoPath() const
3849{
3850 Assert(isReadLockedOnCurrentThread());
3851 return mStrValidationKitIsoPath;
3852}
3853
3854bool Unattended::i_getInstallTestExecService() const
3855{
3856 Assert(isReadLockedOnCurrentThread());
3857 return mfInstallTestExecService;
3858}
3859
3860Utf8Str const &Unattended::i_getTimeZone() const
3861{
3862 Assert(isReadLockedOnCurrentThread());
3863 return mStrTimeZone;
3864}
3865
3866PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
3867{
3868 Assert(isReadLockedOnCurrentThread());
3869 return mpTimeZoneInfo;
3870}
3871
3872Utf8Str const &Unattended::i_getLocale() const
3873{
3874 Assert(isReadLockedOnCurrentThread());
3875 return mStrLocale;
3876}
3877
3878Utf8Str const &Unattended::i_getLanguage() const
3879{
3880 Assert(isReadLockedOnCurrentThread());
3881 return mStrLanguage;
3882}
3883
3884Utf8Str const &Unattended::i_getCountry() const
3885{
3886 Assert(isReadLockedOnCurrentThread());
3887 return mStrCountry;
3888}
3889
3890bool Unattended::i_isMinimalInstallation() const
3891{
3892 size_t i = mPackageSelectionAdjustments.size();
3893 while (i-- > 0)
3894 if (mPackageSelectionAdjustments[i].equals("minimal"))
3895 return true;
3896 return false;
3897}
3898
3899Utf8Str const &Unattended::i_getHostname() const
3900{
3901 Assert(isReadLockedOnCurrentThread());
3902 return mStrHostname;
3903}
3904
3905Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
3906{
3907 Assert(isReadLockedOnCurrentThread());
3908 return mStrAuxiliaryBasePath;
3909}
3910
3911ULONG Unattended::i_getImageIndex() const
3912{
3913 Assert(isReadLockedOnCurrentThread());
3914 return midxImage;
3915}
3916
3917Utf8Str const &Unattended::i_getScriptTemplatePath() const
3918{
3919 Assert(isReadLockedOnCurrentThread());
3920 return mStrScriptTemplatePath;
3921}
3922
3923Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
3924{
3925 Assert(isReadLockedOnCurrentThread());
3926 return mStrPostInstallScriptTemplatePath;
3927}
3928
3929Utf8Str const &Unattended::i_getPostInstallCommand() const
3930{
3931 Assert(isReadLockedOnCurrentThread());
3932 return mStrPostInstallCommand;
3933}
3934
3935Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
3936{
3937 Assert(isReadLockedOnCurrentThread());
3938 /* Only the installer knows, forward the call. */
3939 AssertReturn(mpInstaller != NULL, Utf8Str::Empty);
3940 return mpInstaller->getAuxiliaryInstallDir();
3941}
3942
3943Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
3944{
3945 Assert(isReadLockedOnCurrentThread());
3946 return mStrExtraInstallKernelParameters;
3947}
3948
3949bool Unattended::i_isRtcUsingUtc() const
3950{
3951 Assert(isReadLockedOnCurrentThread());
3952 return mfRtcUseUtc;
3953}
3954
3955bool Unattended::i_isGuestOs64Bit() const
3956{
3957 Assert(isReadLockedOnCurrentThread());
3958 return mfGuestOs64Bit;
3959}
3960
3961bool Unattended::i_isFirmwareEFI() const
3962{
3963 Assert(isReadLockedOnCurrentThread());
3964 return menmFirmwareType != FirmwareType_BIOS;
3965}
3966
3967Utf8Str const &Unattended::i_getDetectedOSVersion()
3968{
3969 Assert(isReadLockedOnCurrentThread());
3970 return mStrDetectedOSVersion;
3971}
3972
3973bool Unattended::i_getAvoidUpdatesOverNetwork() const
3974{
3975 Assert(isReadLockedOnCurrentThread());
3976 return mfAvoidUpdatesOverNetwork;
3977}
3978
3979HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
3980 AutoMultiWriteLock2 &rLock)
3981{
3982 /*
3983 * Attach the disk image
3984 * HACK ALERT! Temporarily release the Unattended lock.
3985 */
3986 rLock.release();
3987
3988 ComPtr<IMedium> ptrMedium;
3989 HRESULT rc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(),
3990 pImage->enmDeviceType,
3991 pImage->enmAccessType,
3992 true,
3993 ptrMedium.asOutParam());
3994 LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", rc));
3995 if (SUCCEEDED(rc))
3996 {
3997 if (pImage->fMountOnly)
3998 {
3999 // mount the opened disk image
4000 rc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4001 pImage->iDevice, ptrMedium, TRUE /*fForce*/);
4002 LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", rc));
4003 }
4004 else
4005 {
4006 //attach the opened disk image to the controller
4007 rc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4008 pImage->iDevice, pImage->enmDeviceType, ptrMedium);
4009 LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", rc));
4010 }
4011 }
4012
4013 rLock.acquire();
4014 return rc;
4015}
4016
4017bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId)
4018{
4019 ComPtr<IGuestOSType> pGuestOSType;
4020 HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam());
4021 if (SUCCEEDED(hrc))
4022 {
4023 BOOL fIs64Bit = FALSE;
4024 if (!pGuestOSType.isNull())
4025 hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit);
4026 if (SUCCEEDED(hrc))
4027 return fIs64Bit != FALSE;
4028 }
4029 return false;
4030}
4031
4032
4033bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage)
4034{
4035 bool fRet = true;
4036
4037 /*
4038 * If the image doesn't have a valid value, we don't change it.
4039 * This is obviously a little bit bogus, but what can we do...
4040 */
4041 const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType);
4042 if (pszOSTypeId && strcmp(pszOSTypeId, "Other") != 0)
4043 mStrDetectedOSTypeId = pszOSTypeId;
4044 else
4045 fRet = false;
4046
4047 if (rImage.mVersion.isNotEmpty())
4048 mStrDetectedOSVersion = rImage.mVersion;
4049 else
4050 fRet = false;
4051
4052 if (rImage.mFlavor.isNotEmpty())
4053 mStrDetectedOSFlavor = rImage.mFlavor;
4054 else
4055 fRet = false;
4056
4057 if (rImage.mLanguages.size() > 0)
4058 mDetectedOSLanguages = rImage.mLanguages;
4059 else
4060 fRet = false;
4061
4062 mEnmOsType = rImage.mEnmOsType;
4063
4064 return fRet;
4065}
Note: See TracBrowser for help on using the repository browser.

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