VirtualBox

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

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

Backed out r154357,r154358,r154360,r154361,r154364 because it still doesn't build, old files weren't renamed or removed, and the changes for bugref:10180 and bugref:4787 got mixed up, making it difficult to selectively revert the latter one only.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 168.9 KB
Line 
1/* $Id: UnattendedImpl.cpp 97367 2022-11-01 10:38:05Z vboxsync $ */
2/** @file
3 * Unattended class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
33#include "LoggingNew.h"
34#include "VirtualBoxBase.h"
35#include "UnattendedImpl.h"
36#include "UnattendedInstaller.h"
37#include "UnattendedScript.h"
38#include "VirtualBoxImpl.h"
39#include "SystemPropertiesImpl.h"
40#include "MachineImpl.h"
41#include "Global.h"
42#include "StringifyEnums.h"
43
44#include <VBox/err.h>
45#include <iprt/cpp/xml.h>
46#include <iprt/ctype.h>
47#include <iprt/file.h>
48#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 {
1976 while (*pszVersion == '-')
1977 pszVersion++;
1978 *pszArch = '\0';
1979 }
1980 else
1981 {
1982 mEnmOsType = (VBOXOSTYPE)(VBOXOSTYPE_FedoraCore | VBOXOSTYPE_UnknownArch);
1983
1984 char ch;
1985 while ((ch = *psz) != '\0' && (!RT_C_IS_DIGIT(ch) || !RT_C_IS_PUNCT(psz[-1])))
1986 psz++;
1987 if (ch != '\0')
1988 pszVersion = psz;
1989 }
1990
1991 /*
1992 * Replace '-' with '.' in the version part and use it as the version.
1993 */
1994 if (pszVersion)
1995 {
1996 psz = pszVersion;
1997 while ((psz = strchr(psz, '-')) != NULL)
1998 *psz++ = '.';
1999 try { mStrDetectedOSVersion = RTStrStrip(pszVersion); }
2000 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2001
2002 *pszVersion = '\0'; /* don't include in flavor */
2003 }
2004
2005 /*
2006 * Split up the pre-arch/version bits into words and use them as the flavor.
2007 */
2008 psz = pszFlavor;
2009 while ((psz = strchr(psz, '-')) != NULL)
2010 *psz++ = ' ';
2011 try { mStrDetectedOSFlavor = RTStrStrip(pszFlavor); }
2012 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2013
2014 /*
2015 * If we don't have an architecture, we look at the vmlinuz file as the x86
2016 * and AMD64 versions starts with a MZ+PE header giving the architecture.
2017 */
2018 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2019 {
2020 static const char * const s_apszVmLinuz[] = { "images/pxeboot/vmlinuz", "isolinux/vmlinuz" };
2021 for (size_t i = 0; i < RT_ELEMENTS(s_apszVmLinuz); i++)
2022 {
2023 RTVFSFILE hVfsFileLinuz = NIL_RTVFSFILE;
2024 int vrc = RTVfsFileOpen(hVfsIso, s_apszVmLinuz[i], RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
2025 &hVfsFileLinuz);
2026 if (RT_SUCCESS(vrc))
2027 {
2028 /* DOS signature: */
2029 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pBuf->ab[0];
2030 AssertCompile(sizeof(*pBuf) > sizeof(*pDosHdr));
2031 vrc = RTVfsFileReadAt(hVfsFileLinuz, 0, pDosHdr, sizeof(*pDosHdr), NULL);
2032 if (RT_SUCCESS(vrc) && pDosHdr->e_magic == IMAGE_DOS_SIGNATURE)
2033 {
2034 /* NT signature - only need magic + file header, so use the 64 version for better debugging: */
2035 PIMAGE_NT_HEADERS64 pNtHdrs = (PIMAGE_NT_HEADERS64)&pBuf->ab[0];
2036 vrc = RTVfsFileReadAt(hVfsFileLinuz, pDosHdr->e_lfanew, pNtHdrs, sizeof(*pNtHdrs), NULL);
2037 AssertCompile(sizeof(*pBuf) > sizeof(*pNtHdrs));
2038 if (RT_SUCCESS(vrc) && pNtHdrs->Signature == IMAGE_NT_SIGNATURE)
2039 {
2040 if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
2041 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x86);
2042 else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
2043 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x64);
2044 else
2045 AssertFailed();
2046 }
2047 }
2048
2049 RTVfsFileRelease(hVfsFileLinuz);
2050 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch)
2051 break;
2052 }
2053 }
2054 }
2055
2056 /*
2057 * If that failed, look for other files that gives away the arch.
2058 */
2059 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2060 {
2061 static struct { const char *pszFile; VBOXOSTYPE fArch; } const s_aArchSpecificFiles[] =
2062 {
2063 { "EFI/BOOT/grubaa64.efi", VBOXOSTYPE_arm64 },
2064 { "EFI/BOOT/BOOTAA64.EFI", VBOXOSTYPE_arm64 },
2065 };
2066 PRTFSOBJINFO pObjInfo = (PRTFSOBJINFO)&pBuf->ab[0];
2067 AssertCompile(sizeof(*pBuf) > sizeof(*pObjInfo));
2068 for (size_t i = 0; i < RT_ELEMENTS(s_aArchSpecificFiles); i++)
2069 {
2070 int vrc = RTVfsQueryPathInfo(hVfsIso, s_aArchSpecificFiles[i].pszFile, pObjInfo,
2071 RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2072 if (RT_SUCCESS(vrc) && RTFS_IS_FILE(pObjInfo->Attr.fMode))
2073 {
2074 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | s_aArchSpecificFiles[i].fArch);
2075 break;
2076 }
2077 }
2078 }
2079
2080 /*
2081 * If we like, we could parse grub.conf to look for fullly spelled out
2082 * flavor, though the menu items typically only contains the major version
2083 * number, so little else to add, really.
2084 */
2085
2086 return (mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch ? S_OK : S_FALSE;
2087}
2088
2089
2090/**
2091 * Detect OS/2 installation ISOs.
2092 *
2093 * Mainly aiming at ACP2/MCP2 as that's what we currently use in our testing.
2094 *
2095 * @returns COM status code.
2096 * @retval S_OK if detected
2097 * @retval S_FALSE if not fully detected.
2098 *
2099 * @param hVfsIso The ISO file system.
2100 * @param pBuf Read buffer.
2101 */
2102HRESULT Unattended::i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2103{
2104 /*
2105 * The OS2SE20.SRC contains the location of the tree with the diskette
2106 * images, typically "\OS2IMAGE".
2107 */
2108 RTVFSFILE hVfsFile;
2109 int vrc = RTVfsFileOpen(hVfsIso, "OS2SE20.SRC", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2110 if (RT_SUCCESS(vrc))
2111 {
2112 size_t cbRead = 0;
2113 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2114 RTVfsFileRelease(hVfsFile);
2115 if (RT_SUCCESS(vrc))
2116 {
2117 pBuf->sz[cbRead] = '\0';
2118 RTStrStrip(pBuf->sz);
2119 vrc = RTStrValidateEncoding(pBuf->sz);
2120 if (RT_SUCCESS(vrc))
2121 LogRelFlow(("Unattended: OS2SE20.SRC=%s\n", pBuf->sz));
2122 else
2123 LogRel(("Unattended: OS2SE20.SRC invalid encoding: %Rrc, %.*Rhxs\n", vrc, cbRead, pBuf->sz));
2124 }
2125 else
2126 LogRel(("Unattended: Error reading OS2SE20.SRC: %\n", vrc));
2127 }
2128 /*
2129 * ArcaOS has dropped the file, assume it's \OS2IMAGE and see if it's there.
2130 */
2131 else if (vrc == VERR_FILE_NOT_FOUND)
2132 RTStrCopy(pBuf->sz, sizeof(pBuf->sz), "\\OS2IMAGE");
2133 else
2134 return S_FALSE;
2135
2136 /*
2137 * Check that the directory directory exists and has a DISK_0 under it
2138 * with an OS2LDR on it.
2139 */
2140 size_t const cchOs2Image = strlen(pBuf->sz);
2141 vrc = RTPathAppend(pBuf->sz, sizeof(pBuf->sz), "DISK_0/OS2LDR");
2142 RTFSOBJINFO ObjInfo = {0};
2143 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2144 if (vrc == VERR_FILE_NOT_FOUND)
2145 {
2146 RTStrCat(pBuf->sz, sizeof(pBuf->sz), "."); /* eCS 2.0 image includes the dot from the 8.3 name. */
2147 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2148 }
2149 if ( RT_FAILURE(vrc)
2150 || !RTFS_IS_FILE(ObjInfo.Attr.fMode))
2151 {
2152 LogRel(("Unattended: RTVfsQueryPathInfo(, '%s' (from OS2SE20.SRC),) -> %Rrc, fMode=%#x\n",
2153 pBuf->sz, vrc, ObjInfo.Attr.fMode));
2154 return S_FALSE;
2155 }
2156
2157 /*
2158 * So, it's some kind of OS/2 2.x or later ISO alright.
2159 */
2160 mEnmOsType = VBOXOSTYPE_OS2;
2161 mStrDetectedOSHints.printf("OS2SE20.SRC=%.*s", cchOs2Image, pBuf->sz);
2162
2163 /*
2164 * ArcaOS ISOs seems to have a AOSBOOT dir on them.
2165 * This contains a ARCANOAE.FLG file with content we can use for the version:
2166 * ArcaOS 5.0.7 EN
2167 * Built 2021-12-07 18:34:34
2168 * We drop the "ArcaOS" bit, as it's covered by mEnmOsType. Then we pull up
2169 * the second line.
2170 *
2171 * Note! Yet to find a way to do unattended install of ArcaOS, as it comes
2172 * with no CD-boot floppy images, only simple .PF archive files for
2173 * unpacking onto the ram disk or whatever. Modifying these is
2174 * possible (ibsen's aPLib v0.36 compression with some simple custom
2175 * headers), but it would probably be a royal pain. Could perhaps
2176 * cook something from OS2IMAGE\DISK_0 thru 3...
2177 */
2178 vrc = RTVfsQueryPathInfo(hVfsIso, "AOSBOOT", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2179 if ( RT_SUCCESS(vrc)
2180 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2181 {
2182 mEnmOsType = VBOXOSTYPE_ArcaOS;
2183
2184 /* Read the version file: */
2185 vrc = RTVfsFileOpen(hVfsIso, "SYS/ARCANOAE.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2186 if (RT_SUCCESS(vrc))
2187 {
2188 size_t cbRead = 0;
2189 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2190 RTVfsFileRelease(hVfsFile);
2191 pBuf->sz[cbRead] = '\0';
2192 if (RT_SUCCESS(vrc))
2193 {
2194 /* Strip the OS name: */
2195 char *pszVersion = RTStrStrip(pBuf->sz);
2196 static char s_szArcaOS[] = "ArcaOS";
2197 if (RTStrStartsWith(pszVersion, s_szArcaOS))
2198 pszVersion = RTStrStripL(pszVersion + sizeof(s_szArcaOS) - 1);
2199
2200 /* Pull up the 2nd line if it, condensing the \r\n into a single space. */
2201 char *pszNewLine = strchr(pszVersion, '\n');
2202 if (pszNewLine && RTStrStartsWith(pszNewLine + 1, "Built 20"))
2203 {
2204 size_t offRemove = 0;
2205 while (RT_C_IS_SPACE(pszNewLine[-1 - (ssize_t)offRemove]))
2206 offRemove++;
2207 if (offRemove > 0)
2208 {
2209 pszNewLine -= offRemove;
2210 memmove(pszNewLine, pszNewLine + offRemove, strlen(pszNewLine + offRemove) - 1);
2211 }
2212 *pszNewLine = ' ';
2213 }
2214
2215 /* Drop any additional lines: */
2216 pszNewLine = strchr(pszVersion, '\n');
2217 if (pszNewLine)
2218 *pszNewLine = '\0';
2219 RTStrStripR(pszVersion);
2220
2221 /* Done (hope it makes some sense). */
2222 mStrDetectedOSVersion = pszVersion;
2223 }
2224 else
2225 LogRel(("Unattended: failed to read AOSBOOT/ARCANOAE.FLG: %Rrc\n", vrc));
2226 }
2227 else
2228 LogRel(("Unattended: failed to open AOSBOOT/ARCANOAE.FLG for reading: %Rrc\n", vrc));
2229 }
2230 /*
2231 * Similarly, eCS has an ECS directory and it typically contains a
2232 * ECS_INST.FLG file with the version info. Content differs a little:
2233 * eComStation 2.0 EN_US Thu May 13 10:27:54 pm 2010
2234 * Built on ECS60441318
2235 * Here we drop the "eComStation" bit and leave the 2nd line as it.
2236 *
2237 * Note! At least 2.0 has a DISKIMGS folder with what looks like boot
2238 * disks, so we could probably get something going here without
2239 * needing to write an OS2 boot sector...
2240 */
2241 else
2242 {
2243 vrc = RTVfsQueryPathInfo(hVfsIso, "ECS", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2244 if ( RT_SUCCESS(vrc)
2245 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2246 {
2247 mEnmOsType = VBOXOSTYPE_ECS;
2248
2249 /* Read the version file: */
2250 vrc = RTVfsFileOpen(hVfsIso, "ECS/ECS_INST.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2251 if (RT_SUCCESS(vrc))
2252 {
2253 size_t cbRead = 0;
2254 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2255 RTVfsFileRelease(hVfsFile);
2256 pBuf->sz[cbRead] = '\0';
2257 if (RT_SUCCESS(vrc))
2258 {
2259 /* Strip the OS name: */
2260 char *pszVersion = RTStrStrip(pBuf->sz);
2261 static char s_szECS[] = "eComStation";
2262 if (RTStrStartsWith(pszVersion, s_szECS))
2263 pszVersion = RTStrStripL(pszVersion + sizeof(s_szECS) - 1);
2264
2265 /* Drop any additional lines: */
2266 char *pszNewLine = strchr(pszVersion, '\n');
2267 if (pszNewLine)
2268 *pszNewLine = '\0';
2269 RTStrStripR(pszVersion);
2270
2271 /* Done (hope it makes some sense). */
2272 mStrDetectedOSVersion = pszVersion;
2273 }
2274 else
2275 LogRel(("Unattended: failed to read ECS/ECS_INST.FLG: %Rrc\n", vrc));
2276 }
2277 else
2278 LogRel(("Unattended: failed to open ECS/ECS_INST.FLG for reading: %Rrc\n", vrc));
2279 }
2280 else
2281 {
2282 /*
2283 * Official IBM OS/2 builds doesn't have any .FLG file on them,
2284 * so need to pry the information out in some other way. Best way
2285 * is to read the SYSLEVEL.OS2 file, which is typically on disk #2,
2286 * though on earlier versions (warp3) it was disk #1.
2287 */
2288 vrc = RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1,
2289 "/DISK_2/SYSLEVEL.OS2");
2290 if (RT_SUCCESS(vrc))
2291 {
2292 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2293 if (vrc == VERR_FILE_NOT_FOUND)
2294 {
2295 RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, "/DISK_1/SYSLEVEL.OS2");
2296 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2297 }
2298 if (RT_SUCCESS(vrc))
2299 {
2300 RT_ZERO(pBuf->ab);
2301 size_t cbRead = 0;
2302 vrc = RTVfsFileRead(hVfsFile, pBuf->ab, sizeof(pBuf->ab), &cbRead);
2303 RTVfsFileRelease(hVfsFile);
2304 if (RT_SUCCESS(vrc))
2305 {
2306 /* Check the header. */
2307 OS2SYSLEVELHDR const *pHdr = (OS2SYSLEVELHDR const *)&pBuf->ab[0];
2308 if ( pHdr->uMinusOne == UINT16_MAX
2309 && pHdr->uSyslevelFileVer == 1
2310 && memcmp(pHdr->achSignature, RT_STR_TUPLE("SYSLEVEL")) == 0
2311 && pHdr->offTable < cbRead
2312 && pHdr->offTable + sizeof(OS2SYSLEVELENTRY) <= cbRead)
2313 {
2314 OS2SYSLEVELENTRY *pEntry = (OS2SYSLEVELENTRY *)&pBuf->ab[pHdr->offTable];
2315 if ( RT_SUCCESS(RTStrValidateEncodingEx(pEntry->szName, sizeof(pEntry->szName),
2316 RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED))
2317 && RT_SUCCESS(RTStrValidateEncodingEx(pEntry->achCsdLevel, sizeof(pEntry->achCsdLevel), 0))
2318 && pEntry->bVersion != 0
2319 && ((pEntry->bVersion >> 4) & 0xf) < 10
2320 && (pEntry->bVersion & 0xf) < 10
2321 && pEntry->bModify < 10
2322 && pEntry->bRefresh < 10)
2323 {
2324 /* Flavor: */
2325 char *pszName = RTStrStrip(pEntry->szName);
2326 if (pszName)
2327 mStrDetectedOSFlavor = pszName;
2328
2329 /* Version: */
2330 if (pEntry->bRefresh != 0)
2331 mStrDetectedOSVersion.printf("%d.%d%d.%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2332 pEntry->bModify, pEntry->bRefresh);
2333 else
2334 mStrDetectedOSVersion.printf("%d.%d%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2335 pEntry->bModify);
2336 pEntry->achCsdLevel[sizeof(pEntry->achCsdLevel) - 1] = '\0';
2337 char *pszCsd = RTStrStrip(pEntry->achCsdLevel);
2338 if (*pszCsd != '\0')
2339 {
2340 mStrDetectedOSVersion.append(' ');
2341 mStrDetectedOSVersion.append(pszCsd);
2342 }
2343 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.50") >= 0)
2344 mEnmOsType = VBOXOSTYPE_OS2Warp45;
2345 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.00") >= 0)
2346 mEnmOsType = VBOXOSTYPE_OS2Warp4;
2347 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "3.00") >= 0)
2348 mEnmOsType = VBOXOSTYPE_OS2Warp3;
2349 }
2350 else
2351 LogRel(("Unattended: bogus SYSLEVEL.OS2 file entry: %.128Rhxd\n", pEntry));
2352 }
2353 else
2354 LogRel(("Unattended: bogus SYSLEVEL.OS2 file header: uMinusOne=%#x uSyslevelFileVer=%#x achSignature=%.8Rhxs offTable=%#x vs cbRead=%#zx\n",
2355 pHdr->uMinusOne, pHdr->uSyslevelFileVer, pHdr->achSignature, pHdr->offTable, cbRead));
2356 }
2357 else
2358 LogRel(("Unattended: failed to read SYSLEVEL.OS2: %Rrc\n", vrc));
2359 }
2360 else
2361 LogRel(("Unattended: failed to open '%s' for reading: %Rrc\n", pBuf->sz, vrc));
2362 }
2363 }
2364 }
2365
2366 /** @todo language detection? */
2367
2368 /*
2369 * Only tested ACP2, so only return S_OK for it.
2370 */
2371 if ( mEnmOsType == VBOXOSTYPE_OS2Warp45
2372 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.52") >= 0
2373 && mStrDetectedOSFlavor.contains("Server", RTCString::CaseInsensitive))
2374 return S_OK;
2375
2376 return S_FALSE;
2377}
2378
2379
2380/**
2381 * Detect FreeBSD distro ISOs.
2382 *
2383 * @returns COM status code.
2384 * @retval S_OK if detected
2385 * @retval S_FALSE if not fully detected.
2386 *
2387 * @param hVfsIso The ISO file system.
2388 * @param pBuf Read buffer.
2389 */
2390HRESULT Unattended::i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2391{
2392 RT_NOREF(pBuf);
2393
2394 /*
2395 * FreeBSD since 10.0 has a .profile file in the root which can be used to determine that this is FreeBSD
2396 * along with the version.
2397 */
2398
2399 RTVFSFILE hVfsFile;
2400 HRESULT hrc = S_FALSE;
2401 int vrc = RTVfsFileOpen(hVfsIso, ".profile", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2402 if (RT_SUCCESS(vrc))
2403 {
2404 static const uint8_t s_abFreeBsdHdr[] = "# $FreeBSD: releng/";
2405 char abRead[32];
2406
2407 vrc = RTVfsFileRead(hVfsFile, &abRead[0], sizeof(abRead), NULL /*pcbRead*/);
2408 if ( RT_SUCCESS(vrc)
2409 && !memcmp(&abRead[0], &s_abFreeBsdHdr[0], sizeof(s_abFreeBsdHdr) - 1)) /* Skip terminator */
2410 {
2411 abRead[sizeof(abRead) - 1] = '\0';
2412
2413 /* Detect the architecture using the volume label. */
2414 char szVolumeId[128];
2415 size_t cchVolumeId;
2416 vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, 128, &cchVolumeId);
2417 if (RT_SUCCESS(vrc))
2418 {
2419 /* Can re-use the Linux code here. */
2420 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_FreeBSD))
2421 LogRel(("Unattended/FBSD: Unknown: arch='%s'\n", szVolumeId));
2422
2423 /* Detect the version from the string coming after the needle in .profile. */
2424 AssertCompile(sizeof(s_abFreeBsdHdr) - 1 < sizeof(abRead));
2425
2426 char *pszVersionStart = &abRead[sizeof(s_abFreeBsdHdr) - 1];
2427 char *pszVersionEnd = pszVersionStart;
2428
2429 while (RT_C_IS_DIGIT(*pszVersionEnd))
2430 pszVersionEnd++;
2431 if (*pszVersionEnd == '.')
2432 {
2433 pszVersionEnd++; /* Skip the . */
2434
2435 while (RT_C_IS_DIGIT(*pszVersionEnd))
2436 pszVersionEnd++;
2437
2438 /* Terminate the version string. */
2439 *pszVersionEnd = '\0';
2440
2441 try { mStrDetectedOSVersion = pszVersionStart; }
2442 catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
2443 }
2444 else
2445 LogRel(("Unattended/FBSD: Unknown: version='%s'\n", &abRead[0]));
2446 }
2447 else
2448 {
2449 LogRel(("Unattended/FBSD: No Volume Label found\n"));
2450 mEnmOsType = VBOXOSTYPE_FreeBSD;
2451 }
2452
2453 hrc = S_OK;
2454 }
2455
2456 RTVfsFileRelease(hVfsFile);
2457 }
2458
2459 return hrc;
2460}
2461
2462
2463HRESULT Unattended::prepare()
2464{
2465 LogFlow(("Unattended::prepare: enter\n"));
2466
2467 /*
2468 * Must have a machine.
2469 */
2470 ComPtr<Machine> ptrMachine;
2471 Guid MachineUuid;
2472 {
2473 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2474 ptrMachine = mMachine;
2475 if (ptrMachine.isNull())
2476 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("No machine associated with this IUnatteded instance"));
2477 MachineUuid = mMachineUuid;
2478 }
2479
2480 /*
2481 * Before we write lock ourselves, we must get stuff from Machine and
2482 * VirtualBox because their locks have higher priorities than ours.
2483 */
2484 Utf8Str strGuestOsTypeId;
2485 Utf8Str strMachineName;
2486 Utf8Str strDefaultAuxBasePath;
2487 HRESULT hrc;
2488 try
2489 {
2490 Bstr bstrTmp;
2491 hrc = ptrMachine->COMGETTER(OSTypeId)(bstrTmp.asOutParam());
2492 if (SUCCEEDED(hrc))
2493 {
2494 strGuestOsTypeId = bstrTmp;
2495 hrc = ptrMachine->COMGETTER(Name)(bstrTmp.asOutParam());
2496 if (SUCCEEDED(hrc))
2497 strMachineName = bstrTmp;
2498 }
2499 int vrc = ptrMachine->i_calculateFullPath(Utf8StrFmt("Unattended-%RTuuid-", MachineUuid.raw()), strDefaultAuxBasePath);
2500 if (RT_FAILURE(vrc))
2501 return setErrorBoth(E_FAIL, vrc);
2502 }
2503 catch (std::bad_alloc &)
2504 {
2505 return E_OUTOFMEMORY;
2506 }
2507 bool const fIs64Bit = i_isGuestOSArchX64(strGuestOsTypeId);
2508
2509 BOOL fRtcUseUtc = FALSE;
2510 hrc = ptrMachine->COMGETTER(RTCUseUTC)(&fRtcUseUtc);
2511 if (FAILED(hrc))
2512 return hrc;
2513
2514 FirmwareType_T enmFirmware = FirmwareType_BIOS;
2515 hrc = ptrMachine->COMGETTER(FirmwareType)(&enmFirmware);
2516 if (FAILED(hrc))
2517 return hrc;
2518
2519 /*
2520 * Write lock this object and set attributes we got from IMachine.
2521 */
2522 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2523
2524 mStrGuestOsTypeId = strGuestOsTypeId;
2525 mfGuestOs64Bit = fIs64Bit;
2526 mfRtcUseUtc = RT_BOOL(fRtcUseUtc);
2527 menmFirmwareType = enmFirmware;
2528
2529 /*
2530 * Do some state checks.
2531 */
2532 if (mpInstaller != NULL)
2533 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The prepare method has been called (must call done to restart)"));
2534 if ((Machine *)ptrMachine != (Machine *)mMachine)
2535 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The 'machine' while we were using it - please don't do that"));
2536
2537 /*
2538 * Check if the specified ISOs and files exist.
2539 */
2540 if (!RTFileExists(mStrIsoPath.c_str()))
2541 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the installation ISO file '%s'"),
2542 mStrIsoPath.c_str());
2543 if (mfInstallGuestAdditions && !RTFileExists(mStrAdditionsIsoPath.c_str()))
2544 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the Guest Additions ISO file '%s'"),
2545 mStrAdditionsIsoPath.c_str());
2546 if (mfInstallTestExecService && !RTFileExists(mStrValidationKitIsoPath.c_str()))
2547 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the validation kit ISO file '%s'"),
2548 mStrValidationKitIsoPath.c_str());
2549 if (mStrScriptTemplatePath.isNotEmpty() && !RTFileExists(mStrScriptTemplatePath.c_str()))
2550 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate unattended installation script template '%s'"),
2551 mStrScriptTemplatePath.c_str());
2552
2553 /*
2554 * Do media detection if it haven't been done yet.
2555 */
2556 if (!mfDoneDetectIsoOS)
2557 {
2558 hrc = detectIsoOS();
2559 if (FAILED(hrc) && hrc != E_NOTIMPL)
2560 return hrc;
2561 }
2562
2563 /*
2564 * We can now check midxImage against mDetectedImages, since the latter is
2565 * populated during the detectIsoOS call. We ignore midxImage if no images
2566 * were detected, assuming that it's not relevant or used for different purposes.
2567 */
2568 if (mDetectedImages.size() > 0)
2569 {
2570 bool fImageFound = false;
2571 for (size_t i = 0; i < mDetectedImages.size(); ++i)
2572 if (midxImage == mDetectedImages[i].mImageIndex)
2573 {
2574 i_updateDetectedAttributeForImage(mDetectedImages[i]);
2575 fImageFound = true;
2576 break;
2577 }
2578 if (!fImageFound)
2579 return setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("imageIndex value %u not found in detectedImageIndices"), midxImage);
2580 }
2581
2582 /*
2583 * Get the ISO's detect guest OS type info and make it's a known one (just
2584 * in case the above step doesn't work right).
2585 */
2586 uint32_t const idxIsoOSType = Global::getOSTypeIndexFromId(mStrDetectedOSTypeId.c_str());
2587 VBOXOSTYPE const enmIsoOSType = idxIsoOSType < Global::cOSTypes ? Global::sOSTypes[idxIsoOSType].osType : VBOXOSTYPE_Unknown;
2588 if ((enmIsoOSType & VBOXOSTYPE_OsTypeMask) == VBOXOSTYPE_Unknown)
2589 return setError(E_FAIL, tr("The supplied ISO file does not contain an OS currently supported for unattended installation"));
2590
2591 /*
2592 * Get the VM's configured guest OS type info.
2593 */
2594 uint32_t const idxMachineOSType = Global::getOSTypeIndexFromId(mStrGuestOsTypeId.c_str());
2595 VBOXOSTYPE const enmMachineOSType = idxMachineOSType < Global::cOSTypes
2596 ? Global::sOSTypes[idxMachineOSType].osType : VBOXOSTYPE_Unknown;
2597
2598 /*
2599 * Check that the detected guest OS type for the ISO is compatible with
2600 * that of the VM, boardly speaking.
2601 */
2602 if (idxMachineOSType != idxIsoOSType)
2603 {
2604 /* Check that the architecture is compatible: */
2605 if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask)
2606 && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86
2607 || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64))
2608 return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch"));
2609
2610 /** @todo check BIOS/EFI requirement */
2611 }
2612
2613 /*
2614 * Do some default property stuff and check other properties.
2615 */
2616 try
2617 {
2618 char szTmp[128];
2619
2620 if (mStrLocale.isEmpty())
2621 {
2622 int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp));
2623 if ( RT_SUCCESS(vrc)
2624 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp))
2625 mStrLocale.assign(szTmp, 5);
2626 else
2627 mStrLocale = "en_US";
2628 Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale));
2629 }
2630
2631 if (mStrLanguage.isEmpty())
2632 {
2633 if (mDetectedOSLanguages.size() > 0)
2634 mStrLanguage = mDetectedOSLanguages[0];
2635 else
2636 mStrLanguage.assign(mStrLocale).findReplace('_', '-');
2637 }
2638
2639 if (mStrCountry.isEmpty())
2640 {
2641 int vrc = RTLocaleQueryUserCountryCode(szTmp);
2642 if (RT_SUCCESS(vrc))
2643 mStrCountry = szTmp;
2644 else if ( mStrLocale.isNotEmpty()
2645 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale))
2646 mStrCountry.assign(mStrLocale, 3, 2);
2647 else
2648 mStrCountry = "US";
2649 }
2650
2651 if (mStrTimeZone.isEmpty())
2652 {
2653 int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp));
2654 if ( RT_SUCCESS(vrc)
2655 && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */)
2656 mStrTimeZone = szTmp;
2657 else
2658 mStrTimeZone = "Etc/UTC";
2659 Assert(mStrTimeZone.isNotEmpty());
2660 }
2661 mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str());
2662 if (!mpTimeZoneInfo)
2663 mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str());
2664 Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC");
2665 if (!mpTimeZoneInfo)
2666 LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str()));
2667
2668 if (mStrHostname.isEmpty())
2669 {
2670 /* Mangle the VM name into a valid hostname. */
2671 for (size_t i = 0; i < strMachineName.length(); i++)
2672 {
2673 char ch = strMachineName[i];
2674 if ( (unsigned)ch < 127
2675 && RT_C_IS_ALNUM(ch))
2676 mStrHostname.append(ch);
2677 else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-"))
2678 mStrHostname.append('-');
2679 }
2680 if (mStrHostname.length() == 0)
2681 mStrHostname.printf("%RTuuid-vm", MachineUuid.raw());
2682 else if (mStrHostname.length() < 3)
2683 mStrHostname.append("-vm");
2684 mStrHostname.append(".myguest.virtualbox.org");
2685 }
2686
2687 if (mStrAuxiliaryBasePath.isEmpty())
2688 {
2689 mStrAuxiliaryBasePath = strDefaultAuxBasePath;
2690 mfIsDefaultAuxiliaryBasePath = true;
2691 }
2692 }
2693 catch (std::bad_alloc &)
2694 {
2695 return E_OUTOFMEMORY;
2696 }
2697
2698 /*
2699 * Instatiate the guest installer matching the ISO.
2700 */
2701 mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion,
2702 mStrDetectedOSFlavor, mStrDetectedOSHints, this);
2703 if (mpInstaller != NULL)
2704 {
2705 hrc = mpInstaller->initInstaller();
2706 if (SUCCEEDED(hrc))
2707 {
2708 /*
2709 * Do the script preps (just reads them).
2710 */
2711 hrc = mpInstaller->prepareUnattendedScripts();
2712 if (SUCCEEDED(hrc))
2713 {
2714 LogFlow(("Unattended::prepare: returns S_OK\n"));
2715 return S_OK;
2716 }
2717 }
2718
2719 /* Destroy the installer instance. */
2720 delete mpInstaller;
2721 mpInstaller = NULL;
2722 }
2723 else
2724 hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND,
2725 tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str());
2726 LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc));
2727 return hrc;
2728}
2729
2730HRESULT Unattended::constructMedia()
2731{
2732 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2733
2734 LogFlow(("===========================================================\n"));
2735 LogFlow(("Call Unattended::constructMedia()\n"));
2736
2737 if (mpInstaller == NULL)
2738 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called");
2739
2740 return mpInstaller->prepareMedia();
2741}
2742
2743HRESULT Unattended::reconfigureVM()
2744{
2745 LogFlow(("===========================================================\n"));
2746 LogFlow(("Call Unattended::reconfigureVM()\n"));
2747
2748 /*
2749 * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues.
2750 */
2751 StorageBus_T enmRecommendedStorageBus = StorageBus_IDE;
2752 {
2753 Bstr bstrGuestOsTypeId;
2754 Bstr bstrDetectedOSTypeId;
2755 {
2756 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2757 if (mpInstaller == NULL)
2758 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2759 bstrGuestOsTypeId = mStrGuestOsTypeId;
2760 bstrDetectedOSTypeId = mStrDetectedOSTypeId;
2761 }
2762 ComPtr<IGuestOSType> ptrGuestOSType;
2763 HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam());
2764 if (SUCCEEDED(hrc))
2765 {
2766 if (!ptrGuestOSType.isNull())
2767 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus);
2768 }
2769 if (FAILED(hrc))
2770 return hrc;
2771
2772 /* If the detected guest OS type differs, log a warning if their DVD storage
2773 bus recommendations differ. */
2774 if (bstrGuestOsTypeId != bstrDetectedOSTypeId)
2775 {
2776 StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE;
2777 hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam());
2778 if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull())
2779 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2);
2780 if (FAILED(hrc))
2781 return hrc;
2782
2783 if (enmRecommendedStorageBus != enmRecommendedStorageBus2)
2784 LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n",
2785 ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(),
2786 ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() ));
2787 }
2788 }
2789
2790 /*
2791 * Take write lock (for lock order reasons, write lock our parent object too)
2792 * then make sure we're the only caller of this method.
2793 */
2794 AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS);
2795 HRESULT hrc;
2796 if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD)
2797 {
2798 RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf();
2799 mhThreadReconfigureVM = hNativeSelf;
2800
2801 /*
2802 * Create a new session, lock the machine and get the session machine object.
2803 * Do the locking without pinning down the write locks, just to be on the safe side.
2804 */
2805 ComPtr<ISession> ptrSession;
2806 try
2807 {
2808 hrc = ptrSession.createInprocObject(CLSID_Session);
2809 }
2810 catch (std::bad_alloc &)
2811 {
2812 hrc = E_OUTOFMEMORY;
2813 }
2814 if (SUCCEEDED(hrc))
2815 {
2816 alock.release();
2817 hrc = mMachine->LockMachine(ptrSession, LockType_Shared);
2818 alock.acquire();
2819 if (SUCCEEDED(hrc))
2820 {
2821 ComPtr<IMachine> ptrSessionMachine;
2822 hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam());
2823 if (SUCCEEDED(hrc))
2824 {
2825 /*
2826 * Hand the session to the inner work and let it do it job.
2827 */
2828 try
2829 {
2830 hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine);
2831 }
2832 catch (...)
2833 {
2834 hrc = E_UNEXPECTED;
2835 }
2836 }
2837
2838 /* Paranoia: release early in case we it a bump below. */
2839 Assert(mhThreadReconfigureVM == hNativeSelf);
2840 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2841
2842 /*
2843 * While unlocking the machine we'll have to drop the locks again.
2844 */
2845 alock.release();
2846
2847 ptrSessionMachine.setNull();
2848 HRESULT hrc2 = ptrSession->UnlockMachine();
2849 AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2));
2850
2851 ptrSession.setNull();
2852
2853 alock.acquire();
2854 }
2855 else
2856 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2857 }
2858 else
2859 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2860 }
2861 else
2862 hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread"));
2863 return hrc;
2864}
2865
2866
2867HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
2868 ComPtr<IMachine> const &rPtrSessionMachine)
2869{
2870 if (mpInstaller == NULL)
2871 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2872
2873 // Fetch all available storage controllers
2874 com::SafeIfaceArray<IStorageController> arrayOfControllers;
2875 HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers));
2876 AssertComRCReturn(hrc, hrc);
2877
2878 /*
2879 * Figure out where the images are to be mounted, adding controllers/ports as needed.
2880 */
2881 std::vector<UnattendedInstallationDisk> vecInstallationDisks;
2882 if (mpInstaller->isAuxiliaryFloppyNeeded())
2883 {
2884 hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock);
2885 if (FAILED(hrc))
2886 return hrc;
2887 }
2888
2889 hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus);
2890 if (FAILED(hrc))
2891 return hrc;
2892
2893 /*
2894 * Mount the images.
2895 */
2896 for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++)
2897 {
2898 UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage);
2899 Assert(pImage->strImagePath.isNotEmpty());
2900 hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock);
2901 if (FAILED(hrc))
2902 return hrc;
2903 }
2904
2905 /*
2906 * Set the boot order.
2907 *
2908 * ASSUME that the HD isn't bootable when we start out, but it will be what
2909 * we boot from after the first stage of the installation is done. Setting
2910 * it first prevents endless reboot cylces.
2911 */
2912 /** @todo consider making 100% sure the disk isn't bootable (edit partition
2913 * table active bits and EFI stuff). */
2914 Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD
2915 || mpInstaller->getBootableDeviceType() == DeviceType_Floppy);
2916 hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk);
2917 if (SUCCEEDED(hrc))
2918 hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType());
2919 if (SUCCEEDED(hrc))
2920 hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD
2921 ? DeviceType_Floppy : DeviceType_DVD);
2922 if (FAILED(hrc))
2923 return hrc;
2924
2925 /*
2926 * Essential step.
2927 *
2928 * HACK ALERT! We have to release the lock here or we'll get into trouble with
2929 * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType).
2930 */
2931 if (SUCCEEDED(hrc))
2932 {
2933 rAutoLock.release();
2934 hrc = rPtrSessionMachine->SaveSettings();
2935 rAutoLock.acquire();
2936 }
2937
2938 return hrc;
2939}
2940
2941/**
2942 * Makes sure we've got a floppy drive attached to a floppy controller, adding
2943 * the auxiliary floppy image to the installation disk vector.
2944 *
2945 * @returns COM status code.
2946 * @param rControllers The existing controllers.
2947 * @param rVecInstallatationDisks The list of image to mount.
2948 * @param rPtrSessionMachine The session machine smart pointer.
2949 * @param rAutoLock The lock.
2950 */
2951HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
2952 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
2953 ComPtr<IMachine> const &rPtrSessionMachine,
2954 AutoMultiWriteLock2 &rAutoLock)
2955{
2956 Assert(mpInstaller->isAuxiliaryFloppyNeeded());
2957
2958 /*
2959 * Look for a floppy controller with a primary drive (A:) we can "insert"
2960 * the auxiliary floppy image. Add a controller and/or a drive if necessary.
2961 */
2962 bool fFoundPort0Dev0 = false;
2963 Bstr bstrControllerName;
2964 Utf8Str strControllerName;
2965
2966 for (size_t i = 0; i < rControllers.size(); ++i)
2967 {
2968 StorageBus_T enmStorageBus;
2969 HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
2970 AssertComRCReturn(hrc, hrc);
2971 if (enmStorageBus == StorageBus_Floppy)
2972 {
2973
2974 /*
2975 * Found a floppy controller.
2976 */
2977 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
2978 AssertComRCReturn(hrc, hrc);
2979
2980 /*
2981 * Check the attchments to see if we've got a device 0 attached on port 0.
2982 *
2983 * While we're at it we eject flppies from all floppy drives we encounter,
2984 * we don't want any confusion at boot or during installation.
2985 */
2986 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
2987 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
2988 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
2989 AssertComRCReturn(hrc, hrc);
2990 strControllerName = bstrControllerName;
2991 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
2992
2993 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
2994 {
2995 LONG iPort = -1;
2996 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
2997 AssertComRCReturn(hrc, hrc);
2998
2999 LONG iDevice = -1;
3000 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3001 AssertComRCReturn(hrc, hrc);
3002
3003 DeviceType_T enmType;
3004 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3005 AssertComRCReturn(hrc, hrc);
3006
3007 if (enmType == DeviceType_Floppy)
3008 {
3009 ComPtr<IMedium> ptrMedium;
3010 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3011 AssertComRCReturn(hrc, hrc);
3012
3013 if (ptrMedium.isNotNull())
3014 {
3015 ptrMedium.setNull();
3016 rAutoLock.release();
3017 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3018 rAutoLock.acquire();
3019 }
3020
3021 if (iPort == 0 && iDevice == 0)
3022 fFoundPort0Dev0 = true;
3023 }
3024 else if (iPort == 0 && iDevice == 0)
3025 return setError(E_FAIL,
3026 tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"),
3027 bstrControllerName.raw());
3028 }
3029 }
3030 }
3031
3032 /*
3033 * Add a floppy controller if we need to.
3034 */
3035 if (strControllerName.isEmpty())
3036 {
3037 bstrControllerName = strControllerName = "Floppy";
3038 ComPtr<IStorageController> ptrControllerIgnored;
3039 HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy,
3040 ptrControllerIgnored.asOutParam());
3041 LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc));
3042 if (FAILED(hrc))
3043 return hrc;
3044 }
3045
3046 /*
3047 * Adding a floppy drive (if needed) and mounting the auxiliary image is
3048 * done later together with the ISOs.
3049 */
3050 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName,
3051 DeviceType_Floppy, AccessMode_ReadWrite,
3052 0, 0,
3053 fFoundPort0Dev0 /*fMountOnly*/,
3054 mpInstaller->getAuxiliaryFloppyFilePath()));
3055 return S_OK;
3056}
3057
3058/**
3059 * Reconfigures DVD drives of the VM to mount all the ISOs we need.
3060 *
3061 * This will umount all DVD media.
3062 *
3063 * @returns COM status code.
3064 * @param rControllers The existing controllers.
3065 * @param rVecInstallatationDisks The list of image to mount.
3066 * @param rPtrSessionMachine The session machine smart pointer.
3067 * @param rAutoLock The lock.
3068 * @param enmRecommendedStorageBus The recommended storage bus type for adding
3069 * DVD drives on.
3070 */
3071HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
3072 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
3073 ComPtr<IMachine> const &rPtrSessionMachine,
3074 AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus)
3075{
3076 /*
3077 * Enumerate the attachements of every controller, looking for DVD drives,
3078 * ASSUMEING all drives are bootable.
3079 *
3080 * Eject the medium from all the drives (don't want any confusion) and look
3081 * for the recommended storage bus in case we need to add more drives.
3082 */
3083 HRESULT hrc;
3084 std::list<ControllerSlot> lstControllerDvdSlots;
3085 Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */
3086 Utf8Str strControllerName;
3087 Bstr bstrControllerName;
3088 for (size_t i = 0; i < rControllers.size(); ++i)
3089 {
3090 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3091 AssertComRCReturn(hrc, hrc);
3092 strControllerName = bstrControllerName;
3093
3094 /* Look for recommended storage bus. */
3095 StorageBus_T enmStorageBus;
3096 hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3097 AssertComRCReturn(hrc, hrc);
3098 if (enmStorageBus == enmRecommendedStorageBus)
3099 {
3100 strRecommendedControllerName = bstrControllerName;
3101 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3102 }
3103
3104 /* Scan the controller attachments. */
3105 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3106 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3107 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3108 AssertComRCReturn(hrc, hrc);
3109
3110 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3111 {
3112 DeviceType_T enmType;
3113 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3114 AssertComRCReturn(hrc, hrc);
3115 if (enmType == DeviceType_DVD)
3116 {
3117 LONG iPort = -1;
3118 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3119 AssertComRCReturn(hrc, hrc);
3120
3121 LONG iDevice = -1;
3122 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3123 AssertComRCReturn(hrc, hrc);
3124
3125 /* Remeber it. */
3126 lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/));
3127
3128 /* Eject the medium, if any. */
3129 ComPtr<IMedium> ptrMedium;
3130 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3131 AssertComRCReturn(hrc, hrc);
3132 if (ptrMedium.isNotNull())
3133 {
3134 ptrMedium.setNull();
3135
3136 rAutoLock.release();
3137 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3138 rAutoLock.acquire();
3139 }
3140 }
3141 }
3142 }
3143
3144 /*
3145 * How many drives do we need? Add more if necessary.
3146 */
3147 ULONG cDvdDrivesNeeded = 0;
3148 if (mpInstaller->isAuxiliaryIsoNeeded())
3149 cDvdDrivesNeeded++;
3150 if (mpInstaller->isOriginalIsoNeeded())
3151 cDvdDrivesNeeded++;
3152#if 0 /* These are now in the AUX VISO. */
3153 if (mpInstaller->isAdditionsIsoNeeded())
3154 cDvdDrivesNeeded++;
3155 if (mpInstaller->isValidationKitIsoNeeded())
3156 cDvdDrivesNeeded++;
3157#endif
3158 Assert(cDvdDrivesNeeded > 0);
3159 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3160 {
3161 /* Do we need to add the recommended controller? */
3162 if (strRecommendedControllerName.isEmpty())
3163 {
3164 switch (enmRecommendedStorageBus)
3165 {
3166 case StorageBus_IDE: strRecommendedControllerName = "IDE"; break;
3167 case StorageBus_SATA: strRecommendedControllerName = "SATA"; break;
3168 case StorageBus_SCSI: strRecommendedControllerName = "SCSI"; break;
3169 case StorageBus_SAS: strRecommendedControllerName = "SAS"; break;
3170 case StorageBus_USB: strRecommendedControllerName = "USB"; break;
3171 case StorageBus_PCIe: strRecommendedControllerName = "PCIe"; break;
3172 default:
3173 return setError(E_FAIL, tr("Support for recommended storage bus %d not implemented"),
3174 (int)enmRecommendedStorageBus);
3175 }
3176 ComPtr<IStorageController> ptrControllerIgnored;
3177 hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus,
3178 ptrControllerIgnored.asOutParam());
3179 LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc));
3180 if (FAILED(hrc))
3181 return hrc;
3182 }
3183
3184 /* Add free controller slots, maybe raising the port limit on the controller if we can. */
3185 hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine,
3186 cDvdDrivesNeeded, lstControllerDvdSlots);
3187 if (FAILED(hrc))
3188 return hrc;
3189 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3190 {
3191 /* We could in many cases create another controller here, but it's not worth the effort. */
3192 return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "",
3193 cDvdDrivesNeeded - lstControllerDvdSlots.size()),
3194 strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size());
3195 }
3196 Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size());
3197 }
3198
3199 /*
3200 * Sort the DVD slots in boot order.
3201 */
3202 lstControllerDvdSlots.sort();
3203
3204 /*
3205 * Prepare ISO mounts.
3206 *
3207 * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots
3208 * according to the boot order.
3209 */
3210 std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin();
3211 if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso())
3212 {
3213 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath()));
3214 ++itDvdSlot;
3215 }
3216
3217 if (mpInstaller->isOriginalIsoNeeded())
3218 {
3219 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath()));
3220 ++itDvdSlot;
3221 }
3222
3223 if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso())
3224 {
3225 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath()));
3226 ++itDvdSlot;
3227 }
3228
3229#if 0 /* These are now in the AUX VISO. */
3230 if (mpInstaller->isAdditionsIsoNeeded())
3231 {
3232 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath()));
3233 ++itDvdSlot;
3234 }
3235
3236 if (mpInstaller->isValidationKitIsoNeeded())
3237 {
3238 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath()));
3239 ++itDvdSlot;
3240 }
3241#endif
3242
3243 return S_OK;
3244}
3245
3246/**
3247 * Used to find more free slots for DVD drives during VM reconfiguration.
3248 *
3249 * This may modify the @a portCount property of the given controller.
3250 *
3251 * @returns COM status code.
3252 * @param rStrControllerName The name of the controller to find/create
3253 * free slots on.
3254 * @param enmStorageBus The storage bus type.
3255 * @param rPtrSessionMachine Reference to the session machine.
3256 * @param cSlotsNeeded Total slots needed (including those we've
3257 * already found).
3258 * @param rDvdSlots The slot collection for DVD drives to add
3259 * free slots to as we find/create them.
3260 */
3261HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
3262 ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
3263 std::list<ControllerSlot> &rDvdSlots)
3264{
3265 Assert(cSlotsNeeded > rDvdSlots.size());
3266
3267 /*
3268 * Get controlleer stats.
3269 */
3270 ComPtr<IStorageController> pController;
3271 HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam());
3272 AssertComRCReturn(hrc, hrc);
3273
3274 ULONG cMaxDevicesPerPort = 1;
3275 hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort);
3276 AssertComRCReturn(hrc, hrc);
3277 AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED);
3278
3279 ULONG cPorts = 0;
3280 hrc = pController->COMGETTER(PortCount)(&cPorts);
3281 AssertComRCReturn(hrc, hrc);
3282
3283 /*
3284 * Get the attachment list and turn into an internal list for lookup speed.
3285 */
3286 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3287 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(),
3288 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3289 AssertComRCReturn(hrc, hrc);
3290
3291 std::vector<ControllerSlot> arrayOfUsedSlots;
3292 for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++)
3293 {
3294 LONG iPort = -1;
3295 hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort);
3296 AssertComRCReturn(hrc, hrc);
3297
3298 LONG iDevice = -1;
3299 hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice);
3300 AssertComRCReturn(hrc, hrc);
3301
3302 arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/));
3303 }
3304
3305 /*
3306 * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots.
3307 */
3308 for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++)
3309 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3310 {
3311 bool fFound = false;
3312 for (size_t i = 0; i < arrayOfUsedSlots.size(); i++)
3313 if ( arrayOfUsedSlots[i].iPort == iPort
3314 && arrayOfUsedSlots[i].iDevice == iDevice)
3315 {
3316 fFound = true;
3317 break;
3318 }
3319 if (!fFound)
3320 {
3321 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3322 if (rDvdSlots.size() >= cSlotsNeeded)
3323 return S_OK;
3324 }
3325 }
3326
3327 /*
3328 * Okay we still need more ports. See if increasing the number of controller
3329 * ports would solve it.
3330 */
3331 ULONG cMaxPorts = 1;
3332 hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts);
3333 AssertComRCReturn(hrc, hrc);
3334 if (cMaxPorts <= cPorts)
3335 return S_OK;
3336 size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort;
3337 if (cPorts + cNewPortsNeeded > cMaxPorts)
3338 return S_OK;
3339
3340 /*
3341 * Raise the port count and add the free slots we've just created.
3342 */
3343 hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded);
3344 AssertComRCReturn(hrc, hrc);
3345 int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded);
3346 for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++)
3347 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3348 {
3349 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3350 if (rDvdSlots.size() >= cSlotsNeeded)
3351 return S_OK;
3352 }
3353
3354 /* We should not get here! */
3355 AssertLogRelFailedReturn(E_UNEXPECTED);
3356}
3357
3358HRESULT Unattended::done()
3359{
3360 LogFlow(("Unattended::done\n"));
3361 if (mpInstaller)
3362 {
3363 LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller));
3364 delete mpInstaller;
3365 mpInstaller = NULL;
3366 }
3367 return S_OK;
3368}
3369
3370HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
3371{
3372 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3373 isoPath = mStrIsoPath;
3374 return S_OK;
3375}
3376
3377HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
3378{
3379 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3380 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3381 mStrIsoPath = isoPath;
3382 mfDoneDetectIsoOS = false;
3383 return S_OK;
3384}
3385
3386HRESULT Unattended::getUser(com::Utf8Str &user)
3387{
3388 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3389 user = mStrUser;
3390 return S_OK;
3391}
3392
3393
3394HRESULT Unattended::setUser(const com::Utf8Str &user)
3395{
3396 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3397 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3398 mStrUser = user;
3399 return S_OK;
3400}
3401
3402HRESULT Unattended::getPassword(com::Utf8Str &password)
3403{
3404 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3405 password = mStrPassword;
3406 return S_OK;
3407}
3408
3409HRESULT Unattended::setPassword(const com::Utf8Str &password)
3410{
3411 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3412 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3413 mStrPassword = password;
3414 return S_OK;
3415}
3416
3417HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
3418{
3419 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3420 fullUserName = mStrFullUserName;
3421 return S_OK;
3422}
3423
3424HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
3425{
3426 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3427 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3428 mStrFullUserName = fullUserName;
3429 return S_OK;
3430}
3431
3432HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
3433{
3434 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3435 productKey = mStrProductKey;
3436 return S_OK;
3437}
3438
3439HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
3440{
3441 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3442 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3443 mStrProductKey = productKey;
3444 return S_OK;
3445}
3446
3447HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
3448{
3449 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3450 additionsIsoPath = mStrAdditionsIsoPath;
3451 return S_OK;
3452}
3453
3454HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
3455{
3456 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3457 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3458 mStrAdditionsIsoPath = additionsIsoPath;
3459 return S_OK;
3460}
3461
3462HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
3463{
3464 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3465 *installGuestAdditions = mfInstallGuestAdditions;
3466 return S_OK;
3467}
3468
3469HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
3470{
3471 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3472 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3473 mfInstallGuestAdditions = installGuestAdditions != FALSE;
3474 return S_OK;
3475}
3476
3477HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
3478{
3479 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3480 aValidationKitIsoPath = mStrValidationKitIsoPath;
3481 return S_OK;
3482}
3483
3484HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
3485{
3486 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3487 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3488 mStrValidationKitIsoPath = aValidationKitIsoPath;
3489 return S_OK;
3490}
3491
3492HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
3493{
3494 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3495 *aInstallTestExecService = mfInstallTestExecService;
3496 return S_OK;
3497}
3498
3499HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
3500{
3501 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3502 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3503 mfInstallTestExecService = aInstallTestExecService != FALSE;
3504 return S_OK;
3505}
3506
3507HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
3508{
3509 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3510 aTimeZone = mStrTimeZone;
3511 return S_OK;
3512}
3513
3514HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
3515{
3516 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3517 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3518 mStrTimeZone = aTimezone;
3519 return S_OK;
3520}
3521
3522HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
3523{
3524 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3525 aLocale = mStrLocale;
3526 return S_OK;
3527}
3528
3529HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
3530{
3531 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3532 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3533 if ( aLocale.isEmpty() /* use default */
3534 || ( aLocale.length() == 5
3535 && RT_C_IS_LOWER(aLocale[0])
3536 && RT_C_IS_LOWER(aLocale[1])
3537 && aLocale[2] == '_'
3538 && RT_C_IS_UPPER(aLocale[3])
3539 && RT_C_IS_UPPER(aLocale[4])) )
3540 {
3541 mStrLocale = aLocale;
3542 return S_OK;
3543 }
3544 return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters"));
3545}
3546
3547HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
3548{
3549 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3550 aLanguage = mStrLanguage;
3551 return S_OK;
3552}
3553
3554HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
3555{
3556 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3557 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3558 mStrLanguage = aLanguage;
3559 return S_OK;
3560}
3561
3562HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
3563{
3564 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3565 aCountry = mStrCountry;
3566 return S_OK;
3567}
3568
3569HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
3570{
3571 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3572 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3573 if ( aCountry.isEmpty()
3574 || ( aCountry.length() == 2
3575 && RT_C_IS_UPPER(aCountry[0])
3576 && RT_C_IS_UPPER(aCountry[1])) )
3577 {
3578 mStrCountry = aCountry;
3579 return S_OK;
3580 }
3581 return setError(E_INVALIDARG, tr("Expected two upper cased letters"));
3582}
3583
3584HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
3585{
3586 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3587 aProxy = mStrProxy; /// @todo turn schema map into string or something.
3588 return S_OK;
3589}
3590
3591HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
3592{
3593 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3594 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3595 if (aProxy.isEmpty())
3596 {
3597 /* set default proxy */
3598 /** @todo BUGBUG! implement this */
3599 }
3600 else if (aProxy.equalsIgnoreCase("none"))
3601 {
3602 /* clear proxy config */
3603 mStrProxy.setNull();
3604 }
3605 else
3606 {
3607 /** @todo Parse and set proxy config into a schema map or something along those lines. */
3608 /** @todo BUGBUG! implement this */
3609 // return E_NOTIMPL;
3610 mStrProxy = aProxy;
3611 }
3612 return S_OK;
3613}
3614
3615HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
3616{
3617 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3618 aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";");
3619 return S_OK;
3620}
3621
3622HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
3623{
3624 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3625 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3626 if (aPackageSelectionAdjustments.isEmpty())
3627 mPackageSelectionAdjustments.clear();
3628 else
3629 {
3630 RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";");
3631 for (size_t i = 0; i < arrayStrSplit.size(); i++)
3632 {
3633 if (arrayStrSplit[i].equals("minimal"))
3634 { /* okay */ }
3635 else
3636 return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str());
3637 }
3638 mPackageSelectionAdjustments = arrayStrSplit;
3639 }
3640 return S_OK;
3641}
3642
3643HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
3644{
3645 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3646 aHostname = mStrHostname;
3647 return S_OK;
3648}
3649
3650HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
3651{
3652 /*
3653 * Validate input.
3654 */
3655 if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U))
3656 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3657 tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()),
3658 aHostname.c_str(), aHostname.length());
3659 size_t cLabels = 0;
3660 const char *pszSrc = aHostname.c_str();
3661 for (;;)
3662 {
3663 size_t cchLabel = 1;
3664 char ch = *pszSrc++;
3665 if (RT_C_IS_ALNUM(ch))
3666 {
3667 cLabels++;
3668 while ((ch = *pszSrc++) != '.' && ch != '\0')
3669 {
3670 if (RT_C_IS_ALNUM(ch) || ch == '-')
3671 {
3672 if (cchLabel < 63)
3673 cchLabel++;
3674 else
3675 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3676 tr("Invalid hostname '%s' - label %u is too long, max is 63."),
3677 aHostname.c_str(), cLabels);
3678 }
3679 else
3680 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3681 tr("Invalid hostname '%s' - illegal char '%c' at position %zu"),
3682 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3683 }
3684 if (cLabels == 1 && cchLabel < 2)
3685 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3686 tr("Invalid hostname '%s' - the name part must be at least two characters long"),
3687 aHostname.c_str());
3688 if (ch == '\0')
3689 break;
3690 }
3691 else if (ch != '\0')
3692 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3693 tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"),
3694 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3695 else
3696 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3697 tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str());
3698 }
3699 if (cLabels < 2)
3700 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3701 tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str());
3702
3703 /*
3704 * Make the change.
3705 */
3706 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3707 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3708 mStrHostname = aHostname;
3709 return S_OK;
3710}
3711
3712HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
3713{
3714 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3715 aAuxiliaryBasePath = mStrAuxiliaryBasePath;
3716 return S_OK;
3717}
3718
3719HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
3720{
3721 if (aAuxiliaryBasePath.isEmpty())
3722 return setError(E_INVALIDARG, tr("Empty base path is not allowed"));
3723 if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str()))
3724 return setError(E_INVALIDARG, tr("Base path must be absolute"));
3725
3726 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3727 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3728 mStrAuxiliaryBasePath = aAuxiliaryBasePath;
3729 mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty();
3730 return S_OK;
3731}
3732
3733HRESULT Unattended::getImageIndex(ULONG *index)
3734{
3735 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3736 *index = midxImage;
3737 return S_OK;
3738}
3739
3740HRESULT Unattended::setImageIndex(ULONG index)
3741{
3742 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3743 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3744
3745 /* Validate the selection if detection was done already: */
3746 if (mDetectedImages.size() > 0)
3747 {
3748 for (size_t i = 0; i < mDetectedImages.size(); i++)
3749 if (mDetectedImages[i].mImageIndex == index)
3750 {
3751 midxImage = index;
3752 i_updateDetectedAttributeForImage(mDetectedImages[i]);
3753 return S_OK;
3754 }
3755 LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */
3756 }
3757
3758 midxImage = index;
3759 return S_OK;
3760}
3761
3762HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
3763{
3764 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3765 return mMachine.queryInterfaceTo(aMachine.asOutParam());
3766}
3767
3768HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
3769{
3770 /*
3771 * Lookup the VM so we can safely get the Machine instance.
3772 * (Don't want to test how reliable XPCOM and COM are with finding
3773 * the local object instance when a client passes a stub back.)
3774 */
3775 Bstr bstrUuidMachine;
3776 HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam());
3777 if (SUCCEEDED(hrc))
3778 {
3779 Guid UuidMachine(bstrUuidMachine);
3780 ComObjPtr<Machine> ptrMachine;
3781 hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
3782 if (SUCCEEDED(hrc))
3783 {
3784 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3785 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER,
3786 tr("Cannot change after prepare() has been called")));
3787 mMachine = ptrMachine;
3788 mMachineUuid = UuidMachine;
3789 if (mfIsDefaultAuxiliaryBasePath)
3790 mStrAuxiliaryBasePath.setNull();
3791 hrc = S_OK;
3792 }
3793 }
3794 return hrc;
3795}
3796
3797HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
3798{
3799 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3800 if ( mStrScriptTemplatePath.isNotEmpty()
3801 || mpInstaller == NULL)
3802 aScriptTemplatePath = mStrScriptTemplatePath;
3803 else
3804 aScriptTemplatePath = mpInstaller->getTemplateFilePath();
3805 return S_OK;
3806}
3807
3808HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
3809{
3810 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3811 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3812 mStrScriptTemplatePath = aScriptTemplatePath;
3813 return S_OK;
3814}
3815
3816HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
3817{
3818 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3819 if ( mStrPostInstallScriptTemplatePath.isNotEmpty()
3820 || mpInstaller == NULL)
3821 aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath;
3822 else
3823 aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath();
3824 return S_OK;
3825}
3826
3827HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
3828{
3829 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3830 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3831 mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath;
3832 return S_OK;
3833}
3834
3835HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
3836{
3837 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3838 aPostInstallCommand = mStrPostInstallCommand;
3839 return S_OK;
3840}
3841
3842HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
3843{
3844 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3845 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3846 mStrPostInstallCommand = aPostInstallCommand;
3847 return S_OK;
3848}
3849
3850HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
3851{
3852 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3853 if ( mStrExtraInstallKernelParameters.isNotEmpty()
3854 || mpInstaller == NULL)
3855 aExtraInstallKernelParameters = mStrExtraInstallKernelParameters;
3856 else
3857 aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters();
3858 return S_OK;
3859}
3860
3861HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
3862{
3863 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3864 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3865 mStrExtraInstallKernelParameters = aExtraInstallKernelParameters;
3866 return S_OK;
3867}
3868
3869HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
3870{
3871 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3872 aDetectedOSTypeId = mStrDetectedOSTypeId;
3873 return S_OK;
3874}
3875
3876HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
3877{
3878 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3879 aDetectedOSVersion = mStrDetectedOSVersion;
3880 return S_OK;
3881}
3882
3883HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
3884{
3885 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3886 aDetectedOSFlavor = mStrDetectedOSFlavor;
3887 return S_OK;
3888}
3889
3890HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
3891{
3892 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3893 aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " ");
3894 return S_OK;
3895}
3896
3897HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
3898{
3899 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3900 aDetectedOSHints = mStrDetectedOSHints;
3901 return S_OK;
3902}
3903
3904HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
3905{
3906 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3907 aDetectedImageNames.clear();
3908 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3909 {
3910 Utf8Str strTmp;
3911 aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp));
3912 }
3913 return S_OK;
3914}
3915
3916HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
3917{
3918 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3919 aDetectedImageIndices.clear();
3920 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3921 aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex);
3922 return S_OK;
3923}
3924
3925HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
3926{
3927 /*
3928 * Take the initial position that it's not supported, so we can return
3929 * right away when we decide it's not possible.
3930 */
3931 *aIsUnattendedInstallSupported = false;
3932
3933 /* Unattended is disabled by default if we could not detect OS type. */
3934 if (mStrDetectedOSTypeId.isEmpty())
3935 return S_OK;
3936
3937 const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask);
3938
3939 /* We require a version to have been detected, except for windows where the
3940 field is generally only used for the service pack number at present and
3941 will be empty for RTMs isos. */
3942 if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT
3943 || enmOsTypeMasked >= VBOXOSTYPE_OS2)
3944 && mStrDetectedOSVersion.isEmpty())
3945 return S_OK;
3946
3947 /*
3948 * Sort out things that we know doesn't work. Order by VBOXOSTYPE value.
3949 */
3950
3951 /* We do not support any of the DOS based windows version, nor DOS, in case
3952 any of that gets detected (it shouldn't): */
3953 if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT)
3954 return S_OK;
3955
3956 /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */
3957 if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4)
3958 return S_OK;
3959
3960 /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been
3961 tested, but we'll get to the others eventually): */
3962 if ( enmOsTypeMasked >= VBOXOSTYPE_OS2
3963 && enmOsTypeMasked < VBOXOSTYPE_Linux
3964 && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ )
3965 return S_OK;
3966
3967 /* Old Debians fail since package repos have been move to some other mirror location. */
3968 if ( enmOsTypeMasked == VBOXOSTYPE_Debian
3969 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0)
3970 return S_OK;
3971
3972 /* Skip all OpenSUSE variants for now. */
3973 if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE)
3974 return S_OK;
3975
3976 if (enmOsTypeMasked == VBOXOSTYPE_Ubuntu)
3977 {
3978 /* We cannot install Ubuntus older than 11.04. */
3979 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0)
3980 return S_OK;
3981 /* Lubuntu, starting with 20.04, has switched to calamares, which cannot be automated. */
3982 if ( RTStrIStr(mStrDetectedOSFlavor.c_str(), "lubuntu")
3983 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "20.04") > 0)
3984 return S_OK;
3985 }
3986
3987 /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */
3988 if ( enmOsTypeMasked == VBOXOSTYPE_Oracle
3989 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0)
3990 return S_OK;
3991
3992 /* Fredora ISOs cannot be installed at present. */
3993 if (enmOsTypeMasked == VBOXOSTYPE_FedoraCore)
3994 return S_OK;
3995
3996 /*
3997 * Assume the rest works.
3998 */
3999 *aIsUnattendedInstallSupported = true;
4000 return S_OK;
4001}
4002
4003HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
4004{
4005 *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork;
4006 return S_OK;
4007}
4008
4009HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
4010{
4011 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4012 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4013 mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork);
4014 return S_OK;
4015}
4016
4017/*
4018 * Getters that the installer and script classes can use.
4019 */
4020Utf8Str const &Unattended::i_getIsoPath() const
4021{
4022 Assert(isReadLockedOnCurrentThread());
4023 return mStrIsoPath;
4024}
4025
4026Utf8Str const &Unattended::i_getUser() const
4027{
4028 Assert(isReadLockedOnCurrentThread());
4029 return mStrUser;
4030}
4031
4032Utf8Str const &Unattended::i_getPassword() const
4033{
4034 Assert(isReadLockedOnCurrentThread());
4035 return mStrPassword;
4036}
4037
4038Utf8Str const &Unattended::i_getFullUserName() const
4039{
4040 Assert(isReadLockedOnCurrentThread());
4041 return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
4042}
4043
4044Utf8Str const &Unattended::i_getProductKey() const
4045{
4046 Assert(isReadLockedOnCurrentThread());
4047 return mStrProductKey;
4048}
4049
4050Utf8Str const &Unattended::i_getProxy() const
4051{
4052 Assert(isReadLockedOnCurrentThread());
4053 return mStrProxy;
4054}
4055
4056Utf8Str const &Unattended::i_getAdditionsIsoPath() const
4057{
4058 Assert(isReadLockedOnCurrentThread());
4059 return mStrAdditionsIsoPath;
4060}
4061
4062bool Unattended::i_getInstallGuestAdditions() const
4063{
4064 Assert(isReadLockedOnCurrentThread());
4065 return mfInstallGuestAdditions;
4066}
4067
4068Utf8Str const &Unattended::i_getValidationKitIsoPath() const
4069{
4070 Assert(isReadLockedOnCurrentThread());
4071 return mStrValidationKitIsoPath;
4072}
4073
4074bool Unattended::i_getInstallTestExecService() const
4075{
4076 Assert(isReadLockedOnCurrentThread());
4077 return mfInstallTestExecService;
4078}
4079
4080Utf8Str const &Unattended::i_getTimeZone() const
4081{
4082 Assert(isReadLockedOnCurrentThread());
4083 return mStrTimeZone;
4084}
4085
4086PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
4087{
4088 Assert(isReadLockedOnCurrentThread());
4089 return mpTimeZoneInfo;
4090}
4091
4092Utf8Str const &Unattended::i_getLocale() const
4093{
4094 Assert(isReadLockedOnCurrentThread());
4095 return mStrLocale;
4096}
4097
4098Utf8Str const &Unattended::i_getLanguage() const
4099{
4100 Assert(isReadLockedOnCurrentThread());
4101 return mStrLanguage;
4102}
4103
4104Utf8Str const &Unattended::i_getCountry() const
4105{
4106 Assert(isReadLockedOnCurrentThread());
4107 return mStrCountry;
4108}
4109
4110bool Unattended::i_isMinimalInstallation() const
4111{
4112 size_t i = mPackageSelectionAdjustments.size();
4113 while (i-- > 0)
4114 if (mPackageSelectionAdjustments[i].equals("minimal"))
4115 return true;
4116 return false;
4117}
4118
4119Utf8Str const &Unattended::i_getHostname() const
4120{
4121 Assert(isReadLockedOnCurrentThread());
4122 return mStrHostname;
4123}
4124
4125Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
4126{
4127 Assert(isReadLockedOnCurrentThread());
4128 return mStrAuxiliaryBasePath;
4129}
4130
4131ULONG Unattended::i_getImageIndex() const
4132{
4133 Assert(isReadLockedOnCurrentThread());
4134 return midxImage;
4135}
4136
4137Utf8Str const &Unattended::i_getScriptTemplatePath() const
4138{
4139 Assert(isReadLockedOnCurrentThread());
4140 return mStrScriptTemplatePath;
4141}
4142
4143Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
4144{
4145 Assert(isReadLockedOnCurrentThread());
4146 return mStrPostInstallScriptTemplatePath;
4147}
4148
4149Utf8Str const &Unattended::i_getPostInstallCommand() const
4150{
4151 Assert(isReadLockedOnCurrentThread());
4152 return mStrPostInstallCommand;
4153}
4154
4155Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
4156{
4157 Assert(isReadLockedOnCurrentThread());
4158 /* Only the installer knows, forward the call. */
4159 AssertReturn(mpInstaller != NULL, Utf8Str::Empty);
4160 return mpInstaller->getAuxiliaryInstallDir();
4161}
4162
4163Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
4164{
4165 Assert(isReadLockedOnCurrentThread());
4166 return mStrExtraInstallKernelParameters;
4167}
4168
4169bool Unattended::i_isRtcUsingUtc() const
4170{
4171 Assert(isReadLockedOnCurrentThread());
4172 return mfRtcUseUtc;
4173}
4174
4175bool Unattended::i_isGuestOs64Bit() const
4176{
4177 Assert(isReadLockedOnCurrentThread());
4178 return mfGuestOs64Bit;
4179}
4180
4181bool Unattended::i_isFirmwareEFI() const
4182{
4183 Assert(isReadLockedOnCurrentThread());
4184 return menmFirmwareType != FirmwareType_BIOS;
4185}
4186
4187Utf8Str const &Unattended::i_getDetectedOSVersion()
4188{
4189 Assert(isReadLockedOnCurrentThread());
4190 return mStrDetectedOSVersion;
4191}
4192
4193bool Unattended::i_getAvoidUpdatesOverNetwork() const
4194{
4195 Assert(isReadLockedOnCurrentThread());
4196 return mfAvoidUpdatesOverNetwork;
4197}
4198
4199HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
4200 AutoMultiWriteLock2 &rLock)
4201{
4202 /*
4203 * Attach the disk image
4204 * HACK ALERT! Temporarily release the Unattended lock.
4205 */
4206 rLock.release();
4207
4208 ComPtr<IMedium> ptrMedium;
4209 HRESULT rc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(),
4210 pImage->enmDeviceType,
4211 pImage->enmAccessType,
4212 true,
4213 ptrMedium.asOutParam());
4214 LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", rc));
4215 if (SUCCEEDED(rc))
4216 {
4217 if (pImage->fMountOnly)
4218 {
4219 // mount the opened disk image
4220 rc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4221 pImage->iDevice, ptrMedium, TRUE /*fForce*/);
4222 LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", rc));
4223 }
4224 else
4225 {
4226 //attach the opened disk image to the controller
4227 rc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4228 pImage->iDevice, pImage->enmDeviceType, ptrMedium);
4229 LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", rc));
4230 }
4231 }
4232
4233 rLock.acquire();
4234 return rc;
4235}
4236
4237bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId)
4238{
4239 ComPtr<IGuestOSType> pGuestOSType;
4240 HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam());
4241 if (SUCCEEDED(hrc))
4242 {
4243 BOOL fIs64Bit = FALSE;
4244 if (!pGuestOSType.isNull())
4245 hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit);
4246 if (SUCCEEDED(hrc))
4247 return fIs64Bit != FALSE;
4248 }
4249 return false;
4250}
4251
4252
4253bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage)
4254{
4255 bool fRet = true;
4256
4257 /*
4258 * If the image doesn't have a valid value, we don't change it.
4259 * This is obviously a little bit bogus, but what can we do...
4260 */
4261 const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType);
4262 if (pszOSTypeId && strcmp(pszOSTypeId, "Other") != 0)
4263 mStrDetectedOSTypeId = pszOSTypeId;
4264 else
4265 fRet = false;
4266
4267 if (rImage.mVersion.isNotEmpty())
4268 mStrDetectedOSVersion = rImage.mVersion;
4269 else
4270 fRet = false;
4271
4272 if (rImage.mFlavor.isNotEmpty())
4273 mStrDetectedOSFlavor = rImage.mFlavor;
4274 else
4275 fRet = false;
4276
4277 if (rImage.mLanguages.size() > 0)
4278 mDetectedOSLanguages = rImage.mLanguages;
4279 else
4280 fRet = false;
4281
4282 mEnmOsType = rImage.mEnmOsType;
4283
4284 return fRet;
4285}
Note: See TracBrowser for help on using the repository browser.

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