VirtualBox

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

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

cleanup fix.

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 53.2 KB
 
1/* $Id: VBoxExtPackHelperApp.cpp 34580 2010-12-01 15:46:52Z 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/ctype.h>
26#include <iprt/dir.h>
27//#include <iprt/env.h>
28#include <iprt/file.h>
29#include <iprt/fs.h>
30#include <iprt/getopt.h>
31#include <iprt/initterm.h>
32#include <iprt/manifest.h>
33#include <iprt/message.h>
34#include <iprt/param.h>
35#include <iprt/path.h>
36//#include <iprt/pipe.h>
37#include <iprt/process.h>
38#include <iprt/string.h>
39#include <iprt/stream.h>
40#include <iprt/vfs.h>
41#include <iprt/zip.h>
42#include <iprt/cpp/ministring.h>
43
44#include <VBox/log.h>
45#include <VBox/err.h>
46#include <VBox/sup.h>
47#include <VBox/version.h>
48
49
50/*******************************************************************************
51* Defined Constants And Macros *
52*******************************************************************************/
53/** The maximum entry name length.
54 * Play short and safe. */
55#define VBOX_EXTPACK_MAX_ENTRY_NAME_LENGTH 128
56
57
58#ifdef IN_RT_R3
59/* Override RTAssertShouldPanic to prevent gdb process creation. */
60RTDECL(bool) RTAssertShouldPanic(void)
61{
62 return true;
63}
64#endif
65
66
67
68/**
69 * Handle the special standard options when these are specified after the
70 * command.
71 *
72 * @param ch The option character.
73 */
74static RTEXITCODE DoStandardOption(int ch)
75{
76 switch (ch)
77 {
78 case 'h':
79 {
80 RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n"
81 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
82 "All rights reserved.\n"
83 "\n"
84 "This NOT intended for general use, please use VBoxManage instead\n"
85 "or call the IExtPackManager API directly.\n"
86 "\n"
87 "Usage: %s <command> [options]\n"
88 "Commands:\n"
89 " install --base-dir <dir> --cert-dir <dir> --name <name> \\\n"
90 " --tarball <tarball> --tarball-fd <fd>\n"
91 " uninstall --base-dir <dir> --name <name>\n"
92 " cleanup --base-dir <dir>\n"
93 , RTProcShortName());
94 return RTEXITCODE_SUCCESS;
95 }
96
97 case 'V':
98 RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
99 return RTEXITCODE_SUCCESS;
100
101 default:
102 AssertFailedReturn(RTEXITCODE_FAILURE);
103 }
104}
105
106
107/**
108 * Checks if the cerficiate directory is valid.
109 *
110 * @returns true if it is valid, false if it isn't.
111 * @param pszCertDir The certificate directory to validate.
112 */
113static bool IsValidCertificateDir(const char *pszCertDir)
114{
115 /*
116 * Just be darn strict for now.
117 */
118 char szCorrect[RTPATH_MAX];
119 int rc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect));
120 if (RT_FAILURE(rc))
121 return false;
122 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR);
123 if (RT_FAILURE(rc))
124 return false;
125
126 return RTPathCompare(szCorrect, pszCertDir) == 0;
127}
128
129
130/**
131 * Checks if the base directory is valid.
132 *
133 * @returns true if it is valid, false if it isn't.
134 * @param pszBaesDir The base directory to validate.
135 */
136static bool IsValidBaseDir(const char *pszBaseDir)
137{
138 /*
139 * Just be darn strict for now.
140 */
141 char szCorrect[RTPATH_MAX];
142 int rc = RTPathAppPrivateArch(szCorrect, sizeof(szCorrect));
143 if (RT_FAILURE(rc))
144 return false;
145 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR);
146 if (RT_FAILURE(rc))
147 return false;
148
149 return RTPathCompare(szCorrect, pszBaseDir) == 0;
150}
151
152
153/**
154 * Cleans up a temporary extension pack directory.
155 *
156 * This is used by 'uninstall', 'cleanup' and in the failure path of 'install'.
157 *
158 * @returns The program exit code.
159 * @param pszDir The directory to clean up. The caller is
160 * responsible for making sure this is valid.
161 * @param fTemporary Whether this is a temporary install directory or
162 * not.
163 */
164static RTEXITCODE RemoveExtPackDir(const char *pszDir, bool fTemporary)
165{
166 /** @todo May have to undo 555 modes here later. */
167 int rc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR);
168 if (RT_FAILURE(rc))
169 return RTMsgErrorExit(RTEXITCODE_FAILURE,
170 "Failed to delete the %sextension pack directory: %Rrc ('%s')",
171 fTemporary ? "temporary " : "", rc, pszDir);
172 return RTEXITCODE_SUCCESS;
173}
174
175
176/**
177 * Rewinds the tarball file handle and creates a gunzip | tar chain that
178 * results in a filesystem stream.
179 *
180 * @returns success or failure, message displayed on failure.
181 * @param hTarballFile The handle to the tarball file.
182 * @param phTarFss Where to return the filesystem stream handle.
183 */
184static RTEXITCODE OpenTarFss(RTFILE hTarballFile, PRTVFSFSSTREAM phTarFss)
185{
186 /*
187 * Rewind the file and set up a VFS chain for it.
188 */
189 int rc = RTFileSeek(hTarballFile, 0, RTFILE_SEEK_BEGIN, NULL);
190 if (RT_FAILURE(rc))
191 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed seeking to the start of the tarball: %Rrc\n", rc);
192
193 RTVFSIOSTREAM hTarballIos;
194 rc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/,
195 &hTarballIos);
196 if (RT_FAILURE(rc))
197 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmFromRTFile failed: %Rrc\n", rc);
198
199 RTVFSIOSTREAM hGunzipIos;
200 rc = RTZipGzipDecompressIoStream(hTarballIos, 0 /*fFlags*/, &hGunzipIos);
201 if (RT_SUCCESS(rc))
202 {
203 RTVFSFSSTREAM hTarFss;
204 rc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss);
205 if (RT_SUCCESS(rc))
206 {
207 RTVfsIoStrmRelease(hGunzipIos);
208 RTVfsIoStrmRelease(hTarballIos);
209 *phTarFss = hTarFss;
210 return RTEXITCODE_SUCCESS;
211 }
212 RTMsgError("RTZipTarFsStreamFromIoStream failed: %Rrc\n", rc);
213 RTVfsIoStrmRelease(hGunzipIos);
214 }
215 else
216 RTMsgError("RTZipGzipDecompressIoStream failed: %Rrc\n", rc);
217 RTVfsIoStrmRelease(hTarballIos);
218 return RTEXITCODE_FAILURE;
219}
220
221
222/**
223 * Sets the permissions of the temporary extension pack directory just before
224 * renaming it.
225 *
226 * By default the temporary directory is only accessible by root, this function
227 * will make it world readable and browseable.
228 *
229 * @returns The program exit code.
230 * @param pszDir The temporary extension pack directory.
231 */
232static RTEXITCODE SetExtPackPermissions(const char *pszDir)
233{
234 RTMsgInfo("Setting permissions...");
235#if !defined(RT_OS_WINDOWS)
236 int rc = RTPathSetMode(pszDir, 0755);
237 if (RT_FAILURE(rc))
238 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir);
239#else
240 /** @todo */
241#endif
242
243 return RTEXITCODE_SUCCESS;
244}
245
246
247/**
248 * Verifies the manifest and its signature.
249 *
250 * @returns Program exit code, failure with message.
251 * @param hManifestFile The xml from the extension pack.
252 * @param pszExtPackName The expected extension pack name.
253 */
254static RTEXITCODE VerifyXml(RTVFSFILE hXmlFile, const char *pszExtPackName)
255{
256 /** @todo implement XML verification. */
257 return RTEXITCODE_SUCCESS;
258}
259
260
261/**
262 * Verifies the manifest and its signature.
263 *
264 * @returns Program exit code, failure with message.
265 * @param hOurManifest The manifest we compiled.
266 * @param hManifestFile The manifest file in the extension pack.
267 * @param hSignatureFile The manifest signature file.
268 */
269static RTEXITCODE VerifyManifestAndSignature(RTMANIFEST hOurManifest, RTVFSFILE hManifestFile, RTVFSFILE hSignatureFile)
270{
271 /*
272 * Read the manifest from the extension pack.
273 */
274 int rc = RTVfsFileSeek(hManifestFile, 0, RTFILE_SEEK_BEGIN, NULL);
275 if (RT_FAILURE(rc))
276 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFileSeek failed: %Rrc", rc);
277
278 RTMANIFEST hTheirManifest;
279 rc = RTManifestCreate(0 /*fFlags*/, &hTheirManifest);
280 if (RT_FAILURE(rc))
281 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestCreate failed: %Rrc", rc);
282
283 RTEXITCODE rcExit;
284 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hManifestFile);
285 rc = RTManifestReadStandard(hTheirManifest, hVfsIos);
286 RTVfsIoStrmRelease(hVfsIos);
287 if (RT_SUCCESS(rc))
288 {
289 /*
290 * Compare the manifests.
291 */
292 static const char *s_apszIgnoreEntries[] =
293 {
294 VBOX_EXTPACK_MANIFEST_NAME,
295 VBOX_EXTPACK_SIGNATURE_NAME,
296 "./" VBOX_EXTPACK_MANIFEST_NAME,
297 "./" VBOX_EXTPACK_SIGNATURE_NAME,
298 NULL
299 };
300 char szError[RTPATH_MAX];
301 rc = RTManifestEqualsEx(hOurManifest, hTheirManifest, &s_apszIgnoreEntries[0], NULL,
302 RTMANIFEST_EQUALS_IGN_MISSING_ATTRS /*fFlags*/,
303 szError, sizeof(szError));
304 if (RT_SUCCESS(rc))
305 {
306 /*
307 * Validate the manifest file signature.
308 */
309 /** @todo implement signature stuff */
310
311 }
312 else if (rc == VERR_NOT_EQUAL && szError[0])
313 RTMsgError("Manifest mismatch: %s", szError);
314 else
315 RTMsgError("RTManifestEqualsEx failed: %Rrc", rc);
316#if 0
317 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
318 RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
319 RTVfsIoStrmWrite(hVfsIosStdOut, "Our:\n", sizeof("Our:\n") - 1, true, NULL);
320 RTManifestWriteStandard(hOurManifest, hVfsIosStdOut);
321 RTVfsIoStrmWrite(hVfsIosStdOut, "Their:\n", sizeof("Their:\n") - 1, true, NULL);
322 RTManifestWriteStandard(hTheirManifest, hVfsIosStdOut);
323#endif
324 }
325 else
326 RTMsgError("Error parsing '%s': %Rrc", VBOX_EXTPACK_MANIFEST_NAME, rc);
327
328 RTManifestRelease(hTheirManifest);
329 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
330}
331
332
333/**
334 * Validates a name in an extension pack.
335 *
336 * We restrict the charset to try make sure the extension pack can be unpacked
337 * on all file systems.
338 *
339 * @returns Program exit code, failure with message.
340 * @param pszName The name to validate.
341 */
342static RTEXITCODE ValidateNameInExtPack(const char *pszName)
343{
344 if (RTPathStartsWithRoot(pszName))
345 return RTMsgErrorExit(RTEXITCODE_FAILURE, "'%s': starts with root spec", pszName);
346
347 const char *pszErr = NULL;
348 const char *psz = pszName;
349 int ch;
350 while ((ch = *psz) != '\0')
351 {
352 /* Character set restrictions. */
353 if (ch < 0 || ch >= 128)
354 {
355 pszErr = "Only 7-bit ASCII allowed";
356 break;
357 }
358 if (ch <= 31 || ch == 127)
359 {
360 pszErr = "No control characters are not allowed";
361 break;
362 }
363 if (ch == '\\')
364 {
365 pszErr = "Only backward slashes are not allowed";
366 break;
367 }
368 if (strchr("'\":;*?|[]<>(){}", ch))
369 {
370 pszErr = "The characters ', \", :, ;, *, ?, |, [, ], <, >, (, ), { and } are not allowed";
371 break;
372 }
373
374 /* Take the simple way out and ban all ".." sequences. */
375 if ( ch == '.'
376 && psz[1] == '.')
377 {
378 pszErr = "Double dot sequence are not allowed";
379 break;
380 }
381
382 /* Keep the tree shallow or the hardening checks will fail. */
383 if (psz - pszName > VBOX_EXTPACK_MAX_ENTRY_NAME_LENGTH)
384 {
385 pszErr = "Too long";
386 break;
387 }
388
389 /* advance */
390 psz++;
391 }
392
393 if (pszErr)
394 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Bad member name '%s' (pos %zu): %s", pszName, (size_t)(psz - pszName), pszErr);
395 return RTEXITCODE_SUCCESS;
396}
397
398
399/**
400 * Validates a file in an extension pack.
401 *
402 * @returns Program exit code, failure with message.
403 * @param pszName The name of the file.
404 * @param hVfsObj The VFS object.
405 */
406static RTEXITCODE ValidateFileInExtPack(const char *pszName, RTVFSOBJ hVfsObj)
407{
408 RTEXITCODE rcExit = ValidateNameInExtPack(pszName);
409 if (rcExit == RTEXITCODE_SUCCESS)
410 {
411 RTFSOBJINFO ObjInfo;
412 int rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
413 if (RT_SUCCESS(rc))
414 {
415 if (ObjInfo.cbObject >= 9*_1G64)
416 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "'%s': too large (%'RU64 bytes)",
417 pszName, (uint64_t)ObjInfo.cbObject);
418 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
419 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
420 "The alleged file '%s' has a mode mask saying differently (%RTfmode)",
421 pszName, ObjInfo.Attr.fMode);
422 }
423 else
424 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
425 }
426 return rcExit;
427}
428
429
430/**
431 * Validates a directory in an extension pack.
432 *
433 * @returns Program exit code, failure with message.
434 * @param pszName The name of the directory.
435 * @param hVfsObj The VFS object.
436 */
437static RTEXITCODE ValidateDirInExtPack(const char *pszName, RTVFSOBJ hVfsObj)
438{
439 RTEXITCODE rcExit = ValidateNameInExtPack(pszName);
440 if (rcExit == RTEXITCODE_SUCCESS)
441 {
442 RTFSOBJINFO ObjInfo;
443 int rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
444 if (RT_SUCCESS(rc))
445 {
446 if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
447 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
448 "The alleged directory '%s' has a mode mask saying differently (%RTfmode)",
449 pszName, ObjInfo.Attr.fMode);
450 }
451 else
452 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
453 }
454 return rcExit;
455}
456
457/**
458 * Validates a member of an extension pack.
459 *
460 * @returns Program exit code, failure with message.
461 * @param pszName The name of the directory.
462 * @param enmType The object type.
463 * @param hVfsObj The VFS object.
464 */
465static RTEXITCODE ValidateMemberOfExtPack(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj)
466{
467 RTEXITCODE rcExit;
468 if ( enmType == RTVFSOBJTYPE_FILE
469 || enmType == RTVFSOBJTYPE_IO_STREAM)
470 rcExit = ValidateFileInExtPack(pszName, hVfsObj);
471 else if ( enmType == RTVFSOBJTYPE_DIR
472 || enmType == RTVFSOBJTYPE_BASE)
473 rcExit = ValidateDirInExtPack(pszName, hVfsObj);
474 else
475 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "'%s' is not a file or directory (enmType=%d)", pszName, enmType);
476 return rcExit;
477}
478
479
480/**
481 * Validates the extension pack tarball prior to unpacking.
482 *
483 * Operations performed:
484 * - Hardening checks.
485 *
486 * @returns The program exit code.
487 * @param pszDir The directory where the extension pack has been
488 * unpacked.
489 * @param pszExtPackName The expected extension pack name.
490 * @param pszTarball The name of the tarball in case we have to
491 * complain about something.
492 */
493static RTEXITCODE ValidateUnpackedExtPack(const char *pszDir, const char *pszTarball, const char *pszExtPackName)
494{
495 RTMsgInfo("Validating unpacked extension pack...");
496
497 char szErr[4096+1024];
498 int rc = SUPR3HardenedVerifyDir(pszDir, true /*fRecursive*/, true /*fCheckFiles*/, szErr, sizeof(szErr));
499 if (RT_FAILURE(rc))
500 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Hardening check failed with %Rrc: %s", rc, szErr);
501 return RTEXITCODE_SUCCESS;
502}
503
504
505/**
506 * Unpacks a directory from an extension pack tarball.
507 *
508 * @returns Program exit code, failure with message.
509 * @param pszDstDirName The name of the unpacked directory.
510 * @param hVfsObj The source object for the directory.
511 */
512static RTEXITCODE UnpackExtPackDir(const char *pszDstDirName, RTVFSOBJ hVfsObj)
513{
514 int rc = RTDirCreate(pszDstDirName, 0755);
515 if (RT_FAILURE(rc))
516 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create directory '%s': %Rrc", pszDstDirName, rc);
517 /** @todo Ownership tricks on windows? */
518 return RTEXITCODE_SUCCESS;
519}
520
521
522/**
523 * Unpacks a file from an extension pack tarball.
524 *
525 * @returns Program exit code, failure with message.
526 * @param pszName The name in the tarball.
527 * @param pszDstFilename The name of the unpacked file.
528 * @param hVfsIosSrc The source stream for the file.
529 * @param hUnpackManifest The manifest to add the file digest to.
530 */
531static RTEXITCODE UnpackExtPackFile(const char *pszName, const char *pszDstFilename,
532 RTVFSIOSTREAM hVfsIosSrc, RTMANIFEST hUnpackManifest)
533{
534 /*
535 * Query the object info, we'll need it for buffer sizing as well as
536 * setting the file mode.
537 */
538 RTFSOBJINFO ObjInfo;
539 int rc = RTVfsIoStrmQueryInfo(hVfsIosSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING);
540 if (RT_FAILURE(rc))
541 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmQueryInfo failed with %Rrc on '%s'", rc, pszDstFilename);
542
543 /*
544 * Create the file.
545 */
546 uint32_t fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE | (0600 << RTFILE_O_CREATE_MODE_SHIFT);
547 RTFILE hFile;
548 rc = RTFileOpen(&hFile, pszDstFilename, fFlags);
549 if (RT_FAILURE(rc))
550 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create '%s': %Rrc", pszDstFilename, rc);
551
552 /*
553 * Create a I/O stream for the destination file, stack a manifest entry
554 * creator on top of it.
555 */
556 RTVFSIOSTREAM hVfsIosDst2;
557 rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIosDst2);
558 if (RT_SUCCESS(rc))
559 {
560 RTVFSIOSTREAM hVfsIosDst;
561 rc = RTManifestEntryAddPassthruIoStream(hUnpackManifest, hVfsIosDst2, pszName,
562 RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256,
563 false /*fReadOrWrite*/, &hVfsIosDst);
564 RTVfsIoStrmRelease(hVfsIosDst2);
565 if (RT_SUCCESS(rc))
566 {
567 /*
568 * Pump the data thru.
569 */
570 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(ObjInfo.cbObject, _1G));
571 if (RT_SUCCESS(rc))
572 {
573 rc = RTManifestPtIosAddEntryNow(hVfsIosDst);
574 if (RT_SUCCESS(rc))
575 {
576 RTVfsIoStrmRelease(hVfsIosDst);
577 hVfsIosDst = NIL_RTVFSIOSTREAM;
578
579 /*
580 * Set the mode mask.
581 */
582 ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP);
583 rc = RTFileSetMode(hFile, ObjInfo.Attr.fMode);
584 /** @todo Windows needs to do more here, I think. */
585 if (RT_SUCCESS(rc))
586 {
587 RTFileClose(hFile);
588 return RTEXITCODE_SUCCESS;
589 }
590
591 RTMsgError("Failed to set the mode of '%s' to %RTfmode: %Rrc", pszDstFilename, ObjInfo.Attr.fMode, rc);
592 }
593 else
594 RTMsgError("RTManifestPtIosAddEntryNow failed for '%s': %Rrc", pszDstFilename, rc);
595 }
596 else
597 RTMsgError("RTVfsUtilPumpIoStreams failed for '%s': %Rrc", pszDstFilename, rc);
598 RTVfsIoStrmRelease(hVfsIosDst);
599 }
600 else
601 RTMsgError("RTManifestEntryAddPassthruIoStream failed: %Rrc", rc);
602 }
603 else
604 RTMsgError("RTVfsIoStrmFromRTFile failed: %Rrc", rc);
605 RTFileClose(hFile);
606 return RTEXITCODE_FAILURE;
607}
608
609
610/**
611 * Unpacks the extension pack into the specified directory.
612 *
613 * This will apply ownership and permission changes to all the content, the
614 * exception is @a pszDirDst which will be handled by SetExtPackPermissions.
615 *
616 * @returns The program exit code.
617 * @param hTarballFile The tarball to unpack.
618 * @param pszDirDst Where to unpack it.
619 * @param hValidManifest The manifest we've validated.
620 * @param pszTarball The name of the tarball in case we have to
621 * complain about something.
622 */
623static RTEXITCODE UnpackExtPack(RTFILE hTarballFile, const char *pszDirDst, RTMANIFEST hValidManifest,
624 const char *pszTarball)
625{
626 RTMsgInfo("Unpacking extension pack into '%s'...", pszDirDst);
627
628 /*
629 * Set up the destination path.
630 */
631 char szDstPath[RTPATH_MAX];
632 int rc = RTPathAbs(pszDirDst, szDstPath, sizeof(szDstPath) - VBOX_EXTPACK_MAX_ENTRY_NAME_LENGTH - 2);
633 if (RT_FAILURE(rc))
634 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs('%s',,) failed: %Rrc", pszDirDst, rc);
635 size_t offDstPath = RTPathStripTrailingSlash(szDstPath);
636 szDstPath[offDstPath++] = '/';
637 szDstPath[offDstPath] = '\0';
638
639 /*
640 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
641 */
642 RTVFSFSSTREAM hTarFss;
643 RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss);
644 if (rcExit != RTEXITCODE_SUCCESS)
645 return rcExit;
646
647 RTMANIFEST hUnpackManifest;
648 rc = RTManifestCreate(0 /*fFlags*/, &hUnpackManifest);
649 if (RT_SUCCESS(rc))
650 {
651 /*
652 * Process the tarball (would be nice to move this to a function).
653 */
654 for (;;)
655 {
656 /*
657 * Get the next stream object.
658 */
659 char *pszName;
660 RTVFSOBJ hVfsObj;
661 RTVFSOBJTYPE enmType;
662 rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
663 if (RT_FAILURE(rc))
664 {
665 if (rc != VERR_EOF)
666 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc);
667 break;
668 }
669 const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
670
671 /*
672 * Check the type & name validity then unpack it.
673 */
674 rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj);
675 if (rcExit == RTEXITCODE_SUCCESS)
676 {
677 szDstPath[offDstPath] = '\0';
678 rc = RTStrCopy(&szDstPath[offDstPath], sizeof(szDstPath) - offDstPath, pszAdjName);
679 if (RT_SUCCESS(rc))
680 {
681 if ( enmType == RTVFSOBJTYPE_FILE
682 || enmType == RTVFSOBJTYPE_IO_STREAM)
683 {
684 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
685 rcExit = UnpackExtPackFile(pszAdjName, szDstPath, hVfsIos, hUnpackManifest);
686 RTVfsIoStrmRelease(hVfsIos);
687 }
688 else if (*pszAdjName && strcmp(pszAdjName, "."))
689 rcExit = UnpackExtPackDir(szDstPath, hVfsObj);
690 }
691 else
692 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Name is too long: '%s' (%Rrc)", pszAdjName, rc);
693 }
694
695 /*
696 * Clean up and break out on failure.
697 */
698 RTVfsObjRelease(hVfsObj);
699 RTStrFree(pszName);
700 if (rcExit != RTEXITCODE_SUCCESS)
701 break;
702 }
703
704 /*
705 * Check that what we just extracted matches the already verified
706 * manifest.
707 */
708 if (rcExit == RTEXITCODE_SUCCESS)
709 {
710 char szError[RTPATH_MAX];
711 rc = RTManifestEqualsEx(hUnpackManifest, hValidManifest, NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttr*/,
712 0 /*fFlags*/, szError, sizeof(szError));
713 if (RT_SUCCESS(rc))
714 rc = RTEXITCODE_SUCCESS;
715 else if (rc == VERR_NOT_EQUAL && szError[0])
716 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Manifest mismatch: %s", szError);
717 else
718 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEqualsEx failed: %Rrc", rc);
719 }
720#if 0
721 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
722 RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
723 RTVfsIoStrmWrite(hVfsIosStdOut, "Unpack:\n", sizeof("Unpack:\n") - 1, true, NULL);
724 RTManifestWriteStandard(hUnpackManifest, hVfsIosStdOut);
725 RTVfsIoStrmWrite(hVfsIosStdOut, "Valid:\n", sizeof("Valid:\n") - 1, true, NULL);
726 RTManifestWriteStandard(hValidManifest, hVfsIosStdOut);
727#endif
728 RTManifestRelease(hUnpackManifest);
729 }
730 RTVfsFsStrmRelease(hTarFss);
731
732 return rcExit;
733}
734
735
736
737/**
738 * Validates the extension pack tarball prior to unpacking.
739 *
740 * Operations performed:
741 * - Mandatory files.
742 * - Manifest check.
743 * - Manifest seal check.
744 * - XML check, match name.
745 *
746 * @returns The program exit code.
747 * @param hTarballFile The handle to open the @a pszTarball file.
748 * @param pszExtPackName The name of the extension pack name.
749 * @param pszTarball The name of the tarball in case we have to
750 * complain about something.
751 * @param phValidManifest Where to return the handle to fully validated
752 * the manifest for the extension pack. This
753 * includes all files.
754 *
755 * @todo This function is a bit too long and should be split up if possible.
756 */
757static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball,
758 PRTMANIFEST phValidManifest)
759{
760 *phValidManifest = NIL_RTMANIFEST;
761 RTMsgInfo("Validating extension pack '%s' ('%s')...", pszTarball, pszExtPackName);
762
763 /*
764 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
765 */
766 RTVFSFSSTREAM hTarFss;
767 RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss);
768 if (rcExit != RTEXITCODE_SUCCESS)
769 return rcExit;
770
771 RTMANIFEST hOurManifest;
772 int rc = RTManifestCreate(0 /*fFlags*/, &hOurManifest);
773 if (RT_SUCCESS(rc))
774 {
775 /*
776 * Process the tarball (would be nice to move this to a function).
777 */
778 RTVFSFILE hXmlFile = NIL_RTVFSFILE;
779 RTVFSFILE hManifestFile = NIL_RTVFSFILE;
780 RTVFSFILE hSignatureFile= NIL_RTVFSFILE;
781 for (;;)
782 {
783 /*
784 * Get the next stream object.
785 */
786 char *pszName;
787 RTVFSOBJ hVfsObj;
788 RTVFSOBJTYPE enmType;
789 rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
790 if (RT_FAILURE(rc))
791 {
792 if (rc != VERR_EOF)
793 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc);
794 break;
795 }
796 const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
797
798 /*
799 * Check the type & name validity.
800 */
801 rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj);
802 if (rcExit == RTEXITCODE_SUCCESS)
803 {
804 /*
805 * Check if this is one of the standard files.
806 */
807 PRTVFSFILE phVfsFile;
808 if (!strcmp(pszAdjName, VBOX_EXTPACK_DESCRIPTION_NAME))
809 phVfsFile = &hXmlFile;
810 else if (!strcmp(pszAdjName, VBOX_EXTPACK_MANIFEST_NAME))
811 phVfsFile = &hManifestFile;
812 else if (!strcmp(pszAdjName, VBOX_EXTPACK_SIGNATURE_NAME))
813 phVfsFile = &hSignatureFile;
814 else
815 phVfsFile = NULL;
816 if (phVfsFile)
817 {
818 /*
819 * Make sure it's a file and that it isn't too large.
820 */
821 if (*phVfsFile != NIL_RTVFSFILE)
822 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "There can only be one '%s'", pszAdjName);
823 else if (enmType != RTVFSOBJTYPE_IO_STREAM && enmType != RTVFSOBJTYPE_FILE)
824 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Standard member '%s' is not a file", pszAdjName);
825 else
826 {
827 RTFSOBJINFO ObjInfo;
828 rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
829 if (RT_SUCCESS(rc))
830 {
831 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
832 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Standard member '%s' is not a file", pszAdjName);
833 else if (ObjInfo.cbObject >= _1M)
834 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
835 "Standard member '%s' is too large: %'RU64 bytes (max 1 MB)",
836 pszAdjName, (uint64_t)ObjInfo.cbObject);
837 else
838 {
839 /*
840 * Make an in memory copy of the stream.
841 */
842 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
843 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsFile);
844 if (RT_SUCCESS(rc))
845 {
846 /*
847 * To simplify the code below, replace
848 * hVfsObj with the memorized file.
849 */
850 RTVfsObjRelease(hVfsObj);
851 hVfsObj = RTVfsObjFromFile(*phVfsFile);
852 }
853 else
854 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
855 "RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc", pszName, rc);
856 RTVfsIoStrmRelease(hVfsIos);
857 }
858 }
859 else
860 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
861 }
862 }
863 }
864
865 /*
866 * Add any I/O stream to the manifest
867 */
868 if ( rcExit == RTEXITCODE_SUCCESS
869 && ( enmType == RTVFSOBJTYPE_FILE
870 || enmType == RTVFSOBJTYPE_IO_STREAM))
871 {
872 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
873 rc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256);
874 if (RT_FAILURE(rc))
875 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEntryAddIoStream failed on '%s': %Rrc", pszAdjName, rc);
876 RTVfsIoStrmRelease(hVfsIos);
877 }
878
879 /*
880 * Clean up and break out on failure.
881 */
882 RTVfsObjRelease(hVfsObj);
883 RTStrFree(pszName);
884 if (rcExit != RTEXITCODE_SUCCESS)
885 break;
886 }
887
888 /*
889 * If we've successfully processed the tarball, verify that the
890 * mandatory files are present.
891 */
892 if (rcExit == RTEXITCODE_SUCCESS)
893 {
894 if (hXmlFile == NIL_RTVFSFILE)
895 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Mandator file '%s' is missing", VBOX_EXTPACK_DESCRIPTION_NAME);
896 if (hManifestFile == NIL_RTVFSFILE)
897 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Mandator file '%s' is missing", VBOX_EXTPACK_MANIFEST_NAME);
898 if (hSignatureFile == NIL_RTVFSFILE)
899 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Mandator file '%s' is missing", VBOX_EXTPACK_SIGNATURE_NAME);
900 }
901
902 /*
903 * Check the manifest and it's signature.
904 */
905 if (rcExit == RTEXITCODE_SUCCESS)
906 rcExit = VerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile);
907
908 /*
909 * Check the XML.
910 */
911 if (rcExit == RTEXITCODE_SUCCESS)
912 rcExit = VerifyXml(hXmlFile, pszExtPackName);
913
914 /*
915 * Release objects and stuff.
916 */
917 if (rcExit == RTEXITCODE_SUCCESS)
918 *phValidManifest = hOurManifest;
919 else
920 RTManifestRelease(hOurManifest);
921
922 RTVfsFileRelease(hXmlFile);
923 RTVfsFileRelease(hManifestFile);
924 RTVfsFileRelease(hSignatureFile);
925 }
926 RTVfsFsStrmRelease(hTarFss);
927
928 return rcExit;
929}
930
931
932/**
933 * The 2nd part of the installation process.
934 *
935 * @returns The program exit code.
936 * @param pszBaseDir The base directory.
937 * @param pszCertDir The certificat directory.
938 * @param pszTarball The tarball name.
939 * @param hTarballFile The handle to open the @a pszTarball file.
940 * @param hTarballFileOpt The tarball file handle (optional).
941 * @param pszName The extension pack name.
942 * @param pszMangledName The mangled extension pack name.
943 */
944static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball,
945 RTFILE hTarballFile, RTFILE hTarballFileOpt,
946 const char *pszName, const char *pszMangledName)
947{
948 /*
949 * Do some basic validation of the tarball file.
950 */
951 RTFSOBJINFO ObjInfo;
952 int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
953 if (RT_FAILURE(rc))
954 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball);
955 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
956 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball);
957
958 if (hTarballFileOpt != NIL_RTFILE)
959 {
960 RTFSOBJINFO ObjInfo2;
961 rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX);
962 if (RT_FAILURE(rc))
963 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc);
964 if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice
965 || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId)
966 return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match");
967 }
968
969 /*
970 * Construct the paths to the two directories we'll be using.
971 */
972 char szFinalPath[RTPATH_MAX];
973 rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszMangledName);
974 if (RT_FAILURE(rc))
975 return RTMsgErrorExit(RTEXITCODE_FAILURE,
976 "Failed to construct the path to the final extension pack directory: %Rrc", rc);
977
978 char szTmpPath[RTPATH_MAX];
979 rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszMangledName);
980 if (RT_SUCCESS(rc))
981 {
982 size_t cchTmpPath = strlen(szTmpPath);
983 RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf());
984 }
985 if (RT_FAILURE(rc))
986 return RTMsgErrorExit(RTEXITCODE_FAILURE,
987 "Failed to construct the path to the temporary extension pack directory: %Rrc", rc);
988
989 /*
990 * Check that they don't exist at this point in time.
991 */
992 rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
993 if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
994 return RTMsgErrorExit(RTEXITCODE_FAILURE, "The extension pack is already installed. You must uninstall the old one first.");
995 if (RT_SUCCESS(rc))
996 return RTMsgErrorExit(RTEXITCODE_FAILURE,
997 "Found non-directory file system object where the extension pack would be installed ('%s')",
998 szFinalPath);
999 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
1000 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
1001
1002 rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1003 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
1004 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
1005
1006 /*
1007 * Create the temporary directory and prepare the extension pack within it.
1008 * If all checks out correctly, rename it to the final directory.
1009 */
1010 RTDirCreate(pszBaseDir, 0755);
1011 rc = RTDirCreate(szTmpPath, 0700);
1012 if (RT_FAILURE(rc))
1013 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath);
1014
1015 RTMANIFEST hValidManifest = NIL_RTMANIFEST;
1016 RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszName, pszTarball, &hValidManifest);
1017 if (rcExit == RTEXITCODE_SUCCESS)
1018 rcExit = UnpackExtPack(hTarballFile, szTmpPath, hValidManifest, pszTarball);
1019 if (rcExit == RTEXITCODE_SUCCESS)
1020 rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName);
1021 if (rcExit == RTEXITCODE_SUCCESS)
1022 rcExit = SetExtPackPermissions(szTmpPath);
1023 RTManifestRelease(hValidManifest);
1024
1025 if (rcExit == RTEXITCODE_SUCCESS)
1026 {
1027 rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
1028 if (RT_SUCCESS(rc))
1029 RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball);
1030 else
1031 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1032 "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')",
1033 rc, szTmpPath, szFinalPath);
1034 }
1035
1036 /*
1037 * Clean up the temporary directory on failure.
1038 */
1039 if (rcExit != RTEXITCODE_SUCCESS)
1040 RemoveExtPackDir(szTmpPath, true /*fTemporary*/);
1041
1042 return rcExit;
1043}
1044
1045
1046/**
1047 * Implements the 'install' command.
1048 *
1049 * @returns The program exit code.
1050 * @param argc The number of program arguments.
1051 * @param argv The program arguments.
1052 */
1053static RTEXITCODE DoInstall(int argc, char **argv)
1054{
1055 /*
1056 * Parse the parameters.
1057 *
1058 * Note! The --base-dir and --cert-dir are only for checking that the
1059 * caller and this help applications have the same idea of where
1060 * things are. Likewise, the --name is for verifying assumptions
1061 * the caller made about the name. The optional --tarball-fd option
1062 * is just for easing the paranoia on the user side.
1063 */
1064 static const RTGETOPTDEF s_aOptions[] =
1065 {
1066 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
1067 { "--cert-dir", 'c', RTGETOPT_REQ_STRING },
1068 { "--name", 'n', RTGETOPT_REQ_STRING },
1069 { "--tarball", 't', RTGETOPT_REQ_STRING },
1070 { "--tarball-fd", 'd', RTGETOPT_REQ_UINT64 }
1071 };
1072 RTGETOPTSTATE GetState;
1073 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
1074 if (RT_FAILURE(rc))
1075 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1076
1077 const char *pszBaseDir = NULL;
1078 const char *pszCertDir = NULL;
1079 const char *pszName = NULL;
1080 const char *pszTarball = NULL;
1081 RTFILE hTarballFileOpt = NIL_RTFILE;
1082 RTGETOPTUNION ValueUnion;
1083 int ch;
1084 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1085 {
1086 switch (ch)
1087 {
1088 case 'b':
1089 if (pszBaseDir)
1090 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
1091 pszBaseDir = ValueUnion.psz;
1092 if (!IsValidBaseDir(pszBaseDir))
1093 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
1094 break;
1095
1096 case 'c':
1097 if (pszCertDir)
1098 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options");
1099 pszCertDir = ValueUnion.psz;
1100 if (!IsValidCertificateDir(pszCertDir))
1101 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir);
1102 break;
1103
1104 case 'n':
1105 if (pszName)
1106 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
1107 pszName = ValueUnion.psz;
1108 if (!VBoxExtPackIsValidName(pszName))
1109 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
1110 break;
1111
1112 case 't':
1113 if (pszTarball)
1114 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options");
1115 pszTarball = ValueUnion.psz;
1116 break;
1117
1118 case 'd':
1119 {
1120 if (hTarballFileOpt != NIL_RTFILE)
1121 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options");
1122 RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64;
1123 if (hNative != ValueUnion.u64)
1124 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64);
1125 rc = RTFileFromNative(&hTarballFileOpt, hNative);
1126 if (RT_FAILURE(rc))
1127 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc);
1128 break;
1129 }
1130
1131 case 'h':
1132 case 'V':
1133 return DoStandardOption(ch);
1134
1135 default:
1136 return RTGetOptPrintError(ch, &ValueUnion);
1137 }
1138 }
1139 if (!pszName)
1140 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
1141 if (!pszBaseDir)
1142 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1143 if (!pszCertDir)
1144 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option");
1145 if (!pszTarball)
1146 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option");
1147
1148 /*
1149 * Ok, down to business.
1150 */
1151 iprt::MiniString *pstrMangledName = VBoxExtPackMangleName(pszName);
1152 if (!pstrMangledName)
1153 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
1154
1155 RTEXITCODE rcExit;
1156 RTFILE hTarballFile;
1157 rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
1158 if (RT_SUCCESS(rc))
1159 {
1160 rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, hTarballFile, hTarballFileOpt,
1161 pszName, pstrMangledName->c_str());
1162 RTFileClose(hTarballFile);
1163 }
1164 else
1165 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball);
1166
1167 delete pstrMangledName;
1168 return rcExit;
1169}
1170
1171
1172/**
1173 * Implements the 'uninstall' command.
1174 *
1175 * @returns The program exit code.
1176 * @param argc The number of program arguments.
1177 * @param argv The program arguments.
1178 */
1179static RTEXITCODE DoUninstall(int argc, char **argv)
1180{
1181 /*
1182 * Parse the parameters.
1183 *
1184 * Note! The --base-dir is only for checking that the caller and this help
1185 * applications have the same idea of where things are.
1186 */
1187 static const RTGETOPTDEF s_aOptions[] =
1188 {
1189 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
1190 { "--name", 'n', RTGETOPT_REQ_STRING }
1191 };
1192 RTGETOPTSTATE GetState;
1193 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
1194 if (RT_FAILURE(rc))
1195 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1196
1197 const char *pszBaseDir = NULL;
1198 const char *pszName = NULL;
1199 RTGETOPTUNION ValueUnion;
1200 int ch;
1201 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1202 {
1203 switch (ch)
1204 {
1205 case 'b':
1206 if (pszBaseDir)
1207 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
1208 pszBaseDir = ValueUnion.psz;
1209 if (!IsValidBaseDir(pszBaseDir))
1210 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
1211 break;
1212
1213 case 'n':
1214 if (pszName)
1215 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
1216 pszName = ValueUnion.psz;
1217 if (!VBoxExtPackIsValidName(pszName))
1218 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
1219 break;
1220
1221 case 'h':
1222 case 'V':
1223 return DoStandardOption(ch);
1224
1225 default:
1226 return RTGetOptPrintError(ch, &ValueUnion);
1227 }
1228 }
1229 if (!pszName)
1230 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
1231 if (!pszBaseDir)
1232 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1233
1234 /*
1235 * Mangle the name so we can construct the directory names.
1236 */
1237 iprt::MiniString *pstrMangledName = VBoxExtPackMangleName(pszName);
1238 if (!pstrMangledName)
1239 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
1240 iprt::MiniString strMangledName(*pstrMangledName);
1241 delete pstrMangledName;
1242
1243 /*
1244 * Ok, down to business.
1245 */
1246 /* Check that it exists. */
1247 char szExtPackDir[RTPATH_MAX];
1248 rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str());
1249 if (RT_FAILURE(rc))
1250 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc);
1251
1252 if (!RTDirExists(szExtPackDir))
1253 {
1254 RTMsgInfo("Extension pack not installed. Nothing to do.");
1255 return RTEXITCODE_SUCCESS;
1256 }
1257
1258 /* Rename the extension pack directory before deleting it to prevent new
1259 VM processes from picking it up. */
1260 char szExtPackUnInstDir[RTPATH_MAX];
1261 rc = RTPathJoin(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszBaseDir, strMangledName.c_str());
1262 if (RT_SUCCESS(rc))
1263 rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst");
1264 if (RT_FAILURE(rc))
1265 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc);
1266
1267 rc = RTDirRename(szExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE);
1268 if (RT_FAILURE(rc))
1269 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the extension pack directory: %Rrc", rc);
1270
1271 /* Recursively delete the directory content. */
1272 RTEXITCODE rcExit = RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/);
1273 if (rcExit == RTEXITCODE_SUCCESS)
1274 RTMsgInfo("Successfully removed extension pack '%s'\n", pszName);
1275
1276 return rcExit;
1277}
1278
1279/**
1280 * Implements the 'cleanup' command.
1281 *
1282 * @returns The program exit code.
1283 * @param argc The number of program arguments.
1284 * @param argv The program arguments.
1285 */
1286static RTEXITCODE DoCleanup(int argc, char **argv)
1287{
1288 /*
1289 * Parse the parameters.
1290 *
1291 * Note! The --base-dir is only for checking that the caller and this help
1292 * applications have the same idea of where things are.
1293 */
1294 static const RTGETOPTDEF s_aOptions[] =
1295 {
1296 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
1297 };
1298 RTGETOPTSTATE GetState;
1299 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
1300 if (RT_FAILURE(rc))
1301 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1302
1303 const char *pszBaseDir = NULL;
1304 RTGETOPTUNION ValueUnion;
1305 int ch;
1306 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1307 {
1308 switch (ch)
1309 {
1310 case 'b':
1311 if (pszBaseDir)
1312 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
1313 pszBaseDir = ValueUnion.psz;
1314 if (!IsValidBaseDir(pszBaseDir))
1315 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
1316 break;
1317
1318 case 'h':
1319 case 'V':
1320 return DoStandardOption(ch);
1321
1322 default:
1323 return RTGetOptPrintError(ch, &ValueUnion);
1324 }
1325 }
1326 if (!pszBaseDir)
1327 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1328
1329 /*
1330 * Ok, down to business.
1331 */
1332 PRTDIR pDir;
1333 rc = RTDirOpen(&pDir, pszBaseDir);
1334 if (RT_FAILURE(rc))
1335 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
1336
1337 uint32_t cCleaned = 0;
1338 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1339 for (;;)
1340 {
1341 RTDIRENTRYEX Entry;
1342 rc = RTDirReadEx(pDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1343 if (RT_FAILURE(rc))
1344 {
1345 if (rc != VERR_NO_MORE_FILES)
1346 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc);
1347 break;
1348 }
1349
1350 /*
1351 * Only directories which conform with our temporary install/uninstall
1352 * naming scheme are candidates for cleaning.
1353 */
1354 if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
1355 && strcmp(Entry.szName, ".") != 0
1356 && strcmp(Entry.szName, "..") != 0)
1357 {
1358 bool fCandidate = false;
1359 char *pszMarker = strstr(Entry.szName, "-_-");
1360 if ( pszMarker
1361 && ( !strcmp(pszMarker, "-_-uninst")
1362 || !strncmp(pszMarker, "-_-inst", sizeof("-_-inst") - 1)))
1363 fCandidate = VBoxExtPackIsValidMangledName(Entry.szName, pszMarker - &Entry.szName[0]);
1364 if (fCandidate)
1365 {
1366 /*
1367 * Recursive delete, safe.
1368 */
1369 char szPath[RTPATH_MAX];
1370 rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName);
1371 if (RT_SUCCESS(rc))
1372 {
1373 RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/);
1374 if (rcExit2 == RTEXITCODE_SUCCESS)
1375 RTMsgInfo("Successfully removed '%s'.", Entry.szName);
1376 else if (rcExit == RTEXITCODE_SUCCESS)
1377 rcExit = rcExit2;
1378 }
1379 else
1380 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
1381 cCleaned++;
1382 }
1383 }
1384 }
1385 RTDirClose(pDir);
1386 if (!cCleaned)
1387 RTMsgInfo("Nothing to clean.");
1388 return rcExit;
1389}
1390
1391
1392
1393int main(int argc, char **argv)
1394{
1395 /*
1396 * Initialize IPRT and check that we're correctly installed.
1397 */
1398 int rc = RTR3Init();
1399 if (RT_FAILURE(rc))
1400 return RTMsgInitFailure(rc);
1401
1402 char szErr[2048];
1403 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, szErr, sizeof(szErr));
1404 if (RT_FAILURE(rc))
1405 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szErr);
1406
1407 /*
1408 * Parse the top level arguments until we find a command.
1409 */
1410 static const RTGETOPTDEF s_aOptions[] =
1411 {
1412#define CMD_INSTALL 1000
1413 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
1414#define CMD_UNINSTALL 1001
1415 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
1416#define CMD_CLEANUP 1002
1417 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
1418 };
1419 RTGETOPTSTATE GetState;
1420 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
1421 if (RT_FAILURE(rc))
1422 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1423 for (;;)
1424 {
1425 RTGETOPTUNION ValueUnion;
1426 int ch = RTGetOpt(&GetState, &ValueUnion);
1427 switch (ch)
1428 {
1429 case 0:
1430 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
1431
1432 case CMD_INSTALL:
1433 return DoInstall( argc - GetState.iNext, argv + GetState.iNext);
1434
1435 case CMD_UNINSTALL:
1436 return DoUninstall(argc - GetState.iNext, argv + GetState.iNext);
1437
1438 case CMD_CLEANUP:
1439 return DoCleanup( argc - GetState.iNext, argv + GetState.iNext);
1440
1441 case 'h':
1442 case 'V':
1443 return DoStandardOption(ch);
1444
1445 default:
1446 return RTGetOptPrintError(ch, &ValueUnion);
1447 }
1448 /* not currently reached */
1449 }
1450 /* not reached */
1451}
1452
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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