VirtualBox

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

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

Main/Unattended: bugref:9781. Build fix.

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

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