VirtualBox

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

Last change on this file since 102347 was 102347, checked in by vboxsync, 15 months ago

Main/Unattended: Added support for Ubuntu Server >= 20.04. bugref:10551

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

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