VirtualBox

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

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

Made extension pack installation work.

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