VirtualBox

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

Last change on this file was 108722, checked in by vboxsync, 3 weeks ago

Unattended. Remove a forgotten include.

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

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