VirtualBox

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

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

Unused headers.

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