VirtualBox

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

最後變更 在這個檔案從95436是 95436,由 vboxsync 提交於 3 年 前

Main/UnattendedInstall: Minimalistic support for FreeBSD >= 10.0 sitting in my tree for too long, bugref:9781

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

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