VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use