VirtualBox

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

Last change on this file since 96706 was 96706, checked in by vboxsync, 3 years ago

Unattended: bugref:9781. Some modifications on OS detection and menu config file editing to enable unattended install for Linux Mint.

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

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