VirtualBox

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

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

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

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

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