VirtualBox

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

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

Main/Unattended: Changed a three uses of VBOXOSTYPE_OsTypeMask to either VBOXOSTYPE_OsMask or VBOXOSTYPE_OsFamilyMask, leaving one use. A bit risky, but the code didn't look right the way it was.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 172.2 KB
Line 
1/* $Id: UnattendedImpl.cpp 102170 2023-11-20 22:54:22Z vboxsync $ */
2/** @file
3 * Unattended class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.215389.xyz.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
33#include "LoggingNew.h"
34#include "VirtualBoxBase.h"
35#include "UnattendedImpl.h"
36#include "UnattendedInstaller.h"
37#include "UnattendedScript.h"
38#include "VirtualBoxImpl.h"
39#include "SystemPropertiesImpl.h"
40#include "MachineImpl.h"
41#include "Global.h"
42#include "StringifyEnums.h"
43
44#include <VBox/err.h>
45#include <iprt/cpp/xml.h>
46#include <iprt/ctype.h>
47#include <iprt/file.h>
48#ifndef RT_OS_WINDOWS
49# include <iprt/formats/mz.h>
50# include <iprt/formats/pecoff.h>
51#endif
52#include <iprt/formats/wim.h>
53#include <iprt/fsvfs.h>
54#include <iprt/inifile.h>
55#include <iprt/locale.h>
56#include <iprt/path.h>
57#include <iprt/vfs.h>
58
59using namespace std;
60
61
62/*********************************************************************************************************************************
63* Structures and Typedefs *
64*********************************************************************************************************************************/
65/**
66 * Controller slot for a DVD drive.
67 *
68 * The slot can be free and needing a drive to be attached along with the ISO
69 * image, or it may already be there and only need mounting the ISO. The
70 * ControllerSlot::fFree member indicates which it is.
71 */
72struct ControllerSlot
73{
74 StorageBus_T enmBus;
75 Utf8Str strControllerName;
76 LONG iPort;
77 LONG iDevice;
78 bool fFree;
79
80 ControllerSlot(StorageBus_T a_enmBus, const Utf8Str &a_rName, LONG a_iPort, LONG a_iDevice, bool a_fFree)
81 : enmBus(a_enmBus), strControllerName(a_rName), iPort(a_iPort), iDevice(a_iDevice), fFree(a_fFree)
82 {}
83
84 bool operator<(const ControllerSlot &rThat) const
85 {
86 if (enmBus == rThat.enmBus)
87 {
88 if (strControllerName == rThat.strControllerName)
89 {
90 if (iPort == rThat.iPort)
91 return iDevice < rThat.iDevice;
92 return iPort < rThat.iPort;
93 }
94 return strControllerName < rThat.strControllerName;
95 }
96
97 /*
98 * Bus comparsion in boot priority order.
99 */
100 /* IDE first. */
101 if (enmBus == StorageBus_IDE)
102 return true;
103 if (rThat.enmBus == StorageBus_IDE)
104 return false;
105 /* SATA next */
106 if (enmBus == StorageBus_SATA)
107 return true;
108 if (rThat.enmBus == StorageBus_SATA)
109 return false;
110 /* SCSI next */
111 if (enmBus == StorageBus_SCSI)
112 return true;
113 if (rThat.enmBus == StorageBus_SCSI)
114 return false;
115 /* numerical */
116 return (int)enmBus < (int)rThat.enmBus;
117 }
118
119 bool operator==(const ControllerSlot &rThat) const
120 {
121 return enmBus == rThat.enmBus
122 && strControllerName == rThat.strControllerName
123 && iPort == rThat.iPort
124 && iDevice == rThat.iDevice;
125 }
126};
127
128/**
129 * Installation disk.
130 *
131 * Used when reconfiguring the VM.
132 */
133typedef struct UnattendedInstallationDisk
134{
135 StorageBus_T enmBusType; /**< @todo nobody is using this... */
136 Utf8Str strControllerName;
137 DeviceType_T enmDeviceType;
138 AccessMode_T enmAccessType;
139 LONG iPort;
140 LONG iDevice;
141 bool fMountOnly;
142 Utf8Str strImagePath;
143 bool fAuxiliary;
144
145 UnattendedInstallationDisk(StorageBus_T a_enmBusType, Utf8Str const &a_rBusName, DeviceType_T a_enmDeviceType,
146 AccessMode_T a_enmAccessType, LONG a_iPort, LONG a_iDevice, bool a_fMountOnly,
147 Utf8Str const &a_rImagePath, bool a_fAuxiliary)
148 : enmBusType(a_enmBusType), strControllerName(a_rBusName), enmDeviceType(a_enmDeviceType), enmAccessType(a_enmAccessType)
149 , iPort(a_iPort), iDevice(a_iDevice), fMountOnly(a_fMountOnly), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
150 {
151 Assert(strControllerName.length() > 0);
152 }
153
154 UnattendedInstallationDisk(std::list<ControllerSlot>::const_iterator const &itDvdSlot, Utf8Str const &a_rImagePath,
155 bool a_fAuxiliary)
156 : enmBusType(itDvdSlot->enmBus), strControllerName(itDvdSlot->strControllerName), enmDeviceType(DeviceType_DVD)
157 , enmAccessType(AccessMode_ReadOnly), iPort(itDvdSlot->iPort), iDevice(itDvdSlot->iDevice)
158 , fMountOnly(!itDvdSlot->fFree), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
159 {
160 Assert(strControllerName.length() > 0);
161 }
162} UnattendedInstallationDisk;
163
164
165/**
166 * OS/2 syslevel file header.
167 */
168#pragma pack(1)
169typedef struct OS2SYSLEVELHDR
170{
171 uint16_t uMinusOne; /**< 0x00: UINT16_MAX */
172 char achSignature[8]; /**< 0x02: "SYSLEVEL" */
173 uint8_t abReserved1[5]; /**< 0x0a: Usually zero. Ignore. */
174 uint16_t uSyslevelFileVer; /**< 0x0f: The syslevel file version: 1. */
175 uint8_t abReserved2[16]; /**< 0x11: Zero. Ignore. */
176 uint32_t offTable; /**< 0x21: Offset of the syslevel table. */
177} OS2SYSLEVELHDR;
178#pragma pack()
179AssertCompileSize(OS2SYSLEVELHDR, 0x25);
180
181/**
182 * OS/2 syslevel table entry.
183 */
184#pragma pack(1)
185typedef struct OS2SYSLEVELENTRY
186{
187 uint16_t id; /**< 0x00: ? */
188 uint8_t bEdition; /**< 0x02: The OS/2 edition: 0=standard, 1=extended, x=component defined */
189 uint8_t bVersion; /**< 0x03: 0x45 = 4.5 */
190 uint8_t bModify; /**< 0x04: Lower nibble is added to bVersion, so 0x45 0x02 => 4.52 */
191 uint8_t abReserved1[2]; /**< 0x05: Zero. Ignore. */
192 char achCsdLevel[8]; /**< 0x07: The current CSD level. */
193 char achCsdPrior[8]; /**< 0x0f: The prior CSD level. */
194 char szName[80]; /**< 0x5f: System/component name. */
195 char achId[9]; /**< 0x67: System/component ID. */
196 uint8_t bRefresh; /**< 0x70: Single digit refresh version, ignored if zero. */
197 char szType[9]; /**< 0x71: Some kind of type string. Optional */
198 uint8_t abReserved2[6]; /**< 0x7a: Zero. Ignore. */
199} OS2SYSLEVELENTRY;
200#pragma pack()
201AssertCompileSize(OS2SYSLEVELENTRY, 0x80);
202
203
204
205/**
206 * Concatenate image name and version strings and return.
207 *
208 * A possible output would be "Windows 10 Home (10.0.19041.330 / x64)".
209 *
210 * @returns Name string to use.
211 * @param r_strName String object that can be formatted into and returned.
212 */
213const Utf8Str &WIMImage::formatName(Utf8Str &r_strName) const
214{
215 /* We skip the mFlavor as it's typically part of the description already. */
216
217 if (mVersion.isEmpty() && mArch.isEmpty() && mDefaultLanguage.isEmpty() && mLanguages.size() == 0)
218 return mName;
219
220 r_strName = mName;
221 bool fFirst = true;
222 if (mVersion.isNotEmpty())
223 {
224 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mVersion.c_str());
225 fFirst = false;
226 }
227 if (mArch.isNotEmpty())
228 {
229 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mArch.c_str());
230 fFirst = false;
231 }
232 if (mDefaultLanguage.isNotEmpty())
233 {
234 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mDefaultLanguage.c_str());
235 fFirst = false;
236 }
237 else
238 for (size_t i = 0; i < mLanguages.size(); i++)
239 {
240 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mLanguages[i].c_str());
241 fFirst = false;
242 }
243 r_strName.append(")");
244 return r_strName;
245}
246
247
248//////////////////////////////////////////////////////////////////////////////////////////////////////
249/*
250*
251*
252* Implementation Unattended functions
253*
254*/
255//////////////////////////////////////////////////////////////////////////////////////////////////////
256
257Unattended::Unattended()
258 : mhThreadReconfigureVM(NIL_RTNATIVETHREAD), mfRtcUseUtc(false), mfGuestOs64Bit(false)
259 , mpInstaller(NULL), mpTimeZoneInfo(NULL), mfIsDefaultAuxiliaryBasePath(true), mfDoneDetectIsoOS(false)
260 , mfAvoidUpdatesOverNetwork(false)
261{ }
262
263Unattended::~Unattended()
264{
265 if (mpInstaller)
266 {
267 delete mpInstaller;
268 mpInstaller = NULL;
269 }
270}
271
272HRESULT Unattended::FinalConstruct()
273{
274 return BaseFinalConstruct();
275}
276
277void Unattended::FinalRelease()
278{
279 uninit();
280
281 BaseFinalRelease();
282}
283
284void Unattended::uninit()
285{
286 /* Enclose the state transition Ready->InUninit->NotReady */
287 AutoUninitSpan autoUninitSpan(this);
288 if (autoUninitSpan.uninitDone())
289 return;
290
291 unconst(mParent) = NULL;
292 mMachine.setNull();
293}
294
295/**
296 * Initializes the unattended object.
297 *
298 * @param aParent Pointer to the parent object.
299 */
300HRESULT Unattended::initUnattended(VirtualBox *aParent)
301{
302 LogFlowThisFunc(("aParent=%p\n", aParent));
303 ComAssertRet(aParent, E_INVALIDARG);
304
305 /* Enclose the state transition NotReady->InInit->Ready */
306 AutoInitSpan autoInitSpan(this);
307 AssertReturn(autoInitSpan.isOk(), E_FAIL);
308
309 unconst(mParent) = aParent;
310
311 /*
312 * Fill public attributes (IUnattended) with useful defaults.
313 */
314 try
315 {
316 mStrUser = "vboxuser";
317 mStrPassword = "changeme";
318 mfInstallGuestAdditions = false;
319 mfInstallTestExecService = false;
320 mfInstallUserPayload = false;
321 midxImage = 1;
322
323 HRESULT hrc = mParent->i_getSystemProperties()->i_getDefaultAdditionsISO(mStrAdditionsIsoPath);
324 ComAssertComRCRet(hrc, hrc);
325 }
326 catch (std::bad_alloc &)
327 {
328 return E_OUTOFMEMORY;
329 }
330
331 /*
332 * Confirm a successful initialization
333 */
334 autoInitSpan.setSucceeded();
335
336 return S_OK;
337}
338
339HRESULT Unattended::detectIsoOS()
340{
341 HRESULT hrc;
342 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
343
344/** @todo once UDF is implemented properly and we've tested this code a lot
345 * more, replace E_NOTIMPL with E_FAIL. */
346
347 /*
348 * Reset output state before we start
349 */
350 mStrDetectedOSTypeId.setNull();
351 mStrDetectedOSVersion.setNull();
352 mStrDetectedOSFlavor.setNull();
353 mDetectedOSLanguages.clear();
354 mStrDetectedOSHints.setNull();
355 mDetectedImages.clear();
356
357 /*
358 * Open the ISO.
359 */
360 RTVFSFILE hVfsFileIso;
361 int vrc = RTVfsFileOpenNormal(mStrIsoPath.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileIso);
362 if (RT_FAILURE(vrc))
363 return setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' (%Rrc)"), mStrIsoPath.c_str(), vrc);
364
365 RTERRINFOSTATIC ErrInfo;
366 RTVFS hVfsIso;
367 vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, RTErrInfoInitStatic(&ErrInfo));
368 if (RT_SUCCESS(vrc))
369 {
370 /*
371 * Try do the detection. Repeat for different file system variations (nojoliet, noudf).
372 */
373 hrc = i_innerDetectIsoOS(hVfsIso);
374
375 RTVfsRelease(hVfsIso);
376 if (hrc == S_FALSE) /** @todo Finish the linux and windows detection code. Only OS/2 returns S_OK right now. */
377 hrc = E_NOTIMPL;
378 }
379 else if (RTErrInfoIsSet(&ErrInfo.Core))
380 hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc) - %s"),
381 mStrIsoPath.c_str(), vrc, ErrInfo.Core.pszMsg);
382 else
383 hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc)"), mStrIsoPath.c_str(), vrc);
384 RTVfsFileRelease(hVfsFileIso);
385
386 /*
387 * Just fake up some windows installation media locale (for <UILanguage>).
388 * Note! The translation here isn't perfect. Feel free to send us a patch.
389 */
390 if (mDetectedOSLanguages.size() == 0)
391 {
392 char szTmp[16];
393 const char *pszFilename = RTPathFilename(mStrIsoPath.c_str());
394 if ( pszFilename
395 && RT_C_IS_ALPHA(pszFilename[0])
396 && RT_C_IS_ALPHA(pszFilename[1])
397 && (pszFilename[2] == '-' || pszFilename[2] == '_') )
398 {
399 szTmp[0] = (char)RT_C_TO_LOWER(pszFilename[0]);
400 szTmp[1] = (char)RT_C_TO_LOWER(pszFilename[1]);
401 szTmp[2] = '-';
402 if (szTmp[0] == 'e' && szTmp[1] == 'n')
403 strcpy(&szTmp[3], "US");
404 else if (szTmp[0] == 'a' && szTmp[1] == 'r')
405 strcpy(&szTmp[3], "SA");
406 else if (szTmp[0] == 'd' && szTmp[1] == 'a')
407 strcpy(&szTmp[3], "DK");
408 else if (szTmp[0] == 'e' && szTmp[1] == 't')
409 strcpy(&szTmp[3], "EE");
410 else if (szTmp[0] == 'e' && szTmp[1] == 'l')
411 strcpy(&szTmp[3], "GR");
412 else if (szTmp[0] == 'h' && szTmp[1] == 'e')
413 strcpy(&szTmp[3], "IL");
414 else if (szTmp[0] == 'j' && szTmp[1] == 'a')
415 strcpy(&szTmp[3], "JP");
416 else if (szTmp[0] == 's' && szTmp[1] == 'v')
417 strcpy(&szTmp[3], "SE");
418 else if (szTmp[0] == 'u' && szTmp[1] == 'k')
419 strcpy(&szTmp[3], "UA");
420 else if (szTmp[0] == 'c' && szTmp[1] == 's')
421 strcpy(szTmp, "cs-CZ");
422 else if (szTmp[0] == 'n' && szTmp[1] == 'o')
423 strcpy(szTmp, "nb-NO");
424 else if (szTmp[0] == 'p' && szTmp[1] == 'p')
425 strcpy(szTmp, "pt-PT");
426 else if (szTmp[0] == 'p' && szTmp[1] == 't')
427 strcpy(szTmp, "pt-BR");
428 else if (szTmp[0] == 'c' && szTmp[1] == 'n')
429 strcpy(szTmp, "zh-CN");
430 else if (szTmp[0] == 'h' && szTmp[1] == 'k')
431 strcpy(szTmp, "zh-HK");
432 else if (szTmp[0] == 't' && szTmp[1] == 'w')
433 strcpy(szTmp, "zh-TW");
434 else if (szTmp[0] == 's' && szTmp[1] == 'r')
435 strcpy(szTmp, "sr-Latn-CS"); /* hmm */
436 else
437 {
438 szTmp[3] = (char)RT_C_TO_UPPER(pszFilename[0]);
439 szTmp[4] = (char)RT_C_TO_UPPER(pszFilename[1]);
440 szTmp[5] = '\0';
441 }
442 }
443 else
444 strcpy(szTmp, "en-US");
445 try
446 {
447 mDetectedOSLanguages.append(szTmp);
448 }
449 catch (std::bad_alloc &)
450 {
451 return E_OUTOFMEMORY;
452 }
453 }
454
455 /** @todo implement actual detection logic. */
456 return hrc;
457}
458
459HRESULT Unattended::i_innerDetectIsoOS(RTVFS hVfsIso)
460{
461 DETECTBUFFER uBuf;
462 mEnmOsType = VBOXOSTYPE_Unknown;
463 HRESULT hrc = i_innerDetectIsoOSWindows(hVfsIso, &uBuf);
464 if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
465 hrc = i_innerDetectIsoOSLinux(hVfsIso, &uBuf);
466 if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
467 hrc = i_innerDetectIsoOSOs2(hVfsIso, &uBuf);
468 if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
469 hrc = i_innerDetectIsoOSFreeBsd(hVfsIso, &uBuf);
470 if (mEnmOsType != VBOXOSTYPE_Unknown)
471 {
472 try { mStrDetectedOSTypeId = Global::OSTypeId(mEnmOsType); }
473 catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
474 }
475 return hrc;
476}
477
478/**
479 * Tries to parse a LANGUAGES element, with the following structure.
480 * @verbatim
481 * <LANGUAGES>
482 * <LANGUAGE>
483 * en-US
484 * </LANGUAGE>
485 * <DEFAULT>
486 * en-US
487 * </DEFAULT>
488 * </LANGUAGES>
489 * @endverbatim
490 *
491 * Will set mLanguages and mDefaultLanguage success.
492 *
493 * @param pElmLanguages Points to the LANGUAGES XML node.
494 * @param rImage Out reference to an WIMImage instance.
495 */
496static void parseLangaguesElement(const xml::ElementNode *pElmLanguages, WIMImage &rImage)
497{
498 /*
499 * The languages.
500 */
501 ElementNodesList children;
502 int cChildren = pElmLanguages->getChildElements(children, "LANGUAGE");
503 if (cChildren == 0)
504 cChildren = pElmLanguages->getChildElements(children, "language");
505 if (cChildren == 0)
506 cChildren = pElmLanguages->getChildElements(children, "Language");
507 for (ElementNodesList::iterator iterator = children.begin(); iterator != children.end(); ++iterator)
508 {
509 const ElementNode * const pElmLanguage = *(iterator);
510 if (pElmLanguage)
511 {
512 const char *pszValue = pElmLanguage->getValue();
513 if (pszValue && *pszValue != '\0')
514 rImage.mLanguages.append(pszValue);
515 }
516 }
517
518 /*
519 * Default language.
520 */
521 const xml::ElementNode *pElmDefault;
522 if ( (pElmDefault = pElmLanguages->findChildElement("DEFAULT")) != NULL
523 || (pElmDefault = pElmLanguages->findChildElement("default")) != NULL
524 || (pElmDefault = pElmLanguages->findChildElement("Default")) != NULL)
525 rImage.mDefaultLanguage = pElmDefault->getValue();
526}
527
528
529/**
530 * Tries to set the image architecture.
531 *
532 * Input examples (x86 and amd64 respectively):
533 * @verbatim
534 * <ARCH>0</ARCH>
535 * <ARCH>9</ARCH>
536 * @endverbatim
537 *
538 * Will set mArch and update mOSType on success.
539 *
540 * @param pElmArch Points to the ARCH XML node.
541 * @param rImage Out reference to an WIMImage instance.
542 */
543static void parseArchElement(const xml::ElementNode *pElmArch, WIMImage &rImage)
544{
545 /* These are from winnt.h */
546 static struct { const char *pszArch; VBOXOSTYPE enmArch; } s_aArches[] =
547 {
548 /* PROCESSOR_ARCHITECTURE_INTEL / [0] = */ { "x86", VBOXOSTYPE_x86 },
549 /* PROCESSOR_ARCHITECTURE_MIPS / [1] = */ { "mips", VBOXOSTYPE_UnknownArch },
550 /* PROCESSOR_ARCHITECTURE_ALPHA / [2] = */ { "alpha", VBOXOSTYPE_UnknownArch },
551 /* PROCESSOR_ARCHITECTURE_PPC / [3] = */ { "ppc", VBOXOSTYPE_UnknownArch },
552 /* PROCESSOR_ARCHITECTURE_SHX / [4] = */ { "shx", VBOXOSTYPE_UnknownArch },
553 /* PROCESSOR_ARCHITECTURE_ARM / [5] = */ { "arm32", VBOXOSTYPE_arm32 },
554 /* PROCESSOR_ARCHITECTURE_IA64 / [6] = */ { "ia64", VBOXOSTYPE_UnknownArch },
555 /* PROCESSOR_ARCHITECTURE_ALPHA64 / [7] = */ { "alpha64", VBOXOSTYPE_UnknownArch },
556 /* PROCESSOR_ARCHITECTURE_MSIL / [8] = */ { "msil", VBOXOSTYPE_UnknownArch },
557 /* PROCESSOR_ARCHITECTURE_AMD64 / [9] = */ { "x64", VBOXOSTYPE_x64 },
558 /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 / [10] = */ { "x86-on-x64", VBOXOSTYPE_UnknownArch },
559 /* PROCESSOR_ARCHITECTURE_NEUTRAL / [11] = */ { "noarch", VBOXOSTYPE_UnknownArch },
560 /* PROCESSOR_ARCHITECTURE_ARM64 / [12] = */ { "arm64", VBOXOSTYPE_arm64 },
561 /* PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64/ [13] = */ { "arm32-on-arm64", VBOXOSTYPE_UnknownArch },
562 /* PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 / [14] = */ { "x86-on-arm32", VBOXOSTYPE_UnknownArch },
563 };
564 const char *pszArch = pElmArch->getValue();
565 if (pszArch && *pszArch)
566 {
567 uint32_t uArch;
568 int vrc = RTStrToUInt32Ex(pszArch, NULL, 10 /*uBase*/, &uArch);
569 if ( RT_SUCCESS(vrc)
570 && vrc != VWRN_NUMBER_TOO_BIG
571 && vrc != VWRN_NEGATIVE_UNSIGNED
572 && uArch < RT_ELEMENTS(s_aArches))
573 {
574 rImage.mArch = s_aArches[uArch].pszArch;
575 rImage.mOSType = (VBOXOSTYPE)(s_aArches[uArch].enmArch | (rImage.mOSType & VBOXOSTYPE_OsMask));
576 }
577 else
578 LogRel(("Unattended: bogus ARCH element value: '%s'\n", pszArch));
579 }
580}
581
582/**
583 * Parses XML Node assuming a structure as follows
584 * @verbatim
585 * <VERSION>
586 * <MAJOR>10</MAJOR>
587 * <MINOR>0</MINOR>
588 * <BUILD>19041</BUILD>
589 * <SPBUILD>1</SPBUILD>
590 * </VERSION>
591 * @endverbatim
592 *
593 * Will update mOSType, mEnmOsType as well as setting mVersion on success.
594 *
595 * @param pNode Points to the vesion XML node,
596 * @param image Out reference to an WIMImage instance.
597 */
598static void parseVersionElement(const xml::ElementNode *pNode, WIMImage &image)
599{
600 /* Major part: */
601 const xml::ElementNode *pElmMajor;
602 if ( (pElmMajor = pNode->findChildElement("MAJOR")) != NULL
603 || (pElmMajor = pNode->findChildElement("major")) != NULL
604 || (pElmMajor = pNode->findChildElement("Major")) != NULL)
605 if (pElmMajor)
606 {
607 const char * const pszMajor = pElmMajor->getValue();
608 if (pszMajor && *pszMajor)
609 {
610 /* Minor part: */
611 const ElementNode *pElmMinor;
612 if ( (pElmMinor = pNode->findChildElement("MINOR")) != NULL
613 || (pElmMinor = pNode->findChildElement("minor")) != NULL
614 || (pElmMinor = pNode->findChildElement("Minor")) != NULL)
615 {
616 const char * const pszMinor = pElmMinor->getValue();
617 if (pszMinor && *pszMinor)
618 {
619 /* Build: */
620 const ElementNode *pElmBuild;
621 if ( (pElmBuild = pNode->findChildElement("BUILD")) != NULL
622 || (pElmBuild = pNode->findChildElement("build")) != NULL
623 || (pElmBuild = pNode->findChildElement("Build")) != NULL)
624 {
625 const char * const pszBuild = pElmBuild->getValue();
626 if (pszBuild && *pszBuild)
627 {
628 /* SPBuild: */
629 const ElementNode *pElmSpBuild;
630 if ( ( (pElmSpBuild = pNode->findChildElement("SPBUILD")) != NULL
631 || (pElmSpBuild = pNode->findChildElement("spbuild")) != NULL
632 || (pElmSpBuild = pNode->findChildElement("Spbuild")) != NULL
633 || (pElmSpBuild = pNode->findChildElement("SpBuild")) != NULL)
634 && pElmSpBuild->getValue()
635 && *pElmSpBuild->getValue() != '\0')
636 image.mVersion.printf("%s.%s.%s.%s", pszMajor, pszMinor, pszBuild, pElmSpBuild->getValue());
637 else
638 image.mVersion.printf("%s.%s.%s", pszMajor, pszMinor, pszBuild);
639
640 /*
641 * Convert that to a version windows OS ID (newest first!).
642 */
643 image.mEnmOsType = VBOXOSTYPE_Unknown;
644 if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.22000.0") >= 0)
645 image.mEnmOsType = VBOXOSTYPE_Win11_x64;
646 else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0)
647 image.mEnmOsType = VBOXOSTYPE_Win10;
648 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.3") >= 0)
649 image.mEnmOsType = VBOXOSTYPE_Win81;
650 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0)
651 image.mEnmOsType = VBOXOSTYPE_Win8;
652 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.1") >= 0)
653 image.mEnmOsType = VBOXOSTYPE_Win7;
654 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0)
655 image.mEnmOsType = VBOXOSTYPE_WinVista;
656 if (image.mFlavor.contains("server", Utf8Str::CaseInsensitive))
657 {
658 if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.20348") >= 0)
659 image.mEnmOsType = VBOXOSTYPE_Win2k22_x64;
660 else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.17763") >= 0)
661 image.mEnmOsType = VBOXOSTYPE_Win2k19_x64;
662 else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0)
663 image.mEnmOsType = VBOXOSTYPE_Win2k16_x64;
664 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0)
665 image.mEnmOsType = VBOXOSTYPE_Win2k12_x64;
666 else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0)
667 image.mEnmOsType = VBOXOSTYPE_Win2k8;
668 }
669 if (image.mEnmOsType != VBOXOSTYPE_Unknown)
670 image.mOSType = (VBOXOSTYPE)( (image.mOSType & VBOXOSTYPE_ArchitectureMask)
671 | (image.mEnmOsType & 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 */
1885 vrc = RTVfsFileOpen(hVfsIso, ".disk/info", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1886 if (RT_SUCCESS(vrc))
1887 {
1888 size_t cchIgn;
1889 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1890 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1891
1892 pBuf->sz[sizeof(*pBuf) - 1] = '\0';
1893 RTVfsFileRelease(hVfsFile);
1894
1895 char *psz = pBuf->sz;
1896 char *pszDiskName = psz;
1897 char *pszArch = NULL;
1898
1899 /* Only care about the first line of the file even if it is multi line and assume disk name ended with ' - '.*/
1900 psz = RTStrStr(pBuf->sz, " - ");
1901 if (psz && memchr(pBuf->sz, '\n', (size_t)(psz - pBuf->sz)) == NULL)
1902 {
1903 *psz = '\0';
1904 psz += 3;
1905 if (*psz)
1906 pszArch = psz;
1907 }
1908
1909 /* Some Debian Live ISO's have info file content as follows:
1910 * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
1911 * thus pszArch stays empty. Try Volume Id (label) if we get lucky and get architecture from that. */
1912 if (!pszArch)
1913 {
1914 char szVolumeId[128];
1915 vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
1916 if (RT_SUCCESS(vrc))
1917 {
1918 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1919 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", szVolumeId));
1920 }
1921 else
1922 LogRel(("Unattended: .disk/info No Volume Label found\n"));
1923 }
1924 else
1925 {
1926 if (!detectLinuxArchII(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1927 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", pszArch));
1928 }
1929
1930 if (pszDiskName)
1931 {
1932 const char *pszVersion = NULL;
1933 if (detectLinuxDistroNameII(pszDiskName, &mEnmOsType, &pszVersion))
1934 {
1935 LogRelFlow(("Unattended: .disk/info: version=%s\n", pszVersion));
1936 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1937 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1938
1939 size_t cchVersionPosition = 0;
1940 if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition))
1941 {
1942 try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); }
1943 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1944 }
1945 }
1946 else
1947 LogRel(("Unattended: .disk/info: Unknown: diskname='%s'\n", pszDiskName));
1948 }
1949
1950 if (mEnmOsType == VBOXOSTYPE_Unknown)
1951 LogRel(("Unattended: .disk/info: Did not find DISKNAME or/and ARCH. :-/\n"));
1952 else
1953 return S_FALSE;
1954 }
1955
1956 /*
1957 * Fedora live iso should be recognizable from the primary volume ID (the
1958 * joliet one is usually truncated). We set fAlternative = true here to
1959 * get the primary volume ID.
1960 */
1961 char szVolumeId[128];
1962 vrc = RTVfsQueryLabel(hVfsIso, true /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
1963 if (RT_SUCCESS(vrc) && RTStrStartsWith(szVolumeId, "Fedora-"))
1964 return i_innerDetectIsoOSLinuxFedora(hVfsIso, pBuf, &szVolumeId[sizeof("Fedora-") - 1]);
1965 return S_FALSE;
1966}
1967
1968
1969/**
1970 * Continues working a Fedora ISO image after the caller found a "Fedora-*"
1971 * volume ID.
1972 *
1973 * Sample Volume IDs:
1974 * - Fedora-WS-Live-34-1-2 (joliet: Fedora-WS-Live-3)
1975 * - Fedora-S-dvd-x86_64-34 (joliet: Fedora-S-dvd-x86)
1976 * - Fedora-WS-dvd-i386-25 (joliet: Fedora-WS-dvd-i3)
1977 */
1978HRESULT Unattended::i_innerDetectIsoOSLinuxFedora(RTVFS hVfsIso, DETECTBUFFER *pBuf, char *pszVolId)
1979{
1980 char * const pszFlavor = pszVolId;
1981 char * psz = pszVolId;
1982
1983 /* The volume id may or may not include an arch, component.
1984 We ASSUME that it includes a numeric part with the version, or at least
1985 part of it. */
1986 char *pszVersion = NULL;
1987 char *pszArch = NULL;
1988 if (detectLinuxArchII(psz, &mEnmOsType, VBOXOSTYPE_FedoraCore, &pszArch, &pszVersion))
1989 {
1990 while (*pszVersion == '-')
1991 pszVersion++;
1992 *pszArch = '\0';
1993 }
1994 else
1995 {
1996 mEnmOsType = (VBOXOSTYPE)(VBOXOSTYPE_FedoraCore | VBOXOSTYPE_UnknownArch);
1997
1998 char ch;
1999 while ((ch = *psz) != '\0' && (!RT_C_IS_DIGIT(ch) || !RT_C_IS_PUNCT(psz[-1])))
2000 psz++;
2001 if (ch != '\0')
2002 pszVersion = psz;
2003 }
2004
2005 /*
2006 * Replace '-' with '.' in the version part and use it as the version.
2007 */
2008 if (pszVersion)
2009 {
2010 psz = pszVersion;
2011 while ((psz = strchr(psz, '-')) != NULL)
2012 *psz++ = '.';
2013 try { mStrDetectedOSVersion = RTStrStrip(pszVersion); }
2014 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2015
2016 *pszVersion = '\0'; /* don't include in flavor */
2017 }
2018
2019 /*
2020 * Split up the pre-arch/version bits into words and use them as the flavor.
2021 */
2022 psz = pszFlavor;
2023 while ((psz = strchr(psz, '-')) != NULL)
2024 *psz++ = ' ';
2025 try { mStrDetectedOSFlavor = RTStrStrip(pszFlavor); }
2026 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2027
2028 /*
2029 * If we don't have an architecture, we look at the vmlinuz file as the x86
2030 * and AMD64 versions starts with a MZ+PE header giving the architecture.
2031 */
2032 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2033 {
2034 static const char * const s_apszVmLinuz[] = { "images/pxeboot/vmlinuz", "isolinux/vmlinuz" };
2035 for (size_t i = 0; i < RT_ELEMENTS(s_apszVmLinuz); i++)
2036 {
2037 RTVFSFILE hVfsFileLinuz = NIL_RTVFSFILE;
2038 int vrc = RTVfsFileOpen(hVfsIso, s_apszVmLinuz[i], RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
2039 &hVfsFileLinuz);
2040 if (RT_SUCCESS(vrc))
2041 {
2042 /* DOS signature: */
2043 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pBuf->ab[0];
2044 AssertCompile(sizeof(*pBuf) > sizeof(*pDosHdr));
2045 vrc = RTVfsFileReadAt(hVfsFileLinuz, 0, pDosHdr, sizeof(*pDosHdr), NULL);
2046 if (RT_SUCCESS(vrc) && pDosHdr->e_magic == IMAGE_DOS_SIGNATURE)
2047 {
2048 /* NT signature - only need magic + file header, so use the 64 version for better debugging: */
2049 PIMAGE_NT_HEADERS64 pNtHdrs = (PIMAGE_NT_HEADERS64)&pBuf->ab[0];
2050 vrc = RTVfsFileReadAt(hVfsFileLinuz, pDosHdr->e_lfanew, pNtHdrs, sizeof(*pNtHdrs), NULL);
2051 AssertCompile(sizeof(*pBuf) > sizeof(*pNtHdrs));
2052 if (RT_SUCCESS(vrc) && pNtHdrs->Signature == IMAGE_NT_SIGNATURE)
2053 {
2054 if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
2055 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x86);
2056 else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
2057 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x64);
2058 else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM64)
2059 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_arm64);
2060 else
2061 AssertFailed();
2062 }
2063 }
2064
2065 RTVfsFileRelease(hVfsFileLinuz);
2066 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch)
2067 break;
2068 }
2069 }
2070 }
2071
2072 /*
2073 * If that failed, look for other files that gives away the arch.
2074 */
2075 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2076 {
2077 static struct { const char *pszFile; VBOXOSTYPE fArch; } const s_aArchSpecificFiles[] =
2078 {
2079 { "EFI/BOOT/grubaa64.efi", VBOXOSTYPE_arm64 },
2080 { "EFI/BOOT/BOOTAA64.EFI", VBOXOSTYPE_arm64 },
2081 };
2082 PRTFSOBJINFO pObjInfo = (PRTFSOBJINFO)&pBuf->ab[0];
2083 AssertCompile(sizeof(*pBuf) > sizeof(*pObjInfo));
2084 for (size_t i = 0; i < RT_ELEMENTS(s_aArchSpecificFiles); i++)
2085 {
2086 int vrc = RTVfsQueryPathInfo(hVfsIso, s_aArchSpecificFiles[i].pszFile, pObjInfo,
2087 RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2088 if (RT_SUCCESS(vrc) && RTFS_IS_FILE(pObjInfo->Attr.fMode))
2089 {
2090 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | s_aArchSpecificFiles[i].fArch);
2091 break;
2092 }
2093 }
2094 }
2095
2096 /*
2097 * If we like, we could parse grub.conf to look for fullly spelled out
2098 * flavor, though the menu items typically only contains the major version
2099 * number, so little else to add, really.
2100 */
2101
2102 return (mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch ? S_OK : S_FALSE;
2103}
2104
2105
2106/**
2107 * Detect OS/2 installation ISOs.
2108 *
2109 * Mainly aiming at ACP2/MCP2 as that's what we currently use in our testing.
2110 *
2111 * @returns COM status code.
2112 * @retval S_OK if detected
2113 * @retval S_FALSE if not fully detected.
2114 *
2115 * @param hVfsIso The ISO file system.
2116 * @param pBuf Read buffer.
2117 */
2118HRESULT Unattended::i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2119{
2120 /*
2121 * The OS2SE20.SRC contains the location of the tree with the diskette
2122 * images, typically "\OS2IMAGE".
2123 */
2124 RTVFSFILE hVfsFile;
2125 int vrc = RTVfsFileOpen(hVfsIso, "OS2SE20.SRC", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2126 if (RT_SUCCESS(vrc))
2127 {
2128 size_t cbRead = 0;
2129 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2130 RTVfsFileRelease(hVfsFile);
2131 if (RT_SUCCESS(vrc))
2132 {
2133 pBuf->sz[cbRead] = '\0';
2134 RTStrStrip(pBuf->sz);
2135 vrc = RTStrValidateEncoding(pBuf->sz);
2136 if (RT_SUCCESS(vrc))
2137 LogRelFlow(("Unattended: OS2SE20.SRC=%s\n", pBuf->sz));
2138 else
2139 LogRel(("Unattended: OS2SE20.SRC invalid encoding: %Rrc, %.*Rhxs\n", vrc, cbRead, pBuf->sz));
2140 }
2141 else
2142 LogRel(("Unattended: Error reading OS2SE20.SRC: %\n", vrc));
2143 }
2144 /*
2145 * ArcaOS has dropped the file, assume it's \OS2IMAGE and see if it's there.
2146 */
2147 else if (vrc == VERR_FILE_NOT_FOUND)
2148 RTStrCopy(pBuf->sz, sizeof(pBuf->sz), "\\OS2IMAGE");
2149 else
2150 return S_FALSE;
2151
2152 /*
2153 * Check that the directory directory exists and has a DISK_0 under it
2154 * with an OS2LDR on it.
2155 */
2156 size_t const cchOs2Image = strlen(pBuf->sz);
2157 vrc = RTPathAppend(pBuf->sz, sizeof(pBuf->sz), "DISK_0/OS2LDR");
2158 RTFSOBJINFO ObjInfo = {0};
2159 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2160 if (vrc == VERR_FILE_NOT_FOUND)
2161 {
2162 RTStrCat(pBuf->sz, sizeof(pBuf->sz), "."); /* eCS 2.0 image includes the dot from the 8.3 name. */
2163 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2164 }
2165 if ( RT_FAILURE(vrc)
2166 || !RTFS_IS_FILE(ObjInfo.Attr.fMode))
2167 {
2168 LogRel(("Unattended: RTVfsQueryPathInfo(, '%s' (from OS2SE20.SRC),) -> %Rrc, fMode=%#x\n",
2169 pBuf->sz, vrc, ObjInfo.Attr.fMode));
2170 return S_FALSE;
2171 }
2172
2173 /*
2174 * So, it's some kind of OS/2 2.x or later ISO alright.
2175 */
2176 mEnmOsType = VBOXOSTYPE_OS2;
2177 mStrDetectedOSHints.printf("OS2SE20.SRC=%.*s", cchOs2Image, pBuf->sz);
2178
2179 /*
2180 * ArcaOS ISOs seems to have a AOSBOOT dir on them.
2181 * This contains a ARCANOAE.FLG file with content we can use for the version:
2182 * ArcaOS 5.0.7 EN
2183 * Built 2021-12-07 18:34:34
2184 * We drop the "ArcaOS" bit, as it's covered by mEnmOsType. Then we pull up
2185 * the second line.
2186 *
2187 * Note! Yet to find a way to do unattended install of ArcaOS, as it comes
2188 * with no CD-boot floppy images, only simple .PF archive files for
2189 * unpacking onto the ram disk or whatever. Modifying these is
2190 * possible (ibsen's aPLib v0.36 compression with some simple custom
2191 * headers), but it would probably be a royal pain. Could perhaps
2192 * cook something from OS2IMAGE\DISK_0 thru 3...
2193 */
2194 vrc = RTVfsQueryPathInfo(hVfsIso, "AOSBOOT", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2195 if ( RT_SUCCESS(vrc)
2196 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2197 {
2198 mEnmOsType = VBOXOSTYPE_ArcaOS;
2199
2200 /* Read the version file: */
2201 vrc = RTVfsFileOpen(hVfsIso, "SYS/ARCANOAE.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2202 if (RT_SUCCESS(vrc))
2203 {
2204 size_t cbRead = 0;
2205 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2206 RTVfsFileRelease(hVfsFile);
2207 pBuf->sz[cbRead] = '\0';
2208 if (RT_SUCCESS(vrc))
2209 {
2210 /* Strip the OS name: */
2211 char *pszVersion = RTStrStrip(pBuf->sz);
2212 static char s_szArcaOS[] = "ArcaOS";
2213 if (RTStrStartsWith(pszVersion, s_szArcaOS))
2214 pszVersion = RTStrStripL(pszVersion + sizeof(s_szArcaOS) - 1);
2215
2216 /* Pull up the 2nd line if it, condensing the \r\n into a single space. */
2217 char *pszNewLine = strchr(pszVersion, '\n');
2218 if (pszNewLine && RTStrStartsWith(pszNewLine + 1, "Built 20"))
2219 {
2220 size_t offRemove = 0;
2221 while (RT_C_IS_SPACE(pszNewLine[-1 - (ssize_t)offRemove]))
2222 offRemove++;
2223 if (offRemove > 0)
2224 {
2225 pszNewLine -= offRemove;
2226 memmove(pszNewLine, pszNewLine + offRemove, strlen(pszNewLine + offRemove) - 1);
2227 }
2228 *pszNewLine = ' ';
2229 }
2230
2231 /* Drop any additional lines: */
2232 pszNewLine = strchr(pszVersion, '\n');
2233 if (pszNewLine)
2234 *pszNewLine = '\0';
2235 RTStrStripR(pszVersion);
2236
2237 /* Done (hope it makes some sense). */
2238 mStrDetectedOSVersion = pszVersion;
2239 }
2240 else
2241 LogRel(("Unattended: failed to read AOSBOOT/ARCANOAE.FLG: %Rrc\n", vrc));
2242 }
2243 else
2244 LogRel(("Unattended: failed to open AOSBOOT/ARCANOAE.FLG for reading: %Rrc\n", vrc));
2245 }
2246 /*
2247 * Similarly, eCS has an ECS directory and it typically contains a
2248 * ECS_INST.FLG file with the version info. Content differs a little:
2249 * eComStation 2.0 EN_US Thu May 13 10:27:54 pm 2010
2250 * Built on ECS60441318
2251 * Here we drop the "eComStation" bit and leave the 2nd line as it.
2252 *
2253 * Note! At least 2.0 has a DISKIMGS folder with what looks like boot
2254 * disks, so we could probably get something going here without
2255 * needing to write an OS2 boot sector...
2256 */
2257 else
2258 {
2259 vrc = RTVfsQueryPathInfo(hVfsIso, "ECS", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2260 if ( RT_SUCCESS(vrc)
2261 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2262 {
2263 mEnmOsType = VBOXOSTYPE_ECS;
2264
2265 /* Read the version file: */
2266 vrc = RTVfsFileOpen(hVfsIso, "ECS/ECS_INST.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2267 if (RT_SUCCESS(vrc))
2268 {
2269 size_t cbRead = 0;
2270 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2271 RTVfsFileRelease(hVfsFile);
2272 pBuf->sz[cbRead] = '\0';
2273 if (RT_SUCCESS(vrc))
2274 {
2275 /* Strip the OS name: */
2276 char *pszVersion = RTStrStrip(pBuf->sz);
2277 static char s_szECS[] = "eComStation";
2278 if (RTStrStartsWith(pszVersion, s_szECS))
2279 pszVersion = RTStrStripL(pszVersion + sizeof(s_szECS) - 1);
2280
2281 /* Drop any additional lines: */
2282 char *pszNewLine = strchr(pszVersion, '\n');
2283 if (pszNewLine)
2284 *pszNewLine = '\0';
2285 RTStrStripR(pszVersion);
2286
2287 /* Done (hope it makes some sense). */
2288 mStrDetectedOSVersion = pszVersion;
2289 }
2290 else
2291 LogRel(("Unattended: failed to read ECS/ECS_INST.FLG: %Rrc\n", vrc));
2292 }
2293 else
2294 LogRel(("Unattended: failed to open ECS/ECS_INST.FLG for reading: %Rrc\n", vrc));
2295 }
2296 else
2297 {
2298 /*
2299 * Official IBM OS/2 builds doesn't have any .FLG file on them,
2300 * so need to pry the information out in some other way. Best way
2301 * is to read the SYSLEVEL.OS2 file, which is typically on disk #2,
2302 * though on earlier versions (warp3) it was disk #1.
2303 */
2304 vrc = RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1,
2305 "/DISK_2/SYSLEVEL.OS2");
2306 if (RT_SUCCESS(vrc))
2307 {
2308 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2309 if (vrc == VERR_FILE_NOT_FOUND)
2310 {
2311 RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, "/DISK_1/SYSLEVEL.OS2");
2312 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2313 }
2314 if (RT_SUCCESS(vrc))
2315 {
2316 RT_ZERO(pBuf->ab);
2317 size_t cbRead = 0;
2318 vrc = RTVfsFileRead(hVfsFile, pBuf->ab, sizeof(pBuf->ab), &cbRead);
2319 RTVfsFileRelease(hVfsFile);
2320 if (RT_SUCCESS(vrc))
2321 {
2322 /* Check the header. */
2323 OS2SYSLEVELHDR const *pHdr = (OS2SYSLEVELHDR const *)&pBuf->ab[0];
2324 if ( pHdr->uMinusOne == UINT16_MAX
2325 && pHdr->uSyslevelFileVer == 1
2326 && memcmp(pHdr->achSignature, RT_STR_TUPLE("SYSLEVEL")) == 0
2327 && pHdr->offTable < cbRead
2328 && pHdr->offTable + sizeof(OS2SYSLEVELENTRY) <= cbRead)
2329 {
2330 OS2SYSLEVELENTRY *pEntry = (OS2SYSLEVELENTRY *)&pBuf->ab[pHdr->offTable];
2331 if ( RT_SUCCESS(RTStrValidateEncodingEx(pEntry->szName, sizeof(pEntry->szName),
2332 RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED))
2333 && RT_SUCCESS(RTStrValidateEncodingEx(pEntry->achCsdLevel, sizeof(pEntry->achCsdLevel), 0))
2334 && pEntry->bVersion != 0
2335 && ((pEntry->bVersion >> 4) & 0xf) < 10
2336 && (pEntry->bVersion & 0xf) < 10
2337 && pEntry->bModify < 10
2338 && pEntry->bRefresh < 10)
2339 {
2340 /* Flavor: */
2341 char *pszName = RTStrStrip(pEntry->szName);
2342 if (pszName)
2343 mStrDetectedOSFlavor = pszName;
2344
2345 /* Version: */
2346 if (pEntry->bRefresh != 0)
2347 mStrDetectedOSVersion.printf("%d.%d%d.%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2348 pEntry->bModify, pEntry->bRefresh);
2349 else
2350 mStrDetectedOSVersion.printf("%d.%d%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2351 pEntry->bModify);
2352 pEntry->achCsdLevel[sizeof(pEntry->achCsdLevel) - 1] = '\0';
2353 char *pszCsd = RTStrStrip(pEntry->achCsdLevel);
2354 if (*pszCsd != '\0')
2355 {
2356 mStrDetectedOSVersion.append(' ');
2357 mStrDetectedOSVersion.append(pszCsd);
2358 }
2359 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.50") >= 0)
2360 mEnmOsType = VBOXOSTYPE_OS2Warp45;
2361 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.00") >= 0)
2362 mEnmOsType = VBOXOSTYPE_OS2Warp4;
2363 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "3.00") >= 0)
2364 mEnmOsType = VBOXOSTYPE_OS2Warp3;
2365 }
2366 else
2367 LogRel(("Unattended: bogus SYSLEVEL.OS2 file entry: %.128Rhxd\n", pEntry));
2368 }
2369 else
2370 LogRel(("Unattended: bogus SYSLEVEL.OS2 file header: uMinusOne=%#x uSyslevelFileVer=%#x achSignature=%.8Rhxs offTable=%#x vs cbRead=%#zx\n",
2371 pHdr->uMinusOne, pHdr->uSyslevelFileVer, pHdr->achSignature, pHdr->offTable, cbRead));
2372 }
2373 else
2374 LogRel(("Unattended: failed to read SYSLEVEL.OS2: %Rrc\n", vrc));
2375 }
2376 else
2377 LogRel(("Unattended: failed to open '%s' for reading: %Rrc\n", pBuf->sz, vrc));
2378 }
2379 }
2380 }
2381
2382 /** @todo language detection? */
2383
2384 /*
2385 * Only tested ACP2, so only return S_OK for it.
2386 */
2387 if ( mEnmOsType == VBOXOSTYPE_OS2Warp45
2388 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.52") >= 0
2389 && mStrDetectedOSFlavor.contains("Server", RTCString::CaseInsensitive))
2390 return S_OK;
2391
2392 return S_FALSE;
2393}
2394
2395
2396/**
2397 * Detect FreeBSD distro ISOs.
2398 *
2399 * @returns COM status code.
2400 * @retval S_OK if detected
2401 * @retval S_FALSE if not fully detected.
2402 *
2403 * @param hVfsIso The ISO file system.
2404 * @param pBuf Read buffer.
2405 */
2406HRESULT Unattended::i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2407{
2408 RT_NOREF(pBuf);
2409
2410 /*
2411 * FreeBSD since 10.0 has a .profile file in the root which can be used to determine that this is FreeBSD
2412 * along with the version.
2413 */
2414
2415 RTVFSFILE hVfsFile;
2416 HRESULT hrc = S_FALSE;
2417 int vrc = RTVfsFileOpen(hVfsIso, ".profile", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2418 if (RT_SUCCESS(vrc))
2419 {
2420 static const uint8_t s_abFreeBsdHdr[] = "# $FreeBSD: releng/";
2421 char abRead[32];
2422
2423 vrc = RTVfsFileRead(hVfsFile, &abRead[0], sizeof(abRead), NULL /*pcbRead*/);
2424 if ( RT_SUCCESS(vrc)
2425 && !memcmp(&abRead[0], &s_abFreeBsdHdr[0], sizeof(s_abFreeBsdHdr) - 1)) /* Skip terminator */
2426 {
2427 abRead[sizeof(abRead) - 1] = '\0';
2428
2429 /* Detect the architecture using the volume label. */
2430 char szVolumeId[128];
2431 size_t cchVolumeId;
2432 vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, 128, &cchVolumeId);
2433 if (RT_SUCCESS(vrc))
2434 {
2435 /* Can re-use the Linux code here. */
2436 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_FreeBSD))
2437 LogRel(("Unattended/FBSD: Unknown: arch='%s'\n", szVolumeId));
2438
2439 /* Detect the version from the string coming after the needle in .profile. */
2440 AssertCompile(sizeof(s_abFreeBsdHdr) - 1 < sizeof(abRead));
2441
2442 char *pszVersionStart = &abRead[sizeof(s_abFreeBsdHdr) - 1];
2443 char *pszVersionEnd = pszVersionStart;
2444
2445 while (RT_C_IS_DIGIT(*pszVersionEnd))
2446 pszVersionEnd++;
2447 if (*pszVersionEnd == '.')
2448 {
2449 pszVersionEnd++; /* Skip the . */
2450
2451 while (RT_C_IS_DIGIT(*pszVersionEnd))
2452 pszVersionEnd++;
2453
2454 /* Terminate the version string. */
2455 *pszVersionEnd = '\0';
2456
2457 try { mStrDetectedOSVersion = pszVersionStart; }
2458 catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
2459 }
2460 else
2461 LogRel(("Unattended/FBSD: Unknown: version='%s'\n", &abRead[0]));
2462 }
2463 else
2464 {
2465 LogRel(("Unattended/FBSD: No Volume Label found\n"));
2466 mEnmOsType = VBOXOSTYPE_FreeBSD;
2467 }
2468
2469 hrc = S_OK;
2470 }
2471
2472 RTVfsFileRelease(hVfsFile);
2473 }
2474
2475 return hrc;
2476}
2477
2478
2479HRESULT Unattended::prepare()
2480{
2481 LogFlow(("Unattended::prepare: enter\n"));
2482
2483 /*
2484 * Must have a machine.
2485 */
2486 ComPtr<Machine> ptrMachine;
2487 Guid MachineUuid;
2488 {
2489 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2490 ptrMachine = mMachine;
2491 if (ptrMachine.isNull())
2492 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("No machine associated with this IUnatteded instance"));
2493 MachineUuid = mMachineUuid;
2494 }
2495
2496 /*
2497 * Before we write lock ourselves, we must get stuff from Machine and
2498 * VirtualBox because their locks have higher priorities than ours.
2499 */
2500 Utf8Str strGuestOsTypeId;
2501 Utf8Str strMachineName;
2502 Utf8Str strDefaultAuxBasePath;
2503 HRESULT hrc;
2504 try
2505 {
2506 Bstr bstrTmp;
2507 hrc = ptrMachine->COMGETTER(OSTypeId)(bstrTmp.asOutParam());
2508 if (SUCCEEDED(hrc))
2509 {
2510 strGuestOsTypeId = bstrTmp;
2511 hrc = ptrMachine->COMGETTER(Name)(bstrTmp.asOutParam());
2512 if (SUCCEEDED(hrc))
2513 strMachineName = bstrTmp;
2514 }
2515 int vrc = ptrMachine->i_calculateFullPath(Utf8StrFmt("Unattended-%RTuuid-", MachineUuid.raw()), strDefaultAuxBasePath);
2516 if (RT_FAILURE(vrc))
2517 return setErrorBoth(E_FAIL, vrc);
2518 }
2519 catch (std::bad_alloc &)
2520 {
2521 return E_OUTOFMEMORY;
2522 }
2523 bool const fIs64Bit = i_isGuestOSArchX64(strGuestOsTypeId);
2524
2525 ComPtr<IPlatform> pPlatform;
2526 hrc = ptrMachine->COMGETTER(Platform)(pPlatform.asOutParam());
2527 AssertComRCReturn(hrc, hrc);
2528
2529 BOOL fRtcUseUtc = FALSE;
2530 hrc = pPlatform->COMGETTER(RTCUseUTC)(&fRtcUseUtc);
2531 if (FAILED(hrc))
2532 return hrc;
2533
2534 ComPtr<IFirmwareSettings> pFirmwareSettings;
2535 hrc = ptrMachine->COMGETTER(FirmwareSettings)(pFirmwareSettings.asOutParam());
2536 AssertComRCReturn(hrc, hrc);
2537
2538 FirmwareType_T enmFirmware = FirmwareType_BIOS;
2539 hrc = pFirmwareSettings->COMGETTER(FirmwareType)(&enmFirmware);
2540 if (FAILED(hrc))
2541 return hrc;
2542
2543 /*
2544 * Write lock this object and set attributes we got from IMachine.
2545 */
2546 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2547
2548 mStrGuestOsTypeId = strGuestOsTypeId;
2549 mfGuestOs64Bit = fIs64Bit;
2550 mfRtcUseUtc = RT_BOOL(fRtcUseUtc);
2551 menmFirmwareType = enmFirmware;
2552
2553 /*
2554 * Do some state checks.
2555 */
2556 if (mpInstaller != NULL)
2557 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The prepare method has been called (must call done to restart)"));
2558 if ((Machine *)ptrMachine != (Machine *)mMachine)
2559 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The 'machine' while we were using it - please don't do that"));
2560
2561 /*
2562 * Check if the specified ISOs and files exist.
2563 */
2564 if (!RTFileExists(mStrIsoPath.c_str()))
2565 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the installation ISO file '%s'"),
2566 mStrIsoPath.c_str());
2567 if (mfInstallGuestAdditions && !RTFileExists(mStrAdditionsIsoPath.c_str()))
2568 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the Guest Additions ISO file '%s'"),
2569 mStrAdditionsIsoPath.c_str());
2570 if (mfInstallTestExecService && !RTFileExists(mStrValidationKitIsoPath.c_str()))
2571 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the validation kit ISO file '%s'"),
2572 mStrValidationKitIsoPath.c_str());
2573 if (mfInstallUserPayload && !RTFileExists(mStrUserPayloadIsoPath.c_str()))
2574 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the User Payload ISO file '%s'"),
2575 mStrUserPayloadIsoPath.c_str());
2576 if (mStrScriptTemplatePath.isNotEmpty() && !RTFileExists(mStrScriptTemplatePath.c_str()))
2577 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate unattended installation script template '%s'"),
2578 mStrScriptTemplatePath.c_str());
2579
2580 /*
2581 * Do media detection if it haven't been done yet.
2582 */
2583 if (!mfDoneDetectIsoOS)
2584 {
2585 hrc = detectIsoOS();
2586 if (FAILED(hrc) && hrc != E_NOTIMPL)
2587 return hrc;
2588 }
2589
2590 /*
2591 * We can now check midxImage against mDetectedImages, since the latter is
2592 * populated during the detectIsoOS call. We ignore midxImage if no images
2593 * were detected, assuming that it's not relevant or used for different purposes.
2594 */
2595 if (mDetectedImages.size() > 0)
2596 {
2597 bool fImageFound = false;
2598 for (size_t i = 0; i < mDetectedImages.size(); ++i)
2599 if (midxImage == mDetectedImages[i].mImageIndex)
2600 {
2601 i_updateDetectedAttributeForImage(mDetectedImages[i]);
2602 fImageFound = true;
2603 break;
2604 }
2605 if (!fImageFound)
2606 return setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("imageIndex value %u not found in detectedImageIndices"), midxImage);
2607 }
2608
2609 /*
2610 * Get the ISO's detect guest OS type info and make it's a known one (just
2611 * in case the above step doesn't work right).
2612 */
2613 uint32_t const idxIsoOSType = Global::getOSTypeIndexFromId(mStrDetectedOSTypeId.c_str());
2614 VBOXOSTYPE const enmIsoOSType = idxIsoOSType < Global::cOSTypes ? Global::sOSTypes[idxIsoOSType].osType : VBOXOSTYPE_Unknown;
2615 if ((enmIsoOSType & VBOXOSTYPE_OsFamilyMask) == VBOXOSTYPE_Unknown)
2616 return setError(E_FAIL, tr("The supplied ISO file does not contain an OS currently supported for unattended installation"));
2617
2618 /*
2619 * Get the VM's configured guest OS type info.
2620 */
2621 uint32_t const idxMachineOSType = Global::getOSTypeIndexFromId(mStrGuestOsTypeId.c_str());
2622 VBOXOSTYPE const enmMachineOSType = idxMachineOSType < Global::cOSTypes
2623 ? Global::sOSTypes[idxMachineOSType].osType : VBOXOSTYPE_Unknown;
2624 uint32_t const osHint = idxMachineOSType < Global::cOSTypes
2625 ? Global::sOSTypes[idxMachineOSType].osHint : 0;
2626 /*
2627 * Check that the detected guest OS type for the ISO is compatible with
2628 * that of the VM, broadly speaking.
2629 */
2630 if (idxMachineOSType != idxIsoOSType)
2631 {
2632 /* Check that the architecture is compatible: */
2633 if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask)
2634 && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86
2635 || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64))
2636 return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch"));
2637 }
2638
2639 /* We don't support guest OSes w/ EFI, as that requires UDF remastering support we don't have yet. */
2640 if ( (osHint & VBOXOSHINT_EFI)
2641 && (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_arm64)
2642 return setError(E_FAIL, tr("The detected guest OS type requires EFI to boot and therefore is not supported yet"));
2643
2644 /* Set the guest additions install package name. */
2645 mStrAdditionsInstallPackage = Global::sOSTypes[idxMachineOSType].guestAdditionsInstallPkgName;
2646
2647 /*
2648 * Do some default property stuff and check other properties.
2649 */
2650 try
2651 {
2652 char szTmp[128];
2653
2654 if (mStrLocale.isEmpty())
2655 {
2656 int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp));
2657 if ( RT_SUCCESS(vrc)
2658 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp))
2659 mStrLocale.assign(szTmp, 5);
2660 else
2661 mStrLocale = "en_US";
2662 Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale));
2663 }
2664
2665 if (mStrLanguage.isEmpty())
2666 {
2667 if (mDetectedOSLanguages.size() > 0)
2668 mStrLanguage = mDetectedOSLanguages[0];
2669 else
2670 mStrLanguage.assign(mStrLocale).findReplace('_', '-');
2671 }
2672
2673 if (mStrCountry.isEmpty())
2674 {
2675 int vrc = RTLocaleQueryUserCountryCode(szTmp);
2676 if (RT_SUCCESS(vrc))
2677 mStrCountry = szTmp;
2678 else if ( mStrLocale.isNotEmpty()
2679 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale))
2680 mStrCountry.assign(mStrLocale, 3, 2);
2681 else
2682 mStrCountry = "US";
2683 }
2684
2685 if (mStrTimeZone.isEmpty())
2686 {
2687 int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp));
2688 if ( RT_SUCCESS(vrc)
2689 && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */)
2690 mStrTimeZone = szTmp;
2691 else
2692 mStrTimeZone = "Etc/UTC";
2693 Assert(mStrTimeZone.isNotEmpty());
2694 }
2695 mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str());
2696 if (!mpTimeZoneInfo)
2697 mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str());
2698 Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC");
2699 if (!mpTimeZoneInfo)
2700 LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str()));
2701
2702 if (mStrHostname.isEmpty())
2703 {
2704 /* Mangle the VM name into a valid hostname. */
2705 for (size_t i = 0; i < strMachineName.length(); i++)
2706 {
2707 char ch = strMachineName[i];
2708 if ( (unsigned)ch < 127
2709 && RT_C_IS_ALNUM(ch))
2710 mStrHostname.append(ch);
2711 else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-"))
2712 mStrHostname.append('-');
2713 }
2714 if (mStrHostname.length() == 0)
2715 mStrHostname.printf("%RTuuid-vm", MachineUuid.raw());
2716 else if (mStrHostname.length() < 3)
2717 mStrHostname.append("-vm");
2718 mStrHostname.append(".myguest.virtualbox.org");
2719 }
2720
2721 if (mStrAuxiliaryBasePath.isEmpty())
2722 {
2723 mStrAuxiliaryBasePath = strDefaultAuxBasePath;
2724 mfIsDefaultAuxiliaryBasePath = true;
2725 }
2726 }
2727 catch (std::bad_alloc &)
2728 {
2729 return E_OUTOFMEMORY;
2730 }
2731
2732 /*
2733 * Instatiate the guest installer matching the ISO.
2734 */
2735 mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion,
2736 mStrDetectedOSFlavor, mStrDetectedOSHints, this);
2737 if (mpInstaller != NULL)
2738 {
2739 hrc = mpInstaller->initInstaller();
2740 if (SUCCEEDED(hrc))
2741 {
2742 /*
2743 * Do the script preps (just reads them).
2744 */
2745 hrc = mpInstaller->prepareUnattendedScripts();
2746 if (SUCCEEDED(hrc))
2747 {
2748 LogFlow(("Unattended::prepare: returns S_OK\n"));
2749 return S_OK;
2750 }
2751 }
2752
2753 /* Destroy the installer instance. */
2754 delete mpInstaller;
2755 mpInstaller = NULL;
2756 }
2757 else
2758 hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND,
2759 tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str());
2760 LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc));
2761 return hrc;
2762}
2763
2764HRESULT Unattended::constructMedia()
2765{
2766 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2767
2768 LogFlow(("===========================================================\n"));
2769 LogFlow(("Call Unattended::constructMedia()\n"));
2770
2771 if (mpInstaller == NULL)
2772 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called");
2773
2774 return mpInstaller->prepareMedia();
2775}
2776
2777HRESULT Unattended::reconfigureVM()
2778{
2779 LogFlow(("===========================================================\n"));
2780 LogFlow(("Call Unattended::reconfigureVM()\n"));
2781
2782 /*
2783 * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues.
2784 */
2785 StorageBus_T enmRecommendedStorageBus = StorageBus_IDE;
2786 {
2787 Bstr bstrGuestOsTypeId;
2788 Bstr bstrDetectedOSTypeId;
2789 {
2790 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2791 if (mpInstaller == NULL)
2792 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2793 bstrGuestOsTypeId = mStrGuestOsTypeId;
2794 bstrDetectedOSTypeId = mStrDetectedOSTypeId;
2795 }
2796 ComPtr<IGuestOSType> ptrGuestOSType;
2797 HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam());
2798 if (SUCCEEDED(hrc))
2799 {
2800 if (!ptrGuestOSType.isNull())
2801 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus);
2802 }
2803 if (FAILED(hrc))
2804 return hrc;
2805
2806 /* If the detected guest OS type differs, log a warning if their DVD storage
2807 bus recommendations differ. */
2808 if (bstrGuestOsTypeId != bstrDetectedOSTypeId)
2809 {
2810 StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE;
2811 hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam());
2812 if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull())
2813 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2);
2814 if (FAILED(hrc))
2815 return hrc;
2816
2817 if (enmRecommendedStorageBus != enmRecommendedStorageBus2)
2818 LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n",
2819 ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(),
2820 ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() ));
2821 }
2822 }
2823
2824 /*
2825 * Take write lock (for lock order reasons, write lock our parent object too)
2826 * then make sure we're the only caller of this method.
2827 */
2828 AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS);
2829 HRESULT hrc;
2830 if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD)
2831 {
2832 RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf();
2833 mhThreadReconfigureVM = hNativeSelf;
2834
2835 /*
2836 * Create a new session, lock the machine and get the session machine object.
2837 * Do the locking without pinning down the write locks, just to be on the safe side.
2838 */
2839 ComPtr<ISession> ptrSession;
2840 try
2841 {
2842 hrc = ptrSession.createInprocObject(CLSID_Session);
2843 }
2844 catch (std::bad_alloc &)
2845 {
2846 hrc = E_OUTOFMEMORY;
2847 }
2848 if (SUCCEEDED(hrc))
2849 {
2850 alock.release();
2851 hrc = mMachine->LockMachine(ptrSession, LockType_Shared);
2852 alock.acquire();
2853 if (SUCCEEDED(hrc))
2854 {
2855 ComPtr<IMachine> ptrSessionMachine;
2856 hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam());
2857 if (SUCCEEDED(hrc))
2858 {
2859 /*
2860 * Hand the session to the inner work and let it do it job.
2861 */
2862 try
2863 {
2864 hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine);
2865 }
2866 catch (...)
2867 {
2868 hrc = E_UNEXPECTED;
2869 }
2870 }
2871
2872 /* Paranoia: release early in case we it a bump below. */
2873 Assert(mhThreadReconfigureVM == hNativeSelf);
2874 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2875
2876 /*
2877 * While unlocking the machine we'll have to drop the locks again.
2878 */
2879 alock.release();
2880
2881 ptrSessionMachine.setNull();
2882 HRESULT hrc2 = ptrSession->UnlockMachine();
2883 AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2));
2884
2885 ptrSession.setNull();
2886
2887 alock.acquire();
2888 }
2889 else
2890 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2891 }
2892 else
2893 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2894 }
2895 else
2896 hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread"));
2897 return hrc;
2898}
2899
2900
2901HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
2902 ComPtr<IMachine> const &rPtrSessionMachine)
2903{
2904 if (mpInstaller == NULL)
2905 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2906
2907 // Fetch all available storage controllers
2908 com::SafeIfaceArray<IStorageController> arrayOfControllers;
2909 HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers));
2910 AssertComRCReturn(hrc, hrc);
2911
2912 /*
2913 * Figure out where the images are to be mounted, adding controllers/ports as needed.
2914 */
2915 std::vector<UnattendedInstallationDisk> vecInstallationDisks;
2916 if (mpInstaller->isAuxiliaryFloppyNeeded())
2917 {
2918 hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock);
2919 if (FAILED(hrc))
2920 return hrc;
2921 }
2922
2923 hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus);
2924 if (FAILED(hrc))
2925 return hrc;
2926
2927 /*
2928 * Mount the images.
2929 */
2930 for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++)
2931 {
2932 UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage);
2933 Assert(pImage->strImagePath.isNotEmpty());
2934 hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock);
2935 if (FAILED(hrc))
2936 return hrc;
2937 }
2938
2939 /*
2940 * Set the boot order.
2941 *
2942 * ASSUME that the HD isn't bootable when we start out, but it will be what
2943 * we boot from after the first stage of the installation is done. Setting
2944 * it first prevents endless reboot cylces.
2945 */
2946 /** @todo consider making 100% sure the disk isn't bootable (edit partition
2947 * table active bits and EFI stuff). */
2948 Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD
2949 || mpInstaller->getBootableDeviceType() == DeviceType_Floppy);
2950 hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk);
2951 if (SUCCEEDED(hrc))
2952 hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType());
2953 if (SUCCEEDED(hrc))
2954 hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD
2955 ? DeviceType_Floppy : DeviceType_DVD);
2956 if (FAILED(hrc))
2957 return hrc;
2958
2959 /*
2960 * Essential step.
2961 *
2962 * HACK ALERT! We have to release the lock here or we'll get into trouble with
2963 * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType).
2964 */
2965 if (SUCCEEDED(hrc))
2966 {
2967 rAutoLock.release();
2968 hrc = rPtrSessionMachine->SaveSettings();
2969 rAutoLock.acquire();
2970 }
2971
2972 return hrc;
2973}
2974
2975/**
2976 * Makes sure we've got a floppy drive attached to a floppy controller, adding
2977 * the auxiliary floppy image to the installation disk vector.
2978 *
2979 * @returns COM status code.
2980 * @param rControllers The existing controllers.
2981 * @param rVecInstallatationDisks The list of image to mount.
2982 * @param rPtrSessionMachine The session machine smart pointer.
2983 * @param rAutoLock The lock.
2984 */
2985HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
2986 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
2987 ComPtr<IMachine> const &rPtrSessionMachine,
2988 AutoMultiWriteLock2 &rAutoLock)
2989{
2990 Assert(mpInstaller->isAuxiliaryFloppyNeeded());
2991
2992 /*
2993 * Look for a floppy controller with a primary drive (A:) we can "insert"
2994 * the auxiliary floppy image. Add a controller and/or a drive if necessary.
2995 */
2996 bool fFoundPort0Dev0 = false;
2997 Bstr bstrControllerName;
2998 Utf8Str strControllerName;
2999
3000 for (size_t i = 0; i < rControllers.size(); ++i)
3001 {
3002 StorageBus_T enmStorageBus;
3003 HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3004 AssertComRCReturn(hrc, hrc);
3005 if (enmStorageBus == StorageBus_Floppy)
3006 {
3007
3008 /*
3009 * Found a floppy controller.
3010 */
3011 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3012 AssertComRCReturn(hrc, hrc);
3013
3014 /*
3015 * Check the attchments to see if we've got a device 0 attached on port 0.
3016 *
3017 * While we're at it we eject flppies from all floppy drives we encounter,
3018 * we don't want any confusion at boot or during installation.
3019 */
3020 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3021 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3022 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3023 AssertComRCReturn(hrc, hrc);
3024 strControllerName = bstrControllerName;
3025 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3026
3027 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3028 {
3029 LONG iPort = -1;
3030 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3031 AssertComRCReturn(hrc, hrc);
3032
3033 LONG iDevice = -1;
3034 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3035 AssertComRCReturn(hrc, hrc);
3036
3037 DeviceType_T enmType;
3038 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3039 AssertComRCReturn(hrc, hrc);
3040
3041 if (enmType == DeviceType_Floppy)
3042 {
3043 ComPtr<IMedium> ptrMedium;
3044 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3045 AssertComRCReturn(hrc, hrc);
3046
3047 if (ptrMedium.isNotNull())
3048 {
3049 ptrMedium.setNull();
3050 rAutoLock.release();
3051 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3052 rAutoLock.acquire();
3053 }
3054
3055 if (iPort == 0 && iDevice == 0)
3056 fFoundPort0Dev0 = true;
3057 }
3058 else if (iPort == 0 && iDevice == 0)
3059 return setError(E_FAIL,
3060 tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"),
3061 bstrControllerName.raw());
3062 }
3063 }
3064 }
3065
3066 /*
3067 * Add a floppy controller if we need to.
3068 */
3069 if (strControllerName.isEmpty())
3070 {
3071 bstrControllerName = strControllerName = "Floppy";
3072 ComPtr<IStorageController> ptrControllerIgnored;
3073 HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy,
3074 ptrControllerIgnored.asOutParam());
3075 LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc));
3076 if (FAILED(hrc))
3077 return hrc;
3078 }
3079
3080 /*
3081 * Adding a floppy drive (if needed) and mounting the auxiliary image is
3082 * done later together with the ISOs.
3083 */
3084 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName,
3085 DeviceType_Floppy, AccessMode_ReadWrite,
3086 0, 0,
3087 fFoundPort0Dev0 /*fMountOnly*/,
3088 mpInstaller->getAuxiliaryFloppyFilePath(), false));
3089 return S_OK;
3090}
3091
3092/**
3093 * Reconfigures DVD drives of the VM to mount all the ISOs we need.
3094 *
3095 * This will umount all DVD media.
3096 *
3097 * @returns COM status code.
3098 * @param rControllers The existing controllers.
3099 * @param rVecInstallatationDisks The list of image to mount.
3100 * @param rPtrSessionMachine The session machine smart pointer.
3101 * @param rAutoLock The lock.
3102 * @param enmRecommendedStorageBus The recommended storage bus type for adding
3103 * DVD drives on.
3104 */
3105HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
3106 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
3107 ComPtr<IMachine> const &rPtrSessionMachine,
3108 AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus)
3109{
3110 /*
3111 * Enumerate the attachements of every controller, looking for DVD drives,
3112 * ASSUMEING all drives are bootable.
3113 *
3114 * Eject the medium from all the drives (don't want any confusion) and look
3115 * for the recommended storage bus in case we need to add more drives.
3116 */
3117 HRESULT hrc;
3118 std::list<ControllerSlot> lstControllerDvdSlots;
3119 Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */
3120 Utf8Str strControllerName;
3121 Bstr bstrControllerName;
3122 for (size_t i = 0; i < rControllers.size(); ++i)
3123 {
3124 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3125 AssertComRCReturn(hrc, hrc);
3126 strControllerName = bstrControllerName;
3127
3128 /* Look for recommended storage bus. */
3129 StorageBus_T enmStorageBus;
3130 hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3131 AssertComRCReturn(hrc, hrc);
3132 if (enmStorageBus == enmRecommendedStorageBus)
3133 {
3134 strRecommendedControllerName = bstrControllerName;
3135 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3136 }
3137
3138 /* Scan the controller attachments. */
3139 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3140 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3141 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3142 AssertComRCReturn(hrc, hrc);
3143
3144 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3145 {
3146 DeviceType_T enmType;
3147 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3148 AssertComRCReturn(hrc, hrc);
3149 if (enmType == DeviceType_DVD)
3150 {
3151 LONG iPort = -1;
3152 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3153 AssertComRCReturn(hrc, hrc);
3154
3155 LONG iDevice = -1;
3156 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3157 AssertComRCReturn(hrc, hrc);
3158
3159 /* Remeber it. */
3160 lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/));
3161
3162 /* Eject the medium, if any. */
3163 ComPtr<IMedium> ptrMedium;
3164 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3165 AssertComRCReturn(hrc, hrc);
3166 if (ptrMedium.isNotNull())
3167 {
3168 ptrMedium.setNull();
3169
3170 rAutoLock.release();
3171 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3172 rAutoLock.acquire();
3173 }
3174 }
3175 }
3176 }
3177
3178 /*
3179 * How many drives do we need? Add more if necessary.
3180 */
3181 ULONG cDvdDrivesNeeded = 0;
3182 if (mpInstaller->isAuxiliaryIsoNeeded())
3183 cDvdDrivesNeeded++;
3184 if (mpInstaller->isOriginalIsoNeeded())
3185 cDvdDrivesNeeded++;
3186#if 0 /* These are now in the AUX VISO. */
3187 if (mpInstaller->isAdditionsIsoNeeded())
3188 cDvdDrivesNeeded++;
3189 if (mpInstaller->isValidationKitIsoNeeded())
3190 cDvdDrivesNeeded++;
3191#endif
3192 Assert(cDvdDrivesNeeded > 0);
3193 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3194 {
3195 /* Do we need to add the recommended controller? */
3196 if (strRecommendedControllerName.isEmpty())
3197 {
3198 strRecommendedControllerName = StorageController::i_controllerNameFromBusType(enmRecommendedStorageBus);
3199
3200 ComPtr<IStorageController> ptrControllerIgnored;
3201 hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus,
3202 ptrControllerIgnored.asOutParam());
3203 LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc));
3204 if (FAILED(hrc))
3205 return hrc;
3206 }
3207
3208 /* Add free controller slots, maybe raising the port limit on the controller if we can. */
3209 hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine,
3210 cDvdDrivesNeeded, lstControllerDvdSlots);
3211 if (FAILED(hrc))
3212 return hrc;
3213 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3214 {
3215 /* We could in many cases create another controller here, but it's not worth the effort. */
3216 return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "",
3217 cDvdDrivesNeeded - lstControllerDvdSlots.size()),
3218 strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size());
3219 }
3220 Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size());
3221 }
3222
3223 /*
3224 * Sort the DVD slots in boot order.
3225 */
3226 lstControllerDvdSlots.sort();
3227
3228 /*
3229 * Prepare ISO mounts.
3230 *
3231 * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots
3232 * according to the boot order.
3233 */
3234 std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin();
3235 if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso())
3236 {
3237 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3238 ++itDvdSlot;
3239 }
3240
3241 if (mpInstaller->isOriginalIsoNeeded())
3242 {
3243 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath(), false));
3244 ++itDvdSlot;
3245 }
3246
3247 if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso())
3248 {
3249 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3250 ++itDvdSlot;
3251 }
3252
3253#if 0 /* These are now in the AUX VISO. */
3254 if (mpInstaller->isAdditionsIsoNeeded())
3255 {
3256 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath(), false));
3257 ++itDvdSlot;
3258 }
3259
3260 if (mpInstaller->isValidationKitIsoNeeded())
3261 {
3262 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath(), false));
3263 ++itDvdSlot;
3264 }
3265#endif
3266
3267 return S_OK;
3268}
3269
3270/**
3271 * Used to find more free slots for DVD drives during VM reconfiguration.
3272 *
3273 * This may modify the @a portCount property of the given controller.
3274 *
3275 * @returns COM status code.
3276 * @param rStrControllerName The name of the controller to find/create
3277 * free slots on.
3278 * @param enmStorageBus The storage bus type.
3279 * @param rPtrSessionMachine Reference to the session machine.
3280 * @param cSlotsNeeded Total slots needed (including those we've
3281 * already found).
3282 * @param rDvdSlots The slot collection for DVD drives to add
3283 * free slots to as we find/create them.
3284 */
3285HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
3286 ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
3287 std::list<ControllerSlot> &rDvdSlots)
3288{
3289 Assert(cSlotsNeeded > rDvdSlots.size());
3290
3291 /*
3292 * Get controlleer stats.
3293 */
3294 ComPtr<IStorageController> pController;
3295 HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam());
3296 AssertComRCReturn(hrc, hrc);
3297
3298 ULONG cMaxDevicesPerPort = 1;
3299 hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort);
3300 AssertComRCReturn(hrc, hrc);
3301 AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED);
3302
3303 ULONG cPorts = 0;
3304 hrc = pController->COMGETTER(PortCount)(&cPorts);
3305 AssertComRCReturn(hrc, hrc);
3306
3307 /*
3308 * Get the attachment list and turn into an internal list for lookup speed.
3309 */
3310 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3311 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(),
3312 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3313 AssertComRCReturn(hrc, hrc);
3314
3315 std::vector<ControllerSlot> arrayOfUsedSlots;
3316 for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++)
3317 {
3318 LONG iPort = -1;
3319 hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort);
3320 AssertComRCReturn(hrc, hrc);
3321
3322 LONG iDevice = -1;
3323 hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice);
3324 AssertComRCReturn(hrc, hrc);
3325
3326 arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/));
3327 }
3328
3329 /*
3330 * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots.
3331 */
3332 for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++)
3333 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3334 {
3335 bool fFound = false;
3336 for (size_t i = 0; i < arrayOfUsedSlots.size(); i++)
3337 if ( arrayOfUsedSlots[i].iPort == iPort
3338 && arrayOfUsedSlots[i].iDevice == iDevice)
3339 {
3340 fFound = true;
3341 break;
3342 }
3343 if (!fFound)
3344 {
3345 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3346 if (rDvdSlots.size() >= cSlotsNeeded)
3347 return S_OK;
3348 }
3349 }
3350
3351 /*
3352 * Okay we still need more ports. See if increasing the number of controller
3353 * ports would solve it.
3354 */
3355 ULONG cMaxPorts = 1;
3356 hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts);
3357 AssertComRCReturn(hrc, hrc);
3358 if (cMaxPorts <= cPorts)
3359 return S_OK;
3360 size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort;
3361 if (cPorts + cNewPortsNeeded > cMaxPorts)
3362 return S_OK;
3363
3364 /*
3365 * Raise the port count and add the free slots we've just created.
3366 */
3367 hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded);
3368 AssertComRCReturn(hrc, hrc);
3369 int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded);
3370 for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++)
3371 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3372 {
3373 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3374 if (rDvdSlots.size() >= cSlotsNeeded)
3375 return S_OK;
3376 }
3377
3378 /* We should not get here! */
3379 AssertLogRelFailedReturn(E_UNEXPECTED);
3380}
3381
3382HRESULT Unattended::done()
3383{
3384 LogFlow(("Unattended::done\n"));
3385 if (mpInstaller)
3386 {
3387 LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller));
3388 delete mpInstaller;
3389 mpInstaller = NULL;
3390 }
3391 return S_OK;
3392}
3393
3394HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
3395{
3396 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3397 isoPath = mStrIsoPath;
3398 return S_OK;
3399}
3400
3401HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
3402{
3403 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3404 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3405 mStrIsoPath = isoPath;
3406 mfDoneDetectIsoOS = false;
3407 return S_OK;
3408}
3409
3410HRESULT Unattended::getUser(com::Utf8Str &user)
3411{
3412 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3413 user = mStrUser;
3414 return S_OK;
3415}
3416
3417
3418HRESULT Unattended::setUser(const com::Utf8Str &user)
3419{
3420 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3421 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3422 mStrUser = user;
3423 return S_OK;
3424}
3425
3426HRESULT Unattended::getPassword(com::Utf8Str &password)
3427{
3428 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3429 password = mStrPassword;
3430 return S_OK;
3431}
3432
3433HRESULT Unattended::setPassword(const com::Utf8Str &password)
3434{
3435 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3436 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3437 mStrPassword = password;
3438 return S_OK;
3439}
3440
3441HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
3442{
3443 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3444 fullUserName = mStrFullUserName;
3445 return S_OK;
3446}
3447
3448HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
3449{
3450 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3451 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3452 mStrFullUserName = fullUserName;
3453 return S_OK;
3454}
3455
3456HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
3457{
3458 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3459 productKey = mStrProductKey;
3460 return S_OK;
3461}
3462
3463HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
3464{
3465 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3466 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3467 mStrProductKey = productKey;
3468 return S_OK;
3469}
3470
3471HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
3472{
3473 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3474 additionsIsoPath = mStrAdditionsIsoPath;
3475 return S_OK;
3476}
3477
3478HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
3479{
3480 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3481 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3482 mStrAdditionsIsoPath = additionsIsoPath;
3483 return S_OK;
3484}
3485
3486HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
3487{
3488 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3489 *installGuestAdditions = mfInstallGuestAdditions;
3490 return S_OK;
3491}
3492
3493HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
3494{
3495 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3496 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3497 mfInstallGuestAdditions = installGuestAdditions != FALSE;
3498 return S_OK;
3499}
3500
3501HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
3502{
3503 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3504 aValidationKitIsoPath = mStrValidationKitIsoPath;
3505 return S_OK;
3506}
3507
3508HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
3509{
3510 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3511 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3512 mStrValidationKitIsoPath = aValidationKitIsoPath;
3513 return S_OK;
3514}
3515
3516HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
3517{
3518 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3519 *aInstallTestExecService = mfInstallTestExecService;
3520 return S_OK;
3521}
3522
3523HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
3524{
3525 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3526 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3527 mfInstallTestExecService = aInstallTestExecService != FALSE;
3528 return S_OK;
3529}
3530
3531HRESULT Unattended::getUserPayloadIsoPath(com::Utf8Str &aUserPayloadIsoPath)
3532{
3533 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3534 aUserPayloadIsoPath = mStrUserPayloadIsoPath;
3535 return S_OK;
3536}
3537
3538HRESULT Unattended::setUserPayloadIsoPath(const com::Utf8Str &aUserPayloadIsoPath)
3539{
3540 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3541 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3542 mStrUserPayloadIsoPath = aUserPayloadIsoPath;
3543 return S_OK;
3544}
3545
3546HRESULT Unattended::getInstallUserPayload(BOOL *aInstallUserPayload)
3547{
3548 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3549 *aInstallUserPayload = mfInstallUserPayload;
3550 return S_OK;
3551}
3552
3553HRESULT Unattended::setInstallUserPayload(BOOL aInstallUserPayload)
3554{
3555 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3556 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3557 mfInstallUserPayload = aInstallUserPayload != FALSE;
3558 return S_OK;
3559}
3560
3561HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
3562{
3563 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3564 aTimeZone = mStrTimeZone;
3565 return S_OK;
3566}
3567
3568HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
3569{
3570 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3571 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3572 mStrTimeZone = aTimezone;
3573 return S_OK;
3574}
3575
3576HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
3577{
3578 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3579 aLocale = mStrLocale;
3580 return S_OK;
3581}
3582
3583HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
3584{
3585 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3586 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3587 if ( aLocale.isEmpty() /* use default */
3588 || ( aLocale.length() == 5
3589 && RT_C_IS_LOWER(aLocale[0])
3590 && RT_C_IS_LOWER(aLocale[1])
3591 && aLocale[2] == '_'
3592 && RT_C_IS_UPPER(aLocale[3])
3593 && RT_C_IS_UPPER(aLocale[4])) )
3594 {
3595 mStrLocale = aLocale;
3596 return S_OK;
3597 }
3598 return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters"));
3599}
3600
3601HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
3602{
3603 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3604 aLanguage = mStrLanguage;
3605 return S_OK;
3606}
3607
3608HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
3609{
3610 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3611 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3612 mStrLanguage = aLanguage;
3613 return S_OK;
3614}
3615
3616HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
3617{
3618 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3619 aCountry = mStrCountry;
3620 return S_OK;
3621}
3622
3623HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
3624{
3625 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3626 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3627 if ( aCountry.isEmpty()
3628 || ( aCountry.length() == 2
3629 && RT_C_IS_UPPER(aCountry[0])
3630 && RT_C_IS_UPPER(aCountry[1])) )
3631 {
3632 mStrCountry = aCountry;
3633 return S_OK;
3634 }
3635 return setError(E_INVALIDARG, tr("Expected two upper cased letters"));
3636}
3637
3638HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
3639{
3640 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3641 aProxy = mStrProxy; /// @todo turn schema map into string or something.
3642 return S_OK;
3643}
3644
3645HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
3646{
3647 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3648 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3649 if (aProxy.isEmpty())
3650 {
3651 /* set default proxy */
3652 /** @todo BUGBUG! implement this */
3653 }
3654 else if (aProxy.equalsIgnoreCase("none"))
3655 {
3656 /* clear proxy config */
3657 mStrProxy.setNull();
3658 }
3659 else
3660 {
3661 /** @todo Parse and set proxy config into a schema map or something along those lines. */
3662 /** @todo BUGBUG! implement this */
3663 // return E_NOTIMPL;
3664 mStrProxy = aProxy;
3665 }
3666 return S_OK;
3667}
3668
3669HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
3670{
3671 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3672 aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";");
3673 return S_OK;
3674}
3675
3676HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
3677{
3678 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3679 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3680 if (aPackageSelectionAdjustments.isEmpty())
3681 mPackageSelectionAdjustments.clear();
3682 else
3683 {
3684 RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";");
3685 for (size_t i = 0; i < arrayStrSplit.size(); i++)
3686 {
3687 if (arrayStrSplit[i].equals("minimal"))
3688 { /* okay */ }
3689 else
3690 return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str());
3691 }
3692 mPackageSelectionAdjustments = arrayStrSplit;
3693 }
3694 return S_OK;
3695}
3696
3697HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
3698{
3699 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3700 aHostname = mStrHostname;
3701 return S_OK;
3702}
3703
3704HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
3705{
3706 /*
3707 * Validate input.
3708 */
3709 if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U))
3710 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3711 tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()),
3712 aHostname.c_str(), aHostname.length());
3713 size_t cLabels = 0;
3714 const char *pszSrc = aHostname.c_str();
3715 for (;;)
3716 {
3717 size_t cchLabel = 1;
3718 char ch = *pszSrc++;
3719 if (RT_C_IS_ALNUM(ch))
3720 {
3721 cLabels++;
3722 while ((ch = *pszSrc++) != '.' && ch != '\0')
3723 {
3724 if (RT_C_IS_ALNUM(ch) || ch == '-')
3725 {
3726 if (cchLabel < 63)
3727 cchLabel++;
3728 else
3729 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3730 tr("Invalid hostname '%s' - label %u is too long, max is 63."),
3731 aHostname.c_str(), cLabels);
3732 }
3733 else
3734 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3735 tr("Invalid hostname '%s' - illegal char '%c' at position %zu"),
3736 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3737 }
3738 if (cLabels == 1 && cchLabel < 2)
3739 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3740 tr("Invalid hostname '%s' - the name part must be at least two characters long"),
3741 aHostname.c_str());
3742 if (ch == '\0')
3743 break;
3744 }
3745 else if (ch != '\0')
3746 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3747 tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"),
3748 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3749 else
3750 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3751 tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str());
3752 }
3753 if (cLabels < 2)
3754 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3755 tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str());
3756
3757 /*
3758 * Make the change.
3759 */
3760 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3761 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3762 mStrHostname = aHostname;
3763 return S_OK;
3764}
3765
3766HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
3767{
3768 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3769 aAuxiliaryBasePath = mStrAuxiliaryBasePath;
3770 return S_OK;
3771}
3772
3773HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
3774{
3775 if (aAuxiliaryBasePath.isEmpty())
3776 return setError(E_INVALIDARG, tr("Empty base path is not allowed"));
3777 if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str()))
3778 return setError(E_INVALIDARG, tr("Base path must be absolute"));
3779
3780 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3781 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3782 mStrAuxiliaryBasePath = aAuxiliaryBasePath;
3783 mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty();
3784 return S_OK;
3785}
3786
3787HRESULT Unattended::getImageIndex(ULONG *index)
3788{
3789 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3790 *index = midxImage;
3791 return S_OK;
3792}
3793
3794HRESULT Unattended::setImageIndex(ULONG index)
3795{
3796 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3797 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3798
3799 /* Validate the selection if detection was done already: */
3800 if (mDetectedImages.size() > 0)
3801 {
3802 for (size_t i = 0; i < mDetectedImages.size(); i++)
3803 if (mDetectedImages[i].mImageIndex == index)
3804 {
3805 midxImage = index;
3806 i_updateDetectedAttributeForImage(mDetectedImages[i]);
3807 return S_OK;
3808 }
3809 LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */
3810 }
3811
3812 midxImage = index;
3813 return S_OK;
3814}
3815
3816HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
3817{
3818 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3819 return mMachine.queryInterfaceTo(aMachine.asOutParam());
3820}
3821
3822HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
3823{
3824 /*
3825 * Lookup the VM so we can safely get the Machine instance.
3826 * (Don't want to test how reliable XPCOM and COM are with finding
3827 * the local object instance when a client passes a stub back.)
3828 */
3829 Bstr bstrUuidMachine;
3830 HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam());
3831 if (SUCCEEDED(hrc))
3832 {
3833 Guid UuidMachine(bstrUuidMachine);
3834 ComObjPtr<Machine> ptrMachine;
3835 hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
3836 if (SUCCEEDED(hrc))
3837 {
3838 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3839 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER,
3840 tr("Cannot change after prepare() has been called")));
3841 mMachine = ptrMachine;
3842 mMachineUuid = UuidMachine;
3843 if (mfIsDefaultAuxiliaryBasePath)
3844 mStrAuxiliaryBasePath.setNull();
3845 hrc = S_OK;
3846 }
3847 }
3848 return hrc;
3849}
3850
3851HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
3852{
3853 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3854 if ( mStrScriptTemplatePath.isNotEmpty()
3855 || mpInstaller == NULL)
3856 aScriptTemplatePath = mStrScriptTemplatePath;
3857 else
3858 aScriptTemplatePath = mpInstaller->getTemplateFilePath();
3859 return S_OK;
3860}
3861
3862HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
3863{
3864 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3865 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3866 mStrScriptTemplatePath = aScriptTemplatePath;
3867 return S_OK;
3868}
3869
3870HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
3871{
3872 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3873 if ( mStrPostInstallScriptTemplatePath.isNotEmpty()
3874 || mpInstaller == NULL)
3875 aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath;
3876 else
3877 aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath();
3878 return S_OK;
3879}
3880
3881HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
3882{
3883 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3884 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3885 mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath;
3886 return S_OK;
3887}
3888
3889HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
3890{
3891 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3892 aPostInstallCommand = mStrPostInstallCommand;
3893 return S_OK;
3894}
3895
3896HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
3897{
3898 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3899 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3900 mStrPostInstallCommand = aPostInstallCommand;
3901 return S_OK;
3902}
3903
3904HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
3905{
3906 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3907 if ( mStrExtraInstallKernelParameters.isNotEmpty()
3908 || mpInstaller == NULL)
3909 aExtraInstallKernelParameters = mStrExtraInstallKernelParameters;
3910 else
3911 aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters();
3912 return S_OK;
3913}
3914
3915HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
3916{
3917 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3918 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3919 mStrExtraInstallKernelParameters = aExtraInstallKernelParameters;
3920 return S_OK;
3921}
3922
3923HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
3924{
3925 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3926 aDetectedOSTypeId = mStrDetectedOSTypeId;
3927 return S_OK;
3928}
3929
3930HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
3931{
3932 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3933 aDetectedOSVersion = mStrDetectedOSVersion;
3934 return S_OK;
3935}
3936
3937HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
3938{
3939 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3940 aDetectedOSFlavor = mStrDetectedOSFlavor;
3941 return S_OK;
3942}
3943
3944HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
3945{
3946 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3947 aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " ");
3948 return S_OK;
3949}
3950
3951HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
3952{
3953 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3954 aDetectedOSHints = mStrDetectedOSHints;
3955 return S_OK;
3956}
3957
3958HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
3959{
3960 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3961 aDetectedImageNames.clear();
3962 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3963 {
3964 Utf8Str strTmp;
3965 aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp));
3966 }
3967 return S_OK;
3968}
3969
3970HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
3971{
3972 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3973 aDetectedImageIndices.clear();
3974 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3975 aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex);
3976 return S_OK;
3977}
3978
3979HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
3980{
3981 /*
3982 * Take the initial position that it's not supported, so we can return
3983 * right away when we decide it's not possible.
3984 */
3985 *aIsUnattendedInstallSupported = false;
3986
3987 /* Unattended is disabled by default if we could not detect OS type. */
3988 if (mStrDetectedOSTypeId.isEmpty())
3989 return S_OK;
3990
3991 /* Note! Includes the OS family and the distro (linux) or (part) of the
3992 major OS version. Use with care. */
3993 const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask);
3994
3995 /* We require a version to have been detected, except for windows where the
3996 field is generally only used for the service pack number at present and
3997 will be empty for RTMs isos. */
3998 if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT
3999 || enmOsTypeMasked >= VBOXOSTYPE_OS2)
4000 && mStrDetectedOSVersion.isEmpty())
4001 return S_OK;
4002
4003 /*
4004 * Sort out things that we know doesn't work. Order by VBOXOSTYPE value.
4005 */
4006
4007 /* We do not support any of the DOS based windows version, nor DOS, in case
4008 any of that gets detected (it shouldn't): */
4009 if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT)
4010 return S_OK;
4011
4012 /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */
4013 if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4)
4014 return S_OK;
4015
4016 /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been
4017 tested, but we'll get to the others eventually): */
4018 if ( enmOsTypeMasked >= VBOXOSTYPE_OS2
4019 && enmOsTypeMasked < VBOXOSTYPE_Linux
4020 && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ )
4021 return S_OK;
4022
4023 /* Old Debians fail since package repos have been move to some other mirror location. */
4024 if ( enmOsTypeMasked == VBOXOSTYPE_Debian
4025 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0)
4026 return S_OK;
4027
4028 /* Skip all OpenSUSE variants for now. */
4029 if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE)
4030 return S_OK;
4031
4032 if (enmOsTypeMasked == VBOXOSTYPE_Ubuntu)
4033 {
4034 /* We cannot install Ubuntus older than 11.04. */
4035 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0)
4036 return S_OK;
4037 /* Lubuntu, starting with 20.04, has switched to calamares, which cannot be automated. */
4038 if ( RTStrIStr(mStrDetectedOSFlavor.c_str(), "lubuntu")
4039 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "20.04") > 0)
4040 return S_OK;
4041 }
4042
4043 /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */
4044 if ( enmOsTypeMasked == VBOXOSTYPE_Oracle
4045 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0)
4046 return S_OK;
4047
4048 /* Fredora ISOs cannot be installed at present. */
4049 if (enmOsTypeMasked == VBOXOSTYPE_FedoraCore)
4050 return S_OK;
4051
4052 /*
4053 * Assume the rest works.
4054 */
4055 *aIsUnattendedInstallSupported = true;
4056 return S_OK;
4057}
4058
4059HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
4060{
4061 *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork;
4062 return S_OK;
4063}
4064
4065HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
4066{
4067 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4068 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4069 mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork);
4070 return S_OK;
4071}
4072
4073/*
4074 * Getters that the installer and script classes can use.
4075 */
4076Utf8Str const &Unattended::i_getIsoPath() const
4077{
4078 Assert(isReadLockedOnCurrentThread());
4079 return mStrIsoPath;
4080}
4081
4082Utf8Str const &Unattended::i_getUser() const
4083{
4084 Assert(isReadLockedOnCurrentThread());
4085 return mStrUser;
4086}
4087
4088Utf8Str const &Unattended::i_getPassword() const
4089{
4090 Assert(isReadLockedOnCurrentThread());
4091 return mStrPassword;
4092}
4093
4094Utf8Str const &Unattended::i_getFullUserName() const
4095{
4096 Assert(isReadLockedOnCurrentThread());
4097 return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
4098}
4099
4100Utf8Str const &Unattended::i_getProductKey() const
4101{
4102 Assert(isReadLockedOnCurrentThread());
4103 return mStrProductKey;
4104}
4105
4106Utf8Str const &Unattended::i_getProxy() const
4107{
4108 Assert(isReadLockedOnCurrentThread());
4109 return mStrProxy;
4110}
4111
4112Utf8Str const &Unattended::i_getAdditionsIsoPath() const
4113{
4114 Assert(isReadLockedOnCurrentThread());
4115 return mStrAdditionsIsoPath;
4116}
4117
4118bool Unattended::i_getInstallGuestAdditions() const
4119{
4120 Assert(isReadLockedOnCurrentThread());
4121 return mfInstallGuestAdditions;
4122}
4123
4124Utf8Str const &Unattended::i_getValidationKitIsoPath() const
4125{
4126 Assert(isReadLockedOnCurrentThread());
4127 return mStrValidationKitIsoPath;
4128}
4129
4130bool Unattended::i_getInstallTestExecService() const
4131{
4132 Assert(isReadLockedOnCurrentThread());
4133 return mfInstallTestExecService;
4134}
4135
4136Utf8Str const &Unattended::i_getUserPayloadIsoPath() const
4137{
4138 Assert(isReadLockedOnCurrentThread());
4139 return mStrUserPayloadIsoPath;
4140}
4141
4142bool Unattended::i_getInstallUserPayload() const
4143{
4144 Assert(isReadLockedOnCurrentThread());
4145 return mfInstallUserPayload;
4146}
4147
4148Utf8Str const &Unattended::i_getTimeZone() const
4149{
4150 Assert(isReadLockedOnCurrentThread());
4151 return mStrTimeZone;
4152}
4153
4154PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
4155{
4156 Assert(isReadLockedOnCurrentThread());
4157 return mpTimeZoneInfo;
4158}
4159
4160Utf8Str const &Unattended::i_getLocale() const
4161{
4162 Assert(isReadLockedOnCurrentThread());
4163 return mStrLocale;
4164}
4165
4166Utf8Str const &Unattended::i_getLanguage() const
4167{
4168 Assert(isReadLockedOnCurrentThread());
4169 return mStrLanguage;
4170}
4171
4172Utf8Str const &Unattended::i_getCountry() const
4173{
4174 Assert(isReadLockedOnCurrentThread());
4175 return mStrCountry;
4176}
4177
4178bool Unattended::i_isMinimalInstallation() const
4179{
4180 size_t i = mPackageSelectionAdjustments.size();
4181 while (i-- > 0)
4182 if (mPackageSelectionAdjustments[i].equals("minimal"))
4183 return true;
4184 return false;
4185}
4186
4187Utf8Str const &Unattended::i_getHostname() const
4188{
4189 Assert(isReadLockedOnCurrentThread());
4190 return mStrHostname;
4191}
4192
4193Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
4194{
4195 Assert(isReadLockedOnCurrentThread());
4196 return mStrAuxiliaryBasePath;
4197}
4198
4199ULONG Unattended::i_getImageIndex() const
4200{
4201 Assert(isReadLockedOnCurrentThread());
4202 return midxImage;
4203}
4204
4205Utf8Str const &Unattended::i_getScriptTemplatePath() const
4206{
4207 Assert(isReadLockedOnCurrentThread());
4208 return mStrScriptTemplatePath;
4209}
4210
4211Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
4212{
4213 Assert(isReadLockedOnCurrentThread());
4214 return mStrPostInstallScriptTemplatePath;
4215}
4216
4217Utf8Str const &Unattended::i_getPostInstallCommand() const
4218{
4219 Assert(isReadLockedOnCurrentThread());
4220 return mStrPostInstallCommand;
4221}
4222
4223Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
4224{
4225 Assert(isReadLockedOnCurrentThread());
4226 /* Only the installer knows, forward the call. */
4227 AssertReturn(mpInstaller != NULL, Utf8Str::Empty);
4228 return mpInstaller->getAuxiliaryInstallDir();
4229}
4230
4231Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
4232{
4233 Assert(isReadLockedOnCurrentThread());
4234 return mStrExtraInstallKernelParameters;
4235}
4236
4237Utf8Str const &Unattended::i_getAdditionsInstallPackage() const
4238{
4239 Assert(isReadLockedOnCurrentThread());
4240 return mStrAdditionsInstallPackage;
4241}
4242
4243bool Unattended::i_isRtcUsingUtc() const
4244{
4245 Assert(isReadLockedOnCurrentThread());
4246 return mfRtcUseUtc;
4247}
4248
4249bool Unattended::i_isGuestOs64Bit() const
4250{
4251 Assert(isReadLockedOnCurrentThread());
4252 return mfGuestOs64Bit;
4253}
4254
4255bool Unattended::i_isFirmwareEFI() const
4256{
4257 Assert(isReadLockedOnCurrentThread());
4258 return menmFirmwareType != FirmwareType_BIOS;
4259}
4260
4261Utf8Str const &Unattended::i_getDetectedOSVersion()
4262{
4263 Assert(isReadLockedOnCurrentThread());
4264 return mStrDetectedOSVersion;
4265}
4266
4267bool Unattended::i_getAvoidUpdatesOverNetwork() const
4268{
4269 Assert(isReadLockedOnCurrentThread());
4270 return mfAvoidUpdatesOverNetwork;
4271}
4272
4273HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
4274 AutoMultiWriteLock2 &rLock)
4275{
4276 /*
4277 * Attach the disk image
4278 * HACK ALERT! Temporarily release the Unattended lock.
4279 */
4280 rLock.release();
4281
4282 ComPtr<IMedium> ptrMedium;
4283 HRESULT hrc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(),
4284 pImage->enmDeviceType,
4285 pImage->enmAccessType,
4286 true,
4287 ptrMedium.asOutParam());
4288 LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", hrc));
4289 if (SUCCEEDED(hrc))
4290 {
4291 if (pImage->fAuxiliary && pImage->strImagePath.endsWith(".viso"))
4292 {
4293 hrc = ptrMedium->SetProperty(Bstr("UnattendedInstall").raw(), Bstr("1").raw());
4294 LogRelFlowFunc(("Medium::SetProperty -> %Rhrc\n", hrc));
4295 }
4296 if (pImage->fMountOnly)
4297 {
4298 // mount the opened disk image
4299 hrc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4300 pImage->iDevice, ptrMedium, TRUE /*fForce*/);
4301 LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", hrc));
4302 }
4303 else
4304 {
4305 //attach the opened disk image to the controller
4306 hrc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4307 pImage->iDevice, pImage->enmDeviceType, ptrMedium);
4308 LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", hrc));
4309 }
4310 }
4311
4312 rLock.acquire();
4313 return hrc;
4314}
4315
4316bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId)
4317{
4318 ComPtr<IGuestOSType> pGuestOSType;
4319 HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam());
4320 if (SUCCEEDED(hrc))
4321 {
4322 BOOL fIs64Bit = FALSE;
4323 if (!pGuestOSType.isNull())
4324 hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit);
4325 if (SUCCEEDED(hrc))
4326 return fIs64Bit != FALSE;
4327 }
4328 return false;
4329}
4330
4331
4332bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage)
4333{
4334 bool fRet = true;
4335
4336 /*
4337 * If the image doesn't have a valid value, we don't change it.
4338 * This is obviously a little bit bogus, but what can we do...
4339 */
4340 const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType);
4341 if (pszOSTypeId && !RTStrStartsWith(pszOSTypeId, GUEST_OS_ID_STR_PARTIAL("Other"))) /** @todo set x64/a64 other variants or not? */
4342 mStrDetectedOSTypeId = pszOSTypeId;
4343 else
4344 fRet = false;
4345
4346 if (rImage.mVersion.isNotEmpty())
4347 mStrDetectedOSVersion = rImage.mVersion;
4348 else
4349 fRet = false;
4350
4351 if (rImage.mFlavor.isNotEmpty())
4352 mStrDetectedOSFlavor = rImage.mFlavor;
4353 else
4354 fRet = false;
4355
4356 if (rImage.mLanguages.size() > 0)
4357 mDetectedOSLanguages = rImage.mLanguages;
4358 else
4359 fRet = false;
4360
4361 mEnmOsType = rImage.mEnmOsType;
4362
4363 return fRet;
4364}
Note: See TracBrowser for help on using the repository browser.

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