VirtualBox

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

最後變更 在這個檔案從102398是 102381,由 vboxsync 提交於 16 月 前

Main/Unattended: Added stubs for IUnattended:keyboardLayout + IUnattended:keyboardVariant attributes. bugref:10553

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

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