VirtualBox

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

Last change on this file since 93552 was 93545, checked in by vboxsync, 3 years ago

FE/Qt, Main/Unattended. ​​bugref:9515, ​​bugref:9781. Creating necessary connections for the windows image selection widget.

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

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