VirtualBox

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

Last change on this file since 102455 was 102417, checked in by vboxsync, 18 months ago

Main/Unattended: If detecting the ISO failed, print everything we got to the VBoxSVC release log, to (hopefully) provide us more clues about which distros don't work.

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

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