VirtualBox

source: vbox/trunk/src/VBox/Main/VBoxExtPackHelperApp.cpp@ 35171

最後變更 在這個檔案從35171是 35100,由 vboxsync 提交於 14 年 前

IExtPackFile::Install: Added a 'replace' parameter for automatically trying to uninstall an existing extension pack during upgrade.

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 58.7 KB
 
1/* $Id: VBoxExtPackHelperApp.cpp 35100 2010-12-14 16:21:38Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root.
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.alldomusa.eu.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "include/ExtPackUtil.h"
23
24#include <iprt/buildconfig.h>
25#include <iprt/dir.h>
26#include <iprt/file.h>
27#include <iprt/fs.h>
28#include <iprt/getopt.h>
29#include <iprt/initterm.h>
30#include <iprt/manifest.h>
31#include <iprt/message.h>
32#include <iprt/param.h>
33#include <iprt/path.h>
34#include <iprt/process.h>
35#include <iprt/string.h>
36#include <iprt/stream.h>
37#include <iprt/vfs.h>
38#include <iprt/zip.h>
39#include <iprt/cpp/ministring.h>
40
41#include <VBox/log.h>
42#include <VBox/err.h>
43#include <VBox/sup.h>
44#include <VBox/version.h>
45
46#if defined(RT_OS_DARWIN)
47# include <sys/types.h>
48# include <unistd.h> /* geteuid */
49#endif
50
51#ifdef RT_OS_WINDOWS
52# define _WIN32_WINNT 0x0501
53# include <Objbase.h> /* CoInitializeEx */
54# include <Windows.h> /* ShellExecuteEx, ++ */
55# ifdef DEBUG
56# include <Sddl.h>
57# endif
58#endif
59
60#ifdef RT_OS_DARWIN
61# include <Security/Authorization.h>
62# include <Security/AuthorizationTags.h>
63# include <CoreFoundation/CoreFoundation.h>
64#endif
65
66#if defined(RT_OS_DARWIN) || defined(RT_OS_WINDOWS)
67# include <stdio.h>
68# include <errno.h>
69#endif
70
71
72/*******************************************************************************
73* Defined Constants And Macros *
74*******************************************************************************/
75/** Enable elevation on Windows and Darwin. */
76#if defined(RT_OS_WINDOWS) || defined(RT_OS_DARWIN) || defined(DOXYGEN_RUNNING)
77# define WITH_ELEVATION
78#endif
79
80
81
82#ifdef IN_RT_R3
83/* Override RTAssertShouldPanic to prevent gdb process creation. */
84RTDECL(bool) RTAssertShouldPanic(void)
85{
86 return true;
87}
88#endif
89
90
91
92/**
93 * Handle the special standard options when these are specified after the
94 * command.
95 *
96 * @param ch The option character.
97 */
98static RTEXITCODE DoStandardOption(int ch)
99{
100 switch (ch)
101 {
102 case 'h':
103 {
104 RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n"
105 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
106 "All rights reserved.\n"
107 "\n"
108 "This NOT intended for general use, please use VBoxManage instead\n"
109 "or call the IExtPackManager API directly.\n"
110 "\n"
111 "Usage: %s <command> [options]\n"
112 "Commands:\n"
113 " install --base-dir <dir> --cert-dir <dir> --name <name> \\\n"
114 " --tarball <tarball> --tarball-fd <fd>\n"
115 " uninstall --base-dir <dir> --name <name>\n"
116 " cleanup --base-dir <dir>\n"
117 , RTProcShortName());
118 return RTEXITCODE_SUCCESS;
119 }
120
121 case 'V':
122 RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
123 return RTEXITCODE_SUCCESS;
124
125 default:
126 AssertFailedReturn(RTEXITCODE_FAILURE);
127 }
128}
129
130
131/**
132 * Checks if the cerficiate directory is valid.
133 *
134 * @returns true if it is valid, false if it isn't.
135 * @param pszCertDir The certificate directory to validate.
136 */
137static bool IsValidCertificateDir(const char *pszCertDir)
138{
139 /*
140 * Just be darn strict for now.
141 */
142 char szCorrect[RTPATH_MAX];
143 int rc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect));
144 if (RT_FAILURE(rc))
145 return false;
146 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR);
147 if (RT_FAILURE(rc))
148 return false;
149
150 return RTPathCompare(szCorrect, pszCertDir) == 0;
151}
152
153
154/**
155 * Checks if the base directory is valid.
156 *
157 * @returns true if it is valid, false if it isn't.
158 * @param pszBaesDir The base directory to validate.
159 */
160static bool IsValidBaseDir(const char *pszBaseDir)
161{
162 /*
163 * Just be darn strict for now.
164 */
165 char szCorrect[RTPATH_MAX];
166 int rc = RTPathAppPrivateArch(szCorrect, sizeof(szCorrect));
167 if (RT_FAILURE(rc))
168 return false;
169 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR);
170 if (RT_FAILURE(rc))
171 return false;
172
173 return RTPathCompare(szCorrect, pszBaseDir) == 0;
174}
175
176
177/**
178 * Cleans up a temporary extension pack directory.
179 *
180 * This is used by 'uninstall', 'cleanup' and in the failure path of 'install'.
181 *
182 * @returns The program exit code.
183 * @param pszDir The directory to clean up. The caller is
184 * responsible for making sure this is valid.
185 * @param fTemporary Whether this is a temporary install directory or
186 * not.
187 */
188static RTEXITCODE RemoveExtPackDir(const char *pszDir, bool fTemporary)
189{
190 /** @todo May have to undo 555 modes here later. */
191 int rc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR);
192 if (RT_FAILURE(rc))
193 return RTMsgErrorExit(RTEXITCODE_FAILURE,
194 "Failed to delete the %sextension pack directory: %Rrc ('%s')",
195 fTemporary ? "temporary " : "", rc, pszDir);
196 return RTEXITCODE_SUCCESS;
197}
198
199
200/**
201 * Common uninstall worker used by both uninstall and install --replace.
202 *
203 * @returns success or failure, message displayed on failure.
204 * @param pszExtPackDir The extension pack directory name.
205 */
206static RTEXITCODE CommonUninstallWorker(const char *pszExtPackDir)
207{
208 /* Rename the extension pack directory before deleting it to prevent new
209 VM processes from picking it up. */
210 char szExtPackUnInstDir[RTPATH_MAX];
211 int rc = RTStrCopy(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszExtPackDir);
212 if (RT_SUCCESS(rc))
213 rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst");
214 if (RT_FAILURE(rc))
215 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc);
216
217 rc = RTDirRename(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE);
218 if (RT_FAILURE(rc))
219 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the extension pack directory: %Rrc", rc);
220
221 /* Recursively delete the directory content. */
222 return RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/);
223}
224
225
226/**
227 * Wrapper around VBoxExtPackOpenTarFss.
228 *
229 * @returns success or failure, message displayed on failure.
230 * @param hTarballFile The handle to the tarball file.
231 * @param phTarFss Where to return the filesystem stream handle.
232 */
233static RTEXITCODE OpenTarFss(RTFILE hTarballFile, PRTVFSFSSTREAM phTarFss)
234{
235 char szError[8192];
236 int rc = VBoxExtPackOpenTarFss(hTarballFile, szError, sizeof(szError), phTarFss);
237 if (RT_FAILURE(rc))
238 {
239 Assert(szError[0]);
240 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
241 }
242 Assert(!szError[0]);
243 return RTEXITCODE_SUCCESS;
244}
245
246
247/**
248 * Sets the permissions of the temporary extension pack directory just before
249 * renaming it.
250 *
251 * By default the temporary directory is only accessible by root, this function
252 * will make it world readable and browseable.
253 *
254 * @returns The program exit code.
255 * @param pszDir The temporary extension pack directory.
256 */
257static RTEXITCODE SetExtPackPermissions(const char *pszDir)
258{
259 RTMsgInfo("Setting permissions...");
260#if !defined(RT_OS_WINDOWS)
261 int rc = RTPathSetMode(pszDir, 0755);
262 if (RT_FAILURE(rc))
263 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir);
264#else
265 /** @todo */
266#endif
267
268 return RTEXITCODE_SUCCESS;
269}
270
271
272/**
273 * Wrapper around VBoxExtPackValidateMember.
274 *
275 * @returns Program exit code, failure with message.
276 * @param pszName The name of the directory.
277 * @param enmType The object type.
278 * @param hVfsObj The VFS object.
279 */
280static RTEXITCODE ValidateMemberOfExtPack(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj)
281{
282 char szError[8192];
283 int rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, szError, sizeof(szError));
284 if (RT_FAILURE(rc))
285 {
286 Assert(szError[0]);
287 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
288 }
289 Assert(!szError[0]);
290 return RTEXITCODE_SUCCESS;
291}
292
293
294/**
295 * Validates the extension pack tarball prior to unpacking.
296 *
297 * Operations performed:
298 * - Hardening checks.
299 *
300 * @returns The program exit code.
301 * @param pszDir The directory where the extension pack has been
302 * unpacked.
303 * @param pszExtPackName The expected extension pack name.
304 * @param pszTarball The name of the tarball in case we have to
305 * complain about something.
306 */
307static RTEXITCODE ValidateUnpackedExtPack(const char *pszDir, const char *pszTarball, const char *pszExtPackName)
308{
309 RTMsgInfo("Validating unpacked extension pack...");
310
311 char szErr[4096+1024];
312 int rc = SUPR3HardenedVerifyDir(pszDir, true /*fRecursive*/, true /*fCheckFiles*/, szErr, sizeof(szErr));
313 if (RT_FAILURE(rc))
314 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Hardening check failed with %Rrc: %s", rc, szErr);
315 return RTEXITCODE_SUCCESS;
316}
317
318
319/**
320 * Unpacks a directory from an extension pack tarball.
321 *
322 * @returns Program exit code, failure with message.
323 * @param pszDstDirName The name of the unpacked directory.
324 * @param hVfsObj The source object for the directory.
325 */
326static RTEXITCODE UnpackExtPackDir(const char *pszDstDirName, RTVFSOBJ hVfsObj)
327{
328 int rc = RTDirCreate(pszDstDirName, 0755);
329 if (RT_FAILURE(rc))
330 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create directory '%s': %Rrc", pszDstDirName, rc);
331 /** @todo Ownership tricks on windows? */
332 return RTEXITCODE_SUCCESS;
333}
334
335
336/**
337 * Unpacks a file from an extension pack tarball.
338 *
339 * @returns Program exit code, failure with message.
340 * @param pszName The name in the tarball.
341 * @param pszDstFilename The name of the unpacked file.
342 * @param hVfsIosSrc The source stream for the file.
343 * @param hUnpackManifest The manifest to add the file digest to.
344 */
345static RTEXITCODE UnpackExtPackFile(const char *pszName, const char *pszDstFilename,
346 RTVFSIOSTREAM hVfsIosSrc, RTMANIFEST hUnpackManifest)
347{
348 /*
349 * Query the object info, we'll need it for buffer sizing as well as
350 * setting the file mode.
351 */
352 RTFSOBJINFO ObjInfo;
353 int rc = RTVfsIoStrmQueryInfo(hVfsIosSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING);
354 if (RT_FAILURE(rc))
355 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmQueryInfo failed with %Rrc on '%s'", rc, pszDstFilename);
356
357 /*
358 * Create the file.
359 */
360 uint32_t fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE | (0600 << RTFILE_O_CREATE_MODE_SHIFT);
361 RTFILE hFile;
362 rc = RTFileOpen(&hFile, pszDstFilename, fFlags);
363 if (RT_FAILURE(rc))
364 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create '%s': %Rrc", pszDstFilename, rc);
365
366 /*
367 * Create a I/O stream for the destination file, stack a manifest entry
368 * creator on top of it.
369 */
370 RTVFSIOSTREAM hVfsIosDst2;
371 rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIosDst2);
372 if (RT_SUCCESS(rc))
373 {
374 RTVFSIOSTREAM hVfsIosDst;
375 rc = RTManifestEntryAddPassthruIoStream(hUnpackManifest, hVfsIosDst2, pszName,
376 RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256,
377 false /*fReadOrWrite*/, &hVfsIosDst);
378 RTVfsIoStrmRelease(hVfsIosDst2);
379 if (RT_SUCCESS(rc))
380 {
381 /*
382 * Pump the data thru.
383 */
384 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(ObjInfo.cbObject, _1G));
385 if (RT_SUCCESS(rc))
386 {
387 rc = RTManifestPtIosAddEntryNow(hVfsIosDst);
388 if (RT_SUCCESS(rc))
389 {
390 RTVfsIoStrmRelease(hVfsIosDst);
391 hVfsIosDst = NIL_RTVFSIOSTREAM;
392
393 /*
394 * Set the mode mask.
395 */
396 ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP);
397 rc = RTFileSetMode(hFile, ObjInfo.Attr.fMode);
398 /** @todo Windows needs to do more here, I think. */
399 if (RT_SUCCESS(rc))
400 {
401 RTFileClose(hFile);
402 return RTEXITCODE_SUCCESS;
403 }
404
405 RTMsgError("Failed to set the mode of '%s' to %RTfmode: %Rrc", pszDstFilename, ObjInfo.Attr.fMode, rc);
406 }
407 else
408 RTMsgError("RTManifestPtIosAddEntryNow failed for '%s': %Rrc", pszDstFilename, rc);
409 }
410 else
411 RTMsgError("RTVfsUtilPumpIoStreams failed for '%s': %Rrc", pszDstFilename, rc);
412 RTVfsIoStrmRelease(hVfsIosDst);
413 }
414 else
415 RTMsgError("RTManifestEntryAddPassthruIoStream failed: %Rrc", rc);
416 }
417 else
418 RTMsgError("RTVfsIoStrmFromRTFile failed: %Rrc", rc);
419 RTFileClose(hFile);
420 return RTEXITCODE_FAILURE;
421}
422
423
424/**
425 * Unpacks the extension pack into the specified directory.
426 *
427 * This will apply ownership and permission changes to all the content, the
428 * exception is @a pszDirDst which will be handled by SetExtPackPermissions.
429 *
430 * @returns The program exit code.
431 * @param hTarballFile The tarball to unpack.
432 * @param pszDirDst Where to unpack it.
433 * @param hValidManifest The manifest we've validated.
434 * @param pszTarball The name of the tarball in case we have to
435 * complain about something.
436 */
437static RTEXITCODE UnpackExtPack(RTFILE hTarballFile, const char *pszDirDst, RTMANIFEST hValidManifest,
438 const char *pszTarball)
439{
440 RTMsgInfo("Unpacking extension pack into '%s'...", pszDirDst);
441
442 /*
443 * Set up the destination path.
444 */
445 char szDstPath[RTPATH_MAX];
446 int rc = RTPathAbs(pszDirDst, szDstPath, sizeof(szDstPath) - VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH - 2);
447 if (RT_FAILURE(rc))
448 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs('%s',,) failed: %Rrc", pszDirDst, rc);
449 size_t offDstPath = RTPathStripTrailingSlash(szDstPath);
450 szDstPath[offDstPath++] = '/';
451 szDstPath[offDstPath] = '\0';
452
453 /*
454 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
455 */
456 RTVFSFSSTREAM hTarFss;
457 RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss);
458 if (rcExit != RTEXITCODE_SUCCESS)
459 return rcExit;
460
461 RTMANIFEST hUnpackManifest;
462 rc = RTManifestCreate(0 /*fFlags*/, &hUnpackManifest);
463 if (RT_SUCCESS(rc))
464 {
465 /*
466 * Process the tarball (would be nice to move this to a function).
467 */
468 for (;;)
469 {
470 /*
471 * Get the next stream object.
472 */
473 char *pszName;
474 RTVFSOBJ hVfsObj;
475 RTVFSOBJTYPE enmType;
476 rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
477 if (RT_FAILURE(rc))
478 {
479 if (rc != VERR_EOF)
480 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc);
481 break;
482 }
483 const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
484
485 /*
486 * Check the type & name validity then unpack it.
487 */
488 rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj);
489 if (rcExit == RTEXITCODE_SUCCESS)
490 {
491 szDstPath[offDstPath] = '\0';
492 rc = RTStrCopy(&szDstPath[offDstPath], sizeof(szDstPath) - offDstPath, pszAdjName);
493 if (RT_SUCCESS(rc))
494 {
495 if ( enmType == RTVFSOBJTYPE_FILE
496 || enmType == RTVFSOBJTYPE_IO_STREAM)
497 {
498 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
499 rcExit = UnpackExtPackFile(pszAdjName, szDstPath, hVfsIos, hUnpackManifest);
500 RTVfsIoStrmRelease(hVfsIos);
501 }
502 else if (*pszAdjName && strcmp(pszAdjName, "."))
503 rcExit = UnpackExtPackDir(szDstPath, hVfsObj);
504 }
505 else
506 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Name is too long: '%s' (%Rrc)", pszAdjName, rc);
507 }
508
509 /*
510 * Clean up and break out on failure.
511 */
512 RTVfsObjRelease(hVfsObj);
513 RTStrFree(pszName);
514 if (rcExit != RTEXITCODE_SUCCESS)
515 break;
516 }
517
518 /*
519 * Check that what we just extracted matches the already verified
520 * manifest.
521 */
522 if (rcExit == RTEXITCODE_SUCCESS)
523 {
524 char szError[RTPATH_MAX];
525 rc = RTManifestEqualsEx(hUnpackManifest, hValidManifest, NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttr*/,
526 0 /*fFlags*/, szError, sizeof(szError));
527 if (RT_SUCCESS(rc))
528 rc = RTEXITCODE_SUCCESS;
529 else if (rc == VERR_NOT_EQUAL && szError[0])
530 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Manifest mismatch: %s", szError);
531 else
532 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEqualsEx failed: %Rrc", rc);
533 }
534#if 0
535 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
536 RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
537 RTVfsIoStrmWrite(hVfsIosStdOut, "Unpack:\n", sizeof("Unpack:\n") - 1, true, NULL);
538 RTManifestWriteStandard(hUnpackManifest, hVfsIosStdOut);
539 RTVfsIoStrmWrite(hVfsIosStdOut, "Valid:\n", sizeof("Valid:\n") - 1, true, NULL);
540 RTManifestWriteStandard(hValidManifest, hVfsIosStdOut);
541#endif
542 RTManifestRelease(hUnpackManifest);
543 }
544 RTVfsFsStrmRelease(hTarFss);
545
546 return rcExit;
547}
548
549
550
551/**
552 * Wrapper around VBoxExtPackValidateTarball.
553 *
554 * @returns The program exit code.
555 * @param hTarballFile The handle to open the @a pszTarball file.
556 * @param pszExtPackName The name of the extension pack name.
557 * @param pszTarball The name of the tarball in case we have to
558 * complain about something.
559 * @param phValidManifest Where to return the handle to fully validated
560 * the manifest for the extension pack. This
561 * includes all files.
562 */
563static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball,
564 PRTMANIFEST phValidManifest)
565{
566 *phValidManifest = NIL_RTMANIFEST;
567 RTMsgInfo("Validating extension pack '%s' ('%s')...", pszTarball, pszExtPackName);
568
569 char szError[8192];
570 int rc = VBoxExtPackValidateTarball(hTarballFile, pszExtPackName, pszTarball,
571 szError, sizeof(szError), phValidManifest, NULL /*phXmlFile*/);
572 if (RT_FAILURE(rc))
573 {
574 Assert(szError[0]);
575 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
576 }
577 Assert(!szError[0]);
578 return RTEXITCODE_SUCCESS;
579}
580
581
582/**
583 * The 2nd part of the installation process.
584 *
585 * @returns The program exit code.
586 * @param pszBaseDir The base directory.
587 * @param pszCertDir The certificat directory.
588 * @param pszTarball The tarball name.
589 * @param hTarballFile The handle to open the @a pszTarball file.
590 * @param hTarballFileOpt The tarball file handle (optional).
591 * @param pszName The extension pack name.
592 * @param pszMangledName The mangled extension pack name.
593 * @param fReplace Whether to replace any existing ext pack.
594 */
595static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball,
596 RTFILE hTarballFile, RTFILE hTarballFileOpt,
597 const char *pszName, const char *pszMangledName, bool fReplace)
598{
599 /*
600 * Do some basic validation of the tarball file.
601 */
602 RTFSOBJINFO ObjInfo;
603 int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
604 if (RT_FAILURE(rc))
605 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball);
606 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
607 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball);
608
609 if (hTarballFileOpt != NIL_RTFILE)
610 {
611 RTFSOBJINFO ObjInfo2;
612 rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX);
613 if (RT_FAILURE(rc))
614 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc);
615 if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice
616 || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId)
617 return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match");
618 }
619
620 /*
621 * Construct the paths to the two directories we'll be using.
622 */
623 char szFinalPath[RTPATH_MAX];
624 rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszMangledName);
625 if (RT_FAILURE(rc))
626 return RTMsgErrorExit(RTEXITCODE_FAILURE,
627 "Failed to construct the path to the final extension pack directory: %Rrc", rc);
628
629 char szTmpPath[RTPATH_MAX];
630 rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszMangledName);
631 if (RT_SUCCESS(rc))
632 {
633 size_t cchTmpPath = strlen(szTmpPath);
634 RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf());
635 }
636 if (RT_FAILURE(rc))
637 return RTMsgErrorExit(RTEXITCODE_FAILURE,
638 "Failed to construct the path to the temporary extension pack directory: %Rrc", rc);
639
640 /*
641 * Check that they don't exist at this point in time, unless fReplace=true.
642 */
643 rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
644 if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
645 {
646 if (!fReplace)
647 return RTMsgErrorExit(RTEXITCODE_FAILURE,
648 "The extension pack is already installed. You must uninstall the old one first.");
649 }
650 else if (RT_SUCCESS(rc))
651 return RTMsgErrorExit(RTEXITCODE_FAILURE,
652 "Found non-directory file system object where the extension pack would be installed ('%s')",
653 szFinalPath);
654 else if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
655 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
656
657 rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
658 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
659 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
660
661 /*
662 * Create the temporary directory and prepare the extension pack within it.
663 * If all checks out correctly, rename it to the final directory.
664 */
665 RTDirCreate(pszBaseDir, 0755);
666 rc = RTDirCreate(szTmpPath, 0700);
667 if (RT_FAILURE(rc))
668 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath);
669
670 RTMANIFEST hValidManifest = NIL_RTMANIFEST;
671 RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszName, pszTarball, &hValidManifest);
672 if (rcExit == RTEXITCODE_SUCCESS)
673 rcExit = UnpackExtPack(hTarballFile, szTmpPath, hValidManifest, pszTarball);
674 if (rcExit == RTEXITCODE_SUCCESS)
675 rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName);
676 if (rcExit == RTEXITCODE_SUCCESS)
677 rcExit = SetExtPackPermissions(szTmpPath);
678 RTManifestRelease(hValidManifest);
679
680 if (rcExit == RTEXITCODE_SUCCESS)
681 {
682 rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
683 if ( RT_FAILURE(rc)
684 && fReplace
685 && RTDirExists(szFinalPath))
686 {
687 /* Automatic uninstall if --replace was given. */
688 rcExit = CommonUninstallWorker(szFinalPath);
689 if (rcExit == RTEXITCODE_SUCCESS)
690 rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
691 }
692 if (RT_SUCCESS(rc))
693 RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball);
694 else if (rcExit == RTEXITCODE_SUCCESS)
695 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
696 "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')",
697 rc, szTmpPath, szFinalPath);
698 }
699
700 /*
701 * Clean up the temporary directory on failure.
702 */
703 if (rcExit != RTEXITCODE_SUCCESS)
704 RemoveExtPackDir(szTmpPath, true /*fTemporary*/);
705
706 return rcExit;
707}
708
709
710/**
711 * Implements the 'install' command.
712 *
713 * @returns The program exit code.
714 * @param argc The number of program arguments.
715 * @param argv The program arguments.
716 */
717static RTEXITCODE DoInstall(int argc, char **argv)
718{
719 /*
720 * Parse the parameters.
721 *
722 * Note! The --base-dir and --cert-dir are only for checking that the
723 * caller and this help applications have the same idea of where
724 * things are. Likewise, the --name is for verifying assumptions
725 * the caller made about the name. The optional --tarball-fd option
726 * is just for easing the paranoia on the user side.
727 */
728 static const RTGETOPTDEF s_aOptions[] =
729 {
730 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
731 { "--cert-dir", 'c', RTGETOPT_REQ_STRING },
732 { "--name", 'n', RTGETOPT_REQ_STRING },
733 { "--tarball", 't', RTGETOPT_REQ_STRING },
734 { "--tarball-fd", 'd', RTGETOPT_REQ_UINT64 },
735 { "--replace", 'r', RTGETOPT_REQ_NOTHING }
736 };
737 RTGETOPTSTATE GetState;
738 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
739 if (RT_FAILURE(rc))
740 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
741
742 const char *pszBaseDir = NULL;
743 const char *pszCertDir = NULL;
744 const char *pszName = NULL;
745 const char *pszTarball = NULL;
746 RTFILE hTarballFileOpt = NIL_RTFILE;
747 bool fReplace = false;
748 RTGETOPTUNION ValueUnion;
749 int ch;
750 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
751 {
752 switch (ch)
753 {
754 case 'b':
755 if (pszBaseDir)
756 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
757 pszBaseDir = ValueUnion.psz;
758 if (!IsValidBaseDir(pszBaseDir))
759 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
760 break;
761
762 case 'c':
763 if (pszCertDir)
764 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options");
765 pszCertDir = ValueUnion.psz;
766 if (!IsValidCertificateDir(pszCertDir))
767 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir);
768 break;
769
770 case 'n':
771 if (pszName)
772 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
773 pszName = ValueUnion.psz;
774 if (!VBoxExtPackIsValidName(pszName))
775 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
776 break;
777
778 case 't':
779 if (pszTarball)
780 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options");
781 pszTarball = ValueUnion.psz;
782 break;
783
784 case 'd':
785 {
786 if (hTarballFileOpt != NIL_RTFILE)
787 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options");
788 RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64;
789 if (hNative != ValueUnion.u64)
790 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64);
791 rc = RTFileFromNative(&hTarballFileOpt, hNative);
792 if (RT_FAILURE(rc))
793 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc);
794 break;
795 }
796
797 case 'r':
798 fReplace = true;
799 break;
800
801 case 'h':
802 case 'V':
803 return DoStandardOption(ch);
804
805 default:
806 return RTGetOptPrintError(ch, &ValueUnion);
807 }
808 }
809 if (!pszName)
810 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
811 if (!pszBaseDir)
812 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
813 if (!pszCertDir)
814 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option");
815 if (!pszTarball)
816 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option");
817
818 /*
819 * Ok, down to business.
820 */
821 iprt::MiniString *pstrMangledName = VBoxExtPackMangleName(pszName);
822 if (!pstrMangledName)
823 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
824
825 RTEXITCODE rcExit;
826 RTFILE hTarballFile;
827 rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
828 if (RT_SUCCESS(rc))
829 {
830 rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, hTarballFile, hTarballFileOpt,
831 pszName, pstrMangledName->c_str(), fReplace);
832 RTFileClose(hTarballFile);
833 }
834 else
835 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball);
836
837 delete pstrMangledName;
838 return rcExit;
839}
840
841
842/**
843 * Implements the 'uninstall' command.
844 *
845 * @returns The program exit code.
846 * @param argc The number of program arguments.
847 * @param argv The program arguments.
848 */
849static RTEXITCODE DoUninstall(int argc, char **argv)
850{
851 /*
852 * Parse the parameters.
853 *
854 * Note! The --base-dir is only for checking that the caller and this help
855 * applications have the same idea of where things are.
856 */
857 static const RTGETOPTDEF s_aOptions[] =
858 {
859 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
860 { "--name", 'n', RTGETOPT_REQ_STRING }
861 };
862 RTGETOPTSTATE GetState;
863 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
864 if (RT_FAILURE(rc))
865 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
866
867 const char *pszBaseDir = NULL;
868 const char *pszName = NULL;
869 RTGETOPTUNION ValueUnion;
870 int ch;
871 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
872 {
873 switch (ch)
874 {
875 case 'b':
876 if (pszBaseDir)
877 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
878 pszBaseDir = ValueUnion.psz;
879 if (!IsValidBaseDir(pszBaseDir))
880 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
881 break;
882
883 case 'n':
884 if (pszName)
885 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
886 pszName = ValueUnion.psz;
887 if (!VBoxExtPackIsValidName(pszName))
888 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
889 break;
890
891 case 'h':
892 case 'V':
893 return DoStandardOption(ch);
894
895 default:
896 return RTGetOptPrintError(ch, &ValueUnion);
897 }
898 }
899 if (!pszName)
900 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
901 if (!pszBaseDir)
902 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
903
904 /*
905 * Mangle the name so we can construct the directory names.
906 */
907 iprt::MiniString *pstrMangledName = VBoxExtPackMangleName(pszName);
908 if (!pstrMangledName)
909 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
910 iprt::MiniString strMangledName(*pstrMangledName);
911 delete pstrMangledName;
912
913 /*
914 * Ok, down to business.
915 */
916 /* Check that it exists. */
917 char szExtPackDir[RTPATH_MAX];
918 rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str());
919 if (RT_FAILURE(rc))
920 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc);
921
922 if (!RTDirExists(szExtPackDir))
923 {
924 RTMsgInfo("Extension pack not installed. Nothing to do.");
925 return RTEXITCODE_SUCCESS;
926 }
927
928 RTEXITCODE rcExit = CommonUninstallWorker(szExtPackDir);
929 if (rcExit == RTEXITCODE_SUCCESS)
930 RTMsgInfo("Successfully removed extension pack '%s'\n", pszName);
931
932 return rcExit;
933}
934
935/**
936 * Implements the 'cleanup' command.
937 *
938 * @returns The program exit code.
939 * @param argc The number of program arguments.
940 * @param argv The program arguments.
941 */
942static RTEXITCODE DoCleanup(int argc, char **argv)
943{
944 /*
945 * Parse the parameters.
946 *
947 * Note! The --base-dir is only for checking that the caller and this help
948 * applications have the same idea of where things are.
949 */
950 static const RTGETOPTDEF s_aOptions[] =
951 {
952 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
953 };
954 RTGETOPTSTATE GetState;
955 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
956 if (RT_FAILURE(rc))
957 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
958
959 const char *pszBaseDir = NULL;
960 RTGETOPTUNION ValueUnion;
961 int ch;
962 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
963 {
964 switch (ch)
965 {
966 case 'b':
967 if (pszBaseDir)
968 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
969 pszBaseDir = ValueUnion.psz;
970 if (!IsValidBaseDir(pszBaseDir))
971 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
972 break;
973
974 case 'h':
975 case 'V':
976 return DoStandardOption(ch);
977
978 default:
979 return RTGetOptPrintError(ch, &ValueUnion);
980 }
981 }
982 if (!pszBaseDir)
983 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
984
985 /*
986 * Ok, down to business.
987 */
988 PRTDIR pDir;
989 rc = RTDirOpen(&pDir, pszBaseDir);
990 if (RT_FAILURE(rc))
991 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
992
993 uint32_t cCleaned = 0;
994 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
995 for (;;)
996 {
997 RTDIRENTRYEX Entry;
998 rc = RTDirReadEx(pDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
999 if (RT_FAILURE(rc))
1000 {
1001 if (rc != VERR_NO_MORE_FILES)
1002 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc);
1003 break;
1004 }
1005
1006 /*
1007 * Only directories which conform with our temporary install/uninstall
1008 * naming scheme are candidates for cleaning.
1009 */
1010 if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
1011 && strcmp(Entry.szName, ".") != 0
1012 && strcmp(Entry.szName, "..") != 0)
1013 {
1014 bool fCandidate = false;
1015 char *pszMarker = strstr(Entry.szName, "-_-");
1016 if ( pszMarker
1017 && ( !strcmp(pszMarker, "-_-uninst")
1018 || !strncmp(pszMarker, "-_-inst", sizeof("-_-inst") - 1)))
1019 fCandidate = VBoxExtPackIsValidMangledName(Entry.szName, pszMarker - &Entry.szName[0]);
1020 if (fCandidate)
1021 {
1022 /*
1023 * Recursive delete, safe.
1024 */
1025 char szPath[RTPATH_MAX];
1026 rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName);
1027 if (RT_SUCCESS(rc))
1028 {
1029 RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/);
1030 if (rcExit2 == RTEXITCODE_SUCCESS)
1031 RTMsgInfo("Successfully removed '%s'.", Entry.szName);
1032 else if (rcExit == RTEXITCODE_SUCCESS)
1033 rcExit = rcExit2;
1034 }
1035 else
1036 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
1037 cCleaned++;
1038 }
1039 }
1040 }
1041 RTDirClose(pDir);
1042 if (!cCleaned)
1043 RTMsgInfo("Nothing to clean.");
1044 return rcExit;
1045}
1046
1047#ifdef WITH_ELEVATION
1048
1049/**
1050 * Copies the content of a file to a stream.
1051 *
1052 * @param hSrc The source file.
1053 * @param pDst The destination stream.
1054 * @param fComplain Whether to complain about errors (i.e. is this
1055 * stderr, if not keep the trap shut because it
1056 * may be missing when running under VBoxSVC.)
1057 */
1058static void CopyFileToStdXxx(RTFILE hSrc, PRTSTREAM pDst, bool fComplain)
1059{
1060 int rc;
1061 for (;;)
1062 {
1063 char abBuf[0x1000];
1064 size_t cbRead;
1065 rc = RTFileRead(hSrc, abBuf, sizeof(abBuf), &cbRead);
1066 if (RT_FAILURE(rc))
1067 {
1068 RTMsgError("RTFileRead failed: %Rrc", rc);
1069 break;
1070 }
1071 if (!cbRead)
1072 break;
1073 rc = RTStrmWrite(pDst, abBuf, cbRead);
1074 if (RT_FAILURE(rc))
1075 {
1076 if (fComplain)
1077 RTMsgError("RTStrmWrite failed: %Rrc", rc);
1078 break;
1079 }
1080 }
1081 rc = RTStrmFlush(pDst);
1082 if (RT_FAILURE(rc) && fComplain)
1083 RTMsgError("RTStrmFlush failed: %Rrc", rc);
1084}
1085
1086
1087/**
1088 * Relaunches ourselves as a elevated process using platform specific facilities.
1089 *
1090 * @returns Program exit code.
1091 * @param pszExecPath The executable path.
1092 * @param cArgs The number of arguments.
1093 * @param papszArgs The arguments.
1094 */
1095static RTEXITCODE RelaunchElevatedNative(const char *pszExecPath, int cArgs, const char * const *papszArgs)
1096{
1097 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1098#ifdef RT_OS_WINDOWS
1099
1100 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
1101
1102 SHELLEXECUTEINFOW Info;
1103
1104 Info.cbSize = sizeof(Info);
1105 Info.fMask = SEE_MASK_NOCLOSEPROCESS;
1106 Info.hwnd = NULL;
1107 Info.lpVerb = L"runas";
1108 int rc = RTStrToUtf16(pszExecPath, (PRTUTF16 *)&Info.lpFile);
1109 if (RT_SUCCESS(rc))
1110 {
1111 char *pszCmdLine;
1112 rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[1], RTGETOPTARGV_CNV_QUOTE_MS_CRT);
1113 if (RT_SUCCESS(rc))
1114 {
1115 rc = RTStrToUtf16(pszCmdLine, (PRTUTF16 *)&Info.lpParameters);
1116 if (RT_SUCCESS(rc))
1117 {
1118 Info.lpDirectory = NULL;
1119 Info.nShow = SW_SHOWMAXIMIZED;
1120 Info.hInstApp = NULL;
1121 Info.lpIDList = NULL;
1122 Info.lpClass = NULL;
1123 Info.hkeyClass = NULL;
1124 Info.dwHotKey = 0;
1125 Info.hIcon = INVALID_HANDLE_VALUE;
1126 Info.hProcess = INVALID_HANDLE_VALUE;
1127
1128 if (ShellExecuteExW(&Info))
1129 {
1130 if (Info.hProcess != INVALID_HANDLE_VALUE)
1131 {
1132 /*
1133 * Wait for the process, make sure the deal with messages.
1134 */
1135 for (;;)
1136 {
1137 DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS);
1138 if (dwRc == WAIT_OBJECT_0)
1139 break;
1140 if ( dwRc != WAIT_TIMEOUT
1141 && dwRc != WAIT_OBJECT_0 + 1)
1142 {
1143 RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError());
1144 break;
1145 }
1146 MSG Msg;
1147 while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE))
1148 {
1149 TranslateMessage(&Msg);
1150 DispatchMessageW(&Msg);
1151 }
1152 }
1153
1154 DWORD dwExitCode;
1155 if (GetExitCodeProcess(Info.hProcess, &dwExitCode))
1156 {
1157 if (dwExitCode >= 0 && dwExitCode < 128)
1158 rcExit = (RTEXITCODE)dwExitCode;
1159 else
1160 rcExit = RTEXITCODE_FAILURE;
1161 }
1162 CloseHandle(Info.hProcess);
1163 }
1164 else
1165 RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess");
1166 }
1167 else
1168 RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError());
1169
1170
1171 RTUtf16Free((PRTUTF16)Info.lpParameters);
1172 }
1173 RTStrFree(pszCmdLine);
1174 }
1175
1176 RTUtf16Free((PRTUTF16)Info.lpFile);
1177 }
1178 else
1179 RTMsgError("RTStrToUtf16 failed: %Rc", rc);
1180
1181#elif defined(RT_OS_DARWIN)
1182 char szIconName[RTPATH_MAX];
1183 int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName));
1184 if (RT_SUCCESS(rc))
1185 rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png");
1186 if (RT_FAILURE(rc))
1187 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc);
1188
1189 AuthorizationRef AuthRef;
1190 OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef);
1191 if (orc == errAuthorizationSuccess)
1192 {
1193 /*
1194 * Preautorize the privileged execution of ourselves.
1195 */
1196 AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 };
1197 AuthorizationRights AuthRights = { 1, &AuthItem };
1198
1199 static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n";
1200 AuthorizationItem aAuthEnvItems[] =
1201 {
1202 { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 },
1203 { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 }
1204 };
1205 AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems };
1206
1207 orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv,
1208 kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed
1209 | kAuthorizationFlagExtendRights,
1210 NULL);
1211 if (orc == errAuthorizationSuccess)
1212 {
1213 /*
1214 * Execute with extra permissions
1215 */
1216 FILE *pSocketStrm;
1217 orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults,
1218 (char * const *)&papszArgs[3],
1219 &pSocketStrm);
1220 if (orc == errAuthorizationSuccess)
1221 {
1222 /*
1223 * Read the output of the tool, the read will fail when it quits.
1224 */
1225 for (;;)
1226 {
1227 char achBuf[1024];
1228 size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm);
1229 if (!cbRead)
1230 break;
1231 fwrite(achBuf, 1, cbRead, stdout);
1232 }
1233 rcExit = RTEXITCODE_SUCCESS;
1234 fclose(pSocketStrm);
1235 }
1236 else
1237 RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc);
1238 }
1239 else if (orc == errAuthorizationCanceled)
1240 RTMsgError("Authorization canceled by the user");
1241 else
1242 RTMsgError("AuthorizationCopyRights failed: %d", orc);
1243 AuthorizationFree(AuthRef, kAuthorizationFlagDefaults);
1244 }
1245 else
1246 RTMsgError("AuthorizationCreate failed: %d", orc);
1247
1248#else
1249# error "PORT ME"
1250#endif
1251 return rcExit;
1252}
1253
1254
1255/**
1256 * Relaunches ourselves as a elevated process using platform specific facilities.
1257 *
1258 * @returns Program exit code.
1259 * @param argc The number of arguments.
1260 * @param argv The arguments.
1261 */
1262static RTEXITCODE RelaunchElevated(int argc, char **argv)
1263{
1264 /*
1265 * We need the executable name later, so get it now when it's easy to quit.
1266 */
1267 char szExecPath[RTPATH_MAX];
1268 if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath)))
1269 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1270
1271 /*
1272 * Create a couple of temporary files for stderr and stdout.
1273 */
1274 char szTempDir[RTPATH_MAX - sizeof("/stderr")];
1275 int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
1276 if (RT_FAILURE(rc))
1277 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc);
1278 rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX");
1279 if (RT_FAILURE(rc))
1280 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc);
1281 rc = RTDirCreateTemp(szTempDir);
1282 if (RT_FAILURE(rc))
1283 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc);
1284
1285 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1286 char szStdOut[RTPATH_MAX];
1287 char szStdErr[RTPATH_MAX];
1288 rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout");
1289 if (RT_SUCCESS(rc))
1290 rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr");
1291 if (RT_SUCCESS(rc))
1292 {
1293 RTFILE hStdOut;
1294 rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1295 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1296 if (RT_SUCCESS(rc))
1297 {
1298 RTFILE hStdErr;
1299 rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1300 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1301 if (RT_SUCCESS(rc))
1302 {
1303 /*
1304 * Insert the --elevated and stdout/err names into the argument
1305 * list. Note that darwin skips the --stdout bit, so don't
1306 * change the order here.
1307 */
1308 int cArgs = argc + 5 + 1;
1309 char const **papszArgs = (char const **)RTMemTmpAllocZ((cArgs + 1) * sizeof(const char *));
1310 if (papszArgs)
1311 {
1312 int iDst = 0;
1313 papszArgs[iDst++] = argv[0];
1314 papszArgs[iDst++] = "--stdout";
1315 papszArgs[iDst++] = szStdOut;
1316 papszArgs[iDst++] = "--stderr";
1317 papszArgs[iDst++] = szStdErr;
1318 papszArgs[iDst++] = "--elevated";
1319 for (int iSrc = 1; iSrc <= argc; iSrc++)
1320 papszArgs[iDst++] = argv[iSrc];
1321
1322 /*
1323 * Do the platform specific process execution (waiting included).
1324 */
1325 rcExit = RelaunchElevatedNative(szExecPath, cArgs, papszArgs);
1326
1327 /*
1328 * Copy the standard files to our standard handles.
1329 */
1330 CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/);
1331 CopyFileToStdXxx(hStdOut, g_pStdOut, false);
1332
1333 RTMemTmpFree(papszArgs);
1334 }
1335
1336 RTFileClose(hStdErr);
1337 RTFileDelete(szStdErr);
1338 }
1339 RTFileClose(hStdOut);
1340 RTFileDelete(szStdOut);
1341 }
1342 }
1343 RTDirRemove(szTempDir);
1344
1345 return rcExit;
1346}
1347
1348
1349/**
1350 * Checks if the process is elevated or not.
1351 *
1352 * @returns RTEXITCODE_SUCCESS if preconditions are fine,
1353 * otherwise error message + RTEXITCODE_FAILURE.
1354 * @param pfElevated Where to store the elevation indicator.
1355 */
1356static RTEXITCODE ElevationCheck(bool *pfElevated)
1357{
1358 *pfElevated = false;
1359
1360# if defined(RT_OS_WINDOWS)
1361 /** @todo This should probably check if UAC is diabled and if we are
1362 * Administrator first. Also needs to check for Vista+ first, probably.
1363 */
1364 DWORD cb;
1365 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1366 HANDLE hToken;
1367 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
1368 return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError());
1369
1370 /*
1371 * Check if we're member of the Administrators group. If we aren't, there
1372 * is no way to elevate ourselves to system admin.
1373 * N.B. CheckTokenMembership does not do the job here (due to attributes?).
1374 */
1375 BOOL fIsAdmin = FALSE;
1376 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
1377 PSID pAdminGrpSid;
1378 if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid))
1379 {
1380# ifdef DEBUG
1381 char *pszAdminGrpSid = NULL;
1382 ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid);
1383# endif
1384
1385 if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb)
1386 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1387 {
1388 PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb);
1389 if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb))
1390 {
1391 for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++)
1392 {
1393# ifdef DEBUG
1394 char *pszGrpSid = NULL;
1395 ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid);
1396# endif
1397 if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid))
1398 {
1399 /* That it's listed is enough I think, ignore attributes. */
1400 fIsAdmin = TRUE;
1401 break;
1402 }
1403 }
1404 }
1405 else
1406 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError());
1407 RTMemFree(pTokenGroups);
1408 }
1409 else
1410 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError());
1411
1412 FreeSid(pAdminGrpSid);
1413 }
1414 else
1415 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError());
1416 if (fIsAdmin)
1417 {
1418 /*
1419 * Check the integrity level (Vista / UAC).
1420 */
1421# define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L
1422# define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
1423 if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb)
1424 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1425 {
1426 PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb);
1427 if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb))
1428 {
1429 DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U);
1430
1431 if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID)
1432 *pfElevated = true;
1433 }
1434 else
1435 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1436 RTMemFree(pSidAndAttr);
1437 }
1438 else if ( GetLastError() == ERROR_INVALID_PARAMETER
1439 || GetLastError() == ERROR_NOT_SUPPORTED)
1440 *pfElevated = true; /* Older Windows version. */
1441 else
1442 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1443 }
1444 else
1445 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action");
1446
1447 CloseHandle(hToken);
1448 return rcExit;
1449
1450# else
1451 /*
1452 * On Unixy systems, we check if the executable and the current user is
1453 * the same. This heuristic works fine for both hardened and development
1454 * builds.
1455 */
1456 char szExecPath[RTPATH_MAX];
1457 if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL)
1458 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1459
1460 RTFSOBJINFO ObjInfo;
1461 int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
1462 if (RT_FAILURE(rc))
1463 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed");
1464
1465 *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid()
1466 || ObjInfo.Attr.u.Unix.uid == getuid();
1467 return RTEXITCODE_SUCCESS;
1468# endif
1469}
1470
1471#endif /* WITH_ELEVATION */
1472
1473int main(int argc, char **argv)
1474{
1475 /*
1476 * Initialize IPRT and check that we're correctly installed.
1477 */
1478 int rc = RTR3Init();
1479 if (RT_FAILURE(rc))
1480 return RTMsgInitFailure(rc);
1481
1482 char szErr[2048];
1483 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, szErr, sizeof(szErr));
1484 if (RT_FAILURE(rc))
1485 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szErr);
1486
1487 /*
1488 * Elevation check.
1489 */
1490 RTEXITCODE rcExit;
1491#ifdef WITH_ELEVATION
1492 bool fElevated;
1493 rcExit = ElevationCheck(&fElevated);
1494 if (rcExit != RTEXITCODE_SUCCESS)
1495 return rcExit;
1496#endif
1497
1498 /*
1499 * Parse the top level arguments until we find a command.
1500 */
1501 static const RTGETOPTDEF s_aOptions[] =
1502 {
1503#define CMD_INSTALL 1000
1504 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
1505#define CMD_UNINSTALL 1001
1506 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
1507#define CMD_CLEANUP 1002
1508 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
1509#ifdef WITH_ELEVATION
1510# define OPT_ELEVATED 1090
1511 { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING },
1512# define OPT_STDOUT 1091
1513 { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING },
1514# define OPT_STDERR 1092
1515 { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING },
1516#endif
1517 };
1518 RTGETOPTSTATE GetState;
1519 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
1520 if (RT_FAILURE(rc))
1521 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1522 for (;;)
1523 {
1524 RTGETOPTUNION ValueUnion;
1525 int ch = RTGetOpt(&GetState, &ValueUnion);
1526 switch (ch)
1527 {
1528 case 0:
1529 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
1530
1531 case CMD_INSTALL:
1532 case CMD_UNINSTALL:
1533 case CMD_CLEANUP:
1534 {
1535#ifdef WITH_ELEVATION
1536 if (!fElevated)
1537 return RelaunchElevated(argc, argv);
1538#endif
1539 int cCmdargs = argc - GetState.iNext;
1540 char **papszCmdArgs = argv + GetState.iNext;
1541 switch (ch)
1542 {
1543 case CMD_INSTALL:
1544 rcExit = DoInstall( cCmdargs, papszCmdArgs);
1545 break;
1546 case CMD_UNINSTALL:
1547 rcExit = DoUninstall(cCmdargs, papszCmdArgs);
1548 break;
1549 case CMD_CLEANUP:
1550 rcExit = DoCleanup( cCmdargs, papszCmdArgs);
1551 break;
1552 default:
1553 AssertReleaseFailedReturn(RTEXITCODE_FAILURE);
1554 }
1555
1556 /*
1557 * Standard error should end with rcExit=RTEXITCODE_SUCCESS on
1558 * success since the exit code may otherwise get lost in the
1559 * process elevation fun.
1560 */
1561 RTStrmFlush(g_pStdOut);
1562 RTStrmFlush(g_pStdErr);
1563 switch (rcExit)
1564 {
1565 case RTEXITCODE_SUCCESS:
1566 RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n");
1567 break;
1568 default:
1569 RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit);
1570 break;
1571 }
1572 RTStrmFlush(g_pStdErr);
1573 RTStrmFlush(g_pStdOut);
1574 return rcExit;
1575 }
1576
1577#ifdef WITH_ELEVATION
1578 case OPT_ELEVATED:
1579 fElevated = true;
1580 break;
1581
1582 case OPT_STDERR:
1583 case OPT_STDOUT:
1584 {
1585 FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr);
1586 if (!pFile)
1587 {
1588 rc = RTErrConvertFromErrno(errno);
1589 return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc);
1590 }
1591 break;
1592 }
1593#endif
1594
1595 case 'h':
1596 case 'V':
1597 return DoStandardOption(ch);
1598
1599 default:
1600 return RTGetOptPrintError(ch, &ValueUnion);
1601 }
1602 /* not currently reached */
1603 }
1604 /* not reached */
1605}
1606
1607
1608#ifdef RT_OS_WINDOWS
1609extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
1610{
1611 NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine); NOREF(hInstance);
1612 return main(__argc, __argv);
1613}
1614#endif
1615
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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