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