VirtualBox

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

Last change on this file was 103109, checked in by vboxsync, 3 months ago

Main/UnattendedImpl.cpp: Disabled confusing error message from r159317. bugref:10516

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 175.2 KB
Line 
1/* $Id: UnattendedImpl.cpp 103109 2024-01-29 16:19:13Z 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#if 0 /** @todo r=bird: Unused, see below. */
2664 uint32_t const fMachineOsHints = idxMachineOSType < Global::cOSTypes
2665 ? Global::sOSTypes[idxMachineOSType].osHint : 0;
2666#endif
2667
2668 /*
2669 * Check that the detected guest OS type for the ISO is compatible with
2670 * that of the VM, broadly speaking.
2671 */
2672 if (idxMachineOSType != idxIsoOSType)
2673 {
2674 /* Check that the architecture is compatible: */
2675 if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask)
2676 && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86
2677 || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64))
2678 return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch"));
2679
2680 /** @todo check BIOS/EFI requirement */
2681 }
2682
2683#if 0 /** @todo r=bird: this is misleading, since it is checking the machine. */
2684 /* We don't support guest OSes w/ EFI, as that requires UDF remastering support we don't have yet. */
2685 if ( (fMachineOsHints & VBOXOSHINT_EFI)
2686 && (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_arm64)
2687 return setError(E_FAIL, tr("The machine is configured with EFI which is currently not supported by unatteded installation"));
2688#endif
2689
2690 /* Set the guest additions install package name. */
2691 mStrAdditionsInstallPackage = Global::sOSTypes[idxMachineOSType].guestAdditionsInstallPkgName;
2692
2693 /*
2694 * Do some default property stuff and check other properties.
2695 */
2696 try
2697 {
2698 char szTmp[128];
2699
2700 if (mStrLocale.isEmpty())
2701 {
2702 int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp));
2703 if ( RT_SUCCESS(vrc)
2704 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp))
2705 mStrLocale.assign(szTmp, 5);
2706 else
2707 mStrLocale = "en_US";
2708 Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale));
2709 }
2710
2711 if (mStrLanguage.isEmpty())
2712 {
2713 if (mDetectedOSLanguages.size() > 0)
2714 mStrLanguage = mDetectedOSLanguages[0];
2715 else
2716 mStrLanguage.assign(mStrLocale).findReplace('_', '-');
2717 }
2718
2719 if (mStrCountry.isEmpty())
2720 {
2721 int vrc = RTLocaleQueryUserCountryCode(szTmp);
2722 if (RT_SUCCESS(vrc))
2723 mStrCountry = szTmp;
2724 else if ( mStrLocale.isNotEmpty()
2725 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale))
2726 mStrCountry.assign(mStrLocale, 3, 2);
2727 else
2728 mStrCountry = "US";
2729 }
2730
2731 if (mStrTimeZone.isEmpty())
2732 {
2733 int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp));
2734 if ( RT_SUCCESS(vrc)
2735 && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */)
2736 mStrTimeZone = szTmp;
2737 else
2738 mStrTimeZone = "Etc/UTC";
2739 Assert(mStrTimeZone.isNotEmpty());
2740 }
2741 mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str());
2742 if (!mpTimeZoneInfo)
2743 mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str());
2744 Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC");
2745 if (!mpTimeZoneInfo)
2746 LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str()));
2747
2748 if (mStrHostname.isEmpty())
2749 {
2750 /* Mangle the VM name into a valid hostname. */
2751 for (size_t i = 0; i < strMachineName.length(); i++)
2752 {
2753 char ch = strMachineName[i];
2754 if ( (unsigned)ch < 127
2755 && RT_C_IS_ALNUM(ch))
2756 mStrHostname.append(ch);
2757 else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-"))
2758 mStrHostname.append('-');
2759 }
2760 if (mStrHostname.length() == 0)
2761 mStrHostname.printf("%RTuuid-vm", MachineUuid.raw());
2762 else if (mStrHostname.length() < 3)
2763 mStrHostname.append("-vm");
2764 mStrHostname.append(".myguest.virtualbox.org");
2765 }
2766
2767 if (mStrAuxiliaryBasePath.isEmpty())
2768 {
2769 mStrAuxiliaryBasePath = strDefaultAuxBasePath;
2770 mfIsDefaultAuxiliaryBasePath = true;
2771 }
2772 }
2773 catch (std::bad_alloc &)
2774 {
2775 return E_OUTOFMEMORY;
2776 }
2777
2778 /*
2779 * Instatiate the guest installer matching the ISO.
2780 */
2781 mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion,
2782 mStrDetectedOSFlavor, mStrDetectedOSHints, this);
2783 if (mpInstaller != NULL)
2784 {
2785 hrc = mpInstaller->initInstaller();
2786 if (SUCCEEDED(hrc))
2787 {
2788 /*
2789 * Do the script preps (just reads them).
2790 */
2791 hrc = mpInstaller->prepareUnattendedScripts();
2792 if (SUCCEEDED(hrc))
2793 {
2794 LogFlow(("Unattended::prepare: returns S_OK\n"));
2795 return S_OK;
2796 }
2797 }
2798
2799 /* Destroy the installer instance. */
2800 delete mpInstaller;
2801 mpInstaller = NULL;
2802 }
2803 else
2804 hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND,
2805 tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str());
2806 LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc));
2807 return hrc;
2808}
2809
2810HRESULT Unattended::constructMedia()
2811{
2812 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2813
2814 LogFlow(("===========================================================\n"));
2815 LogFlow(("Call Unattended::constructMedia()\n"));
2816
2817 if (mpInstaller == NULL)
2818 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called");
2819
2820 return mpInstaller->prepareMedia();
2821}
2822
2823HRESULT Unattended::reconfigureVM()
2824{
2825 LogFlow(("===========================================================\n"));
2826 LogFlow(("Call Unattended::reconfigureVM()\n"));
2827
2828 /*
2829 * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues.
2830 */
2831 StorageBus_T enmRecommendedStorageBus = StorageBus_IDE;
2832 {
2833 Bstr bstrGuestOsTypeId;
2834 Bstr bstrDetectedOSTypeId;
2835 {
2836 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2837 if (mpInstaller == NULL)
2838 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2839 bstrGuestOsTypeId = mStrGuestOsTypeId;
2840 bstrDetectedOSTypeId = mStrDetectedOSTypeId;
2841 }
2842 ComPtr<IGuestOSType> ptrGuestOSType;
2843 HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam());
2844 if (SUCCEEDED(hrc))
2845 {
2846 if (!ptrGuestOSType.isNull())
2847 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus);
2848 }
2849 if (FAILED(hrc))
2850 return hrc;
2851
2852 /* If the detected guest OS type differs, log a warning if their DVD storage
2853 bus recommendations differ. */
2854 if (bstrGuestOsTypeId != bstrDetectedOSTypeId)
2855 {
2856 StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE;
2857 hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam());
2858 if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull())
2859 hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2);
2860 if (FAILED(hrc))
2861 return hrc;
2862
2863 if (enmRecommendedStorageBus != enmRecommendedStorageBus2)
2864 LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n",
2865 ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(),
2866 ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() ));
2867 }
2868 }
2869
2870 /*
2871 * Take write lock (for lock order reasons, write lock our parent object too)
2872 * then make sure we're the only caller of this method.
2873 */
2874 AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS);
2875 HRESULT hrc;
2876 if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD)
2877 {
2878 RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf();
2879 mhThreadReconfigureVM = hNativeSelf;
2880
2881 /*
2882 * Create a new session, lock the machine and get the session machine object.
2883 * Do the locking without pinning down the write locks, just to be on the safe side.
2884 */
2885 ComPtr<ISession> ptrSession;
2886 try
2887 {
2888 hrc = ptrSession.createInprocObject(CLSID_Session);
2889 }
2890 catch (std::bad_alloc &)
2891 {
2892 hrc = E_OUTOFMEMORY;
2893 }
2894 if (SUCCEEDED(hrc))
2895 {
2896 alock.release();
2897 hrc = mMachine->LockMachine(ptrSession, LockType_Shared);
2898 alock.acquire();
2899 if (SUCCEEDED(hrc))
2900 {
2901 ComPtr<IMachine> ptrSessionMachine;
2902 hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam());
2903 if (SUCCEEDED(hrc))
2904 {
2905 /*
2906 * Hand the session to the inner work and let it do it job.
2907 */
2908 try
2909 {
2910 hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine);
2911 }
2912 catch (...)
2913 {
2914 hrc = E_UNEXPECTED;
2915 }
2916 }
2917
2918 /* Paranoia: release early in case we it a bump below. */
2919 Assert(mhThreadReconfigureVM == hNativeSelf);
2920 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2921
2922 /*
2923 * While unlocking the machine we'll have to drop the locks again.
2924 */
2925 alock.release();
2926
2927 ptrSessionMachine.setNull();
2928 HRESULT hrc2 = ptrSession->UnlockMachine();
2929 AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2));
2930
2931 ptrSession.setNull();
2932
2933 alock.acquire();
2934 }
2935 else
2936 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2937 }
2938 else
2939 mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
2940 }
2941 else
2942 hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread"));
2943 return hrc;
2944}
2945
2946
2947HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
2948 ComPtr<IMachine> const &rPtrSessionMachine)
2949{
2950 if (mpInstaller == NULL)
2951 return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
2952
2953 // Fetch all available storage controllers
2954 com::SafeIfaceArray<IStorageController> arrayOfControllers;
2955 HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers));
2956 AssertComRCReturn(hrc, hrc);
2957
2958 /*
2959 * Figure out where the images are to be mounted, adding controllers/ports as needed.
2960 */
2961 std::vector<UnattendedInstallationDisk> vecInstallationDisks;
2962 if (mpInstaller->isAuxiliaryFloppyNeeded())
2963 {
2964 hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock);
2965 if (FAILED(hrc))
2966 return hrc;
2967 }
2968
2969 hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus);
2970 if (FAILED(hrc))
2971 return hrc;
2972
2973 /*
2974 * Mount the images.
2975 */
2976 for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++)
2977 {
2978 UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage);
2979 Assert(pImage->strImagePath.isNotEmpty());
2980 hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock);
2981 if (FAILED(hrc))
2982 return hrc;
2983 }
2984
2985 /*
2986 * Set the boot order.
2987 *
2988 * ASSUME that the HD isn't bootable when we start out, but it will be what
2989 * we boot from after the first stage of the installation is done. Setting
2990 * it first prevents endless reboot cylces.
2991 */
2992 /** @todo consider making 100% sure the disk isn't bootable (edit partition
2993 * table active bits and EFI stuff). */
2994 Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD
2995 || mpInstaller->getBootableDeviceType() == DeviceType_Floppy);
2996 hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk);
2997 if (SUCCEEDED(hrc))
2998 hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType());
2999 if (SUCCEEDED(hrc))
3000 hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD
3001 ? DeviceType_Floppy : DeviceType_DVD);
3002 if (FAILED(hrc))
3003 return hrc;
3004
3005 /*
3006 * Essential step.
3007 *
3008 * HACK ALERT! We have to release the lock here or we'll get into trouble with
3009 * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType).
3010 */
3011 if (SUCCEEDED(hrc))
3012 {
3013 rAutoLock.release();
3014 hrc = rPtrSessionMachine->SaveSettings();
3015 rAutoLock.acquire();
3016 }
3017
3018 return hrc;
3019}
3020
3021/**
3022 * Makes sure we've got a floppy drive attached to a floppy controller, adding
3023 * the auxiliary floppy image to the installation disk vector.
3024 *
3025 * @returns COM status code.
3026 * @param rControllers The existing controllers.
3027 * @param rVecInstallatationDisks The list of image to mount.
3028 * @param rPtrSessionMachine The session machine smart pointer.
3029 * @param rAutoLock The lock.
3030 */
3031HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
3032 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
3033 ComPtr<IMachine> const &rPtrSessionMachine,
3034 AutoMultiWriteLock2 &rAutoLock)
3035{
3036 Assert(mpInstaller->isAuxiliaryFloppyNeeded());
3037
3038 /*
3039 * Look for a floppy controller with a primary drive (A:) we can "insert"
3040 * the auxiliary floppy image. Add a controller and/or a drive if necessary.
3041 */
3042 bool fFoundPort0Dev0 = false;
3043 Bstr bstrControllerName;
3044 Utf8Str strControllerName;
3045
3046 for (size_t i = 0; i < rControllers.size(); ++i)
3047 {
3048 StorageBus_T enmStorageBus;
3049 HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3050 AssertComRCReturn(hrc, hrc);
3051 if (enmStorageBus == StorageBus_Floppy)
3052 {
3053
3054 /*
3055 * Found a floppy controller.
3056 */
3057 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3058 AssertComRCReturn(hrc, hrc);
3059
3060 /*
3061 * Check the attchments to see if we've got a device 0 attached on port 0.
3062 *
3063 * While we're at it we eject flppies from all floppy drives we encounter,
3064 * we don't want any confusion at boot or during installation.
3065 */
3066 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3067 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3068 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3069 AssertComRCReturn(hrc, hrc);
3070 strControllerName = bstrControllerName;
3071 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3072
3073 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3074 {
3075 LONG iPort = -1;
3076 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3077 AssertComRCReturn(hrc, hrc);
3078
3079 LONG iDevice = -1;
3080 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3081 AssertComRCReturn(hrc, hrc);
3082
3083 DeviceType_T enmType;
3084 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3085 AssertComRCReturn(hrc, hrc);
3086
3087 if (enmType == DeviceType_Floppy)
3088 {
3089 ComPtr<IMedium> ptrMedium;
3090 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3091 AssertComRCReturn(hrc, hrc);
3092
3093 if (ptrMedium.isNotNull())
3094 {
3095 ptrMedium.setNull();
3096 rAutoLock.release();
3097 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3098 rAutoLock.acquire();
3099 }
3100
3101 if (iPort == 0 && iDevice == 0)
3102 fFoundPort0Dev0 = true;
3103 }
3104 else if (iPort == 0 && iDevice == 0)
3105 return setError(E_FAIL,
3106 tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"),
3107 bstrControllerName.raw());
3108 }
3109 }
3110 }
3111
3112 /*
3113 * Add a floppy controller if we need to.
3114 */
3115 if (strControllerName.isEmpty())
3116 {
3117 bstrControllerName = strControllerName = "Floppy";
3118 ComPtr<IStorageController> ptrControllerIgnored;
3119 HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy,
3120 ptrControllerIgnored.asOutParam());
3121 LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc));
3122 if (FAILED(hrc))
3123 return hrc;
3124 }
3125
3126 /*
3127 * Adding a floppy drive (if needed) and mounting the auxiliary image is
3128 * done later together with the ISOs.
3129 */
3130 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName,
3131 DeviceType_Floppy, AccessMode_ReadWrite,
3132 0, 0,
3133 fFoundPort0Dev0 /*fMountOnly*/,
3134 mpInstaller->getAuxiliaryFloppyFilePath(), false));
3135 return S_OK;
3136}
3137
3138/**
3139 * Reconfigures DVD drives of the VM to mount all the ISOs we need.
3140 *
3141 * This will umount all DVD media.
3142 *
3143 * @returns COM status code.
3144 * @param rControllers The existing controllers.
3145 * @param rVecInstallatationDisks The list of image to mount.
3146 * @param rPtrSessionMachine The session machine smart pointer.
3147 * @param rAutoLock The lock.
3148 * @param enmRecommendedStorageBus The recommended storage bus type for adding
3149 * DVD drives on.
3150 */
3151HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
3152 std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
3153 ComPtr<IMachine> const &rPtrSessionMachine,
3154 AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus)
3155{
3156 /*
3157 * Enumerate the attachements of every controller, looking for DVD drives,
3158 * ASSUMEING all drives are bootable.
3159 *
3160 * Eject the medium from all the drives (don't want any confusion) and look
3161 * for the recommended storage bus in case we need to add more drives.
3162 */
3163 HRESULT hrc;
3164 std::list<ControllerSlot> lstControllerDvdSlots;
3165 Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */
3166 Utf8Str strControllerName;
3167 Bstr bstrControllerName;
3168 for (size_t i = 0; i < rControllers.size(); ++i)
3169 {
3170 hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
3171 AssertComRCReturn(hrc, hrc);
3172 strControllerName = bstrControllerName;
3173
3174 /* Look for recommended storage bus. */
3175 StorageBus_T enmStorageBus;
3176 hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
3177 AssertComRCReturn(hrc, hrc);
3178 if (enmStorageBus == enmRecommendedStorageBus)
3179 {
3180 strRecommendedControllerName = bstrControllerName;
3181 AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
3182 }
3183
3184 /* Scan the controller attachments. */
3185 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3186 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
3187 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3188 AssertComRCReturn(hrc, hrc);
3189
3190 for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
3191 {
3192 DeviceType_T enmType;
3193 hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
3194 AssertComRCReturn(hrc, hrc);
3195 if (enmType == DeviceType_DVD)
3196 {
3197 LONG iPort = -1;
3198 hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
3199 AssertComRCReturn(hrc, hrc);
3200
3201 LONG iDevice = -1;
3202 hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
3203 AssertComRCReturn(hrc, hrc);
3204
3205 /* Remeber it. */
3206 lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/));
3207
3208 /* Eject the medium, if any. */
3209 ComPtr<IMedium> ptrMedium;
3210 hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
3211 AssertComRCReturn(hrc, hrc);
3212 if (ptrMedium.isNotNull())
3213 {
3214 ptrMedium.setNull();
3215
3216 rAutoLock.release();
3217 hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
3218 rAutoLock.acquire();
3219 }
3220 }
3221 }
3222 }
3223
3224 /*
3225 * How many drives do we need? Add more if necessary.
3226 */
3227 ULONG cDvdDrivesNeeded = 0;
3228 if (mpInstaller->isAuxiliaryIsoNeeded())
3229 cDvdDrivesNeeded++;
3230 if (mpInstaller->isOriginalIsoNeeded())
3231 cDvdDrivesNeeded++;
3232#if 0 /* These are now in the AUX VISO. */
3233 if (mpInstaller->isAdditionsIsoNeeded())
3234 cDvdDrivesNeeded++;
3235 if (mpInstaller->isValidationKitIsoNeeded())
3236 cDvdDrivesNeeded++;
3237#endif
3238 Assert(cDvdDrivesNeeded > 0);
3239 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3240 {
3241 /* Do we need to add the recommended controller? */
3242 if (strRecommendedControllerName.isEmpty())
3243 {
3244 strRecommendedControllerName = StorageController::i_controllerNameFromBusType(enmRecommendedStorageBus);
3245
3246 ComPtr<IStorageController> ptrControllerIgnored;
3247 hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus,
3248 ptrControllerIgnored.asOutParam());
3249 LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc));
3250 if (FAILED(hrc))
3251 return hrc;
3252 }
3253
3254 /* Add free controller slots, maybe raising the port limit on the controller if we can. */
3255 hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine,
3256 cDvdDrivesNeeded, lstControllerDvdSlots);
3257 if (FAILED(hrc))
3258 return hrc;
3259 if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
3260 {
3261 /* We could in many cases create another controller here, but it's not worth the effort. */
3262 return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "",
3263 cDvdDrivesNeeded - lstControllerDvdSlots.size()),
3264 strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size());
3265 }
3266 Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size());
3267 }
3268
3269 /*
3270 * Sort the DVD slots in boot order.
3271 */
3272 lstControllerDvdSlots.sort();
3273
3274 /*
3275 * Prepare ISO mounts.
3276 *
3277 * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots
3278 * according to the boot order.
3279 */
3280 std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin();
3281 if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso())
3282 {
3283 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3284 ++itDvdSlot;
3285 }
3286
3287 if (mpInstaller->isOriginalIsoNeeded())
3288 {
3289 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath(), false));
3290 ++itDvdSlot;
3291 }
3292
3293 if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso())
3294 {
3295 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
3296 ++itDvdSlot;
3297 }
3298
3299#if 0 /* These are now in the AUX VISO. */
3300 if (mpInstaller->isAdditionsIsoNeeded())
3301 {
3302 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath(), false));
3303 ++itDvdSlot;
3304 }
3305
3306 if (mpInstaller->isValidationKitIsoNeeded())
3307 {
3308 rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath(), false));
3309 ++itDvdSlot;
3310 }
3311#endif
3312
3313 return S_OK;
3314}
3315
3316/**
3317 * Used to find more free slots for DVD drives during VM reconfiguration.
3318 *
3319 * This may modify the @a portCount property of the given controller.
3320 *
3321 * @returns COM status code.
3322 * @param rStrControllerName The name of the controller to find/create
3323 * free slots on.
3324 * @param enmStorageBus The storage bus type.
3325 * @param rPtrSessionMachine Reference to the session machine.
3326 * @param cSlotsNeeded Total slots needed (including those we've
3327 * already found).
3328 * @param rDvdSlots The slot collection for DVD drives to add
3329 * free slots to as we find/create them.
3330 */
3331HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
3332 ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
3333 std::list<ControllerSlot> &rDvdSlots)
3334{
3335 Assert(cSlotsNeeded > rDvdSlots.size());
3336
3337 /*
3338 * Get controlleer stats.
3339 */
3340 ComPtr<IStorageController> pController;
3341 HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam());
3342 AssertComRCReturn(hrc, hrc);
3343
3344 ULONG cMaxDevicesPerPort = 1;
3345 hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort);
3346 AssertComRCReturn(hrc, hrc);
3347 AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED);
3348
3349 ULONG cPorts = 0;
3350 hrc = pController->COMGETTER(PortCount)(&cPorts);
3351 AssertComRCReturn(hrc, hrc);
3352
3353 /*
3354 * Get the attachment list and turn into an internal list for lookup speed.
3355 */
3356 com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
3357 hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(),
3358 ComSafeArrayAsOutParam(arrayOfMediumAttachments));
3359 AssertComRCReturn(hrc, hrc);
3360
3361 std::vector<ControllerSlot> arrayOfUsedSlots;
3362 for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++)
3363 {
3364 LONG iPort = -1;
3365 hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort);
3366 AssertComRCReturn(hrc, hrc);
3367
3368 LONG iDevice = -1;
3369 hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice);
3370 AssertComRCReturn(hrc, hrc);
3371
3372 arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/));
3373 }
3374
3375 /*
3376 * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots.
3377 */
3378 for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++)
3379 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3380 {
3381 bool fFound = false;
3382 for (size_t i = 0; i < arrayOfUsedSlots.size(); i++)
3383 if ( arrayOfUsedSlots[i].iPort == iPort
3384 && arrayOfUsedSlots[i].iDevice == iDevice)
3385 {
3386 fFound = true;
3387 break;
3388 }
3389 if (!fFound)
3390 {
3391 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3392 if (rDvdSlots.size() >= cSlotsNeeded)
3393 return S_OK;
3394 }
3395 }
3396
3397 /*
3398 * Okay we still need more ports. See if increasing the number of controller
3399 * ports would solve it.
3400 */
3401 ULONG cMaxPorts = 1;
3402 hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts);
3403 AssertComRCReturn(hrc, hrc);
3404 if (cMaxPorts <= cPorts)
3405 return S_OK;
3406 size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort;
3407 if (cPorts + cNewPortsNeeded > cMaxPorts)
3408 return S_OK;
3409
3410 /*
3411 * Raise the port count and add the free slots we've just created.
3412 */
3413 hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded);
3414 AssertComRCReturn(hrc, hrc);
3415 int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded);
3416 for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++)
3417 for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
3418 {
3419 rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
3420 if (rDvdSlots.size() >= cSlotsNeeded)
3421 return S_OK;
3422 }
3423
3424 /* We should not get here! */
3425 AssertLogRelFailedReturn(E_UNEXPECTED);
3426}
3427
3428HRESULT Unattended::done()
3429{
3430 LogFlow(("Unattended::done\n"));
3431 if (mpInstaller)
3432 {
3433 LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller));
3434 delete mpInstaller;
3435 mpInstaller = NULL;
3436 }
3437 return S_OK;
3438}
3439
3440HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
3441{
3442 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3443 isoPath = mStrIsoPath;
3444 return S_OK;
3445}
3446
3447HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
3448{
3449 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3450 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3451 mStrIsoPath = isoPath;
3452 mfDoneDetectIsoOS = false;
3453 return S_OK;
3454}
3455
3456HRESULT Unattended::getUser(com::Utf8Str &user)
3457{
3458 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3459 user = mStrUser;
3460 return S_OK;
3461}
3462
3463
3464HRESULT Unattended::setUser(const com::Utf8Str &user)
3465{
3466 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3467 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3468 mStrUser = user;
3469 return S_OK;
3470}
3471
3472HRESULT Unattended::getUserPassword(com::Utf8Str &password)
3473{
3474 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3475 password = mStrUserPassword;
3476 return S_OK;
3477}
3478
3479HRESULT Unattended::setUserPassword(const com::Utf8Str &password)
3480{
3481 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3482 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3483 mStrUserPassword = password;
3484 return S_OK;
3485}
3486
3487HRESULT Unattended::getAdminPassword(com::Utf8Str &password)
3488{
3489 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3490 password = mStrAdminPassword;
3491 return S_OK;
3492}
3493
3494HRESULT Unattended::setAdminPassword(const com::Utf8Str &password)
3495{
3496 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3497 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3498 mStrAdminPassword = password;
3499 return S_OK;
3500}
3501
3502HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
3503{
3504 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3505 fullUserName = mStrFullUserName;
3506 return S_OK;
3507}
3508
3509HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
3510{
3511 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3512 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3513 mStrFullUserName = fullUserName;
3514 return S_OK;
3515}
3516
3517HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
3518{
3519 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3520 productKey = mStrProductKey;
3521 return S_OK;
3522}
3523
3524HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
3525{
3526 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3527 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3528 mStrProductKey = productKey;
3529 return S_OK;
3530}
3531
3532HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
3533{
3534 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3535 additionsIsoPath = mStrAdditionsIsoPath;
3536 return S_OK;
3537}
3538
3539HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
3540{
3541 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3542 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3543 mStrAdditionsIsoPath = additionsIsoPath;
3544 return S_OK;
3545}
3546
3547HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
3548{
3549 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3550 *installGuestAdditions = mfInstallGuestAdditions;
3551 return S_OK;
3552}
3553
3554HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
3555{
3556 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3557 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3558 mfInstallGuestAdditions = installGuestAdditions != FALSE;
3559 return S_OK;
3560}
3561
3562HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
3563{
3564 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3565 aValidationKitIsoPath = mStrValidationKitIsoPath;
3566 return S_OK;
3567}
3568
3569HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
3570{
3571 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3572 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3573 mStrValidationKitIsoPath = aValidationKitIsoPath;
3574 return S_OK;
3575}
3576
3577HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
3578{
3579 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3580 *aInstallTestExecService = mfInstallTestExecService;
3581 return S_OK;
3582}
3583
3584HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
3585{
3586 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3587 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3588 mfInstallTestExecService = aInstallTestExecService != FALSE;
3589 return S_OK;
3590}
3591
3592HRESULT Unattended::getUserPayloadIsoPath(com::Utf8Str &aUserPayloadIsoPath)
3593{
3594 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3595 aUserPayloadIsoPath = mStrUserPayloadIsoPath;
3596 return S_OK;
3597}
3598
3599HRESULT Unattended::setUserPayloadIsoPath(const com::Utf8Str &aUserPayloadIsoPath)
3600{
3601 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3602 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3603 mStrUserPayloadIsoPath = aUserPayloadIsoPath;
3604 return S_OK;
3605}
3606
3607HRESULT Unattended::getInstallUserPayload(BOOL *aInstallUserPayload)
3608{
3609 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3610 *aInstallUserPayload = mfInstallUserPayload;
3611 return S_OK;
3612}
3613
3614HRESULT Unattended::setInstallUserPayload(BOOL aInstallUserPayload)
3615{
3616 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3617 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3618 mfInstallUserPayload = aInstallUserPayload != FALSE;
3619 return S_OK;
3620}
3621
3622HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
3623{
3624 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3625 aTimeZone = mStrTimeZone;
3626 return S_OK;
3627}
3628
3629HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
3630{
3631 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3632 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3633 mStrTimeZone = aTimezone;
3634 return S_OK;
3635}
3636
3637HRESULT Unattended::getKeyboardLayout(com::Utf8Str &aKeyboardLayout)
3638{
3639 RT_NOREF(aKeyboardLayout);
3640 return E_NOTIMPL;
3641}
3642
3643HRESULT Unattended::setKeyboardLayout(const com::Utf8Str &aKeyboardLayout)
3644{
3645 RT_NOREF(aKeyboardLayout);
3646 return E_NOTIMPL;
3647}
3648
3649HRESULT Unattended::getKeyboardVariant(com::Utf8Str &aKeyboardVariant)
3650{
3651 RT_NOREF(aKeyboardVariant);
3652 return E_NOTIMPL;
3653}
3654
3655HRESULT Unattended::setKeyboardVariant(const com::Utf8Str &aKeyboardVariant)
3656{
3657 RT_NOREF(aKeyboardVariant);
3658 return E_NOTIMPL;
3659}
3660
3661HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
3662{
3663 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3664 aLocale = mStrLocale;
3665 return S_OK;
3666}
3667
3668HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
3669{
3670 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3671 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3672 if ( aLocale.isEmpty() /* use default */
3673 || ( aLocale.length() == 5
3674 && RT_C_IS_LOWER(aLocale[0])
3675 && RT_C_IS_LOWER(aLocale[1])
3676 && aLocale[2] == '_'
3677 && RT_C_IS_UPPER(aLocale[3])
3678 && RT_C_IS_UPPER(aLocale[4])) )
3679 {
3680 mStrLocale = aLocale;
3681 return S_OK;
3682 }
3683 return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters"));
3684}
3685
3686HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
3687{
3688 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3689 aLanguage = mStrLanguage;
3690 return S_OK;
3691}
3692
3693HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
3694{
3695 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3696 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3697 mStrLanguage = aLanguage;
3698 return S_OK;
3699}
3700
3701HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
3702{
3703 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3704 aCountry = mStrCountry;
3705 return S_OK;
3706}
3707
3708HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
3709{
3710 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3711 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3712 if ( aCountry.isEmpty()
3713 || ( aCountry.length() == 2
3714 && RT_C_IS_UPPER(aCountry[0])
3715 && RT_C_IS_UPPER(aCountry[1])) )
3716 {
3717 mStrCountry = aCountry;
3718 return S_OK;
3719 }
3720 return setError(E_INVALIDARG, tr("Expected two upper cased letters"));
3721}
3722
3723HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
3724{
3725 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3726 aProxy = mStrProxy; /// @todo turn schema map into string or something.
3727 return S_OK;
3728}
3729
3730HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
3731{
3732 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3733 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3734 if (aProxy.isEmpty())
3735 {
3736 /* set default proxy */
3737 /** @todo BUGBUG! implement this */
3738 }
3739 else if (aProxy.equalsIgnoreCase("none"))
3740 {
3741 /* clear proxy config */
3742 mStrProxy.setNull();
3743 }
3744 else
3745 {
3746 /** @todo Parse and set proxy config into a schema map or something along those lines. */
3747 /** @todo BUGBUG! implement this */
3748 // return E_NOTIMPL;
3749 mStrProxy = aProxy;
3750 }
3751 return S_OK;
3752}
3753
3754HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
3755{
3756 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3757 aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";");
3758 return S_OK;
3759}
3760
3761HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
3762{
3763 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3764 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3765 if (aPackageSelectionAdjustments.isEmpty())
3766 mPackageSelectionAdjustments.clear();
3767 else
3768 {
3769 RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";");
3770 for (size_t i = 0; i < arrayStrSplit.size(); i++)
3771 {
3772 if (arrayStrSplit[i].equals("minimal"))
3773 { /* okay */ }
3774 else
3775 return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str());
3776 }
3777 mPackageSelectionAdjustments = arrayStrSplit;
3778 }
3779 return S_OK;
3780}
3781
3782HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
3783{
3784 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3785 aHostname = mStrHostname;
3786 return S_OK;
3787}
3788
3789HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
3790{
3791 /*
3792 * Validate input.
3793 */
3794 if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U))
3795 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3796 tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()),
3797 aHostname.c_str(), aHostname.length());
3798 size_t cLabels = 0;
3799 const char *pszSrc = aHostname.c_str();
3800 for (;;)
3801 {
3802 size_t cchLabel = 1;
3803 char ch = *pszSrc++;
3804 if (RT_C_IS_ALNUM(ch))
3805 {
3806 cLabels++;
3807 while ((ch = *pszSrc++) != '.' && ch != '\0')
3808 {
3809 if (RT_C_IS_ALNUM(ch) || ch == '-')
3810 {
3811 if (cchLabel < 63)
3812 cchLabel++;
3813 else
3814 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3815 tr("Invalid hostname '%s' - label %u is too long, max is 63."),
3816 aHostname.c_str(), cLabels);
3817 }
3818 else
3819 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3820 tr("Invalid hostname '%s' - illegal char '%c' at position %zu"),
3821 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3822 }
3823 if (cLabels == 1 && cchLabel < 2)
3824 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3825 tr("Invalid hostname '%s' - the name part must be at least two characters long"),
3826 aHostname.c_str());
3827 if (ch == '\0')
3828 break;
3829 }
3830 else if (ch != '\0')
3831 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3832 tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"),
3833 aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
3834 else
3835 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3836 tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str());
3837 }
3838 if (cLabels < 2)
3839 return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
3840 tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str());
3841
3842 /*
3843 * Make the change.
3844 */
3845 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3846 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3847 mStrHostname = aHostname;
3848 return S_OK;
3849}
3850
3851HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
3852{
3853 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3854 aAuxiliaryBasePath = mStrAuxiliaryBasePath;
3855 return S_OK;
3856}
3857
3858HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
3859{
3860 if (aAuxiliaryBasePath.isEmpty())
3861 return setError(E_INVALIDARG, tr("Empty base path is not allowed"));
3862 if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str()))
3863 return setError(E_INVALIDARG, tr("Base path must be absolute"));
3864
3865 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3866 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3867 mStrAuxiliaryBasePath = aAuxiliaryBasePath;
3868 mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty();
3869 return S_OK;
3870}
3871
3872HRESULT Unattended::getImageIndex(ULONG *index)
3873{
3874 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3875 *index = midxImage;
3876 return S_OK;
3877}
3878
3879HRESULT Unattended::setImageIndex(ULONG index)
3880{
3881 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3882 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3883
3884 /* Validate the selection if detection was done already: */
3885 if (mDetectedImages.size() > 0)
3886 {
3887 for (size_t i = 0; i < mDetectedImages.size(); i++)
3888 if (mDetectedImages[i].mImageIndex == index)
3889 {
3890 midxImage = index;
3891 i_updateDetectedAttributeForImage(mDetectedImages[i]);
3892 return S_OK;
3893 }
3894 LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */
3895 }
3896
3897 midxImage = index;
3898 return S_OK;
3899}
3900
3901HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
3902{
3903 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3904 return mMachine.queryInterfaceTo(aMachine.asOutParam());
3905}
3906
3907HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
3908{
3909 /*
3910 * Lookup the VM so we can safely get the Machine instance.
3911 * (Don't want to test how reliable XPCOM and COM are with finding
3912 * the local object instance when a client passes a stub back.)
3913 */
3914 Bstr bstrUuidMachine;
3915 HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam());
3916 if (SUCCEEDED(hrc))
3917 {
3918 Guid UuidMachine(bstrUuidMachine);
3919 ComObjPtr<Machine> ptrMachine;
3920 hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
3921 if (SUCCEEDED(hrc))
3922 {
3923 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3924 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER,
3925 tr("Cannot change after prepare() has been called")));
3926 mMachine = ptrMachine;
3927 mMachineUuid = UuidMachine;
3928 if (mfIsDefaultAuxiliaryBasePath)
3929 mStrAuxiliaryBasePath.setNull();
3930 hrc = S_OK;
3931 }
3932 }
3933 return hrc;
3934}
3935
3936HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
3937{
3938 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3939 if ( mStrScriptTemplatePath.isNotEmpty()
3940 || mpInstaller == NULL)
3941 aScriptTemplatePath = mStrScriptTemplatePath;
3942 else
3943 aScriptTemplatePath = mpInstaller->getTemplateFilePath();
3944 return S_OK;
3945}
3946
3947HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
3948{
3949 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3950 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3951 mStrScriptTemplatePath = aScriptTemplatePath;
3952 return S_OK;
3953}
3954
3955HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
3956{
3957 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3958 if ( mStrPostInstallScriptTemplatePath.isNotEmpty()
3959 || mpInstaller == NULL)
3960 aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath;
3961 else
3962 aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath();
3963 return S_OK;
3964}
3965
3966HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
3967{
3968 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3969 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3970 mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath;
3971 return S_OK;
3972}
3973
3974HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
3975{
3976 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3977 aPostInstallCommand = mStrPostInstallCommand;
3978 return S_OK;
3979}
3980
3981HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
3982{
3983 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3984 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
3985 mStrPostInstallCommand = aPostInstallCommand;
3986 return S_OK;
3987}
3988
3989HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
3990{
3991 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3992 if ( mStrExtraInstallKernelParameters.isNotEmpty()
3993 || mpInstaller == NULL)
3994 aExtraInstallKernelParameters = mStrExtraInstallKernelParameters;
3995 else
3996 aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters();
3997 return S_OK;
3998}
3999
4000HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
4001{
4002 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4003 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4004 mStrExtraInstallKernelParameters = aExtraInstallKernelParameters;
4005 return S_OK;
4006}
4007
4008HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
4009{
4010 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4011 aDetectedOSTypeId = mStrDetectedOSTypeId;
4012 return S_OK;
4013}
4014
4015HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
4016{
4017 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4018 aDetectedOSVersion = mStrDetectedOSVersion;
4019 return S_OK;
4020}
4021
4022HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
4023{
4024 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4025 aDetectedOSFlavor = mStrDetectedOSFlavor;
4026 return S_OK;
4027}
4028
4029HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
4030{
4031 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4032 aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " ");
4033 return S_OK;
4034}
4035
4036HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
4037{
4038 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4039 aDetectedOSHints = mStrDetectedOSHints;
4040 return S_OK;
4041}
4042
4043HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
4044{
4045 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4046 aDetectedImageNames.clear();
4047 for (size_t i = 0; i < mDetectedImages.size(); ++i)
4048 {
4049 Utf8Str strTmp;
4050 aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp));
4051 }
4052 return S_OK;
4053}
4054
4055HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
4056{
4057 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4058 aDetectedImageIndices.clear();
4059 for (size_t i = 0; i < mDetectedImages.size(); ++i)
4060 aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex);
4061 return S_OK;
4062}
4063
4064HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
4065{
4066 /*
4067 * Take the initial position that it's not supported, so we can return
4068 * right away when we decide it's not possible.
4069 */
4070 *aIsUnattendedInstallSupported = false;
4071
4072 /* Unattended is disabled by default if we could not detect OS type. */
4073 if (mStrDetectedOSTypeId.isEmpty())
4074 return S_OK;
4075
4076 /* Note! Includes the OS family and the distro (linux) or (part) of the
4077 major OS version. Use with care. */
4078 const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask);
4079
4080 /* We require a version to have been detected, except for windows where the
4081 field is generally only used for the service pack number at present and
4082 will be empty for RTMs isos. */
4083 if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT
4084 || enmOsTypeMasked >= VBOXOSTYPE_OS2)
4085 && mStrDetectedOSVersion.isEmpty())
4086 return S_OK;
4087
4088 /*
4089 * Sort out things that we know doesn't work. Order by VBOXOSTYPE value.
4090 */
4091
4092 /* We do not support any of the DOS based windows version, nor DOS, in case
4093 any of that gets detected (it shouldn't): */
4094 if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT)
4095 return S_OK;
4096
4097 /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */
4098 if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4)
4099 return S_OK;
4100
4101 /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been
4102 tested, but we'll get to the others eventually): */
4103 if ( enmOsTypeMasked >= VBOXOSTYPE_OS2
4104 && enmOsTypeMasked < VBOXOSTYPE_Linux
4105 && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ )
4106 return S_OK;
4107
4108 /* Old Debians fail since package repos have been move to some other mirror location. */
4109 if ( enmOsTypeMasked == VBOXOSTYPE_Debian
4110 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0)
4111 return S_OK;
4112
4113 /* Skip all OpenSUSE variants for now. */
4114 if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE)
4115 return S_OK;
4116
4117 if (enmOsTypeMasked == VBOXOSTYPE_Ubuntu)
4118 {
4119 /* We cannot install Ubuntus older than 11.04. */
4120 if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0)
4121 return S_OK;
4122 /* Lubuntu, starting with 20.04, has switched to calamares, which cannot be automated. */
4123 if ( RTStrIStr(mStrDetectedOSFlavor.c_str(), "lubuntu")
4124 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "20.04") > 0)
4125 return S_OK;
4126 }
4127
4128 /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */
4129 if ( enmOsTypeMasked == VBOXOSTYPE_Oracle
4130 && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0)
4131 return S_OK;
4132
4133 /* Fredora ISOs cannot be installed at present. */
4134 if (enmOsTypeMasked == VBOXOSTYPE_FedoraCore)
4135 return S_OK;
4136
4137 /*
4138 * Assume the rest works.
4139 */
4140 *aIsUnattendedInstallSupported = true;
4141 return S_OK;
4142}
4143
4144HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
4145{
4146 *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork;
4147 return S_OK;
4148}
4149
4150HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
4151{
4152 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
4153 AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
4154 mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork);
4155 return S_OK;
4156}
4157
4158/*
4159 * Getters that the installer and script classes can use.
4160 */
4161Utf8Str const &Unattended::i_getIsoPath() const
4162{
4163 Assert(isReadLockedOnCurrentThread());
4164 return mStrIsoPath;
4165}
4166
4167Utf8Str const &Unattended::i_getUser() const
4168{
4169 Assert(isReadLockedOnCurrentThread());
4170 return mStrUser;
4171}
4172
4173Utf8Str const &Unattended::i_getUserPassword() const
4174{
4175 Assert(isReadLockedOnCurrentThread());
4176 return mStrUserPassword;
4177}
4178
4179Utf8Str const &Unattended::i_getAdminPassword() const
4180{
4181 Assert(isReadLockedOnCurrentThread());
4182
4183 /* If no Administrator / 'root' password is being set, the user password will be used instead.
4184 * Also see API documentation. */
4185 return mStrAdminPassword.isEmpty() ? mStrUserPassword : mStrAdminPassword;
4186}
4187
4188Utf8Str const &Unattended::i_getFullUserName() const
4189{
4190 Assert(isReadLockedOnCurrentThread());
4191 return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
4192}
4193
4194Utf8Str const &Unattended::i_getProductKey() const
4195{
4196 Assert(isReadLockedOnCurrentThread());
4197 return mStrProductKey;
4198}
4199
4200Utf8Str const &Unattended::i_getProxy() const
4201{
4202 Assert(isReadLockedOnCurrentThread());
4203 return mStrProxy;
4204}
4205
4206Utf8Str const &Unattended::i_getAdditionsIsoPath() const
4207{
4208 Assert(isReadLockedOnCurrentThread());
4209 return mStrAdditionsIsoPath;
4210}
4211
4212bool Unattended::i_getInstallGuestAdditions() const
4213{
4214 Assert(isReadLockedOnCurrentThread());
4215 return mfInstallGuestAdditions;
4216}
4217
4218Utf8Str const &Unattended::i_getValidationKitIsoPath() const
4219{
4220 Assert(isReadLockedOnCurrentThread());
4221 return mStrValidationKitIsoPath;
4222}
4223
4224bool Unattended::i_getInstallTestExecService() const
4225{
4226 Assert(isReadLockedOnCurrentThread());
4227 return mfInstallTestExecService;
4228}
4229
4230Utf8Str const &Unattended::i_getUserPayloadIsoPath() const
4231{
4232 Assert(isReadLockedOnCurrentThread());
4233 return mStrUserPayloadIsoPath;
4234}
4235
4236bool Unattended::i_getInstallUserPayload() const
4237{
4238 Assert(isReadLockedOnCurrentThread());
4239 return mfInstallUserPayload;
4240}
4241
4242Utf8Str const &Unattended::i_getTimeZone() const
4243{
4244 Assert(isReadLockedOnCurrentThread());
4245 return mStrTimeZone;
4246}
4247
4248PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
4249{
4250 Assert(isReadLockedOnCurrentThread());
4251 return mpTimeZoneInfo;
4252}
4253
4254Utf8Str const &Unattended::i_getLocale() const
4255{
4256 Assert(isReadLockedOnCurrentThread());
4257 return mStrLocale;
4258}
4259
4260Utf8Str const &Unattended::i_getLanguage() const
4261{
4262 Assert(isReadLockedOnCurrentThread());
4263 return mStrLanguage;
4264}
4265
4266Utf8Str const &Unattended::i_getCountry() const
4267{
4268 Assert(isReadLockedOnCurrentThread());
4269 return mStrCountry;
4270}
4271
4272bool Unattended::i_isMinimalInstallation() const
4273{
4274 size_t i = mPackageSelectionAdjustments.size();
4275 while (i-- > 0)
4276 if (mPackageSelectionAdjustments[i].equals("minimal"))
4277 return true;
4278 return false;
4279}
4280
4281Utf8Str const &Unattended::i_getHostname() const
4282{
4283 Assert(isReadLockedOnCurrentThread());
4284 return mStrHostname;
4285}
4286
4287Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
4288{
4289 Assert(isReadLockedOnCurrentThread());
4290 return mStrAuxiliaryBasePath;
4291}
4292
4293ULONG Unattended::i_getImageIndex() const
4294{
4295 Assert(isReadLockedOnCurrentThread());
4296 return midxImage;
4297}
4298
4299Utf8Str const &Unattended::i_getScriptTemplatePath() const
4300{
4301 Assert(isReadLockedOnCurrentThread());
4302 return mStrScriptTemplatePath;
4303}
4304
4305Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
4306{
4307 Assert(isReadLockedOnCurrentThread());
4308 return mStrPostInstallScriptTemplatePath;
4309}
4310
4311Utf8Str const &Unattended::i_getPostInstallCommand() const
4312{
4313 Assert(isReadLockedOnCurrentThread());
4314 return mStrPostInstallCommand;
4315}
4316
4317Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
4318{
4319 Assert(isReadLockedOnCurrentThread());
4320 /* Only the installer knows, forward the call. */
4321 AssertReturn(mpInstaller != NULL, Utf8Str::Empty);
4322 return mpInstaller->getAuxiliaryInstallDir();
4323}
4324
4325Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
4326{
4327 Assert(isReadLockedOnCurrentThread());
4328 return mStrExtraInstallKernelParameters;
4329}
4330
4331Utf8Str const &Unattended::i_getAdditionsInstallPackage() const
4332{
4333 Assert(isReadLockedOnCurrentThread());
4334 return mStrAdditionsInstallPackage;
4335}
4336
4337bool Unattended::i_isRtcUsingUtc() const
4338{
4339 Assert(isReadLockedOnCurrentThread());
4340 return mfRtcUseUtc;
4341}
4342
4343bool Unattended::i_isGuestOs64Bit() const
4344{
4345 Assert(isReadLockedOnCurrentThread());
4346 return mfGuestOs64Bit;
4347}
4348
4349bool Unattended::i_isFirmwareEFI() const
4350{
4351 Assert(isReadLockedOnCurrentThread());
4352 return menmFirmwareType != FirmwareType_BIOS;
4353}
4354
4355Utf8Str const &Unattended::i_getDetectedOSVersion()
4356{
4357 Assert(isReadLockedOnCurrentThread());
4358 return mStrDetectedOSVersion;
4359}
4360
4361bool Unattended::i_getAvoidUpdatesOverNetwork() const
4362{
4363 Assert(isReadLockedOnCurrentThread());
4364 return mfAvoidUpdatesOverNetwork;
4365}
4366
4367HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
4368 AutoMultiWriteLock2 &rLock)
4369{
4370 /*
4371 * Attach the disk image
4372 * HACK ALERT! Temporarily release the Unattended lock.
4373 */
4374 rLock.release();
4375
4376 ComPtr<IMedium> ptrMedium;
4377 HRESULT hrc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(),
4378 pImage->enmDeviceType,
4379 pImage->enmAccessType,
4380 true,
4381 ptrMedium.asOutParam());
4382 LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", hrc));
4383 if (SUCCEEDED(hrc))
4384 {
4385 if (pImage->fAuxiliary && pImage->strImagePath.endsWith(".viso"))
4386 {
4387 hrc = ptrMedium->SetProperty(Bstr("UnattendedInstall").raw(), Bstr("1").raw());
4388 LogRelFlowFunc(("Medium::SetProperty -> %Rhrc\n", hrc));
4389 }
4390 if (pImage->fMountOnly)
4391 {
4392 // mount the opened disk image
4393 hrc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4394 pImage->iDevice, ptrMedium, TRUE /*fForce*/);
4395 LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", hrc));
4396 }
4397 else
4398 {
4399 //attach the opened disk image to the controller
4400 hrc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort,
4401 pImage->iDevice, pImage->enmDeviceType, ptrMedium);
4402 LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", hrc));
4403 }
4404 }
4405
4406 rLock.acquire();
4407 return hrc;
4408}
4409
4410bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId)
4411{
4412 ComPtr<IGuestOSType> pGuestOSType;
4413 HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam());
4414 if (SUCCEEDED(hrc))
4415 {
4416 BOOL fIs64Bit = FALSE;
4417 if (!pGuestOSType.isNull())
4418 hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit);
4419 if (SUCCEEDED(hrc))
4420 return fIs64Bit != FALSE;
4421 }
4422 return false;
4423}
4424
4425
4426bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage)
4427{
4428 bool fRet = true;
4429
4430 /*
4431 * If the image doesn't have a valid value, we don't change it.
4432 * This is obviously a little bit bogus, but what can we do...
4433 */
4434 const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType);
4435 if (pszOSTypeId && !RTStrStartsWith(pszOSTypeId, GUEST_OS_ID_STR_PARTIAL("Other"))) /** @todo set x64/a64 other variants or not? */
4436 mStrDetectedOSTypeId = pszOSTypeId;
4437 else
4438 fRet = false;
4439
4440 if (rImage.mVersion.isNotEmpty())
4441 mStrDetectedOSVersion = rImage.mVersion;
4442 else
4443 fRet = false;
4444
4445 if (rImage.mFlavor.isNotEmpty())
4446 mStrDetectedOSFlavor = rImage.mFlavor;
4447 else
4448 fRet = false;
4449
4450 if (rImage.mLanguages.size() > 0)
4451 mDetectedOSLanguages = rImage.mLanguages;
4452 else
4453 fRet = false;
4454
4455 mEnmOsType = rImage.mOSType;
4456
4457 return fRet;
4458}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use