VirtualBox

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

Last change on this file since 102797 was 102534, checked in by vboxsync, 13 months ago

Main/Unattended: Renamed the attribute "IUnattended::password" to "IUnattended::userPassword". Added new getter/setter attribute "IUnattended::adminPassword", to set a dedicated admin / root password. If not specified explicitly, the password from "IUnattended::userPassword" will be used. Extended testcases. bugref:10554

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

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