VirtualBox

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

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

Main/Unattended: The image version is inside the WINDOWS element, not directly under IMAGE. Pick up SPBUILD and combine them all into a single version string. Be stricter with the image index parsing (via getAttributeValue override). Catch alloc exceptions. bugref:9781

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

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