VirtualBox

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

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

Main/Unattended: bugref:9781. Reading and storing image index from the xml metadata.

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