VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/UnattendedInstaller.cpp@ 101799

Last change on this file since 101799 was 101697, checked in by vboxsync, 19 months ago

Main/Unattended: Support OracleLinux for unattended installation, bugref:10516

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.9 KB
Line 
1/* $Id: UnattendedInstaller.cpp 101697 2023-11-01 11:56:34Z vboxsync $ */
2/** @file
3 * UnattendedInstaller class and it's descendants implementation
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
33#include "LoggingNew.h"
34#include "VirtualBoxBase.h"
35#include "VirtualBoxErrorInfoImpl.h"
36#include "AutoCaller.h"
37#include <VBox/com/ErrorInfo.h>
38
39#include "UnattendedImpl.h"
40#include "UnattendedInstaller.h"
41#include "UnattendedScript.h"
42
43#include <VBox/err.h>
44#include <iprt/ctype.h>
45#include <iprt/fsisomaker.h>
46#include <iprt/fsvfs.h>
47#include <iprt/getopt.h>
48#include <iprt/file.h>
49#include <iprt/path.h>
50#include <iprt/stream.h>
51#include <iprt/vfs.h>
52#ifdef RT_OS_SOLARIS
53# undef ES /* Workaround for someone dragging the namespace pollutor sys/regset.h. Sigh. */
54#endif
55#include <iprt/formats/iso9660.h>
56#include <iprt/cpp/path.h>
57
58
59using namespace std;
60
61
62/* static */ UnattendedInstaller *
63UnattendedInstaller::createInstance(VBOXOSTYPE enmDetectedOSType, const Utf8Str &strDetectedOSType,
64 const Utf8Str &strDetectedOSVersion, const Utf8Str &strDetectedOSFlavor,
65 const Utf8Str &strDetectedOSHints, Unattended *pParent)
66{
67 UnattendedInstaller *pUinstaller = NULL;
68
69 if (strDetectedOSType.find("Windows") != RTCString::npos)
70 {
71 if (enmDetectedOSType >= VBOXOSTYPE_WinVista)
72 pUinstaller = new UnattendedWindowsXmlInstaller(pParent);
73 else
74 pUinstaller = new UnattendedWindowsSifInstaller(pParent);
75 }
76 else if (enmDetectedOSType >= VBOXOSTYPE_OS2 && enmDetectedOSType < VBOXOSTYPE_Linux)
77 pUinstaller = new UnattendedOs2Installer(pParent, strDetectedOSHints);
78 else
79 {
80 if ( enmDetectedOSType >= VBOXOSTYPE_Debian
81 && ( enmDetectedOSType <= VBOXOSTYPE_Debian_latest_x64
82 || enmDetectedOSType <= VBOXOSTYPE_Debian_latest_arm64))
83 pUinstaller = new UnattendedDebianInstaller(pParent);
84 else if ( enmDetectedOSType >= VBOXOSTYPE_Ubuntu
85 && ( enmDetectedOSType <= VBOXOSTYPE_Ubuntu_latest_x64
86 || enmDetectedOSType <= VBOXOSTYPE_Ubuntu_latest_arm64))
87 pUinstaller = new UnattendedUbuntuInstaller(pParent);
88 else if (enmDetectedOSType >= VBOXOSTYPE_RedHat && enmDetectedOSType <= VBOXOSTYPE_RedHat_latest_x64)
89 {
90 if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "8") >= 0)
91 pUinstaller = new UnattendedRhel8Installer(pParent);
92 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "7") >= 0)
93 pUinstaller = new UnattendedRhel7Installer(pParent);
94 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "6") >= 0)
95 pUinstaller = new UnattendedRhel6Installer(pParent);
96 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "5") >= 0)
97 pUinstaller = new UnattendedRhel5Installer(pParent);
98 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "4") >= 0)
99 pUinstaller = new UnattendedRhel4Installer(pParent);
100 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "3") >= 0)
101 pUinstaller = new UnattendedRhel3Installer(pParent);
102 else
103 pUinstaller = new UnattendedRhel6Installer(pParent);
104 }
105 else if (enmDetectedOSType >= VBOXOSTYPE_FedoraCore && enmDetectedOSType <= VBOXOSTYPE_FedoraCore_x64)
106 pUinstaller = new UnattendedFedoraInstaller(pParent);
107 else if ( enmDetectedOSType >= VBOXOSTYPE_Oracle
108 && ( enmDetectedOSType <= VBOXOSTYPE_Oracle_latest_x64
109 || enmDetectedOSType <= VBOXOSTYPE_Oracle_latest_arm64))
110 {
111 if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "9") >= 0)
112 pUinstaller = new UnattendedOracleLinux9Installer(pParent);
113 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "8") >= 0)
114 pUinstaller = new UnattendedOracleLinux8Installer(pParent);
115 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "7") >= 0)
116 pUinstaller = new UnattendedOracleLinux7Installer(pParent);
117 else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "6") >= 0)
118 pUinstaller = new UnattendedOracleLinux6Installer(pParent);
119 else
120 pUinstaller = new UnattendedOracleLinux6Installer(pParent);
121 }
122 else if ( enmDetectedOSType >= VBOXOSTYPE_FreeBSD
123 && ( enmDetectedOSType <= VBOXOSTYPE_FreeBSD_x64
124 || enmDetectedOSType <= VBOXOSTYPE_FreeBSD_arm64))
125 pUinstaller = new UnattendedFreeBsdInstaller(pParent);
126#if 0 /* doesn't work, so convert later. */
127 else if (enmDetectedOSType == VBOXOSTYPE_OpenSUSE || enmDetectedOSType == VBOXOSTYPE_OpenSUSE_x64)
128 pUinstaller = new UnattendedSuseInstaller(new UnattendedSUSEXMLScript(pParent), pParent);
129#endif
130 }
131 RT_NOREF_PV(strDetectedOSFlavor);
132 RT_NOREF_PV(strDetectedOSHints);
133 return pUinstaller;
134}
135
136
137//////////////////////////////////////////////////////////////////////////////////////////////////////
138/*
139*
140*
141* Implementation Unattended functions
142*
143*/
144//////////////////////////////////////////////////////////////////////////////////////////////////////
145
146/*
147 *
148 * UnattendedInstaller public methods
149 *
150 */
151UnattendedInstaller::UnattendedInstaller(Unattended *pParent,
152 const char *pszMainScriptTemplateName, const char *pszPostScriptTemplateName,
153 const char *pszMainScriptFilename, const char *pszPostScriptFilename,
154 DeviceType_T enmBootDevice /*= DeviceType_DVD */)
155 : mMainScript(pParent, pszMainScriptTemplateName, pszMainScriptFilename)
156 , mPostScript(pParent, pszPostScriptTemplateName, pszPostScriptFilename)
157 , mpParent(pParent)
158 , meBootDevice(enmBootDevice)
159{
160 AssertPtr(pParent);
161 Assert(*pszMainScriptTemplateName);
162 Assert(*pszMainScriptFilename);
163 Assert(*pszPostScriptTemplateName);
164 Assert(*pszPostScriptFilename);
165 Assert(enmBootDevice == DeviceType_DVD || enmBootDevice == DeviceType_Floppy);
166}
167
168UnattendedInstaller::~UnattendedInstaller()
169{
170 mpParent = NULL;
171}
172
173HRESULT UnattendedInstaller::initInstaller()
174{
175 /*
176 * Calculate the full main script template location.
177 */
178 if (mpParent->i_getScriptTemplatePath().isNotEmpty())
179 mStrMainScriptTemplate = mpParent->i_getScriptTemplatePath();
180 else
181 {
182 int vrc = RTPathAppPrivateNoArchCxx(mStrMainScriptTemplate);
183 if (RT_SUCCESS(vrc))
184 vrc = RTPathAppendCxx(mStrMainScriptTemplate, "UnattendedTemplates");
185 if (RT_SUCCESS(vrc))
186 vrc = RTPathAppendCxx(mStrMainScriptTemplate, mMainScript.getDefaultTemplateFilename());
187 if (RT_FAILURE(vrc))
188 return mpParent->setErrorBoth(E_FAIL, vrc,
189 tr("Failed to construct path to the unattended installer script templates (%Rrc)"),
190 vrc);
191 }
192
193 /*
194 * Calculate the full post script template location.
195 */
196 if (mpParent->i_getPostInstallScriptTemplatePath().isNotEmpty())
197 mStrPostScriptTemplate = mpParent->i_getPostInstallScriptTemplatePath();
198 else
199 {
200 int vrc = RTPathAppPrivateNoArchCxx(mStrPostScriptTemplate);
201 if (RT_SUCCESS(vrc))
202 vrc = RTPathAppendCxx(mStrPostScriptTemplate, "UnattendedTemplates");
203 if (RT_SUCCESS(vrc))
204 vrc = RTPathAppendCxx(mStrPostScriptTemplate, mPostScript.getDefaultTemplateFilename());
205 if (RT_FAILURE(vrc))
206 return mpParent->setErrorBoth(E_FAIL, vrc,
207 tr("Failed to construct path to the unattended installer script templates (%Rrc)"),
208 vrc);
209 }
210
211 /*
212 * Construct paths we need.
213 */
214 if (isAuxiliaryFloppyNeeded())
215 {
216 mStrAuxiliaryFloppyFilePath = mpParent->i_getAuxiliaryBasePath();
217 mStrAuxiliaryFloppyFilePath.append("aux-floppy.img");
218 }
219 if (isAuxiliaryIsoNeeded())
220 {
221 mStrAuxiliaryIsoFilePath = mpParent->i_getAuxiliaryBasePath();
222 if (!isAuxiliaryIsoIsVISO())
223 mStrAuxiliaryIsoFilePath.append("aux-iso.iso");
224 else
225 mStrAuxiliaryIsoFilePath.append("aux-iso.viso");
226 }
227
228 /*
229 * Check that we've got the minimum of data available.
230 */
231 if (mpParent->i_getIsoPath().isEmpty())
232 return mpParent->setError(E_INVALIDARG, tr("Cannot proceed with an empty installation ISO path"));
233 if (mpParent->i_getUser().isEmpty())
234 return mpParent->setError(E_INVALIDARG, tr("Empty user name is not allowed"));
235 if (mpParent->i_getPassword().isEmpty())
236 return mpParent->setError(E_INVALIDARG, tr("Empty password is not allowed"));
237
238 LogRelFunc(("UnattendedInstaller::savePassedData(): \n"));
239 return S_OK;
240}
241
242#if 0 /* Always in AUX ISO */
243bool UnattendedInstaller::isAdditionsIsoNeeded() const
244{
245 /* In the VISO case, we'll add the additions to the VISO in a subdir. */
246 return !isAuxiliaryIsoIsVISO() && mpParent->i_getInstallGuestAdditions();
247}
248
249bool UnattendedInstaller::isValidationKitIsoNeeded() const
250{
251 /* In the VISO case, we'll add the validation kit to the VISO in a subdir. */
252 return !isAuxiliaryIsoIsVISO() && mpParent->i_getInstallTestExecService();
253}
254#endif
255
256bool UnattendedInstaller::isAuxiliaryIsoNeeded() const
257{
258 /* In the VISO case we use the AUX ISO for GAs and TXS. */
259 return isAuxiliaryIsoIsVISO()
260 && ( mpParent->i_getInstallGuestAdditions()
261 || mpParent->i_getInstallTestExecService());
262}
263
264
265HRESULT UnattendedInstaller::prepareUnattendedScripts()
266{
267 LogFlow(("UnattendedInstaller::prepareUnattendedScripts()\n"));
268
269 /*
270 * The script template editor calls setError, so status codes just needs to
271 * be passed on to the caller. Do the same for both scripts.
272 */
273 HRESULT hrc = mMainScript.read(getTemplateFilePath());
274 if (SUCCEEDED(hrc))
275 {
276 hrc = mMainScript.parse();
277 if (SUCCEEDED(hrc))
278 {
279 /* Ditto for the post script. */
280 hrc = mPostScript.read(getPostTemplateFilePath());
281 if (SUCCEEDED(hrc))
282 {
283 hrc = mPostScript.parse();
284 if (SUCCEEDED(hrc))
285 {
286 LogFlow(("UnattendedInstaller::prepareUnattendedScripts: returns S_OK\n"));
287 return S_OK;
288 }
289 LogFlow(("UnattendedInstaller::prepareUnattendedScripts: parse failed on post script (%Rhrc)\n", hrc));
290 }
291 else
292 LogFlow(("UnattendedInstaller::prepareUnattendedScripts: error reading post install script template file (%Rhrc)\n", hrc));
293 }
294 else
295 LogFlow(("UnattendedInstaller::prepareUnattendedScripts: parse failed (%Rhrc)\n", hrc));
296 }
297 else
298 LogFlow(("UnattendedInstaller::prepareUnattendedScripts: error reading installation script template file (%Rhrc)\n", hrc));
299 return hrc;
300}
301
302HRESULT UnattendedInstaller::prepareMedia(bool fOverwrite /*=true*/)
303{
304 LogRelFlow(("UnattendedInstaller::prepareMedia:\n"));
305 HRESULT hrc = S_OK;
306 if (isAuxiliaryFloppyNeeded())
307 hrc = prepareAuxFloppyImage(fOverwrite);
308 if (SUCCEEDED(hrc))
309 {
310 if (isAuxiliaryIsoNeeded())
311 {
312 hrc = prepareAuxIsoImage(fOverwrite);
313 if (FAILED(hrc))
314 {
315 LogRelFlow(("UnattendedInstaller::prepareMedia: prepareAuxIsoImage failed\n"));
316
317 /* Delete the floppy image if we created one. */
318 if (isAuxiliaryFloppyNeeded())
319 RTFileDelete(getAuxiliaryFloppyFilePath().c_str());
320 }
321 }
322 }
323 LogRelFlow(("UnattendedInstaller::prepareMedia: returns %Rrc\n", hrc));
324 return hrc;
325}
326
327/*
328 *
329 * UnattendedInstaller protected methods
330 *
331 */
332HRESULT UnattendedInstaller::prepareAuxFloppyImage(bool fOverwrite)
333{
334 Assert(isAuxiliaryFloppyNeeded());
335
336 /*
337 * Create the image.
338 */
339 RTVFSFILE hVfsFile;
340 HRESULT hrc = newAuxFloppyImage(getAuxiliaryFloppyFilePath().c_str(), fOverwrite, &hVfsFile);
341 if (SUCCEEDED(hrc))
342 {
343 /*
344 * Open the FAT file system so we can copy files onto the floppy.
345 */
346 RTERRINFOSTATIC ErrInfo;
347 RTVFS hVfs;
348 int vrc = RTFsFatVolOpen(hVfsFile, false /*fReadOnly*/, 0 /*offBootSector*/, &hVfs, RTErrInfoInitStatic(&ErrInfo));
349 RTVfsFileRelease(hVfsFile);
350 if (RT_SUCCESS(vrc))
351 {
352 /*
353 * Call overridable method to copies the files onto it.
354 */
355 hrc = copyFilesToAuxFloppyImage(hVfs);
356
357 /*
358 * Release the VFS. On failure, delete the floppy image so the operation can
359 * be repeated in non-overwrite mode and so that we don't leave any mess behind.
360 */
361 RTVfsRelease(hVfs);
362 }
363 else if (RTErrInfoIsSet(&ErrInfo.Core))
364 hrc = mpParent->setErrorBoth(E_FAIL, vrc,
365 tr("Failed to open FAT file system on newly created floppy image '%s': %Rrc: %s"),
366 getAuxiliaryFloppyFilePath().c_str(), vrc, ErrInfo.Core.pszMsg);
367 else
368 hrc = mpParent->setErrorBoth(E_FAIL, vrc,
369 tr("Failed to open FAT file system onnewly created floppy image '%s': %Rrc"),
370 getAuxiliaryFloppyFilePath().c_str(), vrc);
371 if (FAILED(hrc))
372 RTFileDelete(getAuxiliaryFloppyFilePath().c_str());
373 }
374 return hrc;
375}
376
377HRESULT UnattendedInstaller::newAuxFloppyImage(const char *pszFilename, bool fOverwrite, PRTVFSFILE phVfsFile)
378{
379 /*
380 * Open the image file.
381 */
382 HRESULT hrc;
383 RTVFSFILE hVfsFile;
384 uint64_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_ALL | (0660 << RTFILE_O_CREATE_MODE_SHIFT);
385 if (fOverwrite)
386 fOpen |= RTFILE_O_CREATE_REPLACE;
387 else
388 fOpen |= RTFILE_O_OPEN;
389 int vrc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile);
390 if (RT_SUCCESS(vrc))
391 {
392 /*
393 * Format it.
394 */
395 vrc = RTFsFatVolFormat144(hVfsFile, false /*fQuick*/);
396 if (RT_SUCCESS(vrc))
397 {
398 *phVfsFile = hVfsFile;
399 LogRelFlow(("UnattendedInstaller::newAuxFloppyImage: created and formatted '%s'\n", pszFilename));
400 return S_OK;
401 }
402
403 hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to format floppy image '%s': %Rrc"), pszFilename, vrc);
404 RTVfsFileRelease(hVfsFile);
405 RTFileDelete(pszFilename);
406 }
407 else
408 hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to create floppy image '%s': %Rrc"), pszFilename, vrc);
409 return hrc;
410}
411
412HRESULT UnattendedInstaller::copyFilesToAuxFloppyImage(RTVFS hVfs)
413{
414 HRESULT hrc = addScriptToFloppyImage(&mMainScript, hVfs);
415 if (SUCCEEDED(hrc))
416 hrc = addScriptToFloppyImage(&mPostScript, hVfs);
417 return hrc;
418}
419
420HRESULT UnattendedInstaller::addScriptToFloppyImage(BaseTextScript *pEditor, RTVFS hVfs)
421{
422 /*
423 * Open the destination file.
424 */
425 HRESULT hrc;
426 RTVFSFILE hVfsFileDst;
427 int vrc = RTVfsFileOpen(hVfs, pEditor->getDefaultFilename(),
428 RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL
429 | (0660 << RTFILE_O_CREATE_MODE_SHIFT),
430 &hVfsFileDst);
431 if (RT_SUCCESS(vrc))
432 {
433 /*
434 * Save the content to a string.
435 */
436 Utf8Str strScript;
437 hrc = pEditor->saveToString(strScript);
438 if (SUCCEEDED(hrc))
439 {
440 /*
441 * Write the string.
442 */
443 vrc = RTVfsFileWrite(hVfsFileDst, strScript.c_str(), strScript.length(), NULL);
444 if (RT_SUCCESS(vrc))
445 hrc = S_OK; /* done */
446 else
447 hrc = mpParent->setErrorBoth(E_FAIL, vrc,
448 tr("Error writing %zu bytes to '%s' in floppy image '%s': %Rrc",
449 "", strScript.length()),
450 strScript.length(), pEditor->getDefaultFilename(),
451 getAuxiliaryFloppyFilePath().c_str());
452 }
453 RTVfsFileRelease(hVfsFileDst);
454 }
455 else
456 hrc = mpParent->setErrorBoth(E_FAIL, vrc,
457 tr("Error creating '%s' in floppy image '%s': %Rrc"),
458 pEditor->getDefaultFilename(), getAuxiliaryFloppyFilePath().c_str());
459 return hrc;
460}
461
462HRESULT UnattendedInstaller::addFileToFloppyImage(RTVFS hVfs, const char *pszSrc, const char *pszDst)
463{
464 HRESULT hrc;
465
466 /*
467 * Open the source file.
468 */
469 RTVFSIOSTREAM hVfsIosSrc;
470 int vrc = RTVfsIoStrmOpenNormal(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsIosSrc);
471 if (RT_SUCCESS(vrc))
472 {
473 /*
474 * Open the destination file.
475 */
476 RTVFSFILE hVfsFileDst;
477 vrc = RTVfsFileOpen(hVfs, pszDst,
478 RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL | (0660 << RTFILE_O_CREATE_MODE_SHIFT),
479 &hVfsFileDst);
480 if (RT_SUCCESS(vrc))
481 {
482 /*
483 * Do the copying.
484 */
485 RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsFileDst);
486 vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
487 if (RT_SUCCESS(vrc))
488 hrc = S_OK;
489 else
490 hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error writing copying '%s' to floppy image '%s': %Rrc"),
491 pszSrc, getAuxiliaryFloppyFilePath().c_str(), vrc);
492 RTVfsIoStrmRelease(hVfsIosDst);
493 RTVfsFileRelease(hVfsFileDst);
494 }
495 else
496 hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error opening '%s' on floppy image '%s' for writing: %Rrc"),
497 pszDst, getAuxiliaryFloppyFilePath().c_str(), vrc);
498
499 RTVfsIoStrmRelease(hVfsIosSrc);
500 }
501 else
502 hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error opening '%s' for copying onto floppy image '%s': %Rrc"),
503 pszSrc, getAuxiliaryFloppyFilePath().c_str(), vrc);
504 return hrc;
505}
506
507HRESULT UnattendedInstaller::prepareAuxIsoImage(bool fOverwrite)
508{
509 /*
510 * Open the original installation ISO.
511 */
512 RTVFS hVfsOrgIso;
513 HRESULT hrc = openInstallIsoImage(&hVfsOrgIso);
514 if (SUCCEEDED(hrc))
515 {
516 /*
517 * The next steps depends on the kind of image we're making.
518 */
519 if (!isAuxiliaryIsoIsVISO())
520 {
521 RTFSISOMAKER hIsoMaker;
522 hrc = newAuxIsoImageMaker(&hIsoMaker);
523 if (SUCCEEDED(hrc))
524 {
525 hrc = addFilesToAuxIsoImageMaker(hIsoMaker, hVfsOrgIso);
526 if (SUCCEEDED(hrc))
527 hrc = finalizeAuxIsoImage(hIsoMaker, getAuxiliaryIsoFilePath().c_str(), fOverwrite);
528 RTFsIsoMakerRelease(hIsoMaker);
529 }
530 }
531 else
532 {
533 RTCList<RTCString> vecFiles(0);
534 RTCList<RTCString> vecArgs(0);
535 try
536 {
537 vecArgs.append() = "--iprt-iso-maker-file-marker-bourne-sh";
538 RTUUID Uuid;
539 int vrc = RTUuidCreate(&Uuid); AssertRC(vrc);
540 char szTmp[RTUUID_STR_LENGTH + 1];
541 vrc = RTUuidToStr(&Uuid, szTmp, sizeof(szTmp)); AssertRC(vrc);
542 vecArgs.append() = szTmp;
543 vecArgs.append() = "--file-mode=0444";
544 vecArgs.append() = "--dir-mode=0555";
545 }
546 catch (std::bad_alloc &)
547 {
548 hrc = E_OUTOFMEMORY;
549 }
550 if (SUCCEEDED(hrc))
551 {
552 hrc = addFilesToAuxVisoVectors(vecArgs, vecFiles, hVfsOrgIso, fOverwrite);
553 if (SUCCEEDED(hrc))
554 hrc = finalizeAuxVisoFile(vecArgs, getAuxiliaryIsoFilePath().c_str(), fOverwrite);
555
556 if (FAILED(hrc))
557 for (size_t i = 0; i < vecFiles.size(); i++)
558 RTFileDelete(vecFiles[i].c_str());
559 }
560 }
561 RTVfsRelease(hVfsOrgIso);
562 }
563 return hrc;
564}
565
566HRESULT UnattendedInstaller::openInstallIsoImage(PRTVFS phVfsIso, uint32_t fFlags /*= 0*/)
567{
568 /* Open the file. */
569 const char *pszIsoPath = mpParent->i_getIsoPath().c_str();
570 RTVFSFILE hOrgIsoFile;
571 int vrc = RTVfsFileOpenNormal(pszIsoPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hOrgIsoFile);
572 if (RT_FAILURE(vrc))
573 return mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to open ISO image '%s' (%Rrc)"), pszIsoPath, vrc);
574
575 /* Pass the file to the ISO file system interpreter. */
576 RTERRINFOSTATIC ErrInfo;
577 vrc = RTFsIso9660VolOpen(hOrgIsoFile, fFlags, phVfsIso, RTErrInfoInitStatic(&ErrInfo));
578 RTVfsFileRelease(hOrgIsoFile);
579 if (RT_SUCCESS(vrc))
580 return S_OK;
581 if (RTErrInfoIsSet(&ErrInfo.Core))
582 return mpParent->setErrorBoth(E_FAIL, vrc, tr("ISO reader fail to open '%s' (%Rrc): %s"),
583 pszIsoPath, vrc, ErrInfo.Core.pszMsg);
584 return mpParent->setErrorBoth(E_FAIL, vrc, tr("ISO reader fail to open '%s' (%Rrc)"), pszIsoPath, vrc);
585}
586
587HRESULT UnattendedInstaller::newAuxIsoImageMaker(PRTFSISOMAKER phIsoMaker)
588{
589 int vrc = RTFsIsoMakerCreate(phIsoMaker);
590 if (RT_SUCCESS(vrc))
591 return S_OK;
592 return mpParent->setErrorBoth(E_FAIL, vrc, tr("RTFsIsoMakerCreate failed (%Rrc)"), vrc);
593}
594
595HRESULT UnattendedInstaller::addFilesToAuxIsoImageMaker(RTFSISOMAKER hIsoMaker, RTVFS hVfsOrgIso)
596{
597 RT_NOREF(hVfsOrgIso);
598
599 /*
600 * Add the two scripts to the image with default names.
601 */
602 HRESULT hrc = addScriptToIsoMaker(&mMainScript, hIsoMaker);
603 if (SUCCEEDED(hrc))
604 hrc = addScriptToIsoMaker(&mPostScript, hIsoMaker);
605 return hrc;
606}
607
608HRESULT UnattendedInstaller::addScriptToIsoMaker(BaseTextScript *pEditor, RTFSISOMAKER hIsoMaker,
609 const char *pszDstFilename /*= NULL*/)
610{
611 /*
612 * Calc default destination filename if desired.
613 */
614 RTCString strDstNameBuf;
615 if (!pszDstFilename)
616 {
617 try
618 {
619 strDstNameBuf = RTPATH_SLASH_STR;
620 strDstNameBuf.append(pEditor->getDefaultTemplateFilename());
621 pszDstFilename = strDstNameBuf.c_str();
622 }
623 catch (std::bad_alloc &)
624 {
625 return E_OUTOFMEMORY;
626 }
627 }
628
629 /*
630 * Create a memory file for the script.
631 */
632 Utf8Str strScript;
633 HRESULT hrc = pEditor->saveToString(strScript);
634 if (SUCCEEDED(hrc))
635 {
636 RTVFSFILE hVfsScriptFile;
637 size_t cchScript = strScript.length();
638 int vrc = RTVfsFileFromBuffer(RTFILE_O_READ, strScript.c_str(), strScript.length(), &hVfsScriptFile);
639 strScript.setNull();
640 if (RT_SUCCESS(vrc))
641 {
642 /*
643 * Add it to the ISO.
644 */
645 vrc = RTFsIsoMakerAddFileWithVfsFile(hIsoMaker, pszDstFilename, hVfsScriptFile, NULL);
646 RTVfsFileRelease(hVfsScriptFile);
647 if (RT_SUCCESS(vrc))
648 hrc = S_OK;
649 else
650 hrc = mpParent->setErrorBoth(E_FAIL, vrc,
651 tr("RTFsIsoMakerAddFileWithVfsFile failed on the script '%s' (%Rrc)"),
652 pszDstFilename, vrc);
653 }
654 else
655 hrc = mpParent->setErrorBoth(E_FAIL, vrc,
656 tr("RTVfsFileFromBuffer failed on the %zu byte script '%s' (%Rrc)", "", cchScript),
657 cchScript, pszDstFilename, vrc);
658 }
659 return hrc;
660}
661
662HRESULT UnattendedInstaller::finalizeAuxIsoImage(RTFSISOMAKER hIsoMaker, const char *pszFilename, bool fOverwrite)
663{
664 /*
665 * Finalize the image.
666 */
667 int vrc = RTFsIsoMakerFinalize(hIsoMaker);
668 if (RT_FAILURE(vrc))
669 return mpParent->setErrorBoth(E_FAIL, vrc, tr("RTFsIsoMakerFinalize failed (%Rrc)"), vrc);
670
671 /*
672 * Open the destination file.
673 */
674 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_ALL;
675 if (fOverwrite)
676 fOpen |= RTFILE_O_CREATE_REPLACE;
677 else
678 fOpen |= RTFILE_O_CREATE;
679 RTVFSFILE hVfsDstFile;
680 vrc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsDstFile);
681 if (RT_FAILURE(vrc))
682 {
683 if (vrc == VERR_ALREADY_EXISTS)
684 return mpParent->setErrorBoth(E_FAIL, vrc, tr("The auxiliary ISO image file '%s' already exists"),
685 pszFilename);
686 return mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to open the auxiliary ISO image file '%s' for writing (%Rrc)"),
687 pszFilename, vrc);
688 }
689
690 /*
691 * Get the source file from the image maker.
692 */
693 HRESULT hrc;
694 RTVFSFILE hVfsSrcFile;
695 vrc = RTFsIsoMakerCreateVfsOutputFile(hIsoMaker, &hVfsSrcFile);
696 if (RT_SUCCESS(vrc))
697 {
698 RTVFSIOSTREAM hVfsSrcIso = RTVfsFileToIoStream(hVfsSrcFile);
699 RTVFSIOSTREAM hVfsDstIso = RTVfsFileToIoStream(hVfsDstFile);
700 if ( hVfsSrcIso != NIL_RTVFSIOSTREAM
701 && hVfsDstIso != NIL_RTVFSIOSTREAM)
702 {
703 vrc = RTVfsUtilPumpIoStreams(hVfsSrcIso, hVfsDstIso, 0 /*cbBufHint*/);
704 if (RT_SUCCESS(vrc))
705 hrc = S_OK;
706 else
707 hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Error writing auxiliary ISO image '%s' (%Rrc)"),
708 pszFilename, vrc);
709 }
710 else
711 hrc = mpParent->setErrorBoth(E_FAIL, VERR_INTERNAL_ERROR_2,
712 tr("Internal Error: Failed to case VFS file to VFS I/O stream"));
713 RTVfsIoStrmRelease(hVfsSrcIso);
714 RTVfsIoStrmRelease(hVfsDstIso);
715 }
716 else
717 hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("RTFsIsoMakerCreateVfsOutputFile failed (%Rrc)"), vrc);
718 RTVfsFileRelease(hVfsSrcFile);
719 RTVfsFileRelease(hVfsDstFile);
720 if (FAILED(hrc))
721 RTFileDelete(pszFilename);
722 return hrc;
723}
724
725HRESULT UnattendedInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
726 RTVFS hVfsOrgIso, bool fOverwrite)
727{
728 RT_NOREF(hVfsOrgIso);
729
730 /*
731 * Save and add the scripts.
732 */
733 HRESULT hrc = addScriptToVisoVectors(&mMainScript, rVecArgs, rVecFiles, fOverwrite);
734 if (SUCCEEDED(hrc))
735 hrc = addScriptToVisoVectors(&mPostScript, rVecArgs, rVecFiles, fOverwrite);
736 if (SUCCEEDED(hrc))
737 {
738 try
739 {
740 /*
741 * If we've got a Guest Additions ISO, add its content to a /vboxadditions dir.
742 */
743 if (mpParent->i_getInstallGuestAdditions())
744 {
745 rVecArgs.append().append("--push-iso=").append(mpParent->i_getAdditionsIsoPath());
746 rVecArgs.append() = "/vboxadditions=/";
747 rVecArgs.append() = "--pop";
748 }
749
750 /*
751 * If we've got a Validation Kit ISO, add its content to a /vboxvalidationkit dir.
752 */
753 if (mpParent->i_getInstallTestExecService())
754 {
755 rVecArgs.append().append("--push-iso=").append(mpParent->i_getValidationKitIsoPath());
756 rVecArgs.append() = "/vboxvalidationkit=/";
757 rVecArgs.append() = "--pop";
758 }
759 }
760 catch (std::bad_alloc &)
761 {
762 hrc = E_OUTOFMEMORY;
763 }
764 }
765 return hrc;
766}
767
768HRESULT UnattendedInstaller::addScriptToVisoVectors(BaseTextScript *pEditor, RTCList<RTCString> &rVecArgs,
769 RTCList<RTCString> &rVecFiles, bool fOverwrite)
770{
771 /*
772 * Calc the aux script file name.
773 */
774 RTCString strScriptName;
775 try
776 {
777 strScriptName = mpParent->i_getAuxiliaryBasePath();
778 strScriptName.append(pEditor->getDefaultFilename());
779 }
780 catch (std::bad_alloc &)
781 {
782 return E_OUTOFMEMORY;
783 }
784
785 /*
786 * Save it.
787 */
788 HRESULT hrc = pEditor->save(strScriptName.c_str(), fOverwrite);
789 if (SUCCEEDED(hrc))
790 {
791 /*
792 * Add it to the vectors.
793 */
794 try
795 {
796 rVecArgs.append().append('/').append(pEditor->getDefaultFilename()).append('=').append(strScriptName);
797 rVecFiles.append(strScriptName);
798 }
799 catch (std::bad_alloc &)
800 {
801 RTFileDelete(strScriptName.c_str());
802 hrc = E_OUTOFMEMORY;
803 }
804 }
805 return hrc;
806}
807
808HRESULT UnattendedInstaller::finalizeAuxVisoFile(RTCList<RTCString> const &rVecArgs, const char *pszFilename, bool fOverwrite)
809{
810 /*
811 * Create a C-style argument vector and turn that into a command line string.
812 */
813 size_t const cArgs = rVecArgs.size();
814 const char **papszArgs = (const char **)RTMemTmpAlloc((cArgs + 1) * sizeof(const char *));
815 if (!papszArgs)
816 return E_OUTOFMEMORY;
817 for (size_t i = 0; i < cArgs; i++)
818 papszArgs[i] = rVecArgs[i].c_str();
819 papszArgs[cArgs] = NULL;
820
821 char *pszCmdLine;
822 int vrc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
823 RTMemTmpFree(papszArgs);
824 if (RT_FAILURE(vrc))
825 return mpParent->setErrorBoth(E_FAIL, vrc, tr("RTGetOptArgvToString failed (%Rrc)"), vrc);
826
827 /*
828 * Open the file.
829 */
830 HRESULT hrc;
831 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ;
832 if (fOverwrite)
833 fOpen |= RTFILE_O_CREATE_REPLACE;
834 else
835 fOpen |= RTFILE_O_CREATE;
836 RTFILE hFile;
837 vrc = RTFileOpen(&hFile, pszFilename, fOpen);
838 if (RT_SUCCESS(vrc))
839 {
840 vrc = RTFileWrite(hFile, pszCmdLine, strlen(pszCmdLine), NULL);
841 if (RT_SUCCESS(vrc))
842 vrc = RTFileClose(hFile);
843 else
844 RTFileClose(hFile);
845 if (RT_SUCCESS(vrc))
846 hrc = S_OK;
847 else
848 hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error writing '%s' (%Rrc)"), pszFilename, vrc);
849 }
850 else
851 hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to create '%s' (%Rrc)"), pszFilename, vrc);
852
853 RTStrFree(pszCmdLine);
854 return hrc;
855}
856
857HRESULT UnattendedInstaller::loadAndParseFileFromIso(RTVFS hVfsOrgIso, const char *pszFilename, AbstractScript *pEditor)
858{
859 HRESULT hrc;
860 RTVFSFILE hVfsFile;
861 int vrc = RTVfsFileOpen(hVfsOrgIso, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsFile);
862 if (RT_SUCCESS(vrc))
863 {
864 hrc = pEditor->readFromHandle(hVfsFile, pszFilename);
865 RTVfsFileRelease(hVfsFile);
866 if (SUCCEEDED(hrc))
867 hrc = pEditor->parse();
868 }
869 else
870 hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to open '%s' on the ISO '%s' (%Rrc)"),
871 pszFilename, mpParent->i_getIsoPath().c_str(), vrc);
872 return hrc;
873}
874
875
876
877//////////////////////////////////////////////////////////////////////////////////////////////////////
878/*
879*
880*
881* Implementation UnattendedLinuxInstaller functions
882*
883*/
884//////////////////////////////////////////////////////////////////////////////////////////////////////
885HRESULT UnattendedLinuxInstaller::editIsoLinuxCfg(GeneralTextScript *pEditor)
886{
887 try
888 {
889 /* Comment out 'display <filename>' directives that's used for displaying files at boot time. */
890 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("display", RTCString::CaseInsensitive);
891 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
892 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("display", RTCString::CaseInsensitive))
893 {
894 HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
895 if (FAILED(hrc))
896 return hrc;
897 }
898 }
899 catch (std::bad_alloc &)
900 {
901 return E_OUTOFMEMORY;
902 }
903 return editIsoLinuxCommon(pEditor);
904}
905
906HRESULT UnattendedLinuxInstaller::editIsoLinuxCommon(GeneralTextScript *pEditor)
907{
908 try
909 {
910 /* Set timeouts to 4 seconds. */
911 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("timeout", RTCString::CaseInsensitive);
912 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
913 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("timeout", RTCString::CaseInsensitive))
914 {
915 HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), "timeout 4");
916 if (FAILED(hrc))
917 return hrc;
918 }
919
920 /* Modify kernel parameters. */
921 vecLineNumbers = pEditor->findTemplate("append", RTCString::CaseInsensitive);
922 if (vecLineNumbers.size() > 0)
923 {
924 Utf8Str const &rStrAppend = mpParent->i_getExtraInstallKernelParameters().isNotEmpty()
925 ? mpParent->i_getExtraInstallKernelParameters()
926 : mStrDefaultExtraInstallKernelParameters;
927
928 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
929 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("append", RTCString::CaseInsensitive))
930 {
931 Utf8Str strLine = pEditor->getContentOfLine(vecLineNumbers[i]);
932
933 /* Do removals. */
934 if (mArrStrRemoveInstallKernelParameters.size() > 0)
935 {
936 size_t offStart = strLine.find("append") + 5;
937 while (offStart < strLine.length() && !RT_C_IS_SPACE(strLine[offStart]))
938 offStart++;
939 while (offStart < strLine.length() && RT_C_IS_SPACE(strLine[offStart]))
940 offStart++;
941 if (offStart < strLine.length())
942 {
943 for (size_t iRemove = 0; iRemove < mArrStrRemoveInstallKernelParameters.size(); iRemove++)
944 {
945 RTCString const &rStrRemove = mArrStrRemoveInstallKernelParameters[iRemove];
946 for (size_t off = offStart; off < strLine.length(); )
947 {
948 Assert(!RT_C_IS_SPACE(strLine[off]));
949
950 /* Find the end of word. */
951 size_t offEnd = off + 1;
952 while (offEnd < strLine.length() && !RT_C_IS_SPACE(strLine[offEnd]))
953 offEnd++;
954
955 /* Check if it matches. */
956 if (RTStrSimplePatternNMatch(rStrRemove.c_str(), rStrRemove.length(),
957 strLine.c_str() + off, offEnd - off))
958 {
959 while (off > 0 && RT_C_IS_SPACE(strLine[off - 1]))
960 off--;
961 strLine.erase(off, offEnd - off);
962 }
963
964 /* Advance to the next word. */
965 off = offEnd;
966 while (off < strLine.length() && RT_C_IS_SPACE(strLine[off]))
967 off++;
968 }
969 }
970 }
971 }
972
973 /* Do the appending. */
974 if (rStrAppend.isNotEmpty())
975 {
976 if (!rStrAppend.startsWith(" ") && !strLine.endsWith(" "))
977 strLine.append(' ');
978 strLine.append(rStrAppend);
979 }
980
981 /* Update line. */
982 HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strLine);
983 if (FAILED(hrc))
984 return hrc;
985 }
986 }
987 }
988 catch (std::bad_alloc &)
989 {
990 return E_OUTOFMEMORY;
991 }
992 return S_OK;
993}
994
995
996HRESULT UnattendedLinuxInstaller::editGrubCfg(GeneralTextScript *pEditor)
997{
998 /* Default menu entry of grub.cfg is set in /etc/deafult/grub file. */
999 try
1000 {
1001 /* Set timeouts to 4 seconds. */
1002 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("set timeout", RTCString::CaseInsensitive);
1003 if (vecLineNumbers.size() > 0)
1004 {
1005 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1006 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("set timeout", RTCString::CaseInsensitive))
1007 {
1008 HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), "set timeout=4");
1009 if (FAILED(hrc))
1010 return hrc;
1011 }
1012 }
1013 else
1014 {
1015 /* Append timeout if not set (happens with arm64 iso images at least). */
1016 HRESULT hrc = pEditor->appendLine("set timeout=4");
1017 if (FAILED(hrc))
1018 return hrc;
1019 }
1020
1021 /* Modify kernel lines assuming that they starts with 'linux' keyword and 2nd word is the kernel command.*
1022 * we remove whatever comes after command and add our own command line options. */
1023 vecLineNumbers = pEditor->findTemplate("linux", RTCString::CaseInsensitive);
1024 if (vecLineNumbers.size() > 0)
1025 {
1026 Utf8Str const &rStrAppend = mpParent->i_getExtraInstallKernelParameters().isNotEmpty()
1027 ? mpParent->i_getExtraInstallKernelParameters()
1028 : mStrDefaultExtraInstallKernelParameters;
1029
1030 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1031 {
1032 HRESULT hrc = S_OK;
1033 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("linux", RTCString::CaseInsensitive))
1034 {
1035 Utf8Str strLine = pEditor->getContentOfLine(vecLineNumbers[i]);
1036 size_t cbPos = strLine.find("linux") + strlen("linux");
1037 bool fSecondWord = false;
1038 /* Find the end of 2nd word assuming that it is kernel command. */
1039 while (cbPos < strLine.length())
1040 {
1041 if (!fSecondWord)
1042 {
1043 if (strLine[cbPos] != '\t' && strLine[cbPos] != ' ')
1044 fSecondWord = true;
1045 }
1046 else
1047 {
1048 if (strLine[cbPos] == '\t' || strLine[cbPos] == ' ')
1049 break;
1050 }
1051 ++cbPos;
1052 }
1053 if (!fSecondWord)
1054 hrc = E_FAIL;
1055
1056 if (SUCCEEDED(hrc))
1057 {
1058 strLine.erase(cbPos, strLine.length() - cbPos);
1059
1060 /* Do the appending. */
1061 if (rStrAppend.isNotEmpty())
1062 {
1063 if (!rStrAppend.startsWith(" ") && !strLine.endsWith(" "))
1064 strLine.append(' ');
1065 strLine.append(rStrAppend);
1066 }
1067
1068 /* Update line. */
1069 hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strLine);
1070 }
1071 if (FAILED(hrc))
1072 return hrc;
1073 }
1074 }
1075 }
1076 }
1077 catch (std::bad_alloc &)
1078 {
1079 return E_OUTOFMEMORY;
1080 }
1081 return S_OK;
1082}
1083
1084
1085//////////////////////////////////////////////////////////////////////////////////////////////////////
1086/*
1087*
1088*
1089* Implementation UnattendedDebianInstaller functions
1090*
1091*/
1092//////////////////////////////////////////////////////////////////////////////////////////////////////
1093
1094/**
1095 * Helper for checking if a file exists.
1096 * @todo promote to IPRT?
1097 */
1098static bool hlpVfsFileExists(RTVFS hVfs, const char *pszPath)
1099{
1100 RTFSOBJINFO ObjInfo;
1101 int vrc = RTVfsQueryPathInfo(hVfs, pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1102 return RT_SUCCESS(vrc) && RTFS_IS_FILE(ObjInfo.Attr.fMode);
1103}
1104
1105HRESULT UnattendedDebianInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
1106 RTVFS hVfsOrgIso, bool fOverwrite)
1107{
1108 /*
1109 * Figure out the name of the menu config file that we have to edit.
1110 */
1111 bool fMenuConfigIsGrub = false;
1112 const char *pszMenuConfigFilename = "/isolinux/txt.cfg";
1113 if (!hlpVfsFileExists(hVfsOrgIso, pszMenuConfigFilename))
1114 {
1115 /* On Debian Live ISOs (at least from 9 to 11) the there is only menu.cfg. */
1116 if (hlpVfsFileExists(hVfsOrgIso, "/isolinux/menu.cfg"))
1117 pszMenuConfigFilename = "/isolinux/menu.cfg";
1118 /* On Linux Mint 20.3, 21, and 19 (at least) there is only isolinux.cfg. */
1119 else if (hlpVfsFileExists(hVfsOrgIso, "/isolinux/isolinux.cfg"))
1120 pszMenuConfigFilename = "/isolinux/isolinux.cfg";
1121 /* Ubuntus 21.10+ are UEFI only. No isolinux directory. We modify grub.cfg. */
1122 else if (hlpVfsFileExists(hVfsOrgIso, "/boot/grub/grub.cfg"))
1123 {
1124 pszMenuConfigFilename = "/boot/grub/grub.cfg";
1125 fMenuConfigIsGrub = true;
1126 }
1127 }
1128
1129 /* Check for existence of isolinux.cfg since UEFI-only ISOs do not have this file. */
1130 bool const fIsoLinuxCfgExists = hlpVfsFileExists(hVfsOrgIso, "isolinux/isolinux.cfg");
1131 Assert(!fIsoLinuxCfgExists || !fMenuConfigIsGrub); /** @todo r=bird: Perhaps prefix the hlpVfsFileExists call with 'fIsoLinuxCfgExists &&' above ? */
1132
1133 /*
1134 * VISO bits and filenames.
1135 */
1136 RTCString strIsoLinuxCfg;
1137 RTCString strTxtCfg;
1138 try
1139 {
1140 /* Remaster ISO. */
1141 rVecArgs.append() = "--no-file-mode";
1142 rVecArgs.append() = "--no-dir-mode";
1143
1144 rVecArgs.append() = "--import-iso";
1145 rVecArgs.append(mpParent->i_getIsoPath());
1146
1147 rVecArgs.append() = "--file-mode=0444";
1148 rVecArgs.append() = "--dir-mode=0555";
1149
1150 /* Replace the isolinux.cfg configuration file. */
1151 if (fIsoLinuxCfgExists)
1152 {
1153 /* First remove. */
1154 rVecArgs.append() = "isolinux/isolinux.cfg=:must-remove:";
1155 /* Then add the modified file. */
1156 strIsoLinuxCfg = mpParent->i_getAuxiliaryBasePath();
1157 strIsoLinuxCfg.append("isolinux-isolinux.cfg");
1158 rVecArgs.append().append("isolinux/isolinux.cfg=").append(strIsoLinuxCfg);
1159 }
1160
1161 /*
1162 * Replace menu configuration file as well.
1163 * Some distros (Linux Mint) has only isolinux.cfg. No menu.cfg or txt.cfg.
1164 */
1165 if (RTStrICmp(pszMenuConfigFilename, "/isolinux/isolinux.cfg") != 0)
1166 {
1167
1168 /* Replace menu configuration file as well. */
1169 rVecArgs.append().assign(pszMenuConfigFilename).append("=:must-remove:");
1170 strTxtCfg = mpParent->i_getAuxiliaryBasePath();
1171 if (fMenuConfigIsGrub)
1172 strTxtCfg.append("grub.cfg");
1173 else
1174 strTxtCfg.append("isolinux-txt.cfg");
1175 rVecArgs.append().assign(pszMenuConfigFilename).append("=").append(strTxtCfg);
1176 }
1177 }
1178 catch (std::bad_alloc &)
1179 {
1180 return E_OUTOFMEMORY;
1181 }
1182
1183 /*
1184 * Edit the isolinux.cfg file if it is there.
1185 */
1186 if (fIsoLinuxCfgExists)
1187 {
1188 GeneralTextScript Editor(mpParent);
1189 HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, "/isolinux/isolinux.cfg", &Editor);
1190 if (SUCCEEDED(hrc))
1191 hrc = editIsoLinuxCfg(&Editor, RTPathFilename(pszMenuConfigFilename));
1192 if (SUCCEEDED(hrc))
1193 {
1194 hrc = Editor.save(strIsoLinuxCfg, fOverwrite);
1195 if (SUCCEEDED(hrc))
1196 {
1197 try
1198 {
1199 rVecFiles.append(strIsoLinuxCfg);
1200 }
1201 catch (std::bad_alloc &)
1202 {
1203 RTFileDelete(strIsoLinuxCfg.c_str());
1204 hrc = E_OUTOFMEMORY;
1205 }
1206 }
1207 }
1208 if (FAILED(hrc))
1209 return hrc;
1210 }
1211
1212 /*
1213 * Edit the menu config file.
1214 * Some distros (Linux Mint) has only isolinux.cfg. No menu.cfg or txt.cfg.
1215 */
1216 if (RTStrICmp(pszMenuConfigFilename, "/isolinux/isolinux.cfg") != 0)
1217 {
1218 GeneralTextScript Editor(mpParent);
1219 HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, pszMenuConfigFilename, &Editor);
1220 if (SUCCEEDED(hrc))
1221 {
1222 if (fMenuConfigIsGrub)
1223 hrc = editGrubCfg(&Editor);
1224 else
1225 hrc = editDebianMenuCfg(&Editor);
1226 if (SUCCEEDED(hrc))
1227 {
1228 hrc = Editor.save(strTxtCfg, fOverwrite);
1229 if (SUCCEEDED(hrc))
1230 {
1231 try
1232 {
1233 rVecFiles.append(strTxtCfg);
1234 }
1235 catch (std::bad_alloc &)
1236 {
1237 RTFileDelete(strTxtCfg.c_str());
1238 hrc = E_OUTOFMEMORY;
1239 }
1240 }
1241 }
1242 }
1243 if (FAILED(hrc))
1244 return hrc;
1245 }
1246
1247 /*
1248 * Call parent to add the preseed file from mAlg.
1249 */
1250 return UnattendedLinuxInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
1251}
1252
1253HRESULT UnattendedDebianInstaller::editIsoLinuxCfg(GeneralTextScript *pEditor, const char *pszMenuConfigFileName)
1254{
1255 try
1256 {
1257 /* Include menu config file. Since it can be txt.cfg, menu.cfg or something else we need to parametrize this. */
1258 if (pszMenuConfigFileName && pszMenuConfigFileName[0] != '\0')
1259 {
1260 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("include", RTCString::CaseInsensitive);
1261 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1262 {
1263 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("include", RTCString::CaseInsensitive))
1264 {
1265 Utf8Str strIncludeLine("include ");
1266 strIncludeLine.append(pszMenuConfigFileName);
1267 HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strIncludeLine);
1268 if (FAILED(hrc))
1269 return hrc;
1270 }
1271 }
1272 }
1273
1274 /* Comment out default directives since in Debian case default is handled in menu config file. */
1275 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("default", RTCString::CaseInsensitive);
1276 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1277 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("default", RTCString::CaseInsensitive)
1278 && !pEditor->getContentOfLine(vecLineNumbers[i]).contains("default vesa", RTCString::CaseInsensitive))
1279 {
1280 HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
1281 if (FAILED(hrc))
1282 return hrc;
1283 }
1284
1285 /* Comment out "ui gfxboot bootlogo" line as it somehow messes things up on Kubuntu 20.04 (possibly others as well). */
1286 vecLineNumbers = pEditor->findTemplate("ui gfxboot", RTCString::CaseInsensitive);
1287 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1288 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("ui gfxboot", RTCString::CaseInsensitive))
1289 {
1290 HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
1291 if (FAILED(hrc))
1292 return hrc;
1293 }
1294 }
1295 catch (std::bad_alloc &)
1296 {
1297 return E_OUTOFMEMORY;
1298 }
1299 return UnattendedLinuxInstaller::editIsoLinuxCfg(pEditor);
1300}
1301
1302HRESULT UnattendedDebianInstaller::editDebianMenuCfg(GeneralTextScript *pEditor)
1303{
1304 /*
1305 * Unlike Redhats, Debian variants define boot menu not in isolinux.cfg but some other
1306 * menu configuration files. They are mostly called txt.cfg and/or menu.cfg (and possibly some other names)
1307 * In this functions we attempt to set menu's default label (default menu item) to the one containing the word 'install',
1308 * failing to find such a label (on Kubuntu 20.04 for example) we pick the first label with name 'live'.
1309 */
1310 try
1311 {
1312 HRESULT hrc = S_OK;
1313 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("label", RTCString::CaseInsensitive);
1314 const char *pszNewLabelName = "VBoxUnatendedInstall";
1315 bool fLabelFound = modifyLabelLine(pEditor, vecLineNumbers, "install", pszNewLabelName);
1316 if (!fLabelFound)
1317 fLabelFound = modifyLabelLine(pEditor, vecLineNumbers, "live", pszNewLabelName);
1318
1319 if (!fLabelFound)
1320 hrc = E_FAIL;;
1321
1322 if (SUCCEEDED(hrc))
1323 {
1324 /* Modify the content of default lines so that they point to label we have chosen above. */
1325 Utf8Str strNewContent("default ");
1326 strNewContent.append(pszNewLabelName);
1327
1328 std::vector<size_t> vecDefaultLineNumbers = pEditor->findTemplate("default", RTCString::CaseInsensitive);
1329 if (!vecDefaultLineNumbers.empty())
1330 {
1331 for (size_t j = 0; j < vecDefaultLineNumbers.size(); ++j)
1332 {
1333 hrc = pEditor->setContentOfLine(vecDefaultLineNumbers[j], strNewContent);
1334 if (FAILED(hrc))
1335 break;
1336 }
1337 }
1338 /* Add a defaul label line. */
1339 else
1340 hrc = pEditor->appendLine(strNewContent);
1341 }
1342 if (FAILED(hrc))
1343 return hrc;
1344 }
1345 catch (std::bad_alloc &)
1346 {
1347 return E_OUTOFMEMORY;
1348 }
1349 return UnattendedLinuxInstaller::editIsoLinuxCommon(pEditor);
1350}
1351
1352bool UnattendedDebianInstaller::modifyLabelLine(GeneralTextScript *pEditor, const std::vector<size_t> &vecLineNumbers,
1353 const char *pszKeyWord, const char *pszNewLabelName)
1354{
1355 if (!pEditor)
1356 return false;
1357 Utf8Str strNewLabel("label ");
1358 strNewLabel.append(pszNewLabelName);
1359 HRESULT hrc = S_OK;
1360 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1361 {
1362 RTCString const &rContent = pEditor->getContentOfLine(vecLineNumbers[i]);
1363 /* Skip this line if it does not start with the word 'label'. */
1364 if (!RTStrIStartsWith(rContent.c_str(), "label"))
1365 continue;
1366 /* Use the first menu item starting with word label and includes pszKeyWord.*/
1367 if (RTStrIStr(rContent.c_str(), pszKeyWord) != NULL)
1368 {
1369 /* Set the content of the line. It looks like multiple word labels (like label Debian Installer)
1370 * does not work very well in some cases. */
1371 hrc = pEditor->setContentOfLine(vecLineNumbers[i], strNewLabel);
1372 if (SUCCEEDED(hrc))
1373 return true;
1374 }
1375 }
1376 return false;
1377}
1378
1379
1380//////////////////////////////////////////////////////////////////////////////////////////////////////
1381/*
1382*
1383*
1384* Implementation UnattendedRhel6Installer functions
1385*
1386*/
1387//////////////////////////////////////////////////////////////////////////////////////////////////////
1388HRESULT UnattendedRhelInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
1389 RTVFS hVfsOrgIso, bool fOverwrite)
1390{
1391 /*
1392 * Figure out the name of the menu config file that we have to edit.
1393 */
1394 bool fMenuConfigIsGrub = false;
1395 const char *pszMenuConfigFilename = "/isolinux/isolinux.cfg";
1396 if (!hlpVfsFileExists(hVfsOrgIso, pszMenuConfigFilename))
1397 {
1398 /* arm64 variants of Oracle Linux 9 have grub. */
1399 if (hlpVfsFileExists(hVfsOrgIso, "/EFI/BOOT/grub.cfg"))
1400 {
1401 pszMenuConfigFilename = "/EFI/BOOT/grub.cfg";
1402 fMenuConfigIsGrub = true;
1403 }
1404 else
1405 AssertFailed();
1406 }
1407
1408 /*
1409 * VISO bits and filenames.
1410 */
1411 RTCString strBootCfg;
1412 try
1413 {
1414#if 1
1415 /* Remaster ISO. */
1416 rVecArgs.append() = "--no-file-mode";
1417 rVecArgs.append() = "--no-dir-mode";
1418
1419 rVecArgs.append() = "--import-iso";
1420 rVecArgs.append(mpParent->i_getIsoPath());
1421
1422 rVecArgs.append() = "--file-mode=0444";
1423 rVecArgs.append() = "--dir-mode=0555";
1424
1425 /* Replace the grub.cfg/isolinux.cfg configuration file. */
1426 if (fMenuConfigIsGrub)
1427 {
1428 /* Replace menu configuration file as well. */
1429 rVecArgs.append().assign(pszMenuConfigFilename).append("=:must-remove:");
1430 strBootCfg = mpParent->i_getAuxiliaryBasePath();
1431 strBootCfg.append("grub.cfg");
1432 rVecArgs.append().assign(pszMenuConfigFilename).append("=").append(strBootCfg);
1433 }
1434 else
1435 {
1436 /* First remove. */
1437 rVecArgs.append() = "isolinux/isolinux.cfg=:must-remove:";
1438 strBootCfg = mpParent->i_getAuxiliaryBasePath();
1439 strBootCfg.append("isolinux-isolinux.cfg");
1440 rVecArgs.append().append("isolinux/isolinux.cfg=").append(strBootCfg);
1441 }
1442#else
1443 /** @todo Maybe we should just remaster the ISO for redhat derivatives too?
1444 * One less CDROM to mount. */
1445 /* Name the ISO. */
1446 rVecArgs.append() = "--volume-id=VBox Unattended Boot";
1447
1448 /* Copy the isolinux directory from the original install ISO. */
1449 rVecArgs.append().append("--push-iso=").append(mpParent->i_getIsoPath());
1450 rVecArgs.append() = "/isolinux=/isolinux";
1451 rVecArgs.append() = "--pop";
1452
1453 /* We replace isolinux.cfg with our edited version (see further down). */
1454 rVecArgs.append() = "/isolinux/isolinux.cfg=:must-remove:";
1455
1456 strBootCfg = mpParent->i_getAuxiliaryBasePath();
1457 strBootCfg.append("isolinux-isolinux.cfg");
1458 rVecArgs.append().append("/isolinux/isolinux.cfg=").append(strBootCfg);
1459
1460 /* Configure booting /isolinux/isolinux.bin. */
1461 rVecArgs.append() = "--eltorito-boot";
1462 rVecArgs.append() = "/isolinux/isolinux.bin";
1463 rVecArgs.append() = "--no-emulation-boot";
1464 rVecArgs.append() = "--boot-info-table";
1465 rVecArgs.append() = "--boot-load-seg=0x07c0";
1466 rVecArgs.append() = "--boot-load-size=4";
1467
1468 /* Make the boot catalog visible in the file system. */
1469 rVecArgs.append() = "--boot-catalog=/isolinux/vboxboot.cat";
1470#endif
1471 }
1472 catch (std::bad_alloc &)
1473 {
1474 return E_OUTOFMEMORY;
1475 }
1476
1477 {
1478 GeneralTextScript Editor(mpParent);
1479 HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, pszMenuConfigFilename, &Editor);
1480 if (SUCCEEDED(hrc))
1481 {
1482 if (fMenuConfigIsGrub)
1483 hrc = editGrubCfg(&Editor);
1484 else
1485 hrc = editIsoLinuxCfg(&Editor);
1486 if (SUCCEEDED(hrc))
1487 {
1488 hrc = Editor.save(strBootCfg, fOverwrite);
1489 if (SUCCEEDED(hrc))
1490 {
1491 try
1492 {
1493 rVecFiles.append(strBootCfg);
1494 }
1495 catch (std::bad_alloc &)
1496 {
1497 RTFileDelete(strBootCfg.c_str());
1498 hrc = E_OUTOFMEMORY;
1499 }
1500 }
1501 }
1502 }
1503 if (FAILED(hrc))
1504 return hrc;
1505 }
1506
1507 /*
1508 * Call parent to add the ks.cfg file from mAlg.
1509 */
1510 return UnattendedLinuxInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
1511}
1512
1513
1514//////////////////////////////////////////////////////////////////////////////////////////////////////
1515/*
1516*
1517*
1518* Implementation UnattendedSuseInstaller functions
1519*
1520*/
1521//////////////////////////////////////////////////////////////////////////////////////////////////////
1522#if 0 /* doesn't work, so convert later */
1523/*
1524 *
1525 * UnattendedSuseInstaller protected methods
1526 *
1527*/
1528HRESULT UnattendedSuseInstaller::setUserData()
1529{
1530 HRESULT hrc = S_OK;
1531 //here base class function must be called first
1532 //because user home directory is set after user name
1533 hrc = UnattendedInstaller::setUserData();
1534
1535 hrc = mAlg->setField(USERHOMEDIR_ID, "");
1536 if (FAILED(hrc))
1537 return hrc;
1538
1539 return hrc;
1540}
1541
1542/*
1543 *
1544 * UnattendedSuseInstaller private methods
1545 *
1546*/
1547
1548HRESULT UnattendedSuseInstaller::iv_initialPhase()
1549{
1550 Assert(isAuxiliaryIsoNeeded());
1551 if (mParent->i_isGuestOs64Bit())
1552 mFilesAndDirsToExtractFromIso.append("boot/x86_64/loader/ ");
1553 else
1554 mFilesAndDirsToExtractFromIso.append("boot/i386/loader/ ");
1555 return extractOriginalIso(mFilesAndDirsToExtractFromIso);
1556}
1557
1558
1559HRESULT UnattendedSuseInstaller::setupScriptOnAuxiliaryCD(const Utf8Str &path)
1560{
1561 HRESULT hrc = S_OK;
1562
1563 GeneralTextScript isoSuseCfgScript(mpParent);
1564 hrc = isoSuseCfgScript.read(path);
1565 hrc = isoSuseCfgScript.parse();
1566 //fix linux core bootable parameters: add path to the preseed script
1567
1568 std::vector<size_t> listOfLines = isoSuseCfgScript.findTemplate("append");
1569 for(unsigned int i=0; i<listOfLines.size(); ++i)
1570 {
1571 isoSuseCfgScript.appendToLine(listOfLines.at(i),
1572 " auto=true priority=critical autoyast=default instmode=cd quiet splash noprompt noshell --");
1573 }
1574
1575 //find all lines with "label" inside
1576 listOfLines = isoSuseCfgScript.findTemplate("label");
1577 for(unsigned int i=0; i<listOfLines.size(); ++i)
1578 {
1579 Utf8Str content = isoSuseCfgScript.getContentOfLine(listOfLines.at(i));
1580
1581 //suppose general string looks like "label linux", two words separated by " ".
1582 RTCList<RTCString> partsOfcontent = content.split(" ");
1583
1584 if (partsOfcontent.at(1).contains("linux"))
1585 {
1586 std::vector<size_t> listOfDefault = isoSuseCfgScript.findTemplate("default");
1587 //handle the lines more intelligently
1588 for(unsigned int j=0; j<listOfDefault.size(); ++j)
1589 {
1590 Utf8Str newContent("default ");
1591 newContent.append(partsOfcontent.at(1));
1592 isoSuseCfgScript.setContentOfLine(listOfDefault.at(j), newContent);
1593 }
1594 }
1595 }
1596
1597 hrc = isoSuseCfgScript.save(path, true);
1598
1599 LogRelFunc(("UnattendedSuseInstaller::setupScriptsOnAuxiliaryCD(): The file %s has been changed\n", path.c_str()));
1600
1601 return hrc;
1602}
1603#endif
1604
1605
1606//////////////////////////////////////////////////////////////////////////////////////////////////////
1607/*
1608*
1609*
1610* Implementation UnattendedFreeBsdInstaller functions
1611*
1612*/
1613//////////////////////////////////////////////////////////////////////////////////////////////////////
1614HRESULT UnattendedFreeBsdInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
1615 RTVFS hVfsOrgIso, bool fOverwrite)
1616{
1617 try
1618 {
1619 RTCString strScriptName;
1620 strScriptName = mpParent->i_getAuxiliaryBasePath();
1621 strScriptName.append(mMainScript.getDefaultFilename());
1622
1623 /* Need to retain the original file permissions for executables. */
1624 rVecArgs.append() = "--no-file-mode";
1625 rVecArgs.append() = "--no-dir-mode";
1626
1627 rVecArgs.append() = "--import-iso";
1628 rVecArgs.append(mpParent->i_getIsoPath());
1629
1630 rVecArgs.append() = "--file-mode=0444";
1631 rVecArgs.append() = "--dir-mode=0555";
1632
1633 /* Remaster ISO, the installer config has to go into /etc. */
1634 rVecArgs.append().append("/etc/installerconfig=").append(strScriptName);
1635 }
1636 catch (std::bad_alloc &)
1637 {
1638 return E_OUTOFMEMORY;
1639 }
1640
1641 /*
1642 * Call parent to add the remaining files
1643 */
1644 return UnattendedInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
1645}
Note: See TracBrowser for help on using the repository browser.

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