VirtualBox

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

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

manifest stuff.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 27.8 KB
Line 
1/* $Id: VBoxExtPackHelperApp.cpp 34381 2010-11-25 15:49:11Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root.
4 */
5
6/*
7 * Copyright (C) 2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "include/ExtPackUtil.h"
23
24#include <iprt/buildconfig.h>
25//#include <iprt/ctype.h>
26#include <iprt/dir.h>
27//#include <iprt/env.h>
28#include <iprt/file.h>
29#include <iprt/fs.h>
30#include <iprt/getopt.h>
31#include <iprt/initterm.h>
32#include <iprt/manifest.h>
33#include <iprt/message.h>
34#include <iprt/param.h>
35#include <iprt/path.h>
36//#include <iprt/pipe.h>
37#include <iprt/process.h>
38#include <iprt/string.h>
39#include <iprt/stream.h>
40#include <iprt/vfs.h>
41#include <iprt/zip.h>
42
43#include <VBox/log.h>
44#include <VBox/err.h>
45#include <VBox/sup.h>
46#include <VBox/version.h>
47
48
49#ifdef IN_RT_R3
50/* Override RTAssertShouldPanic to prevent gdb process creation. */
51RTDECL(bool) RTAssertShouldPanic(void)
52{
53 return true;
54}
55#endif
56
57
58
59/**
60 * Handle the special standard options when these are specified after the
61 * command.
62 *
63 * @param ch The option character.
64 */
65static RTEXITCODE DoStandardOption(int ch)
66{
67 switch (ch)
68 {
69 case 'h':
70 {
71 RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n"
72 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
73 "All rights reserved.\n"
74 "\n"
75 "This NOT intended for general use, please use VBoxManage instead\n"
76 "or call the IExtPackManager API directly.\n"
77 "\n"
78 "Usage: %s <command> [options]\n"
79 "Commands:\n"
80 " install --base-dir <dir> --certificate-dir <dir> --name <name> \\\n"
81 " --tarball <tarball> --tarball-fd <fd>\n"
82 " uninstall --base-dir <dir> --name <name>\n"
83 " cleanup --base-dir <dir>\n"
84 , RTProcShortName());
85 return RTEXITCODE_SUCCESS;
86 }
87
88 case 'V':
89 RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
90 return RTEXITCODE_SUCCESS;
91
92 default:
93 AssertFailedReturn(RTEXITCODE_FAILURE);
94 }
95}
96
97
98/**
99 * Checks if the cerficiate directory is valid.
100 *
101 * @returns true if it is valid, false if it isn't.
102 * @param pszCertDir The certificate directory to validate.
103 */
104static bool IsValidCertificateDir(const char *pszCertDir)
105{
106 /*
107 * Just be darn strict for now.
108 */
109 char szCorrect[RTPATH_MAX];
110 int rc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect));
111 if (RT_FAILURE(rc))
112 return false;
113 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR);
114 if (RT_FAILURE(rc))
115 return false;
116
117 return RTPathCompare(szCorrect, pszCertDir) == 0;
118}
119
120
121/**
122 * Checks if the base directory is valid.
123 *
124 * @returns true if it is valid, false if it isn't.
125 * @param pszBaesDir The base directory to validate.
126 */
127static bool IsValidBaseDir(const char *pszBaseDir)
128{
129 /*
130 * Just be darn strict for now.
131 */
132 char szCorrect[RTPATH_MAX];
133 int rc = RTPathAppPrivateArch(szCorrect, sizeof(szCorrect));
134 if (RT_FAILURE(rc))
135 return false;
136 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR);
137 if (RT_FAILURE(rc))
138 return false;
139
140 return RTPathCompare(szCorrect, pszBaseDir) == 0;
141}
142
143
144/**
145 * Cleans up a temporary extension pack directory.
146 *
147 * This is used by 'uninstall', 'cleanup' and in the failure path of 'install'.
148 *
149 * @returns The program exit code.
150 * @param pszDir The directory to clean up. The caller is
151 * responsible for making sure this is valid.
152 * @param fTemporary Whether this is a temporary install directory or
153 * not.
154 */
155static RTEXITCODE RemoveExtPackDir(const char *pszDir, bool fTemporary)
156{
157 /** @todo May have to undo 555 modes here later. */
158 int rc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR);
159 if (RT_FAILURE(rc))
160 return RTMsgErrorExit(RTEXITCODE_FAILURE,
161 "Failed to delete the %sextension pack directory: %Rrc ('%s')",
162 fTemporary ? "temporary " : "", rc, pszDir);
163 return RTEXITCODE_SUCCESS;
164}
165
166
167/**
168 * Rewinds the tarball file handle and creates a gunzip | tar chain that
169 * results in a filesystem stream.
170 *
171 * @returns success or failure, message displayed on failure.
172 * @param hTarballFile The handle to the tarball file.
173 * @param phTarFss Where to return the filesystem stream handle.
174 */
175static RTEXITCODE OpenTarFss(RTFILE hTarballFile, PRTVFSFSSTREAM phTarFss)
176{
177 /*
178 * Rewind the file and set up a VFS chain for it.
179 */
180 int rc = RTFileSeek(hTarballFile, 0, RTFILE_SEEK_BEGIN, NULL);
181 if (RT_FAILURE(rc))
182 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed seeking to the start of the tarball: %Rrc\n", rc);
183
184 RTVFSIOSTREAM hTarballIos;
185 rc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/,
186 &hTarballIos);
187 if (RT_FAILURE(rc))
188 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmFromRTFile failed: %Rrc\n", rc);
189
190 RTVFSIOSTREAM hGunzipIos;
191 rc = RTZipGzipDecompressIoStream(hTarballIos, 0 /*fFlags*/, &hGunzipIos);
192 if (RT_SUCCESS(rc))
193 {
194 RTVFSFSSTREAM hTarFss;
195 rc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss);
196 if (RT_SUCCESS(rc))
197 {
198 RTVfsIoStrmRelease(hGunzipIos);
199 RTVfsIoStrmRelease(hTarballIos);
200 *phTarFss = hTarFss;
201 return RTEXITCODE_SUCCESS;
202 }
203 RTMsgError("RTZipTarFsStreamFromIoStream failed: %Rrc\n", rc);
204 RTVfsIoStrmRelease(hGunzipIos);
205 }
206 else
207 RTMsgError("RTZipGzipDecompressIoStream failed: %Rrc\n", rc);
208 RTVfsIoStrmRelease(hTarballIos);
209 return RTEXITCODE_FAILURE;
210}
211
212
213/**
214 * Sets the permissions of the temporary extension pack directory just before
215 * renaming it.
216 *
217 * By default the temporary directory is only accessible by root, this function
218 * will make it world readable and browseable.
219 *
220 * @returns The program exit code.
221 * @param pszDir The temporary extension pack directory.
222 */
223static RTEXITCODE SetExtPackPermissions(const char *pszDir)
224{
225#if !defined(RT_OS_WINDOWS)
226 int rc = RTPathSetMode(pszDir, 0755);
227 if (RT_FAILURE(rc))
228 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir);
229#else
230 /** @todo */
231#endif
232
233 return RTEXITCODE_SUCCESS;
234}
235
236/**
237 * Validates the extension pack tarball prior to unpacking.
238 *
239 * Operations performed:
240 * - Hardening checks.
241 * - XML validity check.
242 * - Name check (against XML).
243 *
244 * @returns The program exit code.
245 * @param pszDir The directory where the extension pack has been
246 * unpacked.
247 * @param pszName The expected extension pack name.
248 * @param pszTarball The name of the tarball in case we have to
249 * complain about something.
250 */
251static RTEXITCODE ValidateUnpackedExtPack(const char *pszDir, const char *pszTarball, const char *pszName)
252{
253 /** @todo */
254 return RTEXITCODE_SUCCESS;
255}
256
257
258/**
259 * Unpacks the extension pack into the specified directory.
260 *
261 * This will apply ownership and permission changes to all the content, the
262 * exception is @a pszDirDst which will be handled by SetExtPackPermissions.
263 *
264 * @returns The program exit code.
265 * @param hTarballFile The tarball to unpack.
266 * @param pszDirDst Where to unpack it.
267 * @param pszTarball The name of the tarball in case we have to
268 * complain about something.
269 * @todo Needs to take the previous verified manifest as input.
270 */
271static RTEXITCODE UnpackExtPack(RTFILE hTarballFile, const char *pszDirDst, const char *pszTarball)
272{
273 /** @todo */
274 return RTEXITCODE_SUCCESS;
275}
276
277
278/**
279 * Validates the extension pack tarball prior to unpacking.
280 *
281 * Operations performed:
282 * - Manifest check.
283 * - Manifest seal check.
284 * - Mandatory files.
285 *
286 * @returns The program exit code.
287 * @param hTarballFile The handle to open the @a pszTarball file.
288 * @param pszTarball The name of the tarball in case we have to
289 * complain about something.
290 *
291 * @todo Should validate the XML and name.
292 * @todo Needs to return a manifest.
293 */
294static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszTarball)
295{
296 /*
297 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
298 */
299 RTVFSFSSTREAM hTarFss;
300 RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss);
301 if (rcExit != RTEXITCODE_SUCCESS)
302 return rcExit;
303
304 RTMANIFEST hManifest;
305 int rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
306
307 /** @todo continue coding here! */
308 AssertRC(rc);
309
310 return RTEXITCODE_FAILURE;
311}
312
313
314/**
315 * The 2nd part of the installation process.
316 *
317 * @returns The program exit code.
318 * @param pszBaseDir The base directory.
319 * @param pszCertDir The certificat directory.
320 * @param pszTarball The tarball name.
321 * @param hTarballFile The handle to open the @a pszTarball file.
322 * @param hTarballFileOpt The tarball file handle (optional).
323 * @param pszName The extension pack name.
324 */
325static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball,
326 RTFILE hTarballFile, RTFILE hTarballFileOpt, const char *pszName)
327{
328 /*
329 * Do some basic validation of the tarball file.
330 */
331 RTFSOBJINFO ObjInfo;
332 int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
333 if (RT_FAILURE(rc))
334 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball);
335 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
336 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball);
337
338 if (hTarballFileOpt != NIL_RTFILE)
339 {
340 RTFSOBJINFO ObjInfo2;
341 rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX);
342 if (RT_FAILURE(rc))
343 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc);
344 if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice
345 || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId)
346 return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match");
347 }
348
349 /*
350 * Construct the paths to the two directories we'll be using.
351 */
352 char szFinalPath[RTPATH_MAX];
353 rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszName);
354 if (RT_FAILURE(rc))
355 return RTMsgErrorExit(RTEXITCODE_FAILURE,
356 "Failed to construct the path to the final extension pack directory: %Rrc", rc);
357
358 char szTmpPath[RTPATH_MAX];
359 rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszName);
360 if (RT_SUCCESS(rc))
361 {
362 size_t cchTmpPath = strlen(szTmpPath);
363 RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf());
364 }
365 if (RT_FAILURE(rc))
366 return RTMsgErrorExit(RTEXITCODE_FAILURE,
367 "Failed to construct the path to the temporary extension pack directory: %Rrc", rc);
368
369 /*
370 * Check that they don't exist at this point in time.
371 */
372 rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
373 if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
374 return RTMsgErrorExit(RTEXITCODE_FAILURE, "The extension pack is already installed. You must uninstall the old one first.");
375 if (RT_SUCCESS(rc))
376 return RTMsgErrorExit(RTEXITCODE_FAILURE,
377 "Found non-directory file system object where the extension pack would be installed ('%s')",
378 szFinalPath);
379 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
380 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
381
382 rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
383 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
384 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
385
386 /*
387 * Create the temporary directory and prepare the extension pack within it.
388 * If all checks out correctly, rename it to the final directory.
389 */
390 rc = RTDirCreate(szTmpPath, 0700);
391 if (RT_FAILURE(rc))
392 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath);
393
394 RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszTarball);
395 if (rcExit == RTEXITCODE_SUCCESS)
396 rcExit = UnpackExtPack(hTarballFile, szTmpPath, pszTarball);
397 if (rcExit == RTEXITCODE_SUCCESS)
398 rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName);
399 if (rcExit == RTEXITCODE_SUCCESS)
400 rcExit = SetExtPackPermissions(szTmpPath);
401 if (rcExit == RTEXITCODE_SUCCESS)
402 {
403 rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
404 if (RT_SUCCESS(rc))
405 RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball);
406 else
407 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
408 "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')",
409 rc, szTmpPath, szFinalPath);
410 }
411
412 /*
413 * Clean up the temporary directory on failure.
414 */
415 if (rcExit != RTEXITCODE_SUCCESS)
416 RemoveExtPackDir(szTmpPath, true /*fTemporary*/);
417
418 return rcExit;
419}
420
421
422/**
423 * Implements the 'install' command.
424 *
425 * @returns The program exit code.
426 * @param argc The number of program arguments.
427 * @param argv The program arguments.
428 */
429static RTEXITCODE DoInstall(int argc, char **argv)
430{
431 /*
432 * Parse the parameters.
433 *
434 * Note! The --base-dir and --cert-dir are only for checking that the
435 * caller and this help applications have the same idea of where
436 * things are. Likewise, the --name is for verifying assumptions
437 * the caller made about the name. The optional --tarball-fd option
438 * is just for easing the paranoia on the user side.
439 */
440 static const RTGETOPTDEF s_aOptions[] =
441 {
442 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
443 { "--cert-dir", 'c', RTGETOPT_REQ_STRING },
444 { "--name", 'n', RTGETOPT_REQ_STRING },
445 { "--tarball", 't', RTGETOPT_REQ_STRING },
446 { "--tarball-fd", 'f', RTGETOPT_REQ_UINT64 }
447 };
448 RTGETOPTSTATE GetState;
449 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
450 if (RT_FAILURE(rc))
451 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
452
453 const char *pszBaseDir = NULL;
454 const char *pszCertDir = NULL;
455 const char *pszName = NULL;
456 const char *pszTarball = NULL;
457 RTFILE hTarballFileOpt = NIL_RTFILE;
458 RTGETOPTUNION ValueUnion;
459 int ch;
460 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
461 {
462 switch (ch)
463 {
464 case 'b':
465 if (pszBaseDir)
466 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
467 pszBaseDir = ValueUnion.psz;
468 if (!IsValidBaseDir(pszBaseDir))
469 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
470 break;
471
472 case 'c':
473 if (pszCertDir)
474 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options");
475 pszCertDir = ValueUnion.psz;
476 if (!IsValidCertificateDir(pszCertDir))
477 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir);
478 break;
479
480 case 'n':
481 if (pszName)
482 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
483 pszName = ValueUnion.psz;
484 if (!VBoxExtPackIsValidName(pszName))
485 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
486 break;
487
488 case 't':
489 if (pszTarball)
490 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options");
491 pszTarball = ValueUnion.psz;
492 break;
493
494 case 'd':
495 {
496 if (hTarballFileOpt != NIL_RTFILE)
497 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options");
498 RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64;
499 if (hNative != ValueUnion.u64)
500 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64);
501 rc = RTFileFromNative(&hTarballFileOpt, hNative);
502 if (RT_FAILURE(rc))
503 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc);
504 break;
505 }
506
507 case 'h':
508 case 'V':
509 return DoStandardOption(ch);
510
511 default:
512 return RTGetOptPrintError(ch, &ValueUnion);
513 }
514 }
515 if (!pszName)
516 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
517 if (!pszBaseDir)
518 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
519 if (!pszCertDir)
520 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option");
521 if (!pszTarball)
522 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option");
523
524 /*
525 * Ok, down to business.
526 */
527 RTFILE hTarballFile;
528 rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
529 if (RT_FAILURE(rc))
530 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball);
531
532 RTEXITCODE rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, hTarballFile, hTarballFileOpt, pszName);
533 RTFileClose(hTarballFile);
534 return rcExit;
535}
536
537
538/**
539 * Implements the 'uninstall' command.
540 *
541 * @returns The program exit code.
542 * @param argc The number of program arguments.
543 * @param argv The program arguments.
544 */
545static RTEXITCODE DoUninstall(int argc, char **argv)
546{
547 /*
548 * Parse the parameters.
549 *
550 * Note! The --base-dir is only for checking that the caller and this help
551 * applications have the same idea of where things are.
552 */
553 static const RTGETOPTDEF s_aOptions[] =
554 {
555 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
556 { "--name", 'n', RTGETOPT_REQ_STRING }
557 };
558 RTGETOPTSTATE GetState;
559 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
560 if (RT_FAILURE(rc))
561 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
562
563 const char *pszBaseDir = NULL;
564 const char *pszName = NULL;
565 RTGETOPTUNION ValueUnion;
566 int ch;
567 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
568 {
569 switch (ch)
570 {
571 case 'b':
572 if (pszBaseDir)
573 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
574 pszBaseDir = ValueUnion.psz;
575 if (!IsValidBaseDir(pszBaseDir))
576 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
577 break;
578
579 case 'n':
580 if (pszName)
581 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
582 pszName = ValueUnion.psz;
583 if (!VBoxExtPackIsValidName(pszName))
584 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
585 break;
586
587 case 'h':
588 case 'V':
589 return DoStandardOption(ch);
590
591 default:
592 return RTGetOptPrintError(ch, &ValueUnion);
593 }
594 }
595 if (!pszName)
596 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
597 if (!pszBaseDir)
598 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
599
600 /*
601 * Ok, down to business.
602 */
603 /* Check that it exists. */
604 char szExtPackDir[RTPATH_MAX];
605 rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, pszName);
606 if (RT_FAILURE(rc))
607 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc);
608
609 if (!RTDirExists(szExtPackDir))
610 {
611 RTMsgInfo("Extension pack not installed. Nothing to do.");
612 return RTEXITCODE_SUCCESS;
613 }
614
615 /* Rename the extension pack directory before deleting it to prevent new
616 VM processes from picking it up. */
617 char szExtPackUnInstDir[RTPATH_MAX];
618 rc = RTPathJoin(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszBaseDir, pszName);
619 if (RT_SUCCESS(rc))
620 rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst");
621 if (RT_FAILURE(rc))
622 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc);
623
624 rc = RTDirRename(szExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE);
625 if (RT_FAILURE(rc))
626 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the extension pack directory: %Rrc", rc);
627
628 /* Recursively delete the directory content. */
629 RTEXITCODE rcExit = RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/);
630 if (rcExit == RTEXITCODE_SUCCESS)
631 RTMsgInfo("Successfully removed extension pack '%s'\n", pszName);
632
633 return rcExit;
634}
635
636/**
637 * Implements the 'cleanup' command.
638 *
639 * @returns The program exit code.
640 * @param argc The number of program arguments.
641 * @param argv The program arguments.
642 */
643static RTEXITCODE DoCleanup(int argc, char **argv)
644{
645 /*
646 * Parse the parameters.
647 *
648 * Note! The --base-dir is only for checking that the caller and this help
649 * applications have the same idea of where things are.
650 */
651 static const RTGETOPTDEF s_aOptions[] =
652 {
653 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
654 };
655 RTGETOPTSTATE GetState;
656 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
657 if (RT_FAILURE(rc))
658 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
659
660 const char *pszBaseDir = NULL;
661 RTGETOPTUNION ValueUnion;
662 int ch;
663 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
664 {
665 switch (ch)
666 {
667 case 'b':
668 if (pszBaseDir)
669 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
670 pszBaseDir = ValueUnion.psz;
671 if (!IsValidBaseDir(pszBaseDir))
672 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
673 break;
674
675 case 'h':
676 case 'V':
677 return DoStandardOption(ch);
678
679 default:
680 return RTGetOptPrintError(ch, &ValueUnion);
681 }
682 }
683 if (!pszBaseDir)
684 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
685
686 /*
687 * Ok, down to business.
688 */
689 PRTDIR pDir;
690 rc = RTDirOpen(&pDir, pszBaseDir);
691 if (RT_FAILURE(rc))
692 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
693
694 uint32_t cCleaned = 0;
695 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
696 for (;;)
697 {
698 RTDIRENTRYEX Entry;
699 rc = RTDirReadEx(pDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
700 if (RT_FAILURE(rc))
701 {
702 if (rc != VERR_NO_MORE_FILES)
703 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc);
704 break;
705 }
706 if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
707 && strcmp(Entry.szName, ".") != 0
708 && strcmp(Entry.szName, "..") != 0
709 && !VBoxExtPackIsValidName(Entry.szName) )
710 {
711 char szPath[RTPATH_MAX];
712 rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName);
713 if (RT_SUCCESS(rc))
714 {
715 RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/);
716 if (rcExit2 == RTEXITCODE_SUCCESS)
717 RTMsgInfo("Successfully removed '%s'.", Entry.szName);
718 else if (rcExit == RTEXITCODE_SUCCESS)
719 rcExit = rcExit2;
720 }
721 else
722 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
723 cCleaned++;
724 }
725 }
726 RTDirClose(pDir);
727 if (!cCleaned)
728 RTMsgInfo("Nothing to clean.");
729 return rcExit;
730}
731
732
733
734int main(int argc, char **argv)
735{
736 /*
737 * Initialize IPRT and check that we're correctly installed.
738 */
739 int rc = RTR3Init();
740 if (RT_FAILURE(rc))
741 return RTMsgInitFailure(rc);
742
743 char szErr[2048];
744 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, szErr, sizeof(szErr));
745 if (RT_FAILURE(rc))
746 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szErr);
747
748 /*
749 * Parse the top level arguments until we find a command.
750 */
751 static const RTGETOPTDEF s_aOptions[] =
752 {
753#define CMD_INSTALL 1000
754 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
755#define CMD_UNINSTALL 1001
756 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
757#define CMD_CLEANUP 1002
758 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
759 };
760 RTGETOPTSTATE GetState;
761 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
762 if (RT_FAILURE(rc))
763 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
764 for (;;)
765 {
766 RTGETOPTUNION ValueUnion;
767 int ch = RTGetOpt(&GetState, &ValueUnion);
768 switch (ch)
769 {
770 case 0:
771 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
772
773 case CMD_INSTALL:
774 return DoInstall( argc - GetState.iNext, argv + GetState.iNext);
775
776 case CMD_UNINSTALL:
777 return DoUninstall(argc - GetState.iNext, argv + GetState.iNext);
778
779 case CMD_CLEANUP:
780 return DoCleanup( argc - GetState.iNext, argv + GetState.iNext);
781
782 case 'h':
783 case 'V':
784 return DoStandardOption(ch);
785
786 default:
787 return RTGetOptPrintError(ch, &ValueUnion);
788 }
789 /* not currently reached */
790 }
791 /* not reached */
792}
793
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