VirtualBox

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

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

IPRT/Vfs,Main/Unattended: Detect fedora ISOs, various code cleanups. Added a parameter to RTVfsQueryLabel so we can get the primary volume ID of an ISO (required for fedora ISOs, as the joliet version is truncated). bugref:9781

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

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