VirtualBox

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

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

Main/Unattended: Detect OS/2 ISOs. [build fix]

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

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