VirtualBox

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

Last change on this file since 105917 was 105917, checked in by vboxsync, 3 months ago

Unattended: ​bugref:10511, ​bugref:10384. Check if the detected guest OS type is supported by the host system.

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

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