VirtualBox

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

Last change on this file was 106062, checked in by vboxsync, 8 weeks ago

Unattended: ​bugref:10511. Avoid tripping lock order assert when Unattended::prepare is called.

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