VirtualBox

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

Last change on this file since 101381 was 101381, checked in by vboxsync, 8 months ago

Main: More guest OS id marking. bugref:10384

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 170.1 KB
Line 
1/* $Id: UnattendedImpl.cpp 101381 2023-10-06 10:00:59Z vboxsync $ */
2/** @file
3 * Unattended class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
33#include "LoggingNew.h"
34#include "VirtualBoxBase.h"
35#include "UnattendedImpl.h"
36#include "UnattendedInstaller.h"
37#include "UnattendedScript.h"
38#include "VirtualBoxImpl.h"
39#include "SystemPropertiesImpl.h"
40#include "MachineImpl.h"
41#include "Global.h"
42#include "StringifyEnums.h"
43
44#include <VBox/err.h>
45#include <iprt/cpp/xml.h>
46#include <iprt/ctype.h>
47#include <iprt/file.h>
48#ifndef RT_OS_WINDOWS
49# include <iprt/formats/mz.h>
50# include <iprt/formats/pecoff.h>
51#endif
52#include <iprt/formats/wim.h>
53#include <iprt/fsvfs.h>
54#include <iprt/inifile.h>
55#include <iprt/locale.h>
56#include <iprt/path.h>
57#include <iprt/vfs.h>
58
59using namespace std;
60
61
62/*********************************************************************************************************************************
63* Structures and Typedefs *
64*********************************************************************************************************************************/
65/**
66 * Controller slot for a DVD drive.
67 *
68 * The slot can be free and needing a drive to be attached along with the ISO
69 * image, or it may already be there and only need mounting the ISO. The
70 * ControllerSlot::fFree member indicates which it is.
71 */
72struct ControllerSlot
73{
74 StorageBus_T enmBus;
75 Utf8Str strControllerName;
76 LONG iPort;
77 LONG iDevice;
78 bool fFree;
79
80 ControllerSlot(StorageBus_T a_enmBus, const Utf8Str &a_rName, LONG a_iPort, LONG a_iDevice, bool a_fFree)
81 : enmBus(a_enmBus), strControllerName(a_rName), iPort(a_iPort), iDevice(a_iDevice), fFree(a_fFree)
82 {}
83
84 bool operator<(const ControllerSlot &rThat) const
85 {
86 if (enmBus == rThat.enmBus)
87 {
88 if (strControllerName == rThat.strControllerName)
89 {
90 if (iPort == rThat.iPort)
91 return iDevice < rThat.iDevice;
92 return iPort < rThat.iPort;
93 }
94 return strControllerName < rThat.strControllerName;
95 }
96
97 /*
98 * Bus comparsion in boot priority order.
99 */
100 /* IDE first. */
101 if (enmBus == StorageBus_IDE)
102 return true;
103 if (rThat.enmBus == StorageBus_IDE)
104 return false;
105 /* SATA next */
106 if (enmBus == StorageBus_SATA)
107 return true;
108 if (rThat.enmBus == StorageBus_SATA)
109 return false;
110 /* SCSI next */
111 if (enmBus == StorageBus_SCSI)
112 return true;
113 if (rThat.enmBus == StorageBus_SCSI)
114 return false;
115 /* numerical */
116 return (int)enmBus < (int)rThat.enmBus;
117 }
118
119 bool operator==(const ControllerSlot &rThat) const
120 {
121 return enmBus == rThat.enmBus
122 && strControllerName == rThat.strControllerName
123 && iPort == rThat.iPort
124 && iDevice == rThat.iDevice;
125 }
126};
127
128/**
129 * Installation disk.
130 *
131 * Used when reconfiguring the VM.
132 */
133typedef struct UnattendedInstallationDisk
134{
135 StorageBus_T enmBusType; /**< @todo nobody is using this... */
136 Utf8Str strControllerName;
137 DeviceType_T enmDeviceType;
138 AccessMode_T enmAccessType;
139 LONG iPort;
140 LONG iDevice;
141 bool fMountOnly;
142 Utf8Str strImagePath;
143 bool fAuxiliary;
144
145 UnattendedInstallationDisk(StorageBus_T a_enmBusType, Utf8Str const &a_rBusName, DeviceType_T a_enmDeviceType,
146 AccessMode_T a_enmAccessType, LONG a_iPort, LONG a_iDevice, bool a_fMountOnly,
147 Utf8Str const &a_rImagePath, bool a_fAuxiliary)
148 : enmBusType(a_enmBusType), strControllerName(a_rBusName), enmDeviceType(a_enmDeviceType), enmAccessType(a_enmAccessType)
149 , iPort(a_iPort), iDevice(a_iDevice), fMountOnly(a_fMountOnly), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
150 {
151 Assert(strControllerName.length() > 0);
152 }
153
154 UnattendedInstallationDisk(std::list<ControllerSlot>::const_iterator const &itDvdSlot, Utf8Str const &a_rImagePath,
155 bool a_fAuxiliary)
156 : enmBusType(itDvdSlot->enmBus), strControllerName(itDvdSlot->strControllerName), enmDeviceType(DeviceType_DVD)
157 , enmAccessType(AccessMode_ReadOnly), iPort(itDvdSlot->iPort), iDevice(itDvdSlot->iDevice)
158 , fMountOnly(!itDvdSlot->fFree), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
159 {
160 Assert(strControllerName.length() > 0);
161 }
162} UnattendedInstallationDisk;
163
164
165/**
166 * OS/2 syslevel file header.
167 */
168#pragma pack(1)
169typedef struct OS2SYSLEVELHDR
170{
171 uint16_t uMinusOne; /**< 0x00: UINT16_MAX */
172 char achSignature[8]; /**< 0x02: "SYSLEVEL" */
173 uint8_t abReserved1[5]; /**< 0x0a: Usually zero. Ignore. */
174 uint16_t uSyslevelFileVer; /**< 0x0f: The syslevel file version: 1. */
175 uint8_t abReserved2[16]; /**< 0x11: Zero. Ignore. */
176 uint32_t offTable; /**< 0x21: Offset of the syslevel table. */
177} OS2SYSLEVELHDR;
178#pragma pack()
179AssertCompileSize(OS2SYSLEVELHDR, 0x25);
180
181/**
182 * OS/2 syslevel table entry.
183 */
184#pragma pack(1)
185typedef struct OS2SYSLEVELENTRY
186{
187 uint16_t id; /**< 0x00: ? */
188 uint8_t bEdition; /**< 0x02: The OS/2 edition: 0=standard, 1=extended, x=component defined */
189 uint8_t bVersion; /**< 0x03: 0x45 = 4.5 */
190 uint8_t bModify; /**< 0x04: Lower nibble is added to bVersion, so 0x45 0x02 => 4.52 */
191 uint8_t abReserved1[2]; /**< 0x05: Zero. Ignore. */
192 char achCsdLevel[8]; /**< 0x07: The current CSD level. */
193 char achCsdPrior[8]; /**< 0x0f: The prior CSD level. */
194 char szName[80]; /**< 0x5f: System/component name. */
195 char achId[9]; /**< 0x67: System/component ID. */
196 uint8_t bRefresh; /**< 0x70: Single digit refresh version, ignored if zero. */
197 char szType[9]; /**< 0x71: Some kind of type string. Optional */
198 uint8_t abReserved2[6]; /**< 0x7a: Zero. Ignore. */
199} OS2SYSLEVELENTRY;
200#pragma pack()
201AssertCompileSize(OS2SYSLEVELENTRY, 0x80);
202
203
204
205/**
206 * Concatenate image name and version strings and return.
207 *
208 * A possible output would be "Windows 10 Home (10.0.19041.330 / x64)".
209 *
210 * @returns Name string to use.
211 * @param r_strName String object that can be formatted into and returned.
212 */
213const Utf8Str &WIMImage::formatName(Utf8Str &r_strName) const
214{
215 /* We skip the mFlavor as it's typically part of the description already. */
216
217 if (mVersion.isEmpty() && mArch.isEmpty() && mDefaultLanguage.isEmpty() && mLanguages.size() == 0)
218 return mName;
219
220 r_strName = mName;
221 bool fFirst = true;
222 if (mVersion.isNotEmpty())
223 {
224 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mVersion.c_str());
225 fFirst = false;
226 }
227 if (mArch.isNotEmpty())
228 {
229 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mArch.c_str());
230 fFirst = false;
231 }
232 if (mDefaultLanguage.isNotEmpty())
233 {
234 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mDefaultLanguage.c_str());
235 fFirst = false;
236 }
237 else
238 for (size_t i = 0; i < mLanguages.size(); i++)
239 {
240 r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mLanguages[i].c_str());
241 fFirst = false;
242 }
243 r_strName.append(")");
244 return r_strName;
245}
246
247
248//////////////////////////////////////////////////////////////////////////////////////////////////////
249/*
250*
251*
252* Implementation Unattended functions
253*
254*/
255//////////////////////////////////////////////////////////////////////////////////////////////////////
256
257Unattended::Unattended()
258 : mhThreadReconfigureVM(NIL_RTNATIVETHREAD), mfRtcUseUtc(false), mfGuestOs64Bit(false)
259 , mpInstaller(NULL), mpTimeZoneInfo(NULL), mfIsDefaultAuxiliaryBasePath(true), mfDoneDetectIsoOS(false)
260 , mfAvoidUpdatesOverNetwork(false)
261{ }
262
263Unattended::~Unattended()
264{
265 if (mpInstaller)
266 {
267 delete mpInstaller;
268 mpInstaller = NULL;
269 }
270}
271
272HRESULT Unattended::FinalConstruct()
273{
274 return BaseFinalConstruct();
275}
276
277void Unattended::FinalRelease()
278{
279 uninit();
280
281 BaseFinalRelease();
282}
283
284void Unattended::uninit()
285{
286 /* Enclose the state transition Ready->InUninit->NotReady */
287 AutoUninitSpan autoUninitSpan(this);
288 if (autoUninitSpan.uninitDone())
289 return;
290
291 unconst(mParent) = NULL;
292 mMachine.setNull();
293}
294
295/**
296 * Initializes the unattended object.
297 *
298 * @param aParent Pointer to the parent object.
299 */
300HRESULT Unattended::initUnattended(VirtualBox *aParent)
301{
302 LogFlowThisFunc(("aParent=%p\n", aParent));
303 ComAssertRet(aParent, E_INVALIDARG);
304
305 /* Enclose the state transition NotReady->InInit->Ready */
306 AutoInitSpan autoInitSpan(this);
307 AssertReturn(autoInitSpan.isOk(), E_FAIL);
308
309 unconst(mParent) = aParent;
310
311 /*
312 * Fill public attributes (IUnattended) with useful defaults.
313 */
314 try
315 {
316 mStrUser = "vboxuser";
317 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
1313 { RT_STR_TUPLE("arm32"), VBOXOSTYPE_arm32 },
1314 { RT_STR_TUPLE("arm-32"), VBOXOSTYPE_arm32 },
1315 { RT_STR_TUPLE("arm_32"), VBOXOSTYPE_arm32 },
1316 { RT_STR_TUPLE("armel"), VBOXOSTYPE_arm32 }, // mostly Debians
1317
1318 { RT_STR_TUPLE("x86"), VBOXOSTYPE_x86 },
1319 { RT_STR_TUPLE("i386"), VBOXOSTYPE_x86 },
1320 { RT_STR_TUPLE("i486"), VBOXOSTYPE_x86 },
1321 { RT_STR_TUPLE("i586"), VBOXOSTYPE_x86 },
1322 { RT_STR_TUPLE("i686"), VBOXOSTYPE_x86 },
1323 { RT_STR_TUPLE("i786"), VBOXOSTYPE_x86 },
1324 { RT_STR_TUPLE("i886"), VBOXOSTYPE_x86 },
1325 { RT_STR_TUPLE("i986"), VBOXOSTYPE_x86 },
1326};
1327
1328/**
1329 * Detects linux architecture.
1330 *
1331 * @returns true if detected, false if not.
1332 * @param pszArch The architecture string.
1333 * @param penmOsType Where to return the arch and type on success.
1334 * @param enmBaseOsType The base (x86) OS type to return.
1335 */
1336static bool detectLinuxArch(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType)
1337{
1338 for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++)
1339 if (RTStrNICmp(pszArch, g_aLinuxArches[i].pszArch, g_aLinuxArches[i].cchArch) == 0)
1340 {
1341 *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch);
1342 return true;
1343 }
1344 /** @todo check for 'noarch' since source CDs have been seen to use that. */
1345 return false;
1346}
1347
1348/**
1349 * Detects linux architecture by searching for the architecture substring in @p pszArch.
1350 *
1351 * @returns true if detected, false if not.
1352 * @param pszArch The architecture string.
1353 * @param penmOsType Where to return the arch and type on success.
1354 * @param enmBaseOsType The base (x86) OS type to return.
1355 * @param ppszHit Where to return the pointer to the architecture
1356 * specifier. Optional.
1357 * @param ppszNext Where to return the pointer to the char
1358 * following the architecuture specifier. Optional.
1359 */
1360static bool detectLinuxArchII(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType,
1361 char **ppszHit = NULL, char **ppszNext = NULL)
1362{
1363 for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++)
1364 {
1365 const char *pszHit = RTStrIStr(pszArch, g_aLinuxArches[i].pszArch);
1366 if (pszHit != NULL)
1367 {
1368 if (ppszHit)
1369 *ppszHit = (char *)pszHit;
1370 if (ppszNext)
1371 *ppszNext = (char *)pszHit + g_aLinuxArches[i].cchArch;
1372 *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch);
1373 return true;
1374 }
1375 }
1376 return false;
1377}
1378
1379static bool detectLinuxDistroName(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext)
1380{
1381 bool fRet = true;
1382
1383 if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Red")) == 0
1384 && !RT_C_IS_ALNUM(pszOsAndVersion[3]))
1385
1386 {
1387 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3);
1388 if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Hat")) == 0
1389 && !RT_C_IS_ALNUM(pszOsAndVersion[3]))
1390 {
1391 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1392 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3);
1393 }
1394 else
1395 fRet = false;
1396 }
1397 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("OpenSUSE")) == 0
1398 && !RT_C_IS_ALNUM(pszOsAndVersion[8]))
1399 {
1400 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_OpenSUSE);
1401 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 8);
1402 }
1403 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Oracle")) == 0
1404 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1405 {
1406 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle);
1407 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1408 }
1409 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("CentOS")) == 0
1410 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1411 {
1412 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1413 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1414 }
1415 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Fedora")) == 0
1416 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1417 {
1418 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore);
1419 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1420 }
1421 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Ubuntu")) == 0
1422 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1423 {
1424 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1425 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1426 }
1427 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Linux Mint")) == 0
1428 && !RT_C_IS_ALNUM(pszOsAndVersion[10]))
1429 {
1430 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1431 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 10);
1432 }
1433 else if ( ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Xubuntu")) == 0
1434 || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Kubuntu")) == 0
1435 || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Lubuntu")) == 0)
1436 && !RT_C_IS_ALNUM(pszOsAndVersion[7]))
1437 {
1438 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1439 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 7);
1440 }
1441 else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Debian")) == 0
1442 && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
1443 {
1444 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian);
1445 pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
1446 }
1447 else
1448 fRet = false;
1449
1450 /*
1451 * Skip forward till we get a number.
1452 */
1453 if (ppszNext)
1454 {
1455 *ppszNext = pszOsAndVersion;
1456 char ch;
1457 for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
1458 if (RT_C_IS_DIGIT(ch))
1459 {
1460 *ppszNext = pszVersion;
1461 break;
1462 }
1463 }
1464 return fRet;
1465}
1466
1467static bool detectLinuxDistroNameII(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext)
1468{
1469 bool fRet = true;
1470 if ( RTStrIStr(pszOsAndVersion, "RedHat") != NULL
1471 || RTStrIStr(pszOsAndVersion, "Red Hat") != NULL)
1472 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1473 else if (RTStrIStr(pszOsAndVersion, "Oracle") != NULL)
1474 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle);
1475 else if (RTStrIStr(pszOsAndVersion, "CentOS") != NULL)
1476 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1477 else if (RTStrIStr(pszOsAndVersion, "Fedora") != NULL)
1478 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore);
1479 else if (RTStrIStr(pszOsAndVersion, "Ubuntu") != NULL)
1480 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1481 else if (RTStrIStr(pszOsAndVersion, "Mint") != NULL)
1482 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
1483 else if (RTStrIStr(pszOsAndVersion, "Debian"))
1484 *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian);
1485 else
1486 fRet = false;
1487
1488 /*
1489 * Skip forward till we get a number.
1490 */
1491 if (ppszNext)
1492 {
1493 *ppszNext = pszOsAndVersion;
1494 char ch;
1495 for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
1496 if (RT_C_IS_DIGIT(ch))
1497 {
1498 *ppszNext = pszVersion;
1499 break;
1500 }
1501 }
1502 return fRet;
1503}
1504
1505
1506/**
1507 * Helps detecting linux distro flavor by finding substring position of non numerical
1508 * part of the disk name.
1509 *
1510 * @returns true if detected, false if not.
1511 * @param pszDiskName Name of the disk as it is read from .disk/info or
1512 * README.diskdefines file.
1513 * @param poffVersion String position where first numerical character is
1514 * found. We use substring upto this position as OS flavor
1515 */
1516static bool detectLinuxDistroFlavor(const char *pszDiskName, size_t *poffVersion)
1517{
1518 Assert(poffVersion);
1519 if (!pszDiskName)
1520 return false;
1521 char ch;
1522 while ((ch = *pszDiskName) != '\0' && !RT_C_IS_DIGIT(ch))
1523 {
1524 ++pszDiskName;
1525 *poffVersion += 1;
1526 }
1527 return true;
1528}
1529
1530/**
1531 * Detect Linux distro ISOs.
1532 *
1533 * @returns COM status code.
1534 * @retval S_OK if detected
1535 * @retval S_FALSE if not fully detected.
1536 *
1537 * @param hVfsIso The ISO file system.
1538 * @param pBuf Read buffer.
1539 */
1540HRESULT Unattended::i_innerDetectIsoOSLinux(RTVFS hVfsIso, DETECTBUFFER *pBuf)
1541{
1542 /*
1543 * Redhat and derivatives may have a .treeinfo (ini-file style) with useful info
1544 * or at least a barebone .discinfo file.
1545 */
1546
1547 /*
1548 * Start with .treeinfo: https://release-engineering.github.io/productmd/treeinfo-1.0.html
1549 */
1550 RTVFSFILE hVfsFile;
1551 int vrc = RTVfsFileOpen(hVfsIso, ".treeinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1552 if (RT_SUCCESS(vrc))
1553 {
1554 RTINIFILE hIniFile;
1555 vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1556 RTVfsFileRelease(hVfsFile);
1557 if (RT_SUCCESS(vrc))
1558 {
1559 /* Try figure the architecture first (like with windows). */
1560 vrc = RTIniFileQueryValue(hIniFile, "tree", "arch", pBuf->sz, sizeof(*pBuf), NULL);
1561 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1562 vrc = RTIniFileQueryValue(hIniFile, "general", "arch", pBuf->sz, sizeof(*pBuf), NULL);
1563 if (RT_FAILURE(vrc))
1564 LogRel(("Unattended: .treeinfo: No 'arch' property.\n"));
1565 else
1566 {
1567 LogRelFlow(("Unattended: .treeinfo: arch=%s\n", pBuf->sz));
1568 if (detectLinuxArch(pBuf->sz, &mEnmOsType, VBOXOSTYPE_RedHat))
1569 {
1570 /* Try figure the release name, it doesn't have to be redhat. */
1571 vrc = RTIniFileQueryValue(hIniFile, "release", "name", pBuf->sz, sizeof(*pBuf), NULL);
1572 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1573 vrc = RTIniFileQueryValue(hIniFile, "product", "name", pBuf->sz, sizeof(*pBuf), NULL);
1574 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1575 vrc = RTIniFileQueryValue(hIniFile, "general", "family", pBuf->sz, sizeof(*pBuf), NULL);
1576 if (RT_SUCCESS(vrc))
1577 {
1578 LogRelFlow(("Unattended: .treeinfo: name/family=%s\n", pBuf->sz));
1579 if (!detectLinuxDistroName(pBuf->sz, &mEnmOsType, NULL))
1580 {
1581 LogRel(("Unattended: .treeinfo: Unknown: name/family='%s', assuming Red Hat\n", pBuf->sz));
1582 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
1583 }
1584 }
1585
1586 /* Try figure the version. */
1587 vrc = RTIniFileQueryValue(hIniFile, "release", "version", pBuf->sz, sizeof(*pBuf), NULL);
1588 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1589 vrc = RTIniFileQueryValue(hIniFile, "product", "version", pBuf->sz, sizeof(*pBuf), NULL);
1590 if (RT_FAILURE(vrc) || !pBuf->sz[0])
1591 vrc = RTIniFileQueryValue(hIniFile, "general", "version", pBuf->sz, sizeof(*pBuf), NULL);
1592 if (RT_SUCCESS(vrc))
1593 {
1594 LogRelFlow(("Unattended: .treeinfo: version=%s\n", pBuf->sz));
1595 try { mStrDetectedOSVersion = RTStrStrip(pBuf->sz); }
1596 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1597
1598 size_t cchVersionPosition = 0;
1599 if (detectLinuxDistroFlavor(pBuf->sz, &cchVersionPosition))
1600 {
1601 try { mStrDetectedOSFlavor = Utf8Str(pBuf->sz, cchVersionPosition); }
1602 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1603 }
1604 }
1605 }
1606 else
1607 LogRel(("Unattended: .treeinfo: Unknown: arch='%s'\n", pBuf->sz));
1608 }
1609
1610 RTIniFileRelease(hIniFile);
1611 }
1612
1613 if (mEnmOsType != VBOXOSTYPE_Unknown)
1614 return S_FALSE;
1615 }
1616
1617 /*
1618 * Try .discinfo next: https://release-engineering.github.io/productmd/discinfo-1.0.html
1619 * We will probably need additional info here...
1620 */
1621 vrc = RTVfsFileOpen(hVfsIso, ".discinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1622 if (RT_SUCCESS(vrc))
1623 {
1624 size_t cchIgn;
1625 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1626 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1627 RTVfsFileRelease(hVfsFile);
1628
1629 /* Parse and strip the first 5 lines. */
1630 const char *apszLines[5];
1631 char *psz = pBuf->sz;
1632 for (unsigned i = 0; i < RT_ELEMENTS(apszLines); i++)
1633 {
1634 apszLines[i] = psz;
1635 if (*psz)
1636 {
1637 char *pszEol = (char *)strchr(psz, '\n');
1638 if (!pszEol)
1639 psz = strchr(psz, '\0');
1640 else
1641 {
1642 *pszEol = '\0';
1643 apszLines[i] = RTStrStrip(psz);
1644 psz = pszEol + 1;
1645 }
1646 }
1647 }
1648
1649 /* Do we recognize the architecture? */
1650 LogRelFlow(("Unattended: .discinfo: arch=%s\n", apszLines[2]));
1651 if (detectLinuxArch(apszLines[2], &mEnmOsType, VBOXOSTYPE_RedHat))
1652 {
1653 /* Do we recognize the release string? */
1654 LogRelFlow(("Unattended: .discinfo: product+version=%s\n", apszLines[1]));
1655 const char *pszVersion = NULL;
1656 if (!detectLinuxDistroName(apszLines[1], &mEnmOsType, &pszVersion))
1657 LogRel(("Unattended: .discinfo: Unknown: release='%s'\n", apszLines[1]));
1658
1659 if (*pszVersion)
1660 {
1661 LogRelFlow(("Unattended: .discinfo: version=%s\n", pszVersion));
1662 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1663 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1664
1665 /* CentOS likes to call their release 'Final' without mentioning the actual version
1666 number (e.g. CentOS-4.7-x86_64-binDVD.iso), so we need to go look elsewhere.
1667 This is only important for centos 4.x and 3.x releases. */
1668 if (RTStrNICmp(pszVersion, RT_STR_TUPLE("Final")) == 0)
1669 {
1670 static const char * const s_apszDirs[] = { "CentOS/RPMS/", "RedHat/RPMS", "Server", "Workstation" };
1671 for (unsigned iDir = 0; iDir < RT_ELEMENTS(s_apszDirs); iDir++)
1672 {
1673 RTVFSDIR hVfsDir;
1674 vrc = RTVfsDirOpen(hVfsIso, s_apszDirs[iDir], 0, &hVfsDir);
1675 if (RT_FAILURE(vrc))
1676 continue;
1677 char szRpmDb[128];
1678 char szReleaseRpm[128];
1679 szRpmDb[0] = '\0';
1680 szReleaseRpm[0] = '\0';
1681 for (;;)
1682 {
1683 RTDIRENTRYEX DirEntry;
1684 size_t cbDirEntry = sizeof(DirEntry);
1685 vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING);
1686 if (RT_FAILURE(vrc))
1687 break;
1688
1689 /* redhat-release-4WS-2.4.i386.rpm
1690 centos-release-4-7.x86_64.rpm, centos-release-4-4.3.i386.rpm
1691 centos-release-5-3.el5.centos.1.x86_64.rpm */
1692 if ( (psz = strstr(DirEntry.szName, "-release-")) != NULL
1693 || (psz = strstr(DirEntry.szName, "-RELEASE-")) != NULL)
1694 {
1695 psz += 9;
1696 if (RT_C_IS_DIGIT(*psz))
1697 RTStrCopy(szReleaseRpm, sizeof(szReleaseRpm), psz);
1698 }
1699 /* rpmdb-redhat-4WS-2.4.i386.rpm,
1700 rpmdb-CentOS-4.5-0.20070506.i386.rpm,
1701 rpmdb-redhat-3.9-0.20070703.i386.rpm. */
1702 else if ( ( RTStrStartsWith(DirEntry.szName, "rpmdb-")
1703 || RTStrStartsWith(DirEntry.szName, "RPMDB-"))
1704 && RT_C_IS_DIGIT(DirEntry.szName[6]) )
1705 RTStrCopy(szRpmDb, sizeof(szRpmDb), &DirEntry.szName[6]);
1706 }
1707 RTVfsDirRelease(hVfsDir);
1708
1709 /* Did we find anything relvant? */
1710 psz = szRpmDb;
1711 if (!RT_C_IS_DIGIT(*psz))
1712 psz = szReleaseRpm;
1713 if (RT_C_IS_DIGIT(*psz))
1714 {
1715 /* Convert '-' to '.' and strip stuff which doesn't look like a version string. */
1716 char *pszCur = psz + 1;
1717 for (char ch = *pszCur; ch != '\0'; ch = *++pszCur)
1718 if (ch == '-')
1719 *pszCur = '.';
1720 else if (ch != '.' && !RT_C_IS_DIGIT(ch))
1721 {
1722 *pszCur = '\0';
1723 break;
1724 }
1725 while (&pszCur[-1] != psz && pszCur[-1] == '.')
1726 *--pszCur = '\0';
1727
1728 /* Set it and stop looking. */
1729 try { mStrDetectedOSVersion = psz; }
1730 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1731 break;
1732 }
1733 }
1734 }
1735 }
1736 size_t cchVersionPosition = 0;
1737 if (detectLinuxDistroFlavor(apszLines[1], &cchVersionPosition))
1738 {
1739 try { mStrDetectedOSFlavor = Utf8Str(apszLines[1], cchVersionPosition); }
1740 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1741 }
1742 }
1743 else
1744 LogRel(("Unattended: .discinfo: Unknown: arch='%s'\n", apszLines[2]));
1745
1746 if (mEnmOsType != VBOXOSTYPE_Unknown)
1747 return S_FALSE;
1748 }
1749
1750 /*
1751 * Ubuntu has a README.diskdefines file on their ISO (already on 4.10 / warty warthog).
1752 * Example content:
1753 * #define DISKNAME Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1
1754 * #define TYPE binary
1755 * #define TYPEbinary 1
1756 * #define ARCH amd64
1757 * #define ARCHamd64 1
1758 * #define DISKNUM 1
1759 * #define DISKNUM1 1
1760 * #define TOTALNUM 1
1761 * #define TOTALNUM1 1
1762 */
1763 vrc = RTVfsFileOpen(hVfsIso, "README.diskdefines", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1764 if (RT_SUCCESS(vrc))
1765 {
1766 size_t cchIgn;
1767 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1768 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1769 RTVfsFileRelease(hVfsFile);
1770
1771 /* Find the DISKNAME and ARCH defines. */
1772 const char *pszDiskName = NULL;
1773 const char *pszArch = NULL;
1774 char *psz = pBuf->sz;
1775 while (*psz != '\0')
1776 {
1777 while (RT_C_IS_BLANK(*psz))
1778 psz++;
1779
1780 /* Match #define: */
1781 static const char s_szDefine[] = "#define";
1782 if ( strncmp(psz, s_szDefine, sizeof(s_szDefine) - 1) == 0
1783 && RT_C_IS_BLANK(psz[sizeof(s_szDefine) - 1]))
1784 {
1785 psz = &psz[sizeof(s_szDefine) - 1];
1786 while (RT_C_IS_BLANK(*psz))
1787 psz++;
1788
1789 /* Match the identifier: */
1790 char *pszIdentifier = psz;
1791 if (RT_C_IS_ALPHA(*psz) || *psz == '_')
1792 {
1793 do
1794 psz++;
1795 while (RT_C_IS_ALNUM(*psz) || *psz == '_');
1796 size_t cchIdentifier = (size_t)(psz - pszIdentifier);
1797
1798 /* Skip to the value. */
1799 while (RT_C_IS_BLANK(*psz))
1800 psz++;
1801 char *pszValue = psz;
1802
1803 /* Skip to EOL and strip the value. */
1804 char *pszEol = psz = strchr(psz, '\n');
1805 if (psz)
1806 *psz++ = '\0';
1807 else
1808 pszEol = strchr(pszValue, '\0');
1809 while (pszEol > pszValue && RT_C_IS_SPACE(pszEol[-1]))
1810 *--pszEol = '\0';
1811
1812 LogRelFlow(("Unattended: README.diskdefines: %.*s=%s\n", cchIdentifier, pszIdentifier, pszValue));
1813
1814 /* Do identifier matching: */
1815 if (cchIdentifier == sizeof("DISKNAME") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("DISKNAME")) == 0)
1816 pszDiskName = pszValue;
1817 else if (cchIdentifier == sizeof("ARCH") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("ARCH")) == 0)
1818 pszArch = pszValue;
1819 else
1820 continue;
1821 if (pszDiskName == NULL || pszArch == NULL)
1822 continue;
1823 break;
1824 }
1825 }
1826
1827 /* Next line: */
1828 psz = strchr(psz, '\n');
1829 if (!psz)
1830 break;
1831 psz++;
1832 }
1833
1834 /* Did we find both of them? */
1835 if (pszDiskName && pszArch)
1836 {
1837 if (detectLinuxArch(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1838 {
1839 const char *pszVersion = NULL;
1840 if (detectLinuxDistroName(pszDiskName, &mEnmOsType, &pszVersion))
1841 {
1842 LogRelFlow(("Unattended: README.diskdefines: version=%s\n", pszVersion));
1843 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1844 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1845
1846 size_t cchVersionPosition = 0;
1847 if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition))
1848 {
1849 try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); }
1850 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1851 }
1852 }
1853 else
1854 LogRel(("Unattended: README.diskdefines: Unknown: diskname='%s'\n", pszDiskName));
1855 }
1856 else
1857 LogRel(("Unattended: README.diskdefines: Unknown: arch='%s'\n", pszArch));
1858 }
1859 else
1860 LogRel(("Unattended: README.diskdefines: Did not find both DISKNAME and ARCH. :-/\n"));
1861
1862 if (mEnmOsType != VBOXOSTYPE_Unknown)
1863 return S_FALSE;
1864 }
1865
1866 /*
1867 * All of the debian based distro versions I checked have a single line ./disk/info
1868 * file. Only info I could find related to .disk folder is:
1869 * https://lists.debian.org/debian-cd/2004/01/msg00069.html
1870 *
1871 * Some example content from several install ISOs is as follows:
1872 * Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1 (20041020)
1873 * Linux Mint 20.3 "Una" - Release amd64 20220104
1874 * Debian GNU/Linux 11.2.0 "Bullseye" - Official amd64 NETINST 20211218-11:12
1875 * Debian GNU/Linux 9.13.0 "Stretch" - Official amd64 DVD Binary-1 20200718-11:07
1876 * Xubuntu 20.04.2.0 LTS "Focal Fossa" - Release amd64 (20210209.1)
1877 * Ubuntu 17.10 "Artful Aardvark" - Release amd64 (20180105.1)
1878 * Ubuntu 16.04.6 LTS "Xenial Xerus" - Release i386 (20190227.1)
1879 * Debian GNU/Linux 8.11.1 "Jessie" - Official amd64 CD Binary-1 20190211-02:10
1880 * Kali GNU/Linux 2021.3a "Kali-last-snapshot" - Official amd64 BD Binary-1 with firmware 20211015-16:55
1881 * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
1882 */
1883 vrc = RTVfsFileOpen(hVfsIso, ".disk/info", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
1884 if (RT_SUCCESS(vrc))
1885 {
1886 size_t cchIgn;
1887 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
1888 pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
1889
1890 pBuf->sz[sizeof(*pBuf) - 1] = '\0';
1891 RTVfsFileRelease(hVfsFile);
1892
1893 char *psz = pBuf->sz;
1894 char *pszDiskName = psz;
1895 char *pszArch = NULL;
1896
1897 /* Only care about the first line of the file even if it is multi line and assume disk name ended with ' - '.*/
1898 psz = RTStrStr(pBuf->sz, " - ");
1899 if (psz && memchr(pBuf->sz, '\n', (size_t)(psz - pBuf->sz)) == NULL)
1900 {
1901 *psz = '\0';
1902 psz += 3;
1903 if (*psz)
1904 pszArch = psz;
1905 }
1906
1907 /* Some Debian Live ISO's have info file content as follows:
1908 * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
1909 * thus pszArch stays empty. Try Volume Id (label) if we get lucky and get architecture from that. */
1910 if (!pszArch)
1911 {
1912 char szVolumeId[128];
1913 vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
1914 if (RT_SUCCESS(vrc))
1915 {
1916 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1917 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", szVolumeId));
1918 }
1919 else
1920 LogRel(("Unattended: .disk/info No Volume Label found\n"));
1921 }
1922 else
1923 {
1924 if (!detectLinuxArchII(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
1925 LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", pszArch));
1926 }
1927
1928 if (pszDiskName)
1929 {
1930 const char *pszVersion = NULL;
1931 if (detectLinuxDistroNameII(pszDiskName, &mEnmOsType, &pszVersion))
1932 {
1933 LogRelFlow(("Unattended: .disk/info: version=%s\n", pszVersion));
1934 try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
1935 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1936
1937 size_t cchVersionPosition = 0;
1938 if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition))
1939 {
1940 try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); }
1941 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
1942 }
1943 }
1944 else
1945 LogRel(("Unattended: .disk/info: Unknown: diskname='%s'\n", pszDiskName));
1946 }
1947
1948 if (mEnmOsType == VBOXOSTYPE_Unknown)
1949 LogRel(("Unattended: .disk/info: Did not find DISKNAME or/and ARCH. :-/\n"));
1950 else
1951 return S_FALSE;
1952 }
1953
1954 /*
1955 * Fedora live iso should be recognizable from the primary volume ID (the
1956 * joliet one is usually truncated). We set fAlternative = true here to
1957 * get the primary volume ID.
1958 */
1959 char szVolumeId[128];
1960 vrc = RTVfsQueryLabel(hVfsIso, true /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
1961 if (RT_SUCCESS(vrc) && RTStrStartsWith(szVolumeId, "Fedora-"))
1962 return i_innerDetectIsoOSLinuxFedora(hVfsIso, pBuf, &szVolumeId[sizeof("Fedora-") - 1]);
1963 return S_FALSE;
1964}
1965
1966
1967/**
1968 * Continues working a Fedora ISO image after the caller found a "Fedora-*"
1969 * volume ID.
1970 *
1971 * Sample Volume IDs:
1972 * - Fedora-WS-Live-34-1-2 (joliet: Fedora-WS-Live-3)
1973 * - Fedora-S-dvd-x86_64-34 (joliet: Fedora-S-dvd-x86)
1974 * - Fedora-WS-dvd-i386-25 (joliet: Fedora-WS-dvd-i3)
1975 */
1976HRESULT Unattended::i_innerDetectIsoOSLinuxFedora(RTVFS hVfsIso, DETECTBUFFER *pBuf, char *pszVolId)
1977{
1978 char * const pszFlavor = pszVolId;
1979 char * psz = pszVolId;
1980
1981 /* The volume id may or may not include an arch, component.
1982 We ASSUME that it includes a numeric part with the version, or at least
1983 part of it. */
1984 char *pszVersion = NULL;
1985 char *pszArch = NULL;
1986 if (detectLinuxArchII(psz, &mEnmOsType, VBOXOSTYPE_FedoraCore, &pszArch, &pszVersion))
1987 {
1988 while (*pszVersion == '-')
1989 pszVersion++;
1990 *pszArch = '\0';
1991 }
1992 else
1993 {
1994 mEnmOsType = (VBOXOSTYPE)(VBOXOSTYPE_FedoraCore | VBOXOSTYPE_UnknownArch);
1995
1996 char ch;
1997 while ((ch = *psz) != '\0' && (!RT_C_IS_DIGIT(ch) || !RT_C_IS_PUNCT(psz[-1])))
1998 psz++;
1999 if (ch != '\0')
2000 pszVersion = psz;
2001 }
2002
2003 /*
2004 * Replace '-' with '.' in the version part and use it as the version.
2005 */
2006 if (pszVersion)
2007 {
2008 psz = pszVersion;
2009 while ((psz = strchr(psz, '-')) != NULL)
2010 *psz++ = '.';
2011 try { mStrDetectedOSVersion = RTStrStrip(pszVersion); }
2012 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2013
2014 *pszVersion = '\0'; /* don't include in flavor */
2015 }
2016
2017 /*
2018 * Split up the pre-arch/version bits into words and use them as the flavor.
2019 */
2020 psz = pszFlavor;
2021 while ((psz = strchr(psz, '-')) != NULL)
2022 *psz++ = ' ';
2023 try { mStrDetectedOSFlavor = RTStrStrip(pszFlavor); }
2024 catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
2025
2026 /*
2027 * If we don't have an architecture, we look at the vmlinuz file as the x86
2028 * and AMD64 versions starts with a MZ+PE header giving the architecture.
2029 */
2030 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2031 {
2032 static const char * const s_apszVmLinuz[] = { "images/pxeboot/vmlinuz", "isolinux/vmlinuz" };
2033 for (size_t i = 0; i < RT_ELEMENTS(s_apszVmLinuz); i++)
2034 {
2035 RTVFSFILE hVfsFileLinuz = NIL_RTVFSFILE;
2036 int vrc = RTVfsFileOpen(hVfsIso, s_apszVmLinuz[i], RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
2037 &hVfsFileLinuz);
2038 if (RT_SUCCESS(vrc))
2039 {
2040 /* DOS signature: */
2041 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pBuf->ab[0];
2042 AssertCompile(sizeof(*pBuf) > sizeof(*pDosHdr));
2043 vrc = RTVfsFileReadAt(hVfsFileLinuz, 0, pDosHdr, sizeof(*pDosHdr), NULL);
2044 if (RT_SUCCESS(vrc) && pDosHdr->e_magic == IMAGE_DOS_SIGNATURE)
2045 {
2046 /* NT signature - only need magic + file header, so use the 64 version for better debugging: */
2047 PIMAGE_NT_HEADERS64 pNtHdrs = (PIMAGE_NT_HEADERS64)&pBuf->ab[0];
2048 vrc = RTVfsFileReadAt(hVfsFileLinuz, pDosHdr->e_lfanew, pNtHdrs, sizeof(*pNtHdrs), NULL);
2049 AssertCompile(sizeof(*pBuf) > sizeof(*pNtHdrs));
2050 if (RT_SUCCESS(vrc) && pNtHdrs->Signature == IMAGE_NT_SIGNATURE)
2051 {
2052 if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
2053 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x86);
2054 else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
2055 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x64);
2056 else
2057 AssertFailed();
2058 }
2059 }
2060
2061 RTVfsFileRelease(hVfsFileLinuz);
2062 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch)
2063 break;
2064 }
2065 }
2066 }
2067
2068 /*
2069 * If that failed, look for other files that gives away the arch.
2070 */
2071 if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
2072 {
2073 static struct { const char *pszFile; VBOXOSTYPE fArch; } const s_aArchSpecificFiles[] =
2074 {
2075 { "EFI/BOOT/grubaa64.efi", VBOXOSTYPE_arm64 },
2076 { "EFI/BOOT/BOOTAA64.EFI", VBOXOSTYPE_arm64 },
2077 };
2078 PRTFSOBJINFO pObjInfo = (PRTFSOBJINFO)&pBuf->ab[0];
2079 AssertCompile(sizeof(*pBuf) > sizeof(*pObjInfo));
2080 for (size_t i = 0; i < RT_ELEMENTS(s_aArchSpecificFiles); i++)
2081 {
2082 int vrc = RTVfsQueryPathInfo(hVfsIso, s_aArchSpecificFiles[i].pszFile, pObjInfo,
2083 RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2084 if (RT_SUCCESS(vrc) && RTFS_IS_FILE(pObjInfo->Attr.fMode))
2085 {
2086 mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | s_aArchSpecificFiles[i].fArch);
2087 break;
2088 }
2089 }
2090 }
2091
2092 /*
2093 * If we like, we could parse grub.conf to look for fullly spelled out
2094 * flavor, though the menu items typically only contains the major version
2095 * number, so little else to add, really.
2096 */
2097
2098 return (mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch ? S_OK : S_FALSE;
2099}
2100
2101
2102/**
2103 * Detect OS/2 installation ISOs.
2104 *
2105 * Mainly aiming at ACP2/MCP2 as that's what we currently use in our testing.
2106 *
2107 * @returns COM status code.
2108 * @retval S_OK if detected
2109 * @retval S_FALSE if not fully detected.
2110 *
2111 * @param hVfsIso The ISO file system.
2112 * @param pBuf Read buffer.
2113 */
2114HRESULT Unattended::i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2115{
2116 /*
2117 * The OS2SE20.SRC contains the location of the tree with the diskette
2118 * images, typically "\OS2IMAGE".
2119 */
2120 RTVFSFILE hVfsFile;
2121 int vrc = RTVfsFileOpen(hVfsIso, "OS2SE20.SRC", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2122 if (RT_SUCCESS(vrc))
2123 {
2124 size_t cbRead = 0;
2125 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2126 RTVfsFileRelease(hVfsFile);
2127 if (RT_SUCCESS(vrc))
2128 {
2129 pBuf->sz[cbRead] = '\0';
2130 RTStrStrip(pBuf->sz);
2131 vrc = RTStrValidateEncoding(pBuf->sz);
2132 if (RT_SUCCESS(vrc))
2133 LogRelFlow(("Unattended: OS2SE20.SRC=%s\n", pBuf->sz));
2134 else
2135 LogRel(("Unattended: OS2SE20.SRC invalid encoding: %Rrc, %.*Rhxs\n", vrc, cbRead, pBuf->sz));
2136 }
2137 else
2138 LogRel(("Unattended: Error reading OS2SE20.SRC: %\n", vrc));
2139 }
2140 /*
2141 * ArcaOS has dropped the file, assume it's \OS2IMAGE and see if it's there.
2142 */
2143 else if (vrc == VERR_FILE_NOT_FOUND)
2144 RTStrCopy(pBuf->sz, sizeof(pBuf->sz), "\\OS2IMAGE");
2145 else
2146 return S_FALSE;
2147
2148 /*
2149 * Check that the directory directory exists and has a DISK_0 under it
2150 * with an OS2LDR on it.
2151 */
2152 size_t const cchOs2Image = strlen(pBuf->sz);
2153 vrc = RTPathAppend(pBuf->sz, sizeof(pBuf->sz), "DISK_0/OS2LDR");
2154 RTFSOBJINFO ObjInfo = {0};
2155 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2156 if (vrc == VERR_FILE_NOT_FOUND)
2157 {
2158 RTStrCat(pBuf->sz, sizeof(pBuf->sz), "."); /* eCS 2.0 image includes the dot from the 8.3 name. */
2159 vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2160 }
2161 if ( RT_FAILURE(vrc)
2162 || !RTFS_IS_FILE(ObjInfo.Attr.fMode))
2163 {
2164 LogRel(("Unattended: RTVfsQueryPathInfo(, '%s' (from OS2SE20.SRC),) -> %Rrc, fMode=%#x\n",
2165 pBuf->sz, vrc, ObjInfo.Attr.fMode));
2166 return S_FALSE;
2167 }
2168
2169 /*
2170 * So, it's some kind of OS/2 2.x or later ISO alright.
2171 */
2172 mEnmOsType = VBOXOSTYPE_OS2;
2173 mStrDetectedOSHints.printf("OS2SE20.SRC=%.*s", cchOs2Image, pBuf->sz);
2174
2175 /*
2176 * ArcaOS ISOs seems to have a AOSBOOT dir on them.
2177 * This contains a ARCANOAE.FLG file with content we can use for the version:
2178 * ArcaOS 5.0.7 EN
2179 * Built 2021-12-07 18:34:34
2180 * We drop the "ArcaOS" bit, as it's covered by mEnmOsType. Then we pull up
2181 * the second line.
2182 *
2183 * Note! Yet to find a way to do unattended install of ArcaOS, as it comes
2184 * with no CD-boot floppy images, only simple .PF archive files for
2185 * unpacking onto the ram disk or whatever. Modifying these is
2186 * possible (ibsen's aPLib v0.36 compression with some simple custom
2187 * headers), but it would probably be a royal pain. Could perhaps
2188 * cook something from OS2IMAGE\DISK_0 thru 3...
2189 */
2190 vrc = RTVfsQueryPathInfo(hVfsIso, "AOSBOOT", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2191 if ( RT_SUCCESS(vrc)
2192 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2193 {
2194 mEnmOsType = VBOXOSTYPE_ArcaOS;
2195
2196 /* Read the version file: */
2197 vrc = RTVfsFileOpen(hVfsIso, "SYS/ARCANOAE.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2198 if (RT_SUCCESS(vrc))
2199 {
2200 size_t cbRead = 0;
2201 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2202 RTVfsFileRelease(hVfsFile);
2203 pBuf->sz[cbRead] = '\0';
2204 if (RT_SUCCESS(vrc))
2205 {
2206 /* Strip the OS name: */
2207 char *pszVersion = RTStrStrip(pBuf->sz);
2208 static char s_szArcaOS[] = "ArcaOS";
2209 if (RTStrStartsWith(pszVersion, s_szArcaOS))
2210 pszVersion = RTStrStripL(pszVersion + sizeof(s_szArcaOS) - 1);
2211
2212 /* Pull up the 2nd line if it, condensing the \r\n into a single space. */
2213 char *pszNewLine = strchr(pszVersion, '\n');
2214 if (pszNewLine && RTStrStartsWith(pszNewLine + 1, "Built 20"))
2215 {
2216 size_t offRemove = 0;
2217 while (RT_C_IS_SPACE(pszNewLine[-1 - (ssize_t)offRemove]))
2218 offRemove++;
2219 if (offRemove > 0)
2220 {
2221 pszNewLine -= offRemove;
2222 memmove(pszNewLine, pszNewLine + offRemove, strlen(pszNewLine + offRemove) - 1);
2223 }
2224 *pszNewLine = ' ';
2225 }
2226
2227 /* Drop any additional lines: */
2228 pszNewLine = strchr(pszVersion, '\n');
2229 if (pszNewLine)
2230 *pszNewLine = '\0';
2231 RTStrStripR(pszVersion);
2232
2233 /* Done (hope it makes some sense). */
2234 mStrDetectedOSVersion = pszVersion;
2235 }
2236 else
2237 LogRel(("Unattended: failed to read AOSBOOT/ARCANOAE.FLG: %Rrc\n", vrc));
2238 }
2239 else
2240 LogRel(("Unattended: failed to open AOSBOOT/ARCANOAE.FLG for reading: %Rrc\n", vrc));
2241 }
2242 /*
2243 * Similarly, eCS has an ECS directory and it typically contains a
2244 * ECS_INST.FLG file with the version info. Content differs a little:
2245 * eComStation 2.0 EN_US Thu May 13 10:27:54 pm 2010
2246 * Built on ECS60441318
2247 * Here we drop the "eComStation" bit and leave the 2nd line as it.
2248 *
2249 * Note! At least 2.0 has a DISKIMGS folder with what looks like boot
2250 * disks, so we could probably get something going here without
2251 * needing to write an OS2 boot sector...
2252 */
2253 else
2254 {
2255 vrc = RTVfsQueryPathInfo(hVfsIso, "ECS", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2256 if ( RT_SUCCESS(vrc)
2257 && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
2258 {
2259 mEnmOsType = VBOXOSTYPE_ECS;
2260
2261 /* Read the version file: */
2262 vrc = RTVfsFileOpen(hVfsIso, "ECS/ECS_INST.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2263 if (RT_SUCCESS(vrc))
2264 {
2265 size_t cbRead = 0;
2266 vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
2267 RTVfsFileRelease(hVfsFile);
2268 pBuf->sz[cbRead] = '\0';
2269 if (RT_SUCCESS(vrc))
2270 {
2271 /* Strip the OS name: */
2272 char *pszVersion = RTStrStrip(pBuf->sz);
2273 static char s_szECS[] = "eComStation";
2274 if (RTStrStartsWith(pszVersion, s_szECS))
2275 pszVersion = RTStrStripL(pszVersion + sizeof(s_szECS) - 1);
2276
2277 /* Drop any additional lines: */
2278 char *pszNewLine = strchr(pszVersion, '\n');
2279 if (pszNewLine)
2280 *pszNewLine = '\0';
2281 RTStrStripR(pszVersion);
2282
2283 /* Done (hope it makes some sense). */
2284 mStrDetectedOSVersion = pszVersion;
2285 }
2286 else
2287 LogRel(("Unattended: failed to read ECS/ECS_INST.FLG: %Rrc\n", vrc));
2288 }
2289 else
2290 LogRel(("Unattended: failed to open ECS/ECS_INST.FLG for reading: %Rrc\n", vrc));
2291 }
2292 else
2293 {
2294 /*
2295 * Official IBM OS/2 builds doesn't have any .FLG file on them,
2296 * so need to pry the information out in some other way. Best way
2297 * is to read the SYSLEVEL.OS2 file, which is typically on disk #2,
2298 * though on earlier versions (warp3) it was disk #1.
2299 */
2300 vrc = RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1,
2301 "/DISK_2/SYSLEVEL.OS2");
2302 if (RT_SUCCESS(vrc))
2303 {
2304 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2305 if (vrc == VERR_FILE_NOT_FOUND)
2306 {
2307 RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, "/DISK_1/SYSLEVEL.OS2");
2308 vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2309 }
2310 if (RT_SUCCESS(vrc))
2311 {
2312 RT_ZERO(pBuf->ab);
2313 size_t cbRead = 0;
2314 vrc = RTVfsFileRead(hVfsFile, pBuf->ab, sizeof(pBuf->ab), &cbRead);
2315 RTVfsFileRelease(hVfsFile);
2316 if (RT_SUCCESS(vrc))
2317 {
2318 /* Check the header. */
2319 OS2SYSLEVELHDR const *pHdr = (OS2SYSLEVELHDR const *)&pBuf->ab[0];
2320 if ( pHdr->uMinusOne == UINT16_MAX
2321 && pHdr->uSyslevelFileVer == 1
2322 && memcmp(pHdr->achSignature, RT_STR_TUPLE("SYSLEVEL")) == 0
2323 && pHdr->offTable < cbRead
2324 && pHdr->offTable + sizeof(OS2SYSLEVELENTRY) <= cbRead)
2325 {
2326 OS2SYSLEVELENTRY *pEntry = (OS2SYSLEVELENTRY *)&pBuf->ab[pHdr->offTable];
2327 if ( RT_SUCCESS(RTStrValidateEncodingEx(pEntry->szName, sizeof(pEntry->szName),
2328 RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED))
2329 && RT_SUCCESS(RTStrValidateEncodingEx(pEntry->achCsdLevel, sizeof(pEntry->achCsdLevel), 0))
2330 && pEntry->bVersion != 0
2331 && ((pEntry->bVersion >> 4) & 0xf) < 10
2332 && (pEntry->bVersion & 0xf) < 10
2333 && pEntry->bModify < 10
2334 && pEntry->bRefresh < 10)
2335 {
2336 /* Flavor: */
2337 char *pszName = RTStrStrip(pEntry->szName);
2338 if (pszName)
2339 mStrDetectedOSFlavor = pszName;
2340
2341 /* Version: */
2342 if (pEntry->bRefresh != 0)
2343 mStrDetectedOSVersion.printf("%d.%d%d.%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2344 pEntry->bModify, pEntry->bRefresh);
2345 else
2346 mStrDetectedOSVersion.printf("%d.%d%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
2347 pEntry->bModify);
2348 pEntry->achCsdLevel[sizeof(pEntry->achCsdLevel) - 1] = '\0';
2349 char *pszCsd = RTStrStrip(pEntry->achCsdLevel);
2350 if (*pszCsd != '\0')
2351 {
2352 mStrDetectedOSVersion.append(' ');
2353 mStrDetectedOSVersion.append(pszCsd);
2354 }
2355 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.50") >= 0)
2356 mEnmOsType = VBOXOSTYPE_OS2Warp45;
2357 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.00") >= 0)
2358 mEnmOsType = VBOXOSTYPE_OS2Warp4;
2359 else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "3.00") >= 0)
2360 mEnmOsType = VBOXOSTYPE_OS2Warp3;
2361 }
2362 else
2363 LogRel(("Unattended: bogus SYSLEVEL.OS2 file entry: %.128Rhxd\n", pEntry));
2364 }
2365 else
2366 LogRel(("Unattended: bogus SYSLEVEL.OS2 file header: uMinusOne=%#x uSyslevelFileVer=%#x achSignature=%.8Rhxs offTable=%#x vs cbRead=%#zx\n",
2367 pHdr->uMinusOne, pHdr->uSyslevelFileVer, pHdr->achSignature, pHdr->offTable, cbRead));
2368 }
2369 else
2370 LogRel(("Unattended: failed to read SYSLEVEL.OS2: %Rrc\n", vrc));
2371 }
2372 else
2373 LogRel(("Unattended: failed to open '%s' for reading: %Rrc\n", pBuf->sz, vrc));
2374 }
2375 }
2376 }
2377
2378 /** @todo language detection? */
2379
2380 /*
2381 * Only tested ACP2, so only return S_OK for it.
2382 */
2383 if ( mEnmOsType == VBOXOSTYPE_OS2Warp45
2384 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.52") >= 0
2385 && mStrDetectedOSFlavor.contains("Server", RTCString::CaseInsensitive))
2386 return S_OK;
2387
2388 return S_FALSE;
2389}
2390
2391
2392/**
2393 * Detect FreeBSD distro ISOs.
2394 *
2395 * @returns COM status code.
2396 * @retval S_OK if detected
2397 * @retval S_FALSE if not fully detected.
2398 *
2399 * @param hVfsIso The ISO file system.
2400 * @param pBuf Read buffer.
2401 */
2402HRESULT Unattended::i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *pBuf)
2403{
2404 RT_NOREF(pBuf);
2405
2406 /*
2407 * FreeBSD since 10.0 has a .profile file in the root which can be used to determine that this is FreeBSD
2408 * along with the version.
2409 */
2410
2411 RTVFSFILE hVfsFile;
2412 HRESULT hrc = S_FALSE;
2413 int vrc = RTVfsFileOpen(hVfsIso, ".profile", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
2414 if (RT_SUCCESS(vrc))
2415 {
2416 static const uint8_t s_abFreeBsdHdr[] = "# $FreeBSD: releng/";
2417 char abRead[32];
2418
2419 vrc = RTVfsFileRead(hVfsFile, &abRead[0], sizeof(abRead), NULL /*pcbRead*/);
2420 if ( RT_SUCCESS(vrc)
2421 && !memcmp(&abRead[0], &s_abFreeBsdHdr[0], sizeof(s_abFreeBsdHdr) - 1)) /* Skip terminator */
2422 {
2423 abRead[sizeof(abRead) - 1] = '\0';
2424
2425 /* Detect the architecture using the volume label. */
2426 char szVolumeId[128];
2427 size_t cchVolumeId;
2428 vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, 128, &cchVolumeId);
2429 if (RT_SUCCESS(vrc))
2430 {
2431 /* Can re-use the Linux code here. */
2432 if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_FreeBSD))
2433 LogRel(("Unattended/FBSD: Unknown: arch='%s'\n", szVolumeId));
2434
2435 /* Detect the version from the string coming after the needle in .profile. */
2436 AssertCompile(sizeof(s_abFreeBsdHdr) - 1 < sizeof(abRead));
2437
2438 char *pszVersionStart = &abRead[sizeof(s_abFreeBsdHdr) - 1];
2439 char *pszVersionEnd = pszVersionStart;
2440
2441 while (RT_C_IS_DIGIT(*pszVersionEnd))
2442 pszVersionEnd++;
2443 if (*pszVersionEnd == '.')
2444 {
2445 pszVersionEnd++; /* Skip the . */
2446
2447 while (RT_C_IS_DIGIT(*pszVersionEnd))
2448 pszVersionEnd++;
2449
2450 /* Terminate the version string. */
2451 *pszVersionEnd = '\0';
2452
2453 try { mStrDetectedOSVersion = pszVersionStart; }
2454 catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
2455 }
2456 else
2457 LogRel(("Unattended/FBSD: Unknown: version='%s'\n", &abRead[0]));
2458 }
2459 else
2460 {
2461 LogRel(("Unattended/FBSD: No Volume Label found\n"));
2462 mEnmOsType = VBOXOSTYPE_FreeBSD;
2463 }
2464
2465 hrc = S_OK;
2466 }
2467
2468 RTVfsFileRelease(hVfsFile);
2469 }
2470
2471 return hrc;
2472}
2473
2474
2475HRESULT Unattended::prepare()
2476{
2477 LogFlow(("Unattended::prepare: enter\n"));
2478
2479 /*
2480 * Must have a machine.
2481 */
2482 ComPtr<Machine> ptrMachine;
2483 Guid MachineUuid;
2484 {
2485 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2486 ptrMachine = mMachine;
2487 if (ptrMachine.isNull())
2488 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("No machine associated with this IUnatteded instance"));
2489 MachineUuid = mMachineUuid;
2490 }
2491
2492 /*
2493 * Before we write lock ourselves, we must get stuff from Machine and
2494 * VirtualBox because their locks have higher priorities than ours.
2495 */
2496 Utf8Str strGuestOsTypeId;
2497 Utf8Str strMachineName;
2498 Utf8Str strDefaultAuxBasePath;
2499 HRESULT hrc;
2500 try
2501 {
2502 Bstr bstrTmp;
2503 hrc = ptrMachine->COMGETTER(OSTypeId)(bstrTmp.asOutParam());
2504 if (SUCCEEDED(hrc))
2505 {
2506 strGuestOsTypeId = bstrTmp;
2507 hrc = ptrMachine->COMGETTER(Name)(bstrTmp.asOutParam());
2508 if (SUCCEEDED(hrc))
2509 strMachineName = bstrTmp;
2510 }
2511 int vrc = ptrMachine->i_calculateFullPath(Utf8StrFmt("Unattended-%RTuuid-", MachineUuid.raw()), strDefaultAuxBasePath);
2512 if (RT_FAILURE(vrc))
2513 return setErrorBoth(E_FAIL, vrc);
2514 }
2515 catch (std::bad_alloc &)
2516 {
2517 return E_OUTOFMEMORY;
2518 }
2519 bool const fIs64Bit = i_isGuestOSArchX64(strGuestOsTypeId);
2520
2521 ComPtr<IPlatform> pPlatform;
2522 hrc = ptrMachine->COMGETTER(Platform)(pPlatform.asOutParam());
2523 AssertComRCReturn(hrc, hrc);
2524
2525 BOOL fRtcUseUtc = FALSE;
2526 hrc = pPlatform->COMGETTER(RTCUseUTC)(&fRtcUseUtc);
2527 if (FAILED(hrc))
2528 return hrc;
2529
2530 ComPtr<IFirmwareSettings> pFirmwareSettings;
2531 hrc = ptrMachine->COMGETTER(FirmwareSettings)(pFirmwareSettings.asOutParam());
2532 AssertComRCReturn(hrc, hrc);
2533
2534 FirmwareType_T enmFirmware = FirmwareType_BIOS;
2535 hrc = pFirmwareSettings->COMGETTER(FirmwareType)(&enmFirmware);
2536 if (FAILED(hrc))
2537 return hrc;
2538
2539 /*
2540 * Write lock this object and set attributes we got from IMachine.
2541 */
2542 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2543
2544 mStrGuestOsTypeId = strGuestOsTypeId;
2545 mfGuestOs64Bit = fIs64Bit;
2546 mfRtcUseUtc = RT_BOOL(fRtcUseUtc);
2547 menmFirmwareType = enmFirmware;
2548
2549 /*
2550 * Do some state checks.
2551 */
2552 if (mpInstaller != NULL)
2553 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The prepare method has been called (must call done to restart)"));
2554 if ((Machine *)ptrMachine != (Machine *)mMachine)
2555 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The 'machine' while we were using it - please don't do that"));
2556
2557 /*
2558 * Check if the specified ISOs and files exist.
2559 */
2560 if (!RTFileExists(mStrIsoPath.c_str()))
2561 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the installation ISO file '%s'"),
2562 mStrIsoPath.c_str());
2563 if (mfInstallGuestAdditions && !RTFileExists(mStrAdditionsIsoPath.c_str()))
2564 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the Guest Additions ISO file '%s'"),
2565 mStrAdditionsIsoPath.c_str());
2566 if (mfInstallTestExecService && !RTFileExists(mStrValidationKitIsoPath.c_str()))
2567 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the validation kit ISO file '%s'"),
2568 mStrValidationKitIsoPath.c_str());
2569 if (mStrScriptTemplatePath.isNotEmpty() && !RTFileExists(mStrScriptTemplatePath.c_str()))
2570 return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate unattended installation script template '%s'"),
2571 mStrScriptTemplatePath.c_str());
2572
2573 /*
2574 * Do media detection if it haven't been done yet.
2575 */
2576 if (!mfDoneDetectIsoOS)
2577 {
2578 hrc = detectIsoOS();
2579 if (FAILED(hrc) && hrc != E_NOTIMPL)
2580 return hrc;
2581 }
2582
2583 /*
2584 * We can now check midxImage against mDetectedImages, since the latter is
2585 * populated during the detectIsoOS call. We ignore midxImage if no images
2586 * were detected, assuming that it's not relevant or used for different purposes.
2587 */
2588 if (mDetectedImages.size() > 0)
2589 {
2590 bool fImageFound = false;
2591 for (size_t i = 0; i < mDetectedImages.size(); ++i)
2592 if (midxImage == mDetectedImages[i].mImageIndex)
2593 {
2594 i_updateDetectedAttributeForImage(mDetectedImages[i]);
2595 fImageFound = true;
2596 break;
2597 }
2598 if (!fImageFound)
2599 return setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("imageIndex value %u not found in detectedImageIndices"), midxImage);
2600 }
2601
2602 /*
2603 * Get the ISO's detect guest OS type info and make it's a known one (just
2604 * in case the above step doesn't work right).
2605 */
2606 uint32_t const idxIsoOSType = Global::getOSTypeIndexFromId(mStrDetectedOSTypeId.c_str());
2607 VBOXOSTYPE const enmIsoOSType = idxIsoOSType < Global::cOSTypes ? Global::sOSTypes[idxIsoOSType].osType : VBOXOSTYPE_Unknown;
2608 if ((enmIsoOSType & VBOXOSTYPE_OsTypeMask) == VBOXOSTYPE_Unknown)
2609 return setError(E_FAIL, tr("The supplied ISO file does not contain an OS currently supported for unattended installation"));
2610
2611 /*
2612 * Get the VM's configured guest OS type info.
2613 */
2614 uint32_t const idxMachineOSType = Global::getOSTypeIndexFromId(mStrGuestOsTypeId.c_str());
2615 VBOXOSTYPE const enmMachineOSType = idxMachineOSType < Global::cOSTypes
2616 ? Global::sOSTypes[idxMachineOSType].osType : VBOXOSTYPE_Unknown;
2617 uint32_t const osHint = idxMachineOSType < Global::cOSTypes
2618 ? Global::sOSTypes[idxMachineOSType].osHint : 0;
2619 /*
2620 * Check that the detected guest OS type for the ISO is compatible with
2621 * that of the VM, broadly speaking.
2622 */
2623 if (idxMachineOSType != idxIsoOSType)
2624 {
2625 /* Check that the architecture is compatible: */
2626 if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask)
2627 && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86
2628 || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64))
2629 return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch"));
2630 }
2631
2632 /* We don't support guest OSes w/ EFI, as that requires UDF remastering support we don't have yet. */
2633 if (osHint & VBOXOSHINT_EFI)
2634 return setError(E_FAIL, tr("The detected guest OS type requires EFI to boot and therefore is not supported yet"));
2635
2636 /*
2637 * Do some default property stuff and check other properties.
2638 */
2639 try
2640 {
2641 char szTmp[128];
2642
2643 if (mStrLocale.isEmpty())
2644 {
2645 int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp));
2646 if ( RT_SUCCESS(vrc)
2647 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp))
2648 mStrLocale.assign(szTmp, 5);
2649 else
2650 mStrLocale = "en_US";
2651 Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale));
2652 }
2653
2654 if (mStrLanguage.isEmpty())
2655 {
2656 if (mDetectedOSLanguages.size() > 0)
2657 mStrLanguage = mDetectedOSLanguages[0];
2658 else
2659 mStrLanguage.assign(mStrLocale).findReplace('_', '-');
2660 }
2661
2662 if (mStrCountry.isEmpty())
2663 {
2664 int vrc = RTLocaleQueryUserCountryCode(szTmp);
2665 if (RT_SUCCESS(vrc))
2666 mStrCountry = szTmp;
2667 else if ( mStrLocale.isNotEmpty()
2668 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale))
2669 mStrCountry.assign(mStrLocale, 3, 2);
2670 else
2671 mStrCountry = "US";
2672 }
2673
2674 if (mStrTimeZone.isEmpty())
2675 {
2676 int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp));
2677 if ( RT_SUCCESS(vrc)
2678 && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */)
2679 mStrTimeZone = szTmp;
2680 else
2681 mStrTimeZone = "Etc/UTC";
2682 Assert(mStrTimeZone.isNotEmpty());
2683 }
2684 mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str());
2685 if (!mpTimeZoneInfo)
2686 mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str());
2687 Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC");
2688 if (!mpTimeZoneInfo)
2689 LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str()));
2690
2691 if (mStrHostname.isEmpty())
2692 {
2693 /* Mangle the VM name into a valid hostname. */
2694 for (size_t i = 0; i < strMachineName.length(); i++)
2695 {
2696 char ch = strMachineName[i];
2697 if ( (unsigned)ch < 127
2698 && RT_C_IS_ALNUM(ch))
2699 mStrHostname.append(ch);
2700 else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-"))
2701 mStrHostname.append('-');
2702 }
2703 if (mStrHostname.length() == 0)
2704 mStrHostname.printf("%RTuuid-vm", MachineUuid.raw());
2705 else if (mStrHostname.length() < 3)
2706 mStrHostname.append("-vm");
2707 mStrHostname.append(".myguest.virtualbox.org");
2708 }
2709
2710 if (mStrAuxiliaryBasePath.isEmpty())
2711 {
2712 mStrAuxiliaryBasePath = strDefaultAuxBasePath;
2713 mfIsDefaultAuxiliaryBasePath = true;
2714 }
2715 }
2716 catch (std::bad_alloc &)
2717 {
2718 return E_OUTOFMEMORY;
2719 }
2720
2721 /*
2722 * Instatiate the guest installer matching the ISO.
2723 */
2724 mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion,
2725 mStrDetectedOSFlavor, mStrDetectedOSHints, this);
2726 if (mpInstaller != NULL)
2727 {
2728 hrc = mpInstaller->initInstaller();
2729 if (SUCCEEDED(hrc))
2730 {
2731 /*
2732 * Do the script preps (just reads them).
2733 */
2734 hrc = mpInstaller->prepareUnattendedScripts();
2735 if (SUCCEEDED(hrc))
2736 {
2737 LogFlow(("Unattended::prepare: returns S_OK\n"));
2738 return S_OK;
2739 }
2740 }
2741
2742 /* Destroy the installer instance. */
2743 delete mpInstaller;
2744 mpInstaller = NULL;
2745 }
2746 else
2747 hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND,
2748 tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str());
2749 LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc));
2750 return hrc;
2751}
2752
2753HRESULT Unattended::constructMedia()
2754{
2755 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2756
2757 LogFlow(("===========================================================\n"));
2758 LogFlow(("Call Unattended::constructMedia()\n"));
2759
2760 if (mpInstaller == NULL)
2761 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called");
2762
2763 return mpInstaller->prepareMedia();
2764}
2765
2766HRESULT Unattended::reconfigureVM()
2767{
2768 LogFlow(("===========================================================\n"));
2769 LogFlow(("Call Unattended::reconfigureVM()\n"));
2770
2771 /*
2772 * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues.
2773 */
2774 StorageBus_T enmRecommendedStorageBus = StorageBus_IDE;
2775 {
2776 Bstr bstrGuestOsTypeId;
2777 Bstr bstrDetectedOSTypeId;
2778 {
2779 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2780 if (mpInstaller == NULL)
2781 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2782 bstrGuestOsTypeId = mStrGuestOsTypeId;
2783 bstrDetectedOSTypeId = mStrDetectedOSTypeId;
2784 }
2785 ComPtr<IGuestOSType> ptrGuestOSType;
2786 HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam());
2787 if (SUCCEEDED(hrc))
2788 {
2789 if (!ptrGuestOSType.isNull())
2790 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus);
2791 }
2792 if (FAILED(hrc))
2793 return hrc;
2794
2795 /* If the detected guest OS type differs, log a warning if their DVD storage
2796 bus recommendations differ. */
2797 if (bstrGuestOsTypeId != bstrDetectedOSTypeId)
2798 {
2799 StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE;
2800 hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam());
2801 if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull())
2802 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2);
2803 if (FAILED(hrc))
2804 return hrc;
2805
2806 if (enmRecommendedStorageBus != enmRecommendedStorageBus2)
2807 LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n",
2808 ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(),
2809 ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() ));
2810 }
2811 }
2812
2813 /*
2814 * Take write lock (for lock order reasons, write lock our parent object too)
2815 * then make sure we're the only caller of this method.
2816 */
2817 AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS);
2818 HRESULT hrc;
2819 if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD)
2820 {
2821 RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf();
2822 mhThreadReconfigureVM = hNativeSelf;
2823
2824 /*
2825 * Create a new session, lock the machine and get the session machine object.
2826 * Do the locking without pinning down the write locks, just to be on the safe side.
2827 */
2828 ComPtr<ISession> ptrSession;
2829 try
2830 {
2831 hrc = ptrSession.createInprocObject(CLSID_Session);
2832 }
2833 catch (std::bad_alloc &)
2834 {
2835 hrc = E_OUTOFMEMORY;
2836 }
2837 if (SUCCEEDED(hrc))
2838 {
2839 alock.release();
2840 hrc = mMachine->LockMachine(ptrSession, LockType_Shared);
2841 alock.acquire();
2842 if (SUCCEEDED(hrc))
2843 {
2844 ComPtr<IMachine> ptrSessionMachine;
2845 hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam());
2846 if (SUCCEEDED(hrc))
2847 {
2848 /*
2849 * Hand the session to the inner work and let it do it job.
2850 */
2851 try
2852 {
2853 hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine);
2854 }
2855 catch (...)
2856 {
2857 hrc = E_UNEXPECTED;
2858 }
2859 }
2860
2861 /* Paranoia: release early in case we it a bump below. */
2862 Assert(mhThreadReconfigureVM == hNativeSelf);
2863 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2864
2865 /*
2866 * While unlocking the machine we'll have to drop the locks again.
2867 */
2868 alock.release();
2869
2870 ptrSessionMachine.setNull();
2871 HRESULT hrc2 = ptrSession->UnlockMachine();
2872 AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2));
2873
2874 ptrSession.setNull();
2875
2876 alock.acquire();
2877 }
2878 else
2879 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2880 }
2881 else
2882 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2883 }
2884 else
2885 hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread"));
2886 return hrc;
2887}
2888
2889
2890HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
2891 ComPtr<IMachine> const &rPtrSessionMachine)
2892{
2893 if (mpInstaller == NULL)
2894 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2895
2896 // Fetch all available storage controllers
2897 com::SafeIfaceArray<IStorageController> arrayOfControllers;
2898 HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers));
2899 AssertComRCReturn(hrc, hrc);
2900
2901 /*
2902 * Figure out where the images are to be mounted, adding controllers/ports as needed.
2903 */
2904 std::vector<UnattendedInstallationDisk> vecInstallationDisks;
2905 if (mpInstaller->isAuxiliaryFloppyNeeded())
2906 {
2907 hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock);
2908 if (FAILED(hrc))
2909 return hrc;
2910 }
2911
2912 hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus);
2913 if (FAILED(hrc))
2914 return hrc;
2915
2916 /*
2917 * Mount the images.
2918 */
2919 for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++)
2920 {
2921 UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage);
2922 Assert(pImage->strImagePath.isNotEmpty());
2923 hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock);
2924 if (FAILED(hrc))
2925 return hrc;
2926 }
2927
2928 /*
2929 * Set the boot order.
2930 *
2931 * ASSUME that the HD isn't bootable when we start out, but it will be what
2932 * we boot from after the first stage of the installation is done. Setting
2933 * it first prevents endless reboot cylces.
2934 */
2935 /** @todo consider making 100% sure the disk isn't bootable (edit partition
2936 * table active bits and EFI stuff). */
2937 Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD
2938 || mpInstaller->getBootableDeviceType() == DeviceType_Floppy);
2939 hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk);
2940 if (SUCCEEDED(hrc))
2941 hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType());
2942 if (SUCCEEDED(hrc))
2943 hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD
2944 ? DeviceType_Floppy : DeviceType_DVD);
2945 if (FAILED(hrc))
2946 return hrc;
2947
2948 /*
2949 * Essential step.
2950 *
2951 * HACK ALERT! We have to release the lock here or we'll get into trouble with
2952 * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType).
2953 */
2954 if (SUCCEEDED(hrc))
2955 {
2956 rAutoLock.release();
2957 hrc = rPtrSessionMachine->SaveSettings();
2958 rAutoLock.acquire();
2959 }
2960
2961 return hrc;
2962}
2963
2964/**
2965 * Makes sure we've got a floppy drive attached to a floppy controller, adding
2966 * the auxiliary floppy image to the installation disk vector.
2967 *
2968 * @returns COM status code.
2969 * @param rControllers The existing controllers.
2970 * @param rVecInstallatationDisks The list of image to mount.
2971 * @param rPtrSessionMachine The session machine smart pointer.
2972 * @param rAutoLock The lock.
2973 */
2974HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
2975 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
2976 ComPtr<IMachine> const &rPtrSessionMachine,
2977 AutoMultiWriteLock2 &rAutoLock)
2978{
2979 Assert(mpInstaller->isAuxiliaryFloppyNeeded());
2980
2981 /*
2982 * Look for a floppy controller with a primary drive (A:) we can "insert"
2983 * the auxiliary floppy image. Add a controller and/or a drive if necessary.
2984 */
2985 bool fFoundPort0Dev0 = false;
2986 Bstr bstrControllerName;
2987 Utf8Str strControllerName;
2988
2989 for (size_t i = 0; i < rControllers.size(); ++i)
2990 {
2991 StorageBus_T enmStorageBus;
2992 HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
2993 AssertComRCReturn(hrc, hrc);
2994 if (enmStorageBus == StorageBus_Floppy)
2995 {
2996
2997 /*
2998 * Found a floppy controller.
2999 */
3000 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3001 AssertComRCReturn(hrc, hrc);
3002
3003 /*
3004 * Check the attchments to see if we've got a device 0 attached on port 0.
3005 *
3006 * While we're at it we eject flppies from all floppy drives we encounter,
3007 * we don't want any confusion at boot or during installation.
3008 */
3009 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3010 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3011 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3012 AssertComRCReturn(hrc, hrc);
3013 strControllerName = bstrControllerName;
3014 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3015
3016 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3017 {
3018 LONG iPort = -1;
3019 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3020 AssertComRCReturn(hrc, hrc);
3021
3022 LONG iDevice = -1;
3023 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3024 AssertComRCReturn(hrc, hrc);
3025
3026 DeviceType_T enmType;
3027 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3028 AssertComRCReturn(hrc, hrc);
3029
3030 if (enmType == DeviceType_Floppy)
3031 {
3032 ComPtr<IMedium> ptrMedium;
3033 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3034 AssertComRCReturn(hrc, hrc);
3035
3036 if (ptrMedium.isNotNull())
3037 {
3038 ptrMedium.setNull();
3039 rAutoLock.release();
3040 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3041 rAutoLock.acquire();
3042 }
3043
3044 if (iPort == 0 && iDevice == 0)
3045 fFoundPort0Dev0 = true;
3046 }
3047 else if (iPort == 0 && iDevice == 0)
3048 return setError(E_FAIL,
3049 tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"),
3050 bstrControllerName.raw());
3051 }
3052 }
3053 }
3054
3055 /*
3056 * Add a floppy controller if we need to.
3057 */
3058 if (strControllerName.isEmpty())
3059 {
3060 bstrControllerName = strControllerName = "Floppy";
3061 ComPtr<IStorageController> ptrControllerIgnored;
3062 HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy,
3063 ptrControllerIgnored.asOutParam());
3064 LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc));
3065 if (FAILED(hrc))
3066 return hrc;
3067 }
3068
3069 /*
3070 * Adding a floppy drive (if needed) and mounting the auxiliary image is
3071 * done later together with the ISOs.
3072 */
3073 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName,
3074 DeviceType_Floppy, AccessMode_ReadWrite,
3075 0, 0,
3076 fFoundPort0Dev0 /*fMountOnly*/,
3077 mpInstaller->getAuxiliaryFloppyFilePath(), false));
3078 return S_OK;
3079}
3080
3081/**
3082 * Reconfigures DVD drives of the VM to mount all the ISOs we need.
3083 *
3084 * This will umount all DVD media.
3085 *
3086 * @returns COM status code.
3087 * @param rControllers The existing controllers.
3088 * @param rVecInstallatationDisks The list of image to mount.
3089 * @param rPtrSessionMachine The session machine smart pointer.
3090 * @param rAutoLock The lock.
3091 * @param enmRecommendedStorageBus The recommended storage bus type for adding
3092 * DVD drives on.
3093 */
3094HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
3095 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
3096 ComPtr<IMachine> const &rPtrSessionMachine,
3097 AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus)
3098{
3099 /*
3100 * Enumerate the attachements of every controller, looking for DVD drives,
3101 * ASSUMEING all drives are bootable.
3102 *
3103 * Eject the medium from all the drives (don't want any confusion) and look
3104 * for the recommended storage bus in case we need to add more drives.
3105 */
3106 HRESULT hrc;
3107 std::list<ControllerSlot> lstControllerDvdSlots;
3108 Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */
3109 Utf8Str strControllerName;
3110 Bstr bstrControllerName;
3111 for (size_t i = 0; i < rControllers.size(); ++i)
3112 {
3113 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3114 AssertComRCReturn(hrc, hrc);
3115 strControllerName = bstrControllerName;
3116
3117 /* Look for recommended storage bus. */
3118 StorageBus_T enmStorageBus;
3119 hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3120 AssertComRCReturn(hrc, hrc);
3121 if (enmStorageBus == enmRecommendedStorageBus)
3122 {
3123 strRecommendedControllerName = bstrControllerName;
3124 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3125 }
3126
3127 /* Scan the controller attachments. */
3128 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3129 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3130 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3131 AssertComRCReturn(hrc, hrc);
3132
3133 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3134 {
3135 DeviceType_T enmType;
3136 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3137 AssertComRCReturn(hrc, hrc);
3138 if (enmType == DeviceType_DVD)
3139 {
3140 LONG iPort = -1;
3141 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3142 AssertComRCReturn(hrc, hrc);
3143
3144 LONG iDevice = -1;
3145 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3146 AssertComRCReturn(hrc, hrc);
3147
3148 /* Remeber it. */
3149 lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/));
3150
3151 /* Eject the medium, if any. */
3152 ComPtr<IMedium> ptrMedium;
3153 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3154 AssertComRCReturn(hrc, hrc);
3155 if (ptrMedium.isNotNull())
3156 {
3157 ptrMedium.setNull();
3158
3159 rAutoLock.release();
3160 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3161 rAutoLock.acquire();
3162 }
3163 }
3164 }
3165 }
3166
3167 /*
3168 * How many drives do we need? Add more if necessary.
3169 */
3170 ULONG cDvdDrivesNeeded = 0;
3171 if (mpInstaller->isAuxiliaryIsoNeeded())
3172 cDvdDrivesNeeded++;
3173 if (mpInstaller->isOriginalIsoNeeded())
3174 cDvdDrivesNeeded++;
3175#if 0 /* These are now in the AUX VISO. */
3176 if (mpInstaller->isAdditionsIsoNeeded())
3177 cDvdDrivesNeeded++;
3178 if (mpInstaller->isValidationKitIsoNeeded())
3179 cDvdDrivesNeeded++;
3180#endif
3181 Assert(cDvdDrivesNeeded > 0);
3182 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3183 {
3184 /* Do we need to add the recommended controller? */
3185 if (strRecommendedControllerName.isEmpty())
3186 {
3187 strRecommendedControllerName = StorageController::i_controllerNameFromBusType(enmRecommendedStorageBus);
3188
3189 ComPtr<IStorageController> ptrControllerIgnored;
3190 hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus,
3191 ptrControllerIgnored.asOutParam());
3192 LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc));
3193 if (FAILED(hrc))
3194 return hrc;
3195 }
3196
3197 /* Add free controller slots, maybe raising the port limit on the controller if we can. */
3198 hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine,
3199 cDvdDrivesNeeded, lstControllerDvdSlots);
3200 if (FAILED(hrc))
3201 return hrc;
3202 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3203 {
3204 /* We could in many cases create another controller here, but it's not worth the effort. */
3205 return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "",
3206 cDvdDrivesNeeded - lstControllerDvdSlots.size()),
3207 strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size());
3208 }
3209 Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size());
3210 }
3211
3212 /*
3213 * Sort the DVD slots in boot order.
3214 */
3215 lstControllerDvdSlots.sort();
3216
3217 /*
3218 * Prepare ISO mounts.
3219 *
3220 * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots
3221 * according to the boot order.
3222 */
3223 std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin();
3224 if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso())
3225 {
3226 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3227 ++itDvdSlot;
3228 }
3229
3230 if (mpInstaller->isOriginalIsoNeeded())
3231 {
3232 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath(), false));
3233 ++itDvdSlot;
3234 }
3235
3236 if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso())
3237 {
3238 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3239 ++itDvdSlot;
3240 }
3241
3242#if 0 /* These are now in the AUX VISO. */
3243 if (mpInstaller->isAdditionsIsoNeeded())
3244 {
3245 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath(), false));
3246 ++itDvdSlot;
3247 }
3248
3249 if (mpInstaller->isValidationKitIsoNeeded())
3250 {
3251 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath(), false));
3252 ++itDvdSlot;
3253 }
3254#endif
3255
3256 return S_OK;
3257}
3258
3259/**
3260 * Used to find more free slots for DVD drives during VM reconfiguration.
3261 *
3262 * This may modify the @a portCount property of the given controller.
3263 *
3264 * @returns COM status code.
3265 * @param rStrControllerName The name of the controller to find/create
3266 * free slots on.
3267 * @param enmStorageBus The storage bus type.
3268 * @param rPtrSessionMachine Reference to the session machine.
3269 * @param cSlotsNeeded Total slots needed (including those we've
3270 * already found).
3271 * @param rDvdSlots The slot collection for DVD drives to add
3272 * free slots to as we find/create them.
3273 */
3274HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
3275 ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
3276 std::list<ControllerSlot> &rDvdSlots)
3277{
3278 Assert(cSlotsNeeded > rDvdSlots.size());
3279
3280 /*
3281 * Get controlleer stats.
3282 */
3283 ComPtr<IStorageController> pController;
3284 HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam());
3285 AssertComRCReturn(hrc, hrc);
3286
3287 ULONG cMaxDevicesPerPort = 1;
3288 hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort);
3289 AssertComRCReturn(hrc, hrc);
3290 AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED);
3291
3292 ULONG cPorts = 0;
3293 hrc = pController->COMGETTER(PortCount)(&cPorts);
3294 AssertComRCReturn(hrc, hrc);
3295
3296 /*
3297 * Get the attachment list and turn into an internal list for lookup speed.
3298 */
3299 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3300 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(),
3301 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3302 AssertComRCReturn(hrc, hrc);
3303
3304 std::vector<ControllerSlot> arrayOfUsedSlots;
3305 for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++)
3306 {
3307 LONG iPort = -1;
3308 hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort);
3309 AssertComRCReturn(hrc, hrc);
3310
3311 LONG iDevice = -1;
3312 hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice);
3313 AssertComRCReturn(hrc, hrc);
3314
3315 arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/));
3316 }
3317
3318 /*
3319 * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots.
3320 */
3321 for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++)
3322 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3323 {
3324 bool fFound = false;
3325 for (size_t i = 0; i < arrayOfUsedSlots.size(); i++)
3326 if ( arrayOfUsedSlots[i].iPort == iPort
3327 && arrayOfUsedSlots[i].iDevice == iDevice)
3328 {
3329 fFound = true;
3330 break;
3331 }
3332 if (!fFound)
3333 {
3334 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3335 if (rDvdSlots.size() >= cSlotsNeeded)
3336 return S_OK;
3337 }
3338 }
3339
3340 /*
3341 * Okay we still need more ports. See if increasing the number of controller
3342 * ports would solve it.
3343 */
3344 ULONG cMaxPorts = 1;
3345 hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts);
3346 AssertComRCReturn(hrc, hrc);
3347 if (cMaxPorts <= cPorts)
3348 return S_OK;
3349 size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort;
3350 if (cPorts + cNewPortsNeeded > cMaxPorts)
3351 return S_OK;
3352
3353 /*
3354 * Raise the port count and add the free slots we've just created.
3355 */
3356 hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded);
3357 AssertComRCReturn(hrc, hrc);
3358 int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded);
3359 for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++)
3360 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3361 {
3362 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3363 if (rDvdSlots.size() >= cSlotsNeeded)
3364 return S_OK;
3365 }
3366
3367 /* We should not get here! */
3368 AssertLogRelFailedReturn(E_UNEXPECTED);
3369}
3370
3371HRESULT Unattended::done()
3372{
3373 LogFlow(("Unattended::done\n"));
3374 if (mpInstaller)
3375 {
3376 LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller));
3377 delete mpInstaller;
3378 mpInstaller = NULL;
3379 }
3380 return S_OK;
3381}
3382
3383HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
3384{
3385 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3386 isoPath = mStrIsoPath;
3387 return S_OK;
3388}
3389
3390HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
3391{
3392 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3393 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3394 mStrIsoPath = isoPath;
3395 mfDoneDetectIsoOS = false;
3396 return S_OK;
3397}
3398
3399HRESULT Unattended::getUser(com::Utf8Str &user)
3400{
3401 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3402 user = mStrUser;
3403 return S_OK;
3404}
3405
3406
3407HRESULT Unattended::setUser(const com::Utf8Str &user)
3408{
3409 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3410 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3411 mStrUser = user;
3412 return S_OK;
3413}
3414
3415HRESULT Unattended::getPassword(com::Utf8Str &password)
3416{
3417 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3418 password = mStrPassword;
3419 return S_OK;
3420}
3421
3422HRESULT Unattended::setPassword(const com::Utf8Str &password)
3423{
3424 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3425 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3426 mStrPassword = password;
3427 return S_OK;
3428}
3429
3430HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
3431{
3432 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3433 fullUserName = mStrFullUserName;
3434 return S_OK;
3435}
3436
3437HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
3438{
3439 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3440 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3441 mStrFullUserName = fullUserName;
3442 return S_OK;
3443}
3444
3445HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
3446{
3447 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3448 productKey = mStrProductKey;
3449 return S_OK;
3450}
3451
3452HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
3453{
3454 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3455 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3456 mStrProductKey = productKey;
3457 return S_OK;
3458}
3459
3460HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
3461{
3462 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3463 additionsIsoPath = mStrAdditionsIsoPath;
3464 return S_OK;
3465}
3466
3467HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
3468{
3469 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3470 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3471 mStrAdditionsIsoPath = additionsIsoPath;
3472 return S_OK;
3473}
3474
3475HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
3476{
3477 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3478 *installGuestAdditions = mfInstallGuestAdditions;
3479 return S_OK;
3480}
3481
3482HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
3483{
3484 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3485 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3486 mfInstallGuestAdditions = installGuestAdditions != FALSE;
3487 return S_OK;
3488}
3489
3490HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
3491{
3492 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3493 aValidationKitIsoPath = mStrValidationKitIsoPath;
3494 return S_OK;
3495}
3496
3497HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
3498{
3499 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3500 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3501 mStrValidationKitIsoPath = aValidationKitIsoPath;
3502 return S_OK;
3503}
3504
3505HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
3506{
3507 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3508 *aInstallTestExecService = mfInstallTestExecService;
3509 return S_OK;
3510}
3511
3512HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
3513{
3514 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3515 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3516 mfInstallTestExecService = aInstallTestExecService != FALSE;
3517 return S_OK;
3518}
3519
3520HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
3521{
3522 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3523 aTimeZone = mStrTimeZone;
3524 return S_OK;
3525}
3526
3527HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
3528{
3529 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3530 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3531 mStrTimeZone = aTimezone;
3532 return S_OK;
3533}
3534
3535HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
3536{
3537 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3538 aLocale = mStrLocale;
3539 return S_OK;
3540}
3541
3542HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
3543{
3544 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3545 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3546 if ( aLocale.isEmpty() /* use default */
3547 || ( aLocale.length() == 5
3548 && RT_C_IS_LOWER(aLocale[0])
3549 && RT_C_IS_LOWER(aLocale[1])
3550 && aLocale[2] == '_'
3551 && RT_C_IS_UPPER(aLocale[3])
3552 && RT_C_IS_UPPER(aLocale[4])) )
3553 {
3554 mStrLocale = aLocale;
3555 return S_OK;
3556 }
3557 return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters"));
3558}
3559
3560HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
3561{
3562 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3563 aLanguage = mStrLanguage;
3564 return S_OK;
3565}
3566
3567HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
3568{
3569 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3570 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3571 mStrLanguage = aLanguage;
3572 return S_OK;
3573}
3574
3575HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
3576{
3577 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3578 aCountry = mStrCountry;
3579 return S_OK;
3580}
3581
3582HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
3583{
3584 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3585 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3586 if ( aCountry.isEmpty()
3587 || ( aCountry.length() == 2
3588 && RT_C_IS_UPPER(aCountry[0])
3589 && RT_C_IS_UPPER(aCountry[1])) )
3590 {
3591 mStrCountry = aCountry;
3592 return S_OK;
3593 }
3594 return setError(E_INVALIDARG, tr("Expected two upper cased letters"));
3595}
3596
3597HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
3598{
3599 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3600 aProxy = mStrProxy; /// @todo turn schema map into string or something.
3601 return S_OK;
3602}
3603
3604HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
3605{
3606 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3607 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3608 if (aProxy.isEmpty())
3609 {
3610 /* set default proxy */
3611 /** @todo BUGBUG! implement this */
3612 }
3613 else if (aProxy.equalsIgnoreCase("none"))
3614 {
3615 /* clear proxy config */
3616 mStrProxy.setNull();
3617 }
3618 else
3619 {
3620 /** @todo Parse and set proxy config into a schema map or something along those lines. */
3621 /** @todo BUGBUG! implement this */
3622 // return E_NOTIMPL;
3623 mStrProxy = aProxy;
3624 }
3625 return S_OK;
3626}
3627
3628HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
3629{
3630 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3631 aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";");
3632 return S_OK;
3633}
3634
3635HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
3636{
3637 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3638 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3639 if (aPackageSelectionAdjustments.isEmpty())
3640 mPackageSelectionAdjustments.clear();
3641 else
3642 {
3643 RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";");
3644 for (size_t i = 0; i < arrayStrSplit.size(); i++)
3645 {
3646 if (arrayStrSplit[i].equals("minimal"))
3647 { /* okay */ }
3648 else
3649 return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str());
3650 }
3651 mPackageSelectionAdjustments = arrayStrSplit;
3652 }
3653 return S_OK;
3654}
3655
3656HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
3657{
3658 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3659 aHostname = mStrHostname;
3660 return S_OK;
3661}
3662
3663HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
3664{
3665 /*
3666 * Validate input.
3667 */
3668 if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U))
3669 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3670 tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()),
3671 aHostname.c_str(), aHostname.length());
3672 size_t cLabels = 0;
3673 const char *pszSrc = aHostname.c_str();
3674 for (;;)
3675 {
3676 size_t cchLabel = 1;
3677 char ch = *pszSrc++;
3678 if (RT_C_IS_ALNUM(ch))
3679 {
3680 cLabels++;
3681 while ((ch = *pszSrc++) != '.' && ch != '\0')
3682 {
3683 if (RT_C_IS_ALNUM(ch) || ch == '-')
3684 {
3685 if (cchLabel < 63)
3686 cchLabel++;
3687 else
3688 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3689 tr("Invalid hostname '%s' - label %u is too long, max is 63."),
3690 aHostname.c_str(), cLabels);
3691 }
3692 else
3693 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3694 tr("Invalid hostname '%s' - illegal char '%c' at position %zu"),
3695 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3696 }
3697 if (cLabels == 1 && cchLabel < 2)
3698 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3699 tr("Invalid hostname '%s' - the name part must be at least two characters long"),
3700 aHostname.c_str());
3701 if (ch == '\0')
3702 break;
3703 }
3704 else if (ch != '\0')
3705 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3706 tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"),
3707 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3708 else
3709 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3710 tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str());
3711 }
3712 if (cLabels < 2)
3713 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3714 tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str());
3715
3716 /*
3717 * Make the change.
3718 */
3719 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3720 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3721 mStrHostname = aHostname;
3722 return S_OK;
3723}
3724
3725HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
3726{
3727 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3728 aAuxiliaryBasePath = mStrAuxiliaryBasePath;
3729 return S_OK;
3730}
3731
3732HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
3733{
3734 if (aAuxiliaryBasePath.isEmpty())
3735 return setError(E_INVALIDARG, tr("Empty base path is not allowed"));
3736 if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str()))
3737 return setError(E_INVALIDARG, tr("Base path must be absolute"));
3738
3739 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3740 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3741 mStrAuxiliaryBasePath = aAuxiliaryBasePath;
3742 mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty();
3743 return S_OK;
3744}
3745
3746HRESULT Unattended::getImageIndex(ULONG *index)
3747{
3748 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3749 *index = midxImage;
3750 return S_OK;
3751}
3752
3753HRESULT Unattended::setImageIndex(ULONG index)
3754{
3755 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3756 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3757
3758 /* Validate the selection if detection was done already: */
3759 if (mDetectedImages.size() > 0)
3760 {
3761 for (size_t i = 0; i < mDetectedImages.size(); i++)
3762 if (mDetectedImages[i].mImageIndex == index)
3763 {
3764 midxImage = index;
3765 i_updateDetectedAttributeForImage(mDetectedImages[i]);
3766 return S_OK;
3767 }
3768 LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */
3769 }
3770
3771 midxImage = index;
3772 return S_OK;
3773}
3774
3775HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
3776{
3777 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3778 return mMachine.queryInterfaceTo(aMachine.asOutParam());
3779}
3780
3781HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
3782{
3783 /*
3784 * Lookup the VM so we can safely get the Machine instance.
3785 * (Don't want to test how reliable XPCOM and COM are with finding
3786 * the local object instance when a client passes a stub back.)
3787 */
3788 Bstr bstrUuidMachine;
3789 HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam());
3790 if (SUCCEEDED(hrc))
3791 {
3792 Guid UuidMachine(bstrUuidMachine);
3793 ComObjPtr<Machine> ptrMachine;
3794 hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
3795 if (SUCCEEDED(hrc))
3796 {
3797 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3798 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER,
3799 tr("Cannot change after prepare() has been called")));
3800 mMachine = ptrMachine;
3801 mMachineUuid = UuidMachine;
3802 if (mfIsDefaultAuxiliaryBasePath)
3803 mStrAuxiliaryBasePath.setNull();
3804 hrc = S_OK;
3805 }
3806 }
3807 return hrc;
3808}
3809
3810HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
3811{
3812 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3813 if ( mStrScriptTemplatePath.isNotEmpty()
3814 || mpInstaller == NULL)
3815 aScriptTemplatePath = mStrScriptTemplatePath;
3816 else
3817 aScriptTemplatePath = mpInstaller->getTemplateFilePath();
3818 return S_OK;
3819}
3820
3821HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
3822{
3823 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3824 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3825 mStrScriptTemplatePath = aScriptTemplatePath;
3826 return S_OK;
3827}
3828
3829HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
3830{
3831 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3832 if ( mStrPostInstallScriptTemplatePath.isNotEmpty()
3833 || mpInstaller == NULL)
3834 aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath;
3835 else
3836 aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath();
3837 return S_OK;
3838}
3839
3840HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
3841{
3842 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3843 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3844 mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath;
3845 return S_OK;
3846}
3847
3848HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
3849{
3850 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3851 aPostInstallCommand = mStrPostInstallCommand;
3852 return S_OK;
3853}
3854
3855HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
3856{
3857 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3858 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3859 mStrPostInstallCommand = aPostInstallCommand;
3860 return S_OK;
3861}
3862
3863HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
3864{
3865 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3866 if ( mStrExtraInstallKernelParameters.isNotEmpty()
3867 || mpInstaller == NULL)
3868 aExtraInstallKernelParameters = mStrExtraInstallKernelParameters;
3869 else
3870 aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters();
3871 return S_OK;
3872}
3873
3874HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
3875{
3876 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3877 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3878 mStrExtraInstallKernelParameters = aExtraInstallKernelParameters;
3879 return S_OK;
3880}
3881
3882HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
3883{
3884 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3885 aDetectedOSTypeId = mStrDetectedOSTypeId;
3886 return S_OK;
3887}
3888
3889HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
3890{
3891 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3892 aDetectedOSVersion = mStrDetectedOSVersion;
3893 return S_OK;
3894}
3895
3896HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
3897{
3898 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3899 aDetectedOSFlavor = mStrDetectedOSFlavor;
3900 return S_OK;
3901}
3902
3903HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
3904{
3905 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3906 aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " ");
3907 return S_OK;
3908}
3909
3910HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
3911{
3912 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3913 aDetectedOSHints = mStrDetectedOSHints;
3914 return S_OK;
3915}
3916
3917HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
3918{
3919 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3920 aDetectedImageNames.clear();
3921 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3922 {
3923 Utf8Str strTmp;
3924 aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp));
3925 }
3926 return S_OK;
3927}
3928
3929HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
3930{
3931 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3932 aDetectedImageIndices.clear();
3933 for (size_t i = 0; i < mDetectedImages.size(); ++i)
3934 aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex);
3935 return S_OK;
3936}
3937
3938HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
3939{
3940 /*
3941 * Take the initial position that it's not supported, so we can return
3942 * right away when we decide it's not possible.
3943 */
3944 *aIsUnattendedInstallSupported = false;
3945
3946 /* Unattended is disabled by default if we could not detect OS type. */
3947 if (mStrDetectedOSTypeId.isEmpty())
3948 return S_OK;
3949
3950 /* For now we don't support unattended installation for ARM guests. */
3951 const VBOXOSTYPE enmArchitectureMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_ArchitectureMask);
3952 if ( enmArchitectureMasked == VBOXOSTYPE_arm32
3953 || enmArchitectureMasked == VBOXOSTYPE_arm64)
3954 return S_OK;
3955
3956 const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask);
3957
3958 /* We require a version to have been detected, except for windows where the
3959 field is generally only used for the service pack number at present and
3960 will be empty for RTMs isos. */
3961 if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT
3962 || enmOsTypeMasked >= VBOXOSTYPE_OS2)
3963 && mStrDetectedOSVersion.isEmpty())
3964 return S_OK;
3965
3966 /*
3967 * Sort out things that we know doesn't work. Order by VBOXOSTYPE value.
3968 */
3969
3970 /* We do not support any of the DOS based windows version, nor DOS, in case
3971 any of that gets detected (it shouldn't): */
3972 if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT)
3973 return S_OK;
3974
3975 /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */
3976 if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4)
3977 return S_OK;
3978
3979 /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been
3980 tested, but we'll get to the others eventually): */
3981 if ( enmOsTypeMasked >= VBOXOSTYPE_OS2
3982 && enmOsTypeMasked < VBOXOSTYPE_Linux
3983 && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ )
3984 return S_OK;
3985
3986 /* Old Debians fail since package repos have been move to some other mirror location. */
3987 if ( enmOsTypeMasked == VBOXOSTYPE_Debian
3988 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0)
3989 return S_OK;
3990
3991 /* Skip all OpenSUSE variants for now. */
3992 if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE)
3993 return S_OK;
3994
3995 if (enmOsTypeMasked == VBOXOSTYPE_Ubuntu)
3996 {
3997 /* We cannot install Ubuntus older than 11.04. */
3998 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0)
3999 return S_OK;
4000 /* Lubuntu, starting with 20.04, has switched to calamares, which cannot be automated. */
4001 if ( RTStrIStr(mStrDetectedOSFlavor.c_str(), "lubuntu")
4002 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "20.04") > 0)
4003 return S_OK;
4004 }
4005
4006 /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */
4007 if ( enmOsTypeMasked == VBOXOSTYPE_Oracle
4008 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0)
4009 return S_OK;
4010
4011 /* Fredora ISOs cannot be installed at present. */
4012 if (enmOsTypeMasked == VBOXOSTYPE_FedoraCore)
4013 return S_OK;
4014
4015 /*
4016 * Assume the rest works.
4017 */
4018 *aIsUnattendedInstallSupported = true;
4019 return S_OK;
4020}
4021
4022HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
4023{
4024 *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork;
4025 return S_OK;
4026}
4027
4028HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
4029{
4030 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4031 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4032 mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork);
4033 return S_OK;
4034}
4035
4036/*
4037 * Getters that the installer and script classes can use.
4038 */
4039Utf8Str const &Unattended::i_getIsoPath() const
4040{
4041 Assert(isReadLockedOnCurrentThread());
4042 return mStrIsoPath;
4043}
4044
4045Utf8Str const &Unattended::i_getUser() const
4046{
4047 Assert(isReadLockedOnCurrentThread());
4048 return mStrUser;
4049}
4050
4051Utf8Str const &Unattended::i_getPassword() const
4052{
4053 Assert(isReadLockedOnCurrentThread());
4054 return mStrPassword;
4055}
4056
4057Utf8Str const &Unattended::i_getFullUserName() const
4058{
4059 Assert(isReadLockedOnCurrentThread());
4060 return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
4061}
4062
4063Utf8Str const &Unattended::i_getProductKey() const
4064{
4065 Assert(isReadLockedOnCurrentThread());
4066 return mStrProductKey;
4067}
4068
4069Utf8Str const &Unattended::i_getProxy() const
4070{
4071 Assert(isReadLockedOnCurrentThread());
4072 return mStrProxy;
4073}
4074
4075Utf8Str const &Unattended::i_getAdditionsIsoPath() const
4076{
4077 Assert(isReadLockedOnCurrentThread());
4078 return mStrAdditionsIsoPath;
4079}
4080
4081bool Unattended::i_getInstallGuestAdditions() const
4082{
4083 Assert(isReadLockedOnCurrentThread());
4084 return mfInstallGuestAdditions;
4085}
4086
4087Utf8Str const &Unattended::i_getValidationKitIsoPath() const
4088{
4089 Assert(isReadLockedOnCurrentThread());
4090 return mStrValidationKitIsoPath;
4091}
4092
4093bool Unattended::i_getInstallTestExecService() const
4094{
4095 Assert(isReadLockedOnCurrentThread());
4096 return mfInstallTestExecService;
4097}
4098
4099Utf8Str const &Unattended::i_getTimeZone() const
4100{
4101 Assert(isReadLockedOnCurrentThread());
4102 return mStrTimeZone;
4103}
4104
4105PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
4106{
4107 Assert(isReadLockedOnCurrentThread());
4108 return mpTimeZoneInfo;
4109}
4110
4111Utf8Str const &Unattended::i_getLocale() const
4112{
4113 Assert(isReadLockedOnCurrentThread());
4114 return mStrLocale;
4115}
4116
4117Utf8Str const &Unattended::i_getLanguage() const
4118{
4119 Assert(isReadLockedOnCurrentThread());
4120 return mStrLanguage;
4121}
4122
4123Utf8Str const &Unattended::i_getCountry() const
4124{
4125 Assert(isReadLockedOnCurrentThread());
4126 return mStrCountry;
4127}
4128
4129bool Unattended::i_isMinimalInstallation() const
4130{
4131 size_t i = mPackageSelectionAdjustments.size();
4132 while (i-- > 0)
4133 if (mPackageSelectionAdjustments[i].equals("minimal"))
4134 return true;
4135 return false;
4136}
4137
4138Utf8Str const &Unattended::i_getHostname() const
4139{
4140 Assert(isReadLockedOnCurrentThread());
4141 return mStrHostname;
4142}
4143
4144Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
4145{
4146 Assert(isReadLockedOnCurrentThread());
4147 return mStrAuxiliaryBasePath;
4148}
4149
4150ULONG Unattended::i_getImageIndex() const
4151{
4152 Assert(isReadLockedOnCurrentThread());
4153 return midxImage;
4154}
4155
4156Utf8Str const &Unattended::i_getScriptTemplatePath() const
4157{
4158 Assert(isReadLockedOnCurrentThread());
4159 return mStrScriptTemplatePath;
4160}
4161
4162Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
4163{
4164 Assert(isReadLockedOnCurrentThread());
4165 return mStrPostInstallScriptTemplatePath;
4166}
4167
4168Utf8Str const &Unattended::i_getPostInstallCommand() const
4169{
4170 Assert(isReadLockedOnCurrentThread());
4171 return mStrPostInstallCommand;
4172}
4173
4174Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
4175{
4176 Assert(isReadLockedOnCurrentThread());
4177 /* Only the installer knows, forward the call. */
4178 AssertReturn(mpInstaller != NULL, Utf8Str::Empty);
4179 return mpInstaller->getAuxiliaryInstallDir();
4180}
4181
4182Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
4183{
4184 Assert(isReadLockedOnCurrentThread());
4185 return mStrExtraInstallKernelParameters;
4186}
4187
4188bool Unattended::i_isRtcUsingUtc() const
4189{
4190 Assert(isReadLockedOnCurrentThread());
4191 return mfRtcUseUtc;
4192}
4193
4194bool Unattended::i_isGuestOs64Bit() const
4195{
4196 Assert(isReadLockedOnCurrentThread());
4197 return mfGuestOs64Bit;
4198}
4199
4200bool Unattended::i_isFirmwareEFI() const
4201{
4202 Assert(isReadLockedOnCurrentThread());
4203 return menmFirmwareType != FirmwareType_BIOS;
4204}
4205
4206Utf8Str const &Unattended::i_getDetectedOSVersion()
4207{
4208 Assert(isReadLockedOnCurrentThread());
4209 return mStrDetectedOSVersion;
4210}
4211
4212bool Unattended::i_getAvoidUpdatesOverNetwork() const
4213{
4214 Assert(isReadLockedOnCurrentThread());
4215 return mfAvoidUpdatesOverNetwork;
4216}
4217
4218HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
4219 AutoMultiWriteLock2 &rLock)
4220{
4221 /*
4222 * Attach the disk image
4223 * HACK ALERT! Temporarily release the Unattended lock.
4224 */
4225 rLock.release();
4226
4227 ComPtr<IMedium> ptrMedium;
4228 HRESULT hrc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(),
4229 pImage->enmDeviceType,
4230 pImage->enmAccessType,
4231 true,
4232 ptrMedium.asOutParam());
4233 LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", hrc));
4234 if (SUCCEEDED(hrc))
4235 {
4236 if (pImage->fAuxiliary && pImage->strImagePath.endsWith(".viso"))
4237 {
4238 hrc = ptrMedium->SetProperty(Bstr("UnattendedInstall").raw(), Bstr("1").raw());
4239 LogRelFlowFunc(("Medium::SetProperty -> %Rhrc\n", hrc));
4240 }
4241 if (pImage->fMountOnly)
4242 {
4243 // mount the opened disk image
4244 hrc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4245 pImage->iDevice, ptrMedium, TRUE /*fForce*/);
4246 LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", hrc));
4247 }
4248 else
4249 {
4250 //attach the opened disk image to the controller
4251 hrc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4252 pImage->iDevice, pImage->enmDeviceType, ptrMedium);
4253 LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", hrc));
4254 }
4255 }
4256
4257 rLock.acquire();
4258 return hrc;
4259}
4260
4261bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId)
4262{
4263 ComPtr<IGuestOSType> pGuestOSType;
4264 HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam());
4265 if (SUCCEEDED(hrc))
4266 {
4267 BOOL fIs64Bit = FALSE;
4268 if (!pGuestOSType.isNull())
4269 hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit);
4270 if (SUCCEEDED(hrc))
4271 return fIs64Bit != FALSE;
4272 }
4273 return false;
4274}
4275
4276
4277bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage)
4278{
4279 bool fRet = true;
4280
4281 /*
4282 * If the image doesn't have a valid value, we don't change it.
4283 * This is obviously a little bit bogus, but what can we do...
4284 */
4285 const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType);
4286 if (pszOSTypeId && !RTStrStartsWith(pszOSTypeId, GUEST_OS_ID_STR_PARTIAL("Other"))) /** @todo set x64/a64 other variants or not? */
4287 mStrDetectedOSTypeId = pszOSTypeId;
4288 else
4289 fRet = false;
4290
4291 if (rImage.mVersion.isNotEmpty())
4292 mStrDetectedOSVersion = rImage.mVersion;
4293 else
4294 fRet = false;
4295
4296 if (rImage.mFlavor.isNotEmpty())
4297 mStrDetectedOSFlavor = rImage.mFlavor;
4298 else
4299 fRet = false;
4300
4301 if (rImage.mLanguages.size() > 0)
4302 mDetectedOSLanguages = rImage.mLanguages;
4303 else
4304 fRet = false;
4305
4306 mEnmOsType = rImage.mEnmOsType;
4307
4308 return fRet;
4309}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use