VirtualBox

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

Last change on this file since 102337 was 102337, checked in by vboxsync, 18 months ago

Main/Unattended: Implemented support for subiquity-/cloud-init-based installers. This enables installing guest newer Linux-based OSes like Ubuntu >= 22.10 in Unattended mode. More detection code for other variants will follow. Extended testcases. bugref:10551

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

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