VirtualBox

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

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

Main/Unattended: added user payload to unattended. bugref:10446

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