VirtualBox

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

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

Manifest comparison.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 51.1 KB
Line 
1/* $Id: VBoxExtPackHelperApp.cpp 34535 2010-11-30 17:46:14Z 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.215389.xyz. 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 1
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(pszAdjName, 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 RTMsgError("Manifest mismatch: %s", szError);
716 else
717 RTMsgError("RTManifestEqualsEx failed: %Rrc", rc);
718 }
719
720 RTManifestRelease(hUnpackManifest);
721 }
722 RTVfsFsStrmRelease(hTarFss);
723
724 return rcExit;
725}
726
727
728
729/**
730 * Validates the extension pack tarball prior to unpacking.
731 *
732 * Operations performed:
733 * - Mandatory files.
734 * - Manifest check.
735 * - Manifest seal check.
736 * - XML check, match name.
737 *
738 * @returns The program exit code.
739 * @param hTarballFile The handle to open the @a pszTarball file.
740 * @param pszExtPackName The name of the extension pack name.
741 * @param pszTarball The name of the tarball in case we have to
742 * complain about something.
743 * @param phValidManifest Where to return the handle to fully validated
744 * the manifest for the extension pack. This
745 * includes all files.
746 *
747 * @todo This function is a bit too long and should be split up if possible.
748 */
749static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball,
750 PRTMANIFEST phValidManifest)
751{
752 *phValidManifest = NIL_RTMANIFEST;
753 RTMsgInfo("Validating extension pack '%s' ('%s')...", pszTarball, pszExtPackName);
754
755 /*
756 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
757 */
758 RTVFSFSSTREAM hTarFss;
759 RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss);
760 if (rcExit != RTEXITCODE_SUCCESS)
761 return rcExit;
762
763 RTMANIFEST hOurManifest;
764 int rc = RTManifestCreate(0 /*fFlags*/, &hOurManifest);
765 if (RT_SUCCESS(rc))
766 {
767 /*
768 * Process the tarball (would be nice to move this to a function).
769 */
770 RTVFSFILE hXmlFile = NIL_RTVFSFILE;
771 RTVFSFILE hManifestFile = NIL_RTVFSFILE;
772 RTVFSFILE hSignatureFile= NIL_RTVFSFILE;
773 for (;;)
774 {
775 /*
776 * Get the next stream object.
777 */
778 char *pszName;
779 RTVFSOBJ hVfsObj;
780 RTVFSOBJTYPE enmType;
781 rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
782 if (RT_FAILURE(rc))
783 {
784 if (rc != VERR_EOF)
785 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc);
786 break;
787 }
788 const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
789
790 /*
791 * Check the type & name validity.
792 */
793 rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj);
794 if (rcExit == RTEXITCODE_SUCCESS)
795 {
796 /*
797 * Check if this is one of the standard files.
798 */
799 PRTVFSFILE phVfsFile;
800 if (!strcmp(pszAdjName, VBOX_EXTPACK_DESCRIPTION_NAME))
801 phVfsFile = &hXmlFile;
802 else if (!strcmp(pszAdjName, VBOX_EXTPACK_MANIFEST_NAME))
803 phVfsFile = &hManifestFile;
804 else if (!strcmp(pszAdjName, VBOX_EXTPACK_SIGNATURE_NAME))
805 phVfsFile = &hSignatureFile;
806 else
807 phVfsFile = NULL;
808 if (phVfsFile)
809 {
810 /*
811 * Make sure it's a file and that it isn't too large.
812 */
813 if (*phVfsFile != NIL_RTVFSFILE)
814 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "There can only be one '%s'", pszAdjName);
815 else if (enmType != RTVFSOBJTYPE_IO_STREAM && enmType != RTVFSOBJTYPE_FILE)
816 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Standard member '%s' is not a file", pszAdjName);
817 else
818 {
819 RTFSOBJINFO ObjInfo;
820 rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
821 if (RT_SUCCESS(rc))
822 {
823 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
824 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Standard member '%s' is not a file", pszAdjName);
825 else if (ObjInfo.cbObject >= _1M)
826 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
827 "Standard member '%s' is too large: %'RU64 bytes (max 1 MB)",
828 pszAdjName, (uint64_t)ObjInfo.cbObject);
829 else
830 {
831 /*
832 * Make an in memory copy of the stream.
833 */
834 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
835 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsFile);
836 if (RT_SUCCESS(rc))
837 {
838 /*
839 * To simplify the code below, replace
840 * hVfsObj with the memorized file.
841 */
842 RTVfsObjRelease(hVfsObj);
843 hVfsObj = RTVfsObjFromFile(*phVfsFile);
844 }
845 else
846 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
847 "RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc", pszName, rc);
848 RTVfsIoStrmRelease(hVfsIos);
849 }
850 }
851 else
852 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
853 }
854 }
855 }
856
857 /*
858 * Add any I/O stream to the manifest
859 */
860 if ( rcExit == RTEXITCODE_SUCCESS
861 && ( enmType == RTVFSOBJTYPE_FILE
862 || enmType == RTVFSOBJTYPE_IO_STREAM))
863 {
864 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
865 rc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256);
866 if (RT_FAILURE(rc))
867 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEntryAddIoStream failed on '%s': %Rrc", pszAdjName, rc);
868 RTVfsIoStrmRelease(hVfsIos);
869 }
870
871 /*
872 * Clean up and break out on failure.
873 */
874 RTVfsObjRelease(hVfsObj);
875 RTStrFree(pszName);
876 if (rcExit != RTEXITCODE_SUCCESS)
877 break;
878 }
879
880 /*
881 * If we've successfully processed the tarball, verify that the
882 * mandatory files are present.
883 */
884 if (rcExit == RTEXITCODE_SUCCESS)
885 {
886 if (hXmlFile == NIL_RTVFSFILE)
887 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Mandator file '%s' is missing", VBOX_EXTPACK_DESCRIPTION_NAME);
888 if (hManifestFile == NIL_RTVFSFILE)
889 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Mandator file '%s' is missing", VBOX_EXTPACK_MANIFEST_NAME);
890 if (hSignatureFile == NIL_RTVFSFILE)
891 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Mandator file '%s' is missing", VBOX_EXTPACK_SIGNATURE_NAME);
892 }
893
894 /*
895 * Check the manifest and it's signature.
896 */
897 if (rcExit == RTEXITCODE_SUCCESS)
898 rcExit = VerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile);
899
900 /*
901 * Check the XML.
902 */
903 if (rcExit == RTEXITCODE_SUCCESS)
904 rcExit = VerifyXml(hXmlFile, pszExtPackName);
905
906 /*
907 * Release objects and stuff.
908 */
909 if (rcExit == RTEXITCODE_SUCCESS)
910 *phValidManifest = hOurManifest;
911 else
912 RTManifestRelease(hOurManifest);
913
914 RTVfsFileRelease(hXmlFile);
915 RTVfsFileRelease(hManifestFile);
916 RTVfsFileRelease(hSignatureFile);
917 }
918 RTVfsFsStrmRelease(hTarFss);
919
920 return rcExit;
921}
922
923
924/**
925 * The 2nd part of the installation process.
926 *
927 * @returns The program exit code.
928 * @param pszBaseDir The base directory.
929 * @param pszCertDir The certificat directory.
930 * @param pszTarball The tarball name.
931 * @param hTarballFile The handle to open the @a pszTarball file.
932 * @param hTarballFileOpt The tarball file handle (optional).
933 * @param pszName The extension pack name.
934 */
935static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball,
936 RTFILE hTarballFile, RTFILE hTarballFileOpt, const char *pszName)
937{
938 /*
939 * Do some basic validation of the tarball file.
940 */
941 RTFSOBJINFO ObjInfo;
942 int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
943 if (RT_FAILURE(rc))
944 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball);
945 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
946 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball);
947
948 if (hTarballFileOpt != NIL_RTFILE)
949 {
950 RTFSOBJINFO ObjInfo2;
951 rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX);
952 if (RT_FAILURE(rc))
953 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc);
954 if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice
955 || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId)
956 return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match");
957 }
958
959 /*
960 * Construct the paths to the two directories we'll be using.
961 */
962 char szFinalPath[RTPATH_MAX];
963 rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszName);
964 if (RT_FAILURE(rc))
965 return RTMsgErrorExit(RTEXITCODE_FAILURE,
966 "Failed to construct the path to the final extension pack directory: %Rrc", rc);
967
968 char szTmpPath[RTPATH_MAX];
969 rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszName);
970 if (RT_SUCCESS(rc))
971 {
972 size_t cchTmpPath = strlen(szTmpPath);
973 RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf());
974 }
975 if (RT_FAILURE(rc))
976 return RTMsgErrorExit(RTEXITCODE_FAILURE,
977 "Failed to construct the path to the temporary extension pack directory: %Rrc", rc);
978
979 /*
980 * Check that they don't exist at this point in time.
981 */
982 rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
983 if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
984 return RTMsgErrorExit(RTEXITCODE_FAILURE, "The extension pack is already installed. You must uninstall the old one first.");
985 if (RT_SUCCESS(rc))
986 return RTMsgErrorExit(RTEXITCODE_FAILURE,
987 "Found non-directory file system object where the extension pack would be installed ('%s')",
988 szFinalPath);
989 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
990 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
991
992 rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
993 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
994 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
995
996 /*
997 * Create the temporary directory and prepare the extension pack within it.
998 * If all checks out correctly, rename it to the final directory.
999 */
1000 RTDirCreate(pszBaseDir, 0755);
1001 rc = RTDirCreate(szTmpPath, 0700);
1002 if (RT_FAILURE(rc))
1003 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath);
1004
1005 RTMANIFEST hValidManifest = NIL_RTMANIFEST;
1006 RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszName, pszTarball, &hValidManifest);
1007 if (rcExit == RTEXITCODE_SUCCESS)
1008 rcExit = UnpackExtPack(hTarballFile, szTmpPath, hValidManifest, pszTarball);
1009 if (rcExit == RTEXITCODE_SUCCESS)
1010 rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName);
1011 if (rcExit == RTEXITCODE_SUCCESS)
1012 rcExit = SetExtPackPermissions(szTmpPath);
1013 RTManifestRelease(hValidManifest);
1014
1015 if (rcExit == RTEXITCODE_SUCCESS)
1016 {
1017 rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
1018 if (RT_SUCCESS(rc))
1019 RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball);
1020 else
1021 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1022 "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')",
1023 rc, szTmpPath, szFinalPath);
1024 }
1025
1026 /*
1027 * Clean up the temporary directory on failure.
1028 */
1029 if (rcExit != RTEXITCODE_SUCCESS)
1030 RemoveExtPackDir(szTmpPath, true /*fTemporary*/);
1031
1032 return rcExit;
1033}
1034
1035
1036/**
1037 * Implements the 'install' command.
1038 *
1039 * @returns The program exit code.
1040 * @param argc The number of program arguments.
1041 * @param argv The program arguments.
1042 */
1043static RTEXITCODE DoInstall(int argc, char **argv)
1044{
1045 /*
1046 * Parse the parameters.
1047 *
1048 * Note! The --base-dir and --cert-dir are only for checking that the
1049 * caller and this help applications have the same idea of where
1050 * things are. Likewise, the --name is for verifying assumptions
1051 * the caller made about the name. The optional --tarball-fd option
1052 * is just for easing the paranoia on the user side.
1053 */
1054 static const RTGETOPTDEF s_aOptions[] =
1055 {
1056 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
1057 { "--cert-dir", 'c', RTGETOPT_REQ_STRING },
1058 { "--name", 'n', RTGETOPT_REQ_STRING },
1059 { "--tarball", 't', RTGETOPT_REQ_STRING },
1060 { "--tarball-fd", 'f', RTGETOPT_REQ_UINT64 }
1061 };
1062 RTGETOPTSTATE GetState;
1063 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
1064 if (RT_FAILURE(rc))
1065 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1066
1067 const char *pszBaseDir = NULL;
1068 const char *pszCertDir = NULL;
1069 const char *pszName = NULL;
1070 const char *pszTarball = NULL;
1071 RTFILE hTarballFileOpt = NIL_RTFILE;
1072 RTGETOPTUNION ValueUnion;
1073 int ch;
1074 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1075 {
1076 switch (ch)
1077 {
1078 case 'b':
1079 if (pszBaseDir)
1080 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
1081 pszBaseDir = ValueUnion.psz;
1082 if (!IsValidBaseDir(pszBaseDir))
1083 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
1084 break;
1085
1086 case 'c':
1087 if (pszCertDir)
1088 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options");
1089 pszCertDir = ValueUnion.psz;
1090 if (!IsValidCertificateDir(pszCertDir))
1091 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir);
1092 break;
1093
1094 case 'n':
1095 if (pszName)
1096 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
1097 pszName = ValueUnion.psz;
1098 if (!VBoxExtPackIsValidName(pszName))
1099 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
1100 break;
1101
1102 case 't':
1103 if (pszTarball)
1104 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options");
1105 pszTarball = ValueUnion.psz;
1106 break;
1107
1108 case 'd':
1109 {
1110 if (hTarballFileOpt != NIL_RTFILE)
1111 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options");
1112 RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64;
1113 if (hNative != ValueUnion.u64)
1114 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64);
1115 rc = RTFileFromNative(&hTarballFileOpt, hNative);
1116 if (RT_FAILURE(rc))
1117 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc);
1118 break;
1119 }
1120
1121 case 'h':
1122 case 'V':
1123 return DoStandardOption(ch);
1124
1125 default:
1126 return RTGetOptPrintError(ch, &ValueUnion);
1127 }
1128 }
1129 if (!pszName)
1130 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
1131 if (!pszBaseDir)
1132 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1133 if (!pszCertDir)
1134 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option");
1135 if (!pszTarball)
1136 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option");
1137
1138 /*
1139 * Ok, down to business.
1140 */
1141 RTFILE hTarballFile;
1142 rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
1143 if (RT_FAILURE(rc))
1144 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball);
1145
1146 RTEXITCODE rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, hTarballFile, hTarballFileOpt, pszName);
1147 RTFileClose(hTarballFile);
1148 return rcExit;
1149}
1150
1151
1152/**
1153 * Implements the 'uninstall' command.
1154 *
1155 * @returns The program exit code.
1156 * @param argc The number of program arguments.
1157 * @param argv The program arguments.
1158 */
1159static RTEXITCODE DoUninstall(int argc, char **argv)
1160{
1161 /*
1162 * Parse the parameters.
1163 *
1164 * Note! The --base-dir is only for checking that the caller and this help
1165 * applications have the same idea of where things are.
1166 */
1167 static const RTGETOPTDEF s_aOptions[] =
1168 {
1169 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
1170 { "--name", 'n', RTGETOPT_REQ_STRING }
1171 };
1172 RTGETOPTSTATE GetState;
1173 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
1174 if (RT_FAILURE(rc))
1175 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1176
1177 const char *pszBaseDir = NULL;
1178 const char *pszName = NULL;
1179 RTGETOPTUNION ValueUnion;
1180 int ch;
1181 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1182 {
1183 switch (ch)
1184 {
1185 case 'b':
1186 if (pszBaseDir)
1187 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
1188 pszBaseDir = ValueUnion.psz;
1189 if (!IsValidBaseDir(pszBaseDir))
1190 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
1191 break;
1192
1193 case 'n':
1194 if (pszName)
1195 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
1196 pszName = ValueUnion.psz;
1197 if (!VBoxExtPackIsValidName(pszName))
1198 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
1199 break;
1200
1201 case 'h':
1202 case 'V':
1203 return DoStandardOption(ch);
1204
1205 default:
1206 return RTGetOptPrintError(ch, &ValueUnion);
1207 }
1208 }
1209 if (!pszName)
1210 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
1211 if (!pszBaseDir)
1212 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1213
1214 /*
1215 * Ok, down to business.
1216 */
1217 /* Check that it exists. */
1218 char szExtPackDir[RTPATH_MAX];
1219 rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, pszName);
1220 if (RT_FAILURE(rc))
1221 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc);
1222
1223 if (!RTDirExists(szExtPackDir))
1224 {
1225 RTMsgInfo("Extension pack not installed. Nothing to do.");
1226 return RTEXITCODE_SUCCESS;
1227 }
1228
1229 /* Rename the extension pack directory before deleting it to prevent new
1230 VM processes from picking it up. */
1231 char szExtPackUnInstDir[RTPATH_MAX];
1232 rc = RTPathJoin(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszBaseDir, pszName);
1233 if (RT_SUCCESS(rc))
1234 rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst");
1235 if (RT_FAILURE(rc))
1236 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc);
1237
1238 rc = RTDirRename(szExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE);
1239 if (RT_FAILURE(rc))
1240 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the extension pack directory: %Rrc", rc);
1241
1242 /* Recursively delete the directory content. */
1243 RTEXITCODE rcExit = RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/);
1244 if (rcExit == RTEXITCODE_SUCCESS)
1245 RTMsgInfo("Successfully removed extension pack '%s'\n", pszName);
1246
1247 return rcExit;
1248}
1249
1250/**
1251 * Implements the 'cleanup' command.
1252 *
1253 * @returns The program exit code.
1254 * @param argc The number of program arguments.
1255 * @param argv The program arguments.
1256 */
1257static RTEXITCODE DoCleanup(int argc, char **argv)
1258{
1259 /*
1260 * Parse the parameters.
1261 *
1262 * Note! The --base-dir is only for checking that the caller and this help
1263 * applications have the same idea of where things are.
1264 */
1265 static const RTGETOPTDEF s_aOptions[] =
1266 {
1267 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
1268 };
1269 RTGETOPTSTATE GetState;
1270 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
1271 if (RT_FAILURE(rc))
1272 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1273
1274 const char *pszBaseDir = NULL;
1275 RTGETOPTUNION ValueUnion;
1276 int ch;
1277 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1278 {
1279 switch (ch)
1280 {
1281 case 'b':
1282 if (pszBaseDir)
1283 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
1284 pszBaseDir = ValueUnion.psz;
1285 if (!IsValidBaseDir(pszBaseDir))
1286 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
1287 break;
1288
1289 case 'h':
1290 case 'V':
1291 return DoStandardOption(ch);
1292
1293 default:
1294 return RTGetOptPrintError(ch, &ValueUnion);
1295 }
1296 }
1297 if (!pszBaseDir)
1298 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1299
1300 /*
1301 * Ok, down to business.
1302 */
1303 PRTDIR pDir;
1304 rc = RTDirOpen(&pDir, pszBaseDir);
1305 if (RT_FAILURE(rc))
1306 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
1307
1308 uint32_t cCleaned = 0;
1309 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1310 for (;;)
1311 {
1312 RTDIRENTRYEX Entry;
1313 rc = RTDirReadEx(pDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1314 if (RT_FAILURE(rc))
1315 {
1316 if (rc != VERR_NO_MORE_FILES)
1317 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc);
1318 break;
1319 }
1320 if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
1321 && strcmp(Entry.szName, ".") != 0
1322 && strcmp(Entry.szName, "..") != 0
1323 && !VBoxExtPackIsValidName(Entry.szName) )
1324 {
1325 char szPath[RTPATH_MAX];
1326 rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName);
1327 if (RT_SUCCESS(rc))
1328 {
1329 RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/);
1330 if (rcExit2 == RTEXITCODE_SUCCESS)
1331 RTMsgInfo("Successfully removed '%s'.", Entry.szName);
1332 else if (rcExit == RTEXITCODE_SUCCESS)
1333 rcExit = rcExit2;
1334 }
1335 else
1336 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
1337 cCleaned++;
1338 }
1339 }
1340 RTDirClose(pDir);
1341 if (!cCleaned)
1342 RTMsgInfo("Nothing to clean.");
1343 return rcExit;
1344}
1345
1346
1347
1348int main(int argc, char **argv)
1349{
1350 /*
1351 * Initialize IPRT and check that we're correctly installed.
1352 */
1353 int rc = RTR3Init();
1354 if (RT_FAILURE(rc))
1355 return RTMsgInitFailure(rc);
1356
1357 char szErr[2048];
1358 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, szErr, sizeof(szErr));
1359 if (RT_FAILURE(rc))
1360 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szErr);
1361
1362 /*
1363 * Parse the top level arguments until we find a command.
1364 */
1365 static const RTGETOPTDEF s_aOptions[] =
1366 {
1367#define CMD_INSTALL 1000
1368 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
1369#define CMD_UNINSTALL 1001
1370 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
1371#define CMD_CLEANUP 1002
1372 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
1373 };
1374 RTGETOPTSTATE GetState;
1375 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
1376 if (RT_FAILURE(rc))
1377 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1378 for (;;)
1379 {
1380 RTGETOPTUNION ValueUnion;
1381 int ch = RTGetOpt(&GetState, &ValueUnion);
1382 switch (ch)
1383 {
1384 case 0:
1385 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
1386
1387 case CMD_INSTALL:
1388 return DoInstall( argc - GetState.iNext, argv + GetState.iNext);
1389
1390 case CMD_UNINSTALL:
1391 return DoUninstall(argc - GetState.iNext, argv + GetState.iNext);
1392
1393 case CMD_CLEANUP:
1394 return DoCleanup( argc - GetState.iNext, argv + GetState.iNext);
1395
1396 case 'h':
1397 case 'V':
1398 return DoStandardOption(ch);
1399
1400 default:
1401 return RTGetOptPrintError(ch, &ValueUnion);
1402 }
1403 /* not currently reached */
1404 }
1405 /* not reached */
1406}
1407
Note: See TracBrowser for help on using the repository browser.

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