VirtualBox

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

Last change on this file since 102048 was 101683, checked in by vboxsync, 19 months ago

Main/Unattended|GuestOSType: Add an entry in the guest OS type to indicate the name of the additions install package instead of hardcoding it in the templates. Allows easy adaption if something changes and makes it possible to auto install guest additions for linux.arm64 guests, bugref:10542

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

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