VirtualBox

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

最後變更 在這個檔案從108422是 108355,由 vboxsync 提交於 3 週 前

Unattended: bugref:10864. For Windows11 the product key is required.

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

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