VirtualBox

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

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

Unattended: bugref:9781. Remove the default user password.

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