VirtualBox

source: vbox/trunk/src/VBox/Installer/win/Stub/VBoxStub.cpp@ 34195

Last change on this file since 34195 was 34195, checked in by vboxsync, 14 years ago

VBoxStub: Misc bugfixes, added error string lookup.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 23.2 KB
Line 
1/* $Id: VBoxStub.cpp 34195 2010-11-19 13:14:17Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/*******************************************************************************
19* Header Files *
20*******************************************************************************/
21#include <windows.h>
22#include <lmerr.h>
23#include <msiquery.h>
24#include <objbase.h>
25#include <shlobj.h>
26#include <stdlib.h>
27#include <stdio.h>
28#include <string.h>
29#include <strsafe.h>
30
31#include <VBox/version.h>
32
33#include <iprt/assert.h>
34#include <iprt/dir.h>
35#include <iprt/file.h>
36#include <iprt/initterm.h>
37#include <iprt/mem.h>
38#include <iprt/path.h>
39#include <iprt/param.h>
40#include <iprt/string.h>
41#include <iprt/thread.h>
42
43#include "VBoxStub.h"
44#include "../StubBld/VBoxStubBld.h"
45#include "resource.h"
46
47#ifndef _UNICODE
48#define _UNICODE
49#endif
50
51
52/**
53 * Shows a message box with a printf() style formatted string.
54 *
55 * @returns Message box result (IDOK, IDCANCEL, ...).
56 *
57 * @param uType Type of the message box (see MSDN).
58 * @param pszFmt Printf-style format string to show in the message box body.
59 *
60 */
61static int ShowInfo(const char *pszFmt, ...)
62{
63 char *pszMsg;
64 va_list va;
65
66 va_start(va, pszFmt);
67 RTStrAPrintfV(&pszMsg, pszFmt, va);
68 va_end(va);
69
70 int rc;
71 if (pszMsg)
72 rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
73 else
74 rc = MessageBox(GetDesktopWindow(), pszFmt, VBOX_STUB_TITLE, MB_ICONINFORMATION);
75 RTStrFree(pszMsg);
76 return rc;
77}
78
79
80/**
81 * Shows an error message box with a printf() style formatted string.
82 *
83 * @returns Message box result (IDOK, IDCANCEL, ...).
84 *
85 * @param pszFmt Printf-style format string to show in the message box body.
86 *
87 */
88static int ShowError(const char *pszFmt, ...)
89{
90 char *pszMsg;
91 va_list va;
92 int rc;
93
94 va_start(va, pszFmt);
95 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
96 {
97 rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
98 RTStrFree(pszMsg);
99 }
100 else /* Should never happen! */
101 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
102 va_end(va);
103 return rc;
104}
105
106
107/**
108 * Reads data from a built-in resource.
109 *
110 * @returns iprt status code.
111 *
112 * @param hInst Instance to read the data from.
113 * @param pszDataName Name of resource to read.
114 * @param ppvResource Pointer to buffer which holds the read resource data.
115 * @param pdwSize Pointer which holds the read data size.
116 *
117 */
118static int ReadData(HINSTANCE hInst,
119 const char *pszDataName,
120 PVOID *ppvResource,
121 DWORD *pdwSize)
122{
123 do
124 {
125 AssertMsgBreak(pszDataName, ("Resource name is empty!\n"));
126
127 /* Find our resource. */
128 HRSRC hRsrc = FindResourceEx(hInst, RT_RCDATA, pszDataName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
129 AssertMsgBreak(hRsrc, ("Could not find resource!\n"));
130
131 /* Get resource size. */
132 *pdwSize = SizeofResource(hInst, hRsrc);
133 AssertMsgBreak(*pdwSize > 0, ("Size of resource is invalid!\n"));
134
135 /* Get pointer to resource. */
136 HGLOBAL hData = LoadResource(hInst, hRsrc);
137 AssertMsgBreak(hData, ("Could not load resource!\n"));
138
139 /* Lock resource. */
140 *ppvResource = LockResource(hData);
141 AssertMsgBreak(*ppvResource, ("Could not lock resource!\n"));
142 return VINF_SUCCESS;
143
144 } while (0);
145
146 return VERR_IO_GEN_FAILURE;
147}
148
149
150/**
151 * Constructs a full temporary file path from the given parameters.
152 *
153 * @returns iprt status code.
154 *
155 * @param pszTempPath The pure path to use for construction.
156 * @param pszTargetFileName The pure file name to use for construction.
157 * @param ppszTempFile Pointer to the constructed string. Must be freed
158 * using RTStrFree().
159 */
160static int GetTempFileAlloc(const char *pszTempPath,
161 const char *pszTargetFileName,
162 char **ppszTempFile)
163{
164 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
165 return VINF_SUCCESS;
166 return VERR_NO_STR_MEMORY;
167}
168
169
170/**
171 * Extracts a built-in resource to disk.
172 *
173 * @returns iprt status code.
174 *
175 * @param pszResourceName The resource name to extract.
176 * @param pszTempFile The full file path + name to extract the resource to.
177 *
178 */
179static int ExtractFile(const char *pszResourceName,
180 const char *pszTempFile)
181{
182 int rc;
183 RTFILE fh;
184 BOOL bCreatedFile = FALSE;
185
186 do
187 {
188 AssertMsgBreak(pszResourceName, ("Resource pointer invalid!\n"));
189 AssertMsgBreak(pszTempFile, ("Temp file pointer invalid!"));
190
191 /* Read the data of the built-in resource. */
192 PVOID pvData = NULL;
193 DWORD dwDataSize = 0;
194 rc = ReadData(NULL, pszResourceName, &pvData, &dwDataSize);
195 AssertMsgRCBreak(rc, ("Could not read resource data!\n"));
196
197 /* Create new (and replace an old) file. */
198 rc = RTFileOpen(&fh, pszTempFile,
199 RTFILE_O_CREATE_REPLACE
200 | RTFILE_O_WRITE
201 | RTFILE_O_DENY_NOT_DELETE
202 | RTFILE_O_DENY_WRITE);
203 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
204 bCreatedFile = TRUE;
205
206 /* Write contents to new file. */
207 size_t cbWritten = 0;
208 rc = RTFileWrite(fh, pvData, dwDataSize, &cbWritten);
209 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
210 AssertMsgBreak(dwDataSize == cbWritten, ("File was not extracted completely! Disk full?\n"));
211
212 } while (0);
213
214 if (RTFileIsValid(fh))
215 RTFileClose(fh);
216
217 if (RT_FAILURE(rc))
218 {
219 if (bCreatedFile)
220 RTFileDelete(pszTempFile);
221 }
222 return rc;
223}
224
225
226/**
227 * Extracts a built-in resource to disk.
228 *
229 * @returns iprt status code.
230 *
231 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
232 * @param pszTempFile The full file path + name to extract the resource to.
233 *
234 */
235static int Extract(const PVBOXSTUBPKG pPackage,
236 const char *pszTempFile)
237{
238 return ExtractFile(pPackage->szResourceName,
239 pszTempFile);
240}
241
242
243/**
244 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
245 *
246 * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
247 *
248 */
249static BOOL IsWow64(void)
250{
251 BOOL bIsWow64 = TRUE;
252 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
253 if (NULL != fnIsWow64Process)
254 {
255 if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
256 {
257 /* Error in retrieving process type - assume that we're running on 32bit. */
258 return FALSE;
259 }
260 }
261 return bIsWow64;
262}
263
264
265/**
266 * Decides whether we need a specified package to handle or not.
267 *
268 * @returns TRUE if we need to handle the specified package, FALSE if not.
269 *
270 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
271 *
272 */
273static BOOL PackageIsNeeded(PVBOXSTUBPKG pPackage)
274{
275 BOOL bIsWow64 = IsWow64();
276 if ((bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_AMD64)) /* 64bit Windows. */
277 {
278 return TRUE;
279 }
280 else if ((!bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_X86)) /* 32bit. */
281 {
282 return TRUE;
283 }
284 else if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
285 {
286 return TRUE;
287 }
288 return FALSE;
289}
290
291
292/**
293 * Recursively copies a directory to another location.
294 *
295 * @returns iprt status code.
296 *
297 * @param pszDestDir Location to copy the source directory to.
298 * @param pszSourceDir The source directory to copy.
299 *
300 */
301int CopyDir(const char *pszDestDir, const char *pszSourceDir)
302{
303 char szDest[_MAX_PATH + 1];
304 char szSource[_MAX_PATH + 1];
305
306 AssertStmt(pszDestDir, "Destination directory invalid!");
307 AssertStmt(pszSourceDir, "Source directory invalid!");
308
309 SHFILEOPSTRUCT s = {0};
310 if ( RTStrPrintf(szDest, _MAX_PATH, "%s%c", pszDestDir, '\0') > 0
311 && RTStrPrintf(szSource, _MAX_PATH, "%s%c", pszSourceDir, '\0') > 0)
312 {
313 s.hwnd = NULL;
314 s.wFunc = FO_COPY;
315 s.pTo = szDest;
316 s.pFrom = szSource;
317 s.fFlags = FOF_SILENT |
318 FOF_NOCONFIRMATION |
319 FOF_NOCONFIRMMKDIR |
320 FOF_NOERRORUI;
321 }
322 return RTErrConvertFromWin32(SHFileOperation(&s));
323}
324
325
326int WINAPI WinMain(HINSTANCE hInstance,
327 HINSTANCE hPrevInstance,
328 char *lpCmdLine,
329 int nCmdShow)
330{
331 char **pArgV = __argv;
332 int iArgC = __argc;
333
334 /* Check if we're already running and jump out if so. */
335 /* Do not use a global namespace ("Global\\") for mutex name here, will blow up NT4 compatibility! */
336 HANDLE hMutexAppRunning = CreateMutex (NULL, FALSE, "VBoxStubInstaller");
337 if ( (hMutexAppRunning != NULL)
338 && (GetLastError() == ERROR_ALREADY_EXISTS))
339 {
340 /* Close the mutex for this application instance. */
341 CloseHandle(hMutexAppRunning);
342 hMutexAppRunning = NULL;
343 return 1;
344 }
345
346 /* Init IPRT. */
347 int vrc = RTR3Init();
348 if (RT_FAILURE(vrc))
349 return vrc;
350
351 BOOL fExtractOnly = FALSE;
352 BOOL fSilent = FALSE;
353 BOOL fEnableLogging = FALSE;
354 BOOL bExit = FALSE;
355
356 /* Temp variables for arguments. */
357 char szExtractPath[RTPATH_MAX] = {0};
358 char szMSIArgs[RTPATH_MAX] = {0};
359
360 /* Process arguments. */
361 for (int i = 0; i < iArgC; i++)
362 {
363 if ( (0 == RTStrICmp(pArgV[i], "-x"))
364 || (0 == RTStrICmp(pArgV[i], "-extract"))
365 || (0 == RTStrICmp(pArgV[i], "/extract")))
366 {
367 fExtractOnly = TRUE;
368 }
369
370 else if ( (0 == RTStrICmp(pArgV[i], "-s"))
371 || (0 == RTStrICmp(pArgV[i], "-silent"))
372 || (0 == RTStrICmp(pArgV[i], "/silent")))
373 {
374 fSilent = TRUE;
375 }
376
377 else if ( (0 == RTStrICmp(pArgV[i], "-l"))
378 || (0 == RTStrICmp(pArgV[i], "-logging"))
379 || (0 == RTStrICmp(pArgV[i], "/logging")))
380 {
381 fEnableLogging = TRUE;
382 }
383
384 else if (( (0 == RTStrICmp(pArgV[i], "-p"))
385 || (0 == RTStrICmp(pArgV[i], "-path"))
386 || (0 == RTStrICmp(pArgV[i], "/path")))
387 && (iArgC > i))
388 {
389 vrc = ::StringCbCat(szExtractPath, sizeof(szExtractPath), pArgV[i+1]);
390 i++; /* Avoid the specify path from being parsed */
391 }
392
393 else if (( (0 == RTStrICmp(pArgV[i], "-msiparams"))
394 || (0 == RTStrICmp(pArgV[i], "/msiparams")))
395 && (iArgC > i))
396 {
397 for (int a=i+1; a<iArgC; a++)
398 {
399 if (a > i+1) /* Insert a space. */
400 vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), " ");
401
402 vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), pArgV[a]);
403 }
404 }
405
406 else if ( (0 == RTStrICmp(pArgV[i], "-v"))
407 || (0 == RTStrICmp(pArgV[i], "-version"))
408 || (0 == RTStrICmp(pArgV[i], "/version")))
409 {
410 ShowInfo("Version: %d.%d.%d.%d",
411 VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
412 bExit = TRUE;
413 }
414
415 else if ( (0 == RTStrICmp(pArgV[i], "-help"))
416 || (0 == RTStrICmp(pArgV[i], "/help"))
417 || (0 == RTStrICmp(pArgV[i], "/?")))
418 {
419 ShowInfo("-- %s v%d.%d.%d.%d --\n"
420 "Command Line Parameters:\n\n"
421 "-extract | -x - Extract file contents to temporary directory\n"
422 "-silent | -s - Enables silent mode installation\n"
423 "-path | -p - Sets the path of the extraction directory\n"
424 "-help | /? - Print this help and exit\n"
425 "-msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
426 "-logging | -l - Enables installer logging\n"
427 "-version | -v - Print version number and exit\n\n"
428 "Examples:\n"
429 "%s -msiparams INSTALLDIR=C:\\VBox\n"
430 "%s -extract -path C:\\VBox\n",
431 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
432 pArgV[0], pArgV[0]);
433 bExit = TRUE;
434 }
435 else
436 {
437 if (i > 0)
438 {
439 ShowInfo("Unknown option \"%s\"!\n"
440 "Please refer to the command line help by specifying \"/?\"\n"
441 "to get more information.", pArgV[i]);
442 bExit = TRUE;
443 }
444 }
445 }
446
447 if (bExit)
448 return 0;
449
450 HRESULT hr = S_OK;
451
452 do /* break loop */
453 {
454 /* Get/create our temp path (only if not already set). */
455 if (szExtractPath[0] == '\0')
456 {
457 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
458 AssertMsgRCBreak(vrc, ("Could not retrieve temp directory!\n"));
459
460 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox");
461 AssertMsgRCBreak(vrc, ("Could not construct temp directory!\n"));
462 }
463 if (!RTDirExists(szExtractPath))
464 {
465 vrc = RTDirCreate(szExtractPath, 0700);
466 AssertMsgRCBreak(vrc, ("Could not create temp directory!\n"));
467 }
468
469 /* Get our executable path */
470 char szPathExe[_MAX_PATH];
471 vrc = RTPathExecDir(szPathExe, sizeof(szPathExe));
472 /** @todo error checking */
473
474 /* Read our manifest. */
475 PVBOXSTUBPKGHEADER pHeader = NULL;
476 DWORD cbHeader = 0;
477 vrc = ReadData(NULL, "MANIFEST", (LPVOID*)&pHeader, &cbHeader);
478 AssertMsgRCBreak(vrc, ("Manifest not found!\n"));
479
480 /* Extract files. */
481 for (BYTE k = 0; k < pHeader->byCntPkgs; k++)
482 {
483 PVBOXSTUBPKG pPackage = NULL;
484 DWORD cbPackage = 0;
485 char szHeaderName[_MAX_PATH] = {0};
486
487 hr = ::StringCchPrintf(szHeaderName, _MAX_PATH, "HDR_%02d", k);
488 vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage);
489 AssertMsgRCBreak(vrc, ("Header not found!\n")); /** @todo include header name, how? */
490
491 if (PackageIsNeeded(pPackage) || fExtractOnly)
492 {
493 char *pszTempFile = NULL;
494 vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile);
495 AssertMsgRCBreak(vrc, ("Could not create name for temporary extracted file!\n"));
496 vrc = Extract(pPackage, pszTempFile);
497 AssertMsgRCBreak(vrc, ("Could not extract file!\n"));
498 RTStrFree(pszTempFile);
499 }
500 }
501
502 if (FALSE == fExtractOnly && !RT_FAILURE(vrc))
503 {
504 /*
505 * Copy ".custom" directory into temp directory so that the extracted .MSI
506 * file(s) can use it.
507 */
508 char *pszPathCustomDir = RTPathJoinA(szPathExe, ".custom");
509 if (pszPathCustomDir && RTDirExists(pszPathCustomDir))
510 {
511 vrc = CopyDir(szExtractPath, pszPathCustomDir);
512 if (RT_FAILURE(vrc)) /* Don't fail if it's missing! */
513 vrc = VINF_SUCCESS;
514
515 RTStrFree(pszPathCustomDir);
516 }
517
518 /* Do actions on files. */
519 for (BYTE k = 0; k < pHeader->byCntPkgs; k++)
520 {
521 PVBOXSTUBPKG pPackage = NULL;
522 DWORD cbPackage = 0;
523 char szHeaderName[_MAX_PATH] = {0};
524
525 hr = StringCchPrintf(szHeaderName, _MAX_PATH, "HDR_%02d", k);
526 vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage);
527 AssertMsgRCBreak(vrc, ("Package not found!\n"));
528
529 if (PackageIsNeeded(pPackage))
530 {
531 char *pszTempFile = NULL;
532
533 vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile);
534 AssertMsgRCBreak(vrc, ("Could not create name for temporary action file!\n"));
535
536 /* Handle MSI files. */
537 if (RTStrICmp(RTPathExt(pszTempFile), ".msi") == 0)
538 {
539 /* Set UI level. */
540 INSTALLUILEVEL UILevel = MsiSetInternalUI( fSilent
541 ? INSTALLUILEVEL_NONE
542 : INSTALLUILEVEL_FULL,
543 NULL);
544 AssertMsgBreak(UILevel != INSTALLUILEVEL_NOCHANGE, ("Could not set installer UI level!\n"));
545
546 /* Enable logging? */
547 if (fEnableLogging)
548 {
549 char *pszLog = RTPathJoinA(szExtractPath, "VBoxInstallLog.txt");
550 AssertMsgBreak(pszLog, ("Could not construct path for log file!\n"));
551 UINT uLogLevel = MsiEnableLog(INSTALLLOGMODE_VERBOSE,
552 pszLog, INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
553 RTStrFree(pszLog);
554 AssertMsgBreak(uLogLevel == ERROR_SUCCESS, ("Could not set installer logging level!\n"));
555 }
556
557 UINT uStatus = ::MsiInstallProductA(pszTempFile, szMSIArgs);
558 if ( (uStatus != ERROR_SUCCESS)
559 && (uStatus != ERROR_SUCCESS_REBOOT_REQUIRED)
560 && (uStatus != ERROR_INSTALL_USEREXIT))
561 {
562 if (!fSilent)
563 {
564 switch (uStatus)
565 {
566 case ERROR_INSTALL_PACKAGE_VERSION:
567
568 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
569 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
570 break;
571
572 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
573
574 ShowError("This installation package is not supported on this platform.");
575 break;
576
577 default:
578 {
579 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
580 | FORMAT_MESSAGE_IGNORE_INSERTS
581 | FORMAT_MESSAGE_FROM_SYSTEM;
582 HMODULE hModule = NULL;
583 if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
584 {
585 hModule = LoadLibraryEx(TEXT("netmsg.dll"),
586 NULL,
587 LOAD_LIBRARY_AS_DATAFILE);
588 if (hModule != NULL)
589 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
590 }
591
592 DWORD dwBufferLength;
593 LPSTR szMessageBuffer;
594 if (dwBufferLength = FormatMessageA(dwFormatFlags,
595 hModule, /* If NULL, load system stuff. */
596 uStatus,
597 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
598 (LPSTR)&szMessageBuffer,
599 0,
600 NULL))
601 {
602 ShowError("Installation failed! Error: %s", szMessageBuffer);
603 LocalFree(szMessageBuffer);
604 }
605 else /* If text lookup failed, show at least the error number. */
606 ShowError("Installation failed! Error: %u", uStatus);
607 if (hModule)
608 FreeLibrary(hModule);
609 break;
610 }
611 }
612 }
613
614 vrc = VERR_NO_CHANGE; /* No change done to the system. */
615 }
616 }
617 RTStrFree(pszTempFile);
618 } /* Package needed? */
619 } /* For all packages */
620 }
621
622 /* Clean up (only on success - prevent deleting the log). */
623 if ( !fExtractOnly
624 && RT_SUCCESS(vrc))
625 {
626 for (int i=0; i<5; i++)
627 {
628 vrc = RTDirRemoveRecursive(szExtractPath, 0 /*fFlags*/);
629 if (RT_SUCCESS(vrc))
630 break;
631 RTThreadSleep(3000 /* Wait 3 seconds.*/);
632 }
633 }
634
635 } while (0);
636
637 if (RT_SUCCESS(vrc))
638 {
639 if ( fExtractOnly
640 && !fSilent)
641 {
642 ShowInfo("Files were extracted to: %s", szExtractPath);
643 }
644
645 /** @todo Add more post installation stuff here if required. */
646 }
647
648 /* Release instance mutex. */
649 if (hMutexAppRunning != NULL)
650 {
651 CloseHandle(hMutexAppRunning);
652 hMutexAppRunning = NULL;
653 }
654
655 /* Set final exit (return) code (error level). */
656 if (RT_FAILURE(vrc))
657 {
658 switch(vrc)
659 {
660 case VERR_NO_CHANGE:
661 default:
662 vrc = 1;
663 }
664 }
665 else /* Always set to (VINF_SUCCESS), even if we got something else (like a VWRN etc). */
666 vrc = VINF_SUCCESS;
667 return vrc;
668}
669
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