VirtualBox

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

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

bug fixes

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

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