VirtualBox

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

Last change on this file since 101685 was 101685, checked in by vboxsync, 12 months ago

Main/Unattended|GuestOSType: Add an entry in the guest OS type to indicate the name of the additions install package instead of hardcoding it in the templates. Allows easy adaption if something changes and makes it possible to auto install guest additions for linux.arm64 guests, bugref:10542 [missing files]

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 59.7 KB
Line 
1/* $Id: UnattendedInstaller.cpp 101685 2023-10-31 12:40:20Z 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
996//////////////////////////////////////////////////////////////////////////////////////////////////////
997/*
998*
999*
1000* Implementation UnattendedDebianInstaller functions
1001*
1002*/
1003//////////////////////////////////////////////////////////////////////////////////////////////////////
1004
1005/**
1006 * Helper for checking if a file exists.
1007 * @todo promote to IPRT?
1008 */
1009static bool hlpVfsFileExists(RTVFS hVfs, const char *pszPath)
1010{
1011 RTFSOBJINFO ObjInfo;
1012 int vrc = RTVfsQueryPathInfo(hVfs, pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1013 return RT_SUCCESS(vrc) && RTFS_IS_FILE(ObjInfo.Attr.fMode);
1014}
1015
1016HRESULT UnattendedDebianInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
1017 RTVFS hVfsOrgIso, bool fOverwrite)
1018{
1019 /*
1020 * Figure out the name of the menu config file that we have to edit.
1021 */
1022 bool fMenuConfigIsGrub = false;
1023 const char *pszMenuConfigFilename = "/isolinux/txt.cfg";
1024 if (!hlpVfsFileExists(hVfsOrgIso, pszMenuConfigFilename))
1025 {
1026 /* On Debian Live ISOs (at least from 9 to 11) the there is only menu.cfg. */
1027 if (hlpVfsFileExists(hVfsOrgIso, "/isolinux/menu.cfg"))
1028 pszMenuConfigFilename = "/isolinux/menu.cfg";
1029 /* On Linux Mint 20.3, 21, and 19 (at least) there is only isolinux.cfg. */
1030 else if (hlpVfsFileExists(hVfsOrgIso, "/isolinux/isolinux.cfg"))
1031 pszMenuConfigFilename = "/isolinux/isolinux.cfg";
1032 /* Ubuntus 21.10+ are UEFI only. No isolinux directory. We modify grub.cfg. */
1033 else if (hlpVfsFileExists(hVfsOrgIso, "/boot/grub/grub.cfg"))
1034 {
1035 pszMenuConfigFilename = "/boot/grub/grub.cfg";
1036 fMenuConfigIsGrub = true;
1037 }
1038 }
1039
1040 /* Check for existence of isolinux.cfg since UEFI-only ISOs do not have this file. */
1041 bool const fIsoLinuxCfgExists = hlpVfsFileExists(hVfsOrgIso, "isolinux/isolinux.cfg");
1042 Assert(!fIsoLinuxCfgExists || !fMenuConfigIsGrub); /** @todo r=bird: Perhaps prefix the hlpVfsFileExists call with 'fIsoLinuxCfgExists &&' above ? */
1043
1044 /*
1045 * VISO bits and filenames.
1046 */
1047 RTCString strIsoLinuxCfg;
1048 RTCString strTxtCfg;
1049 try
1050 {
1051 /* Remaster ISO. */
1052 rVecArgs.append() = "--no-file-mode";
1053 rVecArgs.append() = "--no-dir-mode";
1054
1055 rVecArgs.append() = "--import-iso";
1056 rVecArgs.append(mpParent->i_getIsoPath());
1057
1058 rVecArgs.append() = "--file-mode=0444";
1059 rVecArgs.append() = "--dir-mode=0555";
1060
1061 /* Replace the isolinux.cfg configuration file. */
1062 if (fIsoLinuxCfgExists)
1063 {
1064 /* First remove. */
1065 rVecArgs.append() = "isolinux/isolinux.cfg=:must-remove:";
1066 /* Then add the modified file. */
1067 strIsoLinuxCfg = mpParent->i_getAuxiliaryBasePath();
1068 strIsoLinuxCfg.append("isolinux-isolinux.cfg");
1069 rVecArgs.append().append("isolinux/isolinux.cfg=").append(strIsoLinuxCfg);
1070 }
1071
1072 /*
1073 * Replace menu configuration file as well.
1074 * Some distros (Linux Mint) has only isolinux.cfg. No menu.cfg or txt.cfg.
1075 */
1076 if (RTStrICmp(pszMenuConfigFilename, "/isolinux/isolinux.cfg") != 0)
1077 {
1078
1079 /* Replace menu configuration file as well. */
1080 rVecArgs.append().assign(pszMenuConfigFilename).append("=:must-remove:");
1081 strTxtCfg = mpParent->i_getAuxiliaryBasePath();
1082 if (fMenuConfigIsGrub)
1083 strTxtCfg.append("grub.cfg");
1084 else
1085 strTxtCfg.append("isolinux-txt.cfg");
1086 rVecArgs.append().assign(pszMenuConfigFilename).append("=").append(strTxtCfg);
1087 }
1088 }
1089 catch (std::bad_alloc &)
1090 {
1091 return E_OUTOFMEMORY;
1092 }
1093
1094 /*
1095 * Edit the isolinux.cfg file if it is there.
1096 */
1097 if (fIsoLinuxCfgExists)
1098 {
1099 GeneralTextScript Editor(mpParent);
1100 HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, "/isolinux/isolinux.cfg", &Editor);
1101 if (SUCCEEDED(hrc))
1102 hrc = editIsoLinuxCfg(&Editor, RTPathFilename(pszMenuConfigFilename));
1103 if (SUCCEEDED(hrc))
1104 {
1105 hrc = Editor.save(strIsoLinuxCfg, fOverwrite);
1106 if (SUCCEEDED(hrc))
1107 {
1108 try
1109 {
1110 rVecFiles.append(strIsoLinuxCfg);
1111 }
1112 catch (std::bad_alloc &)
1113 {
1114 RTFileDelete(strIsoLinuxCfg.c_str());
1115 hrc = E_OUTOFMEMORY;
1116 }
1117 }
1118 }
1119 if (FAILED(hrc))
1120 return hrc;
1121 }
1122
1123 /*
1124 * Edit the menu config file.
1125 * Some distros (Linux Mint) has only isolinux.cfg. No menu.cfg or txt.cfg.
1126 */
1127 if (RTStrICmp(pszMenuConfigFilename, "/isolinux/isolinux.cfg") != 0)
1128 {
1129 GeneralTextScript Editor(mpParent);
1130 HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, pszMenuConfigFilename, &Editor);
1131 if (SUCCEEDED(hrc))
1132 {
1133 if (fMenuConfigIsGrub)
1134 hrc = editDebianGrubCfg(&Editor);
1135 else
1136 hrc = editDebianMenuCfg(&Editor);
1137 if (SUCCEEDED(hrc))
1138 {
1139 hrc = Editor.save(strTxtCfg, fOverwrite);
1140 if (SUCCEEDED(hrc))
1141 {
1142 try
1143 {
1144 rVecFiles.append(strTxtCfg);
1145 }
1146 catch (std::bad_alloc &)
1147 {
1148 RTFileDelete(strTxtCfg.c_str());
1149 hrc = E_OUTOFMEMORY;
1150 }
1151 }
1152 }
1153 }
1154 if (FAILED(hrc))
1155 return hrc;
1156 }
1157
1158 /*
1159 * Call parent to add the preseed file from mAlg.
1160 */
1161 return UnattendedLinuxInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
1162}
1163
1164HRESULT UnattendedDebianInstaller::editIsoLinuxCfg(GeneralTextScript *pEditor, const char *pszMenuConfigFileName)
1165{
1166 try
1167 {
1168 /* Include menu config file. Since it can be txt.cfg, menu.cfg or something else we need to parametrize this. */
1169 if (pszMenuConfigFileName && pszMenuConfigFileName[0] != '\0')
1170 {
1171 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("include", RTCString::CaseInsensitive);
1172 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1173 {
1174 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("include", RTCString::CaseInsensitive))
1175 {
1176 Utf8Str strIncludeLine("include ");
1177 strIncludeLine.append(pszMenuConfigFileName);
1178 HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strIncludeLine);
1179 if (FAILED(hrc))
1180 return hrc;
1181 }
1182 }
1183 }
1184
1185 /* Comment out default directives since in Debian case default is handled in menu config file. */
1186 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("default", RTCString::CaseInsensitive);
1187 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1188 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("default", RTCString::CaseInsensitive)
1189 && !pEditor->getContentOfLine(vecLineNumbers[i]).contains("default vesa", RTCString::CaseInsensitive))
1190 {
1191 HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
1192 if (FAILED(hrc))
1193 return hrc;
1194 }
1195
1196 /* Comment out "ui gfxboot bootlogo" line as it somehow messes things up on Kubuntu 20.04 (possibly others as well). */
1197 vecLineNumbers = pEditor->findTemplate("ui gfxboot", RTCString::CaseInsensitive);
1198 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1199 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("ui gfxboot", RTCString::CaseInsensitive))
1200 {
1201 HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
1202 if (FAILED(hrc))
1203 return hrc;
1204 }
1205 }
1206 catch (std::bad_alloc &)
1207 {
1208 return E_OUTOFMEMORY;
1209 }
1210 return UnattendedLinuxInstaller::editIsoLinuxCfg(pEditor);
1211}
1212
1213HRESULT UnattendedDebianInstaller::editDebianMenuCfg(GeneralTextScript *pEditor)
1214{
1215 /*
1216 * Unlike Redhats, Debian variants define boot menu not in isolinux.cfg but some other
1217 * menu configuration files. They are mostly called txt.cfg and/or menu.cfg (and possibly some other names)
1218 * In this functions we attempt to set menu's default label (default menu item) to the one containing the word 'install',
1219 * failing to find such a label (on Kubuntu 20.04 for example) we pick the first label with name 'live'.
1220 */
1221 try
1222 {
1223 HRESULT hrc = S_OK;
1224 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("label", RTCString::CaseInsensitive);
1225 const char *pszNewLabelName = "VBoxUnatendedInstall";
1226 bool fLabelFound = modifyLabelLine(pEditor, vecLineNumbers, "install", pszNewLabelName);
1227 if (!fLabelFound)
1228 fLabelFound = modifyLabelLine(pEditor, vecLineNumbers, "live", pszNewLabelName);
1229
1230 if (!fLabelFound)
1231 hrc = E_FAIL;;
1232
1233 if (SUCCEEDED(hrc))
1234 {
1235 /* Modify the content of default lines so that they point to label we have chosen above. */
1236 Utf8Str strNewContent("default ");
1237 strNewContent.append(pszNewLabelName);
1238
1239 std::vector<size_t> vecDefaultLineNumbers = pEditor->findTemplate("default", RTCString::CaseInsensitive);
1240 if (!vecDefaultLineNumbers.empty())
1241 {
1242 for (size_t j = 0; j < vecDefaultLineNumbers.size(); ++j)
1243 {
1244 hrc = pEditor->setContentOfLine(vecDefaultLineNumbers[j], strNewContent);
1245 if (FAILED(hrc))
1246 break;
1247 }
1248 }
1249 /* Add a defaul label line. */
1250 else
1251 hrc = pEditor->appendLine(strNewContent);
1252 }
1253 if (FAILED(hrc))
1254 return hrc;
1255 }
1256 catch (std::bad_alloc &)
1257 {
1258 return E_OUTOFMEMORY;
1259 }
1260 return UnattendedLinuxInstaller::editIsoLinuxCommon(pEditor);
1261}
1262
1263bool UnattendedDebianInstaller::modifyLabelLine(GeneralTextScript *pEditor, const std::vector<size_t> &vecLineNumbers,
1264 const char *pszKeyWord, const char *pszNewLabelName)
1265{
1266 if (!pEditor)
1267 return false;
1268 Utf8Str strNewLabel("label ");
1269 strNewLabel.append(pszNewLabelName);
1270 HRESULT hrc = S_OK;
1271 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1272 {
1273 RTCString const &rContent = pEditor->getContentOfLine(vecLineNumbers[i]);
1274 /* Skip this line if it does not start with the word 'label'. */
1275 if (!RTStrIStartsWith(rContent.c_str(), "label"))
1276 continue;
1277 /* Use the first menu item starting with word label and includes pszKeyWord.*/
1278 if (RTStrIStr(rContent.c_str(), pszKeyWord) != NULL)
1279 {
1280 /* Set the content of the line. It looks like multiple word labels (like label Debian Installer)
1281 * does not work very well in some cases. */
1282 hrc = pEditor->setContentOfLine(vecLineNumbers[i], strNewLabel);
1283 if (SUCCEEDED(hrc))
1284 return true;
1285 }
1286 }
1287 return false;
1288}
1289
1290HRESULT UnattendedDebianInstaller::editDebianGrubCfg(GeneralTextScript *pEditor)
1291{
1292 /* Default menu entry of grub.cfg is set in /etc/deafult/grub file. */
1293 try
1294 {
1295 /* Set timeouts to 4 seconds. */
1296 std::vector<size_t> vecLineNumbers = pEditor->findTemplate("set timeout", RTCString::CaseInsensitive);
1297 if (vecLineNumbers.size() > 0)
1298 {
1299 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1300 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("set timeout", RTCString::CaseInsensitive))
1301 {
1302 HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), "set timeout=4");
1303 if (FAILED(hrc))
1304 return hrc;
1305 }
1306 }
1307 else
1308 {
1309 /* Append timeout if not set (happens with arm64 iso images at least). */
1310 HRESULT hrc = pEditor->appendLine("set timeout=4");
1311 if (FAILED(hrc))
1312 return hrc;
1313 }
1314
1315 /* Modify kernel lines assuming that they starts with 'linux' keyword and 2nd word is the kernel command.*
1316 * we remove whatever comes after command and add our own command line options. */
1317 vecLineNumbers = pEditor->findTemplate("linux", RTCString::CaseInsensitive);
1318 if (vecLineNumbers.size() > 0)
1319 {
1320 Utf8Str const &rStrAppend = mpParent->i_getExtraInstallKernelParameters().isNotEmpty()
1321 ? mpParent->i_getExtraInstallKernelParameters()
1322 : mStrDefaultExtraInstallKernelParameters;
1323
1324 for (size_t i = 0; i < vecLineNumbers.size(); ++i)
1325 {
1326 HRESULT hrc = S_OK;
1327 if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("linux", RTCString::CaseInsensitive))
1328 {
1329 Utf8Str strLine = pEditor->getContentOfLine(vecLineNumbers[i]);
1330 size_t cbPos = strLine.find("linux") + strlen("linux");
1331 bool fSecondWord = false;
1332 /* Find the end of 2nd word assuming that it is kernel command. */
1333 while (cbPos < strLine.length())
1334 {
1335 if (!fSecondWord)
1336 {
1337 if (strLine[cbPos] != '\t' && strLine[cbPos] != ' ')
1338 fSecondWord = true;
1339 }
1340 else
1341 {
1342 if (strLine[cbPos] == '\t' || strLine[cbPos] == ' ')
1343 break;
1344 }
1345 ++cbPos;
1346 }
1347 if (!fSecondWord)
1348 hrc = E_FAIL;
1349
1350 if (SUCCEEDED(hrc))
1351 {
1352 strLine.erase(cbPos, strLine.length() - cbPos);
1353
1354 /* Do the appending. */
1355 if (rStrAppend.isNotEmpty())
1356 {
1357 if (!rStrAppend.startsWith(" ") && !strLine.endsWith(" "))
1358 strLine.append(' ');
1359 strLine.append(rStrAppend);
1360 }
1361
1362 /* Update line. */
1363 hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strLine);
1364 }
1365 if (FAILED(hrc))
1366 return hrc;
1367 }
1368 }
1369 }
1370 }
1371 catch (std::bad_alloc &)
1372 {
1373 return E_OUTOFMEMORY;
1374 }
1375 return S_OK;
1376}
1377
1378//////////////////////////////////////////////////////////////////////////////////////////////////////
1379/*
1380*
1381*
1382* Implementation UnattendedRhel6Installer functions
1383*
1384*/
1385//////////////////////////////////////////////////////////////////////////////////////////////////////
1386HRESULT UnattendedRhelInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
1387 RTVFS hVfsOrgIso, bool fOverwrite)
1388{
1389 Utf8Str strIsoLinuxCfg;
1390 try
1391 {
1392#if 1
1393 /* Remaster ISO. */
1394 rVecArgs.append() = "--no-file-mode";
1395 rVecArgs.append() = "--no-dir-mode";
1396
1397 rVecArgs.append() = "--import-iso";
1398 rVecArgs.append(mpParent->i_getIsoPath());
1399
1400 rVecArgs.append() = "--file-mode=0444";
1401 rVecArgs.append() = "--dir-mode=0555";
1402
1403 /* We replace isolinux.cfg with our edited version (see further down). */
1404 rVecArgs.append() = "isolinux/isolinux.cfg=:must-remove:";
1405 strIsoLinuxCfg = mpParent->i_getAuxiliaryBasePath();
1406 strIsoLinuxCfg.append("isolinux-isolinux.cfg");
1407 rVecArgs.append().append("isolinux/isolinux.cfg=").append(strIsoLinuxCfg);
1408
1409#else
1410 /** @todo Maybe we should just remaster the ISO for redhat derivatives too?
1411 * One less CDROM to mount. */
1412 /* Name the ISO. */
1413 rVecArgs.append() = "--volume-id=VBox Unattended Boot";
1414
1415 /* Copy the isolinux directory from the original install ISO. */
1416 rVecArgs.append().append("--push-iso=").append(mpParent->i_getIsoPath());
1417 rVecArgs.append() = "/isolinux=/isolinux";
1418 rVecArgs.append() = "--pop";
1419
1420 /* We replace isolinux.cfg with our edited version (see further down). */
1421 rVecArgs.append() = "/isolinux/isolinux.cfg=:must-remove:";
1422
1423 strIsoLinuxCfg = mpParent->i_getAuxiliaryBasePath();
1424 strIsoLinuxCfg.append("isolinux-isolinux.cfg");
1425 rVecArgs.append().append("/isolinux/isolinux.cfg=").append(strIsoLinuxCfg);
1426
1427 /* Configure booting /isolinux/isolinux.bin. */
1428 rVecArgs.append() = "--eltorito-boot";
1429 rVecArgs.append() = "/isolinux/isolinux.bin";
1430 rVecArgs.append() = "--no-emulation-boot";
1431 rVecArgs.append() = "--boot-info-table";
1432 rVecArgs.append() = "--boot-load-seg=0x07c0";
1433 rVecArgs.append() = "--boot-load-size=4";
1434
1435 /* Make the boot catalog visible in the file system. */
1436 rVecArgs.append() = "--boot-catalog=/isolinux/vboxboot.cat";
1437#endif
1438 }
1439 catch (std::bad_alloc &)
1440 {
1441 return E_OUTOFMEMORY;
1442 }
1443
1444 /*
1445 * Edit isolinux.cfg and save it.
1446 */
1447 {
1448 GeneralTextScript Editor(mpParent);
1449 HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, "/isolinux/isolinux.cfg", &Editor);
1450 if (SUCCEEDED(hrc))
1451 hrc = editIsoLinuxCfg(&Editor);
1452 if (SUCCEEDED(hrc))
1453 {
1454 hrc = Editor.save(strIsoLinuxCfg, fOverwrite);
1455 if (SUCCEEDED(hrc))
1456 {
1457 try
1458 {
1459 rVecFiles.append(strIsoLinuxCfg);
1460 }
1461 catch (std::bad_alloc &)
1462 {
1463 RTFileDelete(strIsoLinuxCfg.c_str());
1464 hrc = E_OUTOFMEMORY;
1465 }
1466 }
1467 }
1468 if (FAILED(hrc))
1469 return hrc;
1470 }
1471
1472 /*
1473 * Call parent to add the ks.cfg file from mAlg.
1474 */
1475 return UnattendedLinuxInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
1476}
1477
1478
1479//////////////////////////////////////////////////////////////////////////////////////////////////////
1480/*
1481*
1482*
1483* Implementation UnattendedSuseInstaller functions
1484*
1485*/
1486//////////////////////////////////////////////////////////////////////////////////////////////////////
1487#if 0 /* doesn't work, so convert later */
1488/*
1489 *
1490 * UnattendedSuseInstaller protected methods
1491 *
1492*/
1493HRESULT UnattendedSuseInstaller::setUserData()
1494{
1495 HRESULT hrc = S_OK;
1496 //here base class function must be called first
1497 //because user home directory is set after user name
1498 hrc = UnattendedInstaller::setUserData();
1499
1500 hrc = mAlg->setField(USERHOMEDIR_ID, "");
1501 if (FAILED(hrc))
1502 return hrc;
1503
1504 return hrc;
1505}
1506
1507/*
1508 *
1509 * UnattendedSuseInstaller private methods
1510 *
1511*/
1512
1513HRESULT UnattendedSuseInstaller::iv_initialPhase()
1514{
1515 Assert(isAuxiliaryIsoNeeded());
1516 if (mParent->i_isGuestOs64Bit())
1517 mFilesAndDirsToExtractFromIso.append("boot/x86_64/loader/ ");
1518 else
1519 mFilesAndDirsToExtractFromIso.append("boot/i386/loader/ ");
1520 return extractOriginalIso(mFilesAndDirsToExtractFromIso);
1521}
1522
1523
1524HRESULT UnattendedSuseInstaller::setupScriptOnAuxiliaryCD(const Utf8Str &path)
1525{
1526 HRESULT hrc = S_OK;
1527
1528 GeneralTextScript isoSuseCfgScript(mpParent);
1529 hrc = isoSuseCfgScript.read(path);
1530 hrc = isoSuseCfgScript.parse();
1531 //fix linux core bootable parameters: add path to the preseed script
1532
1533 std::vector<size_t> listOfLines = isoSuseCfgScript.findTemplate("append");
1534 for(unsigned int i=0; i<listOfLines.size(); ++i)
1535 {
1536 isoSuseCfgScript.appendToLine(listOfLines.at(i),
1537 " auto=true priority=critical autoyast=default instmode=cd quiet splash noprompt noshell --");
1538 }
1539
1540 //find all lines with "label" inside
1541 listOfLines = isoSuseCfgScript.findTemplate("label");
1542 for(unsigned int i=0; i<listOfLines.size(); ++i)
1543 {
1544 Utf8Str content = isoSuseCfgScript.getContentOfLine(listOfLines.at(i));
1545
1546 //suppose general string looks like "label linux", two words separated by " ".
1547 RTCList<RTCString> partsOfcontent = content.split(" ");
1548
1549 if (partsOfcontent.at(1).contains("linux"))
1550 {
1551 std::vector<size_t> listOfDefault = isoSuseCfgScript.findTemplate("default");
1552 //handle the lines more intelligently
1553 for(unsigned int j=0; j<listOfDefault.size(); ++j)
1554 {
1555 Utf8Str newContent("default ");
1556 newContent.append(partsOfcontent.at(1));
1557 isoSuseCfgScript.setContentOfLine(listOfDefault.at(j), newContent);
1558 }
1559 }
1560 }
1561
1562 hrc = isoSuseCfgScript.save(path, true);
1563
1564 LogRelFunc(("UnattendedSuseInstaller::setupScriptsOnAuxiliaryCD(): The file %s has been changed\n", path.c_str()));
1565
1566 return hrc;
1567}
1568#endif
1569
1570
1571//////////////////////////////////////////////////////////////////////////////////////////////////////
1572/*
1573*
1574*
1575* Implementation UnattendedFreeBsdInstaller functions
1576*
1577*/
1578//////////////////////////////////////////////////////////////////////////////////////////////////////
1579HRESULT UnattendedFreeBsdInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
1580 RTVFS hVfsOrgIso, bool fOverwrite)
1581{
1582 try
1583 {
1584 RTCString strScriptName;
1585 strScriptName = mpParent->i_getAuxiliaryBasePath();
1586 strScriptName.append(mMainScript.getDefaultFilename());
1587
1588 /* Need to retain the original file permissions for executables. */
1589 rVecArgs.append() = "--no-file-mode";
1590 rVecArgs.append() = "--no-dir-mode";
1591
1592 rVecArgs.append() = "--import-iso";
1593 rVecArgs.append(mpParent->i_getIsoPath());
1594
1595 rVecArgs.append() = "--file-mode=0444";
1596 rVecArgs.append() = "--dir-mode=0555";
1597
1598 /* Remaster ISO, the installer config has to go into /etc. */
1599 rVecArgs.append().append("/etc/installerconfig=").append(strScriptName);
1600 }
1601 catch (std::bad_alloc &)
1602 {
1603 return E_OUTOFMEMORY;
1604 }
1605
1606 /*
1607 * Call parent to add the remaining files
1608 */
1609 return UnattendedInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
1610}
Note: See TracBrowser for help on using the repository browser.

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