VBoxExtPackHelperApp.cpp revision 20d5cd0381367988b3d67ba5ff2b27dc109dfe2d
/* $Id$ */
/** @file
* VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root.
*/
/*
* Copyright (C) 2010 Oracle Corporation
*
* Oracle Corporation confidential
* All rights reserved
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "include/ExtPackUtil.h"
#include <iprt/buildconfig.h>
#include <iprt/initterm.h>
/* Override RTAssertShouldPanic to prevent gdb process creation. */
RTDECL(bool) RTAssertShouldPanic(void)
{
return true;
}
/**
* Handle the special standard options when these are specified after the
* command.
*
* @param ch The option character.
*/
{
switch (ch)
{
case 'h':
{
"All rights reserved.\n"
"\n"
"This NOT intended for general use, please use VBoxManage instead\n"
"or call the IExtPackManager API directly.\n"
"\n"
"Usage: %s <command> [options]\n"
"Commands:\n"
" install --base-dir <dir> --certificate-dir <dir> --name <name> \\\n"
" --tarball <tarball> --tarball-fd <fd>\n"
" uninstall --base-dir <dir> --name <name>\n"
, RTProcShortName());
return RTEXITCODE_SUCCESS;
}
case 'V':
return RTEXITCODE_SUCCESS;
default:
}
}
/**
* Checks if the cerficiate directory is valid.
*
* @returns true if it is valid, false if it isn't.
* @param pszCertDir The certificate directory to validate.
*/
static bool IsValidCertificateDir(const char *pszCertDir)
{
/*
* Just be darn strict for now.
*/
char szCorrect[RTPATH_MAX];
if (RT_FAILURE(rc))
return false;
if (RT_FAILURE(rc))
return false;
}
/**
* Checks if the base directory is valid.
*
* @returns true if it is valid, false if it isn't.
* @param pszBaesDir The base directory to validate.
*/
static bool IsValidBaseDir(const char *pszBaseDir)
{
/*
* Just be darn strict for now.
*/
char szCorrect[RTPATH_MAX];
if (RT_FAILURE(rc))
return false;
if (RT_FAILURE(rc))
return false;
}
/**
* Cleans up a temporary extension pack directory.
*
* This is used by 'uninstall', 'cleanup' and in the failure path of 'install'.
*
* @returns The program exit code.
* @param pszDir The directory to clean up. The caller is
* responsible for making sure this is valid.
* @param fTemporary Whether this is a temporary install directory or
* not.
*/
{
/** @todo May have to undo 555 modes here later. */
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"Failed to delete the %sextension pack directory: %Rrc ('%s')",
return RTEXITCODE_SUCCESS;
}
/**
* Sets the permissions of the temporary extension pack directory just before
* renaming it.
*
* By default the temporary directory is only accessible by root, this function
* will make it world readable and browseable.
*
* @returns The program exit code.
* @param pszDir The temporary extension pack directory.
*/
{
#if !defined(RT_OS_WINDOWS)
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir);
#else
/** @todo */
#endif
return RTEXITCODE_SUCCESS;
}
/**
* Validates the extension pack.
*
* Operations performed:
* - Manifest seal check.
* - Manifest check.
* - Recursive hardening check.
* - XML validity check.
* - Name check (against XML).
*
* @returns The program exit code.
* @param pszDir The directory where the extension pack has been
* unpacked.
* @param pszName The expected extension pack name.
* @param pszTarball The name of the tarball in case we have to
* complain about something.
*/
{
/** @todo */
return RTEXITCODE_SUCCESS;
}
/**
* Unpacks the extension pack into the specified directory.
*
* This will apply ownership and permission changes to all the content, the
* exception is @a pszDirDst which will be handled by SetExtPackPermissions.
*
* @returns The program exit code.
* @param hTarballFile The tarball to unpack.
* @param pszDirDst Where to unpack it.
* @param pszTarball The name of the tarball in case we have to
* complain about something.
*/
{
/** @todo */
return RTEXITCODE_SUCCESS;
}
/**
* The 2nd part of the installation process.
*
* @returns The program exit code.
* @param pszBaseDir The base directory.
* @param pszCertDir The certificat directory.
* @param pszTarball The tarball name.
* @param hTarballFile The handle to open the @a pszTarball file.
* @param hTarballFileOpt The tarball file handle (optional).
* @param pszName The extension pack name.
*/
static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball,
{
/*
* Do some basic validation of the tarball file.
*/
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball);
if (hTarballFileOpt != NIL_RTFILE)
{
if (RT_FAILURE(rc))
}
/*
* Construct the paths to the two directories we'll be using.
*/
char szFinalPath[RTPATH_MAX];
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"Failed to construct the path to the final extension pack directory: %Rrc", rc);
char szTmpPath[RTPATH_MAX];
if (RT_SUCCESS(rc))
{
RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf());
}
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"Failed to construct the path to the temporary extension pack directory: %Rrc", rc);
/*
* Check that they don't exist at this point in time.
*/
return RTMsgErrorExit(RTEXITCODE_FAILURE, "The extension pack is already installed. You must uninstall the old one first.");
if (RT_SUCCESS(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"Found non-directory file system object where the extension pack would be installed ('%s')",
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
/*
* Create the temporary directory and prepare the extension pack within it.
* If all checks out correctly, rename it to the final directory.
*/
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath);
if (rcExit == RTEXITCODE_SUCCESS)
if (rcExit == RTEXITCODE_SUCCESS)
if (rcExit == RTEXITCODE_SUCCESS)
{
if (RT_SUCCESS(rc))
else
"Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')",
}
/*
* Clean up the temporary directory on failure.
*/
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
}
/**
* Implements the 'install' command.
*
* @returns The program exit code.
* @param argc The number of program arguments.
* @param argv The program arguments.
*/
{
/*
* Parse the parameters.
*
* Note! The --base-dir and --cert-dir are only for checking that the
* caller and this help applications have the same idea of where
* things are. Likewise, the --name is for verifying assumptions
* the caller made about the name. The optional --tarball-fd option
* is just for easing the paranoia on the user side.
*/
static const RTGETOPTDEF s_aOptions[] =
{
};
if (RT_FAILURE(rc))
const char *pszBaseDir = NULL;
const char *pszCertDir = NULL;
const char *pszTarball = NULL;
int ch;
{
switch (ch)
{
case 'b':
if (pszBaseDir)
if (!IsValidBaseDir(pszBaseDir))
break;
case 'c':
if (pszCertDir)
if (!IsValidCertificateDir(pszCertDir))
break;
case 'n':
if (pszName)
if (!VBoxExtPackIsValidName(pszName))
break;
case 't':
if (pszTarball)
break;
case 'd':
{
if (hTarballFileOpt != NIL_RTFILE)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64);
if (RT_FAILURE(rc))
break;
}
case 'h':
case 'V':
return DoStandardOption(ch);
default:
}
}
if (!pszName)
if (!pszBaseDir)
if (!pszCertDir)
if (!pszTarball)
/*
* Ok, down to business.
*/
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball);
RTEXITCODE rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, hTarballFile, hTarballFileOpt, pszName);
return rcExit;
}
/**
* Implements the 'uninstall' command.
*
* @returns The program exit code.
* @param argc The number of program arguments.
* @param argv The program arguments.
*/
{
/*
* Parse the parameters.
*
* Note! The --base-dir is only for checking that the caller and this help
* applications have the same idea of where things are.
*/
static const RTGETOPTDEF s_aOptions[] =
{
};
if (RT_FAILURE(rc))
const char *pszBaseDir = NULL;
int ch;
{
switch (ch)
{
case 'b':
if (pszBaseDir)
if (!IsValidBaseDir(pszBaseDir))
break;
case 'n':
if (pszName)
if (!VBoxExtPackIsValidName(pszName))
break;
case 'h':
case 'V':
return DoStandardOption(ch);
default:
}
}
if (!pszName)
if (!pszBaseDir)
/*
* Ok, down to business.
*/
/* Check that it exists. */
char szExtPackDir[RTPATH_MAX];
if (RT_FAILURE(rc))
if (!RTDirExists(szExtPackDir))
{
RTMsgInfo("Extension pack not installed. Nothing to do.");
return RTEXITCODE_SUCCESS;
}
/* Rename the extension pack directory before deleting it to prevent new
VM processes from picking it up. */
char szExtPackUnInstDir[RTPATH_MAX];
if (RT_SUCCESS(rc))
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the extension pack directory: %Rrc", rc);
/* Recursively delete the directory content. */
if (rcExit == RTEXITCODE_SUCCESS)
return rcExit;
}
/**
* Implements the 'cleanup' command.
*
* @returns The program exit code.
* @param argc The number of program arguments.
* @param argv The program arguments.
*/
{
/*
* Parse the parameters.
*
* Note! The --base-dir is only for checking that the caller and this help
* applications have the same idea of where things are.
*/
static const RTGETOPTDEF s_aOptions[] =
{
};
if (RT_FAILURE(rc))
const char *pszBaseDir = NULL;
int ch;
{
switch (ch)
{
case 'b':
if (pszBaseDir)
if (!IsValidBaseDir(pszBaseDir))
break;
case 'h':
case 'V':
return DoStandardOption(ch);
default:
}
}
if (!pszBaseDir)
/*
* Ok, down to business.
*/
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
for (;;)
{
if (RT_FAILURE(rc))
{
if (rc != VERR_NO_MORE_FILES)
break;
}
{
char szPath[RTPATH_MAX];
if (RT_SUCCESS(rc))
{
if (rcExit2 == RTEXITCODE_SUCCESS)
else if (rcExit == RTEXITCODE_SUCCESS)
}
else
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
cCleaned++;
}
}
if (!cCleaned)
RTMsgInfo("Nothing to clean.");
return rcExit;
}
{
/*
* Initialize IPRT and check that we're correctly installed.
*/
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
char szErr[2048];
if (RT_FAILURE(rc))
/*
* Parse the top level arguments until we find a command.
*/
static const RTGETOPTDEF s_aOptions[] =
{
#define CMD_INSTALL 1000
#define CMD_UNINSTALL 1001
#define CMD_CLEANUP 1002
};
if (RT_FAILURE(rc))
for (;;)
{
switch (ch)
{
case 0:
case CMD_INSTALL:
case CMD_UNINSTALL:
case CMD_CLEANUP:
case 'h':
case 'V':
return DoStandardOption(ch);
default:
}
/* not currently reached */
}
/* not reached */
}